feat(接口测试): 接口管理模块接口定义自定义字段处理

This commit is contained in:
lan-yonghui 2023-12-13 19:03:02 +08:00 committed by Craftsman
parent c2fa378954
commit aa85ff2ba3
25 changed files with 1422 additions and 192 deletions

View File

@ -0,0 +1,100 @@
package io.metersphere.api.domain;
import io.metersphere.validation.groups.*;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import lombok.Data;
@Data
public class ApiDefinitionCustomField implements Serializable {
@Schema(description = "接口ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_definition_custom_field.api_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{api_definition_custom_field.api_id.length_range}", groups = {Created.class, Updated.class})
private String apiId;
@Schema(description = "字段ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_definition_custom_field.field_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{api_definition_custom_field.field_id.length_range}", groups = {Created.class, Updated.class})
private String fieldId;
@Schema(description = "字段值")
private String value;
private static final long serialVersionUID = 1L;
public enum Column {
apiId("api_id", "apiId", "VARCHAR", false),
fieldId("field_id", "fieldId", "VARCHAR", false),
value("value", "value", "VARCHAR", true);
private static final String BEGINNING_DELIMITER = "`";
private static final String ENDING_DELIMITER = "`";
private final String column;
private final boolean isColumnNameDelimited;
private final String javaProperty;
private final String jdbcType;
public String value() {
return this.column;
}
public String getValue() {
return this.column;
}
public String getJavaProperty() {
return this.javaProperty;
}
public String getJdbcType() {
return this.jdbcType;
}
Column(String column, String javaProperty, String jdbcType, boolean isColumnNameDelimited) {
this.column = column;
this.javaProperty = javaProperty;
this.jdbcType = jdbcType;
this.isColumnNameDelimited = isColumnNameDelimited;
}
public String desc() {
return this.getEscapedColumnName() + " DESC";
}
public String asc() {
return this.getEscapedColumnName() + " ASC";
}
public static Column[] excludes(Column ... excludes) {
ArrayList<Column> columns = new ArrayList<>(Arrays.asList(Column.values()));
if (excludes != null && excludes.length > 0) {
columns.removeAll(new ArrayList<>(Arrays.asList(excludes)));
}
return columns.toArray(new Column[]{});
}
public static Column[] all() {
return Column.values();
}
public String getEscapedColumnName() {
if (this.isColumnNameDelimited) {
return new StringBuilder().append(BEGINNING_DELIMITER).append(this.column).append(ENDING_DELIMITER).toString();
} else {
return this.column;
}
}
public String getAliasedEscapedColumnName() {
return this.getEscapedColumnName();
}
}
}

View File

@ -0,0 +1,410 @@
package io.metersphere.api.domain;
import java.util.ArrayList;
import java.util.List;
public class ApiDefinitionCustomFieldExample {
protected String orderByClause;
protected boolean distinct;
protected List<Criteria> oredCriteria;
public ApiDefinitionCustomFieldExample() {
oredCriteria = new ArrayList<Criteria>();
}
public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
}
public String getOrderByClause() {
return orderByClause;
}
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
public boolean isDistinct() {
return distinct;
}
public List<Criteria> getOredCriteria() {
return oredCriteria;
}
public void or(Criteria criteria) {
oredCriteria.add(criteria);
}
public Criteria or() {
Criteria criteria = createCriteriaInternal();
oredCriteria.add(criteria);
return criteria;
}
public Criteria createCriteria() {
Criteria criteria = createCriteriaInternal();
if (oredCriteria.size() == 0) {
oredCriteria.add(criteria);
}
return criteria;
}
protected Criteria createCriteriaInternal() {
Criteria criteria = new Criteria();
return criteria;
}
public void clear() {
oredCriteria.clear();
orderByClause = null;
distinct = false;
}
protected abstract static class GeneratedCriteria {
protected List<Criterion> criteria;
protected GeneratedCriteria() {
super();
criteria = new ArrayList<Criterion>();
}
public boolean isValid() {
return criteria.size() > 0;
}
public List<Criterion> getAllCriteria() {
return criteria;
}
public List<Criterion> getCriteria() {
return criteria;
}
protected void addCriterion(String condition) {
if (condition == null) {
throw new RuntimeException("Value for condition cannot be null");
}
criteria.add(new Criterion(condition));
}
protected void addCriterion(String condition, Object value, String property) {
if (value == null) {
throw new RuntimeException("Value for " + property + " cannot be null");
}
criteria.add(new Criterion(condition, value));
}
protected void addCriterion(String condition, Object value1, Object value2, String property) {
if (value1 == null || value2 == null) {
throw new RuntimeException("Between values for " + property + " cannot be null");
}
criteria.add(new Criterion(condition, value1, value2));
}
public Criteria andApiIdIsNull() {
addCriterion("api_id is null");
return (Criteria) this;
}
public Criteria andApiIdIsNotNull() {
addCriterion("api_id is not null");
return (Criteria) this;
}
public Criteria andApiIdEqualTo(String value) {
addCriterion("api_id =", value, "apiId");
return (Criteria) this;
}
public Criteria andApiIdNotEqualTo(String value) {
addCriterion("api_id <>", value, "apiId");
return (Criteria) this;
}
public Criteria andApiIdGreaterThan(String value) {
addCriterion("api_id >", value, "apiId");
return (Criteria) this;
}
public Criteria andApiIdGreaterThanOrEqualTo(String value) {
addCriterion("api_id >=", value, "apiId");
return (Criteria) this;
}
public Criteria andApiIdLessThan(String value) {
addCriterion("api_id <", value, "apiId");
return (Criteria) this;
}
public Criteria andApiIdLessThanOrEqualTo(String value) {
addCriterion("api_id <=", value, "apiId");
return (Criteria) this;
}
public Criteria andApiIdLike(String value) {
addCriterion("api_id like", value, "apiId");
return (Criteria) this;
}
public Criteria andApiIdNotLike(String value) {
addCriterion("api_id not like", value, "apiId");
return (Criteria) this;
}
public Criteria andApiIdIn(List<String> values) {
addCriterion("api_id in", values, "apiId");
return (Criteria) this;
}
public Criteria andApiIdNotIn(List<String> values) {
addCriterion("api_id not in", values, "apiId");
return (Criteria) this;
}
public Criteria andApiIdBetween(String value1, String value2) {
addCriterion("api_id between", value1, value2, "apiId");
return (Criteria) this;
}
public Criteria andApiIdNotBetween(String value1, String value2) {
addCriterion("api_id not between", value1, value2, "apiId");
return (Criteria) this;
}
public Criteria andFieldIdIsNull() {
addCriterion("field_id is null");
return (Criteria) this;
}
public Criteria andFieldIdIsNotNull() {
addCriterion("field_id is not null");
return (Criteria) this;
}
public Criteria andFieldIdEqualTo(String value) {
addCriterion("field_id =", value, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdNotEqualTo(String value) {
addCriterion("field_id <>", value, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdGreaterThan(String value) {
addCriterion("field_id >", value, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdGreaterThanOrEqualTo(String value) {
addCriterion("field_id >=", value, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdLessThan(String value) {
addCriterion("field_id <", value, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdLessThanOrEqualTo(String value) {
addCriterion("field_id <=", value, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdLike(String value) {
addCriterion("field_id like", value, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdNotLike(String value) {
addCriterion("field_id not like", value, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdIn(List<String> values) {
addCriterion("field_id in", values, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdNotIn(List<String> values) {
addCriterion("field_id not in", values, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdBetween(String value1, String value2) {
addCriterion("field_id between", value1, value2, "fieldId");
return (Criteria) this;
}
public Criteria andFieldIdNotBetween(String value1, String value2) {
addCriterion("field_id not between", value1, value2, "fieldId");
return (Criteria) this;
}
public Criteria andValueIsNull() {
addCriterion("`value` is null");
return (Criteria) this;
}
public Criteria andValueIsNotNull() {
addCriterion("`value` is not null");
return (Criteria) this;
}
public Criteria andValueEqualTo(String value) {
addCriterion("`value` =", value, "value");
return (Criteria) this;
}
public Criteria andValueNotEqualTo(String value) {
addCriterion("`value` <>", value, "value");
return (Criteria) this;
}
public Criteria andValueGreaterThan(String value) {
addCriterion("`value` >", value, "value");
return (Criteria) this;
}
public Criteria andValueGreaterThanOrEqualTo(String value) {
addCriterion("`value` >=", value, "value");
return (Criteria) this;
}
public Criteria andValueLessThan(String value) {
addCriterion("`value` <", value, "value");
return (Criteria) this;
}
public Criteria andValueLessThanOrEqualTo(String value) {
addCriterion("`value` <=", value, "value");
return (Criteria) this;
}
public Criteria andValueLike(String value) {
addCriterion("`value` like", value, "value");
return (Criteria) this;
}
public Criteria andValueNotLike(String value) {
addCriterion("`value` not like", value, "value");
return (Criteria) this;
}
public Criteria andValueIn(List<String> values) {
addCriterion("`value` in", values, "value");
return (Criteria) this;
}
public Criteria andValueNotIn(List<String> values) {
addCriterion("`value` not in", values, "value");
return (Criteria) this;
}
public Criteria andValueBetween(String value1, String value2) {
addCriterion("`value` between", value1, value2, "value");
return (Criteria) this;
}
public Criteria andValueNotBetween(String value1, String value2) {
addCriterion("`value` not between", value1, value2, "value");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {
protected Criteria() {
super();
}
}
public static class Criterion {
private String condition;
private Object value;
private Object secondValue;
private boolean noValue;
private boolean singleValue;
private boolean betweenValue;
private boolean listValue;
private String typeHandler;
public String getCondition() {
return condition;
}
public Object getValue() {
return value;
}
public Object getSecondValue() {
return secondValue;
}
public boolean isNoValue() {
return noValue;
}
public boolean isSingleValue() {
return singleValue;
}
public boolean isBetweenValue() {
return betweenValue;
}
public boolean isListValue() {
return listValue;
}
public String getTypeHandler() {
return typeHandler;
}
protected Criterion(String condition) {
super();
this.condition = condition;
this.typeHandler = null;
this.noValue = true;
}
protected Criterion(String condition, Object value, String typeHandler) {
super();
this.condition = condition;
this.value = value;
this.typeHandler = typeHandler;
if (value instanceof List<?>) {
this.listValue = true;
} else {
this.singleValue = true;
}
}
protected Criterion(String condition, Object value) {
this(condition, value, null);
}
protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
super();
this.condition = condition;
this.value = value;
this.secondValue = secondValue;
this.typeHandler = typeHandler;
this.betweenValue = true;
}
protected Criterion(String condition, Object value, Object secondValue) {
this(condition, value, secondValue, null);
}
}
}

View File

@ -0,0 +1,34 @@
package io.metersphere.api.mapper;
import io.metersphere.api.domain.ApiDefinitionCustomField;
import io.metersphere.api.domain.ApiDefinitionCustomFieldExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface ApiDefinitionCustomFieldMapper {
long countByExample(ApiDefinitionCustomFieldExample example);
int deleteByExample(ApiDefinitionCustomFieldExample example);
int deleteByPrimaryKey(@Param("apiId") String apiId, @Param("fieldId") String fieldId);
int insert(ApiDefinitionCustomField record);
int insertSelective(ApiDefinitionCustomField record);
List<ApiDefinitionCustomField> selectByExample(ApiDefinitionCustomFieldExample example);
ApiDefinitionCustomField selectByPrimaryKey(@Param("apiId") String apiId, @Param("fieldId") String fieldId);
int updateByExampleSelective(@Param("record") ApiDefinitionCustomField record, @Param("example") ApiDefinitionCustomFieldExample example);
int updateByExample(@Param("record") ApiDefinitionCustomField record, @Param("example") ApiDefinitionCustomFieldExample example);
int updateByPrimaryKeySelective(ApiDefinitionCustomField record);
int updateByPrimaryKey(ApiDefinitionCustomField record);
int batchInsert(@Param("list") List<ApiDefinitionCustomField> list);
int batchInsertSelective(@Param("list") List<ApiDefinitionCustomField> list, @Param("selective") ApiDefinitionCustomField.Column ... selective);
}

View File

@ -0,0 +1,213 @@
<?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.api.mapper.ApiDefinitionCustomFieldMapper">
<resultMap id="BaseResultMap" type="io.metersphere.api.domain.ApiDefinitionCustomField">
<id column="api_id" jdbcType="VARCHAR" property="apiId" />
<id column="field_id" jdbcType="VARCHAR" property="fieldId" />
<result column="value" jdbcType="VARCHAR" property="value" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
<foreach collection="oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause">
<where>
<foreach collection="example.oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List">
api_id, field_id, `value`
</sql>
<select id="selectByExample" parameterType="io.metersphere.api.domain.ApiDefinitionCustomFieldExample" resultMap="BaseResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from api_definition_custom_field
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<select id="selectByPrimaryKey" parameterType="map" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from api_definition_custom_field
where api_id = #{apiId,jdbcType=VARCHAR}
and field_id = #{fieldId,jdbcType=VARCHAR}
</select>
<delete id="deleteByPrimaryKey" parameterType="map">
delete from api_definition_custom_field
where api_id = #{apiId,jdbcType=VARCHAR}
and field_id = #{fieldId,jdbcType=VARCHAR}
</delete>
<delete id="deleteByExample" parameterType="io.metersphere.api.domain.ApiDefinitionCustomFieldExample">
delete from api_definition_custom_field
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.api.domain.ApiDefinitionCustomField">
insert into api_definition_custom_field (api_id, field_id, `value`
)
values (#{apiId,jdbcType=VARCHAR}, #{fieldId,jdbcType=VARCHAR}, #{value,jdbcType=VARCHAR}
)
</insert>
<insert id="insertSelective" parameterType="io.metersphere.api.domain.ApiDefinitionCustomField">
insert into api_definition_custom_field
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="apiId != null">
api_id,
</if>
<if test="fieldId != null">
field_id,
</if>
<if test="value != null">
`value`,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="apiId != null">
#{apiId,jdbcType=VARCHAR},
</if>
<if test="fieldId != null">
#{fieldId,jdbcType=VARCHAR},
</if>
<if test="value != null">
#{value,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.api.domain.ApiDefinitionCustomFieldExample" resultType="java.lang.Long">
select count(*) from api_definition_custom_field
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</select>
<update id="updateByExampleSelective" parameterType="map">
update api_definition_custom_field
<set>
<if test="record.apiId != null">
api_id = #{record.apiId,jdbcType=VARCHAR},
</if>
<if test="record.fieldId != null">
field_id = #{record.fieldId,jdbcType=VARCHAR},
</if>
<if test="record.value != null">
`value` = #{record.value,jdbcType=VARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExample" parameterType="map">
update api_definition_custom_field
set api_id = #{record.apiId,jdbcType=VARCHAR},
field_id = #{record.fieldId,jdbcType=VARCHAR},
`value` = #{record.value,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.api.domain.ApiDefinitionCustomField">
update api_definition_custom_field
<set>
<if test="value != null">
`value` = #{value,jdbcType=VARCHAR},
</if>
</set>
where api_id = #{apiId,jdbcType=VARCHAR}
and field_id = #{fieldId,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.api.domain.ApiDefinitionCustomField">
update api_definition_custom_field
set `value` = #{value,jdbcType=VARCHAR}
where api_id = #{apiId,jdbcType=VARCHAR}
and field_id = #{fieldId,jdbcType=VARCHAR}
</update>
<insert id="batchInsert" parameterType="map">
insert into api_definition_custom_field
(api_id, field_id, `value`)
values
<foreach collection="list" item="item" separator=",">
(#{item.apiId,jdbcType=VARCHAR}, #{item.fieldId,jdbcType=VARCHAR}, #{item.value,jdbcType=VARCHAR}
)
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
insert into api_definition_custom_field (
<foreach collection="selective" item="column" separator=",">
${column.escapedColumnName}
</foreach>
)
values
<foreach collection="list" item="item" separator=",">
(
<foreach collection="selective" item="column" separator=",">
<if test="'api_id'.toString() == column.value">
#{item.apiId,jdbcType=VARCHAR}
</if>
<if test="'field_id'.toString() == column.value">
#{item.fieldId,jdbcType=VARCHAR}
</if>
<if test="'value'.toString() == column.value">
#{item.value,jdbcType=VARCHAR}
</if>
</foreach>
)
</foreach>
</insert>
</mapper>

View File

@ -506,7 +506,20 @@ CREATE TABLE IF NOT EXISTS api_file_resource(
`create_time` BIGINT NOT NULL COMMENT '创建时间' ,
`project_id` VARCHAR(50) NOT NULL COMMENT '项目ID' ,
PRIMARY KEY (resource_id,file_id)
) COMMENT = '接口和所需文件资源的关联表';
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT = '接口和所需文件资源的关联表';
CREATE TABLE IF NOT EXISTS api_definition_custom_field(
`api_id` VARCHAR(50) NOT NULL COMMENT '接口ID' ,
`field_id` VARCHAR(50) NOT NULL COMMENT '字段ID' ,
`value` VARCHAR(1000) COMMENT '字段值' ,
PRIMARY KEY (api_id,field_id)
) 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

@ -289,6 +289,11 @@ api_debug_module.name.not_contain_slash=模块名称不能包含斜杠
api_environment_config.id.not_blank=ID不能为空
api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间
api_environment_config.environment_id.not_blank=环境ID不能为空
#moduleApiDefinitionCustomField
api_definition_custom_field.api_id.not_blank=接口ID不能为空
api_definition_custom_field.field_id.not_blank=自定义字段ID不能为空
api_module.not.exist=模块不存在
permission.api.name=接口测试

View File

@ -297,6 +297,11 @@ api_unplanned_request=Unplanned Api
api_environment_config.id.not_blank=ID cannot be blank
api_environment_config.environment_id.length_range=Environment ID length must be between 1-50
api_environment_config.environment_id.not_blank=Environment ID cannot be blank
#moduleApiDefinitionCustomField
api_definition_custom_field.api_id.not_blank=Interface pk cannot be empty
api_definition_custom_field.field_id.not_blank=Field ID cannot be empty
api_module.not.exist=The module does not exist
permission.api.name=API Test

View File

@ -297,6 +297,11 @@ api_unplanned_request=未规划接口
api_environment_config.id.not_blank=ID不能为空
api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间
api_environment_config.environment_id.not_blank=环境ID不能为空
#moduleApiDefinitionCustomField
api_definition_custom_field.api_id.not_blank=接口ID不能为空
api_definition_custom_field.field_id.not_blank=自定义字段ID不能为空
api_module.not.exist=模块不存在
permission.api.name=接口测试

View File

@ -297,6 +297,11 @@ api_unplanned_request=未规划接口
api_environment_config.id.not_blank=ID不能為空
api_environment_config.environment_id.length_range=環境ID長度必須在1-50之間
api_environment_config.environment_id.not_blank=環境ID不能為空
#moduleApiDefinitionCustomField
api_definition_custom_field.api_id.not_blank=接口ID不能為空
api_definition_custom_field.field_id.not_blank=自定義字段ID不能爲空
api_module.not.exist=模塊不存在
permission.api.name=接口測試

View File

@ -57,8 +57,6 @@ public class ApiDefinitionController {
@PostMapping(value = "/batch-update")
@Operation(summary = "接口测试-接口管理-批量更新接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE)
// 添加修改Log示例
@Log(type = OperationLogType.UPDATE, expression = "#msClass.batchUpdateLog(#request)", msClass = ApiDefinitionLogService.class)
public void batchUpdate(@Validated @RequestBody ApiDefinitionBatchUpdateRequest request) {
apiDefinitionService.batchUpdate(request, SessionUtils.getUserId());
}
@ -73,7 +71,6 @@ public class ApiDefinitionController {
@PostMapping(value = "/batch-del")
@Operation(summary = "接口测试-接口管理-批量删除接口定义到回收站")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE)
@Log(type = OperationLogType.DELETE, expression = "#msClass.batchDelLog(#request)", msClass = ApiDefinitionLogService.class)
public void batchDelete(@Validated @RequestBody ApiDefinitionBatchRequest request) {
apiDefinitionService.batchDelete(request, SessionUtils.getUserId());
}
@ -89,7 +86,6 @@ public class ApiDefinitionController {
@PostMapping("/batch-move")
@Operation(summary = "接口测试-接口管理-批量移动接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE)
@Log(type = OperationLogType.UPDATE, expression = "#msClass.batchMoveLog(#request)", msClass = ApiDefinitionLogService.class)
public void batchMove(@Validated @RequestBody ApiDefinitionBatchMoveRequest request) {
apiDefinitionService.batchMove(request, SessionUtils.getUserId());
}
@ -122,7 +118,7 @@ public class ApiDefinitionController {
public Pager<List<ApiDefinitionDTO>> getPage(@Validated @RequestBody ApiDefinitionPageRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc");
return PageUtils.setPageInfo(page, apiDefinitionService.getApiDefinitionPage(request));
return PageUtils.setPageInfo(page, apiDefinitionService.getApiDefinitionPage(request, SessionUtils.getUserId()));
}
@PostMapping(value = "/restore")
@ -142,7 +138,6 @@ public class ApiDefinitionController {
@PostMapping(value = "/batch-restore")
@Operation(summary = "接口测试-接口管理-批量从回收站恢复接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE)
@Log(type = OperationLogType.UPDATE, expression = "#msClass.batchRestoreLog(#request)", msClass = ApiDefinitionLogService.class)
public void batchRestore(@Validated @RequestBody ApiDefinitionBatchRequest request) {
apiDefinitionService.batchRestore(request, SessionUtils.getUserId());
}
@ -150,7 +145,6 @@ public class ApiDefinitionController {
@PostMapping(value = "/batch-trash-del")
@Operation(summary = "接口测试-接口管理-批量从回收站删除接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE)
@Log(type = OperationLogType.UPDATE, expression = "#msClass.batchTrashDelLog(#request)", msClass = ApiDefinitionLogService.class)
public void batchTrashDel(@Validated @RequestBody ApiDefinitionBatchRequest request) {
apiDefinitionService.batchTrashDel(request, SessionUtils.getUserId());
}
@ -161,7 +155,7 @@ public class ApiDefinitionController {
public Pager<List<ApiDefinitionDTO>> getDocPage(@Validated @RequestBody ApiDefinitionPageRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc");
return PageUtils.setPageInfo(page, apiDefinitionService.getDocPage(request));
return PageUtils.setPageInfo(page, apiDefinitionService.getDocPage(request, SessionUtils.getUserId()));
}
@PostMapping("/upload/temp/file")

View File

@ -5,11 +5,13 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.io.Serial;
import java.io.Serializable;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
/**
* @author lan
@ -81,7 +83,10 @@ public class ApiDefinitionAddRequest implements Serializable {
@Schema(description = "关联文件ID")
private List<String> linkFileIds;
@Schema(description = "自定义字段集合")
private Map<String, String> customFields;
public void setPath(String path) {
this.path = (path != null) ? path.trim() : null;
this.path = StringUtils.trim(path);
}
}

View File

@ -36,6 +36,9 @@ public class ApiDefinitionBatchUpdateRequest extends ApiDefinitionBatchRequest {
@Schema(description = "标签")
private LinkedHashSet<@NotBlank String> tags;
@Schema(description = "自定义字段")
private ApiDefinitionCustomFieldDTO customField;
@Schema(description = "是否追加", requiredMode = Schema.RequiredMode.REQUIRED)
private boolean append = false;

View File

@ -0,0 +1,19 @@
package io.metersphere.api.dto.definition;
import io.metersphere.system.domain.CustomField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author: LAN
* @date: 2023/12/12 10:17
* @version: 1.0
*/
@Data
public class ApiDefinitionCustomFieldDTO extends CustomField {
@Schema(description = "字段值")
private String value;
@Schema(description = "接口ID")
private String apiId;
}

View File

@ -45,4 +45,7 @@ public class ApiDefinitionDTO extends ApiDefinition{
@Schema(description = "是否关注")
private Boolean follow;
@Schema(description = "自定义字段集合")
private List<ApiDefinitionCustomFieldDTO> customFields;
}

View File

@ -0,0 +1,22 @@
package io.metersphere.api.mapper;
import io.metersphere.api.dto.definition.ApiDefinitionCustomFieldDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author: LAN
* @date: 2023/12/12 10:12
* @version: 1.0
*/
public interface ExtApiDefinitionCustomFieldMapper {
/**
* 获取缺陷自定义字段值
* @param apiIds 接口集合
* @param projectId 项目ID
* @return 缺陷自定义字段值
*/
List<ApiDefinitionCustomFieldDTO> getApiCustomFields(@Param("ids") List<String> apiIds, @Param("projectId") String projectId);
}

View File

@ -0,0 +1,12 @@
<?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.api.mapper.ExtApiDefinitionCustomFieldMapper">
<select id="getApiCustomFields" resultType="io.metersphere.api.dto.definition.ApiDefinitionCustomFieldDTO">
select cf.*, adcf.value, adcf.api_id from api_definition_custom_field adcf join custom_field cf on adcf.field_id = cf.id
where cf.scene = 'API' and cf.scope_type = 'PROJECT' and scope_id = #{projectId}
and api_id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
</mapper>

View File

@ -281,6 +281,20 @@
and api_definition.version_id in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<when test="key.startsWith('custom_single')">
and api_definition.id in (
select api_id from api_definition_custom_field where concat('custom_single_', field_id) = #{key}
and trim(both '"' from `value`) in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
)
</when>
<when test="key.startsWith('custom_multiple')">
and api_definition.id in (
select api_id from api_definition_custom_field where concat('custom_multiple_', field_id) = #{key}
and
<include refid="io.metersphere.system.mapper.BaseMapper.filterMultipleWrapper"/>
)
</when>
</choose>
</if>
</foreach>
@ -351,6 +365,44 @@
<property name="object" value="${condition}.tags"/>
</include>
</if>
<if test="${condition}.customs != null and ${condition}.customs.size() > 0">
<foreach collection="${condition}.customs" item="custom" separator="" open="" close="">
<if test="custom.value != ''">
<if test='custom.operator == "not like" or custom.operator == "not in"'>
and api_definition.id not in (
</if>
<if test='custom.operator != "not like" and custom.operator != "not in"'>
and api_definition.id in (
</if>
select api_id from api_definition_custom_field where field_id = #{custom.id}
<choose>
<when test="custom.type == 'richText' or custom.type == 'textarea' or custom.operator == 'current user'">
and `value`
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/>
</include>
</when>
<when test="custom.type == 'multipleMember' or custom.type == 'checkbox' or custom.type == 'multipleSelect'">
and ${custom.value}
</when>
<when test="custom.type == 'date' or custom.type == 'datetime'">
and left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13)
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/>
</include>
</when>
<otherwise>
and trim(both '"' from `value`)
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/>
</include>
</otherwise>
</choose>
)
</if>
</foreach>
</if>
</sql>
<sql id="queryVersionCondition">

View File

@ -1,10 +1,13 @@
package io.metersphere.api.service.definition;
import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.domain.ApiDefinitionBlob;
import io.metersphere.api.domain.ApiDefinitionExample;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.mapper.ApiDefinitionBlobMapper;
import io.metersphere.api.mapper.ApiDefinitionMapper;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.domain.Project;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.constants.HttpMethodConstants;
@ -14,6 +17,7 @@ import io.metersphere.sdk.util.Translator;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.log.dto.LogDTO;
import io.metersphere.system.log.service.OperationLogService;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
@ -21,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
@Transactional(rollbackFor = Exception.class)
@ -29,14 +34,15 @@ public class ApiDefinitionLogService {
@Resource
private ApiDefinitionMapper apiDefinitionMapper;
@Resource
private ApiDefinitionBlobMapper apiDefinitionBlobMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private ApiDefinitionService apiDefinitionService;
private OperationLogService operationLogService;
@Resource
private ApiDefinitionBlobMapper apiDefinitionBlobMapper;
/**
* 添加接口日志
@ -118,31 +124,8 @@ public class ApiDefinitionLogService {
*
* @return
*/
public List<LogDTO> batchDelLog(ApiDefinitionBatchRequest request) {
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false);
List<LogDTO> dtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ids)) {
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdIn(ids).andDeletedEqualTo(false);
List<ApiDefinition> apiDefinitions = apiDefinitionMapper.selectByExample(example);
apiDefinitions.forEach(item -> {
LogDTO dto = new LogDTO(
item.getProjectId(),
"",
item.getId(),
item.getCreateUser(),
OperationLogType.DELETE.name(),
OperationLogModule.API_DEFINITION,
item.getName());
dto.setHistory(true);
dto.setPath("/api/definition/batch-delete");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(item));
dtoList.add(dto);
});
}
return dtoList;
public void batchDelLog(List<String> ids, String userId, String projectId) {
saveBatchLog(projectId, ids, "/api/definition/batch-delete", userId, OperationLogType.DELETE.name(), true);
}
/**
@ -150,30 +133,8 @@ public class ApiDefinitionLogService {
*
* @return
*/
public List<LogDTO> batchUpdateLog(ApiDefinitionBatchUpdateRequest request) {
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false);
List<LogDTO> dtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ids)) {
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdIn(ids);
List<ApiDefinition> apiDefinitions = apiDefinitionMapper.selectByExample(example);
apiDefinitions.forEach(item -> {
LogDTO dto = new LogDTO(
item.getProjectId(),
"",
item.getId(),
item.getCreateUser(),
OperationLogType.UPDATE.name(),
OperationLogModule.API_DEFINITION,
item.getName());
dto.setHistory(true);
dto.setPath("/api/definition/batch-update");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(item));
dtoList.add(dto);
});
}
return dtoList;
public void batchUpdateLog(List<String> ids, String userId, String projectId) {
saveBatchLog(projectId, ids, "/api/definition/batch-update", userId, OperationLogType.UPDATE.name(), true);
}
public LogDTO copyLog(ApiDefinitionCopyRequest request) {
@ -196,30 +157,8 @@ public class ApiDefinitionLogService {
return null;
}
public List<LogDTO> batchMoveLog(ApiDefinitionBatchMoveRequest request) {
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false);
List<LogDTO> dtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ids)) {
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdIn(ids);
List<ApiDefinition> apiDefinitions = apiDefinitionMapper.selectByExample(example);
apiDefinitions.forEach(item -> {
LogDTO dto = new LogDTO(
item.getProjectId(),
"",
item.getId(),
item.getCreateUser(),
OperationLogType.UPDATE.name(),
OperationLogModule.API_DEFINITION,
item.getName());
dto.setHistory(true);
dto.setPath("/api/definition/batch-move");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(item));
dtoList.add(dto);
});
}
return dtoList;
public void batchMoveLog(List<String> ids, String userId, String projectId) {
saveBatchLog(projectId, ids, "/api/definition/batch-move", userId, OperationLogType.UPDATE.name(), true);
}
public LogDTO followLog(String id) {
@ -276,31 +215,8 @@ public class ApiDefinitionLogService {
*
* @return
*/
public List<LogDTO> batchRestoreLog(ApiDefinitionBatchRequest request) {
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false);
List<LogDTO> dtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ids)) {
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdIn(ids).andDeletedEqualTo(false);
List<ApiDefinition> apiDefinitions = apiDefinitionMapper.selectByExample(example);
apiDefinitions.forEach(item -> {
LogDTO dto = new LogDTO(
item.getProjectId(),
"",
item.getId(),
item.getCreateUser(),
OperationLogType.UPDATE.name(),
OperationLogModule.API_DEFINITION,
item.getName());
dto.setHistory(true);
dto.setPath("/api/definition/batch-restore");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(item));
dtoList.add(dto);
});
}
return dtoList;
public void batchRestoreLog(List<String> ids, String userId, String projectId) {
saveBatchLog(projectId, ids, "/api/definition/batch-restore", userId, OperationLogType.UPDATE.name(), true);
}
@ -331,31 +247,8 @@ public class ApiDefinitionLogService {
/**
* 删除回收站接口定义接口日志
*/
public List<LogDTO> batchTrashDelLog(ApiDefinitionBatchRequest request) {
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false);
List<LogDTO> dtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ids)) {
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdIn(ids).andDeletedEqualTo(true);
List<ApiDefinition> apiDefinitions = apiDefinitionMapper.selectByExample(example);
apiDefinitions.forEach(item -> {
LogDTO dto = new LogDTO(
item.getProjectId(),
"",
item.getId(),
item.getCreateUser(),
OperationLogType.DELETE.name(),
OperationLogModule.API_DEFINITION,
item.getName());
dto.setPath("/api/definition/batch-trash-del");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(item));
dtoList.add(dto);
});
}
return dtoList;
public void batchTrashDelLog(List<String> ids, String userId, String projectId) {
saveBatchLog(projectId, ids, "/api/definition/batch-trash-del", userId, OperationLogType.DELETE.name(), true);
}
private ApiDefinitionDTO getOriginalValue(String id){
@ -363,12 +256,52 @@ public class ApiDefinitionLogService {
ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(id);
if(null != apiDefinition){
// 2. 使用Optional避免空指针异常
apiDefinitionService.handleBlob(id, apiDefinitionDTO);
handleBlob(id, apiDefinitionDTO);
BeanUtils.copyBean(apiDefinitionDTO, apiDefinition);
}
return apiDefinitionDTO;
}
public void handleBlob(String id, ApiDefinitionDTO apiDefinitionDTO) {
Optional<ApiDefinitionBlob> apiDefinitionBlobOptional = Optional.ofNullable(apiDefinitionBlobMapper.selectByPrimaryKey(id));
apiDefinitionBlobOptional.ifPresent(blob -> {
apiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(blob.getRequest()), AbstractMsTestElement.class));
// blob.getResponse() null 时不进行转换
if (blob.getResponse() != null) {
apiDefinitionDTO.setResponse(ApiDataUtils.parseArray(new String(blob.getResponse()), HttpResponse.class));
}
});
}
private void saveBatchLog(String projectId, List<String> ids, String path, String userId, String operationType, boolean isHistory) {
List<LogDTO> dtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ids)) {
Project project = projectMapper.selectByPrimaryKey(projectId);
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdIn(ids);
List<ApiDefinition> apiDefinitions = apiDefinitionMapper.selectByExample(example);
apiDefinitions.forEach(item -> {
ApiDefinitionDTO apiDefinitionDTO = new ApiDefinitionDTO();
handleBlob(item.getId(), apiDefinitionDTO);
BeanUtils.copyBean(apiDefinitionDTO, item);
LogDTO dto = new LogDTO(
project.getId(),
project.getOrganizationId(),
item.getId(),
userId,
operationType,
OperationLogModule.API_DEFINITION,
item.getName());
dto.setHistory(isHistory);
dto.setPath(path);
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(apiDefinitionDTO));
dtoList.add(dto);
});
operationLogService.batchAdd(dtoList);
}
}
}

View File

@ -1,30 +1,32 @@
package io.metersphere.api.service.definition;
import io.metersphere.api.constants.ApiDefinitionDocType;
import io.metersphere.api.constants.ApiResourceType;
import io.metersphere.api.controller.result.ApiResultCode;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.enums.ApiDefinitionDocType;
import io.metersphere.api.mapper.*;
import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.sdk.constants.ApiReportStatus;
import io.metersphere.sdk.util.*;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.project.service.ProjectService;
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.exception.MSException;
import io.metersphere.sdk.util.*;
import io.metersphere.system.dto.table.TableBatchProcessDTO;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.service.UserLoginService;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.CustomFieldUtils;
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;
@ -39,7 +41,7 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.metersphere.api.controller.result.ApiResultCode.*;
import static io.metersphere.api.controller.result.ApiResultCode.API_DEFINITION_MODULE_NOT_EXIST;
@Service
@Transactional(rollbackFor = Exception.class)
@ -83,7 +85,17 @@ public class ApiDefinitionService {
@Resource
private ApiDefinitionModuleMapper apiDefinitionModuleMapper;
public List<ApiDefinitionDTO> getApiDefinitionPage(ApiDefinitionPageRequest request){
@Resource
private ApiDefinitionCustomFieldMapper apiDefinitionCustomFieldMapper;
@Resource
private ExtApiDefinitionCustomFieldMapper extApiDefinitionCustomFieldMapper;
@Resource
private ApiDefinitionLogService apiDefinitionLogService;
public List<ApiDefinitionDTO> getApiDefinitionPage(ApiDefinitionPageRequest request, String userId){
CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request, userId);
List<ApiDefinitionDTO> list = extApiDefinitionMapper.list(request);
if (!CollectionUtils.isEmpty(list)) {
processApiDefinitions(list, request.getProjectId());
@ -91,7 +103,8 @@ public class ApiDefinitionService {
return list;
}
public List<ApiDefinitionDTO> getDocPage(ApiDefinitionPageRequest request){
public List<ApiDefinitionDTO> getDocPage(ApiDefinitionPageRequest request, String userId){
CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request, userId);
List<ApiDefinitionDTO> list = extApiDefinitionMapper.list(request);
if (!CollectionUtils.isEmpty(list)) {
processApiDefinitionsDoc(list);
@ -159,6 +172,14 @@ public class ApiDefinitionService {
resourceUpdateRequest.setLinkFileIds(request.getLinkFileIds());
apiFileResourceService.addFileResource(resourceUpdateRequest);
//保存自定义字段
Map<String, String> customFields = request.getCustomFields();
if (MapUtils.isNotEmpty(customFields)) {
List<ApiDefinitionCustomField> list = new ArrayList<>();
customFields.keySet().forEach(key -> createNewCustomField(apiDefinition.getId(), key, customFields.get(key), list));
batchInsertCustomFields(list);
}
return apiDefinition;
}
@ -197,6 +218,9 @@ public class ApiDefinitionService {
apiDefinitionBlob.setResponse(request.getResponse().getBytes());
apiDefinitionBlobMapper.updateByPrimaryKeySelective(apiDefinitionBlob);
// 自定义字段
handleUpdateCustomFields(request, false);
// 处理文件
ApiFileResourceUpdateRequest resourceUpdateRequest = getApiFileResourceUpdateRequest(originApiDefinition.getId(), originApiDefinition.getProjectId(), userId);
resourceUpdateRequest.setUploadFileIds(request.getUploadFileIds());
@ -210,11 +234,24 @@ public class ApiDefinitionService {
public void batchUpdate(ApiDefinitionBatchUpdateRequest request, String userId) {
ProjectService.checkResourceExist(request.getProjectId());
List<String> ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false);
List<String> ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false, userId);
// 记录更新前的数据
apiDefinitionLogService.batchUpdateLog(ids, userId, request.getProjectId());
if (CollectionUtils.isNotEmpty(ids)) {
if (request.getType().equals("tags")) {
handleTags(request, userId, ids);
} else {
} else if(request.getType().equals("customs")){
// 自定义字段处理
ApiDefinitionCustomFieldDTO customField = request.getCustomField();
Map<String, String> customFieldMap = Collections.singletonMap(customField.getId(), customField.getValue());
ApiDefinitionUpdateRequest apiDefinitionUpdateRequest = new ApiDefinitionUpdateRequest();
BeanUtils.copyBean(apiDefinitionUpdateRequest, request);
apiDefinitionUpdateRequest.setCustomFields(customFieldMap);
ids.forEach(id -> {
apiDefinitionUpdateRequest.setId(id);
handleUpdateCustomFields(apiDefinitionUpdateRequest, request.isAppend());
});
}else {
ApiDefinition apiDefinition = new ApiDefinition();
BeanUtils.copyBean(apiDefinition, request);
apiDefinition.setUpdateUser(userId);
@ -226,6 +263,69 @@ public class ApiDefinitionService {
}
}
private void handleUpdateCustomFields(ApiDefinitionUpdateRequest request, boolean append) {
Map<String, String> customFields = request.getCustomFields();
if (MapUtils.isNotEmpty(customFields)) {
List<ApiDefinitionCustomField> addFields = new ArrayList<>();
List<ApiDefinitionCustomField> updateFields = new ArrayList<>();
List<ApiDefinitionCustomFieldDTO> originalFields = extApiDefinitionCustomFieldMapper.getApiCustomFields(List.of(request.getId()), request.getProjectId());
Map<String, String> originalFieldMap = originalFields.stream().collect(Collectors.toMap(ApiDefinitionCustomFieldDTO::getId, ApiDefinitionCustomFieldDTO::getValue));
customFields.keySet().forEach(fieldId -> {
if (!originalFieldMap.containsKey(fieldId)) {
// New custom field relationship
createNewCustomField(request.getId(), fieldId, customFields.get(fieldId), addFields);
} else {
// Existing custom field relationship
updateExistingCustomField(request.getId(), fieldId, append, customFields.get(fieldId), updateFields, originalFieldMap);
}
});
batchInsertCustomFields(addFields);
batchUpdateCustomFields(updateFields);
}
}
private void createNewCustomField(String apiId, String fieldId, String value, List<ApiDefinitionCustomField> addFields) {
ApiDefinitionCustomField apiDefinitionCustomField = new ApiDefinitionCustomField();
apiDefinitionCustomField.setApiId(apiId);
apiDefinitionCustomField.setFieldId(fieldId);
apiDefinitionCustomField.setValue(value);
addFields.add(apiDefinitionCustomField);
}
private void updateExistingCustomField(String apiId, String fieldId, boolean append, String value, List<ApiDefinitionCustomField> updateFields, Map<String, String> originalFieldMap) {
ApiDefinitionCustomField apiDefinitionCustomField = new ApiDefinitionCustomField();
apiDefinitionCustomField.setApiId(apiId);
apiDefinitionCustomField.setFieldId(fieldId);
if (append) {
apiDefinitionCustomField.setValue(CustomFieldUtils.appendToMultipleCustomField(originalFieldMap.get(fieldId), value));
} else {
apiDefinitionCustomField.setValue(value);
}
updateFields.add(apiDefinitionCustomField);
}
private void batchInsertCustomFields(List<ApiDefinitionCustomField> addFields) {
if (CollectionUtils.isNotEmpty(addFields)) {
apiDefinitionCustomFieldMapper.batchInsert(addFields);
}
}
private void batchUpdateCustomFields(List<ApiDefinitionCustomField> updateFields) {
if (CollectionUtils.isNotEmpty(updateFields)) {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ApiDefinitionCustomFieldMapper apiCustomFieldMapper = sqlSession.getMapper(ApiDefinitionCustomFieldMapper.class);
for (ApiDefinitionCustomField apiDefinitionCustomField : updateFields) {
ApiDefinitionCustomFieldExample apiDefinitionCustomFieldExample = new ApiDefinitionCustomFieldExample();
apiDefinitionCustomFieldExample.createCriteria().andApiIdEqualTo(apiDefinitionCustomField.getApiId()).andFieldIdEqualTo(apiDefinitionCustomField.getFieldId());
apiCustomFieldMapper.updateByExample(apiDefinitionCustomField, apiDefinitionCustomFieldExample);
}
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
}
public ApiDefinition copy(ApiDefinitionCopyRequest request, String userId) {
ApiDefinition copyApiDefinition = checkApiDefinition(request.getId());
ApiDefinition apiDefinition = new ApiDefinition();
@ -260,21 +360,25 @@ public class ApiDefinitionService {
public void delete(ApiDefinitionDeleteRequest request, String userId) {
checkApiDefinition(request.getId());
handleDeleteApiDefinition(Collections.singletonList(request.getId()),request.getDeleteAll(), request.getProjectId(), userId);
handleDeleteApiDefinition(Collections.singletonList(request.getId()),request.getDeleteAll(), request.getProjectId(), userId, false);
}
public void batchDelete(ApiDefinitionBatchRequest request, String userId) {
List<String> ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false);
List<String> ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false, userId);
if (CollectionUtils.isNotEmpty(ids)) {
handleDeleteApiDefinition(ids, request.getDeleteAll(), request.getProjectId(), userId);
handleDeleteApiDefinition(ids, request.getDeleteAll(), request.getProjectId(), userId, true);
}
}
public void batchMove(ApiDefinitionBatchMoveRequest request, String userId) {
List<String> ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false);
List<String> ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false, userId);
if (!ids.isEmpty()) {
// 移动接口所有版本引用的数据
List<String> refIds = extApiDefinitionMapper.getRefIds(ids, false);
if (!refIds.isEmpty()) {
// 记录批量移动日志
apiDefinitionLogService.batchMoveLog(extApiDefinitionMapper.getIdsByRefId(refIds, false), userId, request.getProjectId());
extApiDefinitionMapper.batchMove(request, refIds, userId);
}
}
@ -287,12 +391,18 @@ public class ApiDefinitionService {
List<ApiCaseComputeDTO> apiCaseComputeList = extApiDefinitionMapper.selectApiCaseByIdsAndStatusIsNotTrash(apiDefinitionIds, projectId);
Map<String, ApiCaseComputeDTO> resultMap = apiCaseComputeList.stream().collect(Collectors.toMap(ApiCaseComputeDTO::getApiDefinitionId, Function.identity()));
List<ApiDefinitionCustomFieldDTO> customFields = extApiDefinitionCustomFieldMapper.getApiCustomFields(apiDefinitionIds, projectId);
Map<String, List<ApiDefinitionCustomFieldDTO>> customFieldMap = customFields.stream().collect(Collectors.groupingBy(ApiDefinitionCustomFieldDTO::getApiId));
list.forEach(item -> {
// Convert User IDs to Names
item.setCreateUserName(userMap.get(item.getCreateUser()));
item.setDeleteUserName(userMap.get(item.getDeleteUser()));
item.setUpdateUserName(userMap.get(item.getUpdateUser()));
// Custom Fields
item.setCustomFields(customFieldMap.get(item.getId()));
// Calculate API Case Metrics
ApiCaseComputeDTO apiCaseComputeDTO = resultMap.get(item.getId());
if (apiCaseComputeDTO != null) {
@ -442,7 +552,7 @@ public class ApiDefinitionService {
return copyName;
}
private void handleDeleteApiDefinition(List<String> ids, boolean deleteAll, String projectId, String userId) {
private void handleDeleteApiDefinition(List<String> ids, boolean deleteAll, String projectId, String userId, boolean isBatch) {
if (deleteAll) {
//全部删除 进入回收站
List<String> refIds = extApiDefinitionMapper.getRefIds(ids, false);
@ -455,13 +565,17 @@ public class ApiDefinitionService {
deleteApiRelatedData(subList, userId, projectId);
}
});
// 记录删除到回收站的日志, 单条注解记录
if(isBatch){
apiDefinitionLogService.batchDelLog(delApiIds, userId, projectId);
}
extApiDefinitionMapper.batchDeleteByRefId(subRefIds, userId, projectId);
});
}
} else {
// 列表删除
if (!ids.isEmpty()) {
SubListUtils.dealForSubList(ids, 2000, subList -> doDelete(subList, userId, projectId));
SubListUtils.dealForSubList(ids, 2000, subList -> doDelete(subList, userId, projectId, isBatch));
}
}
}
@ -496,7 +610,7 @@ public class ApiDefinitionService {
extApiDefinitionMapper.updateLatestVersion(id, projectId);
}
private void doDelete(List<String> ids, String userId, String projectId) {
private void doDelete(List<String> ids, String userId, String projectId, boolean isBatch) {
if(CollectionUtils.isNotEmpty(ids)){
// 需要判断是否存在多个版本问题
ids.forEach(id -> {
@ -511,6 +625,11 @@ public class ApiDefinitionService {
});
// 删除 case
deleteApiRelatedData(ids, userId, projectId);
// 记录删除到回收站的日志, 单条注解记录
if(isBatch){
apiDefinitionLogService.batchDelLog(ids, userId, projectId);
}
// 删除接口到回收站
extApiDefinitionMapper.batchDeleteById(ids, userId, projectId);
}
@ -531,16 +650,20 @@ public class ApiDefinitionService {
public void restore(ApiDefinitionDeleteRequest request, String userId) {
// 恢复接口到接口列表
handleRestoreApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId());
handleRestoreApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId(), false);
}
private void handleRestoreApiDefinition(List<String> ids, String userId, String projectId){
private void handleRestoreApiDefinition(List<String> ids, String userId, String projectId, boolean isBatch){
if (CollectionUtils.isNotEmpty(ids)) {
SubListUtils.dealForSubList(ids, 2000, subList -> doRestore(subList, userId, projectId));
SubListUtils.dealForSubList(ids, 2000, subList -> doRestore(subList, userId, projectId, isBatch));
}
}
private void doRestore(List<String> apiIds, String userId, String projectId) {
private void doRestore(List<String> apiIds, String userId, String projectId, boolean isBatch) {
if (CollectionUtils.isNotEmpty(apiIds)) {
// 记录恢复数据之前的原数据单条通过注解记录
if(isBatch){
apiDefinitionLogService.batchRestoreLog(apiIds, userId, projectId);
}
extApiDefinitionMapper.batchRestoreById(apiIds, userId, projectId);
apiIds.forEach(id -> {
@ -550,16 +673,7 @@ public class ApiDefinitionService {
List<ApiDefinitionVersionDTO> apiDefinitionVersions = extApiDefinitionMapper.getApiDefinitionByRefId(apiDefinition.getRefId());
if (CollectionUtils.isNotEmpty(apiDefinitionVersions) && apiDefinitionVersions.size() > 1) {
String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(apiDefinition.getProjectId());
// 清除所有最新标识
clearLatestVersion(apiDefinition.getRefId(), apiDefinition.getProjectId());
// 获取最新数据恢复的数据最新标识则最新数据反之获取最新一条数据
ApiDefinition latestData = apiDefinition.getLatest() ? apiDefinition : getLatestData(apiDefinition.getRefId(), apiDefinition.getProjectId());
// 恢复的数据不为最新标识同时接口版本为默认版本则更新此数据为最新标识
if (!latestData.getLatest() && latestData.getVersionId().equals(defaultVersion)) {
updateLatestVersion(apiDefinition.getId(), apiDefinition.getProjectId());
}
handleMultipleVersions(apiDefinition);
}
});
// 恢复接口关联数据
@ -567,6 +681,19 @@ public class ApiDefinitionService {
}
}
private void handleMultipleVersions(ApiDefinition apiDefinition) {
String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(apiDefinition.getProjectId());
// 清除所有最新标识
clearLatestVersion(apiDefinition.getRefId(), apiDefinition.getProjectId());
// 获取最新数据恢复的数据最新标识则最新数据反之获取最新一条数据
ApiDefinition latestData = apiDefinition.getLatest() ? apiDefinition : getLatestData(apiDefinition.getRefId(), apiDefinition.getProjectId());
// 恢复的数据不为最新标识同时接口版本为默认版本则更新此数据为最新标识
if (!latestData.getLatest() && latestData.getVersionId().equals(defaultVersion)) {
updateLatestVersion(apiDefinition.getId(), apiDefinition.getProjectId());
}
}
private void recoverApiRelatedData(List<String> apiIds, String userId, String projectId){
// 是否存在 case 恢复 case
List<ApiTestCase> caseLists = extApiTestCaseMapper.getCaseInfoByApiIds(apiIds, true);
@ -575,30 +702,30 @@ public class ApiDefinitionService {
}
}
public void trashDel(ApiDefinitionDeleteRequest request, String userId) {
handleTrashDelApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId());
handleTrashDelApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId(), false);
}
public void batchRestore(ApiDefinitionBatchRequest request, String userId) {
List<String> ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), true);
List<String> ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), true, userId);
if (CollectionUtils.isNotEmpty(ids)) {
handleRestoreApiDefinition(ids, userId, request.getProjectId());
handleRestoreApiDefinition(ids, userId, request.getProjectId(), true);
}
}
public void batchTrashDel(ApiDefinitionBatchRequest request, String userId) {
List<String> ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), true);
List<String> ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), true, userId);
if (CollectionUtils.isNotEmpty(ids)) {
handleTrashDelApiDefinition(ids, userId, request.getProjectId());
handleTrashDelApiDefinition(ids, userId, request.getProjectId(), true);
}
}
private void handleTrashDelApiDefinition(List<String> ids, String userId, String projectId){
private void handleTrashDelApiDefinition(List<String> ids, String userId, String projectId, boolean isBatch){
if (CollectionUtils.isNotEmpty(ids)) {
SubListUtils.dealForSubList(ids, 2000, subList -> doTrashDel(subList, userId, projectId));
SubListUtils.dealForSubList(ids, 2000, subList -> doTrashDel(subList, userId, projectId, isBatch));
}
}
private void doTrashDel(List<String> ids, String userId, String projectId){
private void doTrashDel(List<String> ids, String userId, String projectId, boolean isBatch){
if(CollectionUtils.isNotEmpty(ids)){
// 删除上传的文件
ids.forEach(id -> {
@ -613,10 +740,15 @@ public class ApiDefinitionService {
// 删除接口关联数据
trashDelApiRelatedData(ids, userId, projectId);
// 记录批量删除日志单条删除通过注解记录
if(isBatch){
apiDefinitionLogService.batchTrashDelLog(ids, userId, projectId);
}
// 删除接口
ApiDefinitionExample apiDefinitionExample = new ApiDefinitionExample();
apiDefinitionExample.createCriteria().andIdIn(ids).andDeletedEqualTo(true).andProjectIdEqualTo(projectId);
apiDefinitionMapper.deleteByExample(apiDefinitionExample);
}
}
@ -632,9 +764,11 @@ public class ApiDefinitionService {
}
// 获取批量操作选中的ID
public <T> List<String> getBatchApiIds(T dto, String projectId, String protocol, boolean deleted) {
public <T> List<String> getBatchApiIds(T dto, String projectId, String protocol, boolean deleted, String userId) {
TableBatchProcessDTO request = (TableBatchProcessDTO) dto;
if (request.isSelectAll()) {
// 全选
CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request.getCondition(), userId);
List<String> ids = extApiDefinitionMapper.getIds(request, projectId, protocol, deleted);
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
ids.removeAll(request.getExcludeIds());
@ -652,7 +786,9 @@ public class ApiDefinitionService {
public ApiDefinitionDTO getApiDefinitionInfo(String id, String userId, ApiDefinition apiDefinition) {
ApiDefinitionDTO apiDefinitionDTO = new ApiDefinitionDTO();
// 2. 使用Optional避免空指针异常
handleBlob(id, apiDefinitionDTO);
apiDefinitionLogService.handleBlob(id, apiDefinitionDTO);
// 3. 查询自定义字段
handleCustomFields(id, apiDefinition.getProjectId(), apiDefinitionDTO);
// 3. 使用Stream简化集合操作
ApiDefinitionFollowerExample example = new ApiDefinitionFollowerExample();
example.createCriteria().andApiDefinitionIdEqualTo(id).andUserIdEqualTo(userId);
@ -661,15 +797,10 @@ public class ApiDefinitionService {
return apiDefinitionDTO;
}
public void handleBlob(String id, ApiDefinitionDTO apiDefinitionDTO) {
Optional<ApiDefinitionBlob> apiDefinitionBlobOptional = Optional.ofNullable(apiDefinitionBlobMapper.selectByPrimaryKey(id));
apiDefinitionBlobOptional.ifPresent(blob -> {
apiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(blob.getRequest()), AbstractMsTestElement.class));
// blob.getResponse() null 时不进行转换
if (blob.getResponse() != null) {
apiDefinitionDTO.setResponse(ApiDataUtils.parseArray(new String(blob.getResponse()), HttpResponse.class));
}
});
public void handleCustomFields(String id, String projectId, ApiDefinitionDTO apiDefinitionDTO) {
List<ApiDefinitionCustomFieldDTO> customFields = extApiDefinitionCustomFieldMapper.getApiCustomFields(Collections.singletonList(id), projectId);
Map<String, List<ApiDefinitionCustomFieldDTO>> customFieldMap = customFields.stream().collect(Collectors.groupingBy(ApiDefinitionCustomFieldDTO::getApiId));
apiDefinitionDTO.setCustomFields(customFieldMap.get(id));
}
public ApiDefinitionDocDTO getDocInfo(ApiDefinitionDocRequest request, String userId) {
@ -680,7 +811,7 @@ public class ApiDefinitionService {
List<ApiDefinitionDTO> list = extApiDefinitionMapper.listDoc(request);
if (!list.isEmpty()) {
ApiDefinitionDTO first = list.get(0);
handleBlob(first.getId(), first);
apiDefinitionLogService.handleBlob(first.getId(), first);
if(ApiDefinitionDocType.ALL.name().equals(request.getType())){
apiDefinitionDocDTO.setDocTitle(Translator.get(ALL_API));
} else {

View File

@ -99,6 +99,7 @@
<table tableName="api_test_case_follower"/>
<table tableName="api_definition_mock"/>
<table tableName="api_definition_mock_config"/>
<table tableName="api_definition_custom_field"/>
</context>

View File

@ -5,7 +5,7 @@ import io.metersphere.api.controller.result.ApiResultCode;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.enums.ApiDefinitionDocType;
import io.metersphere.api.constants.ApiDefinitionDocType;
import io.metersphere.api.mapper.*;
import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.utils.ApiDataUtils;
@ -28,8 +28,8 @@ import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
@ -40,6 +40,7 @@ import org.springframework.test.web.servlet.MvcResult;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import static io.metersphere.api.controller.result.ApiResultCode.API_DEFINITION_NOT_EXIST;
import static io.metersphere.system.controller.handler.result.MsHttpResultCode.NOT_FOUND;
@ -101,6 +102,9 @@ public class ApiDefinitionControllerTests extends BaseTest {
@Resource
private ApiDefinitionModuleMapper apiDefinitionModuleMapper;
@Resource
private ExtApiDefinitionCustomFieldMapper extApiDefinitionCustomFieldMapper;
@Resource
private FileMetadataService fileMetadataService;
private static String fileMetadataId;
@ -197,7 +201,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
// 创建并返回一个 ApiDefinitionAddRequest 对象用于测试
String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(DEFAULT_PROJECT_ID);
ApiDefinitionAddRequest request = new ApiDefinitionAddRequest();
request.setName("接口定义");
request.setName("接口定义test");
request.setProtocol("HTTP");
request.setProjectId(DEFAULT_PROJECT_ID);
request.setMethod("POST");
@ -207,6 +211,11 @@ public class ApiDefinitionControllerTests extends BaseTest {
request.setVersionId(defaultVersion);
request.setDescription("描述内容");
request.setTags(new LinkedHashSet<>(List.of("tag1", "tag2")));
Map<String, String> customFieldMap = new HashMap<>();
customFieldMap.put("custom-field", "oasis");
customFieldMap.put("test_field", JSON.toJSONString(List.of("test")));
request.setCustomFields(customFieldMap);
return request;
}
@ -241,6 +250,12 @@ public class ApiDefinitionControllerTests extends BaseTest {
example.createCriteria().andApiDefinitionIdEqualTo(apiDefinition.getId()).andUserIdEqualTo("admin");
List<ApiDefinitionFollower> followers = apiDefinitionFollowerMapper.selectByExample(example);
copyApiDefinitionDTO.setFollow(CollectionUtils.isNotEmpty(followers));
List<ApiDefinitionCustomFieldDTO> customFields = extApiDefinitionCustomFieldMapper.getApiCustomFields(Collections.singletonList(apiDefinition.getId()), apiDefinition.getProjectId());
if(!customFields.isEmpty()) {
Map<String, List<ApiDefinitionCustomFieldDTO>> customFieldMap = customFields.stream().collect(Collectors.groupingBy(ApiDefinitionCustomFieldDTO::getApiId));
copyApiDefinitionDTO.setCustomFields(customFieldMap.get(apiDefinition.getId()));
}
if(apiDefinitionBlob != null){
copyApiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(apiDefinitionBlob.getRequest()), AbstractMsTestElement.class));
copyApiDefinitionDTO.setResponse(ApiDataUtils.parseArray(new String(apiDefinitionBlob.getResponse()), HttpResponse.class));
@ -268,6 +283,11 @@ public class ApiDefinitionControllerTests extends BaseTest {
request.setMethod("POST");
request.setModuleId("default1");
request.setTags(new LinkedHashSet<>(List.of("tag1", "tag2-update")));
Map<String, String> customFieldMap = new HashMap<>();
customFieldMap.put("custom-field", "oasis-update");
customFieldMap.put("test_field", JSON.toJSONString(List.of("test-update")));
request.setCustomFields(customFieldMap);
MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement();
request.setRequest(ApiDataUtils.toJSONString(msHttpElement));
List<HttpResponse> msHttpResponse = MsHTTPElementTest.getMsHttpResponse();
@ -424,6 +444,18 @@ public class ApiDefinitionControllerTests extends BaseTest {
apiDefinitionBatchUpdateRequest.setAppend(false);
this.requestPostWithOk(BATCH_UPDATE, apiDefinitionBatchUpdateRequest);
assertBatchUpdateApiDefinition(apiDefinitionBatchUpdateRequest, List.of("1003","1004"));
// 自定义字段追加
apiDefinitionBatchUpdateRequest.setType("customs");
apiDefinitionBatchUpdateRequest.setSelectIds(List.of("1002","1003","1004"));
ApiDefinitionCustomFieldDTO field = new ApiDefinitionCustomFieldDTO();
field.setId("test_field");
field.setValue(JSON.toJSONString(List.of("test1-batch")));
apiDefinitionBatchUpdateRequest.setCustomField(field);
apiDefinitionBatchUpdateRequest.setAppend(true);
this.requestPostWithOk(BATCH_UPDATE, apiDefinitionBatchUpdateRequest);
// 自定义字段覆盖
apiDefinitionBatchUpdateRequest.setAppend(false);
this.requestPostWithOk(BATCH_UPDATE, apiDefinitionBatchUpdateRequest);
// 修改协议类型
apiDefinitionBatchUpdateRequest.setType("method");
apiDefinitionBatchUpdateRequest.setMethod("batch-method");
@ -662,13 +694,30 @@ public class ApiDefinitionControllerTests extends BaseTest {
filters.put("status", Arrays.asList("Underway", "Completed"));
filters.put("method", List.of("GET"));
filters.put("version_id", List.of("1005704995741369851"));
filters.put("custom_multiple_custom-field", List.of("oasis"));
request.setFilter(filters);
}
private void configureCombineSearch(ApiDefinitionPageRequest request) {
Map<String, Object> map = new HashMap<>();
map.put("name", Map.of("operator", "like", "value", "test-1"));
map.put("name", Map.of("operator", "like", "value", "test"));
map.put("method", Map.of("operator", "in", "value", Arrays.asList("GET", "POST")));
map.put("createUser", Map.of("operator", "current user", "value", StringUtils.EMPTY));
List<Map<String, Object>> customs = new ArrayList<>();
Map<String, Object> custom = new HashMap<>();
custom.put("id", "test_field");
custom.put("operator", "in");
custom.put("type", "multipleSelect");
custom.put("value", JSON.toJSONString(List.of("test", "default")));
customs.add(custom);
Map<String, Object> currentUserCustom = new HashMap<>();
currentUserCustom.put("id", "test_field");
currentUserCustom.put("operator", "current user");
currentUserCustom.put("type", "multipleMember");
currentUserCustom.put("value", "current user");
customs.add(currentUserCustom);
map.put("customs", customs);
request.setCombine(map);
}
@ -969,7 +1018,6 @@ public class ApiDefinitionControllerTests extends BaseTest {
// @@校验日志
checkLog("1002", OperationLogType.UPDATE);
checkLog("1004", OperationLogType.UPDATE);
checkLog("1005", OperationLogType.UPDATE);
// 恢复全部 条件为关键字为st-6的数据
request.setSelectAll(true);

View File

@ -42,3 +42,17 @@ INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1003',
INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1004', 0x504B03041400080808004A547857000000000000000000000000030000007A6970CD92B14EC3301040FFE5E6A8296B36842A180055281B62709D4B6D61FBDCF3991055FD779C840C949185C5A77BB6CFEF6C9F21921B3D7134563F2B8FD0C0537A68DBFDCEA1C720504158F01C2AC0A00EAEE4BD72092BD0C6BA8E314013B27315647665AD11894D5D0FC3B0114CB2D1E4CBD6A8C494C97A4225F52886BA02EE776D490FD48D6B1583AA434ED0BC9EE11D0B9EC70A3E94CB93CB127FC9749834DB28968ACF9CC1E5AD029ECEFB7BA9A90156BE1D23AE9E8CA76C194B13C2B9ECF2363C62384E6D2E0BBCFAFC093068EAD66326B953461EFFAD1D8941BEA3D0DB233467D014026A69AD47CAE54E6FB6DBED7CBF9142C22BAC91C5F6562BC15B6755794D5073ACA027E76878C1AEE86949ABA0CA4257F03253B32A2C9A9171CFA431259A3EC937A424D7F4F205504B070852639A522B010000E0020000504B010214001400080808004A54785752639A522B010000E00200000300000000000000000000000000000000007A6970504B05060000000001000100310000005C0100000000, 0x504B03041400080808004A547857000000000000000000000000030000007A6970A58F414BC4301085FFCB9C03B69EA4B74559141465295E9622B3CD743798263199C82EA5FFDD49EB82772F19DECB9BEF25FB098C86A656C09740D0C063DBBE81028763513B4AC1BB44B55827424D3141B39FE0932E725B4E05DF6873C9AE5301393C583106B48914684A7D34818D77122A0AE64EC1C16B414CCB6CD7EAEDEBEEE5E361D36E0432443FBE17E03FFB98124B2660C4F1B786E95CAC485FD94492CF73CCB2371AF74CEEC82768EE44E1F9AAEA4A41EF1D93E33F849B60D1B8A5BEF79AAE94C1587A12A6CBD6CEDDAC2031724EF74B046EAB0ACA0B07CC96B7168FEBDADCFD00504B0708FF595995E400000087010000504B010214001400080808004A547857FF595995E4000000870100000300000000000000000000000000000000007A6970504B0506000000000100010031000000150100000000);
INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1005', 0x504B030414000808080049547857000000000000000000000000030000007A6970CD92B14EC3301040FFE5E6A8296B36842A180055281B62709D4B6D61FBDCF3991055FD779C840C949185C5A77BB6CFEF6C9F21921B3D7134563F2B8FD0C0537A68DBFDCEA1C720504158F01C2AC0A00EAEE4BD72092BD0C6BA8E314013B27315647665AD11894D5D0FC3B0114CB2D1E4CBD6A8C494C97A4225F52886BA02EE776D490FD48D6B1583AA434ED0BC9EE11D0B9EC70A3E94CB93CB127FC9749834DB28968ACF9CC1E5AD029ECEFB7BA9A90156BE1D23AE9E8CA76C194B13C2B9ECF2363C62384E6D2E0BBCFAFC093068EAD66326B953461EFFAD1D8941BEA3D0DB233467D014026A69AD47CAE54E6FB6DBED7CBF9142C22BAC91C5F6562BC15B6755794D5073ACA027E76878C1AEE86949ABA0CA4257F03253B32A2C9A9171CFA431259A3EC937A424D7F4F205504B070852639A522B010000E0020000504B010214001400080808004954785752639A522B010000E00200000300000000000000000000000000000000007A6970504B05060000000001000100310000005C0100000000, 0x504B030414000808080049547857000000000000000000000000030000007A6970A58F414BC4301085FFCB9C03B69EA4B74559141465295E9622B3CD743798263199C82EA5FFDD49EB82772F19DECB9BEF25FB098C86A656C09740D0C063DBBE81028763513B4AC1BB44B55827424D3141B39FE0932E725B4E05DF6873C9AE5301393C583106B48914684A7D34818D77122A0AE64EC1C16B414CCB6CD7EAEDEBEEE5E361D36E0432443FBE17E03FFB98124B2660C4F1B786E95CAC485FD94492CF73CCB2371AF74CEEC82768EE44E1F9AAEA4A41EF1D93E33F849B60D1B8A5BEF79AAE94C1587A12A6CBD6CEDDAC2031724EF74B046EAB0ACA0B07CC96B7168FEBDADCFD00504B0708FF595995E400000087010000504B0102140014000808080049547857FF595995E4000000870100000300000000000000000000000000000000007A6970504B0506000000000100010031000000150100000000);
INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1006', 0x504B030414000808080049547857000000000000000000000000030000007A6970CD92B14EC3301040FFE5E6A8296B36842A180055281B62709D4B6D61FBDCF3991055FD779C840C949185C5A77BB6CFEF6C9F21921B3D7134563F2B8FD0C0537A68DBFDCEA1C720504158F01C2AC0A00EAEE4BD72092BD0C6BA8E314013B27315647665AD11894D5D0FC3B0114CB2D1E4CBD6A8C494C97A4225F52886BA02EE776D490FD48D6B1583AA434ED0BC9EE11D0B9EC70A3E94CB93CB127FC9749834DB28968ACF9CC1E5AD029ECEFB7BA9A90156BE1D23AE9E8CA76C194B13C2B9ECF2363C62384E6D2E0BBCFAFC093068EAD66326B953461EFFAD1D8941BEA3D0DB233467D014026A69AD47CAE54E6FB6DBED7CBF9142C22BAC91C5F6562BC15B6755794D5073ACA027E76878C1AEE86949ABA0CA4257F03253B32A2C9A9171CFA431259A3EC937A424D7F4F205504B070852639A522B010000E0020000504B010214001400080808004954785752639A522B010000E00200000300000000000000000000000000000000007A6970504B05060000000001000100310000005C0100000000, 0x504B030414000808080049547857000000000000000000000000030000007A6970A58F414BC4301085FFCB9C03B69EA4B74559141465295E9622B3CD743798263199C82EA5FFDD49EB82772F19DECB9BEF25FB098C86A656C09740D0C063DBBE81028763513B4AC1BB44B55827424D3141B39FE0932E725B4E05DF6873C9AE5301393C583106B48914684A7D34818D77122A0AE64EC1C16B414CCB6CD7EAEDEBEEE5E361D36E0432443FBE17E03FFB98124B2660C4F1B786E95CAC485FD94492CF73CCB2371AF74CEEC82768EE44E1F9AAEA4A41EF1D93E33F849B60D1B8A5BEF79AAE94C1587A12A6CBD6CEDDAC2031724EF74B046EAB0ACA0B07CC96B7168FEBDADCFD00504B0708FF595995E400000087010000504B0102140014000808080049547857FF595995E4000000870100000300000000000000000000000000000000007A6970504B0506000000000100010031000000150100000000);
DELETE FROM `api_definition_custom_field` WHERE `api_id` in ('1003', '1004');
INSERT INTO api_definition_custom_field (api_id, field_id, value) VALUE ('1003', 'test_field', '["default", "default-1"]');
INSERT INTO api_definition_custom_field (api_id, field_id, value) VALUE ('1004', 'test_field', '[]');
DELETE FROM `custom_field` WHERE `id` in ('test_field', 'custom-field');
INSERT INTO custom_field (id, name, scene, type, remark, internal, scope_type, create_time, update_time, create_user, scope_id) VALUE
('test_field', '测试字段', 'API', 'MULTIPLE_SELECT', '', 0, 'PROJECT', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', '100001100001'),
('custom-field', '测试字段', 'API', 'SELECT', '', 0, 'PROJECT', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', '100001100001');
DELETE FROM `template` WHERE `id` in ('api-template-id', 'default-api-template-id');
INSERT INTO template (id, name, remark, internal, update_time, create_time, create_user, scope_type, scope_id, enable_third_part, scene) VALUES
('api-template-id', 'api-template', '', 0, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', '100001100001', 0, 'API'),
('default-api-template-id', 'api-default-template', '', 0, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', '100001100001', 0, 'API');

View File

@ -0,0 +1,105 @@
package io.metersphere.system.utils;
import io.metersphere.sdk.constants.CustomFieldType;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.dto.sdk.BaseCondition;
import lombok.experimental.UtilityClass;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 自定义字段处理工具类
*/
@UtilityClass
public class CustomFieldUtils {
public static final String COMBINE_CUSTOM = "customs";
public static final String COMBINE_CUSTOM_FIELD_TYPE = "type";
public static final String COMBINE_CUSTOM_FIELD_VALUE = "value";
public static final String COMBINE_CUSTOM_FIELD_OPERATOR = "operator";
public static final String IS_CURRENT_USER = "current user";
public static final String CUSTOM_MULTIPLE_PREFIX = "custom_multiple";
/**
* 设置列表查询的多选字段参数
* @param request 请求参数
*/
public static void setBaseQueryRequestCustomMultipleFields(BaseCondition request, String userId) {
handleFilterCustomMultipleFields(request);
handleCombineFields(request, userId);
}
private static void handleFilterCustomMultipleFields(BaseCondition request) {
if (MapUtils.isNotEmpty(request.getFilter())) {
request.getFilter().forEach((key, value) -> {
if (key.startsWith(CUSTOM_MULTIPLE_PREFIX) && CollectionUtils.isNotEmpty(value)) {
List<String> jsonValues = value.stream().map(item -> "[\"".concat(item).concat("\"]")).toList();
request.getFilter().put(key, jsonValues);
}
});
}
}
private static void handleCombineFields(BaseCondition request, String userId) {
Map<String, Object> combine = request.getCombine();
if (MapUtils.isNotEmpty(combine)) {
combine.forEach((k, v) -> {
if (StringUtils.equals(k, COMBINE_CUSTOM) && ObjectUtils.isNotEmpty(v)) {
handleCombineCustomFields((List<Map<String, Object>>) v, userId);
} else {
handleCombineField(v, userId);
}
});
}
}
private static void handleCombineCustomFields(List<Map<String, Object>> customs, String userId) {
customs.forEach(custom -> {
String operator = custom.get(COMBINE_CUSTOM_FIELD_OPERATOR).toString();
if (StringUtils.equalsIgnoreCase(operator, IS_CURRENT_USER)) {
custom.put(COMBINE_CUSTOM_FIELD_VALUE, userId);
return;
}
String fieldType = custom.get(COMBINE_CUSTOM_FIELD_TYPE).toString();
String fieldValue = custom.get(COMBINE_CUSTOM_FIELD_VALUE).toString();
if (StringUtils.equalsAny(fieldType, CustomFieldType.MULTIPLE_MEMBER.getType(),
CustomFieldType.CHECKBOX.getType(), CustomFieldType.MULTIPLE_SELECT.getType()) && StringUtils.isNotEmpty(fieldValue)) {
List<String> customValues = JSON.parseArray(fieldValue, String.class);
List<String> jsonValues = customValues.stream().map(item -> "JSON_CONTAINS(`value`, '[\"".concat(item).concat("\"]')")).toList();
custom.put(COMBINE_CUSTOM_FIELD_VALUE, "(".concat(StringUtils.join(jsonValues, " OR ")).concat(")"));
}
});
}
private static void handleCombineField(Object v, String userId) {
Map<String, Object> combineField = new HashMap<>((Map<String, Object>) v);
if (StringUtils.equalsIgnoreCase(combineField.get(COMBINE_CUSTOM_FIELD_OPERATOR).toString(), IS_CURRENT_USER)) {
combineField.put(COMBINE_CUSTOM_FIELD_VALUE, userId);
}
}
/**
* 多选字段追加值
* @param originalVal 原始值
* @param appendVal 追加值
* @return 追加后的值
*/
public static String appendToMultipleCustomField(String originalVal, String appendVal) {
if (StringUtils.isEmpty(originalVal)) {
return appendVal;
}
List<String> orignalList = JSON.parseArray(originalVal, String.class);
List<String> appendList = JSON.parseArray(appendVal, String.class);
orignalList.addAll(appendList);
// 追加后需去重
return JSON.toJSONString(orignalList.stream().distinct().toList());
}
}

View File

@ -0,0 +1,98 @@
package io.metersphere.system.controller;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.dto.sdk.BaseCondition;
import io.metersphere.system.utils.CustomFieldUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.*;
/**
* @author: LAN
* @date: 2023/12/14 10:18
* @version: 1.0
*/
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class CustomFieldTests extends BaseTest {
@Test
@Order(1)
void testSetBaseQueryRequestCustomMultipleFields() {
BaseCondition baseCondition = new BaseCondition();
Map<String, List<String>> filters = new HashMap<>();
filters.put("status", Arrays.asList("Underway", "Completed"));
filters.put("custom_multiple_custom-field", List.of("oasis"));
Map<String, Object> map = new HashMap<>();
map.put("name", Map.of("operator", "like", "value", "test"));
map.put("method", Map.of("operator", "in", "value", Arrays.asList("GET", "POST")));
map.put("createUser", Map.of("operator", "current user", "value", StringUtils.EMPTY));
List<Map<String, Object>> customs = new ArrayList<>();
Map<String, Object> custom = new HashMap<>();
custom.put("id", "test_field");
custom.put("operator", "in");
custom.put("type", "multipleSelect");
custom.put("value", JSON.toJSONString(List.of("test", "default")));
customs.add(custom);
Map<String, Object> currentUserCustom = new HashMap<>();
currentUserCustom.put("id", "test_field");
currentUserCustom.put("operator", "current user");
currentUserCustom.put("type", "multipleMember");
currentUserCustom.put("value", "current user");
customs.add(currentUserCustom);
map.put("customs", customs);
baseCondition.setFilter(filters);
baseCondition.setCombine(map);
// 调用测试方法
CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(baseCondition, "admin");
// 验证预期结果
Assertions.assertNotNull(baseCondition.getFilter());
Assertions.assertNotNull(baseCondition.getCombine());
// 验证多选字段是否被正确处理
List<String> customMultipleValues = baseCondition.getFilter().get("custom_multiple_custom-field");
Assertions.assertNotNull(customMultipleValues);
Assertions.assertEquals(1, customMultipleValues.size());
Assertions.assertTrue(customMultipleValues.contains("[\"oasis\"]"));
}
@Test
@Order(2)
void testAppendToMultipleCustomFieldWithEmptyOriginalValue() {
// 创建测试数据originalValue 为空
String originalValue = "";
String appendValue = "[\"value2\",\"value3\"]";
// 调用被测试方法
String resultEmptyOriginal = CustomFieldUtils.appendToMultipleCustomField(originalValue, appendValue);
// 验证预期结果
Assertions.assertNotNull(resultEmptyOriginal);
Assertions.assertEquals("[\"value2\",\"value3\"]", resultEmptyOriginal);
}
@Test
@Order(3)
void testAppendToMultipleCustomField() {
// 创建测试数据
String originalValue = "[\"value1\", \"value2\"]";
String appendValue = "[\"value2\", \"value3\"]";
// 调用被测试方法
String result = CustomFieldUtils.appendToMultipleCustomField(originalValue, appendValue);
// 验证预期结果
Assertions.assertNotNull(result);
Assertions.assertEquals("[\"value1\",\"value2\",\"value3\"]", result);
}
}