feat(缺陷管理): 补充缺陷管理附件相关接口
This commit is contained in:
parent
4f6cbed856
commit
7e5967a688
|
@ -0,0 +1,150 @@
|
||||||
|
package io.metersphere.bug.controller;
|
||||||
|
|
||||||
|
import io.metersphere.bug.dto.request.BugDeleteFileRequest;
|
||||||
|
import io.metersphere.bug.dto.request.BugFileSourceRequest;
|
||||||
|
import io.metersphere.bug.dto.request.BugFileTransferRequest;
|
||||||
|
import io.metersphere.bug.dto.request.BugUploadFileRequest;
|
||||||
|
import io.metersphere.bug.dto.response.BugFileDTO;
|
||||||
|
import io.metersphere.bug.service.BugAttachmentService;
|
||||||
|
import io.metersphere.project.dto.filemanagement.request.FileMetadataTableRequest;
|
||||||
|
import io.metersphere.project.dto.filemanagement.response.FileInformationResponse;
|
||||||
|
import io.metersphere.project.service.FileAssociationService;
|
||||||
|
import io.metersphere.project.service.FileMetadataService;
|
||||||
|
import io.metersphere.project.service.FileModuleService;
|
||||||
|
import io.metersphere.sdk.constants.PermissionConstants;
|
||||||
|
import io.metersphere.system.dto.sdk.BaseTreeNode;
|
||||||
|
import io.metersphere.system.security.CheckOwner;
|
||||||
|
import io.metersphere.system.utils.Pager;
|
||||||
|
import io.metersphere.system.utils.SessionUtils;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.apache.shiro.authz.annotation.Logical;
|
||||||
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Tag(name = "缺陷管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/bug/attachment")
|
||||||
|
public class BugAttachmentController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FileModuleService fileModuleService;
|
||||||
|
@Resource
|
||||||
|
private FileMetadataService fileMetadataService;
|
||||||
|
@Resource
|
||||||
|
private BugAttachmentService bugAttachmentService;
|
||||||
|
@Resource
|
||||||
|
private FileAssociationService fileAssociationService;
|
||||||
|
|
||||||
|
@GetMapping("/list/{bugId}")
|
||||||
|
@Operation(summary = "缺陷管理-附件-列表")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
|
||||||
|
@CheckOwner(resourceId = "#bugId", resourceType = "bug")
|
||||||
|
public List<BugFileDTO> page(@PathVariable String bugId) {
|
||||||
|
return bugAttachmentService.getAllBugFiles(bugId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/file/page")
|
||||||
|
@Operation(summary = "缺陷管理-附件-关联文件分页接口")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
|
||||||
|
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
|
||||||
|
public Pager<List<FileInformationResponse>> page(@Validated @RequestBody FileMetadataTableRequest request) {
|
||||||
|
return fileMetadataService.page(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/upload")
|
||||||
|
@Operation(summary = "缺陷管理-附件-上传/关联文件")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
|
||||||
|
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
|
||||||
|
public void uploadFile(@Validated @RequestPart("request") BugUploadFileRequest request, @RequestPart(value = "file", required = false) MultipartFile file) {
|
||||||
|
bugAttachmentService.uploadFile(request, file, SessionUtils.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete")
|
||||||
|
@Operation(summary = "缺陷管理-附件-删除/取消关联文件")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
|
||||||
|
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
|
||||||
|
public void deleteFile(@RequestBody BugDeleteFileRequest request) {
|
||||||
|
bugAttachmentService.deleteFile(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/preview")
|
||||||
|
@Operation(summary = "缺陷管理-附件-预览")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
|
||||||
|
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
|
||||||
|
public ResponseEntity<byte[]> preview(@Validated @RequestBody BugFileSourceRequest request) throws Exception {
|
||||||
|
if (request.getAssociated()) {
|
||||||
|
// 文件库
|
||||||
|
return fileMetadataService.downloadPreviewImgById(request.getFileId());
|
||||||
|
} else {
|
||||||
|
// 本地
|
||||||
|
return bugAttachmentService.downloadOrPreview(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/download")
|
||||||
|
@Operation(summary = "缺陷管理-附件-下载")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
|
||||||
|
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
|
||||||
|
public ResponseEntity<byte[]> download(@Validated @RequestBody BugFileSourceRequest request) throws Exception {
|
||||||
|
if (request.getAssociated()) {
|
||||||
|
// 文件库
|
||||||
|
return fileMetadataService.downloadById(request.getFileId());
|
||||||
|
} else {
|
||||||
|
// 本地
|
||||||
|
return bugAttachmentService.downloadOrPreview(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/transfer/options/{projectId}")
|
||||||
|
@Operation(summary = "缺陷管理-附件-转存选项")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
|
||||||
|
@CheckOwner(resourceId = "#projectId", resourceType = "project")
|
||||||
|
public List<BaseTreeNode> options(@PathVariable String projectId) {
|
||||||
|
return fileModuleService.getTree(projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/transfer")
|
||||||
|
@Operation(summary = "缺陷管理-附件-本地转存")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
|
||||||
|
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
|
||||||
|
public String transfer(@Validated @RequestBody BugFileTransferRequest request) {
|
||||||
|
return bugAttachmentService.transfer(request, SessionUtils.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/check-update")
|
||||||
|
@Operation(summary = "缺陷管理-附件-检查关联文件是否存在更新")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
|
||||||
|
public List<String> checkUpdate(@RequestBody List<String> fileIds) {
|
||||||
|
return fileAssociationService.checkFilesVersion(fileIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/update")
|
||||||
|
@Operation(summary = "缺陷管理-附件-更新关联文件")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
|
||||||
|
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
|
||||||
|
public String update(@Validated @RequestBody BugDeleteFileRequest request) {
|
||||||
|
return bugAttachmentService.upgrade(request, SessionUtils.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/upload/md/file")
|
||||||
|
@Operation(summary = "缺陷管理-富文本附件-上传")
|
||||||
|
@RequiresPermissions(logical = Logical.OR, value = {PermissionConstants.PROJECT_BUG_ADD, PermissionConstants.PROJECT_BUG_UPDATE})
|
||||||
|
public String upload(@RequestParam("file") MultipartFile file) throws Exception {
|
||||||
|
return bugAttachmentService.uploadMdFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/preview/md/compressed")
|
||||||
|
@Operation(summary = "缺陷管理-富文本缩略图-预览")
|
||||||
|
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ)
|
||||||
|
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
|
||||||
|
public ResponseEntity<byte[]> previewMdImg(@Validated @RequestBody BugFileSourceRequest request) {
|
||||||
|
return bugAttachmentService.downloadOrPreview(request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,6 +110,13 @@ public class BugController {
|
||||||
bugService.addOrUpdate(request, files, SessionUtils.getUserId(), SessionUtils.getCurrentOrganizationId(), true);
|
bugService.addOrUpdate(request, files, SessionUtils.getUserId(), SessionUtils.getCurrentOrganizationId(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/get/{id}")
|
||||||
|
@Operation(summary = "缺陷管理-列表-详情&&编辑&&复制")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
|
||||||
|
public void get(@PathVariable String id) {
|
||||||
|
bugService.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/delete/{id}")
|
@GetMapping("/delete/{id}")
|
||||||
@Operation(summary = "缺陷管理-列表-删除缺陷")
|
@Operation(summary = "缺陷管理-列表-删除缺陷")
|
||||||
@RequiresPermissions(PermissionConstants.PROJECT_BUG_DELETE)
|
@RequiresPermissions(PermissionConstants.PROJECT_BUG_DELETE)
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package io.metersphere.bug.dto.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class BugDeleteFileRequest implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "缺陷ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{bug.id.not_blank}")
|
||||||
|
private String bugId;
|
||||||
|
|
||||||
|
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{bug.project_id.not_blank}")
|
||||||
|
private String projectId;
|
||||||
|
|
||||||
|
@Schema(description = "文件关系ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String refId;
|
||||||
|
|
||||||
|
@Schema(description = "是否关联", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private Boolean associated;
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ public class BugEditRequest {
|
||||||
@Schema(description = "自定义字段集合")
|
@Schema(description = "自定义字段集合")
|
||||||
private List<BugCustomFieldDTO> customFields;
|
private List<BugCustomFieldDTO> customFields;
|
||||||
|
|
||||||
@Schema(description = "删除的本地附件集合, 文件ID")
|
@Schema(description = "删除的本地附件集合, {文件ID")
|
||||||
private List<String> deleteLocalFileIds;
|
private List<String> deleteLocalFileIds;
|
||||||
|
|
||||||
@Schema(description = "取消关联附件关系ID集合, 关联关系ID")
|
@Schema(description = "取消关联附件关系ID集合, 关联关系ID")
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package io.metersphere.bug.dto.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class BugFileSourceRequest implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "缺陷ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{bug.id.not_blank}")
|
||||||
|
private String bugId;
|
||||||
|
|
||||||
|
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{bug.project_id.not_blank}")
|
||||||
|
private String projectId;
|
||||||
|
|
||||||
|
@Schema(description = "文件关系ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String fileId;
|
||||||
|
|
||||||
|
@Schema(description = "是否关联", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private Boolean associated;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package io.metersphere.bug.dto.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class BugFileTransferRequest extends BugFileSourceRequest{
|
||||||
|
|
||||||
|
@Schema(description = "转存的模块id",requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{functional_case.module_id.not_blank}")
|
||||||
|
private String moduleId;
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package io.metersphere.bug.dto.request;
|
||||||
|
|
||||||
|
import io.metersphere.bug.dto.response.BugCustomFieldDTO;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class BugQuickEditRequest {
|
||||||
|
|
||||||
|
@Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{bug.id.not_blank}")
|
||||||
|
@Size(min = 1, max = 50, message = "{bug.id.length_range}")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{bug.project_id.not_blank}")
|
||||||
|
@Size(min = 1, max = 50, message = "{bug.project_id.length_range}")
|
||||||
|
private String projectId;
|
||||||
|
|
||||||
|
@Schema(description = "模板ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{bug.template_id.not_blank}")
|
||||||
|
@Size(min = 1, max = 50, message = "{bug.template_id.length_range}")
|
||||||
|
private String templateId;
|
||||||
|
|
||||||
|
@Schema(description = "处理人")
|
||||||
|
private String handleUser;
|
||||||
|
|
||||||
|
@Schema(description = "状态")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "标签")
|
||||||
|
private List<String> tags;
|
||||||
|
|
||||||
|
@Schema(description = "缺陷内容")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "自定义字段集合")
|
||||||
|
private List<BugCustomFieldDTO> customFields;
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package io.metersphere.bug.dto.request;
|
||||||
|
|
||||||
|
import io.metersphere.validation.groups.Created;
|
||||||
|
import io.metersphere.validation.groups.Updated;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class BugUploadFileRequest implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "缺陷ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{bug.id.not_blank}")
|
||||||
|
private String bugId;
|
||||||
|
|
||||||
|
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{bug.project_id.not_blank}")
|
||||||
|
private String projectId;
|
||||||
|
|
||||||
|
@Schema(description = "不勾选的ID")
|
||||||
|
private List<String> excludeIds;
|
||||||
|
|
||||||
|
@Schema(description = "勾选的ID")
|
||||||
|
@Valid
|
||||||
|
private List<
|
||||||
|
@NotBlank(message = "{id must not be blank}", groups = {Created.class, Updated.class})
|
||||||
|
String
|
||||||
|
> selectIds = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "是否全选", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private boolean selectAll;
|
||||||
|
|
||||||
|
@Schema(description = "模块ID(根据模块树查询时要把当前节点以及子节点都放在这里。)")
|
||||||
|
private List<String> moduleIds;
|
||||||
|
|
||||||
|
@Schema(description = "文件类型")
|
||||||
|
private String fileType;
|
||||||
|
|
||||||
|
@Schema(description = "关键字")
|
||||||
|
private String keyword;
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package io.metersphere.bug.dto.response;
|
||||||
import io.metersphere.bug.domain.Bug;
|
import io.metersphere.bug.domain.Bug;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ import java.util.List;
|
||||||
* @author song-cc-rock
|
* @author song-cc-rock
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
public class BugDTO extends Bug {
|
public class BugDTO extends Bug {
|
||||||
|
|
||||||
@Schema(description = "缺陷内容")
|
@Schema(description = "缺陷内容")
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package io.metersphere.bug.dto.response;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author song-cc-rock
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class BugDetailDTO extends BugDTO {
|
||||||
|
|
||||||
|
@Schema(description = "附件集合")
|
||||||
|
List<BugFileDTO> attachments;
|
||||||
|
}
|
|
@ -1,11 +1,15 @@
|
||||||
package io.metersphere.bug.dto.response;
|
package io.metersphere.bug.dto.response;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
public class BugFileDTO {
|
public class BugFileDTO {
|
||||||
|
|
||||||
@Schema(description = "关系ID")
|
@Schema(description = "关系ID")
|
||||||
|
|
|
@ -7,7 +7,7 @@ public enum BugAttachmentSourceType {
|
||||||
*/
|
*/
|
||||||
ATTACHMENT,
|
ATTACHMENT,
|
||||||
/**
|
/**
|
||||||
* MD图片
|
* 缺陷内容
|
||||||
*/
|
*/
|
||||||
MD_PIC;
|
CONTENT;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<select id="list" resultMap="BugDTO">
|
<select id="list" resultMap="BugDTO">
|
||||||
select b.id, b.num, b.title, b.handle_user, b.create_user, b.create_time, b.update_time, b.delete_time, b.delete_user,
|
select b.id, b.num, b.title, b.handle_user, b.create_user, b.create_time, b.update_time, b.delete_time, b.delete_user,
|
||||||
b.project_id, b.template_id, b.platform, b.status, b.tags, bc.description from bug b left join bug_content bc on b.id = bc.bug_id
|
b.project_id, b.template_id, b.platform, b.status, b.tags from bug b
|
||||||
<include refid="queryWhereCondition"/>
|
<include refid="queryWhereCondition"/>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
|
@ -1,36 +1,89 @@
|
||||||
package io.metersphere.bug.service;
|
package io.metersphere.bug.service;
|
||||||
|
|
||||||
|
import io.metersphere.bug.domain.Bug;
|
||||||
import io.metersphere.bug.domain.BugLocalAttachment;
|
import io.metersphere.bug.domain.BugLocalAttachment;
|
||||||
import io.metersphere.bug.domain.BugLocalAttachmentExample;
|
import io.metersphere.bug.domain.BugLocalAttachmentExample;
|
||||||
|
import io.metersphere.bug.dto.request.BugDeleteFileRequest;
|
||||||
|
import io.metersphere.bug.dto.request.BugFileSourceRequest;
|
||||||
|
import io.metersphere.bug.dto.request.BugFileTransferRequest;
|
||||||
|
import io.metersphere.bug.dto.request.BugUploadFileRequest;
|
||||||
import io.metersphere.bug.dto.response.BugFileDTO;
|
import io.metersphere.bug.dto.response.BugFileDTO;
|
||||||
|
import io.metersphere.bug.enums.BugAttachmentSourceType;
|
||||||
|
import io.metersphere.bug.enums.BugPlatform;
|
||||||
import io.metersphere.bug.mapper.BugLocalAttachmentMapper;
|
import io.metersphere.bug.mapper.BugLocalAttachmentMapper;
|
||||||
|
import io.metersphere.bug.mapper.BugMapper;
|
||||||
|
import io.metersphere.plugin.platform.dto.request.SyncAttachmentToPlatformRequest;
|
||||||
|
import io.metersphere.plugin.platform.enums.SyncAttachmentType;
|
||||||
import io.metersphere.project.domain.FileAssociation;
|
import io.metersphere.project.domain.FileAssociation;
|
||||||
import io.metersphere.project.domain.FileAssociationExample;
|
import io.metersphere.project.domain.FileAssociationExample;
|
||||||
import io.metersphere.project.domain.FileMetadata;
|
import io.metersphere.project.domain.FileMetadata;
|
||||||
import io.metersphere.project.domain.FileMetadataExample;
|
import io.metersphere.project.domain.FileMetadataExample;
|
||||||
|
import io.metersphere.project.dto.filemanagement.FileAssociationDTO;
|
||||||
|
import io.metersphere.project.dto.filemanagement.FileLogRecord;
|
||||||
|
import io.metersphere.project.dto.filemanagement.request.FileMetadataTableRequest;
|
||||||
|
import io.metersphere.project.dto.filemanagement.response.FileInformationResponse;
|
||||||
import io.metersphere.project.mapper.FileAssociationMapper;
|
import io.metersphere.project.mapper.FileAssociationMapper;
|
||||||
import io.metersphere.project.mapper.FileMetadataMapper;
|
import io.metersphere.project.mapper.FileMetadataMapper;
|
||||||
|
import io.metersphere.project.service.FileAssociationService;
|
||||||
|
import io.metersphere.project.service.FileMetadataService;
|
||||||
|
import io.metersphere.project.service.FileService;
|
||||||
|
import io.metersphere.sdk.constants.DefaultRepositoryDir;
|
||||||
|
import io.metersphere.sdk.constants.StorageType;
|
||||||
|
import io.metersphere.sdk.exception.MSException;
|
||||||
|
import io.metersphere.sdk.file.FileCenter;
|
||||||
|
import io.metersphere.sdk.file.FileRequest;
|
||||||
|
import io.metersphere.sdk.util.BeanUtils;
|
||||||
import io.metersphere.sdk.util.FileAssociationSourceUtil;
|
import io.metersphere.sdk.util.FileAssociationSourceUtil;
|
||||||
|
import io.metersphere.sdk.util.LogUtils;
|
||||||
|
import io.metersphere.sdk.util.Translator;
|
||||||
|
import io.metersphere.system.log.constants.OperationLogModule;
|
||||||
|
import io.metersphere.system.uid.IDGenerator;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.util.unit.DataSize;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class BugAttachmentService {
|
public class BugAttachmentService {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private FileAssociationMapper fileAssociationMapper;
|
private BugMapper bugMapper;
|
||||||
|
@Resource
|
||||||
|
private FileService fileService;
|
||||||
@Resource
|
@Resource
|
||||||
private FileMetadataMapper fileMetadataMapper;
|
private FileMetadataMapper fileMetadataMapper;
|
||||||
@Resource
|
@Resource
|
||||||
|
private FileMetadataService fileMetadataService;
|
||||||
|
@Resource
|
||||||
|
@Lazy
|
||||||
|
private BugSyncExtraService bugSyncExtraService;
|
||||||
|
@Resource
|
||||||
|
private FileAssociationMapper fileAssociationMapper;
|
||||||
|
@Resource
|
||||||
|
private FileAssociationService fileAssociationService;
|
||||||
|
@Resource
|
||||||
private BugLocalAttachmentMapper bugLocalAttachmentMapper;
|
private BugLocalAttachmentMapper bugLocalAttachmentMapper;
|
||||||
|
|
||||||
|
@Value("50MB")
|
||||||
|
private DataSize maxFileSize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询缺陷的附件集合
|
* 查询缺陷的附件集合
|
||||||
* @param bugId 缺陷ID
|
* @param bugId 缺陷ID
|
||||||
|
@ -39,7 +92,7 @@ public class BugAttachmentService {
|
||||||
public List<BugFileDTO> getAllBugFiles(String bugId) {
|
public List<BugFileDTO> getAllBugFiles(String bugId) {
|
||||||
List<BugFileDTO> bugFiles = new ArrayList<>();
|
List<BugFileDTO> bugFiles = new ArrayList<>();
|
||||||
BugLocalAttachmentExample localAttachmentExample = new BugLocalAttachmentExample();
|
BugLocalAttachmentExample localAttachmentExample = new BugLocalAttachmentExample();
|
||||||
localAttachmentExample.createCriteria().andBugIdEqualTo(bugId);
|
localAttachmentExample.createCriteria().andBugIdEqualTo(bugId).andSourceEqualTo(BugAttachmentSourceType.ATTACHMENT.name());
|
||||||
List<BugLocalAttachment> bugLocalAttachments = bugLocalAttachmentMapper.selectByExample(localAttachmentExample);
|
List<BugLocalAttachment> bugLocalAttachments = bugLocalAttachmentMapper.selectByExample(localAttachmentExample);
|
||||||
if (!CollectionUtils.isEmpty(bugLocalAttachments)) {
|
if (!CollectionUtils.isEmpty(bugLocalAttachments)) {
|
||||||
bugLocalAttachments.forEach(localFile -> {
|
bugLocalAttachments.forEach(localFile -> {
|
||||||
|
@ -53,10 +106,7 @@ public class BugAttachmentService {
|
||||||
List<FileAssociation> fileAssociations = fileAssociationMapper.selectByExample(associationExample);
|
List<FileAssociation> fileAssociations = fileAssociationMapper.selectByExample(associationExample);
|
||||||
if (!CollectionUtils.isEmpty(fileAssociations)) {
|
if (!CollectionUtils.isEmpty(fileAssociations)) {
|
||||||
List<String> associateFileIds = fileAssociations.stream().map(FileAssociation::getFileId).toList();
|
List<String> associateFileIds = fileAssociations.stream().map(FileAssociation::getFileId).toList();
|
||||||
FileMetadataExample metadataExample = new FileMetadataExample();
|
Map<String, FileMetadata> fileMetadataMap = getLinkFileMetaMap(associateFileIds);
|
||||||
metadataExample.createCriteria().andIdIn(associateFileIds);
|
|
||||||
List<FileMetadata> fileMetadataList = fileMetadataMapper.selectByExample(metadataExample);
|
|
||||||
Map<String, FileMetadata> fileMetadataMap = fileMetadataList.stream().collect(Collectors.toMap(FileMetadata::getId, v -> v));
|
|
||||||
fileAssociations.forEach(associatedFile -> {
|
fileAssociations.forEach(associatedFile -> {
|
||||||
FileMetadata associatedFileMetadata = fileMetadataMap.get(associatedFile.getFileId());
|
FileMetadata associatedFileMetadata = fileMetadataMap.get(associatedFile.getFileId());
|
||||||
BugFileDTO associatedFileDTO = BugFileDTO.builder().refId(associatedFile.getId()).fileId(associatedFile.getFileId()).fileName(associatedFileMetadata.getName() + "." + associatedFileMetadata.getType())
|
BugFileDTO associatedFileDTO = BugFileDTO.builder().refId(associatedFile.getId()).fileId(associatedFile.getFileId()).fileName(associatedFileMetadata.getName() + "." + associatedFileMetadata.getType())
|
||||||
|
@ -68,6 +118,329 @@ public class BugAttachmentService {
|
||||||
return bugFiles;
|
return bugFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传附件->缺陷 (同步至平台)
|
||||||
|
* @param request 缺陷关联文件请求参数
|
||||||
|
* @param file 文件
|
||||||
|
* @param currentUser 当前用户
|
||||||
|
*/
|
||||||
|
public void uploadFile(BugUploadFileRequest request, MultipartFile file, String currentUser) {
|
||||||
|
Bug bug = bugMapper.selectByPrimaryKey(request.getBugId());
|
||||||
|
File tempFileDir = new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource(StringUtils.EMPTY)).getPath() + File.separator + "tmp"
|
||||||
|
+ File.separator);
|
||||||
|
List<SyncAttachmentToPlatformRequest> platformAttachments = new ArrayList<>();
|
||||||
|
if (file == null) {
|
||||||
|
// 关联文件
|
||||||
|
List<String> relateFileIds;
|
||||||
|
if (request.isSelectAll()) {
|
||||||
|
// 全选
|
||||||
|
FileMetadataTableRequest metadataTableRequest = new FileMetadataTableRequest();
|
||||||
|
BeanUtils.copyBean(metadataTableRequest, request);
|
||||||
|
List<FileInformationResponse> relateAllFiles = fileMetadataService.list(metadataTableRequest);
|
||||||
|
if (!CollectionUtils.isEmpty(request.getExcludeIds())) {
|
||||||
|
relateAllFiles.removeIf(relateFile -> request.getExcludeIds().contains(relateFile.getId()));
|
||||||
|
}
|
||||||
|
relateFileIds = relateAllFiles.stream().map(FileInformationResponse::getId).collect(Collectors.toList());
|
||||||
|
} else {
|
||||||
|
// 非全选
|
||||||
|
relateFileIds= request.getSelectIds();
|
||||||
|
}
|
||||||
|
// 缺陷与文件库关联
|
||||||
|
if (CollectionUtils.isEmpty(relateFileIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<SyncAttachmentToPlatformRequest> syncLinkFiles = uploadLinkFile(bug.getId(), bug.getPlatformBugId(), request.getProjectId(), tempFileDir, relateFileIds, currentUser, bug.getPlatform(), false);
|
||||||
|
platformAttachments.addAll(syncLinkFiles);
|
||||||
|
} else {
|
||||||
|
// 上传文件
|
||||||
|
List<SyncAttachmentToPlatformRequest> syncLocalFiles = uploadLocalFile(bug.getId(), bug.getPlatformBugId(), request.getProjectId(), tempFileDir, file, currentUser, bug.getPlatform());
|
||||||
|
platformAttachments.addAll(syncLocalFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步至第三方(异步调用)
|
||||||
|
if (!StringUtils.equals(bug.getPlatform(), BugPlatform.LOCAL.getName())) {
|
||||||
|
bugSyncExtraService.syncAttachmentToPlatform(platformAttachments, request.getProjectId(), tempFileDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除或取消关联附件->缺陷 (同步至平台)
|
||||||
|
* @param request 删除文件请求参数
|
||||||
|
*/
|
||||||
|
public void deleteFile(BugDeleteFileRequest request) {
|
||||||
|
Bug bug = bugMapper.selectByPrimaryKey(request.getBugId());
|
||||||
|
File tempFileDir = new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource(StringUtils.EMPTY)).getPath() + File.separator + "tmp"
|
||||||
|
+ File.separator);
|
||||||
|
List<SyncAttachmentToPlatformRequest> platformAttachments = new ArrayList<>();
|
||||||
|
if (request.getAssociated()) {
|
||||||
|
// 取消关联
|
||||||
|
List<SyncAttachmentToPlatformRequest> syncLinkFiles = unLinkFile(bug.getPlatformBugId(), request.getProjectId(),
|
||||||
|
tempFileDir, request.getRefId(), bug.getCreateUser(), bug.getPlatform(), false);
|
||||||
|
platformAttachments.addAll(syncLinkFiles);
|
||||||
|
} else {
|
||||||
|
// 删除本地上传的文件
|
||||||
|
List<SyncAttachmentToPlatformRequest> syncLocalFiles =
|
||||||
|
deleteLocalFile(bug.getId(), bug.getPlatformBugId(), request.getProjectId(), tempFileDir, request.getRefId(), bug.getPlatform(), true);
|
||||||
|
platformAttachments.addAll(syncLocalFiles);
|
||||||
|
}
|
||||||
|
// 同步至第三方(异步调用)
|
||||||
|
if (!StringUtils.equals(bug.getPlatform(), BugPlatform.LOCAL.getName())) {
|
||||||
|
bugSyncExtraService.syncAttachmentToPlatform(platformAttachments, request.getProjectId(), tempFileDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载或预览本地文件
|
||||||
|
* @param request 文件请求参数
|
||||||
|
* @return 文件字节流
|
||||||
|
*/
|
||||||
|
public ResponseEntity<byte[]> downloadOrPreview(BugFileSourceRequest request) {
|
||||||
|
BugLocalAttachment attachment = getLocalFile(request);
|
||||||
|
if (attachment == null) {
|
||||||
|
return ResponseEntity.ok().contentType(MediaType.parseMediaType("application/octet-stream")).body(null);
|
||||||
|
}
|
||||||
|
byte[] bytes = getLocalFileBytes(attachment, request.getProjectId(), request.getBugId());
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.parseMediaType("application/octet-stream"))
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + attachment.getFileName() + "\"")
|
||||||
|
.body(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转存附近至文件库
|
||||||
|
* @param request 请求参数
|
||||||
|
* @param currentUser 当前用户
|
||||||
|
* @return 文件ID
|
||||||
|
*/
|
||||||
|
public String transfer(BugFileTransferRequest request, String currentUser) {
|
||||||
|
BugLocalAttachment attachment = getLocalFile(request);
|
||||||
|
if (attachment == null) {
|
||||||
|
throw new MSException(Translator.get("file.transfer.error"));
|
||||||
|
}
|
||||||
|
byte[] bytes = getLocalFileBytes(attachment, request.getProjectId(), request.getBugId());
|
||||||
|
String fileId;
|
||||||
|
try {
|
||||||
|
FileAssociationDTO association = new FileAssociationDTO(attachment.getFileName(), bytes, attachment.getBugId(),
|
||||||
|
FileAssociationSourceUtil.SOURCE_TYPE_BUG, createFileLogRecord(currentUser, request.getProjectId()));
|
||||||
|
association.setModuleId(request.getModuleId());
|
||||||
|
fileId = fileAssociationService.transferAndAssociation(association);
|
||||||
|
// 删除本地上传的附件
|
||||||
|
deleteLocalFile(request.getBugId(), null, request.getProjectId(), null, attachment.getId(), null, false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MSException(Translator.get("file.transfer.error"));
|
||||||
|
}
|
||||||
|
return fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新文件至最新版本
|
||||||
|
* @param request 请求参数
|
||||||
|
* @param currentUser 当前用户
|
||||||
|
* @return 文件ID
|
||||||
|
*/
|
||||||
|
public String upgrade(BugDeleteFileRequest request, String currentUser) {
|
||||||
|
Bug bug = bugMapper.selectByPrimaryKey(request.getBugId());
|
||||||
|
File tempFileDir = new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource(StringUtils.EMPTY)).getPath() + File.separator + "tmp"
|
||||||
|
+ File.separator);
|
||||||
|
// 取消关联附件->同步
|
||||||
|
List<SyncAttachmentToPlatformRequest> syncUnlinkFiles = unLinkFile(bug.getPlatformBugId(), request.getProjectId(),
|
||||||
|
tempFileDir, request.getRefId(), currentUser, bug.getPlatform(), true);
|
||||||
|
// 更新后的文件需要同步
|
||||||
|
String upgradeFileId = fileAssociationService.upgrade(request.getRefId(), createFileLogRecord(currentUser, request.getProjectId()));
|
||||||
|
// 关联附件->同步
|
||||||
|
List<SyncAttachmentToPlatformRequest> syncLinkFiles = uploadLinkFile(bug.getId(), bug.getPlatformBugId(), request.getProjectId(), tempFileDir, List.of(upgradeFileId), currentUser, bug.getPlatform(), true);
|
||||||
|
List<SyncAttachmentToPlatformRequest> platformAttachments = Stream.concat(syncUnlinkFiles.stream(), syncLinkFiles.stream()).toList();
|
||||||
|
if (!StringUtils.equals(bug.getPlatform(), BugPlatform.LOCAL.getName())) {
|
||||||
|
bugSyncExtraService.syncAttachmentToPlatform(platformAttachments, request.getProjectId(), tempFileDir);
|
||||||
|
}
|
||||||
|
return upgradeFileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传MD文件
|
||||||
|
* @param file 文件
|
||||||
|
* @return 文件ID
|
||||||
|
*/
|
||||||
|
public String uploadMdFile(MultipartFile file) {
|
||||||
|
String fileName = StringUtils.trim(file.getOriginalFilename());
|
||||||
|
if (file.getSize() > maxFileSize.toBytes()) {
|
||||||
|
throw new MSException(Translator.get("file.size.is.too.large"));
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(fileName)) {
|
||||||
|
throw new MSException(Translator.get("file.name.cannot.be.empty"));
|
||||||
|
}
|
||||||
|
String fileId = IDGenerator.nextStr();
|
||||||
|
FileRequest fileRequest = new FileRequest();
|
||||||
|
fileRequest.setFileName(file.getOriginalFilename());
|
||||||
|
String systemTempDir = DefaultRepositoryDir.getSystemTempDir();
|
||||||
|
fileRequest.setFolder(systemTempDir + "/" + fileId);
|
||||||
|
try {
|
||||||
|
FileCenter.getDefaultRepository().saveFile(file, fileRequest);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.error(e);
|
||||||
|
throw new MSException(e.getMessage());
|
||||||
|
}
|
||||||
|
return fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取本地文件字节流
|
||||||
|
* @param attachment 本地附件信息
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param bugId 缺陷ID
|
||||||
|
* @return 文件字节流
|
||||||
|
*/
|
||||||
|
public byte[] getLocalFileBytes(BugLocalAttachment attachment, String projectId, String bugId) {
|
||||||
|
FileRequest fileRequest = buildBugFileRequest(projectId, bugId, attachment.getFileId(), attachment.getFileName());
|
||||||
|
byte[] bytes;
|
||||||
|
try {
|
||||||
|
bytes = fileService.download(fileRequest);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MSException("download file error!");
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传关联的文件(同步至平台)
|
||||||
|
* @param bugId 缺陷ID
|
||||||
|
* @param platformBugKey 平台缺陷ID
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param tmpFileDir 临时文件目录
|
||||||
|
* @param linkFileIds 关联文件ID集合
|
||||||
|
* @param currentUser 创建人
|
||||||
|
* @param platformName 平台名称
|
||||||
|
* @return 同步至平台的附件集合
|
||||||
|
*/
|
||||||
|
private List<SyncAttachmentToPlatformRequest> uploadLinkFile(String bugId, String platformBugKey, String projectId, File tmpFileDir,
|
||||||
|
List<String> linkFileIds, String currentUser, String platformName, boolean syncOnly) {
|
||||||
|
if (!syncOnly) {
|
||||||
|
fileAssociationService.association(bugId, FileAssociationSourceUtil.SOURCE_TYPE_BUG, linkFileIds, createFileLogRecord(currentUser, projectId));
|
||||||
|
}
|
||||||
|
// 同步新关联的附件至平台
|
||||||
|
List<SyncAttachmentToPlatformRequest> linkSyncFiles = new ArrayList<>();
|
||||||
|
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
|
||||||
|
Map<String, FileMetadata> fileMetadataMap = getLinkFileMetaMap(linkFileIds);
|
||||||
|
linkFileIds.forEach(fileId -> {
|
||||||
|
// 平台同步附件集合
|
||||||
|
FileMetadata meta = fileMetadataMap.get(fileId);
|
||||||
|
if (meta != null) {
|
||||||
|
try {
|
||||||
|
File uploadTmpFile = new File(tmpFileDir, meta.getName() + "." + meta.getType());
|
||||||
|
byte[] fileByte = fileMetadataService.getFileByte(meta);
|
||||||
|
FileUtils.writeByteArrayToFile(uploadTmpFile, fileByte);
|
||||||
|
linkSyncFiles.add(new SyncAttachmentToPlatformRequest(platformBugKey, uploadTmpFile, SyncAttachmentType.UPLOAD.syncOperateType()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MSException(Translator.get("bug_attachment_upload_error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return linkSyncFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传本地的文件(同步至平台)
|
||||||
|
* @param bugId 缺陷ID
|
||||||
|
* @param platformBugKey 平台缺陷ID
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param tmpFileDir 临时文件目录
|
||||||
|
* @param file 上传的本地文件
|
||||||
|
* @param currentUser 创建人
|
||||||
|
* @param platformName 平台名称
|
||||||
|
* @return 同步至平台的附件集合
|
||||||
|
*/
|
||||||
|
private List<SyncAttachmentToPlatformRequest> uploadLocalFile(String bugId, String platformBugKey, String projectId, File tmpFileDir,
|
||||||
|
MultipartFile file, String currentUser, String platformName) {
|
||||||
|
BugLocalAttachment record = new BugLocalAttachment();
|
||||||
|
record.setId(IDGenerator.nextStr());
|
||||||
|
record.setBugId(bugId);
|
||||||
|
record.setFileId(IDGenerator.nextStr());
|
||||||
|
record.setFileName(file.getOriginalFilename());
|
||||||
|
record.setSize(file.getSize());
|
||||||
|
record.setSource(BugAttachmentSourceType.ATTACHMENT.name());
|
||||||
|
record.setCreateTime(System.currentTimeMillis());
|
||||||
|
record.setCreateUser(currentUser);
|
||||||
|
bugLocalAttachmentMapper.insert(record);
|
||||||
|
List<SyncAttachmentToPlatformRequest> localSyncFiles = new ArrayList<>();
|
||||||
|
FileRequest fileRequest = buildBugFileRequest(projectId, bugId, record.getFileId(), file.getOriginalFilename());
|
||||||
|
try {
|
||||||
|
fileService.upload(file, fileRequest);
|
||||||
|
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
|
||||||
|
// 非本地平台,同步附件到平台
|
||||||
|
File uploadTmpFile = new File(tmpFileDir, Objects.requireNonNull(file.getOriginalFilename())).toPath().normalize().toFile();
|
||||||
|
FileUtils.writeByteArrayToFile(uploadTmpFile, file.getBytes());
|
||||||
|
localSyncFiles.add(new SyncAttachmentToPlatformRequest(platformBugKey, uploadTmpFile, SyncAttachmentType.UPLOAD.syncOperateType()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MSException(Translator.get("bug_attachment_upload_error"));
|
||||||
|
}
|
||||||
|
return localSyncFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消关联文件(同步至平台)
|
||||||
|
* @param platformBugKey 平台缺陷ID
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param tmpFileDir 临时文件目录
|
||||||
|
* @param refId 取消关联的文件引用ID
|
||||||
|
* @param currentUser 创建人
|
||||||
|
* @param platformName 平台名称
|
||||||
|
* @return 同步至平台的附件集合
|
||||||
|
*/
|
||||||
|
private List<SyncAttachmentToPlatformRequest> unLinkFile(String platformBugKey, String projectId, File tmpFileDir,
|
||||||
|
String refId, String currentUser, String platformName, boolean syncOnly) {
|
||||||
|
List<SyncAttachmentToPlatformRequest> linkSyncFiles = new ArrayList<>();
|
||||||
|
FileAssociation association = fileAssociationMapper.selectByPrimaryKey(refId);
|
||||||
|
FileMetadataExample example = new FileMetadataExample();
|
||||||
|
example.createCriteria().andIdEqualTo(association.getFileId());
|
||||||
|
FileMetadata fileMetadata = fileMetadataMapper.selectByExample(example).get(0);
|
||||||
|
// 取消关联的附件同步至平台
|
||||||
|
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
|
||||||
|
File deleteTmpFile = new File(tmpFileDir, fileMetadata.getName() + "." + fileMetadata.getType());
|
||||||
|
linkSyncFiles.add(new SyncAttachmentToPlatformRequest(platformBugKey, deleteTmpFile, SyncAttachmentType.DELETE.syncOperateType()));
|
||||||
|
}
|
||||||
|
// 取消关联的附件, FILE_ASSOCIATION表
|
||||||
|
if (!syncOnly) {
|
||||||
|
fileAssociationService.deleteByIds(List.of(refId), createFileLogRecord(currentUser, projectId));
|
||||||
|
}
|
||||||
|
return linkSyncFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除本地上传的文件(同步至平台)
|
||||||
|
* @param bugId 缺陷ID
|
||||||
|
* @param platformBugKey 平台缺陷ID
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param tmpFileDir 临时文件目录
|
||||||
|
* @param refId 关联ID
|
||||||
|
* @param platformName 平台名称
|
||||||
|
* @return 同步至平台的附件集合
|
||||||
|
*/
|
||||||
|
private List<SyncAttachmentToPlatformRequest> deleteLocalFile(String bugId, String platformBugKey, String projectId, File tmpFileDir,
|
||||||
|
String refId, String platformName, boolean syncToPlatform) {
|
||||||
|
List<SyncAttachmentToPlatformRequest> syncLocalFiles = new ArrayList<>();
|
||||||
|
BugLocalAttachment localAttachment = bugLocalAttachmentMapper.selectByPrimaryKey(refId);
|
||||||
|
// 删除本地上传的附件, BUG_LOCAL_ATTACHMENT表
|
||||||
|
FileRequest fileRequest = buildBugFileRequest(projectId, bugId, localAttachment.getFileId(), localAttachment.getFileName());
|
||||||
|
try {
|
||||||
|
// 删除MINIO附件
|
||||||
|
fileService.deleteFile(fileRequest);
|
||||||
|
// 删除的本地的附件同步至平台
|
||||||
|
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName()) && syncToPlatform) {
|
||||||
|
File deleteTmpFile = new File(tmpFileDir, localAttachment.getFileName());
|
||||||
|
syncLocalFiles.add(new SyncAttachmentToPlatformRequest(platformBugKey, deleteTmpFile, SyncAttachmentType.DELETE.syncOperateType()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MSException(Translator.get("bug_attachment_delete_error"));
|
||||||
|
}
|
||||||
|
bugLocalAttachmentMapper.deleteByPrimaryKey(refId);
|
||||||
|
return syncLocalFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取本地文件类型
|
* 获取本地文件类型
|
||||||
* @param fileName 文件名
|
* @param fileName 文件名
|
||||||
|
@ -81,4 +454,61 @@ public class BugAttachmentService {
|
||||||
return StringUtils.EMPTY;
|
return StringUtils.EMPTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param operator 操作人
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @return 文件操作日志记录
|
||||||
|
*/
|
||||||
|
private FileLogRecord createFileLogRecord(String operator, String projectId){
|
||||||
|
return FileLogRecord.builder()
|
||||||
|
.logModule(OperationLogModule.BUG_MANAGEMENT)
|
||||||
|
.operator(operator)
|
||||||
|
.projectId(projectId)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建缺陷文件请求
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param resourceId 资源ID
|
||||||
|
* @param fileId 文件ID
|
||||||
|
* @param fileName 文件名称
|
||||||
|
* @return 文件请求对象
|
||||||
|
*/
|
||||||
|
private FileRequest buildBugFileRequest(String projectId, String resourceId, String fileId, String fileName) {
|
||||||
|
FileRequest fileRequest = new FileRequest();
|
||||||
|
fileRequest.setFolder(DefaultRepositoryDir.getBugDir(projectId, resourceId) + "/" + fileId);
|
||||||
|
fileRequest.setFileName(StringUtils.isEmpty(fileName) ? null : fileName);
|
||||||
|
fileRequest.setStorage(StorageType.MINIO.name());
|
||||||
|
return fileRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取关联文件数据
|
||||||
|
* @param linkFileIds 关联文件ID集合
|
||||||
|
* @return 文件集合
|
||||||
|
*/
|
||||||
|
private Map<String, FileMetadata> getLinkFileMetaMap(List<String> linkFileIds) {
|
||||||
|
FileMetadataExample metadataExample = new FileMetadataExample();
|
||||||
|
metadataExample.createCriteria().andIdIn(linkFileIds);
|
||||||
|
List<FileMetadata> fileMetadataList = fileMetadataMapper.selectByExample(metadataExample);
|
||||||
|
return fileMetadataList.stream().collect(Collectors.toMap(FileMetadata::getId, v -> v));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取本地文件
|
||||||
|
* @param request 请求参数
|
||||||
|
* @return 本地文件信息
|
||||||
|
*/
|
||||||
|
private BugLocalAttachment getLocalFile(BugFileSourceRequest request) {
|
||||||
|
BugLocalAttachmentExample example = new BugLocalAttachmentExample();
|
||||||
|
example.createCriteria().andFileIdEqualTo(request.getFileId()).andBugIdEqualTo(request.getBugId());
|
||||||
|
List<BugLocalAttachment> bugLocalAttachments = bugLocalAttachmentMapper.selectByExample(example);
|
||||||
|
if (CollectionUtils.isEmpty(bugLocalAttachments)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return bugLocalAttachments.get(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,27 @@
|
||||||
package io.metersphere.bug.service;
|
package io.metersphere.bug.service;
|
||||||
|
|
||||||
|
import io.metersphere.bug.domain.Bug;
|
||||||
import io.metersphere.bug.dto.request.BugEditRequest;
|
import io.metersphere.bug.dto.request.BugEditRequest;
|
||||||
|
import io.metersphere.bug.dto.response.BugCustomFieldDTO;
|
||||||
|
import io.metersphere.bug.mapper.ExtBugCustomFieldMapper;
|
||||||
import io.metersphere.plugin.platform.dto.SelectOption;
|
import io.metersphere.plugin.platform.dto.SelectOption;
|
||||||
|
import io.metersphere.system.domain.User;
|
||||||
import io.metersphere.system.dto.BugNoticeDTO;
|
import io.metersphere.system.dto.BugNoticeDTO;
|
||||||
import io.metersphere.system.dto.sdk.OptionDTO;
|
import io.metersphere.system.dto.sdk.OptionDTO;
|
||||||
|
import io.metersphere.system.mapper.UserMapper;
|
||||||
|
import io.metersphere.system.notice.NoticeModel;
|
||||||
|
import io.metersphere.system.notice.constants.NoticeConstants;
|
||||||
|
import io.metersphere.system.notice.utils.MessageTemplateUtils;
|
||||||
|
import io.metersphere.system.service.NoticeSendService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.apache.commons.beanutils.BeanMap;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -23,10 +34,16 @@ public class BugNoticeService {
|
||||||
public static final String CUSTOM_STATUS = "status";
|
public static final String CUSTOM_STATUS = "status";
|
||||||
public static final String CUSTOM_HANDLE_USER = "处理人";
|
public static final String CUSTOM_HANDLE_USER = "处理人";
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserMapper userMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private BugService bugService;
|
private BugService bugService;
|
||||||
@Resource
|
@Resource
|
||||||
private BugStatusService bugStatusService;
|
private BugStatusService bugStatusService;
|
||||||
|
@Resource
|
||||||
|
private NoticeSendService noticeSendService;
|
||||||
|
@Resource
|
||||||
|
private ExtBugCustomFieldMapper extBugCustomFieldMapper;
|
||||||
|
|
||||||
public BugNoticeDTO getNoticeByRequest(BugEditRequest request) {
|
public BugNoticeDTO getNoticeByRequest(BugEditRequest request) {
|
||||||
// 获取状态选项, 处理人选项
|
// 获取状态选项, 处理人选项
|
||||||
|
@ -61,6 +78,35 @@ public class BugNoticeService {
|
||||||
return notice;
|
return notice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendDeleteNotice(Bug bug, String currentUser) {
|
||||||
|
Map<String, String> statusMap = getStatusMap(bug.getProjectId());
|
||||||
|
Map<String, String> handlerMap = getHandleMap(bug.getProjectId());
|
||||||
|
// 缺陷相关内容
|
||||||
|
BugNoticeDTO notice = new BugNoticeDTO();
|
||||||
|
notice.setTitle(bug.getTitle());
|
||||||
|
notice.setStatus(statusMap.get(bug.getStatus()));
|
||||||
|
notice.setHandleUser(handlerMap.get(bug.getHandleUser()));
|
||||||
|
List<BugCustomFieldDTO> customFields = extBugCustomFieldMapper.getBugAllCustomFields(List.of(bug.getId()), bug.getProjectId());
|
||||||
|
List<OptionDTO> fields = customFields.stream().map(field -> {
|
||||||
|
OptionDTO fieldDTO = new OptionDTO();
|
||||||
|
fieldDTO.setId(field.getName());
|
||||||
|
fieldDTO.setName(field.getValue());
|
||||||
|
return fieldDTO;
|
||||||
|
}).toList();
|
||||||
|
notice.setCustomFields(fields);
|
||||||
|
BeanMap beanMap = new BeanMap(notice);
|
||||||
|
User user = userMapper.selectByPrimaryKey(currentUser);
|
||||||
|
Map paramMap = new HashMap<>(beanMap);
|
||||||
|
paramMap.put(NoticeConstants.RelatedUser.OPERATOR, user.getName());
|
||||||
|
Map<String, String> defaultTemplateMap = MessageTemplateUtils.getDefaultTemplateMap();
|
||||||
|
String template = defaultTemplateMap.get(NoticeConstants.TemplateText.BUG_TASK_DELETE);
|
||||||
|
Map<String, String> defaultSubjectMap = MessageTemplateUtils.getDefaultTemplateSubjectMap();
|
||||||
|
String subject = defaultSubjectMap.get(NoticeConstants.TemplateText.BUG_TASK_DELETE);
|
||||||
|
NoticeModel noticeModel = NoticeModel.builder().operator(currentUser)
|
||||||
|
.context(template).subject(subject).paramMap(paramMap).event(NoticeConstants.Event.DELETE).build();
|
||||||
|
noticeSendService.send(NoticeConstants.TaskType.BUG_TASK, noticeModel);
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, String> getStatusMap(String projectId) {
|
private Map<String, String> getStatusMap(String projectId) {
|
||||||
List<SelectOption> statusOption = bugStatusService.getHeaderStatusOption(projectId);
|
List<SelectOption> statusOption = bugStatusService.getHeaderStatusOption(projectId);
|
||||||
return statusOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText));
|
return statusOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText));
|
||||||
|
|
|
@ -4,10 +4,7 @@ import io.metersphere.bug.constants.BugExportColumns;
|
||||||
import io.metersphere.bug.domain.*;
|
import io.metersphere.bug.domain.*;
|
||||||
import io.metersphere.bug.dto.BugTemplateInjectField;
|
import io.metersphere.bug.dto.BugTemplateInjectField;
|
||||||
import io.metersphere.bug.dto.request.*;
|
import io.metersphere.bug.dto.request.*;
|
||||||
import io.metersphere.bug.dto.response.BugCustomFieldDTO;
|
import io.metersphere.bug.dto.response.*;
|
||||||
import io.metersphere.bug.dto.response.BugDTO;
|
|
||||||
import io.metersphere.bug.dto.response.BugRelateCaseCountDTO;
|
|
||||||
import io.metersphere.bug.dto.response.BugTagEditDTO;
|
|
||||||
import io.metersphere.bug.enums.BugAttachmentSourceType;
|
import io.metersphere.bug.enums.BugAttachmentSourceType;
|
||||||
import io.metersphere.bug.enums.BugPlatform;
|
import io.metersphere.bug.enums.BugPlatform;
|
||||||
import io.metersphere.bug.enums.BugTemplateCustomField;
|
import io.metersphere.bug.enums.BugTemplateCustomField;
|
||||||
|
@ -38,8 +35,6 @@ import io.metersphere.sdk.util.*;
|
||||||
import io.metersphere.system.domain.ServiceIntegration;
|
import io.metersphere.system.domain.ServiceIntegration;
|
||||||
import io.metersphere.system.domain.Template;
|
import io.metersphere.system.domain.Template;
|
||||||
import io.metersphere.system.domain.TemplateCustomField;
|
import io.metersphere.system.domain.TemplateCustomField;
|
||||||
import io.metersphere.system.domain.User;
|
|
||||||
import io.metersphere.system.dto.BugNoticeDTO;
|
|
||||||
import io.metersphere.system.dto.sdk.OptionDTO;
|
import io.metersphere.system.dto.sdk.OptionDTO;
|
||||||
import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO;
|
import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO;
|
||||||
import io.metersphere.system.dto.sdk.TemplateDTO;
|
import io.metersphere.system.dto.sdk.TemplateDTO;
|
||||||
|
@ -49,16 +44,11 @@ import io.metersphere.system.log.dto.LogDTO;
|
||||||
import io.metersphere.system.log.service.OperationLogService;
|
import io.metersphere.system.log.service.OperationLogService;
|
||||||
import io.metersphere.system.mapper.BaseUserMapper;
|
import io.metersphere.system.mapper.BaseUserMapper;
|
||||||
import io.metersphere.system.mapper.TemplateMapper;
|
import io.metersphere.system.mapper.TemplateMapper;
|
||||||
import io.metersphere.system.mapper.UserMapper;
|
|
||||||
import io.metersphere.system.notice.NoticeModel;
|
|
||||||
import io.metersphere.system.notice.constants.NoticeConstants;
|
|
||||||
import io.metersphere.system.notice.utils.MessageTemplateUtils;
|
|
||||||
import io.metersphere.system.service.*;
|
import io.metersphere.system.service.*;
|
||||||
import io.metersphere.system.uid.IDGenerator;
|
import io.metersphere.system.uid.IDGenerator;
|
||||||
import io.metersphere.system.uid.NumGenerator;
|
import io.metersphere.system.uid.NumGenerator;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jodd.util.StringUtil;
|
import jodd.util.StringUtil;
|
||||||
import org.apache.commons.beanutils.BeanMap;
|
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.collections4.ListUtils;
|
import org.apache.commons.collections4.ListUtils;
|
||||||
import org.apache.commons.collections4.MapUtils;
|
import org.apache.commons.collections4.MapUtils;
|
||||||
|
@ -68,6 +58,7 @@ import org.apache.ibatis.session.ExecutorType;
|
||||||
import org.apache.ibatis.session.SqlSession;
|
import org.apache.ibatis.session.SqlSession;
|
||||||
import org.apache.ibatis.session.SqlSessionFactory;
|
import org.apache.ibatis.session.SqlSessionFactory;
|
||||||
import org.mybatis.spring.SqlSessionUtils;
|
import org.mybatis.spring.SqlSessionUtils;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
@ -95,14 +86,10 @@ public class BugService {
|
||||||
@Resource
|
@Resource
|
||||||
private BugMapper bugMapper;
|
private BugMapper bugMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private UserMapper userMapper;
|
|
||||||
@Resource
|
|
||||||
private ExtBugMapper extBugMapper;
|
private ExtBugMapper extBugMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private ProjectMapper projectMapper;
|
private ProjectMapper projectMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private NoticeSendService noticeSendService;
|
|
||||||
@Resource
|
|
||||||
private BaseUserMapper baseUserMapper;
|
private BaseUserMapper baseUserMapper;
|
||||||
@Resource
|
@Resource
|
||||||
protected TemplateMapper templateMapper;
|
protected TemplateMapper templateMapper;
|
||||||
|
@ -121,6 +108,9 @@ public class BugService {
|
||||||
@Resource
|
@Resource
|
||||||
private BaseTemplateCustomFieldService baseTemplateCustomFieldService;
|
private BaseTemplateCustomFieldService baseTemplateCustomFieldService;
|
||||||
@Resource
|
@Resource
|
||||||
|
@Lazy
|
||||||
|
private BugNoticeService bugNoticeService;
|
||||||
|
@Resource
|
||||||
private BugCustomFieldMapper bugCustomFieldMapper;
|
private BugCustomFieldMapper bugCustomFieldMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private ExtBugCustomFieldMapper extBugCustomFieldMapper;
|
private ExtBugCustomFieldMapper extBugCustomFieldMapper;
|
||||||
|
@ -160,6 +150,8 @@ public class BugService {
|
||||||
private BugStatusService bugStatusService;
|
private BugStatusService bugStatusService;
|
||||||
@Resource
|
@Resource
|
||||||
private ProjectMemberService projectMemberService;
|
private ProjectMemberService projectMemberService;
|
||||||
|
@Resource
|
||||||
|
private BugAttachmentService bugAttachmentService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 缺陷列表查询
|
* 缺陷列表查询
|
||||||
|
@ -191,7 +183,6 @@ public class BugService {
|
||||||
* 2. 第三方平台缺陷需调用插件同步缺陷至其他平台(自定义字段需处理);
|
* 2. 第三方平台缺陷需调用插件同步缺陷至其他平台(自定义字段需处理);
|
||||||
* 3. 保存MS缺陷(基础字段, 自定义字段)
|
* 3. 保存MS缺陷(基础字段, 自定义字段)
|
||||||
* 4. 处理附件(第三方平台缺陷需异步调用接口同步附件至第三方)
|
* 4. 处理附件(第三方平台缺陷需异步调用接口同步附件至第三方)
|
||||||
* 4. 变更历史, 操作记录;
|
|
||||||
*/
|
*/
|
||||||
String platformName = projectApplicationService.getPlatformName(request.getProjectId());
|
String platformName = projectApplicationService.getPlatformName(request.getProjectId());
|
||||||
PlatformBugUpdateDTO platformBug = null;
|
PlatformBugUpdateDTO platformBug = null;
|
||||||
|
@ -224,6 +215,28 @@ public class BugService {
|
||||||
handleAndSaveAttachments(request, files, currentUser, platformName, platformBug);
|
handleAndSaveAttachments(request, files, currentUser, platformName, platformBug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缺陷详情
|
||||||
|
* @param id 缺陷ID
|
||||||
|
* @return 缺陷详情
|
||||||
|
*/
|
||||||
|
public BugDetailDTO get(String id) {
|
||||||
|
BugDetailDTO bugDetail = new BugDetailDTO();
|
||||||
|
Bug bug = checkBugExist(id);
|
||||||
|
BeanUtils.copyBean(bugDetail, bug);
|
||||||
|
// 缺陷内容
|
||||||
|
BugContent bugContent = bugContentMapper.selectByPrimaryKey(id);
|
||||||
|
if (bugContent != null) {
|
||||||
|
bugDetail.setDescription(bugContent.getDescription());
|
||||||
|
}
|
||||||
|
// 缺陷自定义字段
|
||||||
|
List<BugCustomFieldDTO> customFields = extBugCustomFieldMapper.getBugAllCustomFields(List.of(id), bug.getProjectId());
|
||||||
|
bugDetail.setCustomFields(customFields);
|
||||||
|
// 缺陷附件信息
|
||||||
|
bugDetail.setAttachments(bugAttachmentService.getAllBugFiles(id));
|
||||||
|
return bugDetail;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除缺陷
|
* 删除缺陷
|
||||||
*
|
*
|
||||||
|
@ -246,7 +259,7 @@ public class BugService {
|
||||||
bugMapper.deleteByPrimaryKey(id);
|
bugMapper.deleteByPrimaryKey(id);
|
||||||
}
|
}
|
||||||
// 发送通知
|
// 发送通知
|
||||||
sendDeleteNotice(bug, currentUser);
|
bugNoticeService.sendDeleteNotice(bug, currentUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -554,7 +567,6 @@ public class BugService {
|
||||||
// 来自平台模板
|
// 来自平台模板
|
||||||
templateDTO.setPlatformDefault(false);
|
templateDTO.setPlatformDefault(false);
|
||||||
String platformName = projectApplicationService.getPlatformName(projectId);
|
String platformName = projectApplicationService.getPlatformName(projectId);
|
||||||
// TODO: 严重程度
|
|
||||||
|
|
||||||
// 状态字段
|
// 状态字段
|
||||||
attachTemplateStatusField(templateDTO, projectId, fromStatusId, platformBugKey);
|
attachTemplateStatusField(templateDTO, projectId, fromStatusId, platformBugKey);
|
||||||
|
@ -690,12 +702,17 @@ public class BugService {
|
||||||
bug.setUpdateUser(currentUser);
|
bug.setUpdateUser(currentUser);
|
||||||
bug.setUpdateTime(System.currentTimeMillis());
|
bug.setUpdateTime(System.currentTimeMillis());
|
||||||
bugMapper.updateByPrimaryKeySelective(bug);
|
bugMapper.updateByPrimaryKeySelective(bug);
|
||||||
|
BugContent originalContent = bugContentMapper.selectByPrimaryKey(bug.getId());
|
||||||
BugContent bugContent = new BugContent();
|
BugContent bugContent = new BugContent();
|
||||||
bugContent.setBugId(bug.getId());
|
bugContent.setBugId(bug.getId());
|
||||||
bugContent.setDescription(request.getDescription());
|
bugContent.setDescription(request.getDescription());
|
||||||
|
if (originalContent == null) {
|
||||||
|
bugContentMapper.insert(bugContent);
|
||||||
|
} else {
|
||||||
bugContentMapper.updateByPrimaryKeySelective(bugContent);
|
bugContentMapper.updateByPrimaryKeySelective(bugContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验缺陷是否存在
|
* 校验缺陷是否存在
|
||||||
|
@ -792,7 +809,7 @@ public class BugService {
|
||||||
List<SyncAttachmentToPlatformRequest> allSyncAttachments = Stream.concat(removeAttachments.stream(), uploadAttachments.stream()).toList();
|
List<SyncAttachmentToPlatformRequest> allSyncAttachments = Stream.concat(removeAttachments.stream(), uploadAttachments.stream()).toList();
|
||||||
|
|
||||||
// 同步至第三方(异步调用)
|
// 同步至第三方(异步调用)
|
||||||
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
|
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName()) && CollectionUtils.isNotEmpty(allSyncAttachments)) {
|
||||||
bugSyncExtraService.syncAttachmentToPlatform(allSyncAttachments, request.getProjectId(), tempFileDir);
|
bugSyncExtraService.syncAttachmentToPlatform(allSyncAttachments, request.getProjectId(), tempFileDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -815,7 +832,7 @@ public class BugService {
|
||||||
Map<String, BugLocalAttachment> localAttachmentMap = bugLocalAttachments.stream().collect(Collectors.toMap(BugLocalAttachment::getFileId, v -> v));
|
Map<String, BugLocalAttachment> localAttachmentMap = bugLocalAttachments.stream().collect(Collectors.toMap(BugLocalAttachment::getFileId, v -> v));
|
||||||
// 删除本地上传的附件, BUG_LOCAL_ATTACHMENT表
|
// 删除本地上传的附件, BUG_LOCAL_ATTACHMENT表
|
||||||
request.getDeleteLocalFileIds().forEach(deleteFileId -> {
|
request.getDeleteLocalFileIds().forEach(deleteFileId -> {
|
||||||
FileRequest fileRequest = buildBugFileRequest(request.getProjectId(), request.getId(), localAttachmentMap.get(deleteFileId).getFileName());
|
FileRequest fileRequest = buildBugFileRequest(request.getProjectId(), request.getId(), deleteFileId, localAttachmentMap.get(deleteFileId).getFileName());
|
||||||
try {
|
try {
|
||||||
fileService.deleteFile(fileRequest);
|
fileService.deleteFile(fileRequest);
|
||||||
// 删除的本地的附件同步至平台
|
// 删除的本地的附件同步至平台
|
||||||
|
@ -884,12 +901,12 @@ public class BugService {
|
||||||
});
|
});
|
||||||
extBugLocalAttachmentMapper.batchInsert(addFiles);
|
extBugLocalAttachmentMapper.batchInsert(addFiles);
|
||||||
uploadMinioFiles.forEach((fileId, file) -> {
|
uploadMinioFiles.forEach((fileId, file) -> {
|
||||||
FileRequest fileRequest = buildBugFileRequest(request.getProjectId(), request.getId(), file.getOriginalFilename());
|
FileRequest fileRequest = buildBugFileRequest(request.getProjectId(), request.getId(), fileId, file.getOriginalFilename());
|
||||||
try {
|
try {
|
||||||
fileService.upload(file, fileRequest);
|
fileService.upload(file, fileRequest);
|
||||||
// 同步新上传的附件至平台
|
// 同步新上传的附件至平台
|
||||||
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
|
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
|
||||||
File uploadTmpFile = new File(tempFileDir, Objects.requireNonNull(file.getOriginalFilename()));
|
File uploadTmpFile = new File(tempFileDir, Objects.requireNonNull(file.getOriginalFilename())).toPath().normalize().toFile();;
|
||||||
FileUtils.writeByteArrayToFile(uploadTmpFile, file.getBytes());
|
FileUtils.writeByteArrayToFile(uploadTmpFile, file.getBytes());
|
||||||
uploadPlatformAttachments.add(new SyncAttachmentToPlatformRequest(platformBug.getPlatformBugKey(), uploadTmpFile, SyncAttachmentType.UPLOAD.syncOperateType()));
|
uploadPlatformAttachments.add(new SyncAttachmentToPlatformRequest(platformBug.getPlatformBugKey(), uploadTmpFile, SyncAttachmentType.UPLOAD.syncOperateType()));
|
||||||
}
|
}
|
||||||
|
@ -1093,7 +1110,7 @@ public class BugService {
|
||||||
attachmentExample.createCriteria().andBugIdEqualTo(bugId);
|
attachmentExample.createCriteria().andBugIdEqualTo(bugId);
|
||||||
List<BugLocalAttachment> bugLocalAttachments = bugLocalAttachmentMapper.selectByExample(attachmentExample);
|
List<BugLocalAttachment> bugLocalAttachments = bugLocalAttachmentMapper.selectByExample(attachmentExample);
|
||||||
bugLocalAttachments.forEach(bugLocalAttachment -> {
|
bugLocalAttachments.forEach(bugLocalAttachment -> {
|
||||||
FileRequest fileRequest = buildBugFileRequest(projectId, bugId, bugLocalAttachment.getFileName());
|
FileRequest fileRequest = buildBugFileRequest(projectId, bugId, bugLocalAttachment.getFileId(), bugLocalAttachment.getFileName());
|
||||||
try {
|
try {
|
||||||
fileService.deleteFile(fileRequest);
|
fileService.deleteFile(fileRequest);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -1133,12 +1150,13 @@ public class BugService {
|
||||||
* 构建缺陷文件请求
|
* 构建缺陷文件请求
|
||||||
* @param projectId 项目ID
|
* @param projectId 项目ID
|
||||||
* @param resourceId 资源ID
|
* @param resourceId 资源ID
|
||||||
|
* @param fileId 文件ID
|
||||||
* @param fileName 文件名称
|
* @param fileName 文件名称
|
||||||
* @return 文件请求对象
|
* @return 文件请求对象
|
||||||
*/
|
*/
|
||||||
private FileRequest buildBugFileRequest(String projectId, String resourceId, String fileName) {
|
private FileRequest buildBugFileRequest(String projectId, String resourceId, String fileId, String fileName) {
|
||||||
FileRequest fileRequest = new FileRequest();
|
FileRequest fileRequest = new FileRequest();
|
||||||
fileRequest.setFolder(DefaultRepositoryDir.getBugDir(projectId, resourceId));
|
fileRequest.setFolder(DefaultRepositoryDir.getBugDir(projectId, resourceId) + "/" + fileId);
|
||||||
fileRequest.setFileName(StringUtils.isEmpty(fileName) ? null : fileName);
|
fileRequest.setFileName(StringUtils.isEmpty(fileName) ? null : fileName);
|
||||||
fileRequest.setStorage(StorageType.MINIO.name());
|
fileRequest.setStorage(StorageType.MINIO.name());
|
||||||
return fileRequest;
|
return fileRequest;
|
||||||
|
@ -1351,35 +1369,4 @@ public class BugService {
|
||||||
});
|
});
|
||||||
return logs;
|
return logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendDeleteNotice(Bug bug, String currentUser) {
|
|
||||||
List<SelectOption> statusOption = bugStatusService.getHeaderStatusOption(bug.getProjectId());
|
|
||||||
Map<String, String> statusMap = statusOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText));
|
|
||||||
List<SelectOption> handlerOption = getHeaderHandlerOption(bug.getProjectId());
|
|
||||||
Map<String, String> handlerMap = handlerOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText));
|
|
||||||
// 缺陷相关内容
|
|
||||||
BugNoticeDTO notice = new BugNoticeDTO();
|
|
||||||
notice.setTitle(bug.getTitle());
|
|
||||||
notice.setStatus(statusMap.get(bug.getStatus()));
|
|
||||||
notice.setHandleUser(handlerMap.get(bug.getHandleUser()));
|
|
||||||
List<BugCustomFieldDTO> customFields = extBugCustomFieldMapper.getBugAllCustomFields(List.of(bug.getId()), bug.getProjectId());
|
|
||||||
List<OptionDTO> fields = customFields.stream().map(field -> {
|
|
||||||
OptionDTO fieldDTO = new OptionDTO();
|
|
||||||
fieldDTO.setId(field.getName());
|
|
||||||
fieldDTO.setName(field.getValue());
|
|
||||||
return fieldDTO;
|
|
||||||
}).toList();
|
|
||||||
notice.setCustomFields(fields);
|
|
||||||
BeanMap beanMap = new BeanMap(notice);
|
|
||||||
User user = userMapper.selectByPrimaryKey(currentUser);
|
|
||||||
Map paramMap = new HashMap<>(beanMap);
|
|
||||||
paramMap.put(NoticeConstants.RelatedUser.OPERATOR, user.getName());
|
|
||||||
Map<String, String> defaultTemplateMap = MessageTemplateUtils.getDefaultTemplateMap();
|
|
||||||
String template = defaultTemplateMap.get(NoticeConstants.TemplateText.BUG_TASK_DELETE);
|
|
||||||
Map<String, String> defaultSubjectMap = MessageTemplateUtils.getDefaultTemplateSubjectMap();
|
|
||||||
String subject = defaultSubjectMap.get(NoticeConstants.TemplateText.BUG_TASK_DELETE);
|
|
||||||
NoticeModel noticeModel = NoticeModel.builder().operator(currentUser)
|
|
||||||
.context(template).subject(subject).paramMap(paramMap).event(NoticeConstants.Event.DELETE).build();
|
|
||||||
noticeSendService.send(NoticeConstants.TaskType.BUG_TASK, noticeModel);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,249 @@
|
||||||
|
package io.metersphere.bug.controller;
|
||||||
|
|
||||||
|
import io.metersphere.bug.dto.request.BugDeleteFileRequest;
|
||||||
|
import io.metersphere.bug.dto.request.BugFileSourceRequest;
|
||||||
|
import io.metersphere.bug.dto.request.BugFileTransferRequest;
|
||||||
|
import io.metersphere.bug.dto.request.BugUploadFileRequest;
|
||||||
|
import io.metersphere.bug.dto.response.BugFileDTO;
|
||||||
|
import io.metersphere.project.dto.filemanagement.request.FileMetadataTableRequest;
|
||||||
|
import io.metersphere.project.dto.filemanagement.request.FileUploadRequest;
|
||||||
|
import io.metersphere.project.dto.filemanagement.response.FileInformationResponse;
|
||||||
|
import io.metersphere.project.service.FileMetadataService;
|
||||||
|
import io.metersphere.sdk.util.JSON;
|
||||||
|
import io.metersphere.system.base.BaseTest;
|
||||||
|
import io.metersphere.system.controller.handler.ResultHolder;
|
||||||
|
import io.metersphere.system.utils.Pager;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlConfig;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
public class BugAttachmentControllerTests extends BaseTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FileMetadataService fileMetadataService;
|
||||||
|
public static final String BUG_ATTACHMENT_LIST = "/bug/attachment/list";
|
||||||
|
public static final String BUG_ATTACHMENT_RELATED_PAGE = "/bug/attachment/file/page";
|
||||||
|
public static final String BUG_ATTACHMENT_UPLOAD = "/bug/attachment/upload";
|
||||||
|
public static final String BUG_ATTACHMENT_DELETE = "/bug/attachment/delete";
|
||||||
|
public static final String BUG_ATTACHMENT_PREVIEW = "/bug/attachment/preview";
|
||||||
|
public static final String BUG_ATTACHMENT_DOWNLOAD = "/bug/attachment/download";
|
||||||
|
public static final String BUG_ATTACHMENT_TRANSFER_OPTION = "/bug/attachment/transfer/options";
|
||||||
|
public static final String BUG_ATTACHMENT_TRANSFER = "/bug/attachment/transfer";
|
||||||
|
public static final String BUG_ATTACHMENT_CHECK_UPDATE = "/bug/attachment/check-update";
|
||||||
|
public static final String BUG_ATTACHMENT_UPDATE = "/bug/attachment/update";
|
||||||
|
public static final String BUG_ATTACHMENT_UPLOAD_MD = "/bug/attachment/upload/md/file";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(0)
|
||||||
|
void testUploadMdFile() throws Exception {
|
||||||
|
MockMultipartFile fileTooLarge = new MockMultipartFile("file", "test.txt", MediaType.APPLICATION_OCTET_STREAM_VALUE, new byte[50 * 1024 * 1024 + 1]);
|
||||||
|
this.requestUploadFile(BUG_ATTACHMENT_UPLOAD_MD, fileTooLarge).andExpect(status().is5xxServerError());
|
||||||
|
MockMultipartFile fileWithNoName = new MockMultipartFile("file", "", MediaType.APPLICATION_OCTET_STREAM_VALUE, "aa".getBytes());
|
||||||
|
this.requestUploadFile(BUG_ATTACHMENT_UPLOAD_MD, fileWithNoName).andExpect(status().is5xxServerError());
|
||||||
|
// Mock minio save file exception
|
||||||
|
MockMultipartFile file = new MockMultipartFile("file", "test.txt", MediaType.APPLICATION_OCTET_STREAM_VALUE, "aa".getBytes());
|
||||||
|
this.requestUploadFile(BUG_ATTACHMENT_UPLOAD_MD, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
@Sql(scripts = {"/dml/init_bug_attachment.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
|
||||||
|
void prepareData() throws Exception {
|
||||||
|
// 准备文件库数据
|
||||||
|
FileUploadRequest fileUploadRequest = new FileUploadRequest();
|
||||||
|
fileUploadRequest.setProjectId("default-project-for-attachment");
|
||||||
|
MockMultipartFile file1 = new MockMultipartFile("file", "TEST1.JPG", MediaType.APPLICATION_OCTET_STREAM_VALUE, "aa".getBytes());
|
||||||
|
fileMetadataService.upload(fileUploadRequest, "admin", file1);
|
||||||
|
MockMultipartFile file2 = new MockMultipartFile("file", "TEST2.JPG", MediaType.APPLICATION_OCTET_STREAM_VALUE, "bb".getBytes());
|
||||||
|
fileMetadataService.upload(fileUploadRequest, "admin", file2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
void testFilePage() throws Exception {
|
||||||
|
List<FileInformationResponse> unRelatedFiles = getRelatedFiles();
|
||||||
|
Assertions.assertEquals(2, unRelatedFiles.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
void testUpload() throws Exception {
|
||||||
|
List<FileInformationResponse> unRelatedFiles = getRelatedFiles();
|
||||||
|
// 非全选关联
|
||||||
|
BugUploadFileRequest request = new BugUploadFileRequest();
|
||||||
|
request.setBugId("default-attachment-bug-id");
|
||||||
|
request.setProjectId("default-project-for-attachment");
|
||||||
|
request.setSelectAll(false);
|
||||||
|
request.setSelectIds(List.of(unRelatedFiles.get(0).getId()));
|
||||||
|
MultiValueMap<String, Object> paramMap1 = getDefaultMultiPartParam(request, null);
|
||||||
|
this.requestMultipartWithOk(BUG_ATTACHMENT_UPLOAD, paramMap1);
|
||||||
|
// 全选关联
|
||||||
|
request.setSelectAll(true);
|
||||||
|
request.setExcludeIds(List.of(unRelatedFiles.get(0).getId(), unRelatedFiles.get(1).getId()));
|
||||||
|
MultiValueMap<String, Object> paramMap2 = getDefaultMultiPartParam(request, null);
|
||||||
|
this.requestMultipartWithOk(BUG_ATTACHMENT_UPLOAD, paramMap2);
|
||||||
|
request.setSelectAll(true);
|
||||||
|
request.setExcludeIds(null);
|
||||||
|
MultiValueMap<String, Object> paramMap3 = getDefaultMultiPartParam(request, null);
|
||||||
|
this.requestMultipartWithOk(BUG_ATTACHMENT_UPLOAD, paramMap3);
|
||||||
|
String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/test.xlsx")).getPath();
|
||||||
|
File file = new File(filePath);
|
||||||
|
MultiValueMap<String, Object> paramMapWithFile = getDefaultMultiPartParam(request, file);
|
||||||
|
this.requestMultipartWithOk(BUG_ATTACHMENT_UPLOAD, paramMapWithFile);
|
||||||
|
// 第三方平台的缺陷关联文件
|
||||||
|
request.setBugId("default-bug-id-tapd");
|
||||||
|
request.setSelectAll(false);
|
||||||
|
request.setSelectIds(List.of("not-exist-file-id"));
|
||||||
|
MultiValueMap<String, Object> paramMap4 = getDefaultMultiPartParam(request, null);
|
||||||
|
this.requestMultipartWithOk(BUG_ATTACHMENT_UPLOAD, paramMap4);
|
||||||
|
request.setSelectIds(List.of(unRelatedFiles.get(0).getId()));
|
||||||
|
MultiValueMap<String, Object> paramMap5 = getDefaultMultiPartParam(request, null);
|
||||||
|
this.requestMultipartWithOk(BUG_ATTACHMENT_UPLOAD, paramMap5);
|
||||||
|
MultiValueMap<String, Object> paramMap6 = getDefaultMultiPartParam(request, file);
|
||||||
|
this.requestMultipartWithOk(BUG_ATTACHMENT_UPLOAD, paramMap6);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
void previewOrDownload() throws Exception {
|
||||||
|
BugFileSourceRequest request = new BugFileSourceRequest();
|
||||||
|
request.setBugId("default-attachment-bug-id");
|
||||||
|
request.setProjectId("default-project-for-attachment");
|
||||||
|
request.setAssociated(false);
|
||||||
|
request.setFileId("not-exist-file-id");
|
||||||
|
this.requestPostDownloadFile(BUG_ATTACHMENT_PREVIEW, null, request);
|
||||||
|
List<BugFileDTO> files = getBugFiles("default-attachment-bug-id");
|
||||||
|
files.forEach(file -> {
|
||||||
|
request.setFileId(file.getFileId());
|
||||||
|
request.setAssociated(file.getAssociated());
|
||||||
|
try {
|
||||||
|
this.requestPostDownloadFile(BUG_ATTACHMENT_PREVIEW, null, request);
|
||||||
|
this.requestPostDownloadFile(BUG_ATTACHMENT_DOWNLOAD, null, request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
void testTransfer() throws Exception {
|
||||||
|
this.requestGetWithOk(BUG_ATTACHMENT_TRANSFER_OPTION + "/default-project-for-attachment");
|
||||||
|
BugFileTransferRequest request = new BugFileTransferRequest();
|
||||||
|
request.setBugId("default-attachment-bug-id");
|
||||||
|
request.setProjectId("default-project-for-attachment");
|
||||||
|
request.setModuleId("root");
|
||||||
|
request.setAssociated(false);
|
||||||
|
request.setFileId("not-exist-file-id");
|
||||||
|
this.requestPost(BUG_ATTACHMENT_TRANSFER, request).andExpect(status().is5xxServerError());
|
||||||
|
List<BugFileDTO> files = getBugFiles("default-attachment-bug-id");
|
||||||
|
files.stream().filter(file -> !file.getAssociated()).forEach(file -> {
|
||||||
|
request.setFileId(file.getFileId());
|
||||||
|
request.setAssociated(file.getAssociated());
|
||||||
|
try {
|
||||||
|
this.requestPostWithOk(BUG_ATTACHMENT_TRANSFER, request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
|
void testUpgrade() throws Exception {
|
||||||
|
// 检查更新
|
||||||
|
this.requestPostWithOk(BUG_ATTACHMENT_CHECK_UPDATE, List.of("test-id"));
|
||||||
|
List<BugFileDTO> bugFiles = getBugFiles("default-attachment-bug-id");
|
||||||
|
BugDeleteFileRequest request = new BugDeleteFileRequest();
|
||||||
|
request.setBugId("default-attachment-bug-id");
|
||||||
|
request.setProjectId("default-project-for-attachment");
|
||||||
|
request.setAssociated(true);
|
||||||
|
bugFiles.forEach(file -> {
|
||||||
|
try {
|
||||||
|
request.setRefId(file.getRefId());
|
||||||
|
this.requestPostWithOk(BUG_ATTACHMENT_UPDATE, request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
List<BugFileDTO> tapdFiles = getBugFiles("default-bug-id-tapd");
|
||||||
|
tapdFiles.stream().filter(BugFileDTO::getAssociated).forEach(file -> {
|
||||||
|
try {
|
||||||
|
request.setBugId("default-bug-id-tapd");
|
||||||
|
request.setRefId(file.getRefId());
|
||||||
|
this.requestPostWithOk(BUG_ATTACHMENT_UPDATE, request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(7)
|
||||||
|
void testDelete() throws Exception {
|
||||||
|
// Local缺陷附件删除
|
||||||
|
List<BugFileDTO> files = getBugFiles("default-attachment-bug-id");
|
||||||
|
files.forEach(file -> {
|
||||||
|
BugDeleteFileRequest request = new BugDeleteFileRequest();
|
||||||
|
request.setBugId("default-attachment-bug-id");
|
||||||
|
request.setProjectId("default-project-for-attachment");
|
||||||
|
request.setRefId(file.getRefId());
|
||||||
|
request.setAssociated(file.getAssociated());
|
||||||
|
try {
|
||||||
|
this.requestPostWithOk(BUG_ATTACHMENT_DELETE, request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
List<BugFileDTO> tapdBugFiles = getBugFiles("default-bug-id-tapd");
|
||||||
|
tapdBugFiles.forEach(file -> {
|
||||||
|
BugDeleteFileRequest request = new BugDeleteFileRequest();
|
||||||
|
request.setBugId("default-bug-id-tapd");
|
||||||
|
request.setProjectId("default-project-for-attachment");
|
||||||
|
request.setRefId(file.getRefId());
|
||||||
|
request.setAssociated(file.getAssociated());
|
||||||
|
try {
|
||||||
|
this.requestPostWithOk(BUG_ATTACHMENT_DELETE, request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FileInformationResponse> getRelatedFiles() throws Exception {
|
||||||
|
FileMetadataTableRequest request = new FileMetadataTableRequest();
|
||||||
|
request.setProjectId("default-project-for-attachment");
|
||||||
|
request.setCurrent(1);
|
||||||
|
request.setPageSize(10);
|
||||||
|
MvcResult mvcResult = this.requestPostWithOkAndReturn(BUG_ATTACHMENT_RELATED_PAGE, request);
|
||||||
|
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
|
||||||
|
Pager<?> pageData = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
|
||||||
|
// 返回的数据量不超过规定要返回的数据量相同
|
||||||
|
return JSON.parseArray(JSON.toJSONString(pageData.getList()), FileInformationResponse.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BugFileDTO> getBugFiles(String bugId) throws Exception {
|
||||||
|
MvcResult mvcResult = this.requestGetWithOkAndReturn(BUG_ATTACHMENT_LIST + "/" + bugId);
|
||||||
|
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
|
||||||
|
return JSON.parseArray(JSON.toJSONString(resultHolder.getData()), BugFileDTO.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||||
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
|
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
public class BugCommentTests extends BaseTest {
|
public class BugCommentControllerTests extends BaseTest {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private BugCommentMapper bugCommentMapper;
|
private BugCommentMapper bugCommentMapper;
|
|
@ -76,6 +76,8 @@ public class BugControllerTests extends BaseTest {
|
||||||
public static final String BUG_PAGE = "/bug/page";
|
public static final String BUG_PAGE = "/bug/page";
|
||||||
public static final String BUG_ADD = "/bug/add";
|
public static final String BUG_ADD = "/bug/add";
|
||||||
public static final String BUG_UPDATE = "/bug/update";
|
public static final String BUG_UPDATE = "/bug/update";
|
||||||
|
public static final String BUG_DETAIL = "/bug/get";
|
||||||
|
public static final String BUG_QUICK_UPDATE = "/bug/quick-update";
|
||||||
public static final String BUG_DELETE = "/bug/delete";
|
public static final String BUG_DELETE = "/bug/delete";
|
||||||
public static final String BUG_TEMPLATE_OPTION = "/bug/template/option";
|
public static final String BUG_TEMPLATE_OPTION = "/bug/template/option";
|
||||||
public static final String BUG_TEMPLATE_DETAIL = "/bug/template/detail";
|
public static final String BUG_TEMPLATE_DETAIL = "/bug/template/detail";
|
||||||
|
@ -227,6 +229,7 @@ public class BugControllerTests extends BaseTest {
|
||||||
File file = new File(filePath);
|
File file = new File(filePath);
|
||||||
MultiValueMap<String, Object> paramMap = getDefaultMultiPartParam(request, file);
|
MultiValueMap<String, Object> paramMap = getDefaultMultiPartParam(request, file);
|
||||||
this.requestMultipartWithOkAndReturn(BUG_ADD, paramMap);
|
this.requestMultipartWithOkAndReturn(BUG_ADD, paramMap);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -271,8 +274,17 @@ public class BugControllerTests extends BaseTest {
|
||||||
request.setLinkFileIds(null);
|
request.setLinkFileIds(null);
|
||||||
request.setUnLinkRefIds(null);
|
request.setUnLinkRefIds(null);
|
||||||
request.setDeleteLocalFileIds(null);
|
request.setDeleteLocalFileIds(null);
|
||||||
|
request.setDescription("1111");
|
||||||
noFileParamMap.add("request", JSON.toJSONString(request));
|
noFileParamMap.add("request", JSON.toJSONString(request));
|
||||||
this.requestMultipartWithOkAndReturn(BUG_UPDATE, noFileParamMap);
|
this.requestMultipartWithOkAndReturn(BUG_UPDATE, noFileParamMap);
|
||||||
|
// 获取缺陷详情
|
||||||
|
this.requestGetWithOk(BUG_DETAIL + "/default-bug-id");
|
||||||
|
this.requestGetWithOk(BUG_DETAIL + "/" + request.getId());
|
||||||
|
// 更新部分
|
||||||
|
BugQuickEditRequest quickEditRequest = new BugQuickEditRequest();
|
||||||
|
quickEditRequest.setId(request.getId());
|
||||||
|
quickEditRequest.setTags(List.of("TEST"));
|
||||||
|
this.requestPost(BUG_QUICK_UPDATE, quickEditRequest, status().isOk());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -14,6 +14,8 @@ import org.mockito.Mockito;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.test.context.jdbc.Sql;
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
import org.springframework.test.context.jdbc.SqlConfig;
|
import org.springframework.test.context.jdbc.SqlConfig;
|
||||||
|
|
||||||
|
@ -44,6 +46,11 @@ public class BugSyncExtraServiceTests extends BaseTest {
|
||||||
@Sql(scripts = {"/dml/init_bug_sync_extra.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
|
@Sql(scripts = {"/dml/init_bug_sync_extra.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
|
||||||
void test() throws Exception {
|
void test() throws Exception {
|
||||||
List<BugFileDTO> allBugFile = bugAttachmentService.getAllBugFiles("bug-for-sync-extra");
|
List<BugFileDTO> allBugFile = bugAttachmentService.getAllBugFiles("bug-for-sync-extra");
|
||||||
|
// Mock minio upload exception
|
||||||
|
MockMultipartFile file = new MockMultipartFile("file", "test.txt", MediaType.APPLICATION_OCTET_STREAM_VALUE, "aa".getBytes());
|
||||||
|
Mockito.doThrow(new MSException("save minio error!")).when(minioMock).saveFile(Mockito.eq(file), Mockito.any());
|
||||||
|
MSException uploadException = assertThrows(MSException.class, () -> bugAttachmentService.uploadMdFile(file));
|
||||||
|
assertEquals(uploadException.getMessage(), "save minio error!");
|
||||||
// Mock minio delete exception
|
// Mock minio delete exception
|
||||||
Mockito.doThrow(new MSException("delete minio error!")).when(minioMock).delete(Mockito.any());
|
Mockito.doThrow(new MSException("delete minio error!")).when(minioMock).delete(Mockito.any());
|
||||||
MSException deleteException = assertThrows(MSException.class, () ->
|
MSException deleteException = assertThrows(MSException.class, () ->
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time)
|
||||||
|
VALUE ('default-project-for-bug-tmp-1', null, '100001', '测试项目(缺陷)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
|
||||||
|
|
||||||
|
INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time)
|
||||||
|
VALUE ('default-project-for-attachment', null, '100001', '测试项目(缺陷)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
|
||||||
|
|
||||||
|
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time, update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted)
|
||||||
|
VALUES ('default-attachment-bug-id', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-attachment', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 1),
|
||||||
|
('default-bug-id-tapd', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-attachment', 'default-bug-template-id', 'Tapd', 'open', '["default-tag"]', null, 0);
|
|
@ -328,7 +328,7 @@ public class FileMetadataService {
|
||||||
byte[] bytes = this.getFileByte(fileMetadata);
|
byte[] bytes = this.getFileByte(fileMetadata);
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.contentType(MediaType.parseMediaType("application/octet-stream"))
|
.contentType(MediaType.parseMediaType("application/octet-stream"))
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + this.getFileName(fileMetadata.getId(), fileMetadata.getType()) + "\"")
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + this.getFileName(fileMetadata.getName(), fileMetadata.getType()) + "\"")
|
||||||
.body(bytes);
|
.body(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue