From c0870f06e535d0d1cbd224a4fb4ec215f694e866 Mon Sep 17 00:00:00 2001 From: song-cc-rock Date: Sat, 17 Sep 2022 13:21:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B5=8B=E8=AF=95=E8=B7=9F=E8=B8=AA):=20?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E5=8F=8A=E7=BC=BA=E9=99=B7=E9=99=84=E4=BB=B6?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=96=87=E4=BB=B6=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/pom.xml | 12 ++ .../base/domain/AttachmentModuleRelation.java | 2 + .../AttachmentModuleRelationExample.java | 70 +++++++ .../base/domain/FileAttachmentMetadata.java | 7 +- .../mapper/AttachmentModuleRelationMapper.xml | 23 ++- .../ext/ExtAttachmentModuleRelationMapper.xml | 4 +- .../constants/FileAssociationType.java | 2 +- .../metadata/vo/AttachmentDumpRequest.java | 8 + .../io/metersphere/service/FileService.java | 7 + .../controller/AttachmentController.java | 43 +++- .../track/issue/AbstractIssuePlatform.java | 1 - .../request/attachment/AttachmentRequest.java | 4 + .../request/testcase/EditTestCaseRequest.java | 4 + .../request/testcase/IssuesUpdateRequest.java | 5 + .../testplan/FileOperationRequest.java | 1 + .../track/service/AttachmentService.java | 188 ++++++++++++++++- .../track/service/IssuesService.java | 53 ++++- .../track/service/TestCaseService.java | 38 ++++ .../io/metersphere/xmind/utils/FileUtil.java | 26 ++- .../db/migration/V130__2.2.0__release.sql | 5 + .../case/components/TestCaseAttachment.vue | 73 ++++++- .../track/case/components/TestCaseEdit.vue | 11 +- .../case/components/TestCaseEditOtherInfo.vue | 191 +++++++++++++++-- .../track/case/components/TestCaseFile.vue | 4 +- .../track/case/components/TestCasePdf.vue | 5 +- .../track/issue/IssueEditDetail.vue | 195 +++++++++++++++--- frontend/src/i18n/en-US.js | 11 + frontend/src/i18n/track/en-US.js | 2 +- frontend/src/i18n/track/zh-CN.js | 2 +- frontend/src/i18n/track/zh-TW.js | 2 +- frontend/src/i18n/zh-CN.js | 11 + frontend/src/i18n/zh-TW.js | 11 + 32 files changed, 924 insertions(+), 97 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/metadata/vo/AttachmentDumpRequest.java diff --git a/backend/pom.xml b/backend/pom.xml index fd0fd01717..1e9c64bda3 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -27,6 +27,7 @@ 5.1.0 5.1.0 6.2.0.202206071550-r + 1.3 @@ -152,6 +153,17 @@ commons-codec commons-codec + + commons-fileupload + commons-fileupload + ${commons-fileupload.version} + + + commons-io + commons-io + + + com.alibaba diff --git a/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelation.java b/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelation.java index 3a7ab3a4b9..653ee7141c 100644 --- a/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelation.java +++ b/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelation.java @@ -12,5 +12,7 @@ public class AttachmentModuleRelation implements Serializable { private String attachmentId; + private String fileMetadataRefId; + private static final long serialVersionUID = 1L; } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelationExample.java b/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelationExample.java index dcf86f143e..527e3a9c9b 100644 --- a/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelationExample.java +++ b/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelationExample.java @@ -313,6 +313,76 @@ public class AttachmentModuleRelationExample { addCriterion("attachment_id not between", value1, value2, "attachmentId"); return (Criteria) this; } + + public Criteria andFileMetadataRefIdIsNull() { + addCriterion("file_metadata_ref_id is null"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdIsNotNull() { + addCriterion("file_metadata_ref_id is not null"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdEqualTo(String value) { + addCriterion("file_metadata_ref_id =", value, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdNotEqualTo(String value) { + addCriterion("file_metadata_ref_id <>", value, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdGreaterThan(String value) { + addCriterion("file_metadata_ref_id >", value, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdGreaterThanOrEqualTo(String value) { + addCriterion("file_metadata_ref_id >=", value, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdLessThan(String value) { + addCriterion("file_metadata_ref_id <", value, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdLessThanOrEqualTo(String value) { + addCriterion("file_metadata_ref_id <=", value, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdLike(String value) { + addCriterion("file_metadata_ref_id like", value, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdNotLike(String value) { + addCriterion("file_metadata_ref_id not like", value, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdIn(List values) { + addCriterion("file_metadata_ref_id in", values, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdNotIn(List values) { + addCriterion("file_metadata_ref_id not in", values, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdBetween(String value1, String value2) { + addCriterion("file_metadata_ref_id between", value1, value2, "fileMetadataRefId"); + return (Criteria) this; + } + + public Criteria andFileMetadataRefIdNotBetween(String value1, String value2) { + addCriterion("file_metadata_ref_id not between", value1, value2, "fileMetadataRefId"); + return (Criteria) this; + } } public static class Criteria extends GeneratedCriteria { diff --git a/backend/src/main/java/io/metersphere/base/domain/FileAttachmentMetadata.java b/backend/src/main/java/io/metersphere/base/domain/FileAttachmentMetadata.java index 9dd52b4a5b..66d0b198c1 100644 --- a/backend/src/main/java/io/metersphere/base/domain/FileAttachmentMetadata.java +++ b/backend/src/main/java/io/metersphere/base/domain/FileAttachmentMetadata.java @@ -1,8 +1,9 @@ package io.metersphere.base.domain; -import java.io.Serializable; import lombok.Data; +import java.io.Serializable; + @Data public class FileAttachmentMetadata implements Serializable { private String id; @@ -21,5 +22,9 @@ public class FileAttachmentMetadata implements Serializable { private String filePath; + private Boolean isLocal; + + private Boolean isRelatedDeleted; + private static final long serialVersionUID = 1L; } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/AttachmentModuleRelationMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/AttachmentModuleRelationMapper.xml index 8e7087aa88..4b7aa27762 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/AttachmentModuleRelationMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/AttachmentModuleRelationMapper.xml @@ -5,6 +5,7 @@ + @@ -65,7 +66,7 @@ - relation_id, relation_type, attachment_id + relation_id, relation_type, attachment_id, file_metadata_ref_id @@ -136,6 +143,9 @@ attachment_id = #{record.attachmentId,jdbcType=VARCHAR}, + + file_metadata_ref_id = #{record.fileMetadataRefId,jdbcType=VARCHAR}, + @@ -145,7 +155,8 @@ update attachment_module_relation set relation_id = #{record.relationId,jdbcType=VARCHAR}, relation_type = #{record.relationType,jdbcType=VARCHAR}, - attachment_id = #{record.attachmentId,jdbcType=VARCHAR} + attachment_id = #{record.attachmentId,jdbcType=VARCHAR}, + file_metadata_ref_id = #{record.fileMetadataRefId,jdbcType=VARCHAR} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtAttachmentModuleRelationMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtAttachmentModuleRelationMapper.xml index e2c7c87582..8d908415c9 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtAttachmentModuleRelationMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtAttachmentModuleRelationMapper.xml @@ -3,10 +3,10 @@ INSERT INTO - attachment_module_relation (relation_id, relation_type, attachment_id) + attachment_module_relation (relation_id, relation_type, attachment_id, file_metadata_ref_id) VALUES - (#{relation.relationId}, #{relation.relationType}, #{relation.attachmentId}) + (#{relation.relationId}, #{relation.relationType}, #{relation.attachmentId}, #{relation.fileMetadataRefId}) \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/commons/constants/FileAssociationType.java b/backend/src/main/java/io/metersphere/commons/constants/FileAssociationType.java index 5f8dff9fb9..4e65d1cc38 100644 --- a/backend/src/main/java/io/metersphere/commons/constants/FileAssociationType.java +++ b/backend/src/main/java/io/metersphere/commons/constants/FileAssociationType.java @@ -1,5 +1,5 @@ package io.metersphere.commons.constants; public enum FileAssociationType { - API, CASE, SCENARIO, UI, ENVIRONMENT + API, CASE, SCENARIO, UI, ENVIRONMENT, TEST_CASE, ISSUE } diff --git a/backend/src/main/java/io/metersphere/metadata/vo/AttachmentDumpRequest.java b/backend/src/main/java/io/metersphere/metadata/vo/AttachmentDumpRequest.java new file mode 100644 index 0000000000..bdbb46b207 --- /dev/null +++ b/backend/src/main/java/io/metersphere/metadata/vo/AttachmentDumpRequest.java @@ -0,0 +1,8 @@ +package io.metersphere.metadata.vo; + +import lombok.Data; + +@Data +public class AttachmentDumpRequest extends DumpFileRequest{ + private String attachmentId; +} diff --git a/backend/src/main/java/io/metersphere/service/FileService.java b/backend/src/main/java/io/metersphere/service/FileService.java index 35d3a8c93b..e7da233c53 100644 --- a/backend/src/main/java/io/metersphere/service/FileService.java +++ b/backend/src/main/java/io/metersphere/service/FileService.java @@ -8,6 +8,7 @@ import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.*; import io.metersphere.performance.request.QueryProjectFileRequest; import org.apache.commons.collections.CollectionUtils; +import io.metersphere.xmind.utils.FileUtil; import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -50,6 +51,12 @@ public class FileService { return FileUtils.fileToByte(attachmentFile); } + public MultipartFile getAttachmentMultipartFile(String id) { + FileAttachmentMetadata fileAttachmentMetadata = fileAttachmentMetadataMapper.selectByPrimaryKey(id); + File attachmentFile = new File(fileAttachmentMetadata.getFilePath() + "/" + fileAttachmentMetadata.getName()); + return FileUtil.fileToMultipartFile(attachmentFile); + } + public FileContent getFileContent(String fileId) { return fileContentMapper.selectByPrimaryKey(fileId); } diff --git a/backend/src/main/java/io/metersphere/track/controller/AttachmentController.java b/backend/src/main/java/io/metersphere/track/controller/AttachmentController.java index 8d16e848d7..5c5cadae6a 100644 --- a/backend/src/main/java/io/metersphere/track/controller/AttachmentController.java +++ b/backend/src/main/java/io/metersphere/track/controller/AttachmentController.java @@ -1,6 +1,8 @@ package io.metersphere.track.controller; import io.metersphere.base.domain.FileAttachmentMetadata; +import io.metersphere.metadata.service.FileMetadataService; +import io.metersphere.metadata.vo.AttachmentDumpRequest; import io.metersphere.service.FileService; import io.metersphere.track.request.attachment.AttachmentRequest; import io.metersphere.track.request.testplan.FileOperationRequest; @@ -14,6 +16,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; /** @@ -27,15 +30,23 @@ public class AttachmentController { private FileService fileService; @Resource private AttachmentService attachmentService; + @Resource + private FileMetadataService fileMetadataService; @PostMapping(value = "/upload", consumes = {"multipart/form-data"}) public void uploadAttachment(@RequestPart("request") AttachmentRequest request, @RequestPart(value = "file", required = false) MultipartFile file) { attachmentService.uploadAttachment(request, file); } - @GetMapping("/preview/{fileId}") - public ResponseEntity previewAttachment(@PathVariable String fileId) { - byte[] bytes = fileService.getAttachmentBytes(fileId); + @GetMapping("/preview/{fileId}/{isLocal}") + public ResponseEntity previewAttachment(@PathVariable String fileId, @PathVariable Boolean isLocal) { + byte[] bytes; + if (isLocal) { + bytes = fileService.getAttachmentBytes(fileId); + } else { + String refId = attachmentService.getRefIdByAttachmentId(fileId); + bytes = fileMetadataService.loadFileAsBytes(refId); + } return ResponseEntity.ok() .contentType(MediaType.parseMediaType("application/octet-stream")) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileId + "\"") @@ -44,7 +55,13 @@ public class AttachmentController { @PostMapping("/download") public ResponseEntity downloadAttachment(@RequestBody FileOperationRequest fileOperationRequest) { - byte[] bytes = fileService.getAttachmentBytes(fileOperationRequest.getId()); + byte[] bytes; + if (fileOperationRequest.getIsLocal()) { + bytes = fileService.getAttachmentBytes(fileOperationRequest.getId()); + } else { + String refId = attachmentService.getRefIdByAttachmentId(fileOperationRequest.getId()); + bytes = fileMetadataService.loadFileAsBytes(refId); + } return ResponseEntity.ok() .contentType(MediaType.parseMediaType("application/octet-stream")) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileOperationRequest.getName(), StandardCharsets.UTF_8) + "\"") @@ -61,4 +78,22 @@ public class AttachmentController { public List listMetadata(@RequestBody AttachmentRequest request) { return attachmentService.listMetadata(request); } + + @PostMapping("/metadata/relate") + public void relate(@RequestBody AttachmentRequest request) { + attachmentService.relate(request); + } + + @PostMapping("/metadata/unrelated") + public void unrelated(@RequestBody AttachmentRequest request) { + attachmentService.unrelated(request); + } + + @PostMapping(value = "/metadata/dump") + public void dumpFile(@RequestBody AttachmentDumpRequest request) { + List files = new ArrayList<>(); + MultipartFile file = fileService.getAttachmentMultipartFile(request.getAttachmentId()); + files.add(file); + fileMetadataService.dumpFile(request, files); + } } diff --git a/backend/src/main/java/io/metersphere/track/issue/AbstractIssuePlatform.java b/backend/src/main/java/io/metersphere/track/issue/AbstractIssuePlatform.java index 7802caa126..7a5d9e69dc 100644 --- a/backend/src/main/java/io/metersphere/track/issue/AbstractIssuePlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/AbstractIssuePlatform.java @@ -1,6 +1,5 @@ package io.metersphere.track.issue; -import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import io.metersphere.base.domain.*; diff --git a/backend/src/main/java/io/metersphere/track/request/attachment/AttachmentRequest.java b/backend/src/main/java/io/metersphere/track/request/attachment/AttachmentRequest.java index 92f74a9ae4..31bcd6731e 100644 --- a/backend/src/main/java/io/metersphere/track/request/attachment/AttachmentRequest.java +++ b/backend/src/main/java/io/metersphere/track/request/attachment/AttachmentRequest.java @@ -2,6 +2,8 @@ package io.metersphere.track.request.attachment; import lombok.Data; +import java.util.List; + /** * @author songcc @@ -14,4 +16,6 @@ public class AttachmentRequest { private String belongId; private String copyBelongId; + + private List metadataRefIds; } diff --git a/backend/src/main/java/io/metersphere/track/request/testcase/EditTestCaseRequest.java b/backend/src/main/java/io/metersphere/track/request/testcase/EditTestCaseRequest.java index a9ad4c0b63..e9d362a7b9 100644 --- a/backend/src/main/java/io/metersphere/track/request/testcase/EditTestCaseRequest.java +++ b/backend/src/main/java/io/metersphere/track/request/testcase/EditTestCaseRequest.java @@ -28,6 +28,10 @@ public class EditTestCaseRequest extends TestCaseWithBLOBs { private String copyCaseId; // 是否处理附件文件 private boolean handleAttachment = true; + // 关联文件管理引用ID + private List relateFileMetaIds = new ArrayList<>(); + // 取消关联文件应用ID + private List unRelateFileMetaIds = new ArrayList<>(); /** * 创建新版本时 是否连带复制其他信息的配置类 diff --git a/backend/src/main/java/io/metersphere/track/request/testcase/IssuesUpdateRequest.java b/backend/src/main/java/io/metersphere/track/request/testcase/IssuesUpdateRequest.java index b6eaeed611..f4d55fee7f 100644 --- a/backend/src/main/java/io/metersphere/track/request/testcase/IssuesUpdateRequest.java +++ b/backend/src/main/java/io/metersphere/track/request/testcase/IssuesUpdateRequest.java @@ -8,6 +8,7 @@ import io.metersphere.track.dto.PlatformStatusDTO; import lombok.Getter; import lombok.Setter; +import java.util.ArrayList; import java.util.List; @Getter @@ -53,4 +54,8 @@ public class IssuesUpdateRequest extends IssuesWithBLOBs { * 复制缺陷时原始缺陷ID */ private String copyIssueId; + // 关联文件管理引用ID + private List relateFileMetaIds = new ArrayList<>(); + // 取消关联文件应用ID + private List unRelateFileMetaIds = new ArrayList<>(); } diff --git a/backend/src/main/java/io/metersphere/track/request/testplan/FileOperationRequest.java b/backend/src/main/java/io/metersphere/track/request/testplan/FileOperationRequest.java index bc2d5bcd84..d793925fb8 100644 --- a/backend/src/main/java/io/metersphere/track/request/testplan/FileOperationRequest.java +++ b/backend/src/main/java/io/metersphere/track/request/testplan/FileOperationRequest.java @@ -8,4 +8,5 @@ import lombok.Setter; public class FileOperationRequest { private String id; private String name; + private Boolean isLocal; } diff --git a/backend/src/main/java/io/metersphere/track/service/AttachmentService.java b/backend/src/main/java/io/metersphere/track/service/AttachmentService.java index 5c438d0ce4..9d9c036c38 100644 --- a/backend/src/main/java/io/metersphere/track/service/AttachmentService.java +++ b/backend/src/main/java/io/metersphere/track/service/AttachmentService.java @@ -5,24 +5,31 @@ import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.ext.ExtAttachmentModuleRelationMapper; import io.metersphere.commons.constants.AttachmentSyncType; import io.metersphere.commons.constants.AttachmentType; +import io.metersphere.commons.constants.FileAssociationType; import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.BeanUtils; +import io.metersphere.commons.utils.FileUtils; import io.metersphere.commons.utils.SessionUtils; import io.metersphere.i18n.Translator; +import io.metersphere.metadata.service.FileMetadataService; import io.metersphere.service.FileService; import io.metersphere.track.issue.IssueFactory; import io.metersphere.track.request.attachment.AttachmentRequest; import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; /** @@ -48,6 +55,14 @@ public class AttachmentService { private AttachmentModuleRelationMapper attachmentModuleRelationMapper; @Resource private ExtAttachmentModuleRelationMapper extAttachmentModuleRelationMapper; + @Resource + private FileMetadataMapper fileMetadataMapper; + @Resource + private FileAssociationMapper fileAssociationMapper; + @Resource + private FileMetadataService fileMetadataService; + @Resource + SqlSessionFactory sqlSessionFactory; public void uploadAttachment(AttachmentRequest request, MultipartFile file) { // 附件上传的前置校验 @@ -129,25 +144,162 @@ public class AttachmentService { example.createCriteria().andRelationIdEqualTo(request.getCopyBelongId()).andRelationTypeEqualTo(request.getBelongType()); List attachmentModuleRelations = attachmentModuleRelationMapper.selectByExample(example); if (CollectionUtils.isNotEmpty(attachmentModuleRelations)) { - attachmentModuleRelations.forEach(attachmentModuleRelation -> { - FileAttachmentMetadata fileAttachmentMetadata = fileService.copyAttachment(attachmentModuleRelation.getAttachmentId(), request.getBelongType(), request.getBelongId()); + // 本地附件 + List localAttachments = attachmentModuleRelations.stream() + .filter(relation -> StringUtils.isEmpty(relation.getFileMetadataRefId())) + .map(AttachmentModuleRelation::getAttachmentId) + .filter(StringUtils::isNotEmpty).collect(Collectors.toList()); + localAttachments.forEach(localAttachmentId -> { + FileAttachmentMetadata fileAttachmentMetadata = fileService.copyAttachment(localAttachmentId, request.getBelongType(), request.getBelongId()); AttachmentModuleRelation record = new AttachmentModuleRelation(); record.setRelationId(request.getBelongId()); record.setRelationType(request.getBelongType()); record.setAttachmentId(fileAttachmentMetadata.getId()); attachmentModuleRelationMapper.insert(record); }); + // 文件管理关联附件 + List refAttachments = attachmentModuleRelations.stream() + .filter(relation -> StringUtils.isNotEmpty(relation.getFileMetadataRefId())).collect(Collectors.toList()); + refAttachments.forEach(refAttachment -> { + refAttachment.setRelationId(request.getBelongId()); + attachmentModuleRelationMapper.insert(refAttachment); + // 缺陷类型的附件, 关联时需单独同步第三方平台 + if (AttachmentType.ISSUE.type().equals(request.getBelongType())) { + String metadataRefId = getRefIdByAttachmentId(refAttachment.getAttachmentId()); + FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(metadataRefId); + IssuesWithBLOBs issues = issuesMapper.selectByPrimaryKey(request.getBelongId()); + IssuesUpdateRequest updateRequest = new IssuesUpdateRequest(); + updateRequest.setPlatformId(issues.getPlatformId()); + File refFile = downloadMetadataFile(metadataRefId, fileMetadata.getName()); + IssuesRequest issuesRequest = new IssuesRequest(); + issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); + Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest)) + .syncIssuesAttachment(updateRequest, refFile, AttachmentSyncType.UPLOAD); + FileUtils.deleteFile(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileMetadata.getName()); + } + }); } } public List listMetadata(AttachmentRequest request) { - List attachmentIds = getAttachmentIdsByParam(request); - if (CollectionUtils.isEmpty(attachmentIds)) { - return new ArrayList<>(); + List attachments = new ArrayList(); + AttachmentModuleRelationExample example = new AttachmentModuleRelationExample(); + example.createCriteria().andRelationIdEqualTo(request.getBelongId()).andRelationTypeEqualTo(request.getBelongType()); + List attachmentModuleRelations = attachmentModuleRelationMapper.selectByExample(example); + Map relationMap = attachmentModuleRelations.stream() + .collect(Collectors.toMap(AttachmentModuleRelation::getAttachmentId, + relation -> relation.getFileMetadataRefId() == null ? "" : relation.getFileMetadataRefId())); + List attachmentIds = attachmentModuleRelations.stream().map(AttachmentModuleRelation::getAttachmentId) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(attachmentIds)) { + FileAttachmentMetadataExample fileExample = new FileAttachmentMetadataExample(); + fileExample.createCriteria().andIdIn(attachmentIds); + List fileAttachmentMetadata = fileAttachmentMetadataMapper.selectByExample(fileExample); + fileAttachmentMetadata.forEach(file -> { + String fileRefId = relationMap.get(file.getId()); + if (StringUtils.isEmpty(fileRefId)) { + file.setIsLocal(Boolean.TRUE); + file.setIsRelatedDeleted(Boolean.FALSE); + } else { + file.setIsLocal(Boolean.FALSE); + FileAssociation fileAssociation = fileAssociationMapper.selectByPrimaryKey(fileRefId); + if (fileAssociation != null) { + // 关联文件信息同步 + FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(fileAssociation.getFileMetadataId()); + file.setIsRelatedDeleted(Boolean.FALSE); + file.setName(fileMetadata.getName()); + file.setSize(fileMetadata.getSize()); + file.setCreator(fileMetadata.getCreateUser()); + file.setCreateTime(fileMetadata.getCreateTime()); + } else { + file.setIsRelatedDeleted(Boolean.TRUE); + } + } + }); + attachments.addAll(fileAttachmentMetadata); } - FileAttachmentMetadataExample fileExample = new FileAttachmentMetadataExample(); - fileExample.createCriteria().andIdIn(attachmentIds); - return fileAttachmentMetadataMapper.selectByExample(fileExample); + return attachments; + } + + public void relate(AttachmentRequest request) { + // 批量关联文件管理 + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + FileAssociationMapper associationBatchMapper = sqlSession.getMapper(FileAssociationMapper.class); + AttachmentModuleRelationMapper attachmentModuleRelationBatchMapper = sqlSession.getMapper(AttachmentModuleRelationMapper.class); + FileAttachmentMetadataMapper fileAttachmentMetadataBatchMapper = sqlSession.getMapper(FileAttachmentMetadataMapper.class); + if (CollectionUtils.isNotEmpty(request.getMetadataRefIds())) { + request.getMetadataRefIds().forEach(metadataRefId -> { + FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(metadataRefId); + FileAssociation fileAssociation = new FileAssociation(); + fileAssociation.setId(UUID.randomUUID().toString()); + fileAssociation.setFileMetadataId(metadataRefId); + fileAssociation.setFileType(fileMetadata.getType()); + if (AttachmentType.TEST_CASE.type().equals(request.getBelongType())) { + fileAssociation.setType(FileAssociationType.TEST_CASE.name()); + } else { + fileAssociation.setType(FileAssociationType.ISSUE.name()); + } + fileAssociation.setProjectId(fileMetadata.getProjectId()); + fileAssociation.setSourceItemId(metadataRefId); + fileAssociation.setSourceId(request.getBelongId()); + associationBatchMapper.insert(fileAssociation); + AttachmentModuleRelation record = new AttachmentModuleRelation(); + record.setRelationId(request.getBelongId()); + record.setRelationType(request.getBelongType()); + record.setFileMetadataRefId(fileAssociation.getId()); + record.setAttachmentId(UUID.randomUUID().toString()); + attachmentModuleRelationBatchMapper.insert(record); + FileAttachmentMetadata fileAttachmentMetadata = new FileAttachmentMetadata(); + BeanUtils.copyBean(fileAttachmentMetadata, fileMetadata); + fileAttachmentMetadata.setId(record.getAttachmentId()); + fileAttachmentMetadata.setCreator(fileMetadata.getCreateUser()); + fileAttachmentMetadata.setFilePath(fileMetadata.getPath()); + fileAttachmentMetadataBatchMapper.insert(fileAttachmentMetadata); + // 缺陷类型的附件, 关联时需单独同步第三方平台 + if (AttachmentType.ISSUE.type().equals(request.getBelongType())) { + IssuesWithBLOBs issues = issuesMapper.selectByPrimaryKey(request.getBelongId()); + IssuesUpdateRequest updateRequest = new IssuesUpdateRequest(); + updateRequest.setPlatformId(issues.getPlatformId()); + File refFile = downloadMetadataFile(metadataRefId, fileMetadata.getName()); + IssuesRequest issuesRequest = new IssuesRequest(); + issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); + Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest)) + .syncIssuesAttachment(updateRequest, refFile, AttachmentSyncType.UPLOAD); + FileUtils.deleteFile(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileMetadata.getName()); + } + }); + sqlSession.flushStatements(); + if (sqlSession != null && sqlSessionFactory != null) { + SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); + } + } + + } + + public void unrelated(AttachmentRequest request) { + // 缺陷类型的附件, 取消关联时同步第三方平台 + if (AttachmentType.ISSUE.type().equals(request.getBelongType())) { + IssuesWithBLOBs issues = issuesMapper.selectByPrimaryKey(request.getBelongId()); + request.getMetadataRefIds().forEach(metadataRefId -> { + FileAttachmentMetadata fileAttachmentMetadata = fileAttachmentMetadataMapper.selectByPrimaryKey(metadataRefId); + IssuesUpdateRequest updateRequest = new IssuesUpdateRequest(); + updateRequest.setPlatformId(issues.getPlatformId()); + File deleteFile = new File(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileAttachmentMetadata.getName()); + IssuesRequest issuesRequest = new IssuesRequest(); + issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); + Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest)) + .syncIssuesAttachment(updateRequest, deleteFile, AttachmentSyncType.DELETE); + }); + } + AttachmentModuleRelationExample example = new AttachmentModuleRelationExample(); + example.createCriteria().andRelationIdEqualTo(request.getBelongId()) + .andRelationTypeEqualTo(request.getBelongType()) + .andAttachmentIdIn(request.getMetadataRefIds()); + FileAttachmentMetadataExample exampleAttachment = new FileAttachmentMetadataExample(); + exampleAttachment.createCriteria().andIdIn(request.getMetadataRefIds()); + fileAttachmentMetadataMapper.deleteByExample(exampleAttachment); + attachmentModuleRelationMapper.deleteByExample(example); } public List getAttachmentIdsByParam(AttachmentRequest request) { @@ -159,6 +311,15 @@ public class AttachmentService { return attachmentIds; } + public String getRefIdByAttachmentId(String attachmentId) { + AttachmentModuleRelationExample example = new AttachmentModuleRelationExample(); + example.createCriteria().andAttachmentIdEqualTo(attachmentId); + List relations = attachmentModuleRelationMapper.selectByExample(example); + String associationId = relations.get(0).getFileMetadataRefId(); + FileAssociation fileAssociation = fileAssociationMapper.selectByPrimaryKey(associationId); + return fileAssociation.getFileMetadataId(); + } + public void initAttachment() { List attachmentModuleRelations = new ArrayList<>(); List issueFiles = issueFileMapper.selectByExample(new IssueFileExample()); @@ -183,4 +344,9 @@ public class AttachmentService { } extAttachmentModuleRelationMapper.batchInsert(attachmentModuleRelations); } + + public File downloadMetadataFile(String fileMetadataRefId, String filename) { + byte[] refFileBytes = fileMetadataService.loadFileAsBytes(fileMetadataRefId); + return FileUtils.byteToFile(refFileBytes, FileUtils.ATTACHMENT_TMP_DIR, filename); + } } diff --git a/backend/src/main/java/io/metersphere/track/service/IssuesService.java b/backend/src/main/java/io/metersphere/track/service/IssuesService.java index c7a74fe246..f822177261 100644 --- a/backend/src/main/java/io/metersphere/track/service/IssuesService.java +++ b/backend/src/main/java/io/metersphere/track/service/IssuesService.java @@ -37,6 +37,10 @@ import io.metersphere.track.request.testcase.IssuesUpdateRequest; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionUtils; import org.springframework.context.annotation.Lazy; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; @@ -44,6 +48,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import java.io.File; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; @@ -91,11 +96,15 @@ public class IssuesService { @Resource IssueFileMapper issueFileMapper; @Resource + SqlSessionFactory sqlSessionFactory; + @Resource private AttachmentService attachmentService; @Resource private CustomFieldService customFieldService; @Resource private ProjectMapper projectMapper; + @Resource + private FileMetadataMapper fileMetadataMapper; private static final String SYNC_THIRD_PARTY_ISSUES_KEY = "ISSUE:SYNC"; @@ -129,9 +138,9 @@ public class IssuesService { attachmentRequest.setBelongType(AttachmentType.ISSUE.type()); attachmentService.copyAttachment(attachmentRequest); } else { + final String issueId = issues.getId(); // 新增, 需保存并同步所有待上传的附件 if (CollectionUtils.isNotEmpty(files)) { - final String issueId = issues.getId(); files.forEach(file -> { AttachmentRequest attachmentRequest = new AttachmentRequest(); attachmentRequest.setBelongId(issueId); @@ -139,6 +148,48 @@ public class IssuesService { attachmentService.uploadAttachment(attachmentRequest, file); }); } + // 处理待关联的文件附件, 生成关联记录, 并同步至第三方平台 + if (CollectionUtils.isNotEmpty(issuesRequest.getRelateFileMetaIds())) { + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + FileAssociationMapper associationBatchMapper = sqlSession.getMapper(FileAssociationMapper.class); + AttachmentModuleRelationMapper attachmentModuleRelationBatchMapper = sqlSession.getMapper(AttachmentModuleRelationMapper.class); + FileAttachmentMetadataMapper fileAttachmentMetadataBatchMapper = sqlSession.getMapper(FileAttachmentMetadataMapper.class); + issuesRequest.getRelateFileMetaIds().forEach(filemetaId -> { + FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(filemetaId); + FileAssociation fileAssociation = new FileAssociation(); + fileAssociation.setId(UUID.randomUUID().toString()); + fileAssociation.setFileMetadataId(filemetaId); + fileAssociation.setFileType(fileMetadata.getType()); + fileAssociation.setType(FileAssociationType.ISSUE.name()); + fileAssociation.setProjectId(fileMetadata.getProjectId()); + fileAssociation.setSourceItemId(filemetaId); + fileAssociation.setSourceId(issueId); + associationBatchMapper.insert(fileAssociation); + AttachmentModuleRelation relation = new AttachmentModuleRelation(); + relation.setRelationId(issueId); + relation.setRelationType(AttachmentType.ISSUE.type()); + relation.setFileMetadataRefId(fileAssociation.getId()); + relation.setAttachmentId(UUID.randomUUID().toString()); + attachmentModuleRelationBatchMapper.insert(relation); + FileAttachmentMetadata fileAttachmentMetadata = new FileAttachmentMetadata(); + BeanUtils.copyBean(fileAttachmentMetadata, fileMetadata); + fileAttachmentMetadata.setId(relation.getAttachmentId()); + fileAttachmentMetadata.setCreator(fileMetadata.getCreateUser()); + fileAttachmentMetadata.setFilePath(fileMetadata.getPath()); + fileAttachmentMetadataBatchMapper.insert(fileAttachmentMetadata); + // 下载文件管理文件, 同步到第三方平台 + File refFile = attachmentService.downloadMetadataFile(filemetaId, fileMetadata.getName()); + IssuesRequest addIssueRequest = new IssuesRequest(); + addIssueRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); + Objects.requireNonNull(IssueFactory.createPlatform(issuesRequest.getPlatform(), addIssueRequest)) + .syncIssuesAttachment(issuesRequest, refFile, AttachmentSyncType.UPLOAD); + FileUtils.deleteFile(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileMetadata.getName()); + }); + sqlSession.flushStatements(); + if (sqlSession != null && sqlSessionFactory != null) { + SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); + } + } } return getIssue(issues.getId()); } diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java index b30b0429f5..69e3347c6d 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java @@ -19,6 +19,7 @@ import io.metersphere.api.service.ApiTestCaseService; import io.metersphere.base.domain.*; import io.metersphere.base.domain.ext.CustomFieldResource; import io.metersphere.base.mapper.*; +import io.metersphere.base.mapper.ext.ExtAttachmentModuleRelationMapper; import io.metersphere.base.mapper.ext.ExtIssuesMapper; import io.metersphere.base.mapper.ext.ExtProjectVersionMapper; import io.metersphere.base.mapper.ext.ExtTestCaseMapper; @@ -135,6 +136,8 @@ public class TestCaseService { @Resource AttachmentModuleRelationMapper attachmentModuleRelationMapper; @Resource + ExtAttachmentModuleRelationMapper extAttachmentModuleRelationMapper; + @Resource private LoadTestMapper loadTestMapper; @Resource private ApiScenarioMapper apiScenarioMapper; @@ -2101,6 +2104,41 @@ public class TestCaseService { attachmentService.uploadAttachment(attachmentRequest, file); }); } + // 同步待关联的文件附件, 生成关联记录 + if (CollectionUtils.isNotEmpty(request.getRelateFileMetaIds())) { + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + FileAssociationMapper associationBatchMapper = sqlSession.getMapper(FileAssociationMapper.class); + AttachmentModuleRelationMapper attachmentModuleRelationBatchMapper = sqlSession.getMapper(AttachmentModuleRelationMapper.class); + FileAttachmentMetadataMapper fileAttachmentMetadataBatchMapper = sqlSession.getMapper(FileAttachmentMetadataMapper.class); + request.getRelateFileMetaIds().forEach(filemetaId -> { + FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(filemetaId); + FileAssociation fileAssociation = new FileAssociation(); + fileAssociation.setId(UUID.randomUUID().toString()); + fileAssociation.setFileMetadataId(filemetaId); + fileAssociation.setFileType(fileMetadata.getType()); + fileAssociation.setType(FileAssociationType.TEST_CASE.name()); + fileAssociation.setProjectId(fileMetadata.getProjectId()); + fileAssociation.setSourceItemId(filemetaId); + fileAssociation.setSourceId(testCaseWithBLOBs.getId()); + associationBatchMapper.insert(fileAssociation); + AttachmentModuleRelation record = new AttachmentModuleRelation(); + record.setRelationId(testCaseWithBLOBs.getId()); + record.setRelationType(AttachmentType.TEST_CASE.type()); + record.setFileMetadataRefId(fileAssociation.getId()); + record.setAttachmentId(UUID.randomUUID().toString()); + attachmentModuleRelationBatchMapper.insert(record); + FileAttachmentMetadata fileAttachmentMetadata = new FileAttachmentMetadata(); + BeanUtils.copyBean(fileAttachmentMetadata, fileMetadata); + fileAttachmentMetadata.setId(record.getAttachmentId()); + fileAttachmentMetadata.setCreator(fileMetadata.getCreateUser()); + fileAttachmentMetadata.setFilePath(fileMetadata.getPath()); + fileAttachmentMetadataBatchMapper.insert(fileAttachmentMetadata); + }); + sqlSession.flushStatements(); + if (sqlSession != null && sqlSessionFactory != null) { + SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); + } + } } return testCaseWithBLOBs; } diff --git a/backend/src/main/java/io/metersphere/xmind/utils/FileUtil.java b/backend/src/main/java/io/metersphere/xmind/utils/FileUtil.java index 80f9971998..72ce0b2b53 100644 --- a/backend/src/main/java/io/metersphere/xmind/utils/FileUtil.java +++ b/backend/src/main/java/io/metersphere/xmind/utils/FileUtil.java @@ -1,12 +1,13 @@ package io.metersphere.xmind.utils; +import io.metersphere.commons.utils.FileUtils; import io.metersphere.commons.utils.LogUtil; +import org.apache.commons.fileupload.disk.DiskFileItem; +import org.springframework.http.MediaType; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.commons.CommonsMultipartFile; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; /** * 工具类 @@ -47,6 +48,23 @@ public class FileUtil { return null; } + /** + * File 转 MultipartFile + * @param file + * @return + */ + public static MultipartFile fileToMultipartFile(File file) { + DiskFileItem item = new DiskFileItem("file", MediaType.MULTIPART_FORM_DATA_VALUE, true, + file.getName(), (int)file.length(), file.getParentFile()); + try { + OutputStream os = item.getOutputStream(); + os.write(FileUtils.fileToByte(file)); + } catch (IOException e) { + e.printStackTrace(); + } + return new CommonsMultipartFile(item); + } + public static boolean deleteDir(File dir) { if (dir.isDirectory()) { String[] children = dir.list(); diff --git a/backend/src/main/resources/db/migration/V130__2.2.0__release.sql b/backend/src/main/resources/db/migration/V130__2.2.0__release.sql index c290c1c2d8..bd13acb80c 100644 --- a/backend/src/main/resources/db/migration/V130__2.2.0__release.sql +++ b/backend/src/main/resources/db/migration/V130__2.2.0__release.sql @@ -151,3 +151,8 @@ ALTER TABLE `file_metadata` ADD INDEX `INDEX_REF_ID`(`ref_id`); ALTER TABLE `ui_scenario` ADD COLUMN scenario_type VARCHAR(100) NOT NULL DEFAULT 'scenario' COMMENT 'Scenario type' AFTER `level`; ALTER TABLE `ui_scenario_module` ADD COLUMN scenario_type VARCHAR(100) NOT NULL DEFAULT 'scenario' COMMENT 'Scenario type' AFTER `level`; + +-- 功能用例, 缺陷管理文件管理关联字段 +ALTER TABLE `attachment_module_relation` MODIFY COLUMN attachment_id VARCHAR(50) NULL; + +ALTER TABLE `attachment_module_relation` ADD COLUMN file_metadata_ref_id VARCHAR(50) DEFAULT NULL COMMENT 'FILE ASSOCIATION ID'; diff --git a/frontend/src/business/components/track/case/components/TestCaseAttachment.vue b/frontend/src/business/components/track/case/components/TestCaseAttachment.vue index 23ac62ca68..4ddc79dd41 100644 --- a/frontend/src/business/components/track/case/components/TestCaseAttachment.vue +++ b/frontend/src/business/components/track/case/components/TestCaseAttachment.vue @@ -2,13 +2,22 @@
- +