feat(功能用例): 功能用例评论副文本增加文件

This commit is contained in:
guoyuqi 2024-01-11 20:06:31 +08:00 committed by 刘瑞斌
parent d1e36c20b9
commit c76e081451
15 changed files with 408 additions and 33 deletions

View File

@ -1,10 +1,8 @@
package io.metersphere.functional.domain;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.metersphere.validation.groups.*;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import jakarta.validation.constraints.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
@ -12,40 +10,43 @@ import lombok.Data;
@Data
public class FunctionalCaseAttachment implements Serializable {
@Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case_attachment.id.not_blank}", groups = {Updated.class})
@Size(min = 1, max = 50, message = "{functional_case_attachment.id.length_range}", groups = {Created.class, Updated.class})
private String id;
@Schema(description = "功能用例ID", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "功能用例ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case_attachment.case_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{functional_case_attachment.case_id.length_range}", groups = {Created.class, Updated.class})
private String caseId;
@Schema(description = "文件的ID", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "文件的ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case_attachment.file_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{functional_case_attachment.file_id.length_range}", groups = {Created.class, Updated.class})
private String fileId;
@Schema(description = "文件名称", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "文件名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case_attachment.file_name.not_blank}", groups = {Created.class})
@Size(min = 1, max = 255, message = "{functional_case_attachment.file_name.length_range}", groups = {Created.class, Updated.class})
private String fileName;
@Schema(description = "文件大小", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case_attachment.size.not_blank}", groups = {Created.class})
@Size(min = 1, max = 19, message = "{functional_case_attachment.size.length_range}", groups = {Created.class, Updated.class})
@Schema(description = "文件来源", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case_attachment.file_source.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{functional_case_attachment.file_source.length_range}", groups = {Created.class, Updated.class})
private String fileSource;
@Schema(description = "文件大小", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{functional_case_attachment.size.not_blank}", groups = {Created.class})
private Long size;
@Schema(description = "是否本地", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case_attachment.local.not_blank}", groups = {Created.class})
@Size(min = 1, max = 1, message = "{functional_case_attachment.local.length_range}", groups = {Created.class, Updated.class})
@Schema(description = "是否本地", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{functional_case_attachment.local.not_blank}", groups = {Created.class})
private Boolean local;
@Schema(description = "创建人")
@Schema(description = "创建人")
private String createUser;
@Schema(description = "创建时间")
@Schema(description = "创建时间")
private Long createTime;
private static final long serialVersionUID = 1L;
@ -55,6 +56,7 @@ public class FunctionalCaseAttachment implements Serializable {
caseId("case_id", "caseId", "VARCHAR", false),
fileId("file_id", "fileId", "VARCHAR", false),
fileName("file_name", "fileName", "VARCHAR", false),
fileSource("file_source", "fileSource", "VARCHAR", false),
size("size", "size", "BIGINT", true),
local("local", "local", "BIT", true),
createUser("create_user", "createUser", "VARCHAR", false),

View File

@ -384,6 +384,76 @@ public class FunctionalCaseAttachmentExample {
return (Criteria) this;
}
public Criteria andFileSourceIsNull() {
addCriterion("file_source is null");
return (Criteria) this;
}
public Criteria andFileSourceIsNotNull() {
addCriterion("file_source is not null");
return (Criteria) this;
}
public Criteria andFileSourceEqualTo(String value) {
addCriterion("file_source =", value, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceNotEqualTo(String value) {
addCriterion("file_source <>", value, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceGreaterThan(String value) {
addCriterion("file_source >", value, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceGreaterThanOrEqualTo(String value) {
addCriterion("file_source >=", value, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceLessThan(String value) {
addCriterion("file_source <", value, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceLessThanOrEqualTo(String value) {
addCriterion("file_source <=", value, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceLike(String value) {
addCriterion("file_source like", value, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceNotLike(String value) {
addCriterion("file_source not like", value, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceIn(List<String> values) {
addCriterion("file_source in", values, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceNotIn(List<String> values) {
addCriterion("file_source not in", values, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceBetween(String value1, String value2) {
addCriterion("file_source between", value1, value2, "fileSource");
return (Criteria) this;
}
public Criteria andFileSourceNotBetween(String value1, String value2) {
addCriterion("file_source not between", value1, value2, "fileSource");
return (Criteria) this;
}
public Criteria andSizeIsNull() {
addCriterion("`size` is null");
return (Criteria) this;

View File

@ -6,6 +6,7 @@
<result column="case_id" jdbcType="VARCHAR" property="caseId" />
<result column="file_id" jdbcType="VARCHAR" property="fileId" />
<result column="file_name" jdbcType="VARCHAR" property="fileName" />
<result column="file_source" jdbcType="VARCHAR" property="fileSource" />
<result column="size" jdbcType="BIGINT" property="size" />
<result column="local" jdbcType="BIT" property="local" />
<result column="create_user" jdbcType="VARCHAR" property="createUser" />
@ -70,7 +71,7 @@
</where>
</sql>
<sql id="Base_Column_List">
id, case_id, file_id, file_name, `size`, `local`, create_user, create_time
id, case_id, file_id, file_name, file_source, `size`, `local`, create_user, create_time
</sql>
<select id="selectByExample" parameterType="io.metersphere.functional.domain.FunctionalCaseAttachmentExample" resultMap="BaseResultMap">
select
@ -104,11 +105,13 @@
</delete>
<insert id="insert" parameterType="io.metersphere.functional.domain.FunctionalCaseAttachment">
insert into functional_case_attachment (id, case_id, file_id,
file_name, `size`, `local`, create_user,
create_time)
file_name, file_source, `size`,
`local`, create_user, create_time
)
values (#{id,jdbcType=VARCHAR}, #{caseId,jdbcType=VARCHAR}, #{fileId,jdbcType=VARCHAR},
#{fileName,jdbcType=VARCHAR}, #{size,jdbcType=BIGINT}, #{local,jdbcType=BIT}, #{createUser,jdbcType=VARCHAR},
#{createTime,jdbcType=BIGINT})
#{fileName,jdbcType=VARCHAR}, #{fileSource,jdbcType=VARCHAR}, #{size,jdbcType=BIGINT},
#{local,jdbcType=BIT}, #{createUser,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}
)
</insert>
<insert id="insertSelective" parameterType="io.metersphere.functional.domain.FunctionalCaseAttachment">
insert into functional_case_attachment
@ -125,6 +128,9 @@
<if test="fileName != null">
file_name,
</if>
<if test="fileSource != null">
file_source,
</if>
<if test="size != null">
`size`,
</if>
@ -151,6 +157,9 @@
<if test="fileName != null">
#{fileName,jdbcType=VARCHAR},
</if>
<if test="fileSource != null">
#{fileSource,jdbcType=VARCHAR},
</if>
<if test="size != null">
#{size,jdbcType=BIGINT},
</if>
@ -186,6 +195,9 @@
<if test="record.fileName != null">
file_name = #{record.fileName,jdbcType=VARCHAR},
</if>
<if test="record.fileSource != null">
file_source = #{record.fileSource,jdbcType=VARCHAR},
</if>
<if test="record.size != null">
`size` = #{record.size,jdbcType=BIGINT},
</if>
@ -209,6 +221,7 @@
case_id = #{record.caseId,jdbcType=VARCHAR},
file_id = #{record.fileId,jdbcType=VARCHAR},
file_name = #{record.fileName,jdbcType=VARCHAR},
file_source = #{record.fileSource,jdbcType=VARCHAR},
`size` = #{record.size,jdbcType=BIGINT},
`local` = #{record.local,jdbcType=BIT},
create_user = #{record.createUser,jdbcType=VARCHAR},
@ -229,6 +242,9 @@
<if test="fileName != null">
file_name = #{fileName,jdbcType=VARCHAR},
</if>
<if test="fileSource != null">
file_source = #{fileSource,jdbcType=VARCHAR},
</if>
<if test="size != null">
`size` = #{size,jdbcType=BIGINT},
</if>
@ -249,6 +265,7 @@
set case_id = #{caseId,jdbcType=VARCHAR},
file_id = #{fileId,jdbcType=VARCHAR},
file_name = #{fileName,jdbcType=VARCHAR},
file_source = #{fileSource,jdbcType=VARCHAR},
`size` = #{size,jdbcType=BIGINT},
`local` = #{local,jdbcType=BIT},
create_user = #{createUser,jdbcType=VARCHAR},
@ -257,12 +274,14 @@
</update>
<insert id="batchInsert" parameterType="map">
insert into functional_case_attachment
(id, case_id, file_id, file_name, `size`, `local`, create_user, create_time)
(id, case_id, file_id, file_name, file_source, `size`, `local`, create_user, create_time
)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.caseId,jdbcType=VARCHAR}, #{item.fileId,jdbcType=VARCHAR},
#{item.fileName,jdbcType=VARCHAR}, #{item.size,jdbcType=BIGINT}, #{item.local,jdbcType=BIT},
#{item.createUser,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT})
#{item.fileName,jdbcType=VARCHAR}, #{item.fileSource,jdbcType=VARCHAR}, #{item.size,jdbcType=BIGINT},
#{item.local,jdbcType=BIT}, #{item.createUser,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT}
)
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -287,6 +306,9 @@
<if test="'file_name'.toString() == column.value">
#{item.fileName,jdbcType=VARCHAR}
</if>
<if test="'file_source'.toString() == column.value">
#{item.fileSource,jdbcType=VARCHAR}
</if>
<if test="'size'.toString() == column.value">
#{item.size,jdbcType=BIGINT}
</if>

View File

@ -112,6 +112,7 @@ CREATE TABLE IF NOT EXISTS functional_case_attachment
`case_id` VARCHAR(50) NOT NULL COMMENT '功能用例ID',
`file_id` VARCHAR(50) NOT NULL COMMENT '文件的ID',
`file_name` VARCHAR(255) NOT NULL COMMENT '文件名称',
`file_source` VARCHAR(50) NOT NULL DEFAULT 'ATTACHMENT' COMMENT '文件来源' ,
`size` BIGINT NOT NULL COMMENT '文件大小',
`local` BIT(1) NOT NULL COMMENT '是否本地',
`create_user` VARCHAR(50) NOT NULL COMMENT '创建人',
@ -126,6 +127,7 @@ CREATE INDEX idx_case_id ON functional_case_attachment (case_id);
CREATE INDEX idx_local ON functional_case_attachment (local);
CREATE INDEX idx_file_id ON functional_case_attachment (file_id);
CREATE INDEX idx_file_name ON functional_case_attachment (file_name);
CREATE INDEX idx_file_source ON functional_case_attachment(file_source);
CREATE TABLE IF NOT EXISTS functional_case_follower

View File

@ -46,6 +46,7 @@ public class DefaultRepositoryDir {
private static final String PROJECT_API_CASE_DIR = PROJECT_DIR + "/api-case/%s";
private static final String PROJECT_ENV_SSL_DIR = PROJECT_DIR + "/environment/%s";
private static final String PROJECT_FUNCTIONAL_CASE_DIR = PROJECT_DIR + "/functional-case/%s";
private static final String PROJECT_FUNCTIONAL_CASE_PREVIEW_DIR = PROJECT_DIR + "/functional-case/preview/%s";
private static final String PROJECT_FILE_MANAGEMENT_DIR = PROJECT_DIR + "/file-management";
private static final String PROJECT_FILE_MANAGEMENT_PREVIEW_DIR = PROJECT_DIR + "/file-management/preview";
/**
@ -84,6 +85,10 @@ public class DefaultRepositoryDir {
return String.format(PROJECT_FUNCTIONAL_CASE_DIR, projectId, functionalCaseId);
}
public static String getFunctionalCasePreviewDir(String projectId, String functionalCaseId) {
return String.format(PROJECT_FUNCTIONAL_CASE_PREVIEW_DIR, projectId, functionalCaseId);
}
public static String getFileManagementDir(String projectId) {
return String.format(PROJECT_FILE_MANAGEMENT_DIR, projectId);
}

View File

@ -95,4 +95,11 @@ public interface FileRepository {
* @throws Exception
*/
void copyFile(FileCopyRequest request) throws Exception;
/**
* 获取文件大小
* @param request
* @throws Exception
*/
long getFileSize(FileRequest request) throws Exception;
}

View File

@ -83,4 +83,9 @@ public class GitRepository implements FileRepository {
public void copyFile(FileCopyRequest request) throws Exception {
throw new MSException("Not support copy file");
}
@Override
public long getFileSize(FileRequest request) throws Exception {
return 0;
}
}

View File

@ -93,6 +93,12 @@ public class LocalFileRepository implements FileRepository {
throw new MSException("Not support copy file");
}
@Override
public long getFileSize(FileRequest request) throws Exception {
File file = new File(getFilePath(request));
return file.length();
}
private String getFilePath(FileRequest request) {
MsFileUtils.validateFileName(request.getFolder(), request.getFileName());
return StringUtils.join(getFileDir(request), "/", request.getFileName());

View File

@ -206,4 +206,14 @@ public class MinioRepository implements FileRepository {
.object(fileName) // 文件名
.build());
}
@Override
public long getFileSize(FileRequest request) throws Exception {
String fileName = getPath(request);
return client.statObject(StatObjectArgs.builder()
.bucket(BUCKET) // 存储桶
.object(fileName) // 文件名
.build()).size();
}
}

View File

@ -0,0 +1,11 @@
package io.metersphere.functional.constants;
public enum CaseFileSourceType {
ATTACHMENT,//附件
PREREQUISITE,//前置条件
TEXT_DESCRIPTION,//步骤描述
EXPECTED_RESULT,//预期结果
DESCRIPTION,//备注
CASE_COMMENT,//用例评论
REVIEW_COMMENT//评审评论
}

View File

@ -170,4 +170,11 @@ public class FunctionalCaseAttachmentController {
}
@PostMapping("/upload/temp/file")
@Operation(summary = "用例管理-功能用例-上传副文本里所需的文件资源并返回文件ID")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_COMMENT)
public String upload(@RequestParam("file") MultipartFile file) throws Exception {
return functionalCaseAttachmentService.uploadTemp(file);
}
}

View File

@ -6,6 +6,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.util.List;
@Data
public class FunctionalCaseCommentRequest {
@ -34,4 +36,15 @@ public class FunctionalCaseCommentRequest {
@NotBlank(message = "{functional_case_comment.event.not_blank}", groups = {Created.class})
private String event;
@Schema(description = "项目Id")
@NotBlank(message = "{project.id.not_blank}")
private String projectId;
/**
* 新上传的文件ID
* 创建时先按ID创建目录再把文件放入目录
*/
@Schema(description = "新上传的文件ID")
private List<String> uploadFileIds;
}

View File

@ -2,6 +2,7 @@ package io.metersphere.functional.service;
import com.google.common.collect.Lists;
import io.metersphere.functional.constants.CaseFileSourceType;
import io.metersphere.functional.domain.FunctionalCaseAttachment;
import io.metersphere.functional.domain.FunctionalCaseAttachmentExample;
import io.metersphere.functional.dto.FunctionalCaseAttachmentDTO;
@ -18,19 +19,25 @@ 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.FileCopyRequest;
import io.metersphere.sdk.file.FileRepository;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.FileAssociationSourceUtil;
import io.metersphere.sdk.util.*;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.unit.DataSize;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
@ -57,6 +64,10 @@ public class FunctionalCaseAttachmentService {
private static final String UPLOAD_FILE = "/attachment/upload/file";
private static final String DELETED_FILE = "/attachment/delete/file";
@Value("50MB")
private DataSize maxFileSize;
/**
* 保存本地上传文件和用例关联关系
*
@ -102,6 +113,7 @@ public class FunctionalCaseAttachmentService {
caseAttachment.setCaseId(caseId);
caseAttachment.setFileId(fileId);
caseAttachment.setFileName(fileName);
caseAttachment.setFileSource(CaseFileSourceType.ATTACHMENT.toString());
caseAttachment.setSize(fileSize);
caseAttachment.setLocal(isLocal);
caseAttachment.setCreateUser(userId);
@ -313,4 +325,136 @@ public class FunctionalCaseAttachmentService {
this.unAssociation(request.getCaseId(), Arrays.asList(request.getId()), DELETED_FILE, userId, request.getProjectId());
}
}
public String uploadTemp(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(Translator.get("file_upload_fail"));
}
return fileId;
}
public void uploadMinioFile(String caseId, String projectId, List<String> uploadFileIds, String userId, String fileSource){
String functionalCaseDir = DefaultRepositoryDir.getFunctionalCaseDir(projectId, caseId);
// 处理本地上传文件
FileRepository defaultRepository = FileCenter.getDefaultRepository();
String systemTempDir = DefaultRepositoryDir.getSystemTempDir();
if (CollectionUtils.isNotEmpty(uploadFileIds)) {
// 添加文件与功能用例的关联关系
Map<String, String> addFileMap = new HashMap<>();
List<FunctionalCaseAttachment> functionalCaseAttachments = uploadFileIds.stream().map(fileId -> {
FunctionalCaseAttachment functionalCaseAttachment = new FunctionalCaseAttachment();
String fileName = getTempFileNameByFileId(fileId);
functionalCaseAttachment.setId(IDGenerator.nextStr());
functionalCaseAttachment.setCaseId(caseId);
functionalCaseAttachment.setFileId(fileId);
functionalCaseAttachment.setFileName(fileName);
functionalCaseAttachment.setFileSource(fileSource);
long fileSize = 0;
try {
FileCopyRequest fileCopyRequest = new FileCopyRequest();
fileCopyRequest.setFolder(systemTempDir + "/" + fileId);
fileCopyRequest.setFileName(fileName);
fileSize = defaultRepository.getFileSize(fileCopyRequest);
} catch (Exception e) {
LogUtils.error("读取文件大小失败");
}
functionalCaseAttachment.setSize(fileSize);
functionalCaseAttachment.setLocal(true);
functionalCaseAttachment.setCreateUser(userId);
functionalCaseAttachment.setCreateTime(System.currentTimeMillis());
addFileMap.put(fileId, fileName);
return functionalCaseAttachment;
}).toList();
functionalCaseAttachmentMapper.batchInsert(functionalCaseAttachments);
// 上传文件到对象存储
uploadFileResource(functionalCaseDir, addFileMap, projectId, caseId);
}
}
/**
* 根据文件ID查询minio中对应目录下的文件名称
*/
public String getTempFileNameByFileId(String fileId) {
FileRepository defaultRepository = FileCenter.getDefaultRepository();
String systemTempDir = DefaultRepositoryDir.getSystemTempDir();
try {
FileRequest fileRequest = new FileRequest();
fileRequest.setFolder(systemTempDir + "/" + fileId);
List<String> folderFileNames = defaultRepository.getFolderFileNames(fileRequest);
if (CollectionUtils.isEmpty(folderFileNames)) {
return null;
}
String[] pathSplit = folderFileNames.get(0).split("/");
return pathSplit[pathSplit.length - 1];
} catch (Exception e) {
LogUtils.error(e);
return null;
}
}
/**
* 上传用例管理相关的资源文件
*
* @param folder 用例管理文件路径
* @param addFileMap key:fileId value:fileName
*/
public void uploadFileResource(String folder, Map<String, String> addFileMap, String projectId, String caseId) {
if (MapUtils.isEmpty(addFileMap)) {
return;
}
FileRepository defaultRepository = FileCenter.getDefaultRepository();
for (String fileId : addFileMap.keySet()) {
String systemTempDir = DefaultRepositoryDir.getSystemTempDir();
try {
String fileName = addFileMap.get(fileId);
if (StringUtils.isEmpty(fileName)) {
continue;
}
// 按ID建文件夹避免文件名重复
FileCopyRequest fileCopyRequest = new FileCopyRequest();
fileCopyRequest.setCopyFolder(systemTempDir + "/" + fileId);
fileCopyRequest.setCopyfileName(fileName);
fileCopyRequest.setFileName(fileName);
fileCopyRequest.setFolder(folder + "/" + fileId);
// 将文件从临时目录复制到资源目录
defaultRepository.copyFile(fileCopyRequest);
String fileType = StringUtils.substring(fileName, fileName.lastIndexOf(".") + 1);
if (TempFileUtils.isImage(fileType)) {
//图片文件自动生成预览图
byte[] file = defaultRepository.getFile(fileCopyRequest);
byte[] previewImg = TempFileUtils.compressPic(file);
fileCopyRequest.setFolder(DefaultRepositoryDir.getFunctionalCasePreviewDir(projectId,caseId));
fileCopyRequest.setStorage(StorageType.MINIO.toString());
fileService.upload(previewImg, fileCopyRequest);
}
// 删除临时文件
fileCopyRequest.setFolder(systemTempDir + "/" + fileId);
fileCopyRequest.setFileName(fileName);
defaultRepository.delete(fileCopyRequest);
} catch (Exception e) {
LogUtils.error(e);
throw new MSException(Translator.get("file_upload_fail"));
}
}
}
}

View File

@ -1,6 +1,7 @@
package io.metersphere.functional.service;
import io.metersphere.functional.constants.CaseFileSourceType;
import io.metersphere.functional.domain.FunctionalCase;
import io.metersphere.functional.domain.FunctionalCaseComment;
import io.metersphere.functional.domain.FunctionalCaseCommentExample;
@ -56,6 +57,11 @@ public class FunctionalCaseCommentService {
@Resource
private BaseUserMapper baseUserMapper;
@Resource
private FunctionalCaseAttachmentService functionalCaseAttachmentService;
/**
* 新增评论
*
@ -112,6 +118,8 @@ public class FunctionalCaseCommentService {
}
functionalCaseCommentMapper.insert(functionalCaseComment);
FunctionalCaseDTO functionalCaseDTO = functionalCaseNoticeService.getFunctionalCaseDTO(functionalCaseCommentRequest);
//保存文件
functionalCaseAttachmentService.uploadMinioFile(functionalCaseCommentRequest.getCaseId(),functionalCaseCommentRequest.getProjectId(),functionalCaseCommentRequest.getUploadFileIds(), userId, CaseFileSourceType.CASE_COMMENT.toString());
//发送@ 通知人
sendNotice(functionalCaseCommentRequest, userId, functionalCaseDTO);
//发送系统设置的评论通知
@ -135,6 +143,8 @@ public class FunctionalCaseCommentService {
sendNotice(functionalCaseCommentRequest, userId, functionalCaseDTOReply);
functionalCaseCommentRequest.setEvent(NoticeConstants.Event.AT);
FunctionalCaseDTO functionalCaseDTO = functionalCaseNoticeService.getFunctionalCaseDTO(functionalCaseCommentRequest);
//保存文件
functionalCaseAttachmentService.uploadMinioFile(functionalCaseCommentRequest.getCaseId(),functionalCaseCommentRequest.getProjectId(),functionalCaseCommentRequest.getUploadFileIds(), userId, CaseFileSourceType.CASE_COMMENT.toString());
//发通知
sendNotice(functionalCaseCommentRequest, userId, functionalCaseDTO);
return functionalCaseComment;

View File

@ -1,6 +1,9 @@
package io.metersphere.functional.controller;
import io.metersphere.functional.dto.FunctionalCaseAttachmentDTO;
import io.metersphere.functional.constants.CaseFileSourceType;
import io.metersphere.functional.domain.FunctionalCaseAttachment;
import io.metersphere.functional.domain.FunctionalCaseAttachmentExample;
import io.metersphere.functional.mapper.FunctionalCaseAttachmentMapper;
import io.metersphere.functional.request.AttachmentTransferRequest;
import io.metersphere.functional.request.FunctionalCaseAssociationFileRequest;
import io.metersphere.functional.request.FunctionalCaseDeleteFileRequest;
@ -19,6 +22,7 @@ import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
@ -28,11 +32,10 @@ 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.LinkedMultiValueMap;
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -52,6 +55,9 @@ public class FunctionalCaseAttachmentControllerTests extends BaseTest {
@Resource
private FunctionalCaseAttachmentService functionalCaseAttachmentService;
@Resource
private FunctionalCaseAttachmentMapper functionalCaseAttachmentMapper;
public static final String ATTACHMENT_PAGE_URL = "/attachment/page";
public static final String ATTACHMENT_PREVIEW_URL = "/attachment/preview";
public static final String ATTACHMENT_DOWNLOAD_URL = "/attachment/download";
@ -61,6 +67,7 @@ public class FunctionalCaseAttachmentControllerTests extends BaseTest {
public static final String UPLOAD_FILE_URL = "/attachment/upload/file";
public static final String DELETE_FILE_URL = "/attachment/delete/file";
public static final String OPTIONS_URL = "/attachment/options/";
public static final String UPLOAD_TEMP = "/attachment/upload/temp/file";
@Test
@ -210,7 +217,6 @@ public class FunctionalCaseAttachmentControllerTests extends BaseTest {
@Order(8)
public void testDeleteFile() throws Exception {
FunctionalCaseDeleteFileRequest request = new FunctionalCaseDeleteFileRequest();
FunctionalCaseAttachmentDTO attachmentDTO = new FunctionalCaseAttachmentDTO();
//覆盖率
request.setCaseId("TEST_FUNCTIONAL_CASE_ATTACHMENT_ID_1");
request.setProjectId("WX_TEST_PROJECT_ID");
@ -218,8 +224,8 @@ public class FunctionalCaseAttachmentControllerTests extends BaseTest {
request.setLocal(false);
this.requestPost(DELETE_FILE_URL, request);
attachmentDTO.setId("TEST_ATTACHMENT_FILE_ID");
attachmentDTO.setLocal(true);
request.setId("TEST_ATTACHMENT_FILE_ID");
request.setLocal(true);
this.requestPost(DELETE_FILE_URL, request);
}
@ -231,4 +237,59 @@ public class FunctionalCaseAttachmentControllerTests extends BaseTest {
this.requestGet(OPTIONS_URL + DEFAULT_PROJECT_ID);
}
@Test
@Order(10)
public void testUploadTemp() throws Exception {
//覆盖controller方法
MockMultipartFile file = getMockMultipartFile();
String fileId = doUploadTempFile(file);
Assertions.assertTrue(StringUtils.isNotBlank(fileId));
file = getNoNameMockMultipartFile();
doUploadTempFileFalse(file);
functionalCaseAttachmentService.uploadMinioFile("TEST_FUNCTIONAL_CASE_ATTACHMENT_ID_1","WX_TEST_PROJECT_ID", List.of(fileId),"admin", CaseFileSourceType.CASE_COMMENT.toString());
FunctionalCaseAttachmentExample functionalCaseAttachmentExample = new FunctionalCaseAttachmentExample();
functionalCaseAttachmentExample.createCriteria().andCaseIdEqualTo("TEST_FUNCTIONAL_CASE_ATTACHMENT_ID_1").andFileIdEqualTo(fileId).andFileSourceEqualTo(CaseFileSourceType.CASE_COMMENT.toString());
List<FunctionalCaseAttachment> functionalCaseAttachments = functionalCaseAttachmentMapper.selectByExample(functionalCaseAttachmentExample);
Assertions.assertTrue(CollectionUtils.isNotEmpty(functionalCaseAttachments));
functionalCaseAttachmentService.uploadMinioFile("TEST_FUNCTIONAL_CASE_ATTACHMENT_ID_1","WX_TEST_PROJECT_ID",new ArrayList<>(),"admin", CaseFileSourceType.CASE_COMMENT.toString());
String functionalCaseDir = DefaultRepositoryDir.getFunctionalCaseDir("WX_TEST_PROJECT_ID", "TEST_FUNCTIONAL_CASE_ATTACHMENT_ID_1");
functionalCaseAttachmentService.uploadFileResource(functionalCaseDir,new HashMap<>(),"WX_TEST_PROJECT_ID", "TEST_FUNCTIONAL_CASE_ATTACHMENT_ID_1");
Map<String, String> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put(fileId,null);
functionalCaseAttachmentService.uploadFileResource(functionalCaseDir,objectObjectHashMap,"WX_TEST_PROJECT_ID", "TEST_FUNCTIONAL_CASE_ATTACHMENT_ID_1");
}
private static MockMultipartFile getMockMultipartFile() {
MockMultipartFile file = new MockMultipartFile(
"file",
"file_upload.JPG",
MediaType.APPLICATION_OCTET_STREAM_VALUE,
"Hello, World!".getBytes()
);
return file;
}
private static MockMultipartFile getNoNameMockMultipartFile() {
MockMultipartFile file = new MockMultipartFile(
"file",
null,
MediaType.APPLICATION_OCTET_STREAM_VALUE,
"Hello, World!".getBytes()
);
return file;
}
private String doUploadTempFile(MockMultipartFile file) throws Exception {
return JSON.parseObject(requestUploadFileWithOkAndReturn(UPLOAD_TEMP, file)
.getResponse()
.getContentAsString(), ResultHolder.class)
.getData().toString();
}
private void doUploadTempFileFalse(MockMultipartFile file) throws Exception {
this.requestUploadFile(UPLOAD_TEMP, file).andExpect(status().is5xxServerError());
}
}