fix(缺陷管理): 富文本图片同步问题

This commit is contained in:
song-cc-rock 2024-03-22 12:33:28 +08:00 committed by 刘瑞斌
parent 1c6d4a64aa
commit 364d8c9a18
7 changed files with 80 additions and 25 deletions

View File

@ -2,6 +2,8 @@ package io.metersphere.plugin.platform.dto.reponse;
import lombok.Data; import lombok.Data;
import java.util.Map;
@Data @Data
public class PlatformBugUpdateDTO { public class PlatformBugUpdateDTO {
@ -25,4 +27,8 @@ public class PlatformBugUpdateDTO {
* 平台描述 * 平台描述
*/ */
private String platformDescription; private String platformDescription;
/**
* 平台自定义字段值
*/
private Map<String, String> platformCustomFieldMap;
} }

View File

@ -327,7 +327,7 @@ public class BugAttachmentService {
try { try {
// upload platform attachment to minio // upload platform attachment to minio
bytes = in.readAllBytes(); bytes = in.readAllBytes();
FileCenter.getDefaultRepository().saveFile(bytes, buildBugFileRequest(projectId, bugId, fileId, fileName)); FileCenter.getDefaultRepository().saveFile(bytes, buildBugFileRequest(projectId, bugId, fileId, fileName, false));
} catch (Exception e) { } catch (Exception e) {
throw new MSException(e.getMessage()); throw new MSException(e.getMessage());
} }
@ -382,7 +382,7 @@ public class BugAttachmentService {
deleteLocalIds.forEach(deleteLocalId -> { deleteLocalIds.forEach(deleteLocalId -> {
try { try {
BugFileDTO bugFileDTO = localFileMap.get(deleteLocalId); BugFileDTO bugFileDTO = localFileMap.get(deleteLocalId);
FileCenter.getDefaultRepository().delete(buildBugFileRequest(projectId, bugId, bugFileDTO.getFileId(), bugFileDTO.getFileName())); FileCenter.getDefaultRepository().delete(buildBugFileRequest(projectId, bugId, bugFileDTO.getFileId(), bugFileDTO.getFileName(), false));
} catch (Exception e) { } catch (Exception e) {
throw new MSException(e.getMessage()); throw new MSException(e.getMessage());
} }
@ -406,7 +406,7 @@ public class BugAttachmentService {
* @return 文件字节流 * @return 文件字节流
*/ */
public byte[] getLocalFileBytes(BugLocalAttachment attachment, String projectId, String bugId) { public byte[] getLocalFileBytes(BugLocalAttachment attachment, String projectId, String bugId) {
FileRequest fileRequest = buildBugFileRequest(projectId, bugId, attachment.getFileId(), attachment.getFileName()); FileRequest fileRequest = buildBugFileRequest(projectId, bugId, attachment.getFileId(), attachment.getFileName(), false);
byte[] bytes; byte[] bytes;
try { try {
bytes = fileService.download(fileRequest); bytes = fileService.download(fileRequest);
@ -477,7 +477,7 @@ public class BugAttachmentService {
record.setCreateUser(currentUser); record.setCreateUser(currentUser);
bugLocalAttachmentMapper.insert(record); bugLocalAttachmentMapper.insert(record);
List<SyncAttachmentToPlatformRequest> localSyncFiles = new ArrayList<>(); List<SyncAttachmentToPlatformRequest> localSyncFiles = new ArrayList<>();
FileRequest fileRequest = buildBugFileRequest(projectId, bugId, record.getFileId(), file.getOriginalFilename()); FileRequest fileRequest = buildBugFileRequest(projectId, bugId, record.getFileId(), file.getOriginalFilename(), false);
try { try {
fileService.upload(file, fileRequest); fileService.upload(file, fileRequest);
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) { if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
@ -534,7 +534,7 @@ public class BugAttachmentService {
List<SyncAttachmentToPlatformRequest> syncLocalFiles = new ArrayList<>(); List<SyncAttachmentToPlatformRequest> syncLocalFiles = new ArrayList<>();
BugLocalAttachment localAttachment = bugLocalAttachmentMapper.selectByPrimaryKey(refId); BugLocalAttachment localAttachment = bugLocalAttachmentMapper.selectByPrimaryKey(refId);
// 删除本地上传的附件, BUG_LOCAL_ATTACHMENT表 // 删除本地上传的附件, BUG_LOCAL_ATTACHMENT表
FileRequest fileRequest = buildBugFileRequest(projectId, bugId, localAttachment.getFileId(), localAttachment.getFileName()); FileRequest fileRequest = buildBugFileRequest(projectId, bugId, localAttachment.getFileId(), localAttachment.getFileName(), false);
try { try {
// 删除MINIO附件 // 删除MINIO附件
fileService.deleteFile(fileRequest); fileService.deleteFile(fileRequest);
@ -586,11 +586,16 @@ public class BugAttachmentService {
* @param resourceId 资源ID * @param resourceId 资源ID
* @param fileId 文件ID * @param fileId 文件ID
* @param fileName 文件名称 * @param fileName 文件名称
* @param compress 是否压缩预览目录
* @return 文件请求对象 * @return 文件请求对象
*/ */
private FileRequest buildBugFileRequest(String projectId, String resourceId, String fileId, String fileName) { private FileRequest buildBugFileRequest(String projectId, String resourceId, String fileId, String fileName, boolean compress) {
FileRequest fileRequest = new FileRequest(); FileRequest fileRequest = new FileRequest();
fileRequest.setFolder(DefaultRepositoryDir.getBugDir(projectId, resourceId) + "/" + fileId); if (compress) {
fileRequest.setFolder(DefaultRepositoryDir.getBugPreviewDir(projectId, resourceId) + "/" + fileId);
} else {
fileRequest.setFolder(DefaultRepositoryDir.getBugDir(projectId, resourceId) + "/" + fileId);
}
fileRequest.setFileName(StringUtils.isEmpty(fileName) ? null : fileName); fileRequest.setFileName(StringUtils.isEmpty(fileName) ? null : fileName);
fileRequest.setStorage(StorageType.MINIO.name()); fileRequest.setStorage(StorageType.MINIO.name());
return fileRequest; return fileRequest;
@ -763,14 +768,7 @@ public class BugAttachmentService {
//在正式目录获取 //在正式目录获取
BugLocalAttachment attachment = bugAttachments.get(0); BugLocalAttachment attachment = bugAttachments.get(0);
fileName = attachment.getFileName(); fileName = attachment.getFileName();
FileRequest fileRequest = new FileRequest(); FileRequest fileRequest = buildBugFileRequest(projectId, attachment.getBugId(), attachment.getFileId(), attachment.getFileName(), compressed);
fileRequest.setFileName(attachment.getFileName());
if (compressed) {
fileRequest.setFolder(DefaultRepositoryDir.getBugPreviewDir(projectId, attachment.getBugId()) + "/" + attachment.getFileId());
} else {
fileRequest.setFolder(DefaultRepositoryDir.getBugDir(projectId, attachment.getBugId()) + "/" + attachment.getFileId());
}
fileRequest.setStorage(StorageType.MINIO.name());
try { try {
bytes = fileService.download(fileRequest); bytes = fileService.download(fileRequest);
} catch (Exception e) { } catch (Exception e) {

View File

@ -27,6 +27,9 @@ public class BugPlatformService {
public void syncAttachmentToPlatform(List<SyncAttachmentToPlatformRequest> platformAttachments, String projectId) { public void syncAttachmentToPlatform(List<SyncAttachmentToPlatformRequest> platformAttachments, String projectId) {
// 平台缺陷需同步附件 // 平台缺陷需同步附件
Platform platform = projectApplicationService.getPlatform(projectId, true); Platform platform = projectApplicationService.getPlatform(projectId, true);
if (!platform.isSupportAttachment()) {
return;
}
platformAttachments.forEach(attachment -> { platformAttachments.forEach(attachment -> {
platform.syncAttachmentToPlatform(attachment); platform.syncAttachmentToPlatform(attachment);
try { try {

View File

@ -17,6 +17,7 @@ import io.metersphere.plugin.platform.dto.reponse.PlatformBugDTO;
import io.metersphere.plugin.platform.dto.reponse.PlatformBugUpdateDTO; import io.metersphere.plugin.platform.dto.reponse.PlatformBugUpdateDTO;
import io.metersphere.plugin.platform.dto.reponse.PlatformCustomFieldItemDTO; import io.metersphere.plugin.platform.dto.reponse.PlatformCustomFieldItemDTO;
import io.metersphere.plugin.platform.dto.request.*; import io.metersphere.plugin.platform.dto.request.*;
import io.metersphere.plugin.platform.enums.PlatformCustomFieldType;
import io.metersphere.plugin.platform.enums.SyncAttachmentType; import io.metersphere.plugin.platform.enums.SyncAttachmentType;
import io.metersphere.plugin.platform.spi.Platform; import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.project.domain.*; import io.metersphere.project.domain.*;
@ -227,7 +228,7 @@ public class BugService {
// 处理基础字段 // 处理基础字段
Bug bug = handleAndSaveBug(request, currentUser, platformName, platformBug); Bug bug = handleAndSaveBug(request, currentUser, platformName, platformBug);
// 处理自定义字段 // 处理自定义字段
handleAndSaveCustomFields(request, isUpdate); handleAndSaveCustomFields(request, isUpdate, platformBug);
// 处理附件 // 处理附件
handleAndSaveAttachments(request, files, currentUser, platformName, platformBug); handleAndSaveAttachments(request, files, currentUser, platformName, platformBug);
// 处理富文本临时文件 // 处理富文本临时文件
@ -639,7 +640,7 @@ public class BugService {
return bugCustomFieldDTO; return bugCustomFieldDTO;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
customEditRequest.setCustomFields(bugCustomFieldDTOList); customEditRequest.setCustomFields(bugCustomFieldDTOList);
handleAndSaveCustomFields(customEditRequest, true); handleAndSaveCustomFields(customEditRequest, true, null);
}); });
// 批量删除缺陷 // 批量删除缺陷
@ -879,7 +880,7 @@ public class BugService {
* *
* @param request 请求参数 * @param request 请求参数
*/ */
public void handleAndSaveCustomFields(BugEditRequest request, boolean merge) { public void handleAndSaveCustomFields(BugEditRequest request, boolean merge, PlatformBugUpdateDTO platformBug) {
// 处理ID, 值的映射关系 // 处理ID, 值的映射关系
Map<String, String> customFieldMap = request.getCustomFields().stream() Map<String, String> customFieldMap = request.getCustomFields().stream()
.filter(f -> StringUtils.isNotBlank(f.getId())) .filter(f -> StringUtils.isNotBlank(f.getId()))
@ -887,6 +888,11 @@ public class BugService {
if (MapUtils.isEmpty(customFieldMap)) { if (MapUtils.isEmpty(customFieldMap)) {
return; return;
} }
// 拦截, 如果平台返回结果存在自定义字段值, 替换
if (platformBug != null && MapUtils.isNotEmpty(platformBug.getPlatformCustomFieldMap())) {
Map<String, String> platformCustomFieldMap = platformBug.getPlatformCustomFieldMap();
platformCustomFieldMap.keySet().forEach(key -> customFieldMap.put(key, platformCustomFieldMap.get(key)));
}
List<BugCustomField> addFields = new ArrayList<>(); List<BugCustomField> addFields = new ArrayList<>();
List<BugCustomField> updateFields = new ArrayList<>(); List<BugCustomField> updateFields = new ArrayList<>();
if (merge) { if (merge) {
@ -1285,7 +1291,20 @@ public class BugService {
localAttachment.setSource(BugAttachmentSourceType.RICH_TEXT.name()); localAttachment.setSource(BugAttachmentSourceType.RICH_TEXT.name());
bugLocalAttachmentMapper.insert(localAttachment); bugLocalAttachmentMapper.insert(localAttachment);
// 替换富文本中的临时URL, 注意: 第三方的图片附件暂未存储在压缩目录, 因此不支持压缩访问 // 替换富文本中的临时URL, 注意: 第三方的图片附件暂未存储在压缩目录, 因此不支持压缩访问
updateBug.setDescription(updateBug.getDescription().replace("alt=\"" + key + "\"", "src=\"/bug/attachment/preview/md/" + updateBug.getProjectId() + "/" + fileId + "/false\"")); if (StringUtils.contains(updateBug.getDescription(), "alt=\"" + key + "\"")) {
updateBug.setDescription(updateBug.getDescription()
.replace("alt=\"" + key + "\"", "src=\"/bug/attachment/preview/md/" + updateBug.getProjectId() + "/" + fileId + "/false\""));
if (updateBug.getPlatformDefaultTemplate()) {
// 来自富文本自定义字段
PlatformCustomFieldItemDTO descriptionField = updateBug.getCustomFieldList().stream().filter(field -> StringUtils.equals(field.getCustomData(), "description")).toList().get(0);
descriptionField.setValue(updateBug.getDescription());
}
} else {
// 来自富文本自定义字段
PlatformCustomFieldItemDTO richTextField = updateBug.getCustomFieldList().stream().filter(field -> StringUtils.equals(field.getType(), PlatformCustomFieldType.RICH_TEXT.name())
&& field.getValue() != null && StringUtils.contains(field.getValue().toString(), "alt=\"" + key + "\"")).toList().get(0);
richTextField.setValue(richTextField.getValue().toString().replace("alt=\"" + key + "\"", "src=\"/bug/attachment/preview/md/" + updateBug.getProjectId() + "/" + fileId + "/false\""));
}
})); }));
} }
} }

View File

@ -605,6 +605,7 @@ public class BugControllerTests extends BaseTest {
summary.setType("INPUT"); summary.setType("INPUT");
summary.setValue("这是一个系统Jira模板创建的缺陷"); summary.setValue("这是一个系统Jira模板创建的缺陷");
addRequest.getCustomFields().add(summary); addRequest.getCustomFields().add(summary);
addRequest.setRichTextTmpFileIds(List.of("rich-text-file-id"));
MultiValueMap<String, Object> addParam3 = getMultiPartParam(addRequest, null); MultiValueMap<String, Object> addParam3 = getMultiPartParam(addRequest, null);
this.requestMultipart(BUG_ADD, addParam3).andExpect(status().is5xxServerError()); this.requestMultipart(BUG_ADD, addParam3).andExpect(status().is5xxServerError());
@ -612,6 +613,7 @@ public class BugControllerTests extends BaseTest {
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug"); this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 添加没有附件的Jira缺陷 // 添加没有附件的Jira缺陷
addRequest.setRichTextTmpFileIds(null);
addRequest.setLinkFileIds(null); addRequest.setLinkFileIds(null);
addRequest.setTemplateId("default-bug-template-id"); addRequest.setTemplateId("default-bug-template-id");
MultiValueMap<String, Object> addParam2 = getMultiPartParam(addRequest, null); MultiValueMap<String, Object> addParam2 = getMultiPartParam(addRequest, null);

View File

@ -47,8 +47,10 @@
<MsRichText <MsRichText
v-if="contentEditAble" v-if="contentEditAble"
v-model:raw="item.defaultValue" v-model:raw="item.defaultValue"
v-model:filed-ids="descriptionFileIdMap[item.fieldId]"
:disabled="!contentEditAble" :disabled="!contentEditAble"
:placeholder="t('editor.placeholder')" :placeholder="t('editor.placeholder')"
:upload-image="handleUploadImage"
:preview-url="EditorPreviewFileUrl" :preview-url="EditorPreviewFileUrl"
/> />
<div v-else v-dompurify-html="item?.defaultValue || '-'" class="markdown-body"></div> <div v-else v-dompurify-html="item?.defaultValue || '-'" class="markdown-body"></div>
@ -257,6 +259,7 @@
const acceptType = ref('none'); // - const acceptType = ref('none'); // -
// -ID // -ID
const descriptionFileIds = ref<string[]>([]); const descriptionFileIds = ref<string[]>([]);
const descriptionFileIdMap = ref<Record<string, string[]>>({});
const imageUrl = ref<string>(''); const imageUrl = ref<string>('');
const associatedDrawer = ref(false); const associatedDrawer = ref(false);
const fileListRef = ref<InstanceType<typeof MsFileList>>(); const fileListRef = ref<InstanceType<typeof MsFileList>>();
@ -442,6 +445,16 @@
return data; return data;
} }
function getDescriptionFileId() {
const fileIds = [] as string[];
Object.keys(descriptionFileIdMap.value).forEach((key) => {
if (descriptionFileIdMap.value[key].length > 0) {
fileIds.push(...descriptionFileIdMap.value[key]);
}
});
return fileIds;
}
// //
async function handleSave() { async function handleSave() {
try { try {
@ -481,7 +494,7 @@
unLinkRefIds: form.value.unLinkRefIds, unLinkRefIds: form.value.unLinkRefIds,
linkFileIds: form.value.linkFileIds, linkFileIds: form.value.linkFileIds,
customFields, customFields,
richTextTmpFileIds: descriptionFileIds.value, richTextTmpFileIds: props.isPlatformDefaultTemplate ? getDescriptionFileId() : descriptionFileIds.value,
}; };
if (!props.isPlatformDefaultTemplate) { if (!props.isPlatformDefaultTemplate) {
tmpObj.description = form.value.description; tmpObj.description = form.value.description;
@ -521,8 +534,6 @@
} }
watchEffect(() => { watchEffect(() => {
console.log(props.currentPlatform);
console.log(props.detailInfo.platform);
initCurrentDetail(props.detailInfo); initCurrentDetail(props.detailInfo);
}); });
defineExpose({ defineExpose({

View File

@ -63,6 +63,9 @@
<MsRichText <MsRichText
v-if="platformSystemFieldMap[key].type === 'RICH_TEXT'" v-if="platformSystemFieldMap[key].type === 'RICH_TEXT'"
v-model:raw="form.platformSystemFields[key]" v-model:raw="form.platformSystemFields[key]"
v-model:filed-ids="descriptionFileIdMap[key]"
:upload-image="handleUploadImage"
:preview-url="EditorPreviewFileUrl"
/> />
</a-form-item> </a-form-item>
</div> </div>
@ -316,8 +319,10 @@
const isPlatformDefaultTemplate = ref(false); const isPlatformDefaultTemplate = ref(false);
const imageUrl = ref(''); const imageUrl = ref('');
const previewVisible = ref<boolean>(false); const previewVisible = ref<boolean>(false);
// -ID // /ID
const descriptionFileIds = ref<string[]>([]); const descriptionFileIds = ref<string[]>([]);
// -/ID
const descriptionFileIdMap = ref<Record<string, string[]>>({});
const visitedKey = 'doNotNextTipCreateBug'; const visitedKey = 'doNotNextTipCreateBug';
const { getIsVisited } = useVisit(visitedKey); const { getIsVisited } = useVisit(visitedKey);
@ -532,6 +537,16 @@
fileList.value.push(...fileResultList); fileList.value.push(...fileResultList);
} }
function getDescriptionFileId() {
const fileIds = [] as string[];
Object.keys(descriptionFileIdMap.value).forEach((key) => {
if (descriptionFileIdMap.value[key].length > 0) {
fileIds.push(...descriptionFileIdMap.value[key]);
}
});
return fileIds;
}
// //
const saveHandler = async (isContinue = false) => { const saveHandler = async (isContinue = false) => {
formRef.value.validate((error: any) => { formRef.value.validate((error: any) => {
@ -583,11 +598,12 @@
}; };
}); });
} }
const tmpObj: BugEditFormObject = { const tmpObj: BugEditFormObject = {
...form.value, ...form.value,
customFields, customFields,
copyFiles, copyFiles,
richTextTmpFileIds: descriptionFileIds.value, richTextTmpFileIds: isPlatformDefaultTemplate.value ? getDescriptionFileId() : descriptionFileIds.value,
}; };
if (isCopy.value) { if (isCopy.value) {
delete tmpObj.id; delete tmpObj.id;