diff --git a/backend/framework/domain/src/main/java/io/metersphere/api/domain/ApiDefinitionCustomField.java b/backend/framework/domain/src/main/java/io/metersphere/api/domain/ApiDefinitionCustomField.java new file mode 100644 index 0000000000..40c18f9a60 --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/api/domain/ApiDefinitionCustomField.java @@ -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 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(); + } + } +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/api/domain/ApiDefinitionCustomFieldExample.java b/backend/framework/domain/src/main/java/io/metersphere/api/domain/ApiDefinitionCustomFieldExample.java new file mode 100644 index 0000000000..3ed72dc55f --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/api/domain/ApiDefinitionCustomFieldExample.java @@ -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 oredCriteria; + + public ApiDefinitionCustomFieldExample() { + oredCriteria = new ArrayList(); + } + + 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 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 criteria; + + protected GeneratedCriteria() { + super(); + criteria = new ArrayList(); + } + + public boolean isValid() { + return criteria.size() > 0; + } + + public List getAllCriteria() { + return criteria; + } + + public List 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 values) { + addCriterion("api_id in", values, "apiId"); + return (Criteria) this; + } + + public Criteria andApiIdNotIn(List 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 values) { + addCriterion("field_id in", values, "fieldId"); + return (Criteria) this; + } + + public Criteria andFieldIdNotIn(List 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 values) { + addCriterion("`value` in", values, "value"); + return (Criteria) this; + } + + public Criteria andValueNotIn(List 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); + } + } +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/api/mapper/ApiDefinitionCustomFieldMapper.java b/backend/framework/domain/src/main/java/io/metersphere/api/mapper/ApiDefinitionCustomFieldMapper.java new file mode 100644 index 0000000000..e21dcb150f --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/api/mapper/ApiDefinitionCustomFieldMapper.java @@ -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 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 list); + + int batchInsertSelective(@Param("list") List list, @Param("selective") ApiDefinitionCustomField.Column ... selective); +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/api/mapper/ApiDefinitionCustomFieldMapper.xml b/backend/framework/domain/src/main/java/io/metersphere/api/mapper/ApiDefinitionCustomFieldMapper.xml new file mode 100644 index 0000000000..2b95bdd528 --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/api/mapper/ApiDefinitionCustomFieldMapper.xml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + api_id, field_id, `value` + + + + + delete from api_definition_custom_field + where api_id = #{apiId,jdbcType=VARCHAR} + and field_id = #{fieldId,jdbcType=VARCHAR} + + + delete from api_definition_custom_field + + + + + + insert into api_definition_custom_field (api_id, field_id, `value` + ) + values (#{apiId,jdbcType=VARCHAR}, #{fieldId,jdbcType=VARCHAR}, #{value,jdbcType=VARCHAR} + ) + + + insert into api_definition_custom_field + + + api_id, + + + field_id, + + + `value`, + + + + + #{apiId,jdbcType=VARCHAR}, + + + #{fieldId,jdbcType=VARCHAR}, + + + #{value,jdbcType=VARCHAR}, + + + + + + update api_definition_custom_field + + + api_id = #{record.apiId,jdbcType=VARCHAR}, + + + field_id = #{record.fieldId,jdbcType=VARCHAR}, + + + `value` = #{record.value,jdbcType=VARCHAR}, + + + + + + + + update api_definition_custom_field + set api_id = #{record.apiId,jdbcType=VARCHAR}, + field_id = #{record.fieldId,jdbcType=VARCHAR}, + `value` = #{record.value,jdbcType=VARCHAR} + + + + + + update api_definition_custom_field + + + `value` = #{value,jdbcType=VARCHAR}, + + + where api_id = #{apiId,jdbcType=VARCHAR} + and field_id = #{fieldId,jdbcType=VARCHAR} + + + update api_definition_custom_field + set `value` = #{value,jdbcType=VARCHAR} + where api_id = #{apiId,jdbcType=VARCHAR} + and field_id = #{fieldId,jdbcType=VARCHAR} + + + insert into api_definition_custom_field + (api_id, field_id, `value`) + values + + (#{item.apiId,jdbcType=VARCHAR}, #{item.fieldId,jdbcType=VARCHAR}, #{item.value,jdbcType=VARCHAR} + ) + + + + insert into api_definition_custom_field ( + + ${column.escapedColumnName} + + ) + values + + ( + + + #{item.apiId,jdbcType=VARCHAR} + + + #{item.fieldId,jdbcType=VARCHAR} + + + #{item.value,jdbcType=VARCHAR} + + + ) + + + \ No newline at end of file diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_5__api_test.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_5__api_test.sql index 35a734b510..6e77a9c5ce 100644 --- a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_5__api_test.sql +++ b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_5__api_test.sql @@ -506,7 +506,20 @@ CREATE TABLE IF NOT EXISTS api_file_resource( `create_time` BIGINT NOT NULL COMMENT '创建时间' , `project_id` VARCHAR(50) NOT NULL COMMENT '项目ID' , PRIMARY KEY (resource_id,file_id) -) COMMENT = '接口和所需文件资源的关联表'; +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci COMMENT = '接口和所需文件资源的关联表'; + + +CREATE TABLE IF NOT EXISTS api_definition_custom_field( + `api_id` VARCHAR(50) NOT NULL COMMENT '接口ID' , + `field_id` VARCHAR(50) NOT NULL COMMENT '字段ID' , + `value` VARCHAR(1000) COMMENT '字段值' , + PRIMARY KEY (api_id,field_id) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci COMMENT = '自定义字段接口定义关系'; + -- set innodb lock wait timeout to default SET SESSION innodb_lock_wait_timeout = DEFAULT; \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/api.properties b/backend/framework/sdk/src/main/resources/i18n/api.properties index da280f8261..99c7f56b1c 100644 --- a/backend/framework/sdk/src/main/resources/i18n/api.properties +++ b/backend/framework/sdk/src/main/resources/i18n/api.properties @@ -289,6 +289,11 @@ api_debug_module.name.not_contain_slash=模块名称不能包含斜杠 api_environment_config.id.not_blank=ID不能为空 api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间 api_environment_config.environment_id.not_blank=环境ID不能为空 + +#module:ApiDefinitionCustomField +api_definition_custom_field.api_id.not_blank=接口ID不能为空 +api_definition_custom_field.field_id.not_blank=自定义字段ID不能为空 + api_module.not.exist=模块不存在 permission.api.name=接口测试 diff --git a/backend/framework/sdk/src/main/resources/i18n/api_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/api_en_US.properties index e19ceca0b1..6813860655 100644 --- a/backend/framework/sdk/src/main/resources/i18n/api_en_US.properties +++ b/backend/framework/sdk/src/main/resources/i18n/api_en_US.properties @@ -297,6 +297,11 @@ api_unplanned_request=Unplanned Api api_environment_config.id.not_blank=ID cannot be blank api_environment_config.environment_id.length_range=Environment ID length must be between 1-50 api_environment_config.environment_id.not_blank=Environment ID cannot be blank + +#module:ApiDefinitionCustomField +api_definition_custom_field.api_id.not_blank=Interface pk cannot be empty +api_definition_custom_field.field_id.not_blank=Field ID cannot be empty + api_module.not.exist=The module does not exist permission.api.name=API Test diff --git a/backend/framework/sdk/src/main/resources/i18n/api_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/api_zh_CN.properties index 46ce33804a..503bdb090b 100644 --- a/backend/framework/sdk/src/main/resources/i18n/api_zh_CN.properties +++ b/backend/framework/sdk/src/main/resources/i18n/api_zh_CN.properties @@ -297,6 +297,11 @@ api_unplanned_request=未规划接口 api_environment_config.id.not_blank=ID不能为空 api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间 api_environment_config.environment_id.not_blank=环境ID不能为空 + +#module:ApiDefinitionCustomField +api_definition_custom_field.api_id.not_blank=接口ID不能为空 +api_definition_custom_field.field_id.not_blank=自定义字段ID不能为空 + api_module.not.exist=模块不存在 permission.api.name=接口测试 diff --git a/backend/framework/sdk/src/main/resources/i18n/api_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/api_zh_TW.properties index 635930805c..38e86861fc 100644 --- a/backend/framework/sdk/src/main/resources/i18n/api_zh_TW.properties +++ b/backend/framework/sdk/src/main/resources/i18n/api_zh_TW.properties @@ -297,6 +297,11 @@ api_unplanned_request=未规划接口 api_environment_config.id.not_blank=ID不能為空 api_environment_config.environment_id.length_range=環境ID長度必須在1-50之間 api_environment_config.environment_id.not_blank=環境ID不能為空 + +#module:ApiDefinitionCustomField +api_definition_custom_field.api_id.not_blank=接口ID不能為空 +api_definition_custom_field.field_id.not_blank=自定義字段ID不能爲空 + api_module.not.exist=模塊不存在 permission.api.name=接口測試 diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/enums/ApiDefinitionDocType.java b/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiDefinitionDocType.java similarity index 76% rename from backend/services/api-test/src/main/java/io/metersphere/api/enums/ApiDefinitionDocType.java rename to backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiDefinitionDocType.java index 2c4df311af..c496b6b325 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/enums/ApiDefinitionDocType.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiDefinitionDocType.java @@ -1,4 +1,4 @@ -package io.metersphere.api.enums; +package io.metersphere.api.constants; /** * @author: LAN diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java index 64501b6799..a6a1728664 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java @@ -57,8 +57,6 @@ public class ApiDefinitionController { @PostMapping(value = "/batch-update") @Operation(summary = "接口测试-接口管理-批量更新接口定义") @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE) - // 添加修改Log示例 - @Log(type = OperationLogType.UPDATE, expression = "#msClass.batchUpdateLog(#request)", msClass = ApiDefinitionLogService.class) public void batchUpdate(@Validated @RequestBody ApiDefinitionBatchUpdateRequest request) { apiDefinitionService.batchUpdate(request, SessionUtils.getUserId()); } @@ -73,7 +71,6 @@ public class ApiDefinitionController { @PostMapping(value = "/batch-del") @Operation(summary = "接口测试-接口管理-批量删除接口定义到回收站") @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE) - @Log(type = OperationLogType.DELETE, expression = "#msClass.batchDelLog(#request)", msClass = ApiDefinitionLogService.class) public void batchDelete(@Validated @RequestBody ApiDefinitionBatchRequest request) { apiDefinitionService.batchDelete(request, SessionUtils.getUserId()); } @@ -89,7 +86,6 @@ public class ApiDefinitionController { @PostMapping("/batch-move") @Operation(summary = "接口测试-接口管理-批量移动接口定义") @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE) - @Log(type = OperationLogType.UPDATE, expression = "#msClass.batchMoveLog(#request)", msClass = ApiDefinitionLogService.class) public void batchMove(@Validated @RequestBody ApiDefinitionBatchMoveRequest request) { apiDefinitionService.batchMove(request, SessionUtils.getUserId()); } @@ -122,7 +118,7 @@ public class ApiDefinitionController { public Pager> getPage(@Validated @RequestBody ApiDefinitionPageRequest request) { Page page = PageHelper.startPage(request.getCurrent(), request.getPageSize(), StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc"); - return PageUtils.setPageInfo(page, apiDefinitionService.getApiDefinitionPage(request)); + return PageUtils.setPageInfo(page, apiDefinitionService.getApiDefinitionPage(request, SessionUtils.getUserId())); } @PostMapping(value = "/restore") @@ -142,7 +138,6 @@ public class ApiDefinitionController { @PostMapping(value = "/batch-restore") @Operation(summary = "接口测试-接口管理-批量从回收站恢复接口定义") @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE) - @Log(type = OperationLogType.UPDATE, expression = "#msClass.batchRestoreLog(#request)", msClass = ApiDefinitionLogService.class) public void batchRestore(@Validated @RequestBody ApiDefinitionBatchRequest request) { apiDefinitionService.batchRestore(request, SessionUtils.getUserId()); } @@ -150,7 +145,6 @@ public class ApiDefinitionController { @PostMapping(value = "/batch-trash-del") @Operation(summary = "接口测试-接口管理-批量从回收站删除接口定义") @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE) - @Log(type = OperationLogType.UPDATE, expression = "#msClass.batchTrashDelLog(#request)", msClass = ApiDefinitionLogService.class) public void batchTrashDel(@Validated @RequestBody ApiDefinitionBatchRequest request) { apiDefinitionService.batchTrashDel(request, SessionUtils.getUserId()); } @@ -161,7 +155,7 @@ public class ApiDefinitionController { public Pager> getDocPage(@Validated @RequestBody ApiDefinitionPageRequest request) { Page page = PageHelper.startPage(request.getCurrent(), request.getPageSize(), StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc"); - return PageUtils.setPageInfo(page, apiDefinitionService.getDocPage(request)); + return PageUtils.setPageInfo(page, apiDefinitionService.getDocPage(request, SessionUtils.getUserId())); } @PostMapping("/upload/temp/file") diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionAddRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionAddRequest.java index b60f6e3ac0..357e9a1885 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionAddRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionAddRequest.java @@ -5,11 +5,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; +import org.apache.commons.lang3.StringUtils; import java.io.Serial; import java.io.Serializable; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; /** * @author lan @@ -81,7 +83,10 @@ public class ApiDefinitionAddRequest implements Serializable { @Schema(description = "关联文件ID") private List linkFileIds; + @Schema(description = "自定义字段集合") + private Map customFields; + public void setPath(String path) { - this.path = (path != null) ? path.trim() : null; + this.path = StringUtils.trim(path); } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionBatchUpdateRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionBatchUpdateRequest.java index f02602175b..192055512a 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionBatchUpdateRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionBatchUpdateRequest.java @@ -36,6 +36,9 @@ public class ApiDefinitionBatchUpdateRequest extends ApiDefinitionBatchRequest { @Schema(description = "标签") private LinkedHashSet<@NotBlank String> tags; + @Schema(description = "自定义字段") + private ApiDefinitionCustomFieldDTO customField; + @Schema(description = "是否追加", requiredMode = Schema.RequiredMode.REQUIRED) private boolean append = false; diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionCustomFieldDTO.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionCustomFieldDTO.java new file mode 100644 index 0000000000..3d4108d2f6 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionCustomFieldDTO.java @@ -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; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDTO.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDTO.java index 701017f542..46302c2f4e 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDTO.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDTO.java @@ -45,4 +45,7 @@ public class ApiDefinitionDTO extends ApiDefinition{ @Schema(description = "是否关注") private Boolean follow; + @Schema(description = "自定义字段集合") + private List customFields; + } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionCustomFieldMapper.java b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionCustomFieldMapper.java new file mode 100644 index 0000000000..c5c5fc8358 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionCustomFieldMapper.java @@ -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 getApiCustomFields(@Param("ids") List apiIds, @Param("projectId") String projectId); + +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionCustomFieldMapper.xml b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionCustomFieldMapper.xml new file mode 100644 index 0000000000..59111cbb66 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionCustomFieldMapper.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml index b5477f756b..47e6dd674f 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml @@ -281,6 +281,20 @@ and api_definition.version_id in + + 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 + + ) + + + and api_definition.id in ( + select api_id from api_definition_custom_field where concat('custom_multiple_', field_id) = #{key} + and + + ) + @@ -351,6 +365,44 @@ + + + + + + and api_definition.id not in ( + + + and api_definition.id in ( + + select api_id from api_definition_custom_field where field_id = #{custom.id} + + + and `value` + + + + + + and ${custom.value} + + + and left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13) + + + + + + and trim(both '"' from `value`) + + + + + + ) + + + diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionLogService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionLogService.java index 5248869f88..36483206bd 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionLogService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionLogService.java @@ -1,10 +1,13 @@ package io.metersphere.api.service.definition; import io.metersphere.api.domain.ApiDefinition; +import io.metersphere.api.domain.ApiDefinitionBlob; import io.metersphere.api.domain.ApiDefinitionExample; import io.metersphere.api.dto.definition.*; import io.metersphere.api.mapper.ApiDefinitionBlobMapper; import io.metersphere.api.mapper.ApiDefinitionMapper; +import io.metersphere.api.utils.ApiDataUtils; +import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.project.domain.Project; import io.metersphere.project.mapper.ProjectMapper; import io.metersphere.sdk.constants.HttpMethodConstants; @@ -14,6 +17,7 @@ import io.metersphere.sdk.util.Translator; import io.metersphere.system.log.constants.OperationLogModule; import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.log.dto.LogDTO; +import io.metersphere.system.log.service.OperationLogService; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; @@ -21,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Service @Transactional(rollbackFor = Exception.class) @@ -29,14 +34,15 @@ public class ApiDefinitionLogService { @Resource private ApiDefinitionMapper apiDefinitionMapper; + @Resource + private ApiDefinitionBlobMapper apiDefinitionBlobMapper; + @Resource private ProjectMapper projectMapper; @Resource - private ApiDefinitionService apiDefinitionService; + private OperationLogService operationLogService; - @Resource - private ApiDefinitionBlobMapper apiDefinitionBlobMapper; /** * 添加接口日志 @@ -118,31 +124,8 @@ public class ApiDefinitionLogService { * * @return */ - public List batchDelLog(ApiDefinitionBatchRequest request) { - List ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); - List dtoList = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(ids)) { - ApiDefinitionExample example = new ApiDefinitionExample(); - example.createCriteria().andIdIn(ids).andDeletedEqualTo(false); - List apiDefinitions = apiDefinitionMapper.selectByExample(example); - apiDefinitions.forEach(item -> { - LogDTO dto = new LogDTO( - item.getProjectId(), - "", - item.getId(), - item.getCreateUser(), - OperationLogType.DELETE.name(), - OperationLogModule.API_DEFINITION, - item.getName()); - dto.setHistory(true); - dto.setPath("/api/definition/batch-delete"); - dto.setMethod(HttpMethodConstants.POST.name()); - dto.setOriginalValue(JSON.toJSONBytes(item)); - dtoList.add(dto); - }); - } - - return dtoList; + public void batchDelLog(List ids, String userId, String projectId) { + saveBatchLog(projectId, ids, "/api/definition/batch-delete", userId, OperationLogType.DELETE.name(), true); } /** @@ -150,30 +133,8 @@ public class ApiDefinitionLogService { * * @return */ - public List batchUpdateLog(ApiDefinitionBatchUpdateRequest request) { - List ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); - List dtoList = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(ids)) { - ApiDefinitionExample example = new ApiDefinitionExample(); - example.createCriteria().andIdIn(ids); - List apiDefinitions = apiDefinitionMapper.selectByExample(example); - apiDefinitions.forEach(item -> { - LogDTO dto = new LogDTO( - item.getProjectId(), - "", - item.getId(), - item.getCreateUser(), - OperationLogType.UPDATE.name(), - OperationLogModule.API_DEFINITION, - item.getName()); - dto.setHistory(true); - dto.setPath("/api/definition/batch-update"); - dto.setMethod(HttpMethodConstants.POST.name()); - dto.setOriginalValue(JSON.toJSONBytes(item)); - dtoList.add(dto); - }); - } - return dtoList; + public void batchUpdateLog(List ids, String userId, String projectId) { + saveBatchLog(projectId, ids, "/api/definition/batch-update", userId, OperationLogType.UPDATE.name(), true); } public LogDTO copyLog(ApiDefinitionCopyRequest request) { @@ -196,30 +157,8 @@ public class ApiDefinitionLogService { return null; } - public List batchMoveLog(ApiDefinitionBatchMoveRequest request) { - List ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); - List dtoList = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(ids)) { - ApiDefinitionExample example = new ApiDefinitionExample(); - example.createCriteria().andIdIn(ids); - List apiDefinitions = apiDefinitionMapper.selectByExample(example); - apiDefinitions.forEach(item -> { - LogDTO dto = new LogDTO( - item.getProjectId(), - "", - item.getId(), - item.getCreateUser(), - OperationLogType.UPDATE.name(), - OperationLogModule.API_DEFINITION, - item.getName()); - dto.setHistory(true); - dto.setPath("/api/definition/batch-move"); - dto.setMethod(HttpMethodConstants.POST.name()); - dto.setOriginalValue(JSON.toJSONBytes(item)); - dtoList.add(dto); - }); - } - return dtoList; + public void batchMoveLog(List ids, String userId, String projectId) { + saveBatchLog(projectId, ids, "/api/definition/batch-move", userId, OperationLogType.UPDATE.name(), true); } public LogDTO followLog(String id) { @@ -276,31 +215,8 @@ public class ApiDefinitionLogService { * * @return */ - public List batchRestoreLog(ApiDefinitionBatchRequest request) { - List ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); - List dtoList = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(ids)) { - ApiDefinitionExample example = new ApiDefinitionExample(); - example.createCriteria().andIdIn(ids).andDeletedEqualTo(false); - List apiDefinitions = apiDefinitionMapper.selectByExample(example); - apiDefinitions.forEach(item -> { - LogDTO dto = new LogDTO( - item.getProjectId(), - "", - item.getId(), - item.getCreateUser(), - OperationLogType.UPDATE.name(), - OperationLogModule.API_DEFINITION, - item.getName()); - dto.setHistory(true); - dto.setPath("/api/definition/batch-restore"); - dto.setMethod(HttpMethodConstants.POST.name()); - dto.setOriginalValue(JSON.toJSONBytes(item)); - dtoList.add(dto); - }); - } - - return dtoList; + public void batchRestoreLog(List ids, String userId, String projectId) { + saveBatchLog(projectId, ids, "/api/definition/batch-restore", userId, OperationLogType.UPDATE.name(), true); } @@ -331,31 +247,8 @@ public class ApiDefinitionLogService { /** * 删除回收站接口定义接口日志 */ - public List batchTrashDelLog(ApiDefinitionBatchRequest request) { - List ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); - List dtoList = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(ids)) { - ApiDefinitionExample example = new ApiDefinitionExample(); - example.createCriteria().andIdIn(ids).andDeletedEqualTo(true); - List apiDefinitions = apiDefinitionMapper.selectByExample(example); - apiDefinitions.forEach(item -> { - LogDTO dto = new LogDTO( - item.getProjectId(), - "", - item.getId(), - item.getCreateUser(), - OperationLogType.DELETE.name(), - OperationLogModule.API_DEFINITION, - item.getName()); - - dto.setPath("/api/definition/batch-trash-del"); - dto.setMethod(HttpMethodConstants.POST.name()); - dto.setOriginalValue(JSON.toJSONBytes(item)); - dtoList.add(dto); - }); - } - - return dtoList; + public void batchTrashDelLog(List ids, String userId, String projectId) { + saveBatchLog(projectId, ids, "/api/definition/batch-trash-del", userId, OperationLogType.DELETE.name(), true); } private ApiDefinitionDTO getOriginalValue(String id){ @@ -363,12 +256,52 @@ public class ApiDefinitionLogService { ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(id); if(null != apiDefinition){ // 2. 使用Optional避免空指针异常 - apiDefinitionService.handleBlob(id, apiDefinitionDTO); + handleBlob(id, apiDefinitionDTO); BeanUtils.copyBean(apiDefinitionDTO, apiDefinition); } return apiDefinitionDTO; } + public void handleBlob(String id, ApiDefinitionDTO apiDefinitionDTO) { + Optional 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 ids, String path, String userId, String operationType, boolean isHistory) { + List dtoList = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(ids)) { + Project project = projectMapper.selectByPrimaryKey(projectId); + ApiDefinitionExample example = new ApiDefinitionExample(); + example.createCriteria().andIdIn(ids); + List 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); + } + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java index 8a182d6653..ca38ec2897 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java @@ -1,30 +1,32 @@ package io.metersphere.api.service.definition; +import io.metersphere.api.constants.ApiDefinitionDocType; import io.metersphere.api.constants.ApiResourceType; import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.domain.*; import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest; import io.metersphere.api.dto.definition.*; -import io.metersphere.api.enums.ApiDefinitionDocType; import io.metersphere.api.mapper.*; import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.utils.ApiDataUtils; -import io.metersphere.sdk.constants.ApiReportStatus; -import io.metersphere.sdk.util.*; import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; import io.metersphere.project.service.ProjectService; +import io.metersphere.sdk.constants.ApiReportStatus; import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.constants.ModuleConstants; import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.*; import io.metersphere.system.dto.table.TableBatchProcessDTO; import io.metersphere.system.log.constants.OperationLogModule; import io.metersphere.system.service.UserLoginService; import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.NumGenerator; +import io.metersphere.system.utils.CustomFieldUtils; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; @@ -39,7 +41,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import static io.metersphere.api.controller.result.ApiResultCode.*; +import static io.metersphere.api.controller.result.ApiResultCode.API_DEFINITION_MODULE_NOT_EXIST; @Service @Transactional(rollbackFor = Exception.class) @@ -83,7 +85,17 @@ public class ApiDefinitionService { @Resource private ApiDefinitionModuleMapper apiDefinitionModuleMapper; - public List getApiDefinitionPage(ApiDefinitionPageRequest request){ + @Resource + private ApiDefinitionCustomFieldMapper apiDefinitionCustomFieldMapper; + + @Resource + private ExtApiDefinitionCustomFieldMapper extApiDefinitionCustomFieldMapper; + + @Resource + private ApiDefinitionLogService apiDefinitionLogService; + + public List getApiDefinitionPage(ApiDefinitionPageRequest request, String userId){ + CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request, userId); List list = extApiDefinitionMapper.list(request); if (!CollectionUtils.isEmpty(list)) { processApiDefinitions(list, request.getProjectId()); @@ -91,7 +103,8 @@ public class ApiDefinitionService { return list; } - public List getDocPage(ApiDefinitionPageRequest request){ + public List getDocPage(ApiDefinitionPageRequest request, String userId){ + CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request, userId); List list = extApiDefinitionMapper.list(request); if (!CollectionUtils.isEmpty(list)) { processApiDefinitionsDoc(list); @@ -159,6 +172,14 @@ public class ApiDefinitionService { resourceUpdateRequest.setLinkFileIds(request.getLinkFileIds()); apiFileResourceService.addFileResource(resourceUpdateRequest); + //保存自定义字段 + Map customFields = request.getCustomFields(); + if (MapUtils.isNotEmpty(customFields)) { + List list = new ArrayList<>(); + customFields.keySet().forEach(key -> createNewCustomField(apiDefinition.getId(), key, customFields.get(key), list)); + batchInsertCustomFields(list); + } + return apiDefinition; } @@ -197,6 +218,9 @@ public class ApiDefinitionService { apiDefinitionBlob.setResponse(request.getResponse().getBytes()); apiDefinitionBlobMapper.updateByPrimaryKeySelective(apiDefinitionBlob); + // 自定义字段 + handleUpdateCustomFields(request, false); + // 处理文件 ApiFileResourceUpdateRequest resourceUpdateRequest = getApiFileResourceUpdateRequest(originApiDefinition.getId(), originApiDefinition.getProjectId(), userId); resourceUpdateRequest.setUploadFileIds(request.getUploadFileIds()); @@ -210,11 +234,24 @@ public class ApiDefinitionService { public void batchUpdate(ApiDefinitionBatchUpdateRequest request, String userId) { ProjectService.checkResourceExist(request.getProjectId()); - List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); + List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false, userId); + // 记录更新前的数据 + apiDefinitionLogService.batchUpdateLog(ids, userId, request.getProjectId()); if (CollectionUtils.isNotEmpty(ids)) { if (request.getType().equals("tags")) { handleTags(request, userId, ids); - } else { + } else if(request.getType().equals("customs")){ + // 自定义字段处理 + ApiDefinitionCustomFieldDTO customField = request.getCustomField(); + Map customFieldMap = Collections.singletonMap(customField.getId(), customField.getValue()); + ApiDefinitionUpdateRequest apiDefinitionUpdateRequest = new ApiDefinitionUpdateRequest(); + BeanUtils.copyBean(apiDefinitionUpdateRequest, request); + apiDefinitionUpdateRequest.setCustomFields(customFieldMap); + ids.forEach(id -> { + apiDefinitionUpdateRequest.setId(id); + handleUpdateCustomFields(apiDefinitionUpdateRequest, request.isAppend()); + }); + }else { ApiDefinition apiDefinition = new ApiDefinition(); BeanUtils.copyBean(apiDefinition, request); apiDefinition.setUpdateUser(userId); @@ -226,6 +263,69 @@ public class ApiDefinitionService { } } + + private void handleUpdateCustomFields(ApiDefinitionUpdateRequest request, boolean append) { + Map customFields = request.getCustomFields(); + if (MapUtils.isNotEmpty(customFields)) { + List addFields = new ArrayList<>(); + List updateFields = new ArrayList<>(); + List originalFields = extApiDefinitionCustomFieldMapper.getApiCustomFields(List.of(request.getId()), request.getProjectId()); + Map 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 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 updateFields, Map 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 addFields) { + if (CollectionUtils.isNotEmpty(addFields)) { + apiDefinitionCustomFieldMapper.batchInsert(addFields); + } + } + + private void batchUpdateCustomFields(List updateFields) { + if (CollectionUtils.isNotEmpty(updateFields)) { + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + ApiDefinitionCustomFieldMapper apiCustomFieldMapper = sqlSession.getMapper(ApiDefinitionCustomFieldMapper.class); + for (ApiDefinitionCustomField apiDefinitionCustomField : updateFields) { + ApiDefinitionCustomFieldExample apiDefinitionCustomFieldExample = new ApiDefinitionCustomFieldExample(); + apiDefinitionCustomFieldExample.createCriteria().andApiIdEqualTo(apiDefinitionCustomField.getApiId()).andFieldIdEqualTo(apiDefinitionCustomField.getFieldId()); + apiCustomFieldMapper.updateByExample(apiDefinitionCustomField, apiDefinitionCustomFieldExample); + } + sqlSession.flushStatements(); + SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); + } + } + public ApiDefinition copy(ApiDefinitionCopyRequest request, String userId) { ApiDefinition copyApiDefinition = checkApiDefinition(request.getId()); ApiDefinition apiDefinition = new ApiDefinition(); @@ -260,21 +360,25 @@ public class ApiDefinitionService { public void delete(ApiDefinitionDeleteRequest request, String userId) { checkApiDefinition(request.getId()); - handleDeleteApiDefinition(Collections.singletonList(request.getId()),request.getDeleteAll(), request.getProjectId(), userId); + handleDeleteApiDefinition(Collections.singletonList(request.getId()),request.getDeleteAll(), request.getProjectId(), userId, false); } public void batchDelete(ApiDefinitionBatchRequest request, String userId) { - List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); + List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false, userId); if (CollectionUtils.isNotEmpty(ids)) { - handleDeleteApiDefinition(ids, request.getDeleteAll(), request.getProjectId(), userId); + handleDeleteApiDefinition(ids, request.getDeleteAll(), request.getProjectId(), userId, true); } } public void batchMove(ApiDefinitionBatchMoveRequest request, String userId) { - List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); + List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false, userId); if (!ids.isEmpty()) { + // 移动接口所有版本引用的数据 List refIds = extApiDefinitionMapper.getRefIds(ids, false); if (!refIds.isEmpty()) { + // 记录批量移动日志 + apiDefinitionLogService.batchMoveLog(extApiDefinitionMapper.getIdsByRefId(refIds, false), userId, request.getProjectId()); + extApiDefinitionMapper.batchMove(request, refIds, userId); } } @@ -287,12 +391,18 @@ public class ApiDefinitionService { List apiCaseComputeList = extApiDefinitionMapper.selectApiCaseByIdsAndStatusIsNotTrash(apiDefinitionIds, projectId); Map resultMap = apiCaseComputeList.stream().collect(Collectors.toMap(ApiCaseComputeDTO::getApiDefinitionId, Function.identity())); + List customFields = extApiDefinitionCustomFieldMapper.getApiCustomFields(apiDefinitionIds, projectId); + Map> customFieldMap = customFields.stream().collect(Collectors.groupingBy(ApiDefinitionCustomFieldDTO::getApiId)); + list.forEach(item -> { // Convert User IDs to Names item.setCreateUserName(userMap.get(item.getCreateUser())); item.setDeleteUserName(userMap.get(item.getDeleteUser())); item.setUpdateUserName(userMap.get(item.getUpdateUser())); + // Custom Fields + item.setCustomFields(customFieldMap.get(item.getId())); + // Calculate API Case Metrics ApiCaseComputeDTO apiCaseComputeDTO = resultMap.get(item.getId()); if (apiCaseComputeDTO != null) { @@ -442,7 +552,7 @@ public class ApiDefinitionService { return copyName; } - private void handleDeleteApiDefinition(List ids, boolean deleteAll, String projectId, String userId) { + private void handleDeleteApiDefinition(List ids, boolean deleteAll, String projectId, String userId, boolean isBatch) { if (deleteAll) { //全部删除 进入回收站 List refIds = extApiDefinitionMapper.getRefIds(ids, false); @@ -455,13 +565,17 @@ public class ApiDefinitionService { deleteApiRelatedData(subList, userId, projectId); } }); + // 记录删除到回收站的日志, 单条注解记录 + if(isBatch){ + apiDefinitionLogService.batchDelLog(delApiIds, userId, projectId); + } extApiDefinitionMapper.batchDeleteByRefId(subRefIds, userId, projectId); }); } } else { // 列表删除 if (!ids.isEmpty()) { - SubListUtils.dealForSubList(ids, 2000, subList -> doDelete(subList, userId, projectId)); + SubListUtils.dealForSubList(ids, 2000, subList -> doDelete(subList, userId, projectId, isBatch)); } } } @@ -496,7 +610,7 @@ public class ApiDefinitionService { extApiDefinitionMapper.updateLatestVersion(id, projectId); } - private void doDelete(List ids, String userId, String projectId) { + private void doDelete(List ids, String userId, String projectId, boolean isBatch) { if(CollectionUtils.isNotEmpty(ids)){ // 需要判断是否存在多个版本问题 ids.forEach(id -> { @@ -511,6 +625,11 @@ public class ApiDefinitionService { }); // 删除 case deleteApiRelatedData(ids, userId, projectId); + // 记录删除到回收站的日志, 单条注解记录 + if(isBatch){ + apiDefinitionLogService.batchDelLog(ids, userId, projectId); + } + // 删除接口到回收站 extApiDefinitionMapper.batchDeleteById(ids, userId, projectId); } @@ -531,16 +650,20 @@ public class ApiDefinitionService { public void restore(ApiDefinitionDeleteRequest request, String userId) { // 恢复接口到接口列表 - handleRestoreApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId()); + handleRestoreApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId(), false); } - private void handleRestoreApiDefinition(List ids, String userId, String projectId){ + private void handleRestoreApiDefinition(List ids, String userId, String projectId, boolean isBatch){ if (CollectionUtils.isNotEmpty(ids)) { - SubListUtils.dealForSubList(ids, 2000, subList -> doRestore(subList, userId, projectId)); + SubListUtils.dealForSubList(ids, 2000, subList -> doRestore(subList, userId, projectId, isBatch)); } } - private void doRestore(List apiIds, String userId, String projectId) { + private void doRestore(List apiIds, String userId, String projectId, boolean isBatch) { if (CollectionUtils.isNotEmpty(apiIds)) { + // 记录恢复数据之前的原数据,单条通过注解记录 + if(isBatch){ + apiDefinitionLogService.batchRestoreLog(apiIds, userId, projectId); + } extApiDefinitionMapper.batchRestoreById(apiIds, userId, projectId); apiIds.forEach(id -> { @@ -550,16 +673,7 @@ public class ApiDefinitionService { List apiDefinitionVersions = extApiDefinitionMapper.getApiDefinitionByRefId(apiDefinition.getRefId()); if (CollectionUtils.isNotEmpty(apiDefinitionVersions) && apiDefinitionVersions.size() > 1) { - String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(apiDefinition.getProjectId()); - // 清除所有最新标识 - clearLatestVersion(apiDefinition.getRefId(), apiDefinition.getProjectId()); - - // 获取最新数据,恢复的数据最新标识,则最新数据,反之获取最新一条数据 - ApiDefinition latestData = apiDefinition.getLatest() ? apiDefinition : getLatestData(apiDefinition.getRefId(), apiDefinition.getProjectId()); - // 恢复的数据不为最新标识,同时接口版本为默认版本,则更新此数据为最新标识 - if (!latestData.getLatest() && latestData.getVersionId().equals(defaultVersion)) { - updateLatestVersion(apiDefinition.getId(), apiDefinition.getProjectId()); - } + handleMultipleVersions(apiDefinition); } }); // 恢复接口关联数据 @@ -567,6 +681,19 @@ public class ApiDefinitionService { } } + private void handleMultipleVersions(ApiDefinition apiDefinition) { + String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(apiDefinition.getProjectId()); + // 清除所有最新标识 + clearLatestVersion(apiDefinition.getRefId(), apiDefinition.getProjectId()); + + // 获取最新数据,恢复的数据最新标识,则最新数据,反之获取最新一条数据 + ApiDefinition latestData = apiDefinition.getLatest() ? apiDefinition : getLatestData(apiDefinition.getRefId(), apiDefinition.getProjectId()); + // 恢复的数据不为最新标识,同时接口版本为默认版本,则更新此数据为最新标识 + if (!latestData.getLatest() && latestData.getVersionId().equals(defaultVersion)) { + updateLatestVersion(apiDefinition.getId(), apiDefinition.getProjectId()); + } + } + private void recoverApiRelatedData(List apiIds, String userId, String projectId){ // 是否存在 case 恢复 case List caseLists = extApiTestCaseMapper.getCaseInfoByApiIds(apiIds, true); @@ -575,30 +702,30 @@ public class ApiDefinitionService { } } public void trashDel(ApiDefinitionDeleteRequest request, String userId) { - handleTrashDelApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId()); + handleTrashDelApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId(), false); } public void batchRestore(ApiDefinitionBatchRequest request, String userId) { - List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), true); + List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), true, userId); if (CollectionUtils.isNotEmpty(ids)) { - handleRestoreApiDefinition(ids, userId, request.getProjectId()); + handleRestoreApiDefinition(ids, userId, request.getProjectId(), true); } } public void batchTrashDel(ApiDefinitionBatchRequest request, String userId) { - List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), true); + List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), true, userId); if (CollectionUtils.isNotEmpty(ids)) { - handleTrashDelApiDefinition(ids, userId, request.getProjectId()); + handleTrashDelApiDefinition(ids, userId, request.getProjectId(), true); } } - private void handleTrashDelApiDefinition(List ids, String userId, String projectId){ + private void handleTrashDelApiDefinition(List ids, String userId, String projectId, boolean isBatch){ if (CollectionUtils.isNotEmpty(ids)) { - SubListUtils.dealForSubList(ids, 2000, subList -> doTrashDel(subList, userId, projectId)); + SubListUtils.dealForSubList(ids, 2000, subList -> doTrashDel(subList, userId, projectId, isBatch)); } } - private void doTrashDel(List ids, String userId, String projectId){ + private void doTrashDel(List ids, String userId, String projectId, boolean isBatch){ if(CollectionUtils.isNotEmpty(ids)){ // 删除上传的文件 ids.forEach(id -> { @@ -613,10 +740,15 @@ public class ApiDefinitionService { // 删除接口关联数据 trashDelApiRelatedData(ids, userId, projectId); + // 记录批量删除日志,单条删除通过注解记录 + if(isBatch){ + apiDefinitionLogService.batchTrashDelLog(ids, userId, projectId); + } // 删除接口 ApiDefinitionExample apiDefinitionExample = new ApiDefinitionExample(); apiDefinitionExample.createCriteria().andIdIn(ids).andDeletedEqualTo(true).andProjectIdEqualTo(projectId); apiDefinitionMapper.deleteByExample(apiDefinitionExample); + } } @@ -632,9 +764,11 @@ public class ApiDefinitionService { } // 获取批量操作选中的ID - public List getBatchApiIds(T dto, String projectId, String protocol, boolean deleted) { + public List getBatchApiIds(T dto, String projectId, String protocol, boolean deleted, String userId) { TableBatchProcessDTO request = (TableBatchProcessDTO) dto; if (request.isSelectAll()) { + // 全选 + CustomFieldUtils.setBaseQueryRequestCustomMultipleFields(request.getCondition(), userId); List ids = extApiDefinitionMapper.getIds(request, projectId, protocol, deleted); if (CollectionUtils.isNotEmpty(request.getExcludeIds())) { ids.removeAll(request.getExcludeIds()); @@ -652,7 +786,9 @@ public class ApiDefinitionService { public ApiDefinitionDTO getApiDefinitionInfo(String id, String userId, ApiDefinition apiDefinition) { ApiDefinitionDTO apiDefinitionDTO = new ApiDefinitionDTO(); // 2. 使用Optional避免空指针异常 - handleBlob(id, apiDefinitionDTO); + apiDefinitionLogService.handleBlob(id, apiDefinitionDTO); + // 3. 查询自定义字段 + handleCustomFields(id, apiDefinition.getProjectId(), apiDefinitionDTO); // 3. 使用Stream简化集合操作 ApiDefinitionFollowerExample example = new ApiDefinitionFollowerExample(); example.createCriteria().andApiDefinitionIdEqualTo(id).andUserIdEqualTo(userId); @@ -661,15 +797,10 @@ public class ApiDefinitionService { return apiDefinitionDTO; } - public void handleBlob(String id, ApiDefinitionDTO apiDefinitionDTO) { - Optional apiDefinitionBlobOptional = Optional.ofNullable(apiDefinitionBlobMapper.selectByPrimaryKey(id)); - apiDefinitionBlobOptional.ifPresent(blob -> { - apiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(blob.getRequest()), AbstractMsTestElement.class)); - // blob.getResponse() 为 null 时不进行转换 - if (blob.getResponse() != null) { - apiDefinitionDTO.setResponse(ApiDataUtils.parseArray(new String(blob.getResponse()), HttpResponse.class)); - } - }); + public void handleCustomFields(String id, String projectId, ApiDefinitionDTO apiDefinitionDTO) { + List customFields = extApiDefinitionCustomFieldMapper.getApiCustomFields(Collections.singletonList(id), projectId); + Map> customFieldMap = customFields.stream().collect(Collectors.groupingBy(ApiDefinitionCustomFieldDTO::getApiId)); + apiDefinitionDTO.setCustomFields(customFieldMap.get(id)); } public ApiDefinitionDocDTO getDocInfo(ApiDefinitionDocRequest request, String userId) { @@ -680,7 +811,7 @@ public class ApiDefinitionService { List list = extApiDefinitionMapper.listDoc(request); if (!list.isEmpty()) { ApiDefinitionDTO first = list.get(0); - handleBlob(first.getId(), first); + apiDefinitionLogService.handleBlob(first.getId(), first); if(ApiDefinitionDocType.ALL.name().equals(request.getType())){ apiDefinitionDocDTO.setDocTitle(Translator.get(ALL_API)); } else { diff --git a/backend/services/api-test/src/main/resources/apiGeneratorConfig.xml b/backend/services/api-test/src/main/resources/apiGeneratorConfig.xml index 5bc06be643..865ed4c47b 100644 --- a/backend/services/api-test/src/main/resources/apiGeneratorConfig.xml +++ b/backend/services/api-test/src/main/resources/apiGeneratorConfig.xml @@ -99,6 +99,7 @@
+
diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java index e2105454cf..621e15ed7b 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java @@ -5,7 +5,7 @@ import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.domain.*; import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.request.http.MsHTTPElement; -import io.metersphere.api.enums.ApiDefinitionDocType; +import io.metersphere.api.constants.ApiDefinitionDocType; import io.metersphere.api.mapper.*; import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.utils.ApiDataUtils; @@ -28,8 +28,8 @@ import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.utils.Pager; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.*; -import org.junit.platform.commons.util.StringUtils; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; @@ -40,6 +40,7 @@ import org.springframework.test.web.servlet.MvcResult; import java.nio.charset.StandardCharsets; import java.util.*; +import java.util.stream.Collectors; import static io.metersphere.api.controller.result.ApiResultCode.API_DEFINITION_NOT_EXIST; import static io.metersphere.system.controller.handler.result.MsHttpResultCode.NOT_FOUND; @@ -101,6 +102,9 @@ public class ApiDefinitionControllerTests extends BaseTest { @Resource private ApiDefinitionModuleMapper apiDefinitionModuleMapper; + @Resource + private ExtApiDefinitionCustomFieldMapper extApiDefinitionCustomFieldMapper; + @Resource private FileMetadataService fileMetadataService; private static String fileMetadataId; @@ -197,7 +201,7 @@ public class ApiDefinitionControllerTests extends BaseTest { // 创建并返回一个 ApiDefinitionAddRequest 对象,用于测试 String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(DEFAULT_PROJECT_ID); ApiDefinitionAddRequest request = new ApiDefinitionAddRequest(); - request.setName("接口定义"); + request.setName("接口定义test"); request.setProtocol("HTTP"); request.setProjectId(DEFAULT_PROJECT_ID); request.setMethod("POST"); @@ -207,6 +211,11 @@ public class ApiDefinitionControllerTests extends BaseTest { request.setVersionId(defaultVersion); request.setDescription("描述内容"); request.setTags(new LinkedHashSet<>(List.of("tag1", "tag2"))); + Map customFieldMap = new HashMap<>(); + customFieldMap.put("custom-field", "oasis"); + customFieldMap.put("test_field", JSON.toJSONString(List.of("test"))); + + request.setCustomFields(customFieldMap); return request; } @@ -241,6 +250,12 @@ public class ApiDefinitionControllerTests extends BaseTest { example.createCriteria().andApiDefinitionIdEqualTo(apiDefinition.getId()).andUserIdEqualTo("admin"); List followers = apiDefinitionFollowerMapper.selectByExample(example); copyApiDefinitionDTO.setFollow(CollectionUtils.isNotEmpty(followers)); + + List customFields = extApiDefinitionCustomFieldMapper.getApiCustomFields(Collections.singletonList(apiDefinition.getId()), apiDefinition.getProjectId()); + if(!customFields.isEmpty()) { + Map> customFieldMap = customFields.stream().collect(Collectors.groupingBy(ApiDefinitionCustomFieldDTO::getApiId)); + copyApiDefinitionDTO.setCustomFields(customFieldMap.get(apiDefinition.getId())); + } if(apiDefinitionBlob != null){ copyApiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(apiDefinitionBlob.getRequest()), AbstractMsTestElement.class)); copyApiDefinitionDTO.setResponse(ApiDataUtils.parseArray(new String(apiDefinitionBlob.getResponse()), HttpResponse.class)); @@ -268,6 +283,11 @@ public class ApiDefinitionControllerTests extends BaseTest { request.setMethod("POST"); request.setModuleId("default1"); request.setTags(new LinkedHashSet<>(List.of("tag1", "tag2-update"))); + Map customFieldMap = new HashMap<>(); + customFieldMap.put("custom-field", "oasis-update"); + customFieldMap.put("test_field", JSON.toJSONString(List.of("test-update"))); + + request.setCustomFields(customFieldMap); MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement(); request.setRequest(ApiDataUtils.toJSONString(msHttpElement)); List msHttpResponse = MsHTTPElementTest.getMsHttpResponse(); @@ -424,6 +444,18 @@ public class ApiDefinitionControllerTests extends BaseTest { apiDefinitionBatchUpdateRequest.setAppend(false); this.requestPostWithOk(BATCH_UPDATE, apiDefinitionBatchUpdateRequest); assertBatchUpdateApiDefinition(apiDefinitionBatchUpdateRequest, List.of("1003","1004")); + // 自定义字段追加 + apiDefinitionBatchUpdateRequest.setType("customs"); + apiDefinitionBatchUpdateRequest.setSelectIds(List.of("1002","1003","1004")); + ApiDefinitionCustomFieldDTO field = new ApiDefinitionCustomFieldDTO(); + field.setId("test_field"); + field.setValue(JSON.toJSONString(List.of("test1-batch"))); + apiDefinitionBatchUpdateRequest.setCustomField(field); + apiDefinitionBatchUpdateRequest.setAppend(true); + this.requestPostWithOk(BATCH_UPDATE, apiDefinitionBatchUpdateRequest); + // 自定义字段覆盖 + apiDefinitionBatchUpdateRequest.setAppend(false); + this.requestPostWithOk(BATCH_UPDATE, apiDefinitionBatchUpdateRequest); // 修改协议类型 apiDefinitionBatchUpdateRequest.setType("method"); apiDefinitionBatchUpdateRequest.setMethod("batch-method"); @@ -662,13 +694,30 @@ public class ApiDefinitionControllerTests extends BaseTest { filters.put("status", Arrays.asList("Underway", "Completed")); filters.put("method", List.of("GET")); filters.put("version_id", List.of("1005704995741369851")); + filters.put("custom_multiple_custom-field", List.of("oasis")); request.setFilter(filters); } private void configureCombineSearch(ApiDefinitionPageRequest request) { Map map = new HashMap<>(); - map.put("name", Map.of("operator", "like", "value", "test-1")); + map.put("name", Map.of("operator", "like", "value", "test")); map.put("method", Map.of("operator", "in", "value", Arrays.asList("GET", "POST"))); + map.put("createUser", Map.of("operator", "current user", "value", StringUtils.EMPTY)); + List> customs = new ArrayList<>(); + Map 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 currentUserCustom = new HashMap<>(); + currentUserCustom.put("id", "test_field"); + currentUserCustom.put("operator", "current user"); + currentUserCustom.put("type", "multipleMember"); + currentUserCustom.put("value", "current user"); + customs.add(currentUserCustom); + map.put("customs", customs); + request.setCombine(map); } @@ -969,7 +1018,6 @@ public class ApiDefinitionControllerTests extends BaseTest { // @@校验日志 checkLog("1002", OperationLogType.UPDATE); checkLog("1004", OperationLogType.UPDATE); - checkLog("1005", OperationLogType.UPDATE); // 恢复全部 条件为关键字为st-6的数据 request.setSelectAll(true); diff --git a/backend/services/api-test/src/test/resources/dml/init_api_definition.sql b/backend/services/api-test/src/test/resources/dml/init_api_definition.sql index 937a8b851d..7de4e0ff86 100644 --- a/backend/services/api-test/src/test/resources/dml/init_api_definition.sql +++ b/backend/services/api-test/src/test/resources/dml/init_api_definition.sql @@ -42,3 +42,17 @@ INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1003', INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1004', 0x504B03041400080808004A547857000000000000000000000000030000007A6970CD92B14EC3301040FFE5E6A8296B36842A180055281B62709D4B6D61FBDCF3991055FD779C840C949185C5A77BB6CFEF6C9F21921B3D7134563F2B8FD0C0537A68DBFDCEA1C720504158F01C2AC0A00EAEE4BD72092BD0C6BA8E314013B27315647665AD11894D5D0FC3B0114CB2D1E4CBD6A8C494C97A4225F52886BA02EE776D490FD48D6B1583AA434ED0BC9EE11D0B9EC70A3E94CB93CB127FC9749834DB28968ACF9CC1E5AD029ECEFB7BA9A90156BE1D23AE9E8CA76C194B13C2B9ECF2363C62384E6D2E0BBCFAFC093068EAD66326B953461EFFAD1D8941BEA3D0DB233467D014026A69AD47CAE54E6FB6DBED7CBF9142C22BAC91C5F6562BC15B6755794D5073ACA027E76878C1AEE86949ABA0CA4257F03253B32A2C9A9171CFA431259A3EC937A424D7F4F205504B070852639A522B010000E0020000504B010214001400080808004A54785752639A522B010000E00200000300000000000000000000000000000000007A6970504B05060000000001000100310000005C0100000000, 0x504B03041400080808004A547857000000000000000000000000030000007A6970A58F414BC4301085FFCB9C03B69EA4B74559141465295E9622B3CD743798263199C82EA5FFDD49EB82772F19DECB9BEF25FB098C86A656C09740D0C063DBBE81028763513B4AC1BB44B55827424D3141B39FE0932E725B4E05DF6873C9AE5301393C583106B48914684A7D34818D77122A0AE64EC1C16B414CCB6CD7EAEDEBEEE5E361D36E0432443FBE17E03FFB98124B2660C4F1B786E95CAC485FD94492CF73CCB2371AF74CEEC82768EE44E1F9AAEA4A41EF1D93E33F849B60D1B8A5BEF79AAE94C1587A12A6CBD6CEDDAC2031724EF74B046EAB0ACA0B07CC96B7168FEBDADCFD00504B0708FF595995E400000087010000504B010214001400080808004A547857FF595995E4000000870100000300000000000000000000000000000000007A6970504B0506000000000100010031000000150100000000); INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1005', 0x504B030414000808080049547857000000000000000000000000030000007A6970CD92B14EC3301040FFE5E6A8296B36842A180055281B62709D4B6D61FBDCF3991055FD779C840C949185C5A77BB6CFEF6C9F21921B3D7134563F2B8FD0C0537A68DBFDCEA1C720504158F01C2AC0A00EAEE4BD72092BD0C6BA8E314013B27315647665AD11894D5D0FC3B0114CB2D1E4CBD6A8C494C97A4225F52886BA02EE776D490FD48D6B1583AA434ED0BC9EE11D0B9EC70A3E94CB93CB127FC9749834DB28968ACF9CC1E5AD029ECEFB7BA9A90156BE1D23AE9E8CA76C194B13C2B9ECF2363C62384E6D2E0BBCFAFC093068EAD66326B953461EFFAD1D8941BEA3D0DB233467D014026A69AD47CAE54E6FB6DBED7CBF9142C22BAC91C5F6562BC15B6755794D5073ACA027E76878C1AEE86949ABA0CA4257F03253B32A2C9A9171CFA431259A3EC937A424D7F4F205504B070852639A522B010000E0020000504B010214001400080808004954785752639A522B010000E00200000300000000000000000000000000000000007A6970504B05060000000001000100310000005C0100000000, 0x504B030414000808080049547857000000000000000000000000030000007A6970A58F414BC4301085FFCB9C03B69EA4B74559141465295E9622B3CD743798263199C82EA5FFDD49EB82772F19DECB9BEF25FB098C86A656C09740D0C063DBBE81028763513B4AC1BB44B55827424D3141B39FE0932E725B4E05DF6873C9AE5301393C583106B48914684A7D34818D77122A0AE64EC1C16B414CCB6CD7EAEDEBEEE5E361D36E0432443FBE17E03FFB98124B2660C4F1B786E95CAC485FD94492CF73CCB2371AF74CEEC82768EE44E1F9AAEA4A41EF1D93E33F849B60D1B8A5BEF79AAE94C1587A12A6CBD6CEDDAC2031724EF74B046EAB0ACA0B07CC96B7168FEBDADCFD00504B0708FF595995E400000087010000504B0102140014000808080049547857FF595995E4000000870100000300000000000000000000000000000000007A6970504B0506000000000100010031000000150100000000); INSERT INTO `api_definition_blob` (`id`, `request`, `response`) VALUES ('1006', 0x504B030414000808080049547857000000000000000000000000030000007A6970CD92B14EC3301040FFE5E6A8296B36842A180055281B62709D4B6D61FBDCF3991055FD779C840C949185C5A77BB6CFEF6C9F21921B3D7134563F2B8FD0C0537A68DBFDCEA1C720504158F01C2AC0A00EAEE4BD72092BD0C6BA8E314013B27315647665AD11894D5D0FC3B0114CB2D1E4CBD6A8C494C97A4225F52886BA02EE776D490FD48D6B1583AA434ED0BC9EE11D0B9EC70A3E94CB93CB127FC9749834DB28968ACF9CC1E5AD029ECEFB7BA9A90156BE1D23AE9E8CA76C194B13C2B9ECF2363C62384E6D2E0BBCFAFC093068EAD66326B953461EFFAD1D8941BEA3D0DB233467D014026A69AD47CAE54E6FB6DBED7CBF9142C22BAC91C5F6562BC15B6755794D5073ACA027E76878C1AEE86949ABA0CA4257F03253B32A2C9A9171CFA431259A3EC937A424D7F4F205504B070852639A522B010000E0020000504B010214001400080808004954785752639A522B010000E00200000300000000000000000000000000000000007A6970504B05060000000001000100310000005C0100000000, 0x504B030414000808080049547857000000000000000000000000030000007A6970A58F414BC4301085FFCB9C03B69EA4B74559141465295E9622B3CD743798263199C82EA5FFDD49EB82772F19DECB9BEF25FB098C86A656C09740D0C063DBBE81028763513B4AC1BB44B55827424D3141B39FE0932E725B4E05DF6873C9AE5301393C583106B48914684A7D34818D77122A0AE64EC1C16B414CCB6CD7EAEDEBEEE5E361D36E0432443FBE17E03FFB98124B2660C4F1B786E95CAC485FD94492CF73CCB2371AF74CEEC82768EE44E1F9AAEA4A41EF1D93E33F849B60D1B8A5BEF79AAE94C1587A12A6CBD6CEDDAC2031724EF74B046EAB0ACA0B07CC96B7168FEBDADCFD00504B0708FF595995E400000087010000504B0102140014000808080049547857FF595995E4000000870100000300000000000000000000000000000000007A6970504B0506000000000100010031000000150100000000); + +DELETE FROM `api_definition_custom_field` WHERE `api_id` in ('1003', '1004'); +INSERT INTO api_definition_custom_field (api_id, field_id, value) VALUE ('1003', 'test_field', '["default", "default-1"]'); +INSERT INTO api_definition_custom_field (api_id, field_id, value) VALUE ('1004', 'test_field', '[]'); + +DELETE FROM `custom_field` WHERE `id` in ('test_field', 'custom-field'); +INSERT INTO custom_field (id, name, scene, type, remark, internal, scope_type, create_time, update_time, create_user, scope_id) VALUE + ('test_field', '测试字段', 'API', 'MULTIPLE_SELECT', '', 0, 'PROJECT', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', '100001100001'), + ('custom-field', '测试字段', 'API', 'SELECT', '', 0, 'PROJECT', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', '100001100001'); + +DELETE FROM `template` WHERE `id` in ('api-template-id', 'default-api-template-id'); +INSERT INTO template (id, name, remark, internal, update_time, create_time, create_user, scope_type, scope_id, enable_third_part, scene) VALUES + ('api-template-id', 'api-template', '', 0, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', '100001100001', 0, 'API'), + ('default-api-template-id', 'api-default-template', '', 0, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', '100001100001', 0, 'API'); diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/utils/CustomFieldUtils.java b/backend/services/system-setting/src/main/java/io/metersphere/system/utils/CustomFieldUtils.java new file mode 100644 index 0000000000..4be307e24d --- /dev/null +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/utils/CustomFieldUtils.java @@ -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 jsonValues = value.stream().map(item -> "[\"".concat(item).concat("\"]")).toList(); + request.getFilter().put(key, jsonValues); + } + }); + } + } + + private static void handleCombineFields(BaseCondition request, String userId) { + Map combine = request.getCombine(); + if (MapUtils.isNotEmpty(combine)) { + combine.forEach((k, v) -> { + if (StringUtils.equals(k, COMBINE_CUSTOM) && ObjectUtils.isNotEmpty(v)) { + handleCombineCustomFields((List>) v, userId); + } else { + handleCombineField(v, userId); + } + }); + } + } + + private static void handleCombineCustomFields(List> 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 customValues = JSON.parseArray(fieldValue, String.class); + List 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 combineField = new HashMap<>((Map) 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 orignalList = JSON.parseArray(originalVal, String.class); + List appendList = JSON.parseArray(appendVal, String.class); + orignalList.addAll(appendList); + // 追加后需去重 + return JSON.toJSONString(orignalList.stream().distinct().toList()); + } +} diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/CustomFieldTests.java b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/CustomFieldTests.java new file mode 100644 index 0000000000..f091738cdc --- /dev/null +++ b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/CustomFieldTests.java @@ -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> filters = new HashMap<>(); + filters.put("status", Arrays.asList("Underway", "Completed")); + filters.put("custom_multiple_custom-field", List.of("oasis")); + + Map 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> customs = new ArrayList<>(); + Map 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 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 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); + } +}