feat(系统设置): 文件管理删除时,不再删除文件关联表,并加以标识

This commit is contained in:
song-tianyang 2024-02-01 15:06:41 +08:00 committed by 建国
parent b89e6ec53f
commit 3bd1cc45f8
13 changed files with 255 additions and 63 deletions

View File

@ -52,6 +52,13 @@ public class FileAssociation implements Serializable {
@Schema(description = "创建人")
private String createUser;
@Schema(description = "是否删除", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{file_association.deleted.not_blank}", groups = {Created.class})
private Boolean deleted;
@Schema(description = "删除时的文件名称")
private String deletedFileName;
private static final long serialVersionUID = 1L;
public enum Column {
@ -64,7 +71,9 @@ public class FileAssociation implements Serializable {
createTime("create_time", "createTime", "BIGINT", false),
updateUser("update_user", "updateUser", "VARCHAR", false),
updateTime("update_time", "updateTime", "BIGINT", false),
createUser("create_user", "createUser", "VARCHAR", false);
createUser("create_user", "createUser", "VARCHAR", false),
deleted("deleted", "deleted", "BIT", false),
deletedFileName("deleted_file_name", "deletedFileName", "VARCHAR", false);
private static final String BEGINNING_DELIMITER = "`";

View File

@ -783,6 +783,136 @@ public class FileAssociationExample {
addCriterion("create_user not between", value1, value2, "createUser");
return (Criteria) this;
}
public Criteria andDeletedIsNull() {
addCriterion("deleted is null");
return (Criteria) this;
}
public Criteria andDeletedIsNotNull() {
addCriterion("deleted is not null");
return (Criteria) this;
}
public Criteria andDeletedEqualTo(Boolean value) {
addCriterion("deleted =", value, "deleted");
return (Criteria) this;
}
public Criteria andDeletedNotEqualTo(Boolean value) {
addCriterion("deleted <>", value, "deleted");
return (Criteria) this;
}
public Criteria andDeletedGreaterThan(Boolean value) {
addCriterion("deleted >", value, "deleted");
return (Criteria) this;
}
public Criteria andDeletedGreaterThanOrEqualTo(Boolean value) {
addCriterion("deleted >=", value, "deleted");
return (Criteria) this;
}
public Criteria andDeletedLessThan(Boolean value) {
addCriterion("deleted <", value, "deleted");
return (Criteria) this;
}
public Criteria andDeletedLessThanOrEqualTo(Boolean value) {
addCriterion("deleted <=", value, "deleted");
return (Criteria) this;
}
public Criteria andDeletedIn(List<Boolean> values) {
addCriterion("deleted in", values, "deleted");
return (Criteria) this;
}
public Criteria andDeletedNotIn(List<Boolean> values) {
addCriterion("deleted not in", values, "deleted");
return (Criteria) this;
}
public Criteria andDeletedBetween(Boolean value1, Boolean value2) {
addCriterion("deleted between", value1, value2, "deleted");
return (Criteria) this;
}
public Criteria andDeletedNotBetween(Boolean value1, Boolean value2) {
addCriterion("deleted not between", value1, value2, "deleted");
return (Criteria) this;
}
public Criteria andDeletedFileNameIsNull() {
addCriterion("deleted_file_name is null");
return (Criteria) this;
}
public Criteria andDeletedFileNameIsNotNull() {
addCriterion("deleted_file_name is not null");
return (Criteria) this;
}
public Criteria andDeletedFileNameEqualTo(String value) {
addCriterion("deleted_file_name =", value, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameNotEqualTo(String value) {
addCriterion("deleted_file_name <>", value, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameGreaterThan(String value) {
addCriterion("deleted_file_name >", value, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameGreaterThanOrEqualTo(String value) {
addCriterion("deleted_file_name >=", value, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameLessThan(String value) {
addCriterion("deleted_file_name <", value, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameLessThanOrEqualTo(String value) {
addCriterion("deleted_file_name <=", value, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameLike(String value) {
addCriterion("deleted_file_name like", value, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameNotLike(String value) {
addCriterion("deleted_file_name not like", value, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameIn(List<String> values) {
addCriterion("deleted_file_name in", values, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameNotIn(List<String> values) {
addCriterion("deleted_file_name not in", values, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameBetween(String value1, String value2) {
addCriterion("deleted_file_name between", value1, value2, "deletedFileName");
return (Criteria) this;
}
public Criteria andDeletedFileNameNotBetween(String value1, String value2) {
addCriterion("deleted_file_name not between", value1, value2, "deletedFileName");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -12,6 +12,8 @@
<result column="update_user" jdbcType="VARCHAR" property="updateUser" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="create_user" jdbcType="VARCHAR" property="createUser" />
<result column="deleted" jdbcType="BIT" property="deleted" />
<result column="deleted_file_name" jdbcType="VARCHAR" property="deletedFileName" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -73,7 +75,7 @@
</sql>
<sql id="Base_Column_List">
id, source_type, source_id, file_id, file_ref_id, file_version, create_time, update_user,
update_time, create_user
update_time, create_user, deleted, deleted_file_name
</sql>
<select id="selectByExample" parameterType="io.metersphere.project.domain.FileAssociationExample" resultMap="BaseResultMap">
select
@ -109,11 +111,13 @@
insert into file_association (id, source_type, source_id,
file_id, file_ref_id, file_version,
create_time, update_user, update_time,
create_user)
create_user, deleted, deleted_file_name
)
values (#{id,jdbcType=VARCHAR}, #{sourceType,jdbcType=VARCHAR}, #{sourceId,jdbcType=VARCHAR},
#{fileId,jdbcType=VARCHAR}, #{fileRefId,jdbcType=VARCHAR}, #{fileVersion,jdbcType=VARCHAR},
#{createTime,jdbcType=BIGINT}, #{updateUser,jdbcType=VARCHAR}, #{updateTime,jdbcType=BIGINT},
#{createUser,jdbcType=VARCHAR})
#{createUser,jdbcType=VARCHAR}, #{deleted,jdbcType=BIT}, #{deletedFileName,jdbcType=VARCHAR}
)
</insert>
<insert id="insertSelective" parameterType="io.metersphere.project.domain.FileAssociation">
insert into file_association
@ -148,6 +152,12 @@
<if test="createUser != null">
create_user,
</if>
<if test="deleted != null">
deleted,
</if>
<if test="deletedFileName != null">
deleted_file_name,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -180,6 +190,12 @@
<if test="createUser != null">
#{createUser,jdbcType=VARCHAR},
</if>
<if test="deleted != null">
#{deleted,jdbcType=BIT},
</if>
<if test="deletedFileName != null">
#{deletedFileName,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.project.domain.FileAssociationExample" resultType="java.lang.Long">
@ -221,6 +237,12 @@
<if test="record.createUser != null">
create_user = #{record.createUser,jdbcType=VARCHAR},
</if>
<if test="record.deleted != null">
deleted = #{record.deleted,jdbcType=BIT},
</if>
<if test="record.deletedFileName != null">
deleted_file_name = #{record.deletedFileName,jdbcType=VARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -237,7 +259,9 @@
create_time = #{record.createTime,jdbcType=BIGINT},
update_user = #{record.updateUser,jdbcType=VARCHAR},
update_time = #{record.updateTime,jdbcType=BIGINT},
create_user = #{record.createUser,jdbcType=VARCHAR}
create_user = #{record.createUser,jdbcType=VARCHAR},
deleted = #{record.deleted,jdbcType=BIT},
deleted_file_name = #{record.deletedFileName,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -272,6 +296,12 @@
<if test="createUser != null">
create_user = #{createUser,jdbcType=VARCHAR},
</if>
<if test="deleted != null">
deleted = #{deleted,jdbcType=BIT},
</if>
<if test="deletedFileName != null">
deleted_file_name = #{deletedFileName,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -285,19 +315,22 @@
create_time = #{createTime,jdbcType=BIGINT},
update_user = #{updateUser,jdbcType=VARCHAR},
update_time = #{updateTime,jdbcType=BIGINT},
create_user = #{createUser,jdbcType=VARCHAR}
create_user = #{createUser,jdbcType=VARCHAR},
deleted = #{deleted,jdbcType=BIT},
deleted_file_name = #{deletedFileName,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
<insert id="batchInsert" parameterType="map">
insert into file_association
(id, source_type, source_id, file_id, file_ref_id, file_version, create_time, update_user,
update_time, create_user)
update_time, create_user, deleted, deleted_file_name)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.sourceType,jdbcType=VARCHAR}, #{item.sourceId,jdbcType=VARCHAR},
#{item.fileId,jdbcType=VARCHAR}, #{item.fileRefId,jdbcType=VARCHAR}, #{item.fileVersion,jdbcType=VARCHAR},
#{item.createTime,jdbcType=BIGINT}, #{item.updateUser,jdbcType=VARCHAR}, #{item.updateTime,jdbcType=BIGINT},
#{item.createUser,jdbcType=VARCHAR})
#{item.createUser,jdbcType=VARCHAR}, #{item.deleted,jdbcType=BIT}, #{item.deletedFileName,jdbcType=VARCHAR}
)
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -340,6 +373,12 @@
<if test="'create_user'.toString() == column.value">
#{item.createUser,jdbcType=VARCHAR}
</if>
<if test="'deleted'.toString() == column.value">
#{item.deleted,jdbcType=BIT}
</if>
<if test="'deleted_file_name'.toString() == column.value">
#{item.deletedFileName,jdbcType=VARCHAR}
</if>
</foreach>
)
</foreach>

View File

@ -52,16 +52,18 @@ CREATE INDEX idx_name ON fake_error (name);
CREATE TABLE IF NOT EXISTS file_association
(
`id` VARCHAR(50) NOT NULL COMMENT '',
`source_type` VARCHAR(50) NOT NULL COMMENT '资源类型',
`source_id` VARCHAR(50) NOT NULL COMMENT '资源ID',
`file_id` VARCHAR(50) NOT NULL COMMENT '文件ID',
`file_ref_id` VARCHAR(50) NOT NULL COMMENT '文件同版本ID',
`file_version` VARCHAR(50) NOT NULL COMMENT '文件版本',
`create_time` BIGINT NOT NULL COMMENT '创建时间',
`update_user` VARCHAR(50) NOT NULL COMMENT '修改人',
`update_time` BIGINT NOT NULL COMMENT '更新时间',
`create_user` VARCHAR(50) COMMENT '创建人',
`id` VARCHAR(50) NOT NULL COMMENT '' ,
`source_type` VARCHAR(50) NOT NULL COMMENT '资源类型' ,
`source_id` VARCHAR(50) NOT NULL COMMENT '资源ID' ,
`file_id` VARCHAR(50) NOT NULL COMMENT '文件ID' ,
`file_ref_id` VARCHAR(50) NOT NULL COMMENT '文件同版本ID' ,
`file_version` VARCHAR(50) NOT NULL COMMENT '文件版本' ,
`create_time` BIGINT NOT NULL COMMENT '创建时间' ,
`update_user` VARCHAR(50) NOT NULL COMMENT '修改人' ,
`update_time` BIGINT NOT NULL COMMENT '更新时间' ,
`create_user` VARCHAR(50) COMMENT '创建人' ,
`deleted` BIT NOT NULL DEFAULT 0 COMMENT '是否删除' ,
`deleted_file_name` VARCHAR(255) COMMENT '删除时的文件名称' ,
PRIMARY KEY (id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
@ -70,6 +72,7 @@ CREATE TABLE IF NOT EXISTS file_association
CREATE INDEX idx_file_metadata_id ON file_association (file_id);
CREATE INDEX idx_source_type ON file_association (source_type);
CREATE INDEX idx_source_id ON file_association (source_id);

View File

@ -122,9 +122,7 @@
<select id="selectDeleteFileInfoByIds" resultType="io.metersphere.project.domain.FileMetadata">
SELECT
f.id,
f.project_id,
f.storage
f.id,f.name,f.type,f.project_id,f.storage
FROM file_metadata f WHERE f.id IN
<foreach collection="ids" item="item" open="(" separator="," close=")">
#{item}

View File

@ -174,6 +174,7 @@ public class FileAssociationService {
fileAssociation.setUpdateTime(operatorTime);
fileAssociation.setUpdateUser(logRecord.getOperator());
fileAssociation.setFileVersion(item.getFileVersion());
fileAssociation.setDeleted(false);
createFile.add(fileAssociation);
});
fileAssociationMapper.batchInsert(createFile);

View File

@ -4,6 +4,7 @@ import io.metersphere.project.domain.*;
import io.metersphere.project.dto.filemanagement.FileManagementQuery;
import io.metersphere.project.dto.filemanagement.request.FileBatchProcessRequest;
import io.metersphere.project.mapper.*;
import io.metersphere.project.utils.FileMetadataUtils;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.constants.StorageType;
@ -66,9 +67,15 @@ public class FileManagementService {
repositoryExample.createCriteria().andFileMetadataIdIn(deleteIds);
fileMetadataRepositoryMapper.deleteByExample(repositoryExample);
// 2.1 需求 删除文件时对应的关系不应删除要打上删除标记并赋值删除时的文件名称这样在关联列表中就可以看到删除的文件
for (FileMetadata fileMetadata : deleteList) {
FileAssociationExample associationExample = new FileAssociationExample();
associationExample.createCriteria().andFileIdIn(deleteIds);
fileAssociationMapper.deleteByExample(associationExample);
associationExample.createCriteria().andFileIdEqualTo(fileMetadata.getId());
FileAssociation updateAssociation = new FileAssociation();
updateAssociation.setDeleted(true);
updateAssociation.setDeletedFileName(FileMetadataUtils.getFileName(fileMetadata));
fileAssociationMapper.updateByExampleSelective(updateAssociation, associationExample);
}
//记录日志
fileMetadataLogService.saveDeleteLog(deleteList, request.getProjectId(), operator);

View File

@ -328,17 +328,10 @@ public class FileMetadataService {
byte[] bytes = this.getFileByte(fileMetadata);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + this.getFileName(fileMetadata.getName(), fileMetadata.getType()) + "\"")
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + FileMetadataUtils.getFileName(fileMetadata) + "\"")
.body(bytes);
}
private String getFileName(String fileName, String type) {
if (StringUtils.isBlank(type)) {
return fileName;
}
return fileName + "." + type;
}
public void update(FileUpdateRequest request, String operator) {
//检查模块的合法性
FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(request.getId());
@ -391,7 +384,7 @@ public class FileMetadataService {
public void batchDownloadWithResponse(List<FileMetadata> fileMetadataList, HttpServletResponse response) {
Map<String, File> fileMap = new HashMap<>();
fileMetadataList.forEach(fileMetadata -> fileMap.put(this.getFileName(fileMetadata.getName(), fileMetadata.getType()), this.getTmpFile(fileMetadata)));
fileMetadataList.forEach(fileMetadata -> fileMap.put(FileMetadataUtils.getFileName(fileMetadata), this.getTmpFile(fileMetadata)));
FileDownloadUtils.zipFilesWithResponse(fileMap, response);
}
@ -467,7 +460,7 @@ public class FileMetadataService {
return moduleCountMap;
}
public ResponseEntity<byte[]> downloadPreviewImgById(String id) throws Exception {
public ResponseEntity<byte[]> downloadPreviewImgById(String id) {
FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(id);
byte[] bytes = new byte[]{};
@ -488,7 +481,7 @@ public class FileMetadataService {
return ResponseEntity.ok()
.contentType(contentType)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + this.getFileName(fileMetadata.getId(), fileMetadata.getType()) + "\"")
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + FileMetadataUtils.getFileName(fileMetadata) + "\"")
.body(bytes);
}

View File

@ -1,5 +1,6 @@
package io.metersphere.project.utils;
import io.metersphere.project.domain.FileMetadata;
import io.metersphere.project.dto.filemanagement.request.FileMetadataTableRequest;
import org.apache.commons.lang3.StringUtils;
@ -26,6 +27,13 @@ public class FileMetadataUtils {
}
}
public static String getFileName(FileMetadata fileMetadata) {
if (StringUtils.isBlank(fileMetadata.getType())) {
return fileMetadata.getName();
}
return fileMetadata.getName() + "." + fileMetadata.getType();
}
/**
* 将空文件类型转换为unknown
*

View File

@ -72,10 +72,12 @@
<!--要生成的数据库表 -->
<!-- <table tableName="custom_function"/>-->
<!-- <table tableName="fake_error"/>-->
<table tableName="file_metadata">
<columnOverride column="tags" javaType="java.util.List&lt;String&gt;" jdbcType="VARCHAR"
typeHandler="io.metersphere.handler.ListTypeHandler"/>
</table>
<!-- <table tableName="file_metadata">-->
<!-- <columnOverride column="tags" javaType="java.util.List&lt;String&gt;" jdbcType="VARCHAR"-->
<!-- typeHandler="io.metersphere.handler.ListTypeHandler"/>-->
<!-- </table>-->
<table tableName="file_association"/>
<!-- <table tableName="file_metadata_repository"/>-->
<!-- <table tableName="file_module_repository"/>-->
<!-- <table tableName="project"/>-->

View File

@ -1388,6 +1388,22 @@ public class FileManagementControllerTests extends BaseTest {
if (MapUtils.isEmpty(FILE_VERSIONS_ID_MAP)) {
this.fileReUploadTestSuccess();
}
//测试中涉及到全部删除删除文件时FileAssociation不会删除在删除前先把相关数据全部查询出来用于全部删除之后的数据检查对比
FileAssociationExample fileAssociationExample = new FileAssociationExample();
fileAssociationExample.createCriteria().andFileIdIn(new ArrayList<>() {{
this.add(fileAssociationNewFilesOne);
this.add(fileAssociationNewFilesTwo);
this.add(fileAssociationNewFilesThree);
this.add(fileAssociationNewFilesFour);
this.add(fileAssociationNewFileId);
this.add(fileAssociationOldFileId);
}});
List<FileAssociation> beforeDeletedList = fileAssociationMapper.selectByExample(fileAssociationExample);
beforeDeletedList.forEach(item -> {
Assertions.assertFalse(item.getDeleted());
Assertions.assertNull(item.getDeletedFileName());
});
FileBatchProcessRequest fileBatchProcessRequest;
//删除指定文件
for (Map.Entry<String, String> entry : FILE_VERSIONS_ID_MAP.entrySet()) {
@ -1420,17 +1436,13 @@ public class FileManagementControllerTests extends BaseTest {
example.createCriteria().andProjectIdEqualTo(project.getId());
Assertions.assertEquals(fileMetadataMapper.countByExample(example), 0);
//检查fileAssociation表中的数据是否被删除
FileAssociationExample fileAssociationExample = new FileAssociationExample();
fileAssociationExample.createCriteria().andFileIdIn(new ArrayList<>() {{
this.add(fileAssociationNewFilesOne);
this.add(fileAssociationNewFilesTwo);
this.add(fileAssociationNewFilesThree);
this.add(fileAssociationNewFilesFour);
this.add(fileAssociationNewFileId);
this.add(fileAssociationOldFileId);
}});
Assertions.assertEquals(fileAssociationMapper.countByExample(fileAssociationExample), 0);
//检查fileAssociation
List<FileAssociation> aftreDeletedList = fileAssociationMapper.selectByExample(fileAssociationExample);
Assertions.assertEquals(beforeDeletedList.size(), aftreDeletedList.size());
aftreDeletedList.forEach(item -> {
Assertions.assertTrue(item.getDeleted());
Assertions.assertNotNull(item.getDeletedFileName());
});
//重新上传用于后续的测试
this.fileUploadTestSuccess();
@ -1894,7 +1906,7 @@ public class FileManagementControllerTests extends BaseTest {
fileAssociation.setUpdateTime(System.currentTimeMillis());
fileAssociation.setUpdateUser("admin");
fileAssociation.setFileVersion(fileId);
fileAssociationMapper.insert(fileAssociation);
fileAssociationMapper.insertSelective(fileAssociation);
return fileAssociation.getId();
}

View File

@ -1,15 +1,12 @@
package io.metersphere.system.dto.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator;
import jakarta.validation.groups.Default;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Set;
@Component
@ -25,14 +22,7 @@ public class ExcelValidateHelper {
Set<ConstraintViolation<T>> set = excelValidateHelper.validator.validate(obj, Default.class);
if (set != null && !set.isEmpty()) {
for (ConstraintViolation<T> cv : set) {
Field declaredField = obj.getClass().getDeclaredField(cv.getPropertyPath().toString());
ExcelProperty annotation = declaredField.getAnnotation(ExcelProperty.class);
//拼接错误信息包含当前出错数据的标题名字+错误信息 如果列中有必填标识*去掉*
String columnName = annotation.value()[0];
if (StringUtils.endsWith(columnName, "*")) {
columnName = StringUtils.removeEnd(columnName, "*");
}
result.append(columnName + ":" + cv.getMessage()).append("; ");
result.append(cv.getMessage()).append("; ");
}
}
return result.toString();