feat(项目管理): 文件关联功能开发

This commit is contained in:
song-tianyang 2023-11-13 17:17:43 +08:00 committed by 建国
parent 5c47422999
commit 9e5285faa1
30 changed files with 1445 additions and 207 deletions

View File

@ -15,11 +15,6 @@ public class FileAssociation implements Serializable {
@Size(min = 1, max = 50, message = "{file_association.id.length_range}", groups = {Created.class, Updated.class})
private String id;
@Schema(description = "多用于场景步骤内具体的步骤ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{file_association.source_item_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{file_association.source_item_id.length_range}", groups = {Created.class, Updated.class})
private String sourceItemId;
@Schema(description = "资源类型", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{file_association.source_type.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{file_association.source_type.length_range}", groups = {Created.class, Updated.class})
@ -61,7 +56,6 @@ public class FileAssociation implements Serializable {
public enum Column {
id("id", "id", "VARCHAR", false),
sourceItemId("source_item_id", "sourceItemId", "VARCHAR", false),
sourceType("source_type", "sourceType", "VARCHAR", false),
sourceId("source_id", "sourceId", "VARCHAR", false),
fileId("file_id", "fileId", "VARCHAR", false),

View File

@ -174,76 +174,6 @@ public class FileAssociationExample {
return (Criteria) this;
}
public Criteria andSourceItemIdIsNull() {
addCriterion("source_item_id is null");
return (Criteria) this;
}
public Criteria andSourceItemIdIsNotNull() {
addCriterion("source_item_id is not null");
return (Criteria) this;
}
public Criteria andSourceItemIdEqualTo(String value) {
addCriterion("source_item_id =", value, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdNotEqualTo(String value) {
addCriterion("source_item_id <>", value, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdGreaterThan(String value) {
addCriterion("source_item_id >", value, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdGreaterThanOrEqualTo(String value) {
addCriterion("source_item_id >=", value, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdLessThan(String value) {
addCriterion("source_item_id <", value, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdLessThanOrEqualTo(String value) {
addCriterion("source_item_id <=", value, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdLike(String value) {
addCriterion("source_item_id like", value, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdNotLike(String value) {
addCriterion("source_item_id not like", value, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdIn(List<String> values) {
addCriterion("source_item_id in", values, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdNotIn(List<String> values) {
addCriterion("source_item_id not in", values, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdBetween(String value1, String value2) {
addCriterion("source_item_id between", value1, value2, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceItemIdNotBetween(String value1, String value2) {
addCriterion("source_item_id not between", value1, value2, "sourceItemId");
return (Criteria) this;
}
public Criteria andSourceTypeIsNull() {
addCriterion("source_type is null");
return (Criteria) this;

View File

@ -3,7 +3,6 @@
<mapper namespace="io.metersphere.project.mapper.FileAssociationMapper">
<resultMap id="BaseResultMap" type="io.metersphere.project.domain.FileAssociation">
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="source_item_id" jdbcType="VARCHAR" property="sourceItemId" />
<result column="source_type" jdbcType="VARCHAR" property="sourceType" />
<result column="source_id" jdbcType="VARCHAR" property="sourceId" />
<result column="file_id" jdbcType="VARCHAR" property="fileId" />
@ -73,8 +72,8 @@
</where>
</sql>
<sql id="Base_Column_List">
id, source_item_id, source_type, source_id, file_id, file_ref_id, file_version, create_time,
update_user, update_time, create_user
id, source_type, source_id, file_id, file_ref_id, file_version, create_time, update_user,
update_time, create_user
</sql>
<select id="selectByExample" parameterType="io.metersphere.project.domain.FileAssociationExample" resultMap="BaseResultMap">
select
@ -107,14 +106,14 @@
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.project.domain.FileAssociation">
insert into file_association (id, source_item_id, source_type,
source_id, file_id, file_ref_id,
file_version, create_time, update_user,
update_time, create_user)
values (#{id,jdbcType=VARCHAR}, #{sourceItemId,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})
insert into file_association (id, source_type, source_id,
file_id, file_ref_id, file_version,
create_time, update_user, update_time,
create_user)
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})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.project.domain.FileAssociation">
insert into file_association
@ -122,9 +121,6 @@
<if test="id != null">
id,
</if>
<if test="sourceItemId != null">
source_item_id,
</if>
<if test="sourceType != null">
source_type,
</if>
@ -157,9 +153,6 @@
<if test="id != null">
#{id,jdbcType=VARCHAR},
</if>
<if test="sourceItemId != null">
#{sourceItemId,jdbcType=VARCHAR},
</if>
<if test="sourceType != null">
#{sourceType,jdbcType=VARCHAR},
</if>
@ -201,9 +194,6 @@
<if test="record.id != null">
id = #{record.id,jdbcType=VARCHAR},
</if>
<if test="record.sourceItemId != null">
source_item_id = #{record.sourceItemId,jdbcType=VARCHAR},
</if>
<if test="record.sourceType != null">
source_type = #{record.sourceType,jdbcType=VARCHAR},
</if>
@ -239,7 +229,6 @@
<update id="updateByExample" parameterType="map">
update file_association
set id = #{record.id,jdbcType=VARCHAR},
source_item_id = #{record.sourceItemId,jdbcType=VARCHAR},
source_type = #{record.sourceType,jdbcType=VARCHAR},
source_id = #{record.sourceId,jdbcType=VARCHAR},
file_id = #{record.fileId,jdbcType=VARCHAR},
@ -256,9 +245,6 @@
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.project.domain.FileAssociation">
update file_association
<set>
<if test="sourceItemId != null">
source_item_id = #{sourceItemId,jdbcType=VARCHAR},
</if>
<if test="sourceType != null">
source_type = #{sourceType,jdbcType=VARCHAR},
</if>
@ -291,8 +277,7 @@
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.project.domain.FileAssociation">
update file_association
set source_item_id = #{sourceItemId,jdbcType=VARCHAR},
source_type = #{sourceType,jdbcType=VARCHAR},
set source_type = #{sourceType,jdbcType=VARCHAR},
source_id = #{sourceId,jdbcType=VARCHAR},
file_id = #{fileId,jdbcType=VARCHAR},
file_ref_id = #{fileRefId,jdbcType=VARCHAR},
@ -305,14 +290,14 @@
</update>
<insert id="batchInsert" parameterType="map">
insert into file_association
(id, source_item_id, source_type, source_id, file_id, file_ref_id, file_version,
create_time, update_user, update_time, create_user)
(id, source_type, source_id, file_id, file_ref_id, file_version, create_time, update_user,
update_time, create_user)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.sourceItemId,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.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})
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -328,9 +313,6 @@
<if test="'id'.toString() == column.value">
#{item.id,jdbcType=VARCHAR}
</if>
<if test="'source_item_id'.toString() == column.value">
#{item.sourceItemId,jdbcType=VARCHAR}
</if>
<if test="'source_type'.toString() == column.value">
#{item.sourceType,jdbcType=VARCHAR}
</if>

View File

@ -46,7 +46,6 @@ CREATE INDEX idx_name ON fake_error (name);
CREATE TABLE IF NOT EXISTS file_association
(
`id` VARCHAR(50) NOT NULL COMMENT '',
`source_item_id` VARCHAR(50) NOT NULL COMMENT '多用于场景步骤内具体的步骤ID',
`source_type` VARCHAR(50) NOT NULL COMMENT '资源类型',
`source_id` VARCHAR(50) NOT NULL COMMENT '资源ID',
`file_id` VARCHAR(50) NOT NULL COMMENT '文件ID',

View File

@ -0,0 +1,30 @@
package io.metersphere.sdk.util;
import io.metersphere.sdk.exception.MSException;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* 文件关联资源类型
* 类型如果要拓展QUERY_SQL 中要增加对应的数据库名称查询SQL 不需要拼接where条件
*/
public class FileAssociationSourceUtil {
public static final String SOURCE_TYPE_BUG = "BUG";
public static final Map<String, String> QUERY_SQL = new HashMap<>();
static {
QUERY_SQL.put(SOURCE_TYPE_BUG, "SELECT id AS sourceId,title AS sourceName FROM bug");
}
public static void validate(String type) {
if (!QUERY_SQL.containsKey(type)) {
throw new MSException(Translator.get("file.association.error.type"));
}
}
public static String getQuerySql(String type) {
validate(type);
return QUERY_SQL.get(type) + StringUtils.SPACE;
}
}

View File

@ -19,7 +19,7 @@ file_module.project_id.length_range=项目ID长度必须在{min}-{max}之间
file_module.project_id.not_blank=项目ID不能为空
file_module.name.length_range=名称长度必须在{min}-{max}之间
file_module.name.not_blank=名称不能为空
file_repository.connect.error=存储库接失败
file_repository.connect.error=存储库接失败
file_repository.not.exist=存储库不存在
file_repository.platform.error=存储库类型不正确
file_repository.id.not_blank=存储库ID不能为空
@ -30,6 +30,7 @@ file_repository.url.not_blank=存储库地址不能为空
file_repository.branch.not_blank=存储库分支不能为空
file_repository.file_path.not_blank=存储库文件路径不能为空
file.association.error.type=不支持的文件关联资源类型
file.association.not.exist=文件并未关联
file.association.source.not.exist=文件关联时资源不存在
custom_field_template.id.not_blank=ID不能为空
custom_field_template.field_id.length_range=字段ID长度必须在{min}-{max}之间
@ -436,6 +437,7 @@ upload.file.error=上传文件失败
file.not.exist=文件不存在
file.some.not.exist=部分文件不存在
old.file.not.exist=旧文件不存在
latest.file.not.exist=最新文件不存在
file.not.jar=不是jar文件
change.jar.enable=修改了jar文件的启用状态
file.name.exist=文件名已存在
@ -449,7 +451,12 @@ file.log.upload=上传
file.log.repository.add=添加了存储库文件
file.log.re-upload=重新上传
file.log.pull=拉取了文件
file.log.association=关联了文件
file.log.association.update=更新了关联了文件
file.log.association.delete=取消关联了文件
file.log.transfer.association=转存并关联了文件
file.name.cannot.be.empty=文件名称不能为空
file.name.error=文件名不合法
#file management over
# template

View File

@ -32,6 +32,7 @@ file_module.project_id.not_blank=Project ID is required
file_module.name.length_range=Name length must be between {min} and {max}
file_module.name.not_blank=Name is required
file.association.error.type=Source type is error
file.association.not.exist=File not association
file.association.source.not.exist=Source not exist
file_repository.connect.error=Repository connect error
file_repository.not.exist=Repository not exist
@ -472,6 +473,7 @@ upload.file.error=Upload file error
file.not.exist=File does not exist
file.some.not.exist=Some file not exist
old.file.not.exist=Old file does not exist
latest.file.not.exist=New version file note exist
file.not.jar=Not jar file
change.jar.enable=Change jar file enable
file.name.exist=File name already exists
@ -485,7 +487,12 @@ file.log.upload=upload
file.log.repository.add=Add repository file
file.log.re-upload=re-upload
file.log.pull=Pull file
file.log.association=has association file
file.log.association.update=updated file
file.log.association.delete=delete file
file.log.transfer.association=transfer and association file
file.name.cannot.be.empty=File name cannot be empty
file.name.error=File name error
#file management over
# template

View File

@ -32,8 +32,9 @@ file_module.project_id.not_blank=项目ID不能为空
file_module.name.length_range=名称长度必须在{min}-{max}之间
file_module.name.not_blank=名称不能为空
file.association.error.type=不支持的文件关联资源类型
file.association.not.exist=文件并未关联
file.association.source.not.exist=文件关联时资源不存在
file_repository.connect.error=存储库接失败
file_repository.connect.error=存储库接失败
file_repository.not.exist=存储库不存在
file_repository.platform.error=存储库类型不正确
file_repository.id.not_blank=存储库ID不能为空
@ -471,6 +472,7 @@ upload.file.error=上传文件失败
file.not.exist=文件不存在
file.some.not.exist=部分文件不存在
old.file.not.exist=旧文件不存在
latest.file.not.exist=最新文件不存在
file.not.jar=不是jar文件
change.jar.enable=修改了jar文件的启用状态
file.name.exist=文件名已存在
@ -484,7 +486,12 @@ file.log.upload=上传
file.log.repository.add=添加了存储库文件
file.log.re-upload=重新上传
file.log.pull=拉取了文件
file.log.association=关联了文件
file.log.association.update=更新了关联了文件
file.log.association.delete=取消关联了文件
file.log.transfer.association=转存并关联了文件
file.name.cannot.be.empty=文件名称不能为空
file.name.error=文件名不合法
#file management over
# template
project_template_permission_error=未开启项目模板

View File

@ -32,8 +32,9 @@ file_module.project_id.not_blank=項目ID不能為空
file_module.name.length_range=名稱長度必須在{min}-{max}之間
file_module.name.not_blank=名稱不能為空
file.association.error.type=不支持的文件關聯資源類型
file.association.not.exist=文件並未關聯
file.association.source.not.exist=文件關聯時資源不存在
file_repository.connect.error=存儲庫接失敗
file_repository.connect.error=存儲庫接失敗
file_repository.not.exist=存儲庫不存在
file_repository.platform.error=存儲庫類型不正確
file_repository.id.not_blank=存儲庫ID不能為空
@ -472,6 +473,7 @@ upload.file.error=上傳文件失敗
file.not.exist=文件不存在
file.some.not.exist=部分文件不存在
old.file.not.exist=舊文件不存在
latest.file.not.exist=最新文件不存在
file.not.jar=不是jar文件
change.jar.enable=修改了jar文件的啟用狀態
file.name.exist=文件名已存在
@ -485,7 +487,12 @@ file.log.upload=上傳
file.log.repository.add=添加了存儲庫文件
file.log.re-upload=重新上傳
file.log.pull=拉取了文件
file.log.association=關聯了文件
file.log.association.update=更新了關聯了文件
file.log.association.delete=取消關聯了文件
file.log.transfer.association=轉存並關聯了文件
file.name.cannot.be.empty=文件名稱不能為空
file.name.error=文件名不合法
#file management over
# template

View File

@ -0,0 +1,64 @@
package io.metersphere.project.controller;
import io.metersphere.project.dto.filemanagement.FileLogRecord;
import io.metersphere.project.dto.filemanagement.request.FileAssociationDeleteRequest;
import io.metersphere.project.dto.filemanagement.response.FileAssociationResponse;
import io.metersphere.project.service.FileAssociationService;
import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.log.constants.OperationLogModule;
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.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "项目管理-文件管理-文件关联")
@RestController
@RequestMapping("/project/file/association")
public class FileAssociationController {
@Resource
private FileAssociationService fileAssociationService;
@GetMapping("/list/{id}")
@Operation(summary = "项目管理-文件管理-表格分页查询文件")
@RequiresPermissions(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ)
public List<FileAssociationResponse> getAssociationList(@PathVariable String id) {
return fileAssociationService.selectFileAllVersionAssociation(id);
}
@GetMapping("/upgrade/{projectId}/{id}")
@Operation(summary = "项目管理-文件管理-表格分页查询文件")
@RequiresPermissions(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ_UPDATE)
public String upgrade(@PathVariable String projectId,@PathVariable String id) {
FileLogRecord fileLogRecord = FileLogRecord.builder()
.logModule(OperationLogModule.PROJECT_FILE_MANAGEMENT)
.requestMethod(HttpMethodConstants.GET.name())
.requestUrl("/project/file/association/upgrade/{projectId}/{id}")
.operator(SessionUtils.getUserId())
.projectId(projectId)
.build();
return fileAssociationService.upgrade(id,fileLogRecord);
}
@PostMapping("/delete")
@Operation(summary = "项目管理-文件管理-表格分页查询文件")
@RequiresPermissions(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ_UPDATE)
public int delete(@RequestBody @Validated FileAssociationDeleteRequest request) {
FileLogRecord fileLogRecord = FileLogRecord.builder()
.logModule(OperationLogModule.PROJECT_FILE_MANAGEMENT)
.requestMethod(HttpMethodConstants.POST.name())
.requestUrl("/project/file/association/delete")
.operator(SessionUtils.getUserId())
.projectId(request.getProjectId())
.build();
return fileAssociationService.deleteBySourceId(request.getAssociationIds(),fileLogRecord);
}
}

View File

@ -48,7 +48,7 @@ public class FileManagementController {
@Operation(summary = "项目管理-文件管理-查看文件详情")
@RequiresPermissions(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ)
public FileInformationResponse page(@PathVariable String id) {
return fileMetadataService.get(id);
return fileMetadataService.getFileInformation(id);
}

View File

@ -29,7 +29,7 @@ public class FilePreviewController {
@GetMapping(value = "/original/{userId}/{fileId}")
@Operation(summary = "预览原图")
public ResponseEntity<byte[]> originalImg(@PathVariable String userId, @PathVariable String fileId) throws Exception {
FileInformationResponse fileInformationResponse = fileMetadataService.get(fileId);
FileInformationResponse fileInformationResponse = fileMetadataService.getFileInformation(fileId);
if (StringUtils.isEmpty(fileInformationResponse.getId())) {
throw new MSException("file.not.exist");
}
@ -42,7 +42,7 @@ public class FilePreviewController {
@GetMapping(value = "/compressed/{userId}/{fileId}")
@Operation(summary = "预览缩略图")
public ResponseEntity<byte[]> compressedImg(@PathVariable String userId, @PathVariable String fileId) throws Exception {
FileInformationResponse fileInformationResponse = fileMetadataService.get(fileId);
FileInformationResponse fileInformationResponse = fileMetadataService.getFileInformation(fileId);
if (StringUtils.isEmpty(fileInformationResponse.getId())) {
throw new MSException("file.not.exist");
}

View File

@ -0,0 +1,9 @@
package io.metersphere.project.dto.filemanagement;
import lombok.Data;
@Data
public class FileAssociationSource {
private String sourceId;
private String sourceName;
}

View File

@ -0,0 +1,27 @@
package io.metersphere.project.dto.filemanagement;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@AllArgsConstructor
@Builder
public class FileLogRecord {
//操作人
@NotBlank
private String operator;
//请求方法 POST/GET
@NotBlank
private String requestMethod;
//触发log记录的请求路径
@NotBlank
private String requestUrl;
//log所属记录模块
@NotBlank
private String logModule;
//log所属项目ID
@NotBlank
private String projectId;
}

View File

@ -3,8 +3,10 @@ package io.metersphere.project.dto.filemanagement;
import io.metersphere.project.domain.FileModule;
import io.metersphere.project.domain.FileModuleRepository;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class FileRepositoryLog {
private String id;

View File

@ -0,0 +1,20 @@
package io.metersphere.project.dto.filemanagement.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
@Data
public class FileAssociationDeleteRequest {
@Schema(description = "要删除的id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "{file.association.source.not.exist}")
List<@Valid @NotBlank(message = "{file.association.source.not.exist}") String> associationIds;
@Schema(description = "项目Id")
@NotBlank(message = "{project.id.not_blank}")
private String projectId;
}

View File

@ -0,0 +1,27 @@
package io.metersphere.project.dto.filemanagement.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class FileAssociationResponse {
@Schema(description = "ID")
private String id;
@Schema(description = "资源Id")
private String sourceId;
@Schema(description = "文件Id")
private String fileId;
@Schema(description = "资源名称")
private String sourceName;
@Schema(description = "资源类型")
private String sourceType;
@Schema(description = "文件版本")
private String fileVersion;
}

View File

@ -0,0 +1,15 @@
package io.metersphere.project.mapper;
import io.metersphere.project.dto.filemanagement.FileAssociationSource;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 本类中所有带有querySql参数的方法不能直接传入sql
* 要使用FileAssociationResourceUtil.getQuerySql(sourceType)防止SQL注入
*/
public interface ExtFileAssociationMapper {
FileAssociationSource selectNameBySourceTableAndId(@Param("querySql") String querySql, @Param("sourceId") String sourceId);
List<FileAssociationSource> selectAssociationSourceBySourceTableAndIdList(@Param("querySql") String querySql, @Param("idList") List<String> sourceIdList);
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.project.mapper.ExtFileAssociationMapper">
<select id="selectNameBySourceTableAndId"
resultType="io.metersphere.project.dto.filemanagement.FileAssociationSource">
${querySql}
WHERE id = #{sourceId}
</select>
<select id="selectAssociationSourceBySourceTableAndIdList"
resultType="io.metersphere.project.dto.filemanagement.FileAssociationSource">
${querySql}
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper>

View File

@ -0,0 +1,121 @@
package io.metersphere.project.service;
import io.metersphere.project.domain.FileMetadata;
import io.metersphere.project.domain.Project;
import io.metersphere.project.dto.filemanagement.FileLogRecord;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.builder.LogDTOBuilder;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.log.dto.LogDTO;
import io.metersphere.system.log.service.OperationLogService;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@Service
@Transactional(rollbackFor = Exception.class)
public class FileAssociationLogService {
@Resource
private ProjectMapper projectMapper;
@Resource
private OperationLogService operationLogService;
public void saveBatchInsertLog(String sourceName, List<FileMetadata> addFileList, FileLogRecord fileLogRecord) {
List<LogDTO> list = new ArrayList<>();
Project project = projectMapper.selectByPrimaryKey(fileLogRecord.getProjectId());
for (FileMetadata fileMetadata : addFileList) {
LogDTO dto = LogDTOBuilder.builder()
.projectId(project.getId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.ADD.name())
.module(fileLogRecord.getLogModule())
.method(fileLogRecord.getRequestMethod())
.path(fileLogRecord.getRequestUrl())
.createUser(fileLogRecord.getOperator())
.sourceId(fileMetadata.getId())
.content(sourceName + StringUtils.SPACE + Translator.get("file.log.association") + ":" + fileMetadata.getName())
.build().getLogDTO();
list.add(dto);
}
operationLogService.batchAdd(list);
}
public void saveBatchUpdateLog(String sourceName, Collection<FileMetadata> values, FileLogRecord fileLogRecord) {
List<LogDTO> list = new ArrayList<>();
Project project = projectMapper.selectByPrimaryKey(fileLogRecord.getProjectId());
for (FileMetadata fileMetadata : values) {
LogDTO dto = this.genUpdateFileAssociationLogDTO(project, sourceName, fileMetadata, fileLogRecord);
list.add(dto);
}
operationLogService.batchAdd(list);
}
public void saveUpdateLog(String sourceName, FileMetadata fileMetadata, FileLogRecord fileLogRecord) {
Project project = projectMapper.selectByPrimaryKey(fileLogRecord.getProjectId());
LogDTO dto = this.genUpdateFileAssociationLogDTO(project, sourceName, fileMetadata, fileLogRecord);
operationLogService.add(dto);
}
private LogDTO genUpdateFileAssociationLogDTO(Project project, String sourceName, FileMetadata fileMetadata, FileLogRecord fileLogRecord) {
return LogDTOBuilder.builder()
.projectId(project.getId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.UPDATE.name())
.module(fileLogRecord.getLogModule())
.method(fileLogRecord.getRequestMethod())
.path(fileLogRecord.getRequestUrl())
.createUser(fileLogRecord.getOperator())
.sourceId(fileMetadata.getId())
.content(sourceName + StringUtils.SPACE + Translator.get("file.log.association.update") + ":" + fileMetadata.getName())
.build().getLogDTO();
}
public void saveDeleteLog(Map<String, List<String>> sourceToFileNameMap, FileLogRecord fileLogRecord) {
Project project = projectMapper.selectByPrimaryKey(fileLogRecord.getProjectId());
List<LogDTO> list = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : sourceToFileNameMap.entrySet()) {
String sourceName = entry.getKey();
List<String> fileNameList = entry.getValue();
LogDTO dto = LogDTOBuilder.builder()
.projectId(project.getId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.DELETE.name())
.module(fileLogRecord.getLogModule())
.method(fileLogRecord.getRequestMethod())
.path(fileLogRecord.getRequestUrl())
.createUser(fileLogRecord.getOperator())
.sourceId(IDGenerator.nextStr())
.content(sourceName + StringUtils.SPACE + Translator.get("file.log.association.delete") + ":" + StringUtils.join(fileNameList, ","))
.build().getLogDTO();
list.add(dto);
}
operationLogService.batchAdd(list);
}
public void saveTransferAssociationLog(String sourceId, String fileName, String sourceName, FileLogRecord fileLogRecord) {
Project project = projectMapper.selectByPrimaryKey(fileLogRecord.getProjectId());
LogDTO dto = LogDTOBuilder.builder()
.projectId(project.getId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.ADD.name())
.module(fileLogRecord.getLogModule())
.method(fileLogRecord.getRequestMethod())
.path(fileLogRecord.getRequestUrl())
.createUser(fileLogRecord.getOperator())
.sourceId(sourceId)
.content(sourceName + StringUtils.SPACE + Translator.get("file.log.transfer.association") + ":" + fileName)
.build().getLogDTO();
operationLogService.add(dto);
}
}

View File

@ -0,0 +1,329 @@
package io.metersphere.project.service;
import io.metersphere.project.domain.FileAssociation;
import io.metersphere.project.domain.FileAssociationExample;
import io.metersphere.project.domain.FileMetadata;
import io.metersphere.project.dto.filemanagement.FileAssociationSource;
import io.metersphere.project.dto.filemanagement.FileLogRecord;
import io.metersphere.project.dto.filemanagement.response.FileAssociationResponse;
import io.metersphere.project.dto.filemanagement.response.FileInformationResponse;
import io.metersphere.project.mapper.ExtFileAssociationMapper;
import io.metersphere.project.mapper.FileAssociationMapper;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.FileAssociationSourceUtil;
import io.metersphere.sdk.util.Translator;
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.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class FileAssociationService {
@Resource
private FileAssociationMapper fileAssociationMapper;
@Resource
private FileMetadataService fileMetadataService;
@Resource
private FileAssociationLogService fileAssociationLogService;
@Resource
private SqlSessionFactory sqlSessionFactory;
@Resource
private ExtFileAssociationMapper extFileAssociationMapper;
/**
* 通过文件ID检查该文件所有版本对应的资源关联关系
* @param id
* @return
*/
public List<FileAssociationResponse> selectFileAllVersionAssociation(String id) {
FileInformationResponse fileMetadata = fileMetadataService.getFileInformation(id);
if (fileMetadata.getId() == null) {
throw new MSException(Translator.get("file.not.exist"));
}
//通过引用ID查找关联数据
FileAssociationExample example = new FileAssociationExample();
example.createCriteria().andFileRefIdEqualTo(fileMetadata.getRefId());
List<FileAssociation> associationList = fileAssociationMapper.selectByExample(example);
//查找文件信息
Map<String,FileMetadata> fileIdMap = this.getFileIdMap( associationList.stream().map(FileAssociation::getFileId).collect(Collectors.toList()));
//查找资源信息
Map<String,String> sourceIdNameMap = this.getAssociationSourceMap(
associationList.stream().collect(
Collectors.groupingBy(FileAssociation::getSourceType,Collectors.mapping(FileAssociation::getSourceId,Collectors.toList()))
));
//组装返回参数
List<FileAssociationResponse> responseList = new ArrayList<>();
associationList.forEach(item -> {
if(fileIdMap.containsKey(item.getFileId())){
FileAssociationResponse response = new FileAssociationResponse();
response.setId(item.getId());
response.setSourceId(item.getSourceId());
response.setFileId(item.getFileId());
response.setSourceName(sourceIdNameMap.get(item.getSourceId()));
response.setSourceType(item.getSourceType());
response.setFileVersion(fileIdMap.get(item.getFileId()).getFileVersion());
responseList.add(response);
}
});
return responseList;
}
private Map<String, FileMetadata> getFileIdMap(List<String> fileIdList) {
if(CollectionUtils.isEmpty(fileIdList)){
return new HashMap<>();
}else {
List<FileMetadata> fileMetadataList = fileMetadataService.selectByList(fileIdList);
return fileMetadataList.stream().collect(Collectors.toMap(FileMetadata::getId, item -> item));
}
}
//通过资源类型Map查找关联表
private Map<String, String> getAssociationSourceMap(Map<String, List<String>> sourceTypeToIdMap) {
List<FileAssociationSource> sourceQueryList = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : sourceTypeToIdMap.entrySet()) {
String sourceType =entry.getKey();
sourceQueryList.addAll(
extFileAssociationMapper.selectAssociationSourceBySourceTableAndIdList(FileAssociationSourceUtil.getQuerySql(sourceType),entry.getValue()));
}
return sourceQueryList.stream().collect(Collectors.toMap(FileAssociationSource::getSourceId, FileAssociationSource::getSourceName));
}
/**
* 检查文件版本是否是最新的
* @param fileIdList 不是最新的文件ID
* @return
*/
public List<String> checkFilesVersion(List<String> fileIdList){
if (CollectionUtils.isEmpty(fileIdList)) {
throw new MSException(Translator.get("file.not.exist"));
}
List<String> updatedFileId = new ArrayList<>();
List<FileMetadata> fileMetadataList = fileMetadataService.selectByList(fileIdList);
fileMetadataList.forEach(item -> {
if (!item.getLatest()) {
updatedFileId.add(item.getId());
}
});
return updatedFileId;
}
/**
* 资源关联文件
*
* @param sourceId 资源id
* @param sourceType 资源类型
* @param fileIds 文件id集合
* @param fileLogRecord 日志记录相关包含操作人日志所属模块触发日志记录的请求路径和请求方法
* @return 本次涉及到关联的关联表ID
*/
public List<String> association(String sourceId, String sourceType, List<String> fileIds, boolean isOverWrite, @Validated FileLogRecord fileLogRecord) {
if (CollectionUtils.isEmpty(fileIds)) {
throw new MSException(Translator.get("file.not.exist"));
}
FileAssociationSource source = extFileAssociationMapper.selectNameBySourceTableAndId(FileAssociationSourceUtil.getQuerySql(sourceType),sourceId);
this.validateSourceName(source);
List<FileMetadata> fileMetadataList = fileMetadataService.selectByList(fileIds);
if (fileMetadataList.size() != fileIds.size()) {
throw new MSException(Translator.get("file.some.not.exist"));
}
//校验文件是否已经关联
FileAssociationExample example = new FileAssociationExample();
example.createCriteria().andSourceIdEqualTo(sourceId);
List<FileAssociation> associationdList = fileAssociationMapper.selectByExample(example);
Map<String,FileAssociation> refIdFileAssociationMap = associationdList.stream().collect(Collectors.toMap(FileAssociation::getFileRefId, item -> item));
Map<FileAssociation,FileMetadata> updateAssociationMap = new HashMap<>();
List<FileMetadata> addFileList = new ArrayList<>();
for (FileMetadata fileMetadata : fileMetadataList) {
FileAssociation fileAssociation = refIdFileAssociationMap.get(fileMetadata.getRefId());
if(fileAssociation == null){
addFileList.add(fileMetadata);
}else if(!StringUtils.equals(fileAssociation.getFileId(),fileMetadata.getId())){
updateAssociationMap.put(fileAssociation,fileMetadata);
}
}
List<String> associationId = new ArrayList<>();
associationId.addAll(this.createFileAssociation(addFileList,sourceId,source.getSourceName(),sourceType,fileLogRecord));
if(isOverWrite){
associationId.addAll(this.updateFileAssociation(updateAssociationMap,source.getSourceName(),fileLogRecord));
}
return associationId;
}
private Collection<String> createFileAssociation(List<FileMetadata> addFileList, String sourceId, String sourceName,String sourceType, @Validated FileLogRecord logRecord) {
FileAssociationSourceUtil.validate(sourceType);
if(CollectionUtils.isNotEmpty(addFileList)){
List<FileAssociation> createFile = new ArrayList<>();
long operatorTime = System.currentTimeMillis();
addFileList.forEach(item -> {
FileAssociation fileAssociation = new FileAssociation();
fileAssociation.setId(IDGenerator.nextStr());
fileAssociation.setFileId(item.getId());
fileAssociation.setFileRefId(item.getRefId());
fileAssociation.setSourceId(sourceId);
fileAssociation.setSourceType(sourceType);
fileAssociation.setCreateTime(operatorTime);
fileAssociation.setCreateUser(logRecord.getOperator());
fileAssociation.setUpdateTime(operatorTime);
fileAssociation.setUpdateUser(logRecord.getOperator());
fileAssociation.setFileVersion(item.getFileVersion());
createFile.add(fileAssociation);
});
fileAssociationMapper.batchInsert(createFile);
fileAssociationLogService.saveBatchInsertLog(sourceName,addFileList,logRecord);
return createFile.stream().map(FileAssociation::getId).collect(Collectors.toList());
}else {
return new ArrayList<>();
}
}
private Collection<String> updateFileAssociation(Map<FileAssociation, FileMetadata> updateAssociationMap,String sourceName,@Validated FileLogRecord logRecord) {
if(MapUtils.isNotEmpty(updateAssociationMap)){
long operatorTime = System.currentTimeMillis();
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
FileAssociationMapper batchUpdateMapper = sqlSession.getMapper(FileAssociationMapper.class);
for (Map.Entry<FileAssociation, FileMetadata> entry : updateAssociationMap.entrySet()) {
FileAssociation association = entry.getKey();
FileMetadata metadata = entry.getValue();
association.setFileId(metadata.getId());
association.setFileVersion(metadata.getFileVersion());
association.setUpdateUser(logRecord.getOperator());
association.setUpdateTime(operatorTime);
batchUpdateMapper.updateByPrimaryKeySelective(association);
}
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
fileAssociationLogService.saveBatchUpdateLog(sourceName,updateAssociationMap.values(),logRecord);
return updateAssociationMap.keySet().stream().map(FileAssociation::getId).collect(Collectors.toList());
}else {
return new ArrayList<>();
}
}
/**
* 将资源关联的文件更新到最新版本
* @param fileAssociationId 关联表ID
* @param fileLogRecord 日志记录相关
* @return 更新后的文件ID
*/
public String upgrade(String fileAssociationId,@Validated FileLogRecord fileLogRecord){
FileAssociation fileAssociation = fileAssociationMapper.selectByPrimaryKey(fileAssociationId);
if(fileAssociation == null){
throw new MSException(Translator.get("file.association.not.exist"));
}
FileMetadata newFileMetadata = this.getNewVersionFileMetadata(fileAssociation.getFileId());
if(StringUtils.equals(newFileMetadata.getId(),fileAssociation.getFileId())){
return fileAssociation.getFileId();
}else {
fileAssociation.setFileId(newFileMetadata.getId());
fileAssociation.setFileVersion(newFileMetadata.getFileVersion());
fileAssociationMapper.updateByPrimaryKeySelective(fileAssociation);
FileAssociationSource source = extFileAssociationMapper.selectNameBySourceTableAndId(FileAssociationSourceUtil.getQuerySql(fileAssociation.getSourceType()),fileAssociation.getSourceId());
this.validateSourceName(source);
fileAssociationLogService.saveUpdateLog(source.getSourceName(),newFileMetadata,fileLogRecord);
return newFileMetadata.getId();
}
}
private FileMetadata getNewVersionFileMetadata(String fileId){
FileMetadata fileMetadata = fileMetadataService.selectById(fileId);
if(fileMetadata == null){
throw new MSException(Translator.get("file.not.exist"));
}
FileMetadata newVersionFileMetadata = fileMetadataService.selectLatestFileByRefId(fileMetadata.getRefId());
return newVersionFileMetadata;
}
/**
* 取消关联
* @param idList 取消关联的id
* @param logRecord 日志记录相关
* @return
*/
public int deleteBySourceId(List<String> idList, @Validated FileLogRecord logRecord){
if(CollectionUtils.isEmpty(idList)){
return 0;
}
FileAssociationExample example = new FileAssociationExample();
example.createCriteria().andIdIn(idList);
List<FileAssociation> fileAssociationList = fileAssociationMapper.selectByExample(example);
Map<String,List<String>> sourceToFileNameMap = this.genSourceNameFileNameMap(fileAssociationList);
int deleteCount = fileAssociationMapper.deleteByExample(example);
if(MapUtils.isNotEmpty(sourceToFileNameMap)){
fileAssociationLogService.saveDeleteLog(sourceToFileNameMap,logRecord);
}
return deleteCount;
}
private Map<String, List<String>> genSourceNameFileNameMap(List<FileAssociation> fileAssociationList) {
Map<String,List<String>> sourceNameFileNameMap = new HashMap<>();
Map<String,String> fileIdMap = new HashMap<>();
for (FileAssociation fileAssociation:fileAssociationList){
FileAssociationSource source = extFileAssociationMapper.selectNameBySourceTableAndId(FileAssociationSourceUtil.getQuerySql(fileAssociation.getSourceType()),fileAssociation.getSourceId());
this.validateSourceName(source);
String fileName = null;
if(fileIdMap.containsKey(fileAssociation.getFileId())){
fileName = fileIdMap.get(fileAssociation.getFileId());
}else {
FileMetadata fileMetadata = fileMetadataService.selectById(fileAssociation.getFileId());
if(fileMetadata != null){
fileName = fileMetadata.getName();
fileIdMap.put(fileMetadata.getId(),fileName);
}
}
if(StringUtils.isNotEmpty(fileName)){
if(sourceNameFileNameMap.containsKey(source.getSourceName())){
sourceNameFileNameMap.get(source.getSourceName()).add(fileName);
}else {
List<String> fileNameList = new ArrayList<>();
fileNameList.add(fileName);
sourceNameFileNameMap.put(source.getSourceName(), fileNameList);
}
}
}
return sourceNameFileNameMap;
}
/**
* 转存并关联文件
* @param fileName 文件名称含后缀
* @param fileBytes 文件字节流
* @param sourceId 要关联的资源ID
* @param sourceType 要关联的资源名称
* @param fileLogRecord 日志记录相关
* @return
* @throws Exception
*/
public String transferAndAssociation(String fileName,byte[] fileBytes,String sourceId,String sourceType, @Validated FileLogRecord fileLogRecord) throws Exception {
FileAssociationSource source = extFileAssociationMapper.selectNameBySourceTableAndId(FileAssociationSourceUtil.getQuerySql(sourceType),sourceId);
this.validateSourceName(source);
String fileId = fileMetadataService.transferFile(fileName, fileLogRecord.getProjectId(), fileLogRecord.getOperator(),fileBytes);
List<String> accociationList = new ArrayList<>();
accociationList.add(fileId);
this.association(sourceId, sourceType, accociationList, false, fileLogRecord);
fileAssociationLogService.saveTransferAssociationLog(sourceId,fileName,source.getSourceName(),fileLogRecord);
return fileId;
}
private void validateSourceName(FileAssociationSource source){
if(source == null){
throw new MSException(Translator.get("file.association.source.not.exist"));
}
}
}

View File

@ -60,12 +60,12 @@ public class FileMetadataLogService {
.path("/project/file/re-upload")
.sourceId(module.getId())
.content(Translator.get("file.log.re-upload") + " " + module.getName())
.originalValue(JSON.toJSONBytes(module))
.createUser(operator)
.build().getLogDTO();
operationLogService.add(dto);
}
public void saveUpdateLog(FileMetadata module, String projectId, String operator) {
public void saveUpdateLog(FileMetadata oldFile, FileMetadata newFile, String projectId, String operator) {
Project project = projectMapper.selectByPrimaryKey(projectId);
LogDTO dto = LogDTOBuilder.builder()
.projectId(projectId)
@ -74,9 +74,10 @@ public class FileMetadataLogService {
.module(logModule)
.method(HttpMethodConstants.POST.name())
.path("/project/file/update")
.sourceId(module.getId())
.content(module.getName())
.originalValue(JSON.toJSONBytes(module))
.sourceId(newFile.getId())
.content(newFile.getName())
.originalValue(JSON.toJSONBytes(oldFile))
.modifiedValue(JSON.toJSONBytes(newFile))
.createUser(operator)
.build().getLogDTO();
operationLogService.add(dto);
@ -115,7 +116,6 @@ public class FileMetadataLogService {
.path("/project/file/jar-file-status")
.sourceId(module.getId())
.content(Translator.get("change.jar.enable") + ":" + enable)
.originalValue(JSON.toJSONBytes(module))
.createUser(operator)
.build().getLogDTO();
operationLogService.add(dto);
@ -162,7 +162,7 @@ public class FileMetadataLogService {
operationLogService.add(dto);
}
public void saveFilePullLog(FileMetadata module, String operator) {
public void saveFilePullLog(FileMetadata oldFile, FileMetadata module, String operator) {
Project project = projectMapper.selectByPrimaryKey(module.getProjectId());
LogDTO dto = LogDTOBuilder.builder()
.projectId(module.getProjectId())
@ -173,7 +173,8 @@ public class FileMetadataLogService {
.path("/project/file/repository/pull-file")
.sourceId(module.getId())
.content(Translator.get("file.log.pull") + " " + module.getName())
.originalValue(JSON.toJSONBytes(module))
.originalValue(JSON.toJSONBytes(oldFile))
.modifiedValue(JSON.toJSONBytes(module))
.createUser(operator)
.build().getLogDTO();
operationLogService.add(dto);

View File

@ -18,11 +18,14 @@ import io.metersphere.project.utils.FileDownloadUtils;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.TempFileUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.RemoteFileAttachInfo;
import io.metersphere.system.file.FileRepository;
import io.metersphere.system.file.FileRequest;
import io.metersphere.system.file.MinioRepository;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.GitRepositoryUtil;
import io.metersphere.system.utils.PageUtils;
@ -70,7 +73,11 @@ public class FileMetadataService {
@Value("${metersphere.file.batch-download-max:600MB}")
private DataSize maxFileSize;
public FileInformationResponse get(String id) {
public FileMetadata selectById(String id) {
return fileMetadataMapper.selectByPrimaryKey(id);
}
public FileInformationResponse getFileInformation(String id) {
FileMetadata fileMetadata = extFileMetadataMapper.getById(id);
FileInformationResponse dto = new FileInformationResponse(fileMetadata);
initModuleName(dto);
@ -115,7 +122,7 @@ public class FileMetadataService {
}
}
public FileMetadata saveFileMetadata(String projectId, String moduleId, String filePath, String storage, String operator, long size, boolean enable) {
public FileMetadata genFileMetadata(String filePath, String storage, long size, boolean enable, String projectId, String moduleId, String operator) {
String fileName = TempFileUtils.getFileNameByPath(filePath);
FileMetadata fileMetadata = new FileMetadata();
if (StringUtils.lastIndexOf(fileName, ".") > 0) {
@ -131,7 +138,16 @@ public class FileMetadataService {
this.checkEnableFile(fileMetadata.getType());
}
//检查处理后的用户名合法性
this.checkFileName(null, fileMetadata.getName(), projectId);
if (StringUtils.equals(storage, StorageType.MINIO.name())) {
this.checkMinIOFileName(null, fileMetadata.getName(), projectId);
} else {
//Git 存储库下的文件路径不能重复
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andPathEqualTo(filePath).andProjectIdEqualTo(projectId).andStorageEqualTo(StorageType.GIT.name()).andModuleIdEqualTo(moduleId);
if (fileMetadataMapper.countByExample(example) > 0) {
throw new MSException(Translator.get("file.name.exist") + ":" + fileName);
}
}
fileMetadata.setId(IDGenerator.nextStr());
fileMetadata.setStorage(storage);
@ -147,7 +163,6 @@ public class FileMetadataService {
fileMetadata.setLatest(true);
fileMetadata.setRefId(fileMetadata.getId());
fileMetadata.setEnable(false);
fileMetadataMapper.insert(fileMetadata);
return fileMetadata;
}
@ -157,31 +172,95 @@ public class FileMetadataService {
String fileName = StringUtils.trim(uploadFile.getOriginalFilename());
FileMetadata fileMetadata = this.saveFileMetadata(request.getProjectId(), request.getModuleId(), fileName, StorageType.MINIO.name(), operator, uploadFile.getSize(), request.isEnable());
//记录日志
fileMetadataLogService.saveUploadLog(fileMetadata, operator);
FileMetadata fileMetadata = this.genFileMetadata(fileName, StorageType.MINIO.name(), uploadFile.getSize(), request.isEnable(), request.getProjectId(), request.getModuleId(), operator);
// 上传文件
String filePath = this.uploadFile(fileMetadata, uploadFile);
FileMetadata updateFileMetadata = new FileMetadata();
updateFileMetadata.setId(fileMetadata.getId());
updateFileMetadata.setPath(filePath);
updateFileMetadata.setFileVersion(fileMetadata.getId());
fileMetadataMapper.updateByPrimaryKeySelective(updateFileMetadata);
fileMetadata.setPath(filePath);
fileMetadata.setFileVersion(fileMetadata.getId());
fileMetadataMapper.insert(fileMetadata);
//记录日志
fileMetadataLogService.saveUploadLog(fileMetadata, operator);
return fileMetadata.getId();
}
private void checkFileName(String id, String fileName, String projectId) {
/**
* 文件转存
*
* @param fileName 文件名
* @param projectId 项目ID
* @param operator 操作人
* @param fileBytes 文件字节
* @return
* @throws Exception
*/
public String transferFile(String fileName, String projectId, String operator, byte[] fileBytes) throws Exception {
if (StringUtils.isBlank(fileName)) {
throw new MSException(Translator.get("file.name.cannot.be.empty"));
}
fileName = this.genTransferFileName(StringUtils.trim(fileName), projectId);
String moduleId = ModuleConstants.NODE_TYPE_DEFAULT;
FileMetadata fileMetadata = this.genFileMetadata(fileName, StorageType.MINIO.name(), fileBytes.length, false, projectId, moduleId, operator);
FileRequest uploadFileRequest = new FileRequest();
uploadFileRequest.setFileName(fileMetadata.getId());
uploadFileRequest.setProjectId(fileMetadata.getProjectId());
uploadFileRequest.setStorage(StorageType.MINIO.name());
FileRepository minio = CommonBeanFactory.getBean(MinioRepository.class);
String filePath = minio.saveFile(fileBytes, uploadFileRequest);
fileMetadata.setPath(filePath);
fileMetadata.setFileVersion(fileMetadata.getId());
fileMetadataMapper.insert(fileMetadata);
return fileMetadata.getId();
}
private String genTransferFileName(String fullFileName, String projectId) {
if (StringUtils.containsAny(fullFileName, "/")) {
throw new MSException(Translator.get("file.name.error"));
}
String fileName;
String fileType = null;
if (StringUtils.lastIndexOf(fullFileName, ".") > 0) {
//采用这种判断方式可以避免将隐藏文件的后缀名作为文件类型
fileName = StringUtils.substring(fullFileName, 0, fullFileName.lastIndexOf("."));
fileType = StringUtils.substring(fullFileName, fullFileName.lastIndexOf(".") + 1);
} else {
fileName = fullFileName;
}
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andNameEqualTo(fileName).andProjectIdEqualTo(projectId);
int fileIndex = 0;
String originFileName = fileName;
while (fileMetadataMapper.countByExample(example) > 0) {
fileIndex++;
fileName = originFileName + "(" + fileIndex + ")";
example = new FileMetadataExample();
example.createCriteria().andNameEqualTo(fileName).andProjectIdEqualTo(projectId);
}
if (StringUtils.isNotEmpty(fileType)) {
fileName = fileName + "." + fileType;
}
return fileName;
}
private void checkMinIOFileName(String id, String fileName, String projectId) {
if (StringUtils.isBlank(fileName)) {
throw new MSException(Translator.get("file.name.cannot.be.empty"));
}
FileMetadataExample example = new FileMetadataExample();
if (StringUtils.isBlank(id)) {
example.createCriteria().andNameEqualTo(fileName).andProjectIdEqualTo(projectId);
example.createCriteria().andNameEqualTo(fileName).andProjectIdEqualTo(projectId).andStorageEqualTo(StorageType.MINIO.name());
} else {
example.createCriteria().andNameEqualTo(fileName).andProjectIdEqualTo(projectId).andIdNotEqualTo(id);
example.createCriteria().andNameEqualTo(fileName).andProjectIdEqualTo(projectId).andIdNotEqualTo(id).andStorageEqualTo(StorageType.MINIO.name());
}
if (fileMetadataMapper.countByExample(example) > 0) {
throw new MSException(Translator.get("file.name.exist") + ":" + fileName);
@ -263,7 +342,7 @@ public class FileMetadataService {
updateExample.setDescription(request.getDescription());
updateExample.setModuleId(request.getModuleId());
if (StringUtils.isNotBlank(request.getName())) {
this.checkFileName(request.getId(), request.getName(), fileMetadata.getProjectId());
this.checkMinIOFileName(request.getId(), request.getName(), fileMetadata.getProjectId());
updateExample.setName(request.getName());
}
if (CollectionUtils.isNotEmpty(request.getTags())) {
@ -278,8 +357,10 @@ public class FileMetadataService {
updateExample.setUpdateUser(operator);
updateExample.setUpdateTime(System.currentTimeMillis());
fileMetadataMapper.updateByPrimaryKeySelective(updateExample);
FileMetadata newFile = fileMetadataMapper.selectByPrimaryKey(request.getId());
//记录日志
fileMetadataLogService.saveUpdateLog(fileMetadata, fileMetadata.getProjectId(), operator);
fileMetadataLogService.saveUpdateLog(fileMetadata, newFile, fileMetadata.getProjectId(), operator);
}
}
@ -491,6 +572,7 @@ public class FileMetadataService {
if (!StringUtils.equals(gitFileAttachInfo.getCommitId(), metadataRepository.getCommitId())) {
this.setFileVersionIsOld(oldFile, operator);
FileMetadata fileMetadata = this.genNewVersion(oldFile, operator);
fileMetadata.setFileVersion(gitFileAttachInfo.getCommitId());
fileMetadata.setSize(gitFileAttachInfo.getSize());
fileMetadataMapper.insert(fileMetadata);
returnFileId = fileMetadata.getId();
@ -503,10 +585,27 @@ public class FileMetadataService {
fileMetadataRepositoryMapper.insert(fileMetadataRepository);
//记录日志
fileMetadataLogService.saveFilePullLog(fileMetadata, operator);
fileMetadataLogService.saveFilePullLog(oldFile, fileMetadata, operator);
}
}
}
return returnFileId;
}
public List<FileMetadata> selectByList(List<String> fileIds) {
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andIdIn(fileIds);
return fileMetadataMapper.selectByExample(example);
}
public FileMetadata selectLatestFileByRefId(String refId) {
FileMetadataExample fileMetadataExample = new FileMetadataExample();
fileMetadataExample.createCriteria().andRefIdEqualTo(refId).andLatestEqualTo(true);
List<FileMetadata> fileMetadataList = fileMetadataMapper.selectByExample(fileMetadataExample);
if (CollectionUtils.isNotEmpty(fileMetadataList)) {
return fileMetadataList.get(0);
} else {
throw new MSException(Translator.get("latest.file.not.exist"));
}
}
}

View File

@ -64,7 +64,7 @@ public class FileModuleLogService {
operationLogService.add(dto);
}
public void saveUpdateLog(FileModule module, String projectId, String operator) {
public void saveUpdateLog(FileModule oldModule, FileModule newModule, String projectId, String operator) {
Project project = projectMapper.selectByPrimaryKey(projectId);
LogDTO dto = LogDTOBuilder.builder()
.projectId(projectId)
@ -73,26 +73,28 @@ public class FileModuleLogService {
.module(OperationLogModule.PROJECT_FILE_MANAGEMENT)
.method(HttpMethodConstants.POST.name())
.path("/project/file-module/update")
.sourceId(module.getId())
.content(module.getName())
.originalValue(JSON.toJSONBytes(module))
.sourceId(newModule.getId())
.content(newModule.getName())
.originalValue(JSON.toJSONBytes(oldModule))
.modifiedValue(JSON.toJSONBytes(newModule))
.createUser(operator)
.build().getLogDTO();
operationLogService.add(dto);
}
public void saveUpdateRepositoryLog(FileRepositoryLog fileRepositoryLog, String operator) {
Project project = projectMapper.selectByPrimaryKey(fileRepositoryLog.getProjectId());
public void saveUpdateRepositoryLog(FileRepositoryLog oldRepositoryLog, FileRepositoryLog newRepositoryLog, String operator) {
Project project = projectMapper.selectByPrimaryKey(newRepositoryLog.getProjectId());
LogDTO dto = LogDTOBuilder.builder()
.projectId(fileRepositoryLog.getProjectId())
.projectId(newRepositoryLog.getProjectId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.UPDATE.name())
.module(OperationLogModule.PROJECT_FILE_MANAGEMENT)
.method(HttpMethodConstants.POST.name())
.path("/project/file/repository/update-repository")
.sourceId(fileRepositoryLog.getId())
.content(fileRepositoryLog.getName())
.originalValue(JSON.toJSONBytes(fileRepositoryLog))
.sourceId(newRepositoryLog.getId())
.content(newRepositoryLog.getName())
.originalValue(JSON.toJSONBytes(oldRepositoryLog))
.modifiedValue(JSON.toJSONBytes(newRepositoryLog))
.createUser(operator)
.build().getLogDTO();
operationLogService.add(dto);
@ -142,7 +144,6 @@ public class FileModuleLogService {
.path("/project/file-module/move")
.sourceId(moveNode.getId())
.content(logContent)
.originalValue(JSON.toJSONBytes(moveNode))
.createUser(operator)
.build().getLogDTO();
operationLogService.add(dto);

View File

@ -131,8 +131,9 @@ public class FileModuleService extends ModuleTreeService implements CleanupProje
updateModule.setUpdateTime(System.currentTimeMillis());
updateModule.setUpdateUser(userId);
fileModuleMapper.updateByPrimaryKeySelective(updateModule);
FileModule newModule = fileModuleMapper.selectByPrimaryKey(request.getId());
//记录日志
fileModuleLogService.saveUpdateLog(updateModule, module.getProjectId(), userId);
fileModuleLogService.saveUpdateLog(module, newModule, module.getProjectId(), userId);
}

View File

@ -9,6 +9,7 @@ import io.metersphere.project.dto.filemanagement.request.FileRepositoryCreateReq
import io.metersphere.project.dto.filemanagement.request.FileRepositoryUpdateRequest;
import io.metersphere.project.dto.filemanagement.request.RepositoryFileAddRequest;
import io.metersphere.project.dto.filemanagement.response.FileRepositoryResponse;
import io.metersphere.project.mapper.FileMetadataMapper;
import io.metersphere.project.mapper.FileMetadataRepositoryMapper;
import io.metersphere.project.mapper.FileModuleRepositoryMapper;
import io.metersphere.sdk.constants.ModuleConstants;
@ -37,6 +38,8 @@ public class FileRepositoryService extends FileModuleService {
@Resource
private FileMetadataService fileMetadataService;
@Resource
private FileMetadataMapper fileMetadataMapper;
@Resource
private FileModuleRepositoryMapper fileModuleRepositoryMapper;
@Resource
private FileMetadataRepositoryMapper fileMetadataRepositoryMapper;
@ -95,6 +98,9 @@ public class FileRepositoryService extends FileModuleService {
if (ObjectUtils.anyNull(fileModule, repository)) {
throw new MSException(Translator.get("file_repository.connect.error"));
}
FileRepositoryLog oldLog = new FileRepositoryLog(fileModule, repository);
this.connect(repository.getUrl(),
request.getToken() == null ? repository.getToken() : request.getToken(),
request.getUserName() == null ? repository.getUserName() : request.getUserName());
@ -115,8 +121,9 @@ public class FileRepositoryService extends FileModuleService {
}
fileModuleRepositoryMapper.updateByPrimaryKeySelective(repository);
}
//记录日志
fileModuleLogService.saveUpdateRepositoryLog(new FileRepositoryLog(fileModule, repository), operator);
fileModuleLogService.saveUpdateRepositoryLog(oldLog, new FileRepositoryLog(fileModule, repository), operator);
}
private void checkPlatForm(String platform) {
@ -137,9 +144,11 @@ public class FileRepositoryService extends FileModuleService {
if (fileAttachInfo == null) {
throw new MSException(Translator.get("file.not.exist"));
}
FileMetadata fileMetadata = fileMetadataService.genFileMetadata(request.getFilePath(), StorageType.GIT.name(), fileAttachInfo.getSize(), request.isEnable(),
fileModule.getProjectId(), fileModule.getId(), operator);
fileMetadata.setFileVersion(fileAttachInfo.getCommitId());
fileMetadataMapper.insert(fileMetadata);
FileMetadata fileMetadata = fileMetadataService.saveFileMetadata(
fileModule.getProjectId(), fileModule.getId(), request.getFilePath(), StorageType.GIT.name(), operator, fileAttachInfo.getSize(), request.isEnable());
FileMetadataRepository fileMetadataRepository = new FileMetadataRepository();
fileMetadataRepository.setFileMetadataId(fileMetadata.getId());
fileMetadataRepository.setBranch(fileAttachInfo.getBranch());

View File

@ -1,17 +1,19 @@
package io.metersphere.project.controller.filemanagement;
import io.metersphere.project.domain.*;
import io.metersphere.project.dto.filemanagement.FileLogRecord;
import io.metersphere.project.dto.filemanagement.request.*;
import io.metersphere.project.dto.filemanagement.response.FileAssociationResponse;
import io.metersphere.project.dto.filemanagement.response.FileInformationResponse;
import io.metersphere.project.mapper.FileAssociationMapper;
import io.metersphere.project.mapper.FileMetadataMapper;
import io.metersphere.project.mapper.FileModuleMapper;
import io.metersphere.project.service.FileAssociationService;
import io.metersphere.project.service.FileModuleService;
import io.metersphere.project.utils.FileManagementBaseUtils;
import io.metersphere.project.utils.FileManagementRequestUtils;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.util.FileAssociationSourceUtil;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.TempFileUtils;
import io.metersphere.system.base.BaseTest;
@ -32,6 +34,8 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
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.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.LinkedMultiValueMap;
@ -41,6 +45,7 @@ import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -62,6 +67,14 @@ public class FileManagementControllerTests extends BaseTest {
private static String picFileId;
private static String jarFileId;
private static String fileAssociationOldFileId;
private static String fileAssociationNewFileId;
private static String fileAssociationNewFilesOne;
private static String fileAssociationNewFilesTwo;
private static String fileAssociationNewFilesThree;
private static String fileAssociationNewFilesFour;
private static Map<String, List<String>> sourceAssociationFileMap = new HashMap<>();
@Resource
private FileModuleService fileModuleService;
@Resource
@ -569,12 +582,22 @@ public class FileManagementControllerTests extends BaseTest {
this.fileUploadTestSuccess();
}
for (String key : FILE_ID_PATH.keySet()) {
if (StringUtils.isEmpty(fileAssociationNewFilesOne)) {
fileAssociationNewFilesOne = key;
} else if (StringUtils.isEmpty(fileAssociationNewFilesTwo)) {
fileAssociationNewFilesTwo = key;
} else if (StringUtils.isEmpty(fileAssociationNewFilesThree)) {
fileAssociationNewFilesThree = key;
} else if (StringUtils.isEmpty(fileAssociationNewFilesFour)) {
fileAssociationNewFilesFour = key;
} else {
reUploadFileId = key;
}
}
FileUploadRequest fileUploadRequest = new FileUploadRequest();
fileUploadRequest.setProjectId(project.getId());
fileAssociationOldFileId = reUploadFileId;
//构建参数
FileReUploadRequest fileReUploadRequest = new FileReUploadRequest();
fileReUploadRequest.setFileId(reUploadFileId);
@ -600,6 +623,7 @@ public class FileManagementControllerTests extends BaseTest {
checkLog(reUploadId, OperationLogType.UPDATE, FileManagementRequestUtils.URL_FILE_RE_UPLOAD);
FILE_ID_PATH.put(reUploadId, filePath);
FILE_VERSIONS_ID_MAP.put(reUploadId, reUploadFileId);
fileAssociationNewFileId = reUploadId;
}
@Test
@ -922,45 +946,6 @@ public class FileManagementControllerTests extends BaseTest {
.andExpect(status().is5xxServerError());
}
@Test
@Order(20)
public void fileDeleteSuccess() throws Exception {
if (MapUtils.isEmpty(FILE_VERSIONS_ID_MAP)) {
this.fileReUploadTestSuccess();
}
FileBatchProcessRequest fileBatchProcessRequest;
//删除指定文件
for (Map.Entry<String, String> entry : FILE_VERSIONS_ID_MAP.entrySet()) {
String fileMetadataId = entry.getKey();
String refId = entry.getValue();
fileBatchProcessRequest = new FileBatchProcessRequest();
fileBatchProcessRequest.setProjectId(project.getId());
fileBatchProcessRequest.setSelectIds(new ArrayList<>() {{
this.add(fileMetadataId);
}});
this.requestPostWithOk(FileManagementRequestUtils.URL_FILE_DELETE, fileBatchProcessRequest);
this.checkFileIsDeleted(fileMetadataId, refId);
checkLog(fileMetadataId, OperationLogType.DELETE, FileManagementRequestUtils.URL_FILE_DELETE);
}
FILE_VERSIONS_ID_MAP.clear();
//全部删除
fileBatchProcessRequest = new FileBatchProcessRequest();
fileBatchProcessRequest.setSelectAll(true);
fileBatchProcessRequest.setProjectId(project.getId());
fileBatchProcessRequest.setExcludeIds(new ArrayList<>() {{
this.add(IDGenerator.nextStr());
}});
this.requestPostWithOk(FileManagementRequestUtils.URL_FILE_DELETE, fileBatchProcessRequest);
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andProjectIdEqualTo(project.getId());
Assertions.assertEquals(fileMetadataMapper.countByExample(example), 0);
//重新上传用于后续的测试
this.fileUploadTestSuccess();
}
@Test
@Order(21)
@ -1082,6 +1067,507 @@ public class FileManagementControllerTests extends BaseTest {
this.requestGet(String.format(FileManagementRequestUtils.URL_CHANGE_JAR_ENABLE, picFileId, true));
}
/*
30-80之间是测试文件关联的
*/
@Resource
private FileAssociationMapper fileAssociationMapper;
@Resource
private FileAssociationService fileAssociationService;
@Test
@Order(30)
public void fileAssociationCheckFileVersionTest() throws Exception {
if (StringUtils.isAnyEmpty(fileAssociationOldFileId, fileAssociationNewFileId, fileAssociationNewFilesOne, fileAssociationNewFilesTwo, fileAssociationNewFilesThree)) {
this.fileReUploadTestSuccess();
}
//1.没有要更新的文件
List<String> fileIdList = new ArrayList<>() {{
this.add(fileAssociationNewFileId);
this.add(fileAssociationNewFilesOne);
}};
List<String> newVersionFileIdList = fileAssociationService.checkFilesVersion(fileIdList);
Assertions.assertTrue(CollectionUtils.isEmpty(newVersionFileIdList));
//2.有要更新的文件
fileIdList = new ArrayList<>() {{
this.add(fileAssociationOldFileId);
this.add(fileAssociationNewFilesOne);
}};
newVersionFileIdList = fileAssociationService.checkFilesVersion(fileIdList);
Assertions.assertEquals(newVersionFileIdList.size(), 1);
Assertions.assertTrue(newVersionFileIdList.contains(fileAssociationOldFileId));
//3.参数为空
boolean error = false;
try {
fileAssociationService.checkFilesVersion(new ArrayList<>());
} catch (Exception e) {
error = true;
}
Assertions.assertTrue(error);
}
/**
* 文件关联的相关测试由于要注入sql拆分多个单元测试方法时无法保证sql的注入性所以合在一个方法中
*
* @throws Exception
*/
@Test
@Order(31)
@Sql(scripts = {"/dml/init_bug.sql"},
config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED),
executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
public void fileAssociationTests() throws Exception {
//检查文件版本
fileAssociationCheckFileVersionTest();
//预备手动关联缺陷过期文件. bug-id-1和bug-id-2用来后续测试更新 bug-id-3用于本轮测试
List<String> oldFileIdList = new ArrayList<>();
oldFileIdList.add(this.addFileAssociation("sty-file-association-bug-id-2", "BUG", fileAssociationOldFileId));
this.saveSourceAssociationId("sty-file-association-bug-id-2", oldFileIdList);
oldFileIdList = new ArrayList<>();
oldFileIdList.add(this.addFileAssociation("sty-file-association-bug-id-3", "BUG", fileAssociationOldFileId));
this.saveSourceAssociationId("sty-file-association-bug-id-3", oldFileIdList);
oldFileIdList = new ArrayList<>();
oldFileIdList.add(this.addFileAssociation("sty-file-association-bug-id-4", "BUG", fileAssociationOldFileId));
this.saveSourceAssociationId("sty-file-association-bug-id-4", oldFileIdList);
//文件关联
this.associationFile();
//文件更新
this.associationUpgrade();
//文件转存并关联
this.transferAndAssociation();
//文件管理页面-查询关联文件
this.fileAssociationControllerPage();
//文件管理页面-更新关联文件
this.fileAssociationControllerUpgrade();
//文件管理页面-取消关联文件
this.fileAssociationControllerDelete();
//文件取消关联
this.associationDelete();
}
@Test
@Order(40)
public void fileDeleteSuccess() throws Exception {
if (MapUtils.isEmpty(FILE_VERSIONS_ID_MAP)) {
this.fileReUploadTestSuccess();
}
FileBatchProcessRequest fileBatchProcessRequest;
//删除指定文件
for (Map.Entry<String, String> entry : FILE_VERSIONS_ID_MAP.entrySet()) {
String fileMetadataId = entry.getKey();
String refId = entry.getValue();
fileBatchProcessRequest = new FileBatchProcessRequest();
fileBatchProcessRequest.setProjectId(project.getId());
fileBatchProcessRequest.setSelectIds(new ArrayList<>() {{
this.add(fileMetadataId);
}});
this.requestPostWithOk(FileManagementRequestUtils.URL_FILE_DELETE, fileBatchProcessRequest);
this.checkFileIsDeleted(fileMetadataId, refId);
checkLog(fileMetadataId, OperationLogType.DELETE, FileManagementRequestUtils.URL_FILE_DELETE);
}
FILE_VERSIONS_ID_MAP.clear();
//全部删除
fileBatchProcessRequest = new FileBatchProcessRequest();
fileBatchProcessRequest.setSelectAll(true);
fileBatchProcessRequest.setProjectId(project.getId());
fileBatchProcessRequest.setExcludeIds(new ArrayList<>() {{
this.add(IDGenerator.nextStr());
}});
this.requestPostWithOk(FileManagementRequestUtils.URL_FILE_DELETE, fileBatchProcessRequest);
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andProjectIdEqualTo(project.getId());
Assertions.assertEquals(fileMetadataMapper.countByExample(example), 0);
//重新上传用于后续的测试
this.fileUploadTestSuccess();
}
private void fileAssociationControllerDelete() throws Exception {
//删除bug-id-4的
FileAssociationDeleteRequest deleteRequest = new FileAssociationDeleteRequest();
deleteRequest.setAssociationIds(sourceAssociationFileMap.get("sty-file-association-bug-id-4"));
deleteRequest.setProjectId(project.getId());
MvcResult mvcResult = this.requestPostWithOkAndReturn(FileManagementRequestUtils.URL_FILE_ASSOCIATION_DELETE, deleteRequest);
int returnCount = Integer.parseInt(JSON.parseObject(mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData().toString());
Assertions.assertEquals(returnCount, sourceAssociationFileMap.get("sty-file-association-bug-id-4").size());
//重复删除
mvcResult = this.requestPostWithOkAndReturn(FileManagementRequestUtils.URL_FILE_ASSOCIATION_DELETE, deleteRequest);
returnCount = Integer.parseInt(JSON.parseObject(mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData().toString());
Assertions.assertEquals(returnCount, 0);
//参数不合法
deleteRequest = new FileAssociationDeleteRequest();
deleteRequest.setProjectId(project.getId());
deleteRequest.setAssociationIds(null);
this.requestPost(FileManagementRequestUtils.URL_FILE_ASSOCIATION_DELETE, deleteRequest).andExpect(status().isBadRequest());
deleteRequest.setAssociationIds(new ArrayList<>());
this.requestPost(FileManagementRequestUtils.URL_FILE_ASSOCIATION_DELETE, deleteRequest).andExpect(status().isBadRequest());
deleteRequest = new FileAssociationDeleteRequest();
deleteRequest.setAssociationIds(sourceAssociationFileMap.get("sty-file-association-bug-id-4"));
this.requestPost(FileManagementRequestUtils.URL_FILE_ASSOCIATION_DELETE, deleteRequest).andExpect(status().isBadRequest());
FileAssociationDeleteRequest permisionRequest = new FileAssociationDeleteRequest();
permisionRequest.setAssociationIds(sourceAssociationFileMap.get("sty-file-association-bug-id-4"));
permisionRequest.setProjectId(DEFAULT_PROJECT_ID);
this.requestPostPermissionTest(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ_UPDATE, FileManagementRequestUtils.URL_FILE_ASSOCIATION_DELETE, permisionRequest);
}
private void fileAssociationControllerPage() throws Exception {
//关联过的
MvcResult mvcResult = this.requestGetWithOkAndReturn(String.format(FileManagementRequestUtils.URL_FILE_ASSOCIATION_LIST, fileAssociationNewFilesOne));
List<FileAssociationResponse> fileAssociationResponseList = JSON.parseArray(JSON.toJSONString(JSON.parseObject(mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData()), FileAssociationResponse.class);
Assertions.assertEquals(fileAssociationResponseList.size(), 2);//这个文件只关联了id-2和id-3
//没有关联过
mvcResult = this.requestGetWithOkAndReturn(String.format(FileManagementRequestUtils.URL_FILE_ASSOCIATION_LIST, fileAssociationNewFilesFour));
fileAssociationResponseList = JSON.parseArray(JSON.toJSONString(JSON.parseObject(mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData()), FileAssociationResponse.class);
Assertions.assertEquals(fileAssociationResponseList.size(), 0);
//数据不存在
this.requestGet(String.format(FileManagementRequestUtils.URL_FILE_ASSOCIATION_LIST, "sty-file-association-bug-id-0")).andExpect(status().is5xxServerError());
this.requestGetPermissionTest(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ, String.format(FileManagementRequestUtils.URL_FILE_ASSOCIATION_LIST, fileAssociationNewFilesOne));
}
private void fileAssociationControllerUpgrade() throws Exception {
String associationId = sourceAssociationFileMap.get("sty-file-association-bug-id-2").get(0);
MvcResult mvcResult = this.requestGetWithOkAndReturn(String.format(FileManagementRequestUtils.URL_FILE_ASSOCIATION_UPGRADE, project.getId(), associationId));
String fileId = JSON.parseObject(mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData().toString();
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andIdEqualTo(fileId).andLatestEqualTo(true);
Assertions.assertEquals(fileMetadataMapper.countByExample(example), 1);
//重复更新
mvcResult = this.requestGetWithOkAndReturn(String.format(FileManagementRequestUtils.URL_FILE_ASSOCIATION_UPGRADE, project.getId(), associationId));
String newFileId = JSON.parseObject(mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData().toString();
Assertions.assertEquals(newFileId, fileId);
this.requestGetPermissionTest(PermissionConstants.PROJECT_FILE_MANAGEMENT_READ_UPDATE, String.format(FileManagementRequestUtils.URL_FILE_ASSOCIATION_UPGRADE, DEFAULT_PROJECT_ID, associationId));
}
private void saveSourceAssociationId(String sourceId, List<String> associationIdList) {
if (CollectionUtils.isEmpty(associationIdList)) {
return;
}
if (sourceAssociationFileMap.containsKey(sourceId)) {
sourceAssociationFileMap.get(sourceId).addAll(associationIdList);
sourceAssociationFileMap.get(sourceId).stream().distinct().collect(Collectors.toList());
} else {
sourceAssociationFileMap.put(sourceId, associationIdList);
}
}
public void associationFile() {
//关联id-1和id-3 不覆盖
List<String> fileIdList = new ArrayList<>() {{
this.add(fileAssociationNewFileId);
this.add(fileAssociationNewFilesOne);
}};
FileLogRecord fileLogRecord = FileLogRecord.builder()
.logModule(OperationLogModule.PROJECT_FILE_MANAGEMENT)
.requestMethod(HttpMethodConstants.POST.name())
.requestUrl("/project/file/association/test")
.operator("admin")
.projectId(project.getId())
.build();
List<String> associationIdList = fileAssociationService.association("sty-file-association-bug-id-3", FileAssociationSourceUtil.SOURCE_TYPE_BUG, fileIdList, false, fileLogRecord);
this.checkFileAssociation("sty-file-association-bug-id-3", fileIdList, new ArrayList<>() {{
this.add(fileAssociationOldFileId);
}});
this.saveSourceAssociationId("sty-file-association-bug-id-3", associationIdList);
//关联id-3 覆盖
associationIdList = fileAssociationService.association("sty-file-association-bug-id-3", FileAssociationSourceUtil.SOURCE_TYPE_BUG, fileIdList, true, fileLogRecord);
this.checkFileAssociation("sty-file-association-bug-id-3", fileIdList, null);
this.saveSourceAssociationId("sty-file-association-bug-id-3", associationIdList);
//重新关联检查是否会重复插入数据
associationIdList = fileAssociationService.association("sty-file-association-bug-id-3", FileAssociationSourceUtil.SOURCE_TYPE_BUG, fileIdList, true, fileLogRecord);
this.checkFileAssociation("sty-file-association-bug-id-3", fileIdList, null);
this.saveSourceAssociationId("sty-file-association-bug-id-3", associationIdList);
//关联id-2 关联所有正常文件
List<String> bug2IdList = new ArrayList<>() {{
this.add(fileAssociationNewFilesOne);
this.add(fileAssociationNewFilesTwo);
this.add(fileAssociationNewFilesThree);
}};
associationIdList = fileAssociationService.association("sty-file-association-bug-id-2", FileAssociationSourceUtil.SOURCE_TYPE_BUG, bug2IdList, true, fileLogRecord);
this.checkFileAssociation("sty-file-association-bug-id-2", bug2IdList, null);
this.saveSourceAssociationId("sty-file-association-bug-id-2", associationIdList);
//反例
// 文件参数为空
boolean error = false;
try {
fileAssociationService.association("sty-file-association-bug-id-3", FileAssociationSourceUtil.SOURCE_TYPE_BUG, null, true, fileLogRecord);
} catch (Exception e) {
error = true;
}
Assertions.assertTrue(error);
error = false;
try {
fileAssociationService.association("sty-file-association-bug-id-3", FileAssociationSourceUtil.SOURCE_TYPE_BUG, new ArrayList<>(), true, fileLogRecord);
} catch (Exception e) {
error = true;
}
Assertions.assertTrue(error);
//资源不存在
error = false;
try {
fileAssociationService.association("sty-file-association-bug-id-3", FileAssociationSourceUtil.SOURCE_TYPE_BUG, new ArrayList<>() {{
}}, true, fileLogRecord);
} catch (Exception e) {
error = true;
}
Assertions.assertTrue(error);
//文件数量对不上含有不存在的文件ID
error = false;
fileIdList.add(IDGenerator.nextStr());
try {
fileAssociationService.association("sty-file-association-bug-id-3", FileAssociationSourceUtil.SOURCE_TYPE_BUG, fileIdList, true, fileLogRecord);
} catch (Exception e) {
error = true;
}
Assertions.assertTrue(error);
}
public void associationUpgrade() {
FileLogRecord fileLogRecord = FileLogRecord.builder()
.logModule(OperationLogModule.PROJECT_FILE_MANAGEMENT)
.requestMethod(HttpMethodConstants.POST.name())
.requestUrl("/project/file/association/upgrade-test")
.operator("admin")
.projectId(project.getId())
.build();
FileAssociationExample example = new FileAssociationExample();
example.createCriteria().andFileIdEqualTo(fileAssociationOldFileId).andSourceIdEqualTo("sty-file-association-bug-id-4");
FileAssociation upgradeFileAssociation = fileAssociationMapper.selectByExample(example).get(0);
Assertions.assertTrue(sourceAssociationFileMap.get("sty-file-association-bug-id-4").contains(upgradeFileAssociation.getId()));
//当前文件不是最新的
fileAssociationService.upgrade(upgradeFileAssociation.getId(), fileLogRecord);
FileAssociation newAssociation1 = fileAssociationMapper.selectByPrimaryKey(upgradeFileAssociation.getId());
Assertions.assertEquals(newAssociation1.getFileId(), fileAssociationNewFileId);
//当前文件是最新的
fileAssociationService.upgrade(upgradeFileAssociation.getId(), fileLogRecord);
FileAssociation newAssociation2 = fileAssociationMapper.selectByPrimaryKey(upgradeFileAssociation.getId());
Assertions.assertEquals(newAssociation1.getFileId(), newAssociation2.getFileId());
//关联ID不存在
boolean error = false;
try {
fileAssociationService.upgrade(IDGenerator.nextStr(), fileLogRecord);
} catch (Exception e) {
error = true;
}
Assertions.assertTrue(error);
//使用bug-id-2测试 1.关联表中的文件ID不存在
example.clear();
example.createCriteria().andFileIdEqualTo(fileAssociationOldFileId).andSourceIdEqualTo("sty-file-association-bug-id-2");
FileAssociation upgrade2 = fileAssociationMapper.selectByExample(example).get(0);
//先把文件id改成别的测试完成改回来
String originalFileId = upgrade2.getFileId();
String originalRefId = upgrade2.getFileRefId();
upgrade2.setFileId(IDGenerator.nextStr());
upgrade2.setFileRefId(upgrade2.getFileId());
fileAssociationMapper.updateByPrimaryKeySelective(upgrade2);
error = false;
try {
fileAssociationService.upgrade(upgrade2.getId(), fileLogRecord);
} catch (Exception e) {
error = true;
}
Assertions.assertTrue(error);
upgrade2.setFileId(originalFileId);
upgrade2.setFileRefId(originalRefId);
fileAssociationMapper.updateByPrimaryKeySelective(upgrade2);
//使用bug-id-2测试 脏数据->refId找不到最新文件
FileMetadata updateMetadata = new FileMetadata();
updateMetadata.setId(fileAssociationNewFileId);
updateMetadata.setLatest(false);
fileMetadataMapper.updateByPrimaryKeySelective(updateMetadata);
error = false;
try {
fileAssociationService.upgrade(upgrade2.getId(), fileLogRecord);
} catch (Exception e) {
error = true;
}
Assertions.assertTrue(error);
updateMetadata.setLatest(true);
fileMetadataMapper.updateByPrimaryKeySelective(updateMetadata);
}
public void transferAndAssociation() throws Exception {
FileLogRecord fileLogRecord = FileLogRecord.builder()
.logModule(OperationLogModule.PROJECT_FILE_MANAGEMENT)
.requestMethod(HttpMethodConstants.POST.name())
.requestUrl("/project/file/association/transferAndAssociation-test")
.operator("admin")
.projectId(project.getId())
.build();
//关联正常文件
String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/file_upload.JPG")).getPath();
String fileID = fileAssociationService.transferAndAssociation("testTransferFile.jpg", TempFileUtils.getFile(filePath), "sty-file-association-bug-id-4", FileAssociationSourceUtil.SOURCE_TYPE_BUG, fileLogRecord);
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andIdEqualTo(fileID).andNameEqualTo("testTransferFile").andTypeEqualTo("JPG");
Assertions.assertEquals(fileMetadataMapper.countByExample(example), 1);
//重复转存检查文件名是否加1
fileID = fileAssociationService.transferAndAssociation("testTransferFile.jpg", TempFileUtils.getFile(filePath), "sty-file-association-bug-id-4", FileAssociationSourceUtil.SOURCE_TYPE_BUG, fileLogRecord);
example.clear();
example.createCriteria().andIdEqualTo(fileID).andNameEqualTo("testTransferFile(1)").andTypeEqualTo("JPG");
Assertions.assertEquals(fileMetadataMapper.countByExample(example), 1);
//重复转存检查文件名是否加1
fileID = fileAssociationService.transferAndAssociation("testTransferFile.jpg", TempFileUtils.getFile(filePath), "sty-file-association-bug-id-4", FileAssociationSourceUtil.SOURCE_TYPE_BUG, fileLogRecord);
example.clear();
example.createCriteria().andIdEqualTo(fileID).andNameEqualTo("testTransferFile(2)").andTypeEqualTo("JPG");
Assertions.assertEquals(fileMetadataMapper.countByExample(example), 1);
//测试没有后缀的文件名
fileID = fileAssociationService.transferAndAssociation("testTransfer", TempFileUtils.getFile(filePath), "sty-file-association-bug-id-4", FileAssociationSourceUtil.SOURCE_TYPE_BUG, fileLogRecord);
example.clear();
example.createCriteria().andIdEqualTo(fileID).andNameEqualTo("testTransfer");
Assertions.assertEquals(fileMetadataMapper.countByExample(example), 1);
//资源不存在
boolean error = false;
try {
fileAssociationService.transferAndAssociation("testTransferFile.jpg", TempFileUtils.getFile(filePath), IDGenerator.nextStr(), FileAssociationSourceUtil.SOURCE_TYPE_BUG, fileLogRecord);
} catch (Exception e) {
error = true;
}
Assertions.assertTrue(error);
//文件名称不合法
error = false;
try {
fileAssociationService.transferAndAssociation("testTransfer/File.jpg", TempFileUtils.getFile(filePath), IDGenerator.nextStr(), FileAssociationSourceUtil.SOURCE_TYPE_BUG, fileLogRecord);
} catch (Exception e) {
error = true;
}
Assertions.assertTrue(error);
}
public void associationDelete() {
FileLogRecord fileLogRecord = FileLogRecord.builder()
.logModule(OperationLogModule.PROJECT_FILE_MANAGEMENT)
.requestMethod(HttpMethodConstants.POST.name())
.requestUrl("/project/file/association/delete-test")
.operator("admin")
.projectId(project.getId())
.build();
//1.正常删除 资源为bug-1
FileAssociationExample example = new FileAssociationExample();
example.clear();
example.createCriteria().andSourceIdEqualTo("sty-file-association-bug-id-1");
List<FileAssociation> bug1AssociationList = fileAssociationMapper.selectByExample(example);
List<String> idList = bug1AssociationList.stream().map(FileAssociation::getId).collect(Collectors.toList());
int deleteCount = fileAssociationService.deleteBySourceId(idList, fileLogRecord);
Assertions.assertEquals(idList.size(), deleteCount);
//2.入参集合为空
deleteCount = fileAssociationService.deleteBySourceId(new ArrayList<>(), fileLogRecord);
Assertions.assertEquals(0, deleteCount);
deleteCount = fileAssociationService.deleteBySourceId(null, fileLogRecord);
Assertions.assertEquals(0, deleteCount);
//3.里面包含一条已经文件已经删除了的ID 资源为bug-2
example.clear();
example.createCriteria().andSourceIdEqualTo("sty-file-association-bug-id-2").andFileIdEqualTo(fileAssociationNewFilesThree);
FileAssociation association = fileAssociationMapper.selectByExample(example).get(0);
//先把文件id改成别的测试完成改回来
association.setFileId(IDGenerator.nextStr());
association.setFileRefId(association.getFileId());
fileAssociationMapper.updateByPrimaryKeySelective(association);
idList = new ArrayList<>() {{
this.add(association.getId());
}};
fileAssociationService.deleteBySourceId(idList, fileLogRecord);
//4.入参集合包括1条不存在的关联ID 资源为bug-2
example.clear();
example.createCriteria().andSourceIdEqualTo("sty-file-association-bug-id-2");
List<FileAssociation> bug2AssociationList = fileAssociationMapper.selectByExample(example);
idList = bug2AssociationList.stream().map(FileAssociation::getId).collect(Collectors.toList());
idList.add(IDGenerator.nextStr());
deleteCount = fileAssociationService.deleteBySourceId(idList, fileLogRecord);
Assertions.assertEquals(idList.size() - 1, deleteCount);
}
/**
* @param sourceId 资源ID
* @param fileIdList 关联的文件集合
* @param oldFileIds 检查关联的文件中是否有过期文件
*/
private void checkFileAssociation(String sourceId, List<String> fileIdList, List<String> oldFileIds) {
FileAssociationExample example = new FileAssociationExample();
example.createCriteria().andSourceIdEqualTo(sourceId).andFileIdIn(fileIdList);
long count = fileAssociationMapper.countByExample(example);
if (CollectionUtils.isEmpty(oldFileIds)) {
Assertions.assertEquals(count, fileIdList.size());
} else {
Assertions.assertEquals(count, fileIdList.size() - oldFileIds.size());
example.clear();
example.createCriteria().andSourceIdEqualTo(sourceId).andFileIdIn(oldFileIds);
Assertions.assertEquals(fileAssociationMapper.countByExample(example), oldFileIds.size());
}
}
private String addFileAssociation(String sourceId, String sourceType, String fileId) {
FileAssociation fileAssociation = new FileAssociation();
fileAssociation.setId(IDGenerator.nextStr());
fileAssociation.setFileId(fileId);
fileAssociation.setFileRefId(fileId);
fileAssociation.setSourceId(sourceId);
fileAssociation.setSourceType(sourceType);
fileAssociation.setCreateTime(System.currentTimeMillis());
fileAssociation.setCreateUser("admin");
fileAssociation.setUpdateTime(System.currentTimeMillis());
fileAssociation.setUpdateUser("admin");
fileAssociation.setFileVersion(fileId);
fileAssociationMapper.insert(fileAssociation);
return fileAssociation.getId();
}
@Test
@Order(31)
public void upgradeFileAssociationTest() throws Exception {
//预备在fileAssociationTest中已经将sty-file-association-bug-id-1的文件关联过过期文件
//1.首先测试反例手动修改过期文件所有的版本latest都为false测试之后改过来
//2.升级id-3的
//3.升级id-1的
//反例
//文件不存在
}
@Test
@Order(32)
public void deleteFileAssociationTest() throws Exception {
//删除id-1和id-2的
//参数校验空集合
//集合里有个假文件id
//id-3里关联的文件数据里删除1条源文件数据
}
@Test
@Order(40)
@Sql(scripts = {"/dml/delete_bug.sql"},
config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED),
executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void fileAssociationTestOver() throws Exception {
}
/*
80以后是文件模块的移动和删除
*/
@Test
@Order(80)
public void moveTest() throws Exception {

View File

@ -41,8 +41,8 @@ public class FileManagementRequestUtils {
//文件批量移动权限判断需要提前上传文件所以放在了主测试类里
public static final String URL_FILE_BATCH_UPDATE = "/project/file/batch-move";
/**
* 存储库相关路径
/*
存储库相关路径
*/
//存储库列表
public static final String URL_FILE_REPOSITORY_LIST = "/project/file/repository/list/%s";
@ -59,4 +59,13 @@ public class FileManagementRequestUtils {
//添加文件
public static final String URL_FILE_REPOSITORY_FILE_ADD = "/project/file/repository/add-file";
public static final String URL_FILE_REPOSITORY_FILE_PULL = "/project/file/repository/pull-file/%s";
/*
文件关联
*/
public static final String URL_FILE_ASSOCIATION_LIST = "/project/file/association/list/%s";
public static final String URL_FILE_ASSOCIATION_UPGRADE = "/project/file/association/upgrade/%s/%s";
public static final String URL_FILE_ASSOCIATION_DELETE = "/project/file/association/delete";
}

View File

@ -0,0 +1,4 @@
DELETE
from bug
WHERE id IN ('sty-file-association-bug-id-1', 'sty-file-association-bug-id-2', 'sty-file-association-bug-id-3',
'sty-file-association-bug-id-4');

View File

@ -0,0 +1,34 @@
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, tag,
platform_bug_id, deleted)
VALUES ('sty-file-association-bug-id-1', 100000, 'sty-default-bug-1', 'oasis', 'oasis', 'admin',
UNIX_TIMESTAMP() * 1000,
'admin',
UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id',
'Local', 'open', 'default-tag', null, 0);
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, tag,
platform_bug_id, deleted)
VALUES ('sty-file-association-bug-id-2', 100001, 'sty-default-bug-2', 'oasis', 'oasis', 'admin',
UNIX_TIMESTAMP() * 1000,
'admin',
UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id',
'Local', 'open', 'default-tag', null, 0);
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, tag,
platform_bug_id, deleted)
VALUES ('sty-file-association-bug-id-3', 100002, 'sty-default-bug-3', 'oasis', 'oasis', 'admin',
UNIX_TIMESTAMP() * 1000,
'admin',
UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id',
'Local', 'open', 'default-tag', null, 0);
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, tag,
platform_bug_id, deleted)
VALUES ('sty-file-association-bug-id-4', 100003, 'sty-default-bug-4', 'oasis', 'oasis', 'admin',
UNIX_TIMESTAMP() * 1000,
'admin',
UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id',
'Local', 'open', 'default-tag', null, 0);