fix(缺陷管理): 修复富文本图片同步第三方平台问题
--bug=1036946 --user=宋昌昌 【缺陷管理】MS关联禅道,创建缺陷,缺陷内容中粘贴图片,图片没有同步到禅道上 https://www.tapd.cn/55049933/s/1476765 --bug=1036968 --user=宋昌昌 【缺陷管理】集成zentao-zentao缺陷带图片-同步到ms-未显示图片 https://www.tapd.cn/55049933/s/1476792 --bug=1037244 --user=宋昌昌 【缺陷管理】集成zentao平台,缺陷中缺陷内容包含直接粘贴的图片在zentao无法显示 https://www.tapd.cn/55049933/s/1476793
This commit is contained in:
parent
70af6c3718
commit
75ee36b0ea
|
@ -20,4 +20,9 @@ public class PlatformBugDTO extends MsSyncBugDTO {
|
|||
* 缺陷同步所需处理的平台自定义字段ID(同步第三方平台到MS时需要, 非默认模板时使用)
|
||||
*/
|
||||
private List<PlatformCustomFieldItemDTO> needSyncCustomFields;
|
||||
|
||||
/**
|
||||
* 缺陷同步需要下载的第三方富文本图片文件Key
|
||||
*/
|
||||
private List<String> richTextImageKeys;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,9 @@ import io.metersphere.plugin.platform.dto.reponse.PlatformStatusDTO;
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
|
@ -27,4 +30,8 @@ public class PlatformBugUpdateRequest extends PlatformBugDTO {
|
|||
* 第三方平台缺陷的状态
|
||||
*/
|
||||
private PlatformStatusDTO transitions;
|
||||
/**
|
||||
* MS平台缺陷富文本文件集合
|
||||
*/
|
||||
private Map<String, File> richFileMap = new HashMap<>();
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ public class DefaultRepositoryDir {
|
|||
private static final String PROJECT_ENV_SSL_DIR = PROJECT_DIR + "/environment/%s";
|
||||
private static final String PROJECT_FUNCTIONAL_CASE_DIR = PROJECT_DIR + "/functional-case/%s";
|
||||
private static final String PROJECT_FUNCTIONAL_CASE_PREVIEW_DIR = PROJECT_DIR + "/functional-case/preview/%s";
|
||||
private static final String PROJECT_BUG_PREVIEW_DIR = PROJECT_DIR + "/bug/preview/%s";
|
||||
private static final String PROJECT_FILE_MANAGEMENT_DIR = PROJECT_DIR + "/file-management";
|
||||
private static final String PROJECT_FILE_MANAGEMENT_PREVIEW_DIR = PROJECT_DIR + "/file-management/preview";
|
||||
/**
|
||||
|
@ -90,6 +91,10 @@ public class DefaultRepositoryDir {
|
|||
return String.format(PROJECT_FUNCTIONAL_CASE_PREVIEW_DIR, projectId, functionalCaseId);
|
||||
}
|
||||
|
||||
public static String getBugPreviewDir(String projectId, String bugId) {
|
||||
return String.format(PROJECT_BUG_PREVIEW_DIR, projectId, bugId);
|
||||
}
|
||||
|
||||
public static String getFileManagementDir(String projectId) {
|
||||
return String.format(PROJECT_FILE_MANAGEMENT_DIR, projectId);
|
||||
}
|
||||
|
|
|
@ -60,4 +60,7 @@ public class BugEditRequest implements Serializable {
|
|||
|
||||
@Schema(description = "复制的附件")
|
||||
private List<BugFileDTO> copyFiles;
|
||||
|
||||
@Schema(description = "富文本临时文件ID")
|
||||
private List<String> richTextTmpFileIds;
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@ public enum BugAttachmentSourceType {
|
|||
/**
|
||||
* 缺陷内容
|
||||
*/
|
||||
CONTENT
|
||||
RICH_TEXT
|
||||
}
|
||||
|
|
|
@ -32,16 +32,16 @@ import io.metersphere.sdk.constants.LocalRepositoryDir;
|
|||
import io.metersphere.sdk.constants.StorageType;
|
||||
import io.metersphere.sdk.exception.MSException;
|
||||
import io.metersphere.sdk.file.FileCenter;
|
||||
import io.metersphere.sdk.file.FileCopyRequest;
|
||||
import io.metersphere.sdk.file.FileRepository;
|
||||
import io.metersphere.sdk.file.FileRequest;
|
||||
import io.metersphere.sdk.util.FileAssociationSourceUtil;
|
||||
import io.metersphere.sdk.util.LogUtils;
|
||||
import io.metersphere.sdk.util.MsFileUtils;
|
||||
import io.metersphere.sdk.util.Translator;
|
||||
import io.metersphere.sdk.util.*;
|
||||
import io.metersphere.system.dto.sdk.OptionDTO;
|
||||
import io.metersphere.system.log.constants.OperationLogModule;
|
||||
import io.metersphere.system.mapper.BaseUserMapper;
|
||||
import io.metersphere.system.uid.IDGenerator;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
@ -615,4 +615,130 @@ public class BugAttachmentService {
|
|||
}
|
||||
return bugLocalAttachments.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转存临时文件
|
||||
* @param bugId 缺陷ID
|
||||
* @param projectId 项目ID
|
||||
* @param uploadFileIds 上传的文件ID集合
|
||||
* @param userId 用户ID
|
||||
* @param source 文件来源
|
||||
*/
|
||||
public void transferTmpFile(String bugId, String projectId, List<String> uploadFileIds, String userId, String source) {
|
||||
if (org.apache.commons.collections.CollectionUtils.isEmpty(uploadFileIds)) {
|
||||
return;
|
||||
}
|
||||
//过滤已上传过的
|
||||
BugLocalAttachmentExample bugLocalAttachmentExample = new BugLocalAttachmentExample();
|
||||
bugLocalAttachmentExample.createCriteria().andBugIdEqualTo(bugId).andFileIdIn(uploadFileIds).andSourceEqualTo(source);
|
||||
List<BugLocalAttachment> existFiles = bugLocalAttachmentMapper.selectByExample(bugLocalAttachmentExample);
|
||||
List<String> existFileIds = existFiles.stream().map(BugLocalAttachment::getFileId).distinct().toList();
|
||||
List<String> fileIds = uploadFileIds.stream().filter(t -> !existFileIds.contains(t) && StringUtils.isNotBlank(t)).toList();
|
||||
if (CollectionUtils.isEmpty(fileIds)) {
|
||||
return;
|
||||
}
|
||||
// 处理本地上传文件
|
||||
FileRepository defaultRepository = FileCenter.getDefaultRepository();
|
||||
String systemTempDir = DefaultRepositoryDir.getSystemTempDir();
|
||||
// 添加文件与功能用例的关联关系
|
||||
Map<String, String> addFileMap = new HashMap<>();
|
||||
LogUtils.info("开始上传副文本里的附件");
|
||||
List<BugLocalAttachment> localAttachments = fileIds.stream().map(fileId -> {
|
||||
BugLocalAttachment localAttachment = new BugLocalAttachment();
|
||||
String fileName = getTempFileNameByFileId(fileId);
|
||||
localAttachment.setId(IDGenerator.nextStr());
|
||||
localAttachment.setBugId(bugId);
|
||||
localAttachment.setFileId(fileId);
|
||||
localAttachment.setFileName(fileName);
|
||||
localAttachment.setSource(source);
|
||||
long fileSize = 0;
|
||||
try {
|
||||
FileCopyRequest fileCopyRequest = new FileCopyRequest();
|
||||
fileCopyRequest.setFolder(systemTempDir + "/" + fileId);
|
||||
fileCopyRequest.setFileName(fileName);
|
||||
fileSize = defaultRepository.getFileSize(fileCopyRequest);
|
||||
} catch (Exception e) {
|
||||
LogUtils.error("读取文件大小失败");
|
||||
}
|
||||
localAttachment.setSize(fileSize);
|
||||
localAttachment.setCreateUser(userId);
|
||||
localAttachment.setCreateTime(System.currentTimeMillis());
|
||||
addFileMap.put(fileId, fileName);
|
||||
return localAttachment;
|
||||
}).toList();
|
||||
bugLocalAttachmentMapper.batchInsert(localAttachments);
|
||||
// 上传文件到对象存储
|
||||
LogUtils.info("upload to minio start");
|
||||
uploadFileResource(DefaultRepositoryDir.getBugDir(projectId, bugId), addFileMap, projectId, bugId);
|
||||
LogUtils.info("upload to minio end");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件ID,查询MINIO中对应目录下的文件名称
|
||||
*/
|
||||
public String getTempFileNameByFileId(String fileId) {
|
||||
FileRepository defaultRepository = FileCenter.getDefaultRepository();
|
||||
try {
|
||||
FileRequest fileRequest = new FileRequest();
|
||||
fileRequest.setFolder(DefaultRepositoryDir.getSystemTempDir() + "/" + fileId);
|
||||
List<String> folderFileNames = defaultRepository.getFolderFileNames(fileRequest);
|
||||
if (CollectionUtils.isEmpty(folderFileNames)) {
|
||||
return null;
|
||||
}
|
||||
String[] pathSplit = folderFileNames.get(0).split("/");
|
||||
return pathSplit[pathSplit.length - 1];
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到资源目录
|
||||
* @param folder 文件夹
|
||||
* @param addFileMap 文件ID与文件名映射
|
||||
* @param projectId 项目ID
|
||||
* @param bugId 缺陷ID
|
||||
*/
|
||||
public void uploadFileResource(String folder, Map<String, String> addFileMap, String projectId, String bugId) {
|
||||
if (MapUtils.isEmpty(addFileMap)) {
|
||||
return;
|
||||
}
|
||||
FileRepository defaultRepository = FileCenter.getDefaultRepository();
|
||||
for (String fileId : addFileMap.keySet()) {
|
||||
String systemTempDir = DefaultRepositoryDir.getSystemTempDir();
|
||||
try {
|
||||
String fileName = addFileMap.get(fileId);
|
||||
if (StringUtils.isEmpty(fileName)) {
|
||||
continue;
|
||||
}
|
||||
// 按ID建文件夹,避免文件名重复
|
||||
FileCopyRequest fileCopyRequest = new FileCopyRequest();
|
||||
fileCopyRequest.setCopyFolder(systemTempDir + "/" + fileId);
|
||||
fileCopyRequest.setCopyfileName(fileName);
|
||||
fileCopyRequest.setFileName(fileName);
|
||||
fileCopyRequest.setFolder(folder + "/" + fileId);
|
||||
// 将文件从临时目录复制到资源目录
|
||||
defaultRepository.copyFile(fileCopyRequest);
|
||||
|
||||
String fileType = StringUtils.substring(fileName, fileName.lastIndexOf(".") + 1);
|
||||
if (TempFileUtils.isImage(fileType)) {
|
||||
//图片文件自动生成预览图
|
||||
byte[] file = defaultRepository.getFile(fileCopyRequest);
|
||||
byte[] previewImg = TempFileUtils.compressPic(file);
|
||||
fileCopyRequest.setFolder(DefaultRepositoryDir.getBugPreviewDir(projectId, bugId) + "/" + fileId);
|
||||
fileCopyRequest.setStorage(StorageType.MINIO.toString());
|
||||
fileService.upload(previewImg, fileCopyRequest);
|
||||
}
|
||||
// 删除临时文件
|
||||
fileCopyRequest.setFolder(systemTempDir + "/" + fileId);
|
||||
fileCopyRequest.setFileName(fileName);
|
||||
defaultRepository.delete(fileCopyRequest);
|
||||
} catch (Exception e) {
|
||||
LogUtils.error("上传副文本文件失败:{}",e);
|
||||
throw new MSException(Translator.get("file_upload_fail"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,6 +198,8 @@ public class BugService {
|
|||
* 2. 第三方平台缺陷需调用插件同步缺陷至其他平台(自定义字段需处理);
|
||||
* 3. 保存MS缺陷(基础字段, 自定义字段)
|
||||
* 4. 处理附件(第三方平台缺陷需异步调用接口同步附件至第三方)
|
||||
* 5. 处理富文本临时文件
|
||||
* 6. 处理缺陷-用例关联关系
|
||||
*/
|
||||
String platformName = projectApplicationService.getPlatformName(request.getProjectId());
|
||||
PlatformBugUpdateDTO platformBug = null;
|
||||
|
@ -228,6 +230,8 @@ public class BugService {
|
|||
handleAndSaveCustomFields(request, isUpdate);
|
||||
// 处理附件
|
||||
handleAndSaveAttachments(request, files, currentUser, platformName, platformBug);
|
||||
// 处理富文本临时文件
|
||||
handleRichTextTmpFile(request, bug.getId(), currentUser);
|
||||
// 处理用例关联关系
|
||||
handleAndSaveCaseRelation(request, isUpdate, bug, currentUser);
|
||||
|
||||
|
@ -596,6 +600,38 @@ public class BugService {
|
|||
|
||||
// 批量更新缺陷
|
||||
updateBugs.forEach(updateBug -> {
|
||||
if (CollectionUtils.isNotEmpty(updateBug.getRichTextImageKeys())) {
|
||||
// 同步第三方的富文本文件
|
||||
updateBug.getRichTextImageKeys().forEach(key -> {
|
||||
platform.getAttachmentContent(key, (in) -> {
|
||||
if (in == null) {
|
||||
return;
|
||||
}
|
||||
String fileId = IDGenerator.nextStr();
|
||||
byte[] bytes;
|
||||
try {
|
||||
// upload platform attachment to minio
|
||||
bytes = in.readAllBytes();
|
||||
FileCenter.getDefaultRepository().saveFile(bytes, buildBugFileRequest(updateBug.getProjectId(), updateBug.getId(), fileId, "image.png"));
|
||||
} catch (Exception e) {
|
||||
throw new MSException(e.getMessage());
|
||||
}
|
||||
// save bug attachment relation
|
||||
BugLocalAttachment localAttachment = new BugLocalAttachment();
|
||||
localAttachment.setId(IDGenerator.nextStr());
|
||||
localAttachment.setBugId(updateBug.getId());
|
||||
localAttachment.setFileId(fileId);
|
||||
localAttachment.setFileName("image.png");
|
||||
localAttachment.setSize((long) bytes.length);
|
||||
localAttachment.setCreateTime(System.currentTimeMillis());
|
||||
localAttachment.setCreateUser("admin");
|
||||
localAttachment.setSource(BugAttachmentSourceType.RICH_TEXT.name());
|
||||
bugLocalAttachmentMapper.insert(localAttachment);
|
||||
// 替换富文本中的临时URL
|
||||
updateBug.setDescription(updateBug.getDescription().replace("alt=\"" + key + "\"", "src=\"/attachment/download/file/" + updateBug.getProjectId() + "/" + fileId + "/true\""));
|
||||
});
|
||||
});
|
||||
}
|
||||
updateBug.setCreateUser(null);
|
||||
Bug bug = new Bug();
|
||||
BeanUtils.copyBean(bug, updateBug);
|
||||
|
@ -1093,6 +1129,16 @@ public class BugService {
|
|||
return uploadPlatformAttachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理富文本临时文件
|
||||
* @param request 请求参数
|
||||
* @param bugId 缺陷ID
|
||||
* @param currentUser 当前用户
|
||||
*/
|
||||
private void handleRichTextTmpFile(BugEditRequest request, String bugId, String currentUser) {
|
||||
bugAttachmentService.transferTmpFile(bugId, request.getProjectId(), request.getRichTextTmpFileIds(), currentUser, BugAttachmentSourceType.RICH_TEXT.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理并保存缺陷用例关联关系
|
||||
* @param request 请求参数
|
||||
|
@ -1132,6 +1178,20 @@ public class BugService {
|
|||
// TITLE, DESCRIPTION 传到平台插件处理
|
||||
platformRequest.setTitle(request.getTitle());
|
||||
platformRequest.setDescription(request.getDescription());
|
||||
if (CollectionUtils.isNotEmpty(request.getRichTextTmpFileIds())) {
|
||||
request.getRichTextTmpFileIds().forEach(tmpFileId -> {
|
||||
// 目前只支持富文本图片临时文件的下载, 并同步至第三方平台 (后续支持富文本其他类型文件)
|
||||
FileRequest downloadRequest = buildTmpImageFileRequest(tmpFileId);
|
||||
try {
|
||||
byte[] tmpBytes = fileService.download(downloadRequest);
|
||||
File uploadTmpFile = new File(LocalRepositoryDir.getBugTmpDir() + "/" + tmpFileId + "/" + downloadRequest.getFileName());
|
||||
FileUtils.writeByteArrayToFile(uploadTmpFile, tmpBytes);
|
||||
platformRequest.getRichFileMap().put(tmpFileId, uploadTmpFile);
|
||||
} catch (Exception e) {
|
||||
LogUtils.info("缺陷富文本临时图片文件下载失败, 文件ID: " + tmpFileId);
|
||||
}
|
||||
});
|
||||
}
|
||||
return platformRequest;
|
||||
}
|
||||
|
||||
|
@ -1347,7 +1407,7 @@ public class BugService {
|
|||
* @param fileName 文件名称
|
||||
* @return 文件请求对象
|
||||
*/
|
||||
private FileRequest buildBugFileRequest(String projectId, String resourceId, String fileId, String fileName) {
|
||||
public FileRequest buildBugFileRequest(String projectId, String resourceId, String fileId, String fileName) {
|
||||
FileRequest fileRequest = new FileRequest();
|
||||
fileRequest.setFolder(DefaultRepositoryDir.getBugDir(projectId, resourceId) + "/" + fileId);
|
||||
fileRequest.setFileName(StringUtils.isEmpty(fileName) ? null : fileName);
|
||||
|
@ -1355,6 +1415,20 @@ public class BugService {
|
|||
return fileRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建临时图片文件请求
|
||||
* @param fileId 文件ID
|
||||
* @return 文件请求对象
|
||||
*/
|
||||
private FileRequest buildTmpImageFileRequest(String fileId) {
|
||||
FileRequest fileRequest = new FileRequest();
|
||||
fileRequest.setFolder(DefaultRepositoryDir.getSystemTempCompressDir() + "/" + fileId);
|
||||
// 临时图片文件名称固定为image.png
|
||||
fileRequest.setFileName("image.png");
|
||||
fileRequest.setStorage(StorageType.MINIO.name());
|
||||
return fileRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出缺陷
|
||||
* @param request 导出请求参数
|
||||
|
|
|
@ -47,7 +47,7 @@ export const deleteFileOrCancelAssociationUrl = '/bug/attachment/delete';
|
|||
// 获取附件列表
|
||||
export const getAttachmentListUrl = '/bug/attachment/list/';
|
||||
// 富文本编辑器上传图片
|
||||
export const editorUploadFileUrl = '/bug/attachment/upload/md/file';
|
||||
export const editorUploadFileUrl = '/attachment/upload/temp/file';
|
||||
|
||||
// 获取回收站列表
|
||||
export const getRecycleListUrl = '/bug/trash/page';
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<MsRichText
|
||||
v-if="contentEditAble"
|
||||
v-model:raw="form.description"
|
||||
v-model:filed-ids="fileIds"
|
||||
v-model:filed-ids="descriptionFileIds"
|
||||
:disabled="!contentEditAble"
|
||||
:placeholder="t('editor.placeholder')"
|
||||
:upload-image="handleUploadImage"
|
||||
|
@ -238,8 +238,8 @@
|
|||
const transferVisible = ref<boolean>(false);
|
||||
const previewVisible = ref<boolean>(false);
|
||||
const acceptType = ref('none'); // 模块-上传文件类型
|
||||
// 富文本附件id
|
||||
const fileIds = ref<string[]>([]);
|
||||
// 描述-富文本临时附件ID
|
||||
const descriptionFileIds = ref<string[]>([]);
|
||||
const imageUrl = ref<string>('');
|
||||
const associatedDrawer = ref(false);
|
||||
const fileListRef = ref<InstanceType<typeof MsFileList>>();
|
||||
|
@ -464,6 +464,7 @@
|
|||
unLinkRefIds: form.value.unLinkRefIds,
|
||||
linkFileIds: form.value.linkFileIds,
|
||||
customFields,
|
||||
richTextTmpFileIds: descriptionFileIds.value,
|
||||
};
|
||||
if (!props.isPlatformDefaultTemplate) {
|
||||
tmpObj.description = form.value.description;
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<a-form-item v-if="!isPlatformDefaultTemplate" field="description" :label="t('bugManagement.edit.content')">
|
||||
<MsRichText
|
||||
v-model:raw="form.description"
|
||||
v-model:filed-ids="richTextFileIds"
|
||||
v-model:filed-ids="descriptionFileIds"
|
||||
:upload-image="handleUploadImage"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
@ -314,7 +314,8 @@
|
|||
const isPlatformDefaultTemplate = ref(false);
|
||||
const imageUrl = ref('');
|
||||
const previewVisible = ref<boolean>(false);
|
||||
const richTextFileIds = ref<string[]>([]);
|
||||
// 描述-富文本临时附件ID
|
||||
const descriptionFileIds = ref<string[]>([]);
|
||||
const visitedKey = 'doNotNextTipCreateBug';
|
||||
const { getIsVisited } = useVisit(visitedKey);
|
||||
|
||||
|
@ -584,6 +585,7 @@
|
|||
...form.value,
|
||||
customFields,
|
||||
copyFiles,
|
||||
richTextTmpFileIds: descriptionFileIds.value,
|
||||
};
|
||||
if (isCopy.value) {
|
||||
delete tmpObj.id;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
@confirm="handleConfirm"
|
||||
>
|
||||
<a-form ref="formRef" class="rounded-[4px]" :model="form" layout="vertical">
|
||||
<a-form-item field="PLATFORM_KEY" :label="t('project.menu.platformLabel')">
|
||||
<a-form-item field="platformKey" :label="t('project.menu.platformLabel')">
|
||||
<a-select
|
||||
v-model="form.PLATFORM_KEY"
|
||||
allow-clear
|
||||
|
|
Loading…
Reference in New Issue