feat(接口测试): 接口管理模块接口定义变更记录处理

This commit is contained in:
lan-yonghui 2024-01-09 20:15:17 +08:00 committed by Craftsman
parent e72c5126f2
commit 40a4e3b72d
21 changed files with 642 additions and 51 deletions

View File

@ -36,6 +36,9 @@ public class OperationHistory implements Serializable {
@Schema(description = "操作模块/api/case/scenario/ui")
private String module;
@Schema(description = "关联id关联变更记录id来源")
private Long refId;
private static final long serialVersionUID = 1L;
public enum Column {
@ -45,7 +48,8 @@ public class OperationHistory implements Serializable {
createUser("create_user", "createUser", "VARCHAR", false),
sourceId("source_id", "sourceId", "VARCHAR", false),
type("type", "type", "VARCHAR", true),
module("module", "module", "VARCHAR", true);
module("module", "module", "VARCHAR", true),
refId("ref_id", "refId", "BIGINT", false);
private static final String BEGINNING_DELIMITER = "`";

View File

@ -573,6 +573,66 @@ public class OperationHistoryExample {
addCriterion("`module` not between", value1, value2, "module");
return (Criteria) this;
}
public Criteria andRefIdIsNull() {
addCriterion("ref_id is null");
return (Criteria) this;
}
public Criteria andRefIdIsNotNull() {
addCriterion("ref_id is not null");
return (Criteria) this;
}
public Criteria andRefIdEqualTo(Long value) {
addCriterion("ref_id =", value, "refId");
return (Criteria) this;
}
public Criteria andRefIdNotEqualTo(Long value) {
addCriterion("ref_id <>", value, "refId");
return (Criteria) this;
}
public Criteria andRefIdGreaterThan(Long value) {
addCriterion("ref_id >", value, "refId");
return (Criteria) this;
}
public Criteria andRefIdGreaterThanOrEqualTo(Long value) {
addCriterion("ref_id >=", value, "refId");
return (Criteria) this;
}
public Criteria andRefIdLessThan(Long value) {
addCriterion("ref_id <", value, "refId");
return (Criteria) this;
}
public Criteria andRefIdLessThanOrEqualTo(Long value) {
addCriterion("ref_id <=", value, "refId");
return (Criteria) this;
}
public Criteria andRefIdIn(List<Long> values) {
addCriterion("ref_id in", values, "refId");
return (Criteria) this;
}
public Criteria andRefIdNotIn(List<Long> values) {
addCriterion("ref_id not in", values, "refId");
return (Criteria) this;
}
public Criteria andRefIdBetween(Long value1, Long value2) {
addCriterion("ref_id between", value1, value2, "refId");
return (Criteria) this;
}
public Criteria andRefIdNotBetween(Long value1, Long value2) {
addCriterion("ref_id not between", value1, value2, "refId");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -9,6 +9,7 @@
<result column="source_id" jdbcType="VARCHAR" property="sourceId" />
<result column="type" jdbcType="VARCHAR" property="type" />
<result column="module" jdbcType="VARCHAR" property="module" />
<result column="ref_id" jdbcType="BIGINT" property="refId" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -69,7 +70,7 @@
</where>
</sql>
<sql id="Base_Column_List">
id, project_id, create_time, create_user, source_id, `type`, `module`
id, project_id, create_time, create_user, source_id, `type`, `module`, ref_id
</sql>
<select id="selectByExample" parameterType="io.metersphere.system.domain.OperationHistoryExample" resultMap="BaseResultMap">
select
@ -104,10 +105,10 @@
<insert id="insert" parameterType="io.metersphere.system.domain.OperationHistory">
insert into operation_history (id, project_id, create_time,
create_user, source_id, `type`,
`module`)
`module`, ref_id)
values (#{id,jdbcType=BIGINT}, #{projectId,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT},
#{createUser,jdbcType=VARCHAR}, #{sourceId,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
#{module,jdbcType=VARCHAR})
#{module,jdbcType=VARCHAR}, #{refId,jdbcType=BIGINT})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.system.domain.OperationHistory">
insert into operation_history
@ -133,6 +134,9 @@
<if test="module != null">
`module`,
</if>
<if test="refId != null">
ref_id,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -156,6 +160,9 @@
<if test="module != null">
#{module,jdbcType=VARCHAR},
</if>
<if test="refId != null">
#{refId,jdbcType=BIGINT},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.system.domain.OperationHistoryExample" resultType="java.lang.Long">
@ -188,6 +195,9 @@
<if test="record.module != null">
`module` = #{record.module,jdbcType=VARCHAR},
</if>
<if test="record.refId != null">
ref_id = #{record.refId,jdbcType=BIGINT},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -201,7 +211,8 @@
create_user = #{record.createUser,jdbcType=VARCHAR},
source_id = #{record.sourceId,jdbcType=VARCHAR},
`type` = #{record.type,jdbcType=VARCHAR},
`module` = #{record.module,jdbcType=VARCHAR}
`module` = #{record.module,jdbcType=VARCHAR},
ref_id = #{record.refId,jdbcType=BIGINT}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -227,6 +238,9 @@
<if test="module != null">
`module` = #{module,jdbcType=VARCHAR},
</if>
<if test="refId != null">
ref_id = #{refId,jdbcType=BIGINT},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
@ -237,17 +251,18 @@
create_user = #{createUser,jdbcType=VARCHAR},
source_id = #{sourceId,jdbcType=VARCHAR},
`type` = #{type,jdbcType=VARCHAR},
`module` = #{module,jdbcType=VARCHAR}
`module` = #{module,jdbcType=VARCHAR},
ref_id = #{refId,jdbcType=BIGINT}
where id = #{id,jdbcType=BIGINT}
</update>
<insert id="batchInsert" parameterType="map">
insert into operation_history
(id, project_id, create_time, create_user, source_id, `type`, `module`)
(id, project_id, create_time, create_user, source_id, `type`, `module`, ref_id)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=BIGINT}, #{item.projectId,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT},
#{item.createUser,jdbcType=VARCHAR}, #{item.sourceId,jdbcType=VARCHAR}, #{item.type,jdbcType=VARCHAR},
#{item.module,jdbcType=VARCHAR})
#{item.module,jdbcType=VARCHAR}, #{item.refId,jdbcType=BIGINT})
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -281,6 +296,9 @@
<if test="'module'.toString() == column.value">
#{item.module,jdbcType=VARCHAR}
</if>
<if test="'ref_id'.toString() == column.value">
#{item.refId,jdbcType=BIGINT}
</if>
</foreach>
)
</foreach>

View File

@ -134,15 +134,15 @@ CREATE TABLE IF NOT EXISTS worker_node
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT = 'DB WorkerID Assigner for UID Generator';
CREATE TABLE operation_history
(
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键' ,
CREATE TABLE IF NOT EXISTS operation_history(
`id` BIGINT(50) NOT NULL AUTO_INCREMENT COMMENT '主键' ,
`project_id` VARCHAR(50) NOT NULL DEFAULT 'NONE' COMMENT '项目id' ,
`create_time` BIGINT NOT NULL COMMENT '操作时间' ,
`create_user` VARCHAR(50) COMMENT '操作人' ,
`source_id` VARCHAR(50) COMMENT '资源id' ,
`type` VARCHAR(20) NOT NULL COMMENT '操作类型/add/update/delete' ,
`module` VARCHAR(50) COMMENT '操作模块/api/case/scenario/ui' ,
`ref_id` BIGINT(50) COMMENT '关联id关联变更记录id来源' ,
PRIMARY KEY (id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
@ -154,9 +154,6 @@ CREATE INDEX idx_module ON operation_history (`module`);
CREATE INDEX idx_project_id ON operation_history (`project_id`);
CREATE INDEX idx_type ON operation_history (`type`);
-- set innodb lock wait timeout to default
SET SESSION innodb_lock_wait_timeout = DEFAULT;
CREATE TABLE IF NOT EXISTS share_info
(
`id` VARCHAR(50) NOT NULL COMMENT '分享ID' ,
@ -170,3 +167,6 @@ CREATE TABLE IF NOT EXISTS share_info
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT = '分享';
-- set innodb lock wait timeout to default
SET SESSION innodb_lock_wait_timeout = DEFAULT;

View File

@ -299,6 +299,15 @@ user_local_config.type.not_blank=用户本地配置类型不能为空
user_local_config.type.length_range=用户本地配置类型长度必须在{min}和{max}之间
current_user_local_config_not_validate=当前用户本地配置不合法
# operation_history
operation_history.id.not_blank=变更记录 ID 不能为空
operation_history.project_id.not_blank=变更记录项目 ID 不能为空
operation_history.project_id.length_range=变更记录项目 ID 长度必须在{min}和{max}之间
operation_history.type.not_blank=变更记录操作类型不能为空
operation_history.type.length_range=变更记录操作类型长度必须在{min}和{max}之间
operation_history.source_id.not_blank=变更记录资源 ID 不能为空
operation_history.version_id.not_blank=变更记录版本 ID 不能为空
operation_history.version_id.length_range=变更记录版本 ID 长度必须在{min}和{max}之间

View File

@ -301,5 +301,13 @@ user_local_config.type.not_blank=type cannot be empty
user_local_config.type.length_range=type length must be between {min} and {max}
current_user_local_config_not_validate=Current user local config not validate
# operation_history
operation_history.id.not_blank=Operating history id cannot be empty
operation_history.project_id.not_blank=Operating history project id cannot be empty
operation_history.project_id.length_range=Operating history project id length must be between {min} and {max}
operation_history.type.not_blank=Operating history type cannot be empty
operation_history.type.length_range=Operating history type length must be between {min} and {max}
operation_history.source_id.not_blank=Operating history source id cannot be empty
operation_history.version_id.not_blank=Operating history version id cannot be empty
operation_history.version_id.length_range=Operating history version id length must be between {min} and {max}

View File

@ -300,3 +300,13 @@ user_local_config.user_url.length_range=用户本地配置用户URL长度必须
user_local_config.type.not_blank=用户本地配置类型不能为空
user_local_config.type.length_range=用户本地配置类型长度必须在{min}和{max}之间
current_user_local_config_not_validate=当前用户本地配置不合法
# operation_history
operation_history.id.not_blank=变更记录 ID 不能为空
operation_history.project_id.not_blank=变更记录项目 ID 不能为空
operation_history.project_id.length_range=变更记录项目 ID 长度必须在{min}和{max}之间
operation_history.type.not_blank=变更记录操作类型不能为空
operation_history.type.length_range=变更记录操作类型长度必须在{min}和{max}之间
operation_history.source_id.not_blank=变更记录资源 ID 不能为空
operation_history.version_id.not_blank=变更记录版本 ID 不能为空
operation_history.version_id.length_range=变更记录版本 ID 长度必须在{min}和{max}之间

View File

@ -299,3 +299,13 @@ user_local_config.user_url.length_range=用户本地配置用户URL長度必須
user_local_config.type.not_blank=用户本地配置类型不能為空
user_local_config.type.length_range=用户本地配置类型長度必須在{min}和{max}之间
current_user_local_config_not_validate=当前用户本地配置不合法
# operation_history
operation_history.id.not_blank=變更記錄 ID 不能為空
operation_history.project_id.not_blank=變更記錄項目 ID 不能為空
operation_history.project_id.length_range=變更記錄項目 ID 長度必須在{min}和{max}之間
operation_history.type.not_blank=變更記錄操作類型不能為空
operation_history.type.length_range=變更記錄操作類型長度必須在{min}和{max}之間
operation_history.source_id.not_blank=變更記錄資源 ID 不能為空
operation_history.version_id.not_blank=變更記錄版本 ID 不能為空
operation_history.version_id.length_range=變更記錄版本 ID 長度必須在{min}和{max}之間

View File

@ -9,6 +9,9 @@ import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.service.definition.ApiDefinitionLogService;
import io.metersphere.api.service.definition.ApiDefinitionService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.request.OperationHistoryVersionRequest;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.security.CheckOwner;
@ -202,4 +205,31 @@ public class ApiDefinitionController {
return apiDefinitionService.apiTestImport(file, request);
}
@PostMapping("/operation-history")
@Operation(summary = "接口测试-接口管理-接口变更历史")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ)
@CheckOwner(resourceId = "#request.getSourceId()", resourceType = "api_definition")
public Pager<List<OperationHistoryDTO>> operationHistoryList(@Validated @RequestBody OperationHistoryRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc");
return PageUtils.setPageInfo(page, apiDefinitionService.list(request));
}
@PostMapping("/operation-history/recover")
@Operation(summary = "接口测试-接口管理-接口变更历史恢复")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE)
@CheckOwner(resourceId = "#request.getId()", resourceType = "operation_history")
public void operationHistoryRecover(@Validated @RequestBody OperationHistoryVersionRequest request) {
apiDefinitionService.recoverOperationHistory(request);
}
@PostMapping("/operation-history/save")
@Operation(summary = "接口测试-接口管理-另存变更历史为指定版本")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE)
@CheckOwner(resourceId = "#request.getId()", resourceType = "operation_history")
public void saveOperationHistory(@Validated @RequestBody OperationHistoryVersionRequest request) {
apiDefinitionService.saveOperationHistory(request);
}
}

View File

@ -20,12 +20,15 @@ public class ApiDefinitionVersionDTO implements Serializable {
@Schema(description = "接口ID")
private String id;
@Schema(description = "名称")
@Schema(description = "接口名称")
private String name;
@Schema(description = "版本id")
private String versionId;
@Schema(description = "版本name")
private String versionName;
@Schema(description = "版本引用id")
private String refId;

View File

@ -3,6 +3,7 @@ package io.metersphere.api.mapper;
import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.importdto.ApiDefinitionImportDTO;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.metersphere.system.dto.table.TableBatchProcessDTO;
import org.apache.ibatis.annotations.Param;
@ -46,4 +47,8 @@ public interface ExtApiDefinitionMapper {
List<String> selectIdsByIdsAndDeleted(@Param("ids") List<String> ids, @Param("deleted") boolean deleted);
List<String> selectByProjectId(@Param("projectId") String projectId);
List<OptionDTO> selectVersionOptionByIds(@Param("apiIds") List<String> apiIds);
ApiDefinition selectApiDefinitionByVersion(@Param("refId") String refId, @Param("versionId") String versionId);
}

View File

@ -118,13 +118,15 @@
</select>
<select id="getApiDefinitionByRefId" resultType="io.metersphere.api.dto.definition.ApiDefinitionVersionDTO">
SELECT id,
`name`,
version_id,
ref_id,
project_id
FROM api_definition
WHERE ref_id = #{refId}
SELECT api_definition.id,
api_definition.`name`,
api_definition.version_id,
api_definition.ref_id,
api_definition.project_id,
project_version.name as version_name
from api_definition
LEFT JOIN project_version ON project_version.id = api_definition.version_id
where api_definition.ref_id = #{refId}
</select>
<select id="getTagsByIds" resultMap="BaseResultMap">
@ -470,4 +472,26 @@
</include>
</sql>
<select id="selectVersionOptionByIds" resultType="io.metersphere.system.dto.sdk.OptionDTO">
select
api_definition.id, project_version.name as name
from api_definition
LEFT JOIN project_version ON project_version.id = api_definition.version_id
where api_definition.ref_id in
<foreach collection="apiIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<select id="selectApiDefinitionByVersion" resultType="io.metersphere.api.domain.ApiDefinition">
select
api_definition.id, api_definition.`name`, api_definition.protocol, api_definition.`method`,
api_definition.`path`, api_definition.`status`, api_definition.num, api_definition.tags, api_definition.pos,
api_definition.project_id, api_definition.module_id, api_definition.latest, api_definition.version_id,
api_definition.ref_id, api_definition.description, api_definition.create_time, api_definition.create_user,
api_definition.update_time, api_definition.update_user, api_definition.delete_user, api_definition.delete_time,
api_definition.deleted
from api_definition
where api_definition.ref_id = #{refId} and api_definition.version_id = #{versionId}
</select>
</mapper>

View File

@ -59,7 +59,7 @@ public class ApiDefinitionLogService {
OperationLogType.ADD.name(),
OperationLogModule.API_DEFINITION,
request.getName());
dto.setHistory(true);
dto.setHistory(false);
dto.setPath("/api/definition/add");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(request));
@ -109,7 +109,7 @@ public class ApiDefinitionLogService {
OperationLogType.DELETE.name(),
OperationLogModule.API_DEFINITION,
apiDefinition.getName());
dto.setHistory(true);
dto.setHistory(false);
dto.setPath("/api/definition/delete");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(apiDefinition));
@ -125,7 +125,7 @@ public class ApiDefinitionLogService {
* @return
*/
public void batchDelLog(List<String> ids, String userId, String projectId) {
saveBatchLog(projectId, ids, "/api/definition/batch-del", userId, OperationLogType.DELETE.name(), true);
saveBatchLog(projectId, ids, "/api/definition/batch-del", userId, OperationLogType.DELETE.name(), false);
}
/**
@ -148,7 +148,7 @@ public class ApiDefinitionLogService {
OperationLogType.UPDATE.name(),
OperationLogModule.API_DEFINITION,
apiDefinition.getName());
dto.setHistory(true);
dto.setHistory(false);
dto.setPath("/api/definition/copy");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(apiDefinition));
@ -158,7 +158,7 @@ public class ApiDefinitionLogService {
}
public void batchMoveLog(List<String> ids, String userId, String projectId) {
saveBatchLog(projectId, ids, "/api/definition/batch-move", userId, OperationLogType.UPDATE.name(), true);
saveBatchLog(projectId, ids, "/api/definition/batch-move", userId, OperationLogType.UPDATE.name(), false);
}
public LogDTO followLog(String id) {
@ -199,7 +199,7 @@ public class ApiDefinitionLogService {
OperationLogType.RECOVER.name(),
OperationLogModule.API_DEFINITION,
apiDefinition.getName());
dto.setHistory(true);
dto.setHistory(false);
dto.setPath("/api/definition/recover");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(apiDefinition));
@ -216,7 +216,7 @@ public class ApiDefinitionLogService {
* @return
*/
public void batchRecoverLog(List<String> ids, String userId, String projectId) {
saveBatchLog(projectId, ids, "/api/definition/batch-recover", userId, OperationLogType.RECOVER.name(), true);
saveBatchLog(projectId, ids, "/api/definition/batch-recover", userId, OperationLogType.RECOVER.name(), false);
}
@ -248,7 +248,7 @@ public class ApiDefinitionLogService {
* 删除回收站接口定义接口日志
*/
public void batchTrashDelLog(List<String> ids, String userId, String projectId) {
saveBatchLog(projectId, ids, "/api/definition/batch-trash-del", userId, OperationLogType.DELETE.name(), true);
saveBatchLog(projectId, ids, "/api/definition/batch-trash-del", userId, OperationLogType.DELETE.name(), false);
}
private ApiDefinitionDTO getOriginalValue(String id) {
@ -303,5 +303,41 @@ public class ApiDefinitionLogService {
}
}
public Long saveOperationHistoryLog(ApiDefinitionDTO apiDefinitionDTO, String userId, String projectId) {
Project project = projectMapper.selectByPrimaryKey(projectId);
LogDTO dto = new LogDTO(
project.getId(),
project.getOrganizationId(),
apiDefinitionDTO.getId(),
userId,
OperationLogType.UPDATE.name(),
OperationLogModule.API_DEFINITION,
apiDefinitionDTO.getName());
dto.setHistory(true);
dto.setPath("/api/definition/operation-history/save");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(apiDefinitionDTO));
operationLogService.add(dto);
return dto.getId();
}
public Long recoverOperationHistoryLog(ApiDefinitionDTO apiDefinitionDTO, String userId, String projectId) {
Project project = projectMapper.selectByPrimaryKey(projectId);
LogDTO dto = new LogDTO(
project.getId(),
project.getOrganizationId(),
apiDefinitionDTO.getId(),
userId,
OperationLogType.RECOVER.name(),
OperationLogModule.API_DEFINITION,
apiDefinitionDTO.getName());
dto.setHistory(true);
dto.setPath("/api/definition/operation-history/recover");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(apiDefinitionDTO));
operationLogService.add(dto);
return dto.getId();
}
}

View File

@ -22,10 +22,17 @@ import io.metersphere.sdk.constants.ApiReportStatus;
import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.domain.OperationLogBlob;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.mapper.OperationLogBlobMapper;
import io.metersphere.sdk.util.*;
import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.request.OperationHistoryVersionRequest;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.metersphere.system.dto.table.TableBatchProcessDTO;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.service.OperationHistoryService;
import io.metersphere.system.service.UserLoginService;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
@ -42,6 +49,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -107,6 +115,12 @@ public class ApiDefinitionService {
@Resource
private ApiDefinitionMockService apiDefinitionMockService;
@Resource
private OperationHistoryService operationHistoryService;
@Resource
private OperationLogBlobMapper operationLogBlobMapper;
public List<ApiDefinitionDTO> getApiDefinitionPage(ApiDefinitionPageRequest request, String userId) {
CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request, userId);
List<ApiDefinitionDTO> list = extApiDefinitionMapper.list(request);
@ -910,4 +924,84 @@ public class ApiDefinitionService {
}
return apiImport;
}
public List<OperationHistoryDTO> list(OperationHistoryRequest request) {
List<OperationHistoryDTO> operationHistoryList = operationHistoryService.list(request);
if (CollectionUtils.isNotEmpty(operationHistoryList)) {
List<String> apiIds = operationHistoryList.stream()
.map(OperationHistoryDTO::getSourceId).toList();
Map<String, String> apiMap = extApiDefinitionMapper.selectVersionOptionByIds(apiIds).stream()
.collect(Collectors.toMap(OptionDTO::getId, OptionDTO::getName));
operationHistoryList.forEach(item -> item.setVersionName(apiMap.getOrDefault(item.getSourceId(), StringUtils.EMPTY)));
}
return operationHistoryList;
}
/**
* 是否存在所选版本的接口定义不存在则创建,同时创建日志记录
*/
public void saveOperationHistory(OperationHistoryVersionRequest request) {
ApiDefinitionExample apiDefinitionExample = new ApiDefinitionExample();
apiDefinitionExample.createCriteria().andRefIdEqualTo(request.getSourceId()).andVersionIdEqualTo(request.getVersionId());
List<ApiDefinition> matchingApiDefinitions = apiDefinitionMapper.selectByExample(apiDefinitionExample);
ApiDefinition apiDefinition = matchingApiDefinitions.stream().findFirst().orElseGet(ApiDefinition::new);
ApiDefinitionBlob copyApiDefinitionBlob = getApiDefinitionBlob(request.getSourceId());
if (apiDefinition.getId() == null) {
ApiDefinition copyApiDefinition = apiDefinitionMapper.selectByPrimaryKey(request.getSourceId());
BeanUtils.copyBean(apiDefinition, copyApiDefinition);
apiDefinition.setId(IDGenerator.nextStr());
apiDefinition.setRefId(request.getSourceId());
apiDefinition.setVersionId(request.getVersionId());
apiDefinition.setCreateTime(System.currentTimeMillis());
apiDefinition.setUpdateTime(System.currentTimeMillis());
apiDefinitionMapper.insertSelective(apiDefinition);
ApiDefinitionBlob apiDefinitionBlob = new ApiDefinitionBlob();
if(copyApiDefinitionBlob != null) {
apiDefinitionBlob.setId(apiDefinition.getId());
apiDefinitionBlob.setRequest(copyApiDefinitionBlob.getRequest());
apiDefinitionBlob.setResponse(copyApiDefinitionBlob.getResponse());
apiDefinitionBlobMapper.insertSelective(apiDefinitionBlob);
}
}
ApiDefinitionDTO apiDefinitionDTO = new ApiDefinitionDTO();
BeanUtils.copyBean(apiDefinitionDTO, apiDefinition);
Optional.ofNullable(copyApiDefinitionBlob)
.map(blob -> new String(blob.getRequest()))
.ifPresent(requestBlob -> apiDefinitionDTO.setRequest(ApiDataUtils.parseObject(requestBlob, AbstractMsTestElement.class)));
Optional.ofNullable(copyApiDefinitionBlob)
.map(blob -> new String(blob.getResponse()))
.ifPresent(responseBlob -> apiDefinitionDTO.setResponse(ApiDataUtils.parseArray(responseBlob, HttpResponse.class)));
Long logId = apiDefinitionLogService.saveOperationHistoryLog(apiDefinitionDTO, apiDefinition.getCreateUser(), apiDefinition.getProjectId());
operationHistoryService.associationRefId(request.getId(), logId);
}
public void recoverOperationHistory(OperationHistoryVersionRequest request) {
OperationLogBlob operationLogBlob = operationLogBlobMapper.selectByPrimaryKey(request.getId());
ApiDefinitionDTO apiDefinitionDTO = ApiDataUtils.parseObject(new String(operationLogBlob.getOriginalValue()), ApiDefinitionDTO.class);
Long logId = recoverApiDefinition(apiDefinitionDTO);
operationHistoryService.associationRefId(operationLogBlob.getId(), logId);
}
public Long recoverApiDefinition(ApiDefinitionDTO apiDefinitionDTO) {
ApiDefinition apiDefinition = new ApiDefinition();
BeanUtils.copyBean(apiDefinition, apiDefinitionDTO);
apiDefinition.setUpdateTime(System.currentTimeMillis());
apiDefinitionMapper.updateByPrimaryKeySelective(apiDefinition);
ApiDefinitionBlob apiDefinitionBlob = new ApiDefinitionBlob();
apiDefinitionBlob.setId(apiDefinition.getId());
apiDefinitionBlob.setRequest(ApiDataUtils.toJSONString(apiDefinitionDTO.getRequest()).getBytes(StandardCharsets.UTF_8));
apiDefinitionBlob.setResponse(ApiDataUtils.toJSONString(apiDefinitionDTO.getResponse()).getBytes(StandardCharsets.UTF_8));
apiDefinitionBlobMapper.updateByPrimaryKeySelective(apiDefinitionBlob);
// 记录操作日志
return apiDefinitionLogService.recoverOperationHistoryLog(apiDefinitionDTO, apiDefinition.getCreateUser(), apiDefinition.getProjectId());
}
}

View File

@ -23,12 +23,19 @@ import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.mapper.OperationLogBlobMapper;
import io.metersphere.sdk.mapper.OperationLogMapper;
import io.metersphere.sdk.util.*;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.controller.handler.result.MsHttpResultCode;
import io.metersphere.system.domain.OperationHistory;
import io.metersphere.system.domain.OperationHistoryExample;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.request.OperationHistoryVersionRequest;
import io.metersphere.system.dto.sdk.BaseCondition;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.mapper.OperationHistoryMapper;
import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
@ -85,6 +92,9 @@ public class ApiDefinitionControllerTests extends BaseTest {
private static final String GET = BASE_PATH + "get-detail/";
private static final String FOLLOW = BASE_PATH + "follow/";
private static final String VERSION = BASE_PATH + "version/";
private static final String OPERATION_HISTORY = BASE_PATH + "operation-history";
private static final String OPERATION_HISTORY_RECOVER = BASE_PATH + "operation-history/recover";
private static final String OPERATION_HISTORY_SAVE = BASE_PATH + "operation-history/save";
private static final String UPLOAD_TEMP_FILE = BASE_PATH + "/upload/temp/file";
private static final String DEFAULT_MODULE_ID = "10001";
@ -119,6 +129,15 @@ public class ApiDefinitionControllerTests extends BaseTest {
@Resource
private ExtApiDefinitionCustomFieldMapper extApiDefinitionCustomFieldMapper;
@Resource
private OperationHistoryMapper operationHistoryMapper;
@Resource
private OperationLogMapper operationLogMapper;
@Resource
private OperationLogBlobMapper operationLogBlobMapper;
@Resource
private FileMetadataService fileMetadataService;
private static String fileMetadataId;
@ -895,6 +914,88 @@ public class ApiDefinitionControllerTests extends BaseTest {
@Test
@Order(12)
public void testOperationHistoryList() throws Exception {
OperationHistoryRequest request = new OperationHistoryRequest();
apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001");
request.setSourceId(apiDefinition.getId());
request.setProjectId(DEFAULT_PROJECT_ID);
request.setCurrent(1);
request.setPageSize(10);
request.setSort(Map.of("createTime", "asc"));
MvcResult mvcResult = this.requestPostWithOkAndReturn(OPERATION_HISTORY, request);
// 获取返回值
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
// 返回请求正常
Assertions.assertNotNull(resultHolder);
Pager<?> pageData = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
// 返回值不为空
Assertions.assertNotNull(pageData);
// 返回值的页码和当前页码相同
Assertions.assertEquals(pageData.getCurrent(), request.getCurrent());
// 返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= request.getPageSize());
request.setSort(Map.of());
this.requestPost(OPERATION_HISTORY, request);
}
@Test
@Order(12)
public void testOperationHistorySave() throws Exception {
OperationHistoryVersionRequest operationHistoryVersionRequest = new OperationHistoryVersionRequest();
apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1002");
OperationHistoryExample operationHistoryExample = new OperationHistoryExample();
operationHistoryExample.createCriteria().andSourceIdEqualTo(apiDefinition.getId());
operationHistoryExample.setOrderByClause("id DESC");
OperationHistory operationHistory = operationHistoryMapper.selectByExample(operationHistoryExample).getFirst();
operationHistoryVersionRequest.setId(operationHistory.getId());
operationHistoryVersionRequest.setSourceId(apiDefinition.getId());
operationHistoryVersionRequest.setVersionId(apiDefinition.getVersionId());
this.requestPostWithOkAndReturn(OPERATION_HISTORY_SAVE, operationHistoryVersionRequest);
checkLogModelList.add(new CheckLogModel(apiDefinition.getId(), OperationLogType.UPDATE, OPERATION_HISTORY_SAVE));
OperationHistoryExample comparisonExample = new OperationHistoryExample();
comparisonExample.createCriteria().andSourceIdEqualTo(apiDefinition.getId()).andRefIdEqualTo(operationHistory.getId()).andTypeEqualTo(OperationLogType.UPDATE.name());
comparisonExample.setOrderByClause("id DESC");
OperationHistory comparison = operationHistoryMapper.selectByExample(operationHistoryExample).getFirst();
Assertions.assertNotNull(comparison);
operationHistoryVersionRequest.setId(operationHistory.getId());
operationHistoryVersionRequest.setSourceId("1002");
operationHistoryVersionRequest.setVersionId("1002002002");
this.requestPostWithOkAndReturn(OPERATION_HISTORY_SAVE, operationHistoryVersionRequest);
checkLogModelList.add(new CheckLogModel(apiDefinition.getId(), OperationLogType.UPDATE, OPERATION_HISTORY_SAVE));
OperationHistoryExample comparisonExampleNewVersion = new OperationHistoryExample();
comparisonExampleNewVersion.createCriteria().andSourceIdEqualTo(apiDefinition.getId()).andRefIdEqualTo(operationHistory.getId()).andTypeEqualTo(OperationLogType.UPDATE.name());
comparisonExampleNewVersion.setOrderByClause("id DESC");
OperationHistory comparisonNewVersion = operationHistoryMapper.selectByExample(operationHistoryExample).getFirst();
Assertions.assertNotNull(comparisonNewVersion);
}
@Test
@Order(13)
public void testOperationHistoryRecover() throws Exception {
OperationHistoryVersionRequest operationHistoryVersionRequest = new OperationHistoryVersionRequest();
apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1002");
OperationHistoryExample operationHistoryExample = new OperationHistoryExample();
operationHistoryExample.createCriteria().andSourceIdEqualTo(apiDefinition.getId());
operationHistoryExample.setOrderByClause("id DESC");
OperationHistory operationHistory = operationHistoryMapper.selectByExample(operationHistoryExample).getFirst();
operationHistoryVersionRequest.setId(operationHistory.getId());
operationHistoryVersionRequest.setSourceId(apiDefinition.getId());
operationHistoryVersionRequest.setVersionId(apiDefinition.getVersionId());
this.requestPostWithOkAndReturn(OPERATION_HISTORY_RECOVER, operationHistoryVersionRequest);
checkLogModelList.add(new CheckLogModel(apiDefinition.getId(), OperationLogType.RECOVER, OPERATION_HISTORY_RECOVER));
OperationHistoryExample comparisonExample = new OperationHistoryExample();
comparisonExample.createCriteria().andSourceIdEqualTo(apiDefinition.getId()).andRefIdEqualTo(operationHistory.getId()).andTypeEqualTo(OperationLogType.RECOVER.name());
comparisonExample.setOrderByClause("id DESC");
OperationHistory comparison = operationHistoryMapper.selectByExample(operationHistoryExample).getFirst();
Assertions.assertNotNull(comparison);
}
@Test
@Order(14)
public void testDel() throws Exception {
LogUtils.info("delete api test");
apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001");
@ -968,7 +1069,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
}
@Test
@Order(13)
@Order(15)
public void testBatchDel() throws Exception {
LogUtils.info("batch delete api test");
ApiDefinitionBatchRequest request = new ApiDefinitionBatchRequest();
@ -1002,7 +1103,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
@Test
@Order(14)
@Order(16)
public void testRecover() throws Exception {
LogUtils.info("recover api test");
apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001");
@ -1047,7 +1148,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
}
@Test
@Order(15)
@Order(20)
public void testBatchRecover() throws Exception {
LogUtils.info("batch recover api test");
ApiDefinitionBatchRequest request = new ApiDefinitionBatchRequest();
@ -1095,7 +1196,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
}
@Test
@Order(16)
@Order(21)
public void testTrashDel() throws Exception {
LogUtils.info("trashDel api test");
apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001");
@ -1137,7 +1238,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
}
@Test
@Order(17)
@Order(25)
public void testBatchTrashDel() throws Exception {
LogUtils.info("batch trash delete api test");
ApiDefinitionBatchRequest request = new ApiDefinitionBatchRequest();

View File

@ -0,0 +1,26 @@
package io.metersphere.system.dto;
import io.metersphere.system.domain.OperationHistory;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author: LAN
* @date: 2024/1/2 19:03
* @version: 1.0
*/
@Data
public class OperationHistoryDTO extends OperationHistory implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
// 操作人
private String createUserName;
// 版本
private String versionName;
}

View File

@ -0,0 +1,33 @@
package io.metersphere.system.dto.request;
import io.metersphere.system.dto.sdk.BasePageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class OperationHistoryRequest extends BasePageRequest {
@Schema(description = "项目id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{operation_history.project_id.not_blank}")
@Size(min = 1, max = 50, message = "{operation_history.project_id.length_range}")
private String projectId;
@Schema(description = "资源id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{operation_history.source_id.not_blank}")
private String sourceId;
@Schema(description = "操作人")
private String createUser;
@Schema(description = "操作类型")
private String type;
@Schema(description = "操作模块")
private String module;
}

View File

@ -0,0 +1,39 @@
package io.metersphere.system.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
/**
* @author: LAN
* @date: 2024/1/3 16:40
* @version: 1.0
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class OperationHistoryVersionRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "变更记录id")
@NotNull(message = "{operation_history.id.not_blank}")
private Long id;
@Schema(description = "资源id当前变更记录的资源id")
@NotBlank(message = "{operation_history.source_id.not_blank}")
private String sourceId;
@Schema(description = "版本id")
@NotBlank(message = "{operation_history.version_id.not_blank}")
@Size(min = 1, max = 50, message = "{operation_history.version_id.length_range}")
private String versionId;
}

View File

@ -1,6 +1,8 @@
package io.metersphere.system.mapper;
import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -12,4 +14,6 @@ public interface BaseOperationHistoryMapper {
List<Long> selectIdsBySourceId(@Param("sourceId") String sourceId, @Param("limit") int limit);
void deleteByIds(@Param("sourceId") String sourceId, @Param("ids") List<Long> ids);
List<OperationHistoryDTO> list(@Param("request") OperationHistoryRequest request);
}

View File

@ -18,4 +18,26 @@
#{id}
</foreach>
</delete>
<select id="list" resultType="io.metersphere.system.dto.OperationHistoryDTO">
select id, project_id, create_time, create_user, source_id, `type`, `module`, ref_id
from operation_history
<where>
<if test="request.projectId != null and request.projectId != ''">
AND project_id = #{request.projectId,jdbcType=VARCHAR}
</if>
<if test="request.sourceId != null and request.sourceId != ''">
AND source_id = #{request.sourceId,jdbcType=VARCHAR}
</if>
<if test="request.createUser != null and request.createUser != ''">
AND create_user = #{request.createUser,jdbcType=VARCHAR}
</if>
<if test="request.type != null and request.type != ''">
AND `type` = #{request.type,jdbcType=VARCHAR}
</if>
<if test="request.module != null and request.module != ''">
AND `module` = #{request.module,jdbcType=VARCHAR}
</if>
</where>
</select>
</mapper>

View File

@ -0,0 +1,55 @@
package io.metersphere.system.service;
import io.metersphere.system.domain.OperationHistory;
import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.metersphere.system.mapper.BaseOperationHistoryMapper;
import io.metersphere.system.mapper.BaseUserMapper;
import io.metersphere.system.mapper.OperationHistoryMapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class OperationHistoryService {
@Resource
private BaseOperationHistoryMapper baseOperationHistoryMapper;
@Resource
private BaseUserMapper baseUserMapper;
@Resource
private OperationHistoryMapper operationHistoryMapper;
public List<OperationHistoryDTO> list(OperationHistoryRequest request) {
List<OperationHistoryDTO> list = baseOperationHistoryMapper.list(request);
if (CollectionUtils.isNotEmpty(list)) {
List<String> userIds = list.stream().distinct()
.map(OperationHistoryDTO::getCreateUser).toList();
Map<String, String> userMap = baseUserMapper.selectUserOptionByIds(userIds).stream()
.collect(Collectors.toMap(OptionDTO::getId, OptionDTO::getName));
list.forEach(item -> item.setCreateUserName(userMap.getOrDefault(item.getCreateUser(), StringUtils.EMPTY)));
}
return list;
}
public void associationRefId(Long refLogId, Long logId) {
OperationHistory operationHistory = operationHistoryMapper.selectByPrimaryKey(logId);
operationHistory.setRefId(refLogId);
operationHistoryMapper.updateByPrimaryKeySelective(operationHistory);
}
}