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 '创建时间' , `create_time` BIGINT NOT NULL COMMENT '创建时间' ,
`project_id` VARCHAR(50) NOT NULL COMMENT '项目ID' , `project_id` VARCHAR(50) NOT NULL COMMENT '项目ID' ,
PRIMARY KEY (resource_id,file_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 innodb lock wait timeout to default
SET SESSION innodb_lock_wait_timeout = 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.id.not_blank=ID不能为空
api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间 api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间
api_environment_config.environment_id.not_blank=环境ID不能为空 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=模块不存在 api_module.not.exist=模块不存在
permission.api.name=接口测试 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.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.length_range=Environment ID length must be between 1-50
api_environment_config.environment_id.not_blank=Environment ID cannot be blank 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 api_module.not.exist=The module does not exist
permission.api.name=API Test permission.api.name=API Test

View File

@ -297,6 +297,11 @@ api_unplanned_request=未规划接口
api_environment_config.id.not_blank=ID不能为空 api_environment_config.id.not_blank=ID不能为空
api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间 api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间
api_environment_config.environment_id.not_blank=环境ID不能为空 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=模块不存在 api_module.not.exist=模块不存在
permission.api.name=接口测试 permission.api.name=接口测试

View File

@ -297,6 +297,11 @@ api_unplanned_request=未规划接口
api_environment_config.id.not_blank=ID不能為空 api_environment_config.id.not_blank=ID不能為空
api_environment_config.environment_id.length_range=環境ID長度必須在1-50之間 api_environment_config.environment_id.length_range=環境ID長度必須在1-50之間
api_environment_config.environment_id.not_blank=環境ID不能為空 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=模塊不存在 api_module.not.exist=模塊不存在
permission.api.name=接口測試 permission.api.name=接口測試

View File

@ -1,4 +1,4 @@
package io.metersphere.api.enums; package io.metersphere.api.constants;
/** /**
* @author: LAN * @author: LAN

View File

@ -57,8 +57,6 @@ public class ApiDefinitionController {
@PostMapping(value = "/batch-update") @PostMapping(value = "/batch-update")
@Operation(summary = "接口测试-接口管理-批量更新接口定义") @Operation(summary = "接口测试-接口管理-批量更新接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE) @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) { public void batchUpdate(@Validated @RequestBody ApiDefinitionBatchUpdateRequest request) {
apiDefinitionService.batchUpdate(request, SessionUtils.getUserId()); apiDefinitionService.batchUpdate(request, SessionUtils.getUserId());
} }
@ -73,7 +71,6 @@ public class ApiDefinitionController {
@PostMapping(value = "/batch-del") @PostMapping(value = "/batch-del")
@Operation(summary = "接口测试-接口管理-批量删除接口定义到回收站") @Operation(summary = "接口测试-接口管理-批量删除接口定义到回收站")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE) @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE)
@Log(type = OperationLogType.DELETE, expression = "#msClass.batchDelLog(#request)", msClass = ApiDefinitionLogService.class)
public void batchDelete(@Validated @RequestBody ApiDefinitionBatchRequest request) { public void batchDelete(@Validated @RequestBody ApiDefinitionBatchRequest request) {
apiDefinitionService.batchDelete(request, SessionUtils.getUserId()); apiDefinitionService.batchDelete(request, SessionUtils.getUserId());
} }
@ -89,7 +86,6 @@ public class ApiDefinitionController {
@PostMapping("/batch-move") @PostMapping("/batch-move")
@Operation(summary = "接口测试-接口管理-批量移动接口定义") @Operation(summary = "接口测试-接口管理-批量移动接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE) @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE)
@Log(type = OperationLogType.UPDATE, expression = "#msClass.batchMoveLog(#request)", msClass = ApiDefinitionLogService.class)
public void batchMove(@Validated @RequestBody ApiDefinitionBatchMoveRequest request) { public void batchMove(@Validated @RequestBody ApiDefinitionBatchMoveRequest request) {
apiDefinitionService.batchMove(request, SessionUtils.getUserId()); apiDefinitionService.batchMove(request, SessionUtils.getUserId());
} }
@ -122,7 +118,7 @@ public class ApiDefinitionController {
public Pager<List<ApiDefinitionDTO>> getPage(@Validated @RequestBody ApiDefinitionPageRequest request) { public Pager<List<ApiDefinitionDTO>> getPage(@Validated @RequestBody ApiDefinitionPageRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(), Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc"); 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") @PostMapping(value = "/restore")
@ -142,7 +138,6 @@ public class ApiDefinitionController {
@PostMapping(value = "/batch-restore") @PostMapping(value = "/batch-restore")
@Operation(summary = "接口测试-接口管理-批量从回收站恢复接口定义") @Operation(summary = "接口测试-接口管理-批量从回收站恢复接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE) @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE)
@Log(type = OperationLogType.UPDATE, expression = "#msClass.batchRestoreLog(#request)", msClass = ApiDefinitionLogService.class)
public void batchRestore(@Validated @RequestBody ApiDefinitionBatchRequest request) { public void batchRestore(@Validated @RequestBody ApiDefinitionBatchRequest request) {
apiDefinitionService.batchRestore(request, SessionUtils.getUserId()); apiDefinitionService.batchRestore(request, SessionUtils.getUserId());
} }
@ -150,7 +145,6 @@ public class ApiDefinitionController {
@PostMapping(value = "/batch-trash-del") @PostMapping(value = "/batch-trash-del")
@Operation(summary = "接口测试-接口管理-批量从回收站删除接口定义") @Operation(summary = "接口测试-接口管理-批量从回收站删除接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE) @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE)
@Log(type = OperationLogType.UPDATE, expression = "#msClass.batchTrashDelLog(#request)", msClass = ApiDefinitionLogService.class)
public void batchTrashDel(@Validated @RequestBody ApiDefinitionBatchRequest request) { public void batchTrashDel(@Validated @RequestBody ApiDefinitionBatchRequest request) {
apiDefinitionService.batchTrashDel(request, SessionUtils.getUserId()); apiDefinitionService.batchTrashDel(request, SessionUtils.getUserId());
} }
@ -161,7 +155,7 @@ public class ApiDefinitionController {
public Pager<List<ApiDefinitionDTO>> getDocPage(@Validated @RequestBody ApiDefinitionPageRequest request) { public Pager<List<ApiDefinitionDTO>> getDocPage(@Validated @RequestBody ApiDefinitionPageRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(), Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc"); 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") @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.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author lan * @author lan
@ -81,7 +83,10 @@ public class ApiDefinitionAddRequest implements Serializable {
@Schema(description = "关联文件ID") @Schema(description = "关联文件ID")
private List<String> linkFileIds; private List<String> linkFileIds;
@Schema(description = "自定义字段集合")
private Map<String, String> customFields;
public void setPath(String path) { 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 = "标签") @Schema(description = "标签")
private LinkedHashSet<@NotBlank String> tags; private LinkedHashSet<@NotBlank String> tags;
@Schema(description = "自定义字段")
private ApiDefinitionCustomFieldDTO customField;
@Schema(description = "是否追加", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "是否追加", requiredMode = Schema.RequiredMode.REQUIRED)
private boolean append = false; 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 = "是否关注") @Schema(description = "是否关注")
private Boolean follow; 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 and api_definition.version_id in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/> <include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when> </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> </choose>
</if> </if>
</foreach> </foreach>
@ -351,6 +365,44 @@
<property name="object" value="${condition}.tags"/> <property name="object" value="${condition}.tags"/>
</include> </include>
</if> </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>
<sql id="queryVersionCondition"> <sql id="queryVersionCondition">

View File

@ -1,10 +1,13 @@
package io.metersphere.api.service.definition; package io.metersphere.api.service.definition;
import io.metersphere.api.domain.ApiDefinition; import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.domain.ApiDefinitionBlob;
import io.metersphere.api.domain.ApiDefinitionExample; import io.metersphere.api.domain.ApiDefinitionExample;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.mapper.ApiDefinitionBlobMapper; import io.metersphere.api.mapper.ApiDefinitionBlobMapper;
import io.metersphere.api.mapper.ApiDefinitionMapper; 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.domain.Project;
import io.metersphere.project.mapper.ProjectMapper; import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.constants.HttpMethodConstants; 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.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.log.dto.LogDTO; import io.metersphere.system.log.dto.LogDTO;
import io.metersphere.system.log.service.OperationLogService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -21,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
@Service @Service
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -29,14 +34,15 @@ public class ApiDefinitionLogService {
@Resource @Resource
private ApiDefinitionMapper apiDefinitionMapper; private ApiDefinitionMapper apiDefinitionMapper;
@Resource
private ApiDefinitionBlobMapper apiDefinitionBlobMapper;
@Resource @Resource
private ProjectMapper projectMapper; private ProjectMapper projectMapper;
@Resource @Resource
private ApiDefinitionService apiDefinitionService; private OperationLogService operationLogService;
@Resource
private ApiDefinitionBlobMapper apiDefinitionBlobMapper;
/** /**
* 添加接口日志 * 添加接口日志
@ -118,31 +124,8 @@ public class ApiDefinitionLogService {
* *
* @return * @return
*/ */
public List<LogDTO> batchDelLog(ApiDefinitionBatchRequest request) { public void batchDelLog(List<String> ids, String userId, String projectId) {
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); saveBatchLog(projectId, ids, "/api/definition/batch-delete", userId, OperationLogType.DELETE.name(), true);
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;
} }
/** /**
@ -150,30 +133,8 @@ public class ApiDefinitionLogService {
* *
* @return * @return
*/ */
public List<LogDTO> batchUpdateLog(ApiDefinitionBatchUpdateRequest request) { public void batchUpdateLog(List<String> ids, String userId, String projectId) {
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); saveBatchLog(projectId, ids, "/api/definition/batch-update", userId, OperationLogType.UPDATE.name(), true);
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 LogDTO copyLog(ApiDefinitionCopyRequest request) { public LogDTO copyLog(ApiDefinitionCopyRequest request) {
@ -196,30 +157,8 @@ public class ApiDefinitionLogService {
return null; return null;
} }
public List<LogDTO> batchMoveLog(ApiDefinitionBatchMoveRequest request) { public void batchMoveLog(List<String> ids, String userId, String projectId) {
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); saveBatchLog(projectId, ids, "/api/definition/batch-move", userId, OperationLogType.UPDATE.name(), true);
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 LogDTO followLog(String id) { public LogDTO followLog(String id) {
@ -276,31 +215,8 @@ public class ApiDefinitionLogService {
* *
* @return * @return
*/ */
public List<LogDTO> batchRestoreLog(ApiDefinitionBatchRequest request) { public void batchRestoreLog(List<String> ids, String userId, String projectId) {
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); saveBatchLog(projectId, ids, "/api/definition/batch-restore", userId, OperationLogType.UPDATE.name(), true);
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;
} }
@ -331,31 +247,8 @@ public class ApiDefinitionLogService {
/** /**
* 删除回收站接口定义接口日志 * 删除回收站接口定义接口日志
*/ */
public List<LogDTO> batchTrashDelLog(ApiDefinitionBatchRequest request) { public void batchTrashDelLog(List<String> ids, String userId, String projectId) {
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); saveBatchLog(projectId, ids, "/api/definition/batch-trash-del", userId, OperationLogType.DELETE.name(), true);
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;
} }
private ApiDefinitionDTO getOriginalValue(String id){ private ApiDefinitionDTO getOriginalValue(String id){
@ -363,12 +256,52 @@ public class ApiDefinitionLogService {
ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(id); ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(id);
if(null != apiDefinition){ if(null != apiDefinition){
// 2. 使用Optional避免空指针异常 // 2. 使用Optional避免空指针异常
apiDefinitionService.handleBlob(id, apiDefinitionDTO); handleBlob(id, apiDefinitionDTO);
BeanUtils.copyBean(apiDefinitionDTO, apiDefinition); BeanUtils.copyBean(apiDefinitionDTO, apiDefinition);
} }
return apiDefinitionDTO; 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; package io.metersphere.api.service.definition;
import io.metersphere.api.constants.ApiDefinitionDocType;
import io.metersphere.api.constants.ApiResourceType; import io.metersphere.api.constants.ApiResourceType;
import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.controller.result.ApiResultCode;
import io.metersphere.api.domain.*; import io.metersphere.api.domain.*;
import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest; import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.enums.ApiDefinitionDocType;
import io.metersphere.api.mapper.*; import io.metersphere.api.mapper.*;
import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.utils.ApiDataUtils; 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.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.project.service.ProjectService; import io.metersphere.project.service.ProjectService;
import io.metersphere.sdk.constants.ApiReportStatus;
import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.ModuleConstants; import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.*;
import io.metersphere.system.dto.table.TableBatchProcessDTO; import io.metersphere.system.dto.table.TableBatchProcessDTO;
import io.metersphere.system.log.constants.OperationLogModule; import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.service.UserLoginService; import io.metersphere.system.service.UserLoginService;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.CustomFieldUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSession;
@ -39,7 +41,7 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; 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 @Service
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -83,7 +85,17 @@ public class ApiDefinitionService {
@Resource @Resource
private ApiDefinitionModuleMapper apiDefinitionModuleMapper; 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); List<ApiDefinitionDTO> list = extApiDefinitionMapper.list(request);
if (!CollectionUtils.isEmpty(list)) { if (!CollectionUtils.isEmpty(list)) {
processApiDefinitions(list, request.getProjectId()); processApiDefinitions(list, request.getProjectId());
@ -91,7 +103,8 @@ public class ApiDefinitionService {
return list; 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); List<ApiDefinitionDTO> list = extApiDefinitionMapper.list(request);
if (!CollectionUtils.isEmpty(list)) { if (!CollectionUtils.isEmpty(list)) {
processApiDefinitionsDoc(list); processApiDefinitionsDoc(list);
@ -159,6 +172,14 @@ public class ApiDefinitionService {
resourceUpdateRequest.setLinkFileIds(request.getLinkFileIds()); resourceUpdateRequest.setLinkFileIds(request.getLinkFileIds());
apiFileResourceService.addFileResource(resourceUpdateRequest); 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; return apiDefinition;
} }
@ -197,6 +218,9 @@ public class ApiDefinitionService {
apiDefinitionBlob.setResponse(request.getResponse().getBytes()); apiDefinitionBlob.setResponse(request.getResponse().getBytes());
apiDefinitionBlobMapper.updateByPrimaryKeySelective(apiDefinitionBlob); apiDefinitionBlobMapper.updateByPrimaryKeySelective(apiDefinitionBlob);
// 自定义字段
handleUpdateCustomFields(request, false);
// 处理文件 // 处理文件
ApiFileResourceUpdateRequest resourceUpdateRequest = getApiFileResourceUpdateRequest(originApiDefinition.getId(), originApiDefinition.getProjectId(), userId); ApiFileResourceUpdateRequest resourceUpdateRequest = getApiFileResourceUpdateRequest(originApiDefinition.getId(), originApiDefinition.getProjectId(), userId);
resourceUpdateRequest.setUploadFileIds(request.getUploadFileIds()); resourceUpdateRequest.setUploadFileIds(request.getUploadFileIds());
@ -210,10 +234,23 @@ public class ApiDefinitionService {
public void batchUpdate(ApiDefinitionBatchUpdateRequest request, String userId) { public void batchUpdate(ApiDefinitionBatchUpdateRequest request, String userId) {
ProjectService.checkResourceExist(request.getProjectId()); 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 (CollectionUtils.isNotEmpty(ids)) {
if (request.getType().equals("tags")) { if (request.getType().equals("tags")) {
handleTags(request, userId, ids); handleTags(request, userId, ids);
} 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 { }else {
ApiDefinition apiDefinition = new ApiDefinition(); ApiDefinition apiDefinition = new ApiDefinition();
BeanUtils.copyBean(apiDefinition, request); BeanUtils.copyBean(apiDefinition, request);
@ -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) { public ApiDefinition copy(ApiDefinitionCopyRequest request, String userId) {
ApiDefinition copyApiDefinition = checkApiDefinition(request.getId()); ApiDefinition copyApiDefinition = checkApiDefinition(request.getId());
ApiDefinition apiDefinition = new ApiDefinition(); ApiDefinition apiDefinition = new ApiDefinition();
@ -260,21 +360,25 @@ public class ApiDefinitionService {
public void delete(ApiDefinitionDeleteRequest request, String userId) { public void delete(ApiDefinitionDeleteRequest request, String userId) {
checkApiDefinition(request.getId()); 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) { 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)) { 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) { 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()) { if (!ids.isEmpty()) {
// 移动接口所有版本引用的数据
List<String> refIds = extApiDefinitionMapper.getRefIds(ids, false); List<String> refIds = extApiDefinitionMapper.getRefIds(ids, false);
if (!refIds.isEmpty()) { if (!refIds.isEmpty()) {
// 记录批量移动日志
apiDefinitionLogService.batchMoveLog(extApiDefinitionMapper.getIdsByRefId(refIds, false), userId, request.getProjectId());
extApiDefinitionMapper.batchMove(request, refIds, userId); extApiDefinitionMapper.batchMove(request, refIds, userId);
} }
} }
@ -287,12 +391,18 @@ public class ApiDefinitionService {
List<ApiCaseComputeDTO> apiCaseComputeList = extApiDefinitionMapper.selectApiCaseByIdsAndStatusIsNotTrash(apiDefinitionIds, projectId); List<ApiCaseComputeDTO> apiCaseComputeList = extApiDefinitionMapper.selectApiCaseByIdsAndStatusIsNotTrash(apiDefinitionIds, projectId);
Map<String, ApiCaseComputeDTO> resultMap = apiCaseComputeList.stream().collect(Collectors.toMap(ApiCaseComputeDTO::getApiDefinitionId, Function.identity())); 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 -> { list.forEach(item -> {
// Convert User IDs to Names // Convert User IDs to Names
item.setCreateUserName(userMap.get(item.getCreateUser())); item.setCreateUserName(userMap.get(item.getCreateUser()));
item.setDeleteUserName(userMap.get(item.getDeleteUser())); item.setDeleteUserName(userMap.get(item.getDeleteUser()));
item.setUpdateUserName(userMap.get(item.getUpdateUser())); item.setUpdateUserName(userMap.get(item.getUpdateUser()));
// Custom Fields
item.setCustomFields(customFieldMap.get(item.getId()));
// Calculate API Case Metrics // Calculate API Case Metrics
ApiCaseComputeDTO apiCaseComputeDTO = resultMap.get(item.getId()); ApiCaseComputeDTO apiCaseComputeDTO = resultMap.get(item.getId());
if (apiCaseComputeDTO != null) { if (apiCaseComputeDTO != null) {
@ -442,7 +552,7 @@ public class ApiDefinitionService {
return copyName; 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) { if (deleteAll) {
//全部删除 进入回收站 //全部删除 进入回收站
List<String> refIds = extApiDefinitionMapper.getRefIds(ids, false); List<String> refIds = extApiDefinitionMapper.getRefIds(ids, false);
@ -455,13 +565,17 @@ public class ApiDefinitionService {
deleteApiRelatedData(subList, userId, projectId); deleteApiRelatedData(subList, userId, projectId);
} }
}); });
// 记录删除到回收站的日志, 单条注解记录
if(isBatch){
apiDefinitionLogService.batchDelLog(delApiIds, userId, projectId);
}
extApiDefinitionMapper.batchDeleteByRefId(subRefIds, userId, projectId); extApiDefinitionMapper.batchDeleteByRefId(subRefIds, userId, projectId);
}); });
} }
} else { } else {
// 列表删除 // 列表删除
if (!ids.isEmpty()) { 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); 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)){ if(CollectionUtils.isNotEmpty(ids)){
// 需要判断是否存在多个版本问题 // 需要判断是否存在多个版本问题
ids.forEach(id -> { ids.forEach(id -> {
@ -511,6 +625,11 @@ public class ApiDefinitionService {
}); });
// 删除 case // 删除 case
deleteApiRelatedData(ids, userId, projectId); deleteApiRelatedData(ids, userId, projectId);
// 记录删除到回收站的日志, 单条注解记录
if(isBatch){
apiDefinitionLogService.batchDelLog(ids, userId, projectId);
}
// 删除接口到回收站
extApiDefinitionMapper.batchDeleteById(ids, userId, projectId); extApiDefinitionMapper.batchDeleteById(ids, userId, projectId);
} }
@ -531,16 +650,20 @@ public class ApiDefinitionService {
public void restore(ApiDefinitionDeleteRequest request, String userId) { 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)) { 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 (CollectionUtils.isNotEmpty(apiIds)) {
// 记录恢复数据之前的原数据单条通过注解记录
if(isBatch){
apiDefinitionLogService.batchRestoreLog(apiIds, userId, projectId);
}
extApiDefinitionMapper.batchRestoreById(apiIds, userId, projectId); extApiDefinitionMapper.batchRestoreById(apiIds, userId, projectId);
apiIds.forEach(id -> { apiIds.forEach(id -> {
@ -550,6 +673,15 @@ public class ApiDefinitionService {
List<ApiDefinitionVersionDTO> apiDefinitionVersions = extApiDefinitionMapper.getApiDefinitionByRefId(apiDefinition.getRefId()); List<ApiDefinitionVersionDTO> apiDefinitionVersions = extApiDefinitionMapper.getApiDefinitionByRefId(apiDefinition.getRefId());
if (CollectionUtils.isNotEmpty(apiDefinitionVersions) && apiDefinitionVersions.size() > 1) { if (CollectionUtils.isNotEmpty(apiDefinitionVersions) && apiDefinitionVersions.size() > 1) {
handleMultipleVersions(apiDefinition);
}
});
// 恢复接口关联数据
recoverApiRelatedData(apiIds, userId, projectId);
}
}
private void handleMultipleVersions(ApiDefinition apiDefinition) {
String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(apiDefinition.getProjectId()); String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(apiDefinition.getProjectId());
// 清除所有最新标识 // 清除所有最新标识
clearLatestVersion(apiDefinition.getRefId(), apiDefinition.getProjectId()); clearLatestVersion(apiDefinition.getRefId(), apiDefinition.getProjectId());
@ -561,11 +693,6 @@ public class ApiDefinitionService {
updateLatestVersion(apiDefinition.getId(), apiDefinition.getProjectId()); updateLatestVersion(apiDefinition.getId(), apiDefinition.getProjectId());
} }
} }
});
// 恢复接口关联数据
recoverApiRelatedData(apiIds, userId, projectId);
}
}
private void recoverApiRelatedData(List<String> apiIds, String userId, String projectId){ private void recoverApiRelatedData(List<String> apiIds, String userId, String projectId){
// 是否存在 case 恢复 case // 是否存在 case 恢复 case
@ -575,30 +702,30 @@ public class ApiDefinitionService {
} }
} }
public void trashDel(ApiDefinitionDeleteRequest request, String userId) { 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) { 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)) { if (CollectionUtils.isNotEmpty(ids)) {
handleRestoreApiDefinition(ids, userId, request.getProjectId()); handleRestoreApiDefinition(ids, userId, request.getProjectId(), true);
} }
} }
public void batchTrashDel(ApiDefinitionBatchRequest request, String userId) { 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)) { 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)) { 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)){ if(CollectionUtils.isNotEmpty(ids)){
// 删除上传的文件 // 删除上传的文件
ids.forEach(id -> { ids.forEach(id -> {
@ -613,10 +740,15 @@ public class ApiDefinitionService {
// 删除接口关联数据 // 删除接口关联数据
trashDelApiRelatedData(ids, userId, projectId); trashDelApiRelatedData(ids, userId, projectId);
// 记录批量删除日志单条删除通过注解记录
if(isBatch){
apiDefinitionLogService.batchTrashDelLog(ids, userId, projectId);
}
// 删除接口 // 删除接口
ApiDefinitionExample apiDefinitionExample = new ApiDefinitionExample(); ApiDefinitionExample apiDefinitionExample = new ApiDefinitionExample();
apiDefinitionExample.createCriteria().andIdIn(ids).andDeletedEqualTo(true).andProjectIdEqualTo(projectId); apiDefinitionExample.createCriteria().andIdIn(ids).andDeletedEqualTo(true).andProjectIdEqualTo(projectId);
apiDefinitionMapper.deleteByExample(apiDefinitionExample); apiDefinitionMapper.deleteByExample(apiDefinitionExample);
} }
} }
@ -632,9 +764,11 @@ public class ApiDefinitionService {
} }
// 获取批量操作选中的ID // 获取批量操作选中的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; TableBatchProcessDTO request = (TableBatchProcessDTO) dto;
if (request.isSelectAll()) { if (request.isSelectAll()) {
// 全选
CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request.getCondition(), userId);
List<String> ids = extApiDefinitionMapper.getIds(request, projectId, protocol, deleted); List<String> ids = extApiDefinitionMapper.getIds(request, projectId, protocol, deleted);
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) { if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
ids.removeAll(request.getExcludeIds()); ids.removeAll(request.getExcludeIds());
@ -652,7 +786,9 @@ public class ApiDefinitionService {
public ApiDefinitionDTO getApiDefinitionInfo(String id, String userId, ApiDefinition apiDefinition) { public ApiDefinitionDTO getApiDefinitionInfo(String id, String userId, ApiDefinition apiDefinition) {
ApiDefinitionDTO apiDefinitionDTO = new ApiDefinitionDTO(); ApiDefinitionDTO apiDefinitionDTO = new ApiDefinitionDTO();
// 2. 使用Optional避免空指针异常 // 2. 使用Optional避免空指针异常
handleBlob(id, apiDefinitionDTO); apiDefinitionLogService.handleBlob(id, apiDefinitionDTO);
// 3. 查询自定义字段
handleCustomFields(id, apiDefinition.getProjectId(), apiDefinitionDTO);
// 3. 使用Stream简化集合操作 // 3. 使用Stream简化集合操作
ApiDefinitionFollowerExample example = new ApiDefinitionFollowerExample(); ApiDefinitionFollowerExample example = new ApiDefinitionFollowerExample();
example.createCriteria().andApiDefinitionIdEqualTo(id).andUserIdEqualTo(userId); example.createCriteria().andApiDefinitionIdEqualTo(id).andUserIdEqualTo(userId);
@ -661,15 +797,10 @@ public class ApiDefinitionService {
return apiDefinitionDTO; return apiDefinitionDTO;
} }
public void handleBlob(String id, ApiDefinitionDTO apiDefinitionDTO) { public void handleCustomFields(String id, String projectId, ApiDefinitionDTO apiDefinitionDTO) {
Optional<ApiDefinitionBlob> apiDefinitionBlobOptional = Optional.ofNullable(apiDefinitionBlobMapper.selectByPrimaryKey(id)); List<ApiDefinitionCustomFieldDTO> customFields = extApiDefinitionCustomFieldMapper.getApiCustomFields(Collections.singletonList(id), projectId);
apiDefinitionBlobOptional.ifPresent(blob -> { Map<String, List<ApiDefinitionCustomFieldDTO>> customFieldMap = customFields.stream().collect(Collectors.groupingBy(ApiDefinitionCustomFieldDTO::getApiId));
apiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(blob.getRequest()), AbstractMsTestElement.class)); apiDefinitionDTO.setCustomFields(customFieldMap.get(id));
// blob.getResponse() null 时不进行转换
if (blob.getResponse() != null) {
apiDefinitionDTO.setResponse(ApiDataUtils.parseArray(new String(blob.getResponse()), HttpResponse.class));
}
});
} }
public ApiDefinitionDocDTO getDocInfo(ApiDefinitionDocRequest request, String userId) { public ApiDefinitionDocDTO getDocInfo(ApiDefinitionDocRequest request, String userId) {
@ -680,7 +811,7 @@ public class ApiDefinitionService {
List<ApiDefinitionDTO> list = extApiDefinitionMapper.listDoc(request); List<ApiDefinitionDTO> list = extApiDefinitionMapper.listDoc(request);
if (!list.isEmpty()) { if (!list.isEmpty()) {
ApiDefinitionDTO first = list.get(0); ApiDefinitionDTO first = list.get(0);
handleBlob(first.getId(), first); apiDefinitionLogService.handleBlob(first.getId(), first);
if(ApiDefinitionDocType.ALL.name().equals(request.getType())){ if(ApiDefinitionDocType.ALL.name().equals(request.getType())){
apiDefinitionDocDTO.setDocTitle(Translator.get(ALL_API)); apiDefinitionDocDTO.setDocTitle(Translator.get(ALL_API));
} else { } else {

View File

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

View File

@ -5,7 +5,7 @@ import io.metersphere.api.controller.result.ApiResultCode;
import io.metersphere.api.domain.*; import io.metersphere.api.domain.*;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.request.http.MsHTTPElement; 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.mapper.*;
import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
@ -28,8 +28,8 @@ import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.utils.Pager; import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*; 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.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -40,6 +40,7 @@ import org.springframework.test.web.servlet.MvcResult;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import static io.metersphere.api.controller.result.ApiResultCode.API_DEFINITION_NOT_EXIST; import static io.metersphere.api.controller.result.ApiResultCode.API_DEFINITION_NOT_EXIST;
import static io.metersphere.system.controller.handler.result.MsHttpResultCode.NOT_FOUND; import static io.metersphere.system.controller.handler.result.MsHttpResultCode.NOT_FOUND;
@ -101,6 +102,9 @@ public class ApiDefinitionControllerTests extends BaseTest {
@Resource @Resource
private ApiDefinitionModuleMapper apiDefinitionModuleMapper; private ApiDefinitionModuleMapper apiDefinitionModuleMapper;
@Resource
private ExtApiDefinitionCustomFieldMapper extApiDefinitionCustomFieldMapper;
@Resource @Resource
private FileMetadataService fileMetadataService; private FileMetadataService fileMetadataService;
private static String fileMetadataId; private static String fileMetadataId;
@ -197,7 +201,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
// 创建并返回一个 ApiDefinitionAddRequest 对象用于测试 // 创建并返回一个 ApiDefinitionAddRequest 对象用于测试
String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(DEFAULT_PROJECT_ID); String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(DEFAULT_PROJECT_ID);
ApiDefinitionAddRequest request = new ApiDefinitionAddRequest(); ApiDefinitionAddRequest request = new ApiDefinitionAddRequest();
request.setName("接口定义"); request.setName("接口定义test");
request.setProtocol("HTTP"); request.setProtocol("HTTP");
request.setProjectId(DEFAULT_PROJECT_ID); request.setProjectId(DEFAULT_PROJECT_ID);
request.setMethod("POST"); request.setMethod("POST");
@ -207,6 +211,11 @@ public class ApiDefinitionControllerTests extends BaseTest {
request.setVersionId(defaultVersion); request.setVersionId(defaultVersion);
request.setDescription("描述内容"); request.setDescription("描述内容");
request.setTags(new LinkedHashSet<>(List.of("tag1", "tag2"))); 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; return request;
} }
@ -241,6 +250,12 @@ public class ApiDefinitionControllerTests extends BaseTest {
example.createCriteria().andApiDefinitionIdEqualTo(apiDefinition.getId()).andUserIdEqualTo("admin"); example.createCriteria().andApiDefinitionIdEqualTo(apiDefinition.getId()).andUserIdEqualTo("admin");
List<ApiDefinitionFollower> followers = apiDefinitionFollowerMapper.selectByExample(example); List<ApiDefinitionFollower> followers = apiDefinitionFollowerMapper.selectByExample(example);
copyApiDefinitionDTO.setFollow(CollectionUtils.isNotEmpty(followers)); 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){ if(apiDefinitionBlob != null){
copyApiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(apiDefinitionBlob.getRequest()), AbstractMsTestElement.class)); copyApiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(apiDefinitionBlob.getRequest()), AbstractMsTestElement.class));
copyApiDefinitionDTO.setResponse(ApiDataUtils.parseArray(new String(apiDefinitionBlob.getResponse()), HttpResponse.class)); copyApiDefinitionDTO.setResponse(ApiDataUtils.parseArray(new String(apiDefinitionBlob.getResponse()), HttpResponse.class));
@ -268,6 +283,11 @@ public class ApiDefinitionControllerTests extends BaseTest {
request.setMethod("POST"); request.setMethod("POST");
request.setModuleId("default1"); request.setModuleId("default1");
request.setTags(new LinkedHashSet<>(List.of("tag1", "tag2-update"))); 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(); MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement();
request.setRequest(ApiDataUtils.toJSONString(msHttpElement)); request.setRequest(ApiDataUtils.toJSONString(msHttpElement));
List<HttpResponse> msHttpResponse = MsHTTPElementTest.getMsHttpResponse(); List<HttpResponse> msHttpResponse = MsHTTPElementTest.getMsHttpResponse();
@ -424,6 +444,18 @@ public class ApiDefinitionControllerTests extends BaseTest {
apiDefinitionBatchUpdateRequest.setAppend(false); apiDefinitionBatchUpdateRequest.setAppend(false);
this.requestPostWithOk(BATCH_UPDATE, apiDefinitionBatchUpdateRequest); this.requestPostWithOk(BATCH_UPDATE, apiDefinitionBatchUpdateRequest);
assertBatchUpdateApiDefinition(apiDefinitionBatchUpdateRequest, List.of("1003","1004")); 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.setType("method");
apiDefinitionBatchUpdateRequest.setMethod("batch-method"); apiDefinitionBatchUpdateRequest.setMethod("batch-method");
@ -662,13 +694,30 @@ public class ApiDefinitionControllerTests extends BaseTest {
filters.put("status", Arrays.asList("Underway", "Completed")); filters.put("status", Arrays.asList("Underway", "Completed"));
filters.put("method", List.of("GET")); filters.put("method", List.of("GET"));
filters.put("version_id", List.of("1005704995741369851")); filters.put("version_id", List.of("1005704995741369851"));
filters.put("custom_multiple_custom-field", List.of("oasis"));
request.setFilter(filters); request.setFilter(filters);
} }
private void configureCombineSearch(ApiDefinitionPageRequest request) { private void configureCombineSearch(ApiDefinitionPageRequest request) {
Map<String, Object> map = new HashMap<>(); 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("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); request.setCombine(map);
} }
@ -969,7 +1018,6 @@ public class ApiDefinitionControllerTests extends BaseTest {
// @@校验日志 // @@校验日志
checkLog("1002", OperationLogType.UPDATE); checkLog("1002", OperationLogType.UPDATE);
checkLog("1004", OperationLogType.UPDATE); checkLog("1004", OperationLogType.UPDATE);
checkLog("1005", OperationLogType.UPDATE);
// 恢复全部 条件为关键字为st-6的数据 // 恢复全部 条件为关键字为st-6的数据
request.setSelectAll(true); 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 ('1004', 0x504B03041400080808004A547857000000000000000000000000030000007A6970CD92B14EC3301040FFE5E6A8296B36842A180055281B62709D4B6D61FBDCF3991055FD779C840C949185C5A77BB6CFEF6C9F21921B3D7134563F2B8FD0C0537A68DBFDCEA1C720504158F01C2AC0A00EAEE4BD72092BD0C6BA8E314013B27315647665AD11894D5D0FC3B0114CB2D1E4CBD6A8C494C97A4225F52886BA02EE776D490FD48D6B1583AA434ED0BC9EE11D0B9EC70A3E94CB93CB127FC9749834DB28968ACF9CC1E5AD029ECEFB7BA9A90156BE1D23AE9E8CA76C194B13C2B9ECF2363C62384E6D2E0BBCFAFC093068EAD66326B953461EFFAD1D8941BEA3D0DB233467D014026A69AD47CAE54E6FB6DBED7CBF9142C22BAC91C5F6562BC15B6755794D5073ACA027E76878C1AEE86949ABA0CA4257F03253B32A2C9A9171CFA431259A3EC937A424D7F4F205504B070852639A522B010000E0020000504B010214001400080808004A54785752639A522B010000E00200000300000000000000000000000000000000007A6970504B05060000000001000100310000005C0100000000, 0x504B03041400080808004A547857000000000000000000000000030000007A6970A58F414BC4301085FFCB9C03B69EA4B74559141465295E9622B3CD743798263199C82EA5FFDD49EB82772F19DECB9BEF25FB098C86A656C09740D0C063DBBE81028763513B4AC1BB44B55827424D3141B39FE0932E725B4E05DF6873C9AE5301393C583106B48914684A7D34818D77122A0AE64EC1C16B414CCB6CD7EAEDEBEEE5E361D36E0432443FBE17E03FFB98124B2660C4F1B786E95CAC485FD94492CF73CCB2371AF74CEEC82768EE44E1F9AAEA4A41EF1D93E33F849B60D1B8A5BEF79AAE94C1587A12A6CBD6CEDDAC2031724EF74B046EAB0ACA0B07CC96B7168FEBDADCFD00504B0708FF595995E400000087010000504B010214001400080808004A547857FF595995E4000000870100000300000000000000000000000000000000007A6970504B0506000000000100010031000000150100000000);
INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1005', 0x504B030414000808080049547857000000000000000000000000030000007A6970CD92B14EC3301040FFE5E6A8296B36842A180055281B62709D4B6D61FBDCF3991055FD779C840C949185C5A77BB6CFEF6C9F21921B3D7134563F2B8FD0C0537A68DBFDCEA1C720504158F01C2AC0A00EAEE4BD72092BD0C6BA8E314013B27315647665AD11894D5D0FC3B0114CB2D1E4CBD6A8C494C97A4225F52886BA02EE776D490FD48D6B1583AA434ED0BC9EE11D0B9EC70A3E94CB93CB127FC9749834DB28968ACF9CC1E5AD029ECEFB7BA9A90156BE1D23AE9E8CA76C194B13C2B9ECF2363C62384E6D2E0BBCFAFC093068EAD66326B953461EFFAD1D8941BEA3D0DB233467D014026A69AD47CAE54E6FB6DBED7CBF9142C22BAC91C5F6562BC15B6755794D5073ACA027E76878C1AEE86949ABA0CA4257F03253B32A2C9A9171CFA431259A3EC937A424D7F4F205504B070852639A522B010000E0020000504B010214001400080808004954785752639A522B010000E00200000300000000000000000000000000000000007A6970504B05060000000001000100310000005C0100000000, 0x504B030414000808080049547857000000000000000000000000030000007A6970A58F414BC4301085FFCB9C03B69EA4B74559141465295E9622B3CD743798263199C82EA5FFDD49EB82772F19DECB9BEF25FB098C86A656C09740D0C063DBBE81028763513B4AC1BB44B55827424D3141B39FE0932E725B4E05DF6873C9AE5301393C583106B48914684A7D34818D77122A0AE64EC1C16B414CCB6CD7EAEDEBEEE5E361D36E0432443FBE17E03FFB98124B2660C4F1B786E95CAC485FD94492CF73CCB2371AF74CEEC82768EE44E1F9AAEA4A41EF1D93E33F849B60D1B8A5BEF79AAE94C1587A12A6CBD6CEDDAC2031724EF74B046EAB0ACA0B07CC96B7168FEBDADCFD00504B0708FF595995E400000087010000504B0102140014000808080049547857FF595995E4000000870100000300000000000000000000000000000000007A6970504B0506000000000100010031000000150100000000); INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1005', 0x504B030414000808080049547857000000000000000000000000030000007A6970CD92B14EC3301040FFE5E6A8296B36842A180055281B62709D4B6D61FBDCF3991055FD779C840C949185C5A77BB6CFEF6C9F21921B3D7134563F2B8FD0C0537A68DBFDCEA1C720504158F01C2AC0A00EAEE4BD72092BD0C6BA8E314013B27315647665AD11894D5D0FC3B0114CB2D1E4CBD6A8C494C97A4225F52886BA02EE776D490FD48D6B1583AA434ED0BC9EE11D0B9EC70A3E94CB93CB127FC9749834DB28968ACF9CC1E5AD029ECEFB7BA9A90156BE1D23AE9E8CA76C194B13C2B9ECF2363C62384E6D2E0BBCFAFC093068EAD66326B953461EFFAD1D8941BEA3D0DB233467D014026A69AD47CAE54E6FB6DBED7CBF9142C22BAC91C5F6562BC15B6755794D5073ACA027E76878C1AEE86949ABA0CA4257F03253B32A2C9A9171CFA431259A3EC937A424D7F4F205504B070852639A522B010000E0020000504B010214001400080808004954785752639A522B010000E00200000300000000000000000000000000000000007A6970504B05060000000001000100310000005C0100000000, 0x504B030414000808080049547857000000000000000000000000030000007A6970A58F414BC4301085FFCB9C03B69EA4B74559141465295E9622B3CD743798263199C82EA5FFDD49EB82772F19DECB9BEF25FB098C86A656C09740D0C063DBBE81028763513B4AC1BB44B55827424D3141B39FE0932E725B4E05DF6873C9AE5301393C583106B48914684A7D34818D77122A0AE64EC1C16B414CCB6CD7EAEDEBEEE5E361D36E0432443FBE17E03FFB98124B2660C4F1B786E95CAC485FD94492CF73CCB2371AF74CEEC82768EE44E1F9AAEA4A41EF1D93E33F849B60D1B8A5BEF79AAE94C1587A12A6CBD6CEDDAC2031724EF74B046EAB0ACA0B07CC96B7168FEBDADCFD00504B0708FF595995E400000087010000504B0102140014000808080049547857FF595995E4000000870100000300000000000000000000000000000000007A6970504B0506000000000100010031000000150100000000);
INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1006', 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);
}
}