diff --git a/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelation.java b/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelation.java new file mode 100644 index 0000000000..3a7ab3a4b9 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelation.java @@ -0,0 +1,16 @@ +package io.metersphere.base.domain; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class AttachmentModuleRelation implements Serializable { + private String relationId; + + private String relationType; + + private String attachmentId; + + 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 new file mode 100644 index 0000000000..dcf86f143e --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/domain/AttachmentModuleRelationExample.java @@ -0,0 +1,410 @@ +package io.metersphere.base.domain; + +import java.util.ArrayList; +import java.util.List; + +public class AttachmentModuleRelationExample { + protected String orderByClause; + + protected boolean distinct; + + protected List oredCriteria; + + public AttachmentModuleRelationExample() { + oredCriteria = new ArrayList(); + } + + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; + } + + public String getOrderByClause() { + return orderByClause; + } + + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + + public boolean isDistinct() { + return distinct; + } + + public List getOredCriteria() { + return oredCriteria; + } + + public void or(Criteria criteria) { + oredCriteria.add(criteria); + } + + public Criteria or() { + Criteria criteria = createCriteriaInternal(); + oredCriteria.add(criteria); + return criteria; + } + + public Criteria createCriteria() { + Criteria criteria = createCriteriaInternal(); + if (oredCriteria.size() == 0) { + oredCriteria.add(criteria); + } + return criteria; + } + + protected Criteria createCriteriaInternal() { + Criteria criteria = new Criteria(); + return criteria; + } + + public void clear() { + oredCriteria.clear(); + orderByClause = null; + distinct = false; + } + + protected abstract static class GeneratedCriteria { + protected List criteria; + + protected GeneratedCriteria() { + super(); + criteria = new ArrayList(); + } + + public boolean isValid() { + return criteria.size() > 0; + } + + public List getAllCriteria() { + return criteria; + } + + public List getCriteria() { + return criteria; + } + + protected void addCriterion(String condition) { + if (condition == null) { + throw new RuntimeException("Value for condition cannot be null"); + } + criteria.add(new Criterion(condition)); + } + + protected void addCriterion(String condition, Object value, String property) { + if (value == null) { + throw new RuntimeException("Value for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value)); + } + + protected void addCriterion(String condition, Object value1, Object value2, String property) { + if (value1 == null || value2 == null) { + throw new RuntimeException("Between values for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value1, value2)); + } + + public Criteria andRelationIdIsNull() { + addCriterion("relation_id is null"); + return (Criteria) this; + } + + public Criteria andRelationIdIsNotNull() { + addCriterion("relation_id is not null"); + return (Criteria) this; + } + + public Criteria andRelationIdEqualTo(String value) { + addCriterion("relation_id =", value, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdNotEqualTo(String value) { + addCriterion("relation_id <>", value, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdGreaterThan(String value) { + addCriterion("relation_id >", value, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdGreaterThanOrEqualTo(String value) { + addCriterion("relation_id >=", value, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdLessThan(String value) { + addCriterion("relation_id <", value, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdLessThanOrEqualTo(String value) { + addCriterion("relation_id <=", value, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdLike(String value) { + addCriterion("relation_id like", value, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdNotLike(String value) { + addCriterion("relation_id not like", value, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdIn(List values) { + addCriterion("relation_id in", values, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdNotIn(List values) { + addCriterion("relation_id not in", values, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdBetween(String value1, String value2) { + addCriterion("relation_id between", value1, value2, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationIdNotBetween(String value1, String value2) { + addCriterion("relation_id not between", value1, value2, "relationId"); + return (Criteria) this; + } + + public Criteria andRelationTypeIsNull() { + addCriterion("relation_type is null"); + return (Criteria) this; + } + + public Criteria andRelationTypeIsNotNull() { + addCriterion("relation_type is not null"); + return (Criteria) this; + } + + public Criteria andRelationTypeEqualTo(String value) { + addCriterion("relation_type =", value, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeNotEqualTo(String value) { + addCriterion("relation_type <>", value, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeGreaterThan(String value) { + addCriterion("relation_type >", value, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeGreaterThanOrEqualTo(String value) { + addCriterion("relation_type >=", value, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeLessThan(String value) { + addCriterion("relation_type <", value, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeLessThanOrEqualTo(String value) { + addCriterion("relation_type <=", value, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeLike(String value) { + addCriterion("relation_type like", value, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeNotLike(String value) { + addCriterion("relation_type not like", value, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeIn(List values) { + addCriterion("relation_type in", values, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeNotIn(List values) { + addCriterion("relation_type not in", values, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeBetween(String value1, String value2) { + addCriterion("relation_type between", value1, value2, "relationType"); + return (Criteria) this; + } + + public Criteria andRelationTypeNotBetween(String value1, String value2) { + addCriterion("relation_type not between", value1, value2, "relationType"); + return (Criteria) this; + } + + public Criteria andAttachmentIdIsNull() { + addCriterion("attachment_id is null"); + return (Criteria) this; + } + + public Criteria andAttachmentIdIsNotNull() { + addCriterion("attachment_id is not null"); + return (Criteria) this; + } + + public Criteria andAttachmentIdEqualTo(String value) { + addCriterion("attachment_id =", value, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdNotEqualTo(String value) { + addCriterion("attachment_id <>", value, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdGreaterThan(String value) { + addCriterion("attachment_id >", value, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdGreaterThanOrEqualTo(String value) { + addCriterion("attachment_id >=", value, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdLessThan(String value) { + addCriterion("attachment_id <", value, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdLessThanOrEqualTo(String value) { + addCriterion("attachment_id <=", value, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdLike(String value) { + addCriterion("attachment_id like", value, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdNotLike(String value) { + addCriterion("attachment_id not like", value, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdIn(List values) { + addCriterion("attachment_id in", values, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdNotIn(List values) { + addCriterion("attachment_id not in", values, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdBetween(String value1, String value2) { + addCriterion("attachment_id between", value1, value2, "attachmentId"); + return (Criteria) this; + } + + public Criteria andAttachmentIdNotBetween(String value1, String value2) { + addCriterion("attachment_id not between", value1, value2, "attachmentId"); + return (Criteria) this; + } + } + + public static class Criteria extends GeneratedCriteria { + + protected Criteria() { + super(); + } + } + + public static class Criterion { + private String condition; + + private Object value; + + private Object secondValue; + + private boolean noValue; + + private boolean singleValue; + + private boolean betweenValue; + + private boolean listValue; + + private String typeHandler; + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } + + protected Criterion(String condition) { + super(); + this.condition = condition; + this.typeHandler = null; + this.noValue = true; + } + + protected Criterion(String condition, Object value, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.typeHandler = typeHandler; + if (value instanceof List) { + this.listValue = true; + } else { + this.singleValue = true; + } + } + + protected Criterion(String condition, Object value) { + this(condition, value, null); + } + + protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.secondValue = secondValue; + this.typeHandler = typeHandler; + this.betweenValue = true; + } + + protected Criterion(String condition, Object value, Object secondValue) { + this(condition, value, secondValue, null); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/AttachmentModuleRelationMapper.java b/backend/src/main/java/io/metersphere/base/mapper/AttachmentModuleRelationMapper.java new file mode 100644 index 0000000000..8f18100a28 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/AttachmentModuleRelationMapper.java @@ -0,0 +1,23 @@ +package io.metersphere.base.mapper; + +import io.metersphere.base.domain.AttachmentModuleRelation; +import io.metersphere.base.domain.AttachmentModuleRelationExample; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface AttachmentModuleRelationMapper { + long countByExample(AttachmentModuleRelationExample example); + + int deleteByExample(AttachmentModuleRelationExample example); + + int insert(AttachmentModuleRelation record); + + int insertSelective(AttachmentModuleRelation record); + + List selectByExample(AttachmentModuleRelationExample example); + + int updateByExampleSelective(@Param("record") AttachmentModuleRelation record, @Param("example") AttachmentModuleRelationExample example); + + int updateByExample(@Param("record") AttachmentModuleRelation record, @Param("example") AttachmentModuleRelationExample example); +} \ 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 new file mode 100644 index 0000000000..8e7087aa88 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/AttachmentModuleRelationMapper.xml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + relation_id, relation_type, attachment_id + + + + delete from attachment_module_relation + + + + + + insert into attachment_module_relation (relation_id, relation_type, attachment_id + ) + values (#{relationId,jdbcType=VARCHAR}, #{relationType,jdbcType=VARCHAR}, #{attachmentId,jdbcType=VARCHAR} + ) + + + insert into attachment_module_relation + + + relation_id, + + + relation_type, + + + attachment_id, + + + + + #{relationId,jdbcType=VARCHAR}, + + + #{relationType,jdbcType=VARCHAR}, + + + #{attachmentId,jdbcType=VARCHAR}, + + + + + + update attachment_module_relation + + + relation_id = #{record.relationId,jdbcType=VARCHAR}, + + + relation_type = #{record.relationType,jdbcType=VARCHAR}, + + + attachment_id = #{record.attachmentId,jdbcType=VARCHAR}, + + + + + + + + update attachment_module_relation + set relation_id = #{record.relationId,jdbcType=VARCHAR}, + relation_type = #{record.relationType,jdbcType=VARCHAR}, + attachment_id = #{record.attachmentId,jdbcType=VARCHAR} + + + + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtAttachmentModuleRelationMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtAttachmentModuleRelationMapper.java new file mode 100644 index 0000000000..2530f15a3a --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtAttachmentModuleRelationMapper.java @@ -0,0 +1,18 @@ +package io.metersphere.base.mapper.ext; + +import io.metersphere.base.domain.AttachmentModuleRelation; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @author songcc + */ +public interface ExtAttachmentModuleRelationMapper { + + /** + * 批量插入 + * @param attachmentModuleRelations 附件关系记录 + */ + void batchInsert(@Param("attachmentModuleRelations") List attachmentModuleRelations); +} 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 new file mode 100644 index 0000000000..e2c7c87582 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtAttachmentModuleRelationMapper.xml @@ -0,0 +1,12 @@ + + + + + INSERT INTO + attachment_module_relation (relation_id, relation_type, attachment_id) + VALUES + + (#{relation.relationId}, #{relation.relationType}, #{relation.attachmentId}) + + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/commons/constants/AttachmentSyncType.java b/backend/src/main/java/io/metersphere/commons/constants/AttachmentSyncType.java new file mode 100644 index 0000000000..1f4c40f34f --- /dev/null +++ b/backend/src/main/java/io/metersphere/commons/constants/AttachmentSyncType.java @@ -0,0 +1,28 @@ +package io.metersphere.commons.constants; + +/** + * @author songcc + */ + +public enum AttachmentSyncType { + + /** + * 附件上传 + */ + UPLOAD("upload"), + + /** + * 附件删除 + */ + DELETE("delete"); + + private String syncOperateType; + + AttachmentSyncType(String syncOperateType) { + this.syncOperateType = syncOperateType; + } + + public String syncOperateType() { + return syncOperateType; + } +} diff --git a/backend/src/main/java/io/metersphere/commons/constants/AttachmentType.java b/backend/src/main/java/io/metersphere/commons/constants/AttachmentType.java index 36225a93c1..27713632b8 100644 --- a/backend/src/main/java/io/metersphere/commons/constants/AttachmentType.java +++ b/backend/src/main/java/io/metersphere/commons/constants/AttachmentType.java @@ -1,10 +1,21 @@ package io.metersphere.commons.constants; +/** + * @author songcc + */ + public enum AttachmentType { - TEST_CASE("testcase"), ISSUE("issue"); + /** + * 测试用例类型 + */ + TEST_CASE("testcase"), + + /** + * 缺陷类型 + */ + ISSUE("issue"); - // 附件类型名称 private String type; AttachmentType(String type) { diff --git a/backend/src/main/java/io/metersphere/commons/utils/ShiroUtils.java b/backend/src/main/java/io/metersphere/commons/utils/ShiroUtils.java index a3a2d8d285..feb3e561ce 100644 --- a/backend/src/main/java/io/metersphere/commons/utils/ShiroUtils.java +++ b/backend/src/main/java/io/metersphere/commons/utils/ShiroUtils.java @@ -79,8 +79,7 @@ public class ShiroUtils { public static void ignoreCsrfFilter(Map filterChainDefinitionMap) { filterChainDefinitionMap.put("/", "apikey, authc"); // 跳转到 / 不用校验 csrf filterChainDefinitionMap.put("/language", "apikey, authc");// 跳转到 /language 不用校验 csrf - filterChainDefinitionMap.put("/test/case/file/preview/**", "apikey, authc"); // 预览测试用例附件 不用校验 csrf - filterChainDefinitionMap.put("/test/case/attachment/preview/**", "apikey, authc"); // 预览测试用例附件 不用校验 csrf + filterChainDefinitionMap.put("/attachment/preview/**", "apikey, authc"); // 预览测试用例附件 不用校验 csrf filterChainDefinitionMap.put("/mock", "apikey, authc"); // 跳转到 /mock接口 不用校验 csrf } diff --git a/backend/src/main/java/io/metersphere/listener/AppStartListener.java b/backend/src/main/java/io/metersphere/listener/AppStartListener.java index e78b0d70dc..60467cb5f8 100644 --- a/backend/src/main/java/io/metersphere/listener/AppStartListener.java +++ b/backend/src/main/java/io/metersphere/listener/AppStartListener.java @@ -50,6 +50,8 @@ public class AppStartListener implements ApplicationListener previewAttachment(@PathVariable String fileId) { + byte[] bytes = fileService.getAttachmentBytes(fileId); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType("application/octet-stream")) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileId + "\"") + .body(bytes); + } + + @PostMapping("/download") + public ResponseEntity downloadAttachment(@RequestBody FileOperationRequest fileOperationRequest) { + byte[] bytes = fileService.getAttachmentBytes(fileOperationRequest.getId()); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType("application/octet-stream")) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileOperationRequest.getName(), StandardCharsets.UTF_8) + "\"") + .body(bytes); + } + + @GetMapping("/delete/{attachmentType}/{attachmentId}") + public void deleteAttachment(@PathVariable String attachmentId, @PathVariable String attachmentType) { + attachmentService.deleteAttachment(attachmentId, attachmentType); + } + + @PostMapping("/metadata/list") + public List listMetadata(@RequestBody AttachmentRequest request) { + return attachmentService.listMetadata(request); + } +} diff --git a/backend/src/main/java/io/metersphere/track/controller/IssuesController.java b/backend/src/main/java/io/metersphere/track/controller/IssuesController.java index 62b516b94f..297e740b2a 100644 --- a/backend/src/main/java/io/metersphere/track/controller/IssuesController.java +++ b/backend/src/main/java/io/metersphere/track/controller/IssuesController.java @@ -2,7 +2,6 @@ package io.metersphere.track.controller; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; -import io.metersphere.base.domain.FileAttachmentMetadata; import io.metersphere.base.domain.Issues; import io.metersphere.base.domain.IssuesDao; import io.metersphere.base.domain.IssuesWithBLOBs; @@ -27,10 +26,8 @@ import io.metersphere.track.request.testcase.AuthUserIssueRequest; import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest; import io.metersphere.track.service.IssuesService; -import io.metersphere.track.service.TestCaseService; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.util.List; @@ -83,22 +80,6 @@ public class IssuesController { issuesService.updateIssues(issuesRequest); } - @PostMapping(value = "/attachment/upload", consumes = {"multipart/form-data"}) - @MsAuditLog(module = OperLogModule.TRACK_BUG, type = OperLogConstants.IMPORT, beforeEvent = "#msClass.getLogDetails(#issuesRequest.id)", content = "#msClass.getLogDetails(#request.id)", msClass = TestCaseService.class) - public void uploadAttachment(@RequestPart("request") IssuesUpdateRequest issuesRequest, @RequestPart(value = "file", required = false) MultipartFile file) { - issuesService.uploadAttachment(issuesRequest, file); - } - - @GetMapping("/attachment/delete/{fileId}") - public void deleteAttachment(@PathVariable String fileId) { - issuesService.deleteAttachment(fileId); - } - - @GetMapping("/file/attachmentMetadata/{issueId}") - public List getFileAttachmentMetadataByIssueId(@PathVariable String issueId) { - return fileService.getFileAttachmentMetadataByIssueId(issueId); - } - @GetMapping("/get/case/{refType}/{id}") @RequiresPermissions(PermissionConstants.PROJECT_TRACK_ISSUE_READ) public List getIssues(@PathVariable String refType, @PathVariable String id) { diff --git a/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java b/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java index 27d8087ef8..13ffe0361f 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java @@ -6,7 +6,10 @@ import io.metersphere.api.dto.automation.ApiScenarioDTO; import io.metersphere.api.dto.automation.ApiScenarioRequest; import io.metersphere.api.dto.definition.ApiTestCaseDTO; import io.metersphere.api.dto.definition.ApiTestCaseRequest; -import io.metersphere.base.domain.*; +import io.metersphere.base.domain.FileMetadata; +import io.metersphere.base.domain.Project; +import io.metersphere.base.domain.TestCase; +import io.metersphere.base.domain.TestCaseWithBLOBs; import io.metersphere.base.mapper.TestCaseMapper; import io.metersphere.commons.constants.NoticeConstants; import io.metersphere.commons.constants.OperLogConstants; @@ -371,11 +374,6 @@ public class TestCaseController { return fileService.getFileMetadataByCaseId(caseId); } - @GetMapping("/file/attachmentMetadata/{caseId}") - public List getFileAttachmentMetadataByCaseId(@PathVariable String caseId) { - return fileService.getFileAttachmentMetadataByCaseId(caseId); - } - @PostMapping("/file/download") public ResponseEntity download(@RequestBody FileOperationRequest fileOperationRequest) { byte[] bytes = fileService.loadFileAsBytes(fileOperationRequest.getId()); @@ -394,35 +392,6 @@ public class TestCaseController { .body(bytes); } - @PostMapping(value = "/attachment/upload", consumes = {"multipart/form-data"}) - @MsAuditLog(module = OperLogModule.TRACK_TEST_CASE, type = OperLogConstants.IMPORT, beforeEvent = "#msClass.getLogDetails(#request.id)", title = "#request.name", content = "#msClass.getLogDetails(#request.id)", msClass = TestCaseService.class) - public void uploadAttachment(@RequestPart("request") EditTestCaseRequest request, @RequestPart(value = "file", required = false) MultipartFile file) { - testCaseService.uploadAttachment(request, file); - } - - @GetMapping("/attachment/preview/{fileId}") - public ResponseEntity previewAttachment(@PathVariable String fileId) { - byte[] bytes = fileService.getAttachmentBytes(fileId); - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType("application/octet-stream")) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileId + "\"") - .body(bytes); - } - - @PostMapping("/attachment/download") - public ResponseEntity downloadAttachment(@RequestBody FileOperationRequest fileOperationRequest) { - byte[] bytes = fileService.getAttachmentBytes(fileOperationRequest.getId()); - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType("application/octet-stream")) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileOperationRequest.getName(), StandardCharsets.UTF_8) + "\"") - .body(bytes); - } - - @GetMapping("/attachment/delete/{fileId}") - public void deleteAttachment(@PathVariable String fileId) { - testCaseService.deleteAttachment(fileId); - } - @PostMapping("/save") @MsAuditLog(module = OperLogModule.TRACK_TEST_CASE, type = OperLogConstants.CREATE, title = "#testCaseWithBLOBs.name", content = "#msClass.getLogDetails(#testCaseWithBLOBs.id)", msClass = TestCaseService.class) public TestCaseWithBLOBs saveTestCase(@RequestBody EditTestCaseRequest request) { 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 bf784db9a9..73d96676e6 100644 --- a/backend/src/main/java/io/metersphere/track/issue/AbstractIssuePlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/AbstractIssuePlatform.java @@ -4,7 +4,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import io.metersphere.base.domain.*; -import io.metersphere.base.mapper.IssueFileMapper; +import io.metersphere.base.mapper.AttachmentModuleRelationMapper; import io.metersphere.base.mapper.IssuesMapper; import io.metersphere.base.mapper.ProjectMapper; import io.metersphere.base.mapper.TestCaseIssuesMapper; @@ -24,6 +24,7 @@ import io.metersphere.track.issue.domain.ProjectIssueConfig; import io.metersphere.track.request.testcase.EditTestCaseRequest; import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest; +import io.metersphere.track.service.AttachmentService; import io.metersphere.track.service.IssuesService; import io.metersphere.track.service.TestCaseIssueService; import io.metersphere.track.service.TestCaseService; @@ -67,7 +68,8 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform { protected CustomFieldService customFieldService; protected IssuesService issuesService; protected FileService fileService; - protected IssueFileMapper issueFileMapper; + protected AttachmentService attachmentService; + protected AttachmentModuleRelationMapper attachmentModuleRelationMapper; public String getKey() { return key; @@ -96,7 +98,8 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform { this.customFieldService = CommonBeanFactory.getBean(CustomFieldService.class); this.issuesService = CommonBeanFactory.getBean(IssuesService.class); this.fileService = CommonBeanFactory.getBean(FileService.class); - this.issueFileMapper = CommonBeanFactory.getBean(IssueFileMapper.class); + this.attachmentService = CommonBeanFactory.getBean(AttachmentService.class); + this.attachmentModuleRelationMapper = CommonBeanFactory.getBean(AttachmentModuleRelationMapper.class); } protected String getPlatformConfig(String platform) { diff --git a/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java b/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java index 57b1d84dd7..b6c65ffb3f 100644 --- a/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java @@ -3,6 +3,7 @@ package io.metersphere.track.issue; import io.metersphere.base.domain.IssuesDao; import io.metersphere.base.domain.IssuesWithBLOBs; import io.metersphere.base.domain.Project; +import io.metersphere.commons.constants.AttachmentSyncType; import io.metersphere.dto.IssueTemplateDao; import io.metersphere.dto.UserDTO; import io.metersphere.track.dto.DemandDTO; @@ -12,6 +13,7 @@ import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest; import org.springframework.http.ResponseEntity; +import java.io.File; import java.util.List; public interface IssuesPlatform { @@ -113,4 +115,12 @@ public interface IssuesPlatform { * @return */ ResponseEntity proxyForGet(String url, Class responseEntityClazz); + + /** + * 同步MS缺陷附件到第三方平台 + * @param issuesRequest 平台参数 + * @param file 附件 + * @param syncType 同步操作类型: UPLOAD, DELETE + */ + void syncIssuesAttachment(IssuesUpdateRequest issuesRequest, File file, AttachmentSyncType syncType); } diff --git a/backend/src/main/java/io/metersphere/track/issue/JiraPlatform.java b/backend/src/main/java/io/metersphere/track/issue/JiraPlatform.java index 999c5e44fb..187afcbbaf 100644 --- a/backend/src/main/java/io/metersphere/track/issue/JiraPlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/JiraPlatform.java @@ -4,10 +4,7 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import io.metersphere.base.domain.*; import io.metersphere.base.domain.ext.CustomFieldResource; -import io.metersphere.commons.constants.AttachmentType; -import io.metersphere.commons.constants.CustomFieldType; -import io.metersphere.commons.constants.IssuesManagePlatform; -import io.metersphere.commons.constants.IssuesStatus; +import io.metersphere.commons.constants.*; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.LogUtil; @@ -22,6 +19,7 @@ import io.metersphere.track.issue.client.JiraClientV2; import io.metersphere.track.issue.domain.PlatformUser; import io.metersphere.track.issue.domain.ProjectIssueConfig; import io.metersphere.track.issue.domain.jira.*; +import io.metersphere.track.request.attachment.AttachmentRequest; import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest; import io.metersphere.track.service.IssuesService; @@ -243,15 +241,15 @@ public class JiraPlatform extends AbstractIssuePlatform { // 用例与第三方缺陷平台中的缺陷关联 handleTestCaseIssues(issuesRequest); - // 如果是复制新增, 同步附件到第三方平台 + // 如果是复制新增, 同步MS附件到Jira if (StringUtils.isNotEmpty(issuesRequest.getCopyIssueId())) { - IssueFileExample example = new IssueFileExample(); - example.createCriteria().andIssueIdEqualTo(issuesRequest.getCopyIssueId()); - List issueFiles = issueFileMapper.selectByExample(example); - if (issueFiles != null) { - issueFiles.forEach(issueFile -> { - FileAttachmentMetadata fileAttachmentMetadata = fileService.getFileAttachmentMetadataByFileId(issueFile.getFileId()); - // 同步第三方平台附件 + AttachmentRequest request = new AttachmentRequest(); + request.setBelongId(issuesRequest.getCopyIssueId()); + request.setBelongType(AttachmentType.ISSUE.type()); + List attachmentIds = attachmentService.getAttachmentIdsByParam(request); + if (CollectionUtils.isNotEmpty(attachmentIds)) { + attachmentIds.forEach(attachmentId -> { + FileAttachmentMetadata fileAttachmentMetadata = fileService.getFileAttachmentMetadataByFileId(attachmentId); File file = new File(fileAttachmentMetadata.getFilePath() + "/" + fileAttachmentMetadata.getName()); jiraClientV2.uploadAttachment(result.getKey(), file); }); @@ -474,46 +472,9 @@ public class JiraPlatform extends AbstractIssuePlatform { public void updateIssue(IssuesUpdateRequest request) { setUserConfig(); Project project = getProject(); - List imageFiles = getImageFiles(request); - JSONObject param = buildUpdateParam(request, getIssueType(project.getIssueConfig()), project.getJiraKey()); jiraClientV2.updateIssue(request.getPlatformId(), JSONObject.toJSONString(param)); - List newFiles = fileService.getFileAttachmentMetadataByIssueId(request.getId()); - List newFileNames = newFiles.stream().map(FileAttachmentMetadata::getName).collect(Collectors.toList()); - Set attachmentNames = new HashSet<>(); - // 同步Jira平台附件 - JiraIssue jiraIssue = jiraClientV2.getIssues(request.getPlatformId()); - JSONObject fields = jiraIssue.getFields(); - JSONArray attachments = fields.getJSONArray("attachment"); - // 删除旧附件,若缺陷描述中不存在且附件上传的删除列表中存在则删除 - if (!attachments.isEmpty() && attachments.size() > 0) { - for (int i = 0; i < attachments.size(); i++) { - JSONObject attachment = attachments.getJSONObject(i); - String filename = attachment.getString("filename"); - attachmentNames.add(filename); - if (!request.getDescription().contains(filename) && !newFileNames.contains(filename)) { - String fileId = attachment.getString("id"); - jiraClientV2.deleteAttachment(fileId); - } - } - } - //上传新附件 - if (CollectionUtils.isNotEmpty(newFiles)) { - newFiles.forEach(file -> { - if (!attachmentNames.contains(file.getName())) { - File newFile = new File(file.getFilePath() + "/" + file.getName()); - jiraClientV2.uploadAttachment(request.getPlatformId(), newFile); - } - }); - } - imageFiles.forEach(img -> { - if (!attachmentNames.contains(img.getName())) { - // 旧附件没有才上传新附件 - jiraClientV2.uploadAttachment(request.getPlatformId(), img); - } - }); - if (request.getTransitions() != null) { try { List transitions = jiraClientV2.getTransitions(request.getPlatformId()); @@ -873,8 +834,35 @@ public class JiraPlatform extends AbstractIssuePlatform { return jiraClientV2.proxyForGet(url, responseEntityClazz); } + @Override + public void syncIssuesAttachment(IssuesUpdateRequest issuesRequest, File file, AttachmentSyncType syncType) { + // 同步缺陷MS附件到Jira + if ("upload".equals(syncType.syncOperateType())) { + // 上传附件 + jiraClientV2.uploadAttachment(issuesRequest.getPlatformId(), file); + } else if ("delete".equals(syncType.syncOperateType())) { + // 删除附件 + JiraIssue jiraIssue = jiraClientV2.getIssues(issuesRequest.getPlatformId()); + JSONObject fields = jiraIssue.getFields(); + JSONArray attachments = fields.getJSONArray("attachment"); + if (!attachments.isEmpty() && attachments.size() > 0) { + for (int i = 0; i < attachments.size(); i++) { + JSONObject attachment = attachments.getJSONObject(i); + String filename = attachment.getString("filename"); + if (filename.equals(file.getName())) { + String fileId = attachment.getString("id"); + jiraClientV2.deleteAttachment(fileId); + } + } + } + } + } + public void syncJiraIssueAttachments(IssuesWithBLOBs issue, JiraIssue jiraIssue) { - issuesService.deleteIssueAttachments(issue.getId()); + AttachmentRequest request = new AttachmentRequest(); + request.setBelongType(AttachmentType.ISSUE.type()); + request.setBelongId(issue.getId()); + attachmentService.deleteAttachment(request); JSONArray attachments = jiraIssue.getFields().getJSONArray("attachment"); if (CollectionUtils.isEmpty(attachments)) { return; @@ -882,14 +870,16 @@ public class JiraPlatform extends AbstractIssuePlatform { for (int i = 0; i < attachments.size(); i++) { JSONObject attachment = attachments.getJSONObject(i); String filename = attachment.getString("filename"); - if (!issue.getDescription().contains(filename) && !issue.getCustomFields().contains(filename)) { + if ((issue.getDescription() == null || !issue.getDescription().contains(filename)) + && (issue.getCustomFields() == null || !issue.getCustomFields().contains(filename))) { String id = attachment.getString("id"); byte[] content = jiraClientV2.getAttachmentContent(id); FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachmentByBytes(content, AttachmentType.ISSUE.type(), issue.getId(), filename); - IssueFile issueFile = new IssueFile(); - issueFile.setIssueId(issue.getId()); - issueFile.setFileId(fileAttachmentMetadata.getId()); - issueFileMapper.insert(issueFile); + AttachmentModuleRelation attachmentModuleRelation = new AttachmentModuleRelation(); + attachmentModuleRelation.setAttachmentId(fileAttachmentMetadata.getId()); + attachmentModuleRelation.setRelationId(issue.getId()); + attachmentModuleRelation.setRelationType(AttachmentType.ISSUE.type()); + attachmentModuleRelationMapper.insert(attachmentModuleRelation); } } } diff --git a/backend/src/main/java/io/metersphere/track/issue/LocalPlatform.java b/backend/src/main/java/io/metersphere/track/issue/LocalPlatform.java index fe16dd1b1e..76e9d2488f 100644 --- a/backend/src/main/java/io/metersphere/track/issue/LocalPlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/LocalPlatform.java @@ -3,6 +3,7 @@ package io.metersphere.track.issue; import com.alibaba.fastjson.JSONObject; import io.metersphere.base.domain.IssuesDao; import io.metersphere.base.domain.IssuesWithBLOBs; +import io.metersphere.commons.constants.AttachmentSyncType; import io.metersphere.commons.constants.IssuesManagePlatform; import io.metersphere.commons.user.SessionUser; import io.metersphere.commons.utils.BeanUtils; @@ -14,6 +15,7 @@ import io.metersphere.track.request.testcase.IssuesUpdateRequest; import io.metersphere.track.request.testcase.TestCaseBatchRequest; import org.apache.commons.lang3.StringUtils; +import java.io.File; import java.util.List; import java.util.UUID; @@ -78,4 +80,10 @@ public class LocalPlatform extends LocalAbstractPlatform { public void updateIssue(IssuesUpdateRequest request) { handleIssueUpdate(request); } + + @Override + public void syncIssuesAttachment(IssuesUpdateRequest issuesRequest, File file, AttachmentSyncType syncType) { + // 不需要同步 + } + } diff --git a/backend/src/main/java/io/metersphere/track/issue/TapdPlatform.java b/backend/src/main/java/io/metersphere/track/issue/TapdPlatform.java index 376513346b..8e5ceee4a0 100644 --- a/backend/src/main/java/io/metersphere/track/issue/TapdPlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/TapdPlatform.java @@ -7,6 +7,7 @@ import io.metersphere.base.domain.IssuesDao; import io.metersphere.base.domain.IssuesWithBLOBs; import io.metersphere.base.domain.Project; import io.metersphere.base.domain.ext.CustomFieldResource; +import io.metersphere.commons.constants.AttachmentSyncType; import io.metersphere.commons.constants.IssuesManagePlatform; import io.metersphere.commons.constants.IssuesStatus; import io.metersphere.commons.exception.MSException; @@ -31,6 +32,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import java.io.File; import java.util.*; import java.util.stream.Collectors; @@ -306,6 +308,11 @@ public class TapdPlatform extends AbstractIssuePlatform { } } + @Override + public void syncIssuesAttachment(IssuesUpdateRequest issuesRequest, File file, AttachmentSyncType syncType) { + // TODO: 同步缺陷MS附件到TAPD + } + @Override public List getTransitions(String issueKey) { List platformStatusDTOS = new ArrayList<>(); diff --git a/backend/src/main/java/io/metersphere/track/issue/ZentaoPlatform.java b/backend/src/main/java/io/metersphere/track/issue/ZentaoPlatform.java index b950a3b708..5d2b236829 100644 --- a/backend/src/main/java/io/metersphere/track/issue/ZentaoPlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/ZentaoPlatform.java @@ -9,6 +9,7 @@ import io.metersphere.base.domain.IssuesExample; import io.metersphere.base.domain.IssuesWithBLOBs; import io.metersphere.base.domain.Project; import io.metersphere.base.domain.ext.CustomFieldResource; +import io.metersphere.commons.constants.AttachmentSyncType; import io.metersphere.commons.constants.IssuesManagePlatform; import io.metersphere.commons.constants.IssuesStatus; import io.metersphere.commons.constants.ZentaoIssuePlatformStatus; @@ -38,6 +39,7 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; +import java.io.File; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -537,6 +539,11 @@ public class ZentaoPlatform extends AbstractIssuePlatform { return zentaoClient.checkProjectExist(relateId); } + @Override + public void syncIssuesAttachment(IssuesUpdateRequest issuesRequest, File file, AttachmentSyncType syncType) { + // TODO: 同步缺陷MS附件到禅道 + } + @Override public List getTransitions(String issueKey) { List platformStatusDTOS = new ArrayList<>(); 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 new file mode 100644 index 0000000000..92f74a9ae4 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/request/attachment/AttachmentRequest.java @@ -0,0 +1,17 @@ +package io.metersphere.track.request.attachment; + +import lombok.Data; + + +/** + * @author songcc + */ +@Data +public class AttachmentRequest { + + private String belongType; + + private String belongId; + + private String copyBelongId; +} diff --git a/backend/src/main/java/io/metersphere/track/service/AttachmentService.java b/backend/src/main/java/io/metersphere/track/service/AttachmentService.java new file mode 100644 index 0000000000..5c438d0ce4 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/service/AttachmentService.java @@ -0,0 +1,186 @@ +package io.metersphere.track.service; + +import io.metersphere.base.domain.*; +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.exception.MSException; +import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.i18n.Translator; +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.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.stream.Collectors; + +/** + * @author songcc + */ +@Service +@Transactional(rollbackFor = Exception.class) +public class AttachmentService { + + @Resource + FileService fileService; + @Resource + private IssuesMapper issuesMapper; + @Resource + private IssueFileMapper issueFileMapper; + @Resource + private TestCaseMapper testCaseMapper; + @Resource + private TestCaseFileMapper testCaseFileMapper; + @Resource + private FileAttachmentMetadataMapper fileAttachmentMetadataMapper; + @Resource + private AttachmentModuleRelationMapper attachmentModuleRelationMapper; + @Resource + private ExtAttachmentModuleRelationMapper extAttachmentModuleRelationMapper; + + public void uploadAttachment(AttachmentRequest request, MultipartFile file) { + // 附件上传的前置校验 + if (AttachmentType.ISSUE.type().equals(request.getBelongType())) { + IssuesWithBLOBs issues = issuesMapper.selectByPrimaryKey(request.getBelongId()); + if (issues == null) { + MSException.throwException(Translator.get("issues_attachment_upload_not_found") + request.getBelongId()); + } + } else if (AttachmentType.TEST_CASE.type().equals(request.getBelongType())) { + TestCaseWithBLOBs testCase = testCaseMapper.selectByPrimaryKey(request.getBelongId()); + if (testCase == null) { + MSException.throwException(Translator.get("test_case_attachment_upload_not_found") + request.getBelongId()); + } + } + + // 上传MS平台 + FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachment(file, request.getBelongType(), request.getBelongId()); + AttachmentModuleRelation attachmentModuleRelation = new AttachmentModuleRelation(); + attachmentModuleRelation.setRelationId(request.getBelongId()); + attachmentModuleRelation.setRelationType(request.getBelongType()); + attachmentModuleRelation.setAttachmentId(fileAttachmentMetadata.getId()); + attachmentModuleRelationMapper.insert(attachmentModuleRelation); + + // 附件上传完成后的后置操作 + // 缺陷类型的附件, 需单独同步第三方平台 + if (AttachmentType.ISSUE.type().equals(request.getBelongType())) { + IssuesWithBLOBs issues = issuesMapper.selectByPrimaryKey(request.getBelongId()); + IssuesUpdateRequest updateRequest = new IssuesUpdateRequest(); + updateRequest.setPlatformId(issues.getPlatformId()); + File uploadFile = new File(fileAttachmentMetadata.getFilePath() + "/" + fileAttachmentMetadata.getName()); + IssuesRequest issuesRequest = new IssuesRequest(); + issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); + Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest)) + .syncIssuesAttachment(updateRequest, uploadFile, AttachmentSyncType.UPLOAD); + } + } + + public void deleteAttachment(String attachmentId, String attachmentType) { + FileAttachmentMetadata fileAttachmentMetadata = fileAttachmentMetadataMapper.selectByPrimaryKey(attachmentId); + List ids = List.of(attachmentId); + AttachmentModuleRelationExample example = new AttachmentModuleRelationExample(); + example.createCriteria().andAttachmentIdIn(ids).andRelationTypeEqualTo(attachmentType); + // 缺陷类型的附件, 需先同步第三方平台 + if (AttachmentType.ISSUE.type().equals(attachmentType)) { + List moduleRelations = attachmentModuleRelationMapper.selectByExample(example); + if (CollectionUtils.isNotEmpty(moduleRelations) && moduleRelations.size() == 1) { + IssuesWithBLOBs issues = issuesMapper.selectByPrimaryKey(moduleRelations.get(0).getRelationId()); + IssuesUpdateRequest updateRequest = new IssuesUpdateRequest(); + updateRequest.setPlatformId(issues.getPlatformId()); + File deleteFile = new File(fileAttachmentMetadata.getFilePath() + "/" + fileAttachmentMetadata.getName()); + IssuesRequest issuesRequest = new IssuesRequest(); + issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); + Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest)) + .syncIssuesAttachment(updateRequest, deleteFile, AttachmentSyncType.DELETE); + } + } + + // 删除MS附件及关联数据 + fileService.deleteAttachment(ids); + fileService.deleteFileAttachmentByIds(ids); + attachmentModuleRelationMapper.deleteByExample(example); + } + + public void deleteAttachment(AttachmentRequest request) { + fileService.deleteAttachment(request.getBelongType(), request.getBelongId()); + List attachmentIds = getAttachmentIdsByParam(request); + if (CollectionUtils.isNotEmpty(attachmentIds)) { + FileAttachmentMetadataExample fileAttachmentMetadataExample = new FileAttachmentMetadataExample(); + fileAttachmentMetadataExample.createCriteria().andIdIn(attachmentIds); + fileAttachmentMetadataMapper.deleteByExample(fileAttachmentMetadataExample); + } + AttachmentModuleRelationExample example = new AttachmentModuleRelationExample(); + example.createCriteria().andRelationIdEqualTo(request.getBelongId()).andRelationTypeEqualTo(request.getBelongType()); + attachmentModuleRelationMapper.deleteByExample(example); + } + + public void copyAttachment(AttachmentRequest request) { + AttachmentModuleRelationExample example = new AttachmentModuleRelationExample(); + 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()); + AttachmentModuleRelation record = new AttachmentModuleRelation(); + record.setRelationId(request.getBelongId()); + record.setRelationType(request.getBelongType()); + record.setAttachmentId(fileAttachmentMetadata.getId()); + attachmentModuleRelationMapper.insert(record); + }); + } + } + + public List listMetadata(AttachmentRequest request) { + List attachmentIds = getAttachmentIdsByParam(request); + if (CollectionUtils.isEmpty(attachmentIds)) { + return new ArrayList<>(); + } + FileAttachmentMetadataExample fileExample = new FileAttachmentMetadataExample(); + fileExample.createCriteria().andIdIn(attachmentIds); + return fileAttachmentMetadataMapper.selectByExample(fileExample); + } + + public List getAttachmentIdsByParam(AttachmentRequest request) { + AttachmentModuleRelationExample example = new AttachmentModuleRelationExample(); + example.createCriteria().andRelationIdEqualTo(request.getBelongId()).andRelationTypeEqualTo(request.getBelongType()); + List attachmentModuleRelations = attachmentModuleRelationMapper.selectByExample(example); + List attachmentIds = attachmentModuleRelations.stream().map(AttachmentModuleRelation::getAttachmentId) + .collect(Collectors.toList()); + return attachmentIds; + } + + public void initAttachment() { + List attachmentModuleRelations = new ArrayList<>(); + List issueFiles = issueFileMapper.selectByExample(new IssueFileExample()); + List testCaseFiles = testCaseFileMapper.selectByExample(new TestCaseFileExample()); + if (CollectionUtils.isNotEmpty(issueFiles)) { + issueFiles.forEach(issueFile -> { + AttachmentModuleRelation relation = new AttachmentModuleRelation(); + relation.setAttachmentId(issueFile.getFileId()); + relation.setRelationId(issueFile.getIssueId()); + relation.setRelationType(AttachmentType.ISSUE.type()); + attachmentModuleRelations.add(relation); + }); + } + if (CollectionUtils.isNotEmpty(testCaseFiles)) { + testCaseFiles.forEach(testCaseFile -> { + AttachmentModuleRelation relation = new AttachmentModuleRelation(); + relation.setAttachmentId(testCaseFile.getFileId()); + relation.setRelationId(testCaseFile.getCaseId()); + relation.setRelationType(AttachmentType.TEST_CASE.type()); + attachmentModuleRelations.add(relation); + }); + } + extAttachmentModuleRelationMapper.batchInsert(attachmentModuleRelations); + } +} 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 95d9ff8843..242a6e1f5d 100644 --- a/backend/src/main/java/io/metersphere/track/service/IssuesService.java +++ b/backend/src/main/java/io/metersphere/track/service/IssuesService.java @@ -28,6 +28,7 @@ import io.metersphere.track.issue.*; import io.metersphere.track.issue.domain.PlatformUser; import io.metersphere.track.issue.domain.jira.JiraIssueType; import io.metersphere.track.issue.domain.zentao.ZentaoBuild; +import io.metersphere.track.request.attachment.AttachmentRequest; import io.metersphere.track.request.issues.JiraIssueTypeRequest; import io.metersphere.track.request.issues.PlatformIssueTypeRequest; import io.metersphere.track.request.testcase.AuthUserIssueRequest; @@ -40,7 +41,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.util.*; @@ -91,6 +91,8 @@ public class IssuesService { IssueFileMapper issueFileMapper; @Resource private FileAttachmentMetadataMapper fileAttachmentMetadataMapper; + @Resource + private AttachmentService attachmentService; private static final String SYNC_THIRD_PARTY_ISSUES_KEY = "ISSUE:SYNC"; @@ -116,22 +118,13 @@ public class IssuesService { saveFollows(issuesRequest.getId(), issuesRequest.getFollows()); customFieldIssuesService.addFields(issuesRequest.getId(), issuesRequest.getAddFields()); customFieldIssuesService.editFields(issuesRequest.getId(), issuesRequest.getEditFields()); - // copy附件 + // 复制新增, 同步缺陷的MS附件 if (StringUtils.isNotEmpty(issuesRequest.getCopyIssueId())) { - final String addIssueId = issues.getId(); - IssueFileExample example = new IssueFileExample(); - example.createCriteria().andIssueIdEqualTo(issuesRequest.getCopyIssueId()); - List issueFiles = issueFileMapper.selectByExample(example); - if (issueFiles != null) { - issueFiles.forEach(issueFile -> { - // 同步MS附件 - FileAttachmentMetadata fileAttachmentMetadata = fileService.copyAttachment(issueFile.getFileId(), AttachmentType.ISSUE.type(), addIssueId); - IssueFile newIssueFile = new IssueFile(); - newIssueFile.setIssueId(addIssueId); - newIssueFile.setFileId(fileAttachmentMetadata.getId()); - issueFileMapper.insert(newIssueFile); - }); - } + AttachmentRequest attachmentRequest = new AttachmentRequest(); + attachmentRequest.setCopyBelongId(issuesRequest.getCopyIssueId()); + attachmentRequest.setBelongId(issues.getId()); + attachmentRequest.setBelongType(AttachmentType.ISSUE.type()); + attachmentService.copyAttachment(attachmentRequest); } return issues; } @@ -145,31 +138,6 @@ public class IssuesService { customFieldIssuesService.addFields(issuesRequest.getId(), issuesRequest.getAddFields()); } - public void uploadAttachment(IssuesUpdateRequest request, MultipartFile file) { - IssuesWithBLOBs issuesWithBLOBs = issuesMapper.selectByPrimaryKey(request.getId()); - if (issuesWithBLOBs == null) { - MSException.throwException(Translator.get("issues_attachment_upload_not_found") + request.getId()); - } - FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachment(file, AttachmentType.ISSUE.type(), request.getId()); - IssueFile issueFile = new IssueFile(); - issueFile.setIssueId(request.getId()); - issueFile.setFileId(fileAttachmentMetadata.getId()); - issueFileMapper.insert(issueFile); - } - - public void deleteAttachment(String id) { - // 删除附件记录, 目录下附件文件 - if (StringUtils.isNotEmpty(id)) { - List ids = Arrays.asList(id); - fileService.deleteAttachment(ids); - fileService.deleteFileAttachmentByIds(ids); - //删除缺陷文件关联记录 - IssueFileExample issueFileExample = new IssueFileExample(); - issueFileExample.createCriteria().andFileIdIn(ids); - issueFileMapper.deleteByExample(issueFileExample); - } - } - public void saveFollows(String issueId, List follows) { IssueFollowExample example = new IssueFollowExample(); example.createCriteria().andIssueIdEqualTo(issueId); @@ -369,17 +337,10 @@ public class IssuesService { AbstractIssuePlatform platform = IssueFactory.createPlatform(issuesWithBLOBs.getPlatform(), issuesRequest); platform.deleteIssue(id); // 删除缺陷对应的附件 - fileService.deleteAttachment(AttachmentType.ISSUE.type(), id); - IssueFileExample issueFileExample = new IssueFileExample(); - issueFileExample.createCriteria().andIssueIdEqualTo(id); - List issueFiles = issueFileMapper.selectByExample(issueFileExample); - List fileIds = issueFiles.stream().map(IssueFile::getFileId).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(fileIds)) { - FileAttachmentMetadataExample fileAttachmentMetadataExample = new FileAttachmentMetadataExample(); - fileAttachmentMetadataExample.createCriteria().andIdIn(fileIds); - fileAttachmentMetadataMapper.deleteByExample(fileAttachmentMetadataExample); - } - issueFileMapper.deleteByExample(issueFileExample); + AttachmentRequest request = new AttachmentRequest(); + request.setBelongId(id); + request.setBelongType(AttachmentType.ISSUE.type()); + attachmentService.deleteAttachment(request); } public IssuesWithBLOBs get(String id) { 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 b2f6f20276..ce3db637ff 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java @@ -50,6 +50,7 @@ import io.metersphere.track.dto.TestCaseNodeDTO; import io.metersphere.track.issue.AbstractIssuePlatform; import io.metersphere.track.issue.IssueFactory; import io.metersphere.track.issue.service.XpackIssueService; +import io.metersphere.track.request.attachment.AttachmentRequest; import io.metersphere.track.request.testcase.*; import io.metersphere.track.request.testplan.LoadCaseRequest; import io.metersphere.xmind.XmindCaseParser; @@ -182,6 +183,8 @@ public class TestCaseService { private FileMetadataMapper fileMetadataMapper; @Resource private FileContentMapper fileContentMapper; + @Resource + private AttachmentService attachmentService; private ThreadLocal importCreateNum = new ThreadLocal<>(); private ThreadLocal beforeImportCreateNum = new ThreadLocal<>(); @@ -614,17 +617,10 @@ public class TestCaseService { customFieldTestCaseService.deleteByResourceId(testCaseId); // 删除自定义字段关联关系 functionCaseExecutionInfoService.deleteBySourceId(testCaseId); // 删除用例附件关联数据, 附件内容 - TestCaseFileExample testCaseFileExample = new TestCaseFileExample(); - testCaseFileExample.createCriteria().andCaseIdEqualTo(testCaseId); - List testCaseFiles = testCaseFileMapper.selectByExample(testCaseFileExample); - List fileIds = testCaseFiles.stream().map(TestCaseFile::getFileId).collect(Collectors.toList()); - if (CollectionUtils.isNotEmpty(fileIds)) { - FileAttachmentMetadataExample fileAttachmentMetadataExample = new FileAttachmentMetadataExample(); - fileAttachmentMetadataExample.createCriteria().andIdIn(fileIds); - fileAttachmentMetadataMapper.deleteByExample(fileAttachmentMetadataExample); - } - testCaseFileMapper.deleteByExample(testCaseFileExample); - fileService.deleteAttachment(AttachmentType.TEST_CASE.type(), testCaseId); + AttachmentRequest request = new AttachmentRequest(); + request.setBelongId(testCaseId); + request.setBelongType(AttachmentType.TEST_CASE.type()); + attachmentService.deleteAttachment(request); return testCaseMapper.deleteByPrimaryKey(testCaseId); } @@ -1987,18 +1983,11 @@ public class TestCaseService { final TestCaseWithBLOBs testCaseWithBLOBs = addTestCase(request); // 复制用例时复制对应附件数据 if (StringUtils.isNotEmpty(request.getCopyCaseId())) { - TestCaseFileExample example = new TestCaseFileExample(); - example.createCriteria().andCaseIdEqualTo(request.getCopyCaseId()); - List testCaseFiles = testCaseFileMapper.selectByExample(example); - if (testCaseFiles != null) { - testCaseFiles.forEach(testCaseFile -> { - FileAttachmentMetadata fileAttachmentMetadata = fileService.copyAttachment(testCaseFile.getFileId(), AttachmentType.TEST_CASE.type(), testCaseWithBLOBs.getId()); - TestCaseFile newTestCaseFile = new TestCaseFile(); - newTestCaseFile.setCaseId(testCaseWithBLOBs.getId()); - newTestCaseFile.setFileId(fileAttachmentMetadata.getId()); - testCaseFileMapper.insert(newTestCaseFile); - }); - } + AttachmentRequest attachmentRequest = new AttachmentRequest(); + attachmentRequest.setCopyBelongId(request.getCopyCaseId()); + attachmentRequest.setBelongId(testCaseWithBLOBs.getId()); + attachmentRequest.setBelongType(AttachmentType.TEST_CASE.type()); + attachmentService.copyAttachment(attachmentRequest); } return testCaseWithBLOBs; } @@ -2051,32 +2040,6 @@ public class TestCaseService { return editTestCase(request); } - public void uploadAttachment(EditTestCaseRequest request, MultipartFile file) { - TestCaseWithBLOBs testCaseWithBLOBs = testCaseMapper.selectByPrimaryKey(request.getId()); - if (testCaseWithBLOBs == null) { - MSException.throwException(Translator.get("test_case_attachment_upload_not_found") + request.getId()); - } - - FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachment(file, AttachmentType.TEST_CASE.type(), request.getId()); - TestCaseFile testCaseFile = new TestCaseFile(); - testCaseFile.setFileId(fileAttachmentMetadata.getId()); - testCaseFile.setCaseId(request.getId()); - testCaseFileMapper.insert(testCaseFile); - } - - public void deleteAttachment(String id) { - // 删除附件记录, 目录下附件文件 - if (StringUtils.isNotEmpty(id)) { - List ids = Arrays.asList(id); - fileService.deleteAttachment(ids); - fileService.deleteFileAttachmentByIds(ids); - //删除用例文件关联记录 - TestCaseFileExample testCaseFileExample = new TestCaseFileExample(); - testCaseFileExample.createCriteria().andFileIdIn(ids); - testCaseFileMapper.deleteByExample(testCaseFileExample); - } - } - public String editTestCase(EditTestCaseRequest request, List files) { String testCaseId = testPlanTestCaseMapper.selectByPrimaryKey(request.getId()).getCaseId(); request.setId(testCaseId); diff --git a/frontend/src/business/components/track/case/components/TestCaseAttachment.vue b/frontend/src/business/components/track/case/components/TestCaseAttachment.vue index 617b785647..11df24e333 100644 --- a/frontend/src/business/components/track/case/components/TestCaseAttachment.vue +++ b/frontend/src/business/components/track/case/components/TestCaseAttachment.vue @@ -121,7 +121,7 @@ export default { id: file.id, }; let config = { - url: '/test/case/attachment/download', + url: '/attachment/download', method: 'post', data: data, responseType: 'blob' diff --git a/frontend/src/business/components/track/case/components/TestCaseEditOtherInfo.vue b/frontend/src/business/components/track/case/components/TestCaseEditOtherInfo.vue index 2b70d49f99..36ef2656c9 100644 --- a/frontend/src/business/components/track/case/components/TestCaseEditOtherInfo.vue +++ b/frontend/src/business/components/track/case/components/TestCaseEditOtherInfo.vue @@ -267,7 +267,7 @@ export default { let file = param.file; let progress = 0; let formData = new FormData(); - let requestJson = JSON.stringify({"id": this.caseId}); + let requestJson = JSON.stringify({"belongId": this.caseId, "belongType": "testcase"}); formData.append("file", file); formData.append('request', new Blob([requestJson], { type: "application/json" @@ -278,7 +278,7 @@ export default { axios({ headers: { 'Content-Type': 'application/json;charset=UTF-8' }, method: 'post', - url: '/test/case/attachment/upload', + url: '/attachment/upload', data: formData, cancelToken: new CancelToken(function executor(c) { self.cancelFileToken.push({"name": file.name, "cancelFunc": c}); @@ -322,36 +322,6 @@ export default { this.getFileMetaData(); } }, - handleDownload(file) { - let data = { - name: file.name, - id: file.id, - }; - let config = { - url: '/test/case/file/download', - method: 'post', - data: data, - responseType: 'blob' - }; - this.result = this.$request(config).then(response => { - const content = response.data; - const blob = new Blob([content]); - if ("download" in document.createElement("a")) { - // 非IE下载 - // chrome/firefox - let aTag = document.createElement('a'); - aTag.download = file.name; - aTag.href = URL.createObjectURL(blob); - aTag.click(); - URL.revokeObjectURL(aTag.href); - } else { - // IE10+下载 - navigator.msSaveBlob(blob, this.filename); - } - }).catch(e => { - Message.error({message: e.message, showClose: true}); - }); - }, handleDelete(file, index) { this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "?", '', { confirmButtonText: this.$t('commons.confirm'), @@ -365,7 +335,7 @@ export default { _handleDelete(file, index) { this.fileList.splice(index, 1); this.tableData.splice(index, 1); - this.$get('/test/case/attachment/delete/' + file.id, () => { + this.$get('/attachment/delete/testcase/' + file.id , response => { this.$success(this.$t('commons.delete_success')); this.getFileMetaData(); }); @@ -390,9 +360,9 @@ export default { testCaseId = id ? id : this.caseId; } if (testCaseId) { - this.result = this.$get("test/case/file/attachmentMetadata/" + testCaseId, response => { + let data = {'belongType': 'testcase', 'belongId': testCaseId}; + this.result = this.$post("/attachment/metadata/list", data, response => { let files = response.data; - if (!files) { return; } diff --git a/frontend/src/business/components/track/case/components/TestCaseFile.vue b/frontend/src/business/components/track/case/components/TestCaseFile.vue index cf189bfee4..883964d706 100644 --- a/frontend/src/business/components/track/case/components/TestCaseFile.vue +++ b/frontend/src/business/components/track/case/components/TestCaseFile.vue @@ -1,7 +1,7 @@