refactor(测试跟踪): 附件功能代码优化

--story=1006991 --user=宋昌昌 【测试跟踪】功能用例&缺陷增加附件功能支持视频文件(1.20分支同步上) https://www.tapd.cn/55049933/s/1204166
This commit is contained in:
song-cc-rock 2022-07-19 14:59:12 +08:00 committed by jianxing
parent a89cd8f348
commit 4e5be145de
28 changed files with 1069 additions and 289 deletions

View File

@ -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;
}

View File

@ -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<Criteria> oredCriteria;
public AttachmentModuleRelationExample() {
oredCriteria = new ArrayList<Criteria>();
}
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<Criteria> 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<Criterion> criteria;
protected GeneratedCriteria() {
super();
criteria = new ArrayList<Criterion>();
}
public boolean isValid() {
return criteria.size() > 0;
}
public List<Criterion> getAllCriteria() {
return criteria;
}
public List<Criterion> 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<String> values) {
addCriterion("relation_id in", values, "relationId");
return (Criteria) this;
}
public Criteria andRelationIdNotIn(List<String> 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<String> values) {
addCriterion("relation_type in", values, "relationType");
return (Criteria) this;
}
public Criteria andRelationTypeNotIn(List<String> 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<String> values) {
addCriterion("attachment_id in", values, "attachmentId");
return (Criteria) this;
}
public Criteria andAttachmentIdNotIn(List<String> 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);
}
}
}

View File

@ -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<AttachmentModuleRelation> selectByExample(AttachmentModuleRelationExample example);
int updateByExampleSelective(@Param("record") AttachmentModuleRelation record, @Param("example") AttachmentModuleRelationExample example);
int updateByExample(@Param("record") AttachmentModuleRelation record, @Param("example") AttachmentModuleRelationExample example);
}

View File

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.base.mapper.AttachmentModuleRelationMapper">
<resultMap id="BaseResultMap" type="io.metersphere.base.domain.AttachmentModuleRelation">
<result column="relation_id" jdbcType="VARCHAR" property="relationId" />
<result column="relation_type" jdbcType="VARCHAR" property="relationType" />
<result column="attachment_id" jdbcType="VARCHAR" property="attachmentId" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
<foreach collection="oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause">
<where>
<foreach collection="example.oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List">
relation_id, relation_type, attachment_id
</sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.AttachmentModuleRelationExample" resultMap="BaseResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from attachment_module_relation
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<delete id="deleteByExample" parameterType="io.metersphere.base.domain.AttachmentModuleRelationExample">
delete from attachment_module_relation
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.AttachmentModuleRelation">
insert into attachment_module_relation (relation_id, relation_type, attachment_id
)
values (#{relationId,jdbcType=VARCHAR}, #{relationType,jdbcType=VARCHAR}, #{attachmentId,jdbcType=VARCHAR}
)
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.AttachmentModuleRelation">
insert into attachment_module_relation
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="relationId != null">
relation_id,
</if>
<if test="relationType != null">
relation_type,
</if>
<if test="attachmentId != null">
attachment_id,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="relationId != null">
#{relationId,jdbcType=VARCHAR},
</if>
<if test="relationType != null">
#{relationType,jdbcType=VARCHAR},
</if>
<if test="attachmentId != null">
#{attachmentId,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.AttachmentModuleRelationExample" resultType="java.lang.Long">
select count(*) from attachment_module_relation
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</select>
<update id="updateByExampleSelective" parameterType="map">
update attachment_module_relation
<set>
<if test="record.relationId != null">
relation_id = #{record.relationId,jdbcType=VARCHAR},
</if>
<if test="record.relationType != null">
relation_type = #{record.relationType,jdbcType=VARCHAR},
</if>
<if test="record.attachmentId != null">
attachment_id = #{record.attachmentId,jdbcType=VARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExample" parameterType="map">
update attachment_module_relation
set relation_id = #{record.relationId,jdbcType=VARCHAR},
relation_type = #{record.relationType,jdbcType=VARCHAR},
attachment_id = #{record.attachmentId,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
</mapper>

View File

@ -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<AttachmentModuleRelation> attachmentModuleRelations);
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.base.mapper.ext.ExtAttachmentModuleRelationMapper">
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO
attachment_module_relation (relation_id, relation_type, attachment_id)
VALUES
<foreach collection="attachmentModuleRelations" item="relation" separator="," >
(#{relation.relationId}, #{relation.relationType}, #{relation.attachmentId})
</foreach>
</insert>
</mapper>

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -79,8 +79,7 @@ public class ShiroUtils {
public static void ignoreCsrfFilter(Map<String, String> 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
}

View File

@ -50,6 +50,8 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
@Resource
private TestCaseService testCaseService;
@Resource
private AttachmentService attachmentService;
@Resource
private ApiTestCaseService apiTestCaseService;
@Resource
private TestPlanTestCaseService testPlanTestCaseService;
@ -158,6 +160,7 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
initOnceOperate(customFieldResourceService::compatibleData, "init.custom.field.resource");
initOnceOperate(jarConfigService::initJarPath, "init.jar.path");
initOnceOperate(testCaseService::initAttachment, "init.attachment.test.case");
initOnceOperate(attachmentService::initAttachment, "init.attachment.all");
}
/**

View File

@ -0,0 +1,63 @@
package io.metersphere.track.controller;
import io.metersphere.base.domain.FileAttachmentMetadata;
import io.metersphere.service.FileService;
import io.metersphere.track.request.attachment.AttachmentRequest;
import io.metersphere.track.request.testplan.FileOperationRequest;
import io.metersphere.track.service.AttachmentService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* @author songcc
*/
@RequestMapping("attachment")
@RestController
public class AttachmentController {
@Resource
private FileService fileService;
@Resource
private AttachmentService attachmentService;
@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<byte[]> 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<byte[]> 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<FileAttachmentMetadata> listMetadata(@RequestBody AttachmentRequest request) {
return attachmentService.listMetadata(request);
}
}

View File

@ -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<FileAttachmentMetadata> getFileAttachmentMetadataByIssueId(@PathVariable String issueId) {
return fileService.getFileAttachmentMetadataByIssueId(issueId);
}
@GetMapping("/get/case/{refType}/{id}")
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_ISSUE_READ)
public List<IssuesDao> getIssues(@PathVariable String refType, @PathVariable String id) {

View File

@ -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<FileAttachmentMetadata> getFileAttachmentMetadataByCaseId(@PathVariable String caseId) {
return fileService.getFileAttachmentMetadataByCaseId(caseId);
}
@PostMapping("/file/download")
public ResponseEntity<byte[]> 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<byte[]> 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<byte[]> 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) {

View File

@ -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) {

View File

@ -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);
}

View File

@ -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<IssueFile> 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<String> 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<File> imageFiles = getImageFiles(request);
JSONObject param = buildUpdateParam(request, getIssueType(project.getIssueConfig()), project.getJiraKey());
jiraClientV2.updateIssue(request.getPlatformId(), JSONObject.toJSONString(param));
List<FileAttachmentMetadata> newFiles = fileService.getFileAttachmentMetadataByIssueId(request.getId());
List<String> newFileNames = newFiles.stream().map(FileAttachmentMetadata::getName).collect(Collectors.toList());
Set<String> 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<JiraTransitionsResponse.Transitions> 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);
}
}
}

View File

@ -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) {
// 不需要同步
}
}

View File

@ -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<PlatformStatusDTO> getTransitions(String issueKey) {
List<PlatformStatusDTO> platformStatusDTOS = new ArrayList<>();

View File

@ -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<PlatformStatusDTO> getTransitions(String issueKey) {
List<PlatformStatusDTO> platformStatusDTOS = new ArrayList<>();

View File

@ -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;
}

View File

@ -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<String> ids = List.of(attachmentId);
AttachmentModuleRelationExample example = new AttachmentModuleRelationExample();
example.createCriteria().andAttachmentIdIn(ids).andRelationTypeEqualTo(attachmentType);
// 缺陷类型的附件, 需先同步第三方平台
if (AttachmentType.ISSUE.type().equals(attachmentType)) {
List<AttachmentModuleRelation> 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<String> 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<AttachmentModuleRelation> 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<FileAttachmentMetadata> listMetadata(AttachmentRequest request) {
List<String> attachmentIds = getAttachmentIdsByParam(request);
if (CollectionUtils.isEmpty(attachmentIds)) {
return new ArrayList<>();
}
FileAttachmentMetadataExample fileExample = new FileAttachmentMetadataExample();
fileExample.createCriteria().andIdIn(attachmentIds);
return fileAttachmentMetadataMapper.selectByExample(fileExample);
}
public List<String> getAttachmentIdsByParam(AttachmentRequest request) {
AttachmentModuleRelationExample example = new AttachmentModuleRelationExample();
example.createCriteria().andRelationIdEqualTo(request.getBelongId()).andRelationTypeEqualTo(request.getBelongType());
List<AttachmentModuleRelation> attachmentModuleRelations = attachmentModuleRelationMapper.selectByExample(example);
List<String> attachmentIds = attachmentModuleRelations.stream().map(AttachmentModuleRelation::getAttachmentId)
.collect(Collectors.toList());
return attachmentIds;
}
public void initAttachment() {
List<AttachmentModuleRelation> attachmentModuleRelations = new ArrayList<>();
List<IssueFile> issueFiles = issueFileMapper.selectByExample(new IssueFileExample());
List<TestCaseFile> 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);
}
}

View File

@ -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<IssueFile> 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<String> 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<String> 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<IssueFile> issueFiles = issueFileMapper.selectByExample(issueFileExample);
List<String> 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) {

View File

@ -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<Integer> importCreateNum = new ThreadLocal<>();
private ThreadLocal<Integer> 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<TestCaseFile> testCaseFiles = testCaseFileMapper.selectByExample(testCaseFileExample);
List<String> 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<TestCaseFile> 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<String> 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<MultipartFile> files) {
String testCaseId = testPlanTestCaseMapper.selectByPrimaryKey(request.getId()).getCaseId();
request.setId(testCaseId);

View File

@ -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'

View File

@ -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;
}

View File

@ -1,7 +1,7 @@
<template>
<el-dialog :visible.sync="dialogVisible" width="80%" :destroy-on-close="true" :before-close="close" :append-to-body="true">
<div>
<img :src="'/test/case/attachment/preview/' + file.id" :alt="$t('test_track.case.img_loading_fail')" style="width: 100%;height: 100%;"
<img :src="'/attachment/preview/' + file.id" :alt="$t('test_track.case.img_loading_fail')" style="width: 100%;height: 100%;"
v-if="file.type === 'JPG' || file.type === 'JPEG' || file.type === 'PNG'">
<div v-if="file.type === 'PDF'">
<test-case-pdf :file-id="file.id"/>

View File

@ -21,7 +21,7 @@ export default {
},
mounted() {
this.loading = true;
this.loadingTask = pdf.createLoadingTask("/test/case/attachment/preview/" + this.fileId);
this.loadingTask = pdf.createLoadingTask("/attachment/preview/" + this.fileId);
this.loadingTask.promise.then(pdf => {
this.numPages = pdf.numPages
this.loading = false;

View File

@ -659,7 +659,7 @@ export default {
let file = param.file;
let progress = 0;
let formData = new FormData();
let requestJson = JSON.stringify({"id": this.issueId});
let requestJson = JSON.stringify({"belongId": this.issueId, "belongType": "issue"});
formData.append("file", file);
formData.append('request', new Blob([requestJson], {
type: "application/json"
@ -670,7 +670,7 @@ export default {
axios({
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
method: 'post',
url: '/issues/attachment/upload',
url: '/attachment/upload',
data: formData,
cancelToken: new CancelToken(function executor(c) {
self.cancelFileToken.push({"name": file.name, "cancelFunc": c});
@ -714,36 +714,6 @@ export default {
this.getFileMetaData(this.issueId);
}
},
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'),
@ -757,7 +727,8 @@ export default {
_handleDelete(file, index) {
this.fileList.splice(index, 1);
this.tableData.splice(index, 1);
this.$get('/issues/attachment/delete/' + file.id, () => {
let data = {"belongId": this.issueId, "belongType": "issue"}
this.$get('/attachment/delete/issue/' + file.id , response => {
this.$success(this.$t('commons.delete_success'));
this.getFileMetaData(this.issueId);
});
@ -775,7 +746,8 @@ export default {
this.fileList = [];
this.tableData = [];
if (id) {
this.result = this.$get("issues/file/attachmentMetadata/" + id, response => {
let data = {'belongType': 'issue', 'belongId': id};
this.result = this.$post("/attachment/metadata/list", data, response => {
let files = response.data;
if (!files) {
return;