feat(接口测试): 增加导入功能

This commit is contained in:
wxg0103 2023-12-27 16:10:45 +08:00 committed by Craftsman
parent bf68a4e688
commit 3f6ba6680c
32 changed files with 5026 additions and 115 deletions

View File

@ -206,7 +206,6 @@ public class PermissionConstants {
/*------ end: PROJECT_TEMPLATE ------*/
/*------ start: FUNCTIONAL_CASE ------*/
public static final String FUNCTIONAL_CASE_READ = "FUNCTIONAL_CASE:READ";
public static final String FUNCTIONAL_CASE_READ_ADD = "FUNCTIONAL_CASE:READ+ADD";
@ -278,12 +277,10 @@ public class PermissionConstants {
public static final String PROJECT_API_SCENARIO_DEBUG = "PROJECT_API_SCENARIO:READ+DEBUG";
/*------ end: API_MANAGEMENT ------*/
//个人中心
/*------ start: PERSONAL_CENTER ------*/
public static final String SYSTEM_PERSONAL_API_KEY_ADD = "SYSTEM_PERSONAL_API_KEYD:READ+ADD";
public static final String SYSTEM_PERSONAL_API_KEY_ADD = "SYSTEM_PERSONAL_API_KEY:READ+ADD";
public static final String SYSTEM_PERSONAL_API_KEY_DELETE = "SYSTEM_PERSONAL_API_KEY:READ+DELETE";
public static final String SYSTEM_PERSONAL_API_KEY_READ = "SYSTEM_PERSONAL_API_KEY:READ";
public static final String SYSTEM_PERSONAL_API_KEY_UPDATE = "SYSTEM_PERSONAL_API_KEY:READ+UPDATE";

View File

@ -304,3 +304,4 @@ api_definition_exist=接口已存在
api_definition_mock_exist=接口 MOCK 已存在
execute_resource_pool_not_config_error=请在【项目管理-应用管理-接口测试】中选择资源池
resource_pool_execute_error=资源池调用失败
api_swagger_url_error=Swagger url无法连通

View File

@ -308,3 +308,4 @@ api_definition_exist=The API already exists
api_definition_mock_exist=The API MOCK already exists
execute_resource_pool_not_config_error=Select a resource pool in 【Project Management - Application Management - Interface Testing】
resource_pool_execute_error=The resource pool call failed
api_swagger_url_error=Swagger url unable to connect

View File

@ -308,3 +308,4 @@ api_definition_exist=接口已存在
api_definition_mock_exist=接口 MOCK 已存在
execute_resource_pool_not_config_error=请在【项目管理-应用管理-接口测试】中选择资源池
resource_pool_execute_error=资源池调用失败
api_swagger_url_error=Swagger url无法连通

View File

@ -308,3 +308,4 @@ api_definition_exist=接口已存在
api_definition_mock_exist=接口 MOCK 已存在
execute_resource_pool_not_config_error=請在【項目管理-應用管理-接口測試】中選擇資源池
resource_pool_execute_error=資源池調用失敗
api_swagger_url_error=Swagger url無法調解

View File

@ -492,3 +492,5 @@ apikey_has_expired=ApiKey 已过期
user_key.id.not_blank=ApiKey ID不能为空
expire_time_not_null=过期时间不能为空
permission.organization.name=组织
swagger_parse_error_with_auth=Swagger 解析失败,请确认认证信息是否正确或文件格式是否正确!
swagger_parse_error=Swagger 解析失败,请确认文件格式是否正确!

View File

@ -502,3 +502,5 @@ apikey_has_expired=ApiKey has expired
user_key.id.not_blank=User key id can not blank
expire_time_not_null=Expire time can not null
permission.organization.name=Organization
swagger_parse_error_with_auth=Swagger parsing failed, please confirm whether the verification information is correct or the file format is correct!
swagger_parse_error=Swagger parsing failed or file format is incorrect!

View File

@ -498,3 +498,5 @@ apikey_has_expired=ApiKey 已过期
user_key.id.not_blank=ApiKey ID不能为空
expire_time_not_null=过期时间不能为空
permission.organization.name=组织
swagger_parse_error_with_auth=Swagger 解析失败,请确认认证信息是否正确或文件格式是否正确!
swagger_parse_error=Swagger 解析失败,请确认文件格式是否正确!

View File

@ -498,3 +498,5 @@ apikey_has_expired=ApiKey 已過期
user_key.id.not_blank=ApiKey ID不能为空
expire_time_not_null=過期時間不能為空
permission.organization.name=組織
swagger_parse_error_with_auth=Swagger 解析失敗,請檢查 Swagger 接口是否需要認證
swagger_parse_error=Swagger 解析失敗

View File

@ -4,6 +4,8 @@ import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.importdto.ApiDefinitionImport;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.service.definition.ApiDefinitionLogService;
import io.metersphere.api.service.definition.ApiDefinitionService;
import io.metersphere.sdk.constants.PermissionConstants;
@ -73,6 +75,7 @@ public class ApiDefinitionController {
public void delete(@Validated @RequestBody ApiDefinitionDeleteRequest request) {
apiDefinitionService.delete(request, SessionUtils.getUserId());
}
@PostMapping(value = "/batch-del")
@Operation(summary = "接口测试-接口管理-批量删除接口定义到回收站")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE)
@ -141,6 +144,7 @@ public class ApiDefinitionController {
public void recover(@Validated @RequestBody ApiDefinitionDeleteRequest request) {
apiDefinitionService.recover(request, SessionUtils.getUserId());
}
@PostMapping(value = "/trash-del")
@Operation(summary = "接口测试-接口管理-删除回收站接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE)
@ -149,6 +153,7 @@ public class ApiDefinitionController {
public void trashDel(@Validated @RequestBody ApiDefinitionDeleteRequest request) {
apiDefinitionService.trashDel(request, SessionUtils.getUserId());
}
@PostMapping(value = "/batch-recover")
@Operation(summary = "接口测试-接口管理-批量从回收站恢复接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_RECOVER)
@ -190,4 +195,11 @@ public class ApiDefinitionController {
return apiDefinitionService.getDocInfo(request);
}
@PostMapping(value = "/import", consumes = {"multipart/form-data"})
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_IMPORT)
@Operation(summary = "接口测试-接口管理-导入接口定义")
public ApiDefinitionImport testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ImportRequest request) {
return apiDefinitionService.apiTestImport(file, request);
}
}

View File

@ -0,0 +1,22 @@
package io.metersphere.api.dto.definition.importdto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class ApiDeatlWithData {
@Schema(description = "相同的数据的key")
List<String> sameList = new ArrayList<>();
@Schema(description = "不同的数据的key")
List<String> differenceList = new ArrayList<>();
@Schema(description = "数据库中存在的数据")
Map<String, ApiDefinitionImportDTO> apiDateMap = new HashMap<>();
@Schema(description = "导入的数据")
Map<String, ApiDefinitionImportDTO> importDataMap = new HashMap<>();
}

View File

@ -0,0 +1,21 @@
package io.metersphere.api.dto.definition.importdto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Data
public class ApiDeatlWithDataUpdate {
@Schema(description = "需要更新模块的数据")
List<ApiDefinitionImportDTO> updateModuleData = new ArrayList<>();
@Schema(description = "需要更新接口的数据")
List<ApiDefinitionImportDTO> updateRequestData = new ArrayList<>();
@Schema(description = "需要新增的接口数据")
List<ApiDefinitionImportDTO> addModuleData = new ArrayList<>();
@Schema(description = "需要新增的日志数据")
Map<String, ApiDefinitionImportDTO> logData;
}

View File

@ -0,0 +1,18 @@
package io.metersphere.api.dto.definition.importdto;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class ApiDefinitionImport {
private String projectName;
private String protocol;
private List<ApiDefinitionImportDTO> data;
// 新版本带用例导出
private List<ApiTestCaseDTO> cases = new ArrayList<>();
}

View File

@ -1,4 +1,4 @@
package io.metersphere.api.dto.importdto;
package io.metersphere.api.dto.definition.importdto;
import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.dto.definition.HttpResponse;

View File

@ -1,31 +1,45 @@
package io.metersphere.api.dto.request;
import io.metersphere.api.dto.request.http.Header;
import io.metersphere.api.dto.request.http.QueryParam;
import io.metersphere.api.dto.request.http.auth.HTTPAuth;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
public class ImportRequest {
private String id;
private String name;
private String moduleId;
private String environmentId;
private String projectId;
private String platform;
@Schema(description = "导入的swagger地址")
private String swaggerUrl;
//导入策略
private String modeId;
@Schema(description = "如果是定时任务的时候 需要传入创建人id")
private String userId;
private String versionId; // 新导入选择的版本
private String updateVersionId; // 覆盖导入已存在的接口选择的版本
private String defaultVersion;
private String platform;
//调用类型
private String type;
// 是否开启自定义ID
private Boolean openCustomNum = false;
// 是否覆盖模块
@Schema(description = "是否覆盖模块")
private Boolean coverModule;
@Schema(description = "是否同步导入用例")
private Boolean syncCase;
@Schema(description = "是否覆盖数据")
private Boolean coverData;
// 当前协议
private String protocol;
//上传文件来源目前用于辨别是否是idea插件
private String origin;
@Schema(description = "swagger的请求头参数")
private List<Header> headers;
@Schema(description = "swagger的请求参数")
private List<QueryParam> arguments;
@Schema(description = "swagger的认证参数")
private HTTPAuth authManager;
@Schema(description = "唯一标识 默认是Method & Path 后续估计会补充")
private String uniquelyIdentifies = "Method & Path";
}

View File

@ -1,5 +1,6 @@
package io.metersphere.api.dto.request.http.body;
import io.metersphere.api.dto.schema.JsonSchemaItem;
import lombok.Data;
/**
@ -20,5 +21,9 @@ public class JsonBody {
* 启用 json-schema 时的参数对象
* todo json-schema 编辑器待调研暂时使用 Object 类型
*/
private Object jsonSchema;
private JsonSchemaItem jsonSchema;
/**
* 是否开启转换
*/
private Boolean enable = false;
}

View File

@ -0,0 +1,58 @@
package io.metersphere.api.dto.schema;
import lombok.Data;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Data
public class JsonSchemaItem {
private Object example;
private String id;
private String title;
private String type = "string";
private String description;
private JsonSchemaItem items;
private Map<String, Object> mock;
private Map<String, JsonSchemaItem> properties;
private JsonSchemaItem additionalProperties;
private List<String> required;
private String pattern;
private Integer maxLength;
private Integer minLength;
private BigDecimal minimum;
private BigDecimal maximum;
private String schema;
private String format;
private List<String> enumString;
private List<Number> enumInteger;
private List<BigDecimal> enumNumber;
private Map<String, Object> extensions = null;
public JsonSchemaItem() {
this.mock = new LinkedHashMap<>();
this.mock.put("mock", "");
}
public JsonSchemaItem(String type) {
this.type = type;
this.initParam(type);
}
public void setType(String type) {
this.type = type;
this.initParam(type);
}
private void initParam(String type) {
if (type.equals("object")) {
this.properties = new LinkedHashMap<>();
} else if (type.equals("array")) {
this.items = new JsonSchemaItem();
}
}
}

View File

@ -0,0 +1,33 @@
package io.metersphere.api.enums;
public class PropertyConstant {
public final static String REQUIRED = "required";
public final static String ALL_OF = "allOf";
public final static String PROPERTIES = "properties";
public final static String ADDITIONAL_PROPERTIES = "additionalProperties";
public final static String TYPE = "type";
public final static String MS_OBJECT = "MS-OBJECT";
public final static String ARRAY = "array";
public final static String OBJECT = "object";
public final static String DEFAULT = "default";
public final static String STRING = "string";
public final static String BOOLEAN = "boolean";
public final static String NUMBER = "number";
public final static String INTEGER = "integer";
public final static String MOCK = "mock";
public final static String NULL = "null";
public final static String ENUM = "enum";
public final static String ITEMS = "items";
public final static String ITEM = "_item";
public final static String NONE = "none";
public final static String ROOT = "root";
public final static String XML_PATH = "XML_PATH";
public final static String ASS_OPTION = "ASS_OPTION";
public final static String EXPECTED_VALUE = "EXPECTED_VALUE";
public final static String ELEMENT_CONDITION = "ElementCondition";
public final static String ENVIRONMENT = "useEnvironment";
public final static String ENVIRONMENT_ID = "environmentId";
public final static String DATASOURCE_ID = "dataSourceId";
public final static String PROJECT_ID = "projectId";
}

View File

@ -2,7 +2,7 @@ package io.metersphere.api.mapper;
import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.importdto.ApiDefinitionImportDTO;
import io.metersphere.api.dto.definition.importdto.ApiDefinitionImportDTO;
import io.metersphere.system.dto.table.TableBatchProcessDTO;
import org.apache.ibatis.annotations.Param;

View File

@ -129,14 +129,14 @@
</foreach>
and deleted = #{deleted}
</select>
<select id="importList" resultType="io.metersphere.api.dto.importdto.ApiDefinitionImportDTO">
<select id="importList" resultType="io.metersphere.api.dto.definition.importdto.ApiDefinitionImportDTO">
select
api_definition.id, api_definition.`name`, api_definition.protocol, api_definition.`method`,
api_definition.`path`, api_definition.version_id,
api_definition.ref_id
api_definition.ref_id, api_definition.module_id
from api_definition
LEFT JOIN project_version ON project_version.id = api_definition.version_id
where api_definition.deleted = true
where api_definition.deleted = false
<include refid="queryWhereCondition"/>
</select>
<select id="selectIdsByIdsAndDeleted" resultType="java.lang.String">

View File

@ -1,23 +1,686 @@
package io.metersphere.api.parser.api;
import io.metersphere.api.dto.definition.HttpResponse;
import io.metersphere.api.dto.definition.importdto.ApiDefinitionImport;
import io.metersphere.api.dto.definition.importdto.ApiDefinitionImportDTO;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.dto.request.http.*;
import io.metersphere.api.dto.request.http.auth.NoAuth;
import io.metersphere.api.dto.request.http.body.*;
import io.metersphere.api.dto.schema.JsonSchemaItem;
import io.metersphere.api.enums.PropertyConstant;
import io.metersphere.api.parser.ImportParser;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.utils.SessionUtils;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.parameters.*;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.parser.core.models.AuthorizationValue;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
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 org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class Swagger3Parser<T> implements ImportParser<T> {
public class Swagger3Parser<T> implements ImportParser<ApiDefinitionImport> {
protected String projectId;
private Components components;
public static final String PATH = "path";
public static final String FORM_DATA = "formData";
public static final String FILE = "file";
public static final String HEADER = "header";
public static final String BODY = "body";
public static final String COOKIE = "cookie";
public static final String QUERY = "query";
@Override
public T parse(InputStream source, ImportRequest request) throws Exception {
public ApiDefinitionImport parse(InputStream source, ImportRequest request) throws Exception {
LogUtils.info("Swagger3Parser parse");
String apiTestStr = getApiTestStr(source);
List<AuthorizationValue> auths = setAuths(request);
SwaggerParseResult result = null;
if (StringUtils.isNotBlank(request.getSwaggerUrl())) {
result = new OpenAPIParser().readLocation(request.getSwaggerUrl(), auths, null);
if (result == null || result.getOpenAPI() == null || !result.getOpenAPI().getOpenapi().startsWith("3.0") || result.isOpenapi31()) {
throw new MSException(Translator.get("swagger_parse_error_with_auth"));
}
} else {
result = new OpenAPIParser().readContents(apiTestStr, null, null);
if (result == null || result.getOpenAPI() == null || !result.getOpenAPI().getOpenapi().startsWith("3.0") || result.isOpenapi31()) {
throw new MSException(Translator.get("swagger_parse_error"));
}
}
ApiDefinitionImport apiDefinitionImport = new ApiDefinitionImport();
OpenAPI openAPI = result.getOpenAPI();
apiDefinitionImport.setData(parseRequests(openAPI, request));
return apiDefinitionImport;
}
// todo: 检查swagger文件版本
private List<AuthorizationValue> setAuths(ImportRequest request) {
List<AuthorizationValue> auths = new ArrayList<>();
// TODO 如果有 BaseAuth 参数base64 编码后转换成 headers
// todo检查文件的合规性
// 设置 headers
if (!CollectionUtils.isEmpty(request.getHeaders())) {
for (Header keyValue : request.getHeaders()) {
// 当有 key 时才进行设置
if (keyValue.getValue() != null) {
AuthorizationValue authorizationValue = new AuthorizationValue();
authorizationValue.setType("header");
authorizationValue.setKeyName(keyValue.getKey());
authorizationValue.setValue(String.valueOf(keyValue.getValue()));
authorizationValue.setUrlMatcher((url) -> true);
auths.add(authorizationValue);
}
}
}
// 设置 query 参数
if (!CollectionUtils.isEmpty(request.getArguments())) {
StringBuilder pathBuilder = new StringBuilder();
pathBuilder.append(request.getSwaggerUrl());
if (StringUtils.isNotBlank(request.getSwaggerUrl()) && !request.getSwaggerUrl().contains("?")) {
pathBuilder.append("?");
}
for (QueryParam keyValue : request.getArguments()) {
if (StringUtils.isNotBlank(keyValue.getKey())) {
AuthorizationValue authorizationValue = new AuthorizationValue();
authorizationValue.setType("query");
authorizationValue.setKeyName(keyValue.getKey());
try {
authorizationValue.setValue(URLEncoder.encode(String.valueOf(keyValue.getValue()), StandardCharsets.UTF_8));
} catch (Exception e) {
LogUtils.info("swagger3 url encode error: " + e);
}
pathBuilder.append(keyValue.getKey()).append("=").append(authorizationValue.getValue()).append("&");
}
}
request.setSwaggerUrl(pathBuilder.substring(0, pathBuilder.length() - 1));
}
return CollectionUtils.size(auths) == 0 ? null : auths;
}
// todo解析文件pojo
protected String getApiTestStr(InputStream source) {
StringBuilder testStr = null;
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(source, StandardCharsets.UTF_8))) {
testStr = new StringBuilder();
String inputStr;
while ((inputStr = bufferedReader.readLine()) != null) {
testStr.append(inputStr);
}
source.close();
} catch (Exception e) {
LogUtils.error(e.getMessage(), e);
throw new MSException(e.getMessage());
}
return StringUtils.isNotBlank(testStr) ? testStr.toString() : StringUtils.EMPTY;
}
private List<ApiDefinitionImportDTO> parseRequests(OpenAPI openAPI, ImportRequest importRequest) {
Paths paths = openAPI.getPaths();
Set<String> pathNames = paths.keySet();
this.components = openAPI.getComponents();
List<ApiDefinitionImportDTO> results = new ArrayList<>();
for (String pathName : pathNames) {
PathItem pathItem = paths.get(pathName);
Map<String, Operation> operationsMap = new HashMap<>();
operationsMap.put(HttpMethod.GET.name(), pathItem.getGet());
operationsMap.put(HttpMethod.POST.name(), pathItem.getPost());
operationsMap.put(HttpMethod.DELETE.name(), pathItem.getDelete());
operationsMap.put(HttpMethod.PUT.name(), pathItem.getPut());
operationsMap.put(HttpMethod.PATCH.name(), pathItem.getPatch());
operationsMap.put(HttpMethod.HEAD.name(), pathItem.getHead());
operationsMap.put(HttpMethod.OPTIONS.name(), pathItem.getOptions());
operationsMap.put(HttpMethod.TRACE.name(), pathItem.getTrace());
for (String method : operationsMap.keySet()) {
Operation operation = operationsMap.get(method);
if (operation != null) {
//构建基本请求
ApiDefinitionImportDTO apiDefinitionDTO = buildApiDefinition(operation, pathName, method, importRequest);
//构建请求参数
MsHTTPElement request = buildRequest(apiDefinitionDTO.getName(), pathName, method);
parseParameters(operation, request);
parseParameters(pathItem, request);
//构建请求体
parseRequestBody(operation.getRequestBody(), request.getBody());
apiDefinitionDTO.setRequest(request);
//解析请求内容
parseResponse(operation.getResponses(), apiDefinitionDTO.getResponse());
results.add(apiDefinitionDTO);
}
}
}
return results;
}
private void parseRequestBody(RequestBody requestBody, Body body) {
if (requestBody != null) {
Content content = requestBody.getContent();
if (content != null) {
content.forEach((key, value) -> {
setBodyData(key, value, body);
});
}
} else {
body.setBodyType(Body.BodyType.NONE.name());
body.setNoneBody(new NoneBody());
}
}
private void parseWWWFormBody(JsonSchemaItem item, Body body) {
WWWFormBody wwwFormBody = new WWWFormBody();
List<String> required = item.getRequired();
List<FormDataKV> formDataKVS = new ArrayList<>();
item.getProperties().forEach((key, value) -> {
if (value != null && !StringUtils.equals(PropertyConstant.OBJECT, value.getType())) {
FormDataKV formDataKV = new FormDataKV();
formDataKV.setKey(key);
formDataKV.setValue(String.valueOf(value.getExample()));
formDataKV.setRequired(CollectionUtils.isNotEmpty(required) && required.contains(key));
formDataKV.setDescription(value.getDescription());
formDataKV.setParamType(value.getType());
formDataKV.setMinLength(value.getMinLength());
formDataKV.setMaxLength(value.getMaxLength());
formDataKVS.add(formDataKV);
}
});
wwwFormBody.setFromValues(formDataKVS);
body.setWwwFormBody(wwwFormBody);
}
private void parseResponse(ApiResponses responseBody, List<HttpResponse> response) {
if (responseBody != null) {
responseBody.forEach((key, value) -> {
HttpResponse httpResponse = new HttpResponse();
//TODO headers
httpResponse.setStatusCode(key);
Body body = new Body();
Map<String, io.swagger.v3.oas.models.headers.Header> headers = value.getHeaders();
if (MapUtils.isNotEmpty(headers)) {
List<Header> headerList = new ArrayList<>();
headers.forEach((k, v) -> {
Header header = new Header();
header.setKey(k);
header.setValue(String.valueOf(v.getExample()));
header.setDescription(getDefaultStringValue(v.getDescription()));
headerList.add(header);
});
httpResponse.setHeaders(headerList);
}
if (value.getContent() != null) {
value.getContent().forEach((k, v) -> {
setBodyData(k, v, body);
});
} else {
body.setBodyType(Body.BodyType.NONE.name());
}
httpResponse.setBody(body);
response.add(httpResponse);
});
}
}
private void setBodyData(String k, io.swagger.v3.oas.models.media.MediaType value, Body body) {
//TODO body 默认如果json格式
JsonSchemaItem jsonSchemaItem = parseSchema(value.getSchema());
switch (k) {
case MediaType.APPLICATION_JSON_VALUE, MediaType.ALL_VALUE -> {
body.setBodyType(Body.BodyType.JSON.name());
JsonBody jsonBody = new JsonBody();
jsonBody.setJsonSchema(jsonSchemaItem);
jsonBody.setEnableJsonSchema(true);
if (ObjectUtils.isNotEmpty(value.getExample())) {
jsonBody.setJsonValue(ApiDataUtils.toJSONString(value.getExample()));
}
body.setJsonBody(jsonBody);
}
case MediaType.APPLICATION_XML_VALUE -> {
if (StringUtils.isBlank(body.getBodyType())) {
body.setBodyType(Body.BodyType.XML.name());
}
XmlBody xml = new XmlBody();
//xml.setValue(XMLUtils.jsonToXmlStr(jsonValue));
body.setXmlBody(xml);
}
case MediaType.APPLICATION_FORM_URLENCODED_VALUE -> {
if (StringUtils.isBlank(body.getBodyType())) {
body.setBodyType(Body.BodyType.WWW_FORM.name());
}
parseWWWFormBody(jsonSchemaItem, body);
}
case MediaType.MULTIPART_FORM_DATA_VALUE -> {
if (StringUtils.isBlank(body.getBodyType())) {
body.setBodyType(Body.BodyType.FORM_DATA.name());
}
}
case MediaType.APPLICATION_OCTET_STREAM_VALUE -> {
if (StringUtils.isBlank(body.getBodyType())) {
body.setBodyType(Body.BodyType.BINARY.name());
}
}
case MediaType.TEXT_PLAIN_VALUE -> {
if (StringUtils.isBlank(body.getBodyType())) {
body.setBodyType(Body.BodyType.RAW.name());
}
RawBody rawBody = new RawBody();
body.setRawBody(rawBody);
}
default -> body.setBodyType(Body.BodyType.NONE.name());
}
}
private ApiDefinitionImportDTO buildApiDefinition(Operation operation, String path, String
method, ImportRequest importRequest) {
String name;
if (StringUtils.isNotBlank(operation.getSummary())) {
name = operation.getSummary();
} else if (StringUtils.isNotBlank(operation.getOperationId())) {
name = operation.getOperationId();
} else {
name = path;
}
ApiDefinitionImportDTO apiDefinition = new ApiDefinitionImportDTO();
apiDefinition.setName(name);
apiDefinition.setPath(formatPath(path));
apiDefinition.setProtocol("HTTP");
apiDefinition.setMethod(method);
apiDefinition.setProjectId(this.projectId);
if (StringUtils.equalsIgnoreCase("schedule", importRequest.getType())) {
apiDefinition.setCreateUser(importRequest.getUserId());
} else {
apiDefinition.setCreateUser(SessionUtils.getUserId());
}
apiDefinition.setModulePath(CollectionUtils.isNotEmpty(operation.getTags()) ? StringUtils.join("/", operation.getTags().get(0)) : StringUtils.EMPTY);
apiDefinition.setResponse(new ArrayList<>());
return apiDefinition;
}
protected MsHTTPElement buildRequest(String name, String path, String method) {
MsHTTPElement request = new MsHTTPElement();
request.setName(name);
// 路径去掉域名/IP 地址保留方法名称及参数
request.setPath(formatPath(path));
request.setMethod(method);
request.setHeaders(new ArrayList<>());
request.setQuery(new ArrayList<>());
request.setRest(new ArrayList<>());
request.setBody(new Body());
MsHTTPConfig httpConfig = new MsHTTPConfig();
httpConfig.setConnectTimeout(60000L);
httpConfig.setResponseTimeout(60000L);
request.setOtherConfig(httpConfig);
request.setAuthConfig(new NoAuth());
return request;
}
private void parseParameters(Operation operation, MsHTTPElement request) {
List<Parameter> parameters = operation.getParameters();
if (CollectionUtils.isEmpty(parameters)) {
return;
}
parameters.forEach(parameter -> {
if (parameter instanceof QueryParameter queryParameter) {
parseQueryParameters(queryParameter, request.getQuery());
} else if (parameter instanceof PathParameter pathParameter) {
parsePathParameters(pathParameter, request.getRest());
} else if (parameter instanceof HeaderParameter headerParameter) {
parseHeaderParameters(headerParameter, request.getHeaders());
} else if (parameter instanceof CookieParameter cookieParameter) {
parseCookieParameters(cookieParameter, request.getHeaders());
}
});
}
private void parseParameters(PathItem path, MsHTTPElement request) {
if (path.getParameters() == null) {
return;
}
List<Parameter> parameters = path.getParameters();
// 处理特殊格式 rest参数是和请求平级的情况
for (Parameter parameter : parameters) {
if (StringUtils.isNotBlank(parameter.getIn())) {
switch (parameter.getIn()) {
case PATH -> parsePathParameters((PathParameter) parameter, request.getRest());
case QUERY -> parseQueryParameters((QueryParameter) parameter, request.getQuery());
case HEADER -> parseHeaderParameters((HeaderParameter) parameter, request.getHeaders());
case COOKIE -> parseCookieParameters((CookieParameter) parameter, request.getHeaders());
default -> {
return;
}
}
}
}
}
private void parseQueryParameters(QueryParameter queryParameter, List<QueryParam> arguments) {
QueryParam queryParam = new QueryParam();
queryParam.setKey(getDefaultStringValue(queryParameter.getName()));
queryParam.setRequired(queryParameter.getRequired());
queryParam.setDescription(getDefaultStringValue(queryParameter.getDescription()));
if (queryParameter.getSchema() != null) {
queryParam.setParamType(queryParameter.getSchema().getType());
queryParam.setValue(getDefaultStringValue(String.valueOf(queryParameter.getSchema().getExample())));
queryParam.setMinLength(queryParameter.getSchema().getMinLength());
queryParam.setMaxLength(queryParameter.getSchema().getMaxLength());
}
arguments.add(queryParam);
}
private void parseCookieParameters(CookieParameter cookieParameter, List<Header> headers) {
Header headerParams = new Header();
headerParams.setKey(getDefaultStringValue(cookieParameter.getName()));
headerParams.setDescription(getDefaultStringValue(cookieParameter.getDescription()));
if (cookieParameter.getSchema() != null) {
headerParams.setValue(getDefaultStringValue(String.valueOf(cookieParameter.getSchema().getExample())));
}
headers.add(headerParams);
}
private void parseHeaderParameters(HeaderParameter headerParameter, List<Header> headers) {
Header headerParams = new Header();
headerParams.setKey(getDefaultStringValue(headerParameter.getName()));
headerParams.setDescription(getDefaultStringValue(headerParameter.getDescription()));
if (headerParameter.getSchema() != null) {
headerParams.setValue(getDefaultStringValue(String.valueOf(headerParameter.getSchema().getExample())));
}
headers.add(headerParams);
}
private void parsePathParameters(PathParameter parameter, List<RestParam> rest) {
RestParam restParam = new RestParam();
restParam.setKey(getDefaultStringValue(parameter.getName()));
restParam.setRequired(parameter.getRequired());
restParam.setDescription(getDefaultStringValue(parameter.getDescription()));
if (parameter.getSchema() != null) {
restParam.setParamType(parameter.getSchema().getType());
restParam.setValue(getDefaultStringValue(String.valueOf(parameter.getSchema().getExample())));
restParam.setMinLength(parameter.getSchema().getMinLength());
restParam.setMaxLength(parameter.getSchema().getMaxLength());
}
rest.add(restParam);
}
private String getDefaultStringValue(String val) {
return StringUtils.isBlank(val) ? StringUtils.EMPTY : val;
}
private Schema getModelByRef(String ref) {
if (StringUtils.isBlank(ref)) {
return null;
}
if (ref.split("/").length > 3) {
ref = ref.replace("#/components/schemas/", StringUtils.EMPTY);
}
if (this.components.getSchemas() != null) return this.components.getSchemas().get(ref);
return null;
}
private JsonSchemaItem parseSchema(Schema schema) {
if (schema != null) {
String refName = schema.get$ref();
Schema modelByRef = null;
if (StringUtils.isNotBlank(refName)) {
modelByRef = getModelByRef(refName);
} else {
modelByRef = schema;
}
if (modelByRef != null) {
if (modelByRef instanceof ArraySchema arraySchema) {
return parseArraySchema(arraySchema.getItems());
} else if (modelByRef instanceof ObjectSchema objectSchema) {
return parseObject(objectSchema);
}
}
}
return null;
}
private JsonSchemaItem parseMapObject(MapSchema mapSchema) {
JsonSchemaItem jsonSchemaItem = new JsonSchemaItem();
jsonSchemaItem.setType(PropertyConstant.OBJECT);
jsonSchemaItem.setRequired(mapSchema.getRequired());
jsonSchemaItem.setDescription(mapSchema.getDescription());
Object value = mapSchema.getAdditionalProperties();
Map<String, JsonSchemaItem> jsonSchemaProperties = new LinkedHashMap<>();
if (ObjectUtils.isEmpty(value)) {
return jsonSchemaItem;
}
JsonSchemaItem item = new JsonSchemaItem();
if (value instanceof IntegerSchema integerSchema) {
item = parseInteger(integerSchema);
} else if (value instanceof StringSchema stringSchema) {
item = parseString(stringSchema);
} else if (value instanceof NumberSchema numberSchema) {
item = parseNumber(numberSchema);
} else if (value instanceof BooleanSchema booleanSchema) {
item = parseBoolean(booleanSchema);
} else if (value instanceof ArraySchema arraySchema) {
item = parseArraySchema(arraySchema.getItems());
} else if (value instanceof ObjectSchema objectSchemaItem) {
item = parseObject(objectSchemaItem);
}
jsonSchemaProperties.put(StringUtils.EMPTY, item);
jsonSchemaItem.setProperties(jsonSchemaProperties);
return jsonSchemaItem;
}
private JsonSchemaItem parseObject(ObjectSchema objectSchema) {
JsonSchemaItem jsonSchemaItem = new JsonSchemaItem();
jsonSchemaItem.setType(PropertyConstant.OBJECT);
jsonSchemaItem.setRequired(objectSchema.getRequired());
jsonSchemaItem.setDescription(objectSchema.getDescription());
Map<String, Schema> properties = objectSchema.getProperties();
Map<String, JsonSchemaItem> jsonSchemaProperties = new LinkedHashMap<>();
if (MapUtils.isNotEmpty(properties)) {
properties.forEach((key, value) -> {
JsonSchemaItem item = new JsonSchemaItem();
if (value instanceof IntegerSchema integerSchema) {
item = parseInteger(integerSchema);
} else if (value instanceof StringSchema stringSchema) {
item = parseString(stringSchema);
} else if (value instanceof NumberSchema numberSchema) {
item = parseNumber(numberSchema);
} else if (value instanceof BooleanSchema booleanSchema) {
item = parseBoolean(booleanSchema);
} else if (value instanceof ArraySchema arraySchema) {
if (!isRef(arraySchema.getItems(), 0)) {
JsonSchemaItem arrayItem = new JsonSchemaItem();
arrayItem.setType(PropertyConstant.ARRAY);
arrayItem.setItems(new JsonSchemaItem());
item = arrayItem;
jsonSchemaProperties.put(key, item);
return;
}
item = parseArraySchema(arraySchema.getItems());
} else if (value instanceof ObjectSchema objectSchemaItem) {
if (!isRef(objectSchemaItem, 0)) {
JsonSchemaItem objectItem = new JsonSchemaItem();
objectItem.setType(PropertyConstant.OBJECT);
objectItem.setProperties(new LinkedHashMap<>());
item = objectItem;
jsonSchemaProperties.put(key, item);
return;
}
item = parseObject(objectSchemaItem);
} else if (StringUtils.equals(value.getType(), "null")) {
item = parseNull();
} else if (value instanceof MapSchema mapSchema) {
item = parseMapObject(mapSchema);
} else if (value instanceof Schema<?> items) {
/*if (StringUtils.isNotBlank(items.get$ref()) && StringUtils.equals(items.get$ref(), ref)) {
return;
}*/
item = parseSchema(items);
}
jsonSchemaProperties.put(key, item);
});
}
jsonSchemaItem.setProperties(jsonSchemaProperties);
return jsonSchemaItem;
}
//判断对象是否存在一直引用
private boolean isRef(Schema schema, int level) {
if (schema == null) {
return false;
}
if (level > 20) {
return true;
}
if (StringUtils.isNotBlank(schema.get$ref())) {
return false;
}
if (schema instanceof ArraySchema arraySchema) {
return isRef(arraySchema.getItems(), level + 1);
}
if (schema instanceof ObjectSchema objectSchema) {
Map<String, Schema> properties = objectSchema.getProperties();
if (MapUtils.isNotEmpty(properties)) {
int finalLevel = level;
properties.forEach((key, value) -> {
if (value instanceof ArraySchema arraySchemaItem) {
isRef(arraySchemaItem.getItems(), finalLevel + 1);
}
if (value instanceof ObjectSchema objectSchemaItem) {
isRef(objectSchemaItem, finalLevel + 1);
}
});
}
}
return false;
}
private JsonSchemaItem parseString(StringSchema stringSchema) {
JsonSchemaItem jsonSchemaString = new JsonSchemaItem();
jsonSchemaString.setType(PropertyConstant.STRING);
jsonSchemaString.setFormat(StringUtils.isNotBlank(stringSchema.getFormat()) ? stringSchema.getFormat() : StringUtils.EMPTY);
jsonSchemaString.setDescription(getDefaultStringValue(stringSchema.getDescription()));
jsonSchemaString.setExample(stringSchema.getExample());
if (stringSchema.getMaxLength() != null) {
jsonSchemaString.setMaxLength(stringSchema.getMaxLength());
}
if (stringSchema.getMinLength() != null) {
jsonSchemaString.setMinLength(stringSchema.getMinLength());
}
jsonSchemaString.setPattern(stringSchema.getPattern());
jsonSchemaString.setEnumString(stringSchema.getEnum());
if (stringSchema.getExample() == null && CollectionUtils.isNotEmpty(stringSchema.getEnum())) {
jsonSchemaString.setExample(stringSchema.getEnum().get(0));
}
return jsonSchemaString;
}
private JsonSchemaItem parseInteger(IntegerSchema integerSchema) {
JsonSchemaItem jsonSchemaInteger = new JsonSchemaItem();
jsonSchemaInteger.setType(PropertyConstant.INTEGER);
jsonSchemaInteger.setFormat(StringUtils.isNotBlank(integerSchema.getFormat()) ? integerSchema.getFormat() : StringUtils.EMPTY);
jsonSchemaInteger.setDescription(StringUtils.isNotBlank(integerSchema.getDescription()) ? integerSchema.getDescription() : StringUtils.EMPTY);
jsonSchemaInteger.setExample(integerSchema.getExample());
jsonSchemaInteger.setMaximum(integerSchema.getMaximum());
jsonSchemaInteger.setMinimum(integerSchema.getMinimum());
jsonSchemaInteger.setEnumInteger(integerSchema.getEnum());
return jsonSchemaInteger;
}
private JsonSchemaItem parseNumber(NumberSchema numberSchema) {
JsonSchemaItem jsonSchemaNumber = new JsonSchemaItem();
jsonSchemaNumber.setType(PropertyConstant.NUMBER);
jsonSchemaNumber.setDescription(StringUtils.isNotBlank(numberSchema.getDescription()) ? numberSchema.getDescription() : StringUtils.EMPTY);
jsonSchemaNumber.setExample(numberSchema.getExample());
jsonSchemaNumber.setEnumNumber(numberSchema.getEnum());
return jsonSchemaNumber;
}
private JsonSchemaItem parseBoolean(BooleanSchema booleanSchema) {
JsonSchemaItem jsonSchemaBoolean = new JsonSchemaItem();
jsonSchemaBoolean.setType(PropertyConstant.BOOLEAN);
jsonSchemaBoolean.setDescription(getDefaultStringValue(booleanSchema.getDescription()));
jsonSchemaBoolean.setExample(booleanSchema.getExample());
return jsonSchemaBoolean;
}
private JsonSchemaItem parseNull() {
JsonSchemaItem jsonSchemaNull = new JsonSchemaItem();
jsonSchemaNull.setType(PropertyConstant.NULL);
return jsonSchemaNull;
}
private JsonSchemaItem parseArraySchema(Schema<?> items) {
JsonSchemaItem jsonSchemaArray = new JsonSchemaItem();
jsonSchemaArray.setType(PropertyConstant.ARRAY);
Schema itemsSchema = null;
if (StringUtils.isNotBlank(items.get$ref())) {
itemsSchema = getModelByRef(items.get$ref());
} else {
itemsSchema = items;
}
if (itemsSchema instanceof IntegerSchema integerSchema) {
jsonSchemaArray.setItems(parseInteger(integerSchema));
} else if (itemsSchema instanceof StringSchema stringSchema) {
jsonSchemaArray.setItems(parseString(stringSchema));
} else if (itemsSchema instanceof NumberSchema numberSchema) {
jsonSchemaArray.setItems(parseNumber(numberSchema));
} else if (itemsSchema instanceof BooleanSchema booleanSchema) {
jsonSchemaArray.setItems(parseBoolean(booleanSchema));
} else if (itemsSchema instanceof ArraySchema arraySchema) {
jsonSchemaArray.setItems(parseArraySchema(arraySchema.getItems()));
} else if (itemsSchema instanceof ObjectSchema objectSchema) {
jsonSchemaArray.setItems(parseObject(objectSchema));
} else if (ObjectUtils.isNotEmpty(itemsSchema) && StringUtils.equals("null", itemsSchema.getType())) {
jsonSchemaArray.setItems(parseNull());
}
return jsonSchemaArray;
}
private String formatPath(String url) {
try {
URI urlObject = new URI(url);
String path = StringUtils.isBlank(urlObject.getPath()) ? "/" : urlObject.getPath();
StringBuilder pathBuffer = new StringBuilder(path);
if (StringUtils.isNotEmpty(urlObject.getQuery())) {
pathBuffer.append("?").append(urlObject.getQuery());
}
return pathBuffer.toString();
} catch (Exception ex) {
return url;
}
}
}

View File

@ -38,6 +38,7 @@ public class MsJsonBodyConverter extends MsBodyConverter<JsonBody> {
/**
* 将json中的 @xxx 转换成 ${__Mock(@xxx)}
*
* @param jsonStr
* @return
*/

View File

@ -0,0 +1,647 @@
package io.metersphere.api.service.definition;
import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.domain.ApiDefinitionBlob;
import io.metersphere.api.domain.ApiDefinitionBlobExample;
import io.metersphere.api.domain.ApiDefinitionModule;
import io.metersphere.api.dto.definition.ApiDefinitionDTO;
import io.metersphere.api.dto.definition.ApiDefinitionPageRequest;
import io.metersphere.api.dto.definition.ApiModuleRequest;
import io.metersphere.api.dto.definition.importdto.ApiDeatlWithData;
import io.metersphere.api.dto.definition.importdto.ApiDeatlWithDataUpdate;
import io.metersphere.api.dto.definition.importdto.ApiDefinitionImport;
import io.metersphere.api.dto.definition.importdto.ApiDefinitionImportDTO;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.dto.request.http.Header;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.request.http.QueryParam;
import io.metersphere.api.dto.request.http.RestParam;
import io.metersphere.api.dto.request.http.body.*;
import io.metersphere.api.dto.schema.JsonSchemaItem;
import io.metersphere.api.enums.PropertyConstant;
import io.metersphere.api.mapper.*;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.project.domain.Project;
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.project.mapper.ProjectApplicationMapper;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.BaseTreeNode;
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 io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
import jakarta.annotation.Resource;
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 org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class ApiDefinitionImportUtilService {
private static final String UNPLANNED_API = "api_unplanned_request";
private static final String MODULE = "modulePath";
public static final Long ORDER_STEP = 5000L;
private final ThreadLocal<Long> currentApiOrder = new ThreadLocal<>();
private final ThreadLocal<Long> currentApiCaseOrder = new ThreadLocal<>();
private final ThreadLocal<Long> currentModuleOrder = new ThreadLocal<>();
@Resource
private SqlSessionFactory sqlSessionFactory;
@Resource
private ExtBaseProjectVersionMapper extBaseProjectVersionMapper;
@Resource
private ProjectApplicationMapper projectApplicationMapper;
@Resource
private ExtApiDefinitionModuleMapper extApiDefinitionModuleMapper;
@Resource
private ExtApiDefinitionMapper extApiDefinitionMapper;
@Resource
private ApiDefinitionBlobMapper apiDefinitionBlobMapper;
@Resource
private ApiTestCaseService apiTestCaseService;
@Resource
private ApiDefinitionModuleService apiDefinitionModuleService;
@Resource
private ProjectMapper projectMapper;
@Resource
private OperationLogService operationLogService;
public void checkFileSuffixName(ImportRequest request, String suffixName) {
if ("jmx".equalsIgnoreCase(suffixName)) {
if (!"JMeter".equalsIgnoreCase(request.getPlatform())) {
throw new MSException(Translator.get("file_format_does_not_meet_requirements"));
}
}
if ("har".equalsIgnoreCase(suffixName)) {
if (!"Har".equalsIgnoreCase(request.getPlatform())) {
throw new MSException(Translator.get("file_format_does_not_meet_requirements"));
}
}
if ("json".equalsIgnoreCase(suffixName)) {
if ("Har".equalsIgnoreCase(request.getPlatform()) || "Jmeter".equalsIgnoreCase(request.getPlatform())) {
throw new MSException(Translator.get("file_format_does_not_meet_requirements"));
}
}
}
public void importApi(ImportRequest request, ApiDefinitionImport apiImport) {
String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(request.getProjectId());
request.setDefaultVersion(defaultVersion);
if (request.getVersionId() == null) {
request.setVersionId(defaultVersion);
}
List<ApiDefinitionImportDTO> initData = apiImport.getData();
//TODO 查询项目菜单参数
/*ProjectApplicationExample applicationExample = new ProjectApplicationExample();
applicationExample.createCriteria().andProjectIdEqualTo(request.getProjectId()).andTypeEqualTo("API_URL_REPEATABLE");
List<ProjectApplication> projectApplications = projectApplicationMapper.selectByExample(applicationExample);
if (CollectionUtils.isNotEmpty(projectApplications)) {
String typeValue = projectApplications.get(0).getTypeValue();
}*/
//过滤(一次只导入一个协议)
List<ApiDefinitionImportDTO> filterData = initData.stream().filter(t -> t.getProtocol().equals(request.getProtocol())).collect(Collectors.toList());
if (filterData.isEmpty()) {
return;
}
//处理数据判断数据是否重复
dealWithData(request, filterData);
}
private void dealWithData(ImportRequest request, List<ApiDefinitionImportDTO> importData) {
//查询数据库中所有的数据 用于判断是否重复
ApiDefinitionPageRequest pageRequest = new ApiDefinitionPageRequest();
pageRequest.setProjectId(request.getProjectId());
pageRequest.setProtocol(request.getProtocol());
//TODO 如果是有版本的话 需要加上版本的判断
List<ApiDefinitionImportDTO> apiLists = extApiDefinitionMapper.importList(pageRequest);
List<BaseTreeNode> apiModules = this.buildTreeData(request.getProjectId(), request.getProtocol());
//将apiModules转换成新的map 要求key是attachInfo中的modulePath 使用stream实现
Map<String, BaseTreeNode> modulePathMap = apiModules.stream().collect(Collectors.toMap(t -> t.getAttachInfo().get(MODULE), t -> t));
Map<String, BaseTreeNode> idModuleMap = apiModules.stream().collect(Collectors.toMap(BaseTreeNode::getId, apiModuleDTO -> apiModuleDTO));
//如果导入的时候选择了模块需要把所有的导入数据的模块路径前面拼接上选择的模块路径
if (StringUtils.isNotBlank(request.getModuleId())) {
BaseTreeNode baseTreeNode = idModuleMap.get(request.getModuleId());
String modulePath = baseTreeNode.getAttachInfo().get(MODULE);
importData.forEach(t -> {
t.setModulePath(modulePath + t.getModulePath());
});
}
//去掉apiLists中不存在的模块数据
apiLists = apiLists.stream().filter(t -> modulePathMap.containsKey(t.getModulePath())).toList();
apiLists.forEach(t -> {
t.setModulePath(idModuleMap.get(t.getModuleId()) != null ? idModuleMap.get(t.getModuleId()).getAttachInfo().get(MODULE) : StringUtils.EMPTY);
});
ApiDeatlWithData apiDeatlWithData = new ApiDeatlWithData();
//判断数据是否是唯一的
checkApiDataOnly(request, importData, apiLists, apiDeatlWithData);
ApiDeatlWithDataUpdate apiDeatlWithDataUpdate = new ApiDeatlWithDataUpdate();
getNeedUpdateData(request, apiDeatlWithData, apiDeatlWithDataUpdate);
//数据入库
insertData(modulePathMap, idModuleMap, apiDeatlWithDataUpdate, request);
}
public Long getNextOrder(String projectId) {
Long pos = extApiDefinitionMapper.getPos(projectId);
return (pos == null ? 0 : pos) + ORDER_STEP;
}
public Long getImportNextOrder(String projectId) {
Long order = currentApiOrder.get();
if (order == null) {
order = getNextOrder(projectId);
}
order = order + ORDER_STEP;
currentApiOrder.set(order);
return order;
}
public Long getImportNextCaseOrder(String projectId) {
Long order = currentApiCaseOrder.get();
if (order == null) {
order = apiTestCaseService.getNextOrder(projectId);
}
order = order + ORDER_STEP;
currentApiCaseOrder.set(order);
return order;
}
public Long getImportNextModuleOrder(String projectId) {
Long order = currentModuleOrder.get();
if (order == null) {
order = apiDefinitionModuleService.getNextOrder(projectId);
}
order = order + ORDER_STEP;
currentModuleOrder.set(order);
return order;
}
@Transactional(rollbackFor = Exception.class)
public void insertData(Map<String, BaseTreeNode> modulePathMap,
Map<String, BaseTreeNode> idModuleMap,
ApiDeatlWithDataUpdate apiDeatlWithDataUpdate,
ImportRequest request) {
//先判断是否需要新增模块
List<ApiDefinitionImportDTO> addModuleData = apiDeatlWithDataUpdate.getAddModuleData();
List<ApiDefinitionImportDTO> updateModuleData = apiDeatlWithDataUpdate.getUpdateModuleData();
//取addModuleData的模块放到set中 生成一个新的set
Set<String> moduleSet = addModuleData.stream().map(ApiDefinitionImportDTO::getModulePath).collect(Collectors.toSet());
//取updateModuleData的模块放到set中 生成一个新的set
Set<String> updateModuleSet = updateModuleData.stream().map(ApiDefinitionImportDTO::getModulePath).collect(Collectors.toSet());
moduleSet.addAll(updateModuleSet);
//将modulePathMap的key转成set
Set<String> modulePathSet = modulePathMap.keySet();
//取modulePathSet中不存在的
Set<String> differenceSet = moduleSet.stream().filter(t -> !modulePathSet.contains(t)).collect(Collectors.toSet());
//不存在的需要新增
List<BaseTreeNode> addModuleList = new ArrayList<>();
currentApiCaseOrder.remove();
currentApiOrder.remove();
currentModuleOrder.remove();
differenceSet.forEach(item -> {
//解析modulePath 格式为/a/b/c
String[] split = item.split("/");
//一层一层的创建
for (int i = 0; i < split.length; i++) {
String path = StringUtils.join(split, "/", 0, i + 1);
BaseTreeNode baseTreeNode = modulePathMap.get(path);
if (baseTreeNode == null) {
//创建模块
BaseTreeNode module = new BaseTreeNode();
module.setId(IDGenerator.nextStr());
module.setName(split[i]);
module.setParentId(i == 0 ? ModuleConstants.ROOT_NODE_PARENT_ID : modulePathMap.get(StringUtils.join(split, "/", 0, i)).getId());
addModuleList.add(module);
modulePathMap.put(path, module);
idModuleMap.put(module.getId(), module);
}
}
});
//创建模块
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ApiDefinitionModuleMapper moduleMapper = sqlSession.getMapper(ApiDefinitionModuleMapper.class);
ApiDefinitionMapper apiMapper = sqlSession.getMapper(ApiDefinitionMapper.class);
ApiDefinitionBlobMapper apiBlobMapper = sqlSession.getMapper(ApiDefinitionBlobMapper.class);
addModuleList.forEach(t -> {
ApiDefinitionModule module = new ApiDefinitionModule();
module.setId(t.getId());
module.setName(t.getName());
module.setParentId(t.getParentId());
module.setProjectId(request.getProjectId());
module.setCreateUser(request.getUserId());
module.setPos(getImportNextModuleOrder(request.getProjectId()));
module.setCreateTime(System.currentTimeMillis());
module.setUpdateUser(request.getUserId());
module.setUpdateTime(System.currentTimeMillis());
moduleMapper.insertSelective(module);
});
//取出需要更新的数据的id
List<String> updateModuleLists = updateModuleData.stream().map(ApiDefinitionImportDTO::getId).toList();
//更新模块数据
updateModuleData.forEach(t -> {
ApiDefinition apiDefinition = new ApiDefinition();
apiDefinition.setId(t.getId());
apiDefinition.setModuleId(modulePathMap.get(t.getModulePath()).getId());
apiDefinition.setUpdateUser(request.getUserId());
apiDefinition.setUpdateTime(System.currentTimeMillis());
apiMapper.updateByPrimaryKeySelective(apiDefinition);
});
List<LogDTO> operationLogs = new ArrayList<>();
List<ApiDefinitionImportDTO> updateRequestData = apiDeatlWithDataUpdate.getUpdateRequestData();
updateRequestData.forEach(t -> {
if (CollectionUtils.isNotEmpty(updateModuleLists) && updateModuleLists.contains(t.getId())) {
ApiDefinition apiDefinition = new ApiDefinition();
apiDefinition.setId(t.getId());
apiDefinition.setUpdateUser(request.getUserId());
apiDefinition.setUpdateTime(System.currentTimeMillis());
apiMapper.updateByPrimaryKeySelective(apiDefinition);
}
//更新blob数据
ApiDefinitionBlob apiDefinitionBlob = new ApiDefinitionBlob();
apiDefinitionBlob.setId(t.getId());
apiDefinitionBlob.setRequest(JSON.toJSONBytes(t.getRequest()));
apiDefinitionBlob.setResponse(JSON.toJSONBytes(t.getResponse()));
apiBlobMapper.updateByPrimaryKeySelective(apiDefinitionBlob);
});
Map<String, ApiDefinitionImportDTO> logData = apiDeatlWithDataUpdate.getLogData();
Project project = projectMapper.selectByPrimaryKey(request.getProjectId());
if (MapUtils.isNotEmpty(logData)) {
logData.forEach((k, v) -> {
ApiDefinitionDTO apiDefinitionDTO = new ApiDefinitionDTO();
BeanUtils.copyBean(apiDefinitionDTO, v);
LogDTO dto = new LogDTO(
project.getId(),
project.getOrganizationId(),
v.getId(),
request.getUserId(),
OperationLogType.IMPORT.name(),
OperationLogModule.API_DEFINITION,
v.getName());
dto.setHistory(true);
dto.setPath("/api/definition/import");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(apiDefinitionDTO));
operationLogs.add(dto);
});
}
addModuleData.forEach(t -> {
ApiDefinition apiDefinition = new ApiDefinition();
BeanUtils.copyBean(apiDefinition, t);
apiDefinition.setId(IDGenerator.nextStr());
apiDefinition.setModuleId(modulePathMap.get(t.getModulePath()).getId());
apiDefinition.setProjectId(request.getProjectId());
apiDefinition.setProtocol(request.getProtocol());
apiDefinition.setCreateUser(request.getUserId());
apiDefinition.setPos(getImportNextOrder(request.getProjectId()));
apiDefinition.setCreateTime(System.currentTimeMillis());
apiDefinition.setUpdateUser(request.getUserId());
apiDefinition.setUpdateTime(System.currentTimeMillis());
apiDefinition.setNum(NumGenerator.nextNum(request.getProjectId(), ApplicationNumScope.API_DEFINITION));
apiDefinition.setLatest(true);
apiDefinition.setStatus("Underway");
apiDefinition.setRefId(apiDefinition.getId());
apiDefinition.setVersionId(request.getVersionId());
apiMapper.insertSelective(apiDefinition);
//插入blob数据
ApiDefinitionBlob apiDefinitionBlob = new ApiDefinitionBlob();
apiDefinitionBlob.setId(apiDefinition.getId());
apiDefinitionBlob.setRequest(JSON.toJSONBytes(t.getRequest()));
apiDefinitionBlob.setResponse(JSON.toJSONBytes(t.getResponse()));
apiBlobMapper.insertSelective(apiDefinitionBlob);
ApiDefinitionDTO apiDefinitionDTO = new ApiDefinitionDTO();
BeanUtils.copyBean(apiDefinitionDTO, t);
LogDTO dto = new LogDTO(
project.getId(),
project.getOrganizationId(),
t.getId(),
request.getUserId(),
OperationLogType.IMPORT.name(),
OperationLogModule.API_DEFINITION,
t.getName());
dto.setHistory(true);
dto.setPath("/api/definition/import");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(apiDefinitionDTO));
});
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
operationLogService.batchAdd(operationLogs);
}
private void getNeedUpdateData(ImportRequest request, ApiDeatlWithData apiDeatlWithData, ApiDeatlWithDataUpdate apiDeatlWithDataUpdate) {
List<String> sameList = apiDeatlWithData.getSameList();
List<String> differenceList = apiDeatlWithData.getDifferenceList();
Map<String, ApiDefinitionImportDTO> apiDateMap = apiDeatlWithData.getApiDateMap();
Map<String, ApiDefinitionImportDTO> importDataMap = apiDeatlWithData.getImportDataMap();
List<ApiDefinitionImportDTO> updateModuleData = new ArrayList<>();
List<ApiDefinitionImportDTO> updateRequestData = new ArrayList<>();
List<ApiDefinitionImportDTO> addData = new ArrayList<>();
//判断参数是否一样 一样的参数需要判断是否需要覆盖模块 如果需要就要update数据 如果不需要 就直接跳过
if (CollectionUtils.isNotEmpty(sameList) && getFullCoverage(request.getCoverData())) {
//需要覆盖数据的 会判断是否需要覆盖模块
List<ApiDefinitionImportDTO> sameData = sameList.stream().map(apiDateMap::get).toList();
//取所有id为新的list 需要取查询blob的数据
List<String> sameIds = sameData.stream().map(ApiDefinitionImportDTO::getId).toList();
ApiDefinitionBlobExample blobExample = new ApiDefinitionBlobExample();
blobExample.createCriteria().andIdIn(sameIds);
List<ApiDefinitionBlob> apiDefinitionBlobs = apiDefinitionBlobMapper.selectByExampleWithBLOBs(blobExample);
Map<String, ApiDefinitionImportDTO> logMap = new HashMap<>();
Map<String, ApiDefinitionBlob> blobMap = apiDefinitionBlobs.stream().collect(Collectors.toMap(ApiDefinitionBlob::getId, t -> t));
//判断参数是否一样
for (ApiDefinitionImportDTO apiDefinitionDTO : sameData) {
ApiDefinitionImportDTO importDTO = importDataMap.get(apiDefinitionDTO.getMethod() + apiDefinitionDTO.getPath());
ApiDefinitionBlob apiDefinitionBlob = blobMap.get(apiDefinitionDTO.getId());
if (apiDefinitionBlob != null) {
MsHTTPElement dbRequest = ApiDataUtils.parseObject(new String(apiDefinitionBlob.getRequest()), MsHTTPElement.class);
//判断参数是否一样 参数类型有 请求头 请求参数 请求体
boolean isSame = dataIsSame(dbRequest, (MsHTTPElement) importDTO.getRequest());
//判断是否开启模块覆盖
if (getFullCoverage(request.getCoverModule())) {
//判断模块是否一样
if (!StringUtils.equals(apiDefinitionDTO.getModulePath(), importDTO.getModulePath())) {
//不一样的模块需要更
apiDefinitionDTO.setModulePath(apiDefinitionDTO.getModulePath());
updateModuleData.add(apiDefinitionDTO);
logMap.put(apiDefinitionDTO.getId(), importDTO);
}
}
//不相同的数据需要覆盖 所以这里记录id就可以
if (!isSame) {
importDTO.setId(apiDefinitionDTO.getId());
updateRequestData.add(importDTO);
logMap.put(apiDefinitionDTO.getId(), importDTO);
}
}
}
}
//不存在的数据是肯定要插入的 TODO 这里需要判断是否需要创建模块
if (CollectionUtils.isNotEmpty(differenceList)) {
addData = differenceList.stream().map(importDataMap::get).toList();
}
apiDeatlWithDataUpdate.setUpdateModuleData(updateModuleData);
apiDeatlWithDataUpdate.setUpdateRequestData(updateRequestData);
apiDeatlWithDataUpdate.setAddModuleData(addData);
}
private void checkApiDataOnly(ImportRequest request,
List<ApiDefinitionImportDTO> importData,
List<ApiDefinitionImportDTO> apiLists,
ApiDeatlWithData apiDeatlWithData) {
//判断是否是同一接口 需要返回的数据 需要insert的 update的
switch (request.getUniquelyIdentifies()) {
case "Method & Path" -> methodAndPath(importData, apiLists, apiDeatlWithData);
default -> {
}
}
}
public void methodAndPath(List<ApiDefinitionImportDTO> importData,
List<ApiDefinitionImportDTO> lists,
ApiDeatlWithData apiDeatlWithData) {
Map<String, ApiDefinitionImportDTO> apiDateMap = lists.stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t));
Map<String, ApiDefinitionImportDTO> importDataMap = importData.stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t));
//判断是否重复
List<String> orgList = apiDateMap.keySet().stream().toList();
List<String> importList = importDataMap.keySet().stream().toList();
//取交集数据
List<String> sameList = importList.stream().filter(orgList::contains).toList();
// 不同接口的数据
List<String> differenceList = importList.stream().filter(t -> !orgList.contains(t)).toList();
apiDeatlWithData.setSameList(sameList);
apiDeatlWithData.setDifferenceList(differenceList);
apiDeatlWithData.setApiDateMap(apiDateMap);
apiDeatlWithData.setImportDataMap(importDataMap);
}
public boolean dataIsSame(MsHTTPElement dbRequest, MsHTTPElement importRequest) {
boolean same = true;
//判断请求头是否一样
List<Header> dbHeaders = dbRequest.getHeaders();
List<Header> importHeaders = importRequest.getHeaders();
if (CollectionUtils.isNotEmpty(dbHeaders) || CollectionUtils.isNotEmpty(importHeaders)) {
List<String> dbHeaderKeys = dbHeaders.stream().map(Header::getKey).toList();
List<String> importHeaderKeys = importHeaders.stream().map(Header::getKey).toList();
if (!paramsIsSame(dbHeaderKeys, importHeaderKeys)) {
return false;
}
}
//判断请求参数是否一样 query rest body
List<QueryParam> dbQuery = dbRequest.getQuery();
List<QueryParam> importQuery = importRequest.getQuery();
if (CollectionUtils.isNotEmpty(dbQuery) || CollectionUtils.isNotEmpty(importQuery)) {
List<String> dbQueryKeys = dbQuery.stream().map(QueryParam::getKey).toList();
List<String> importQueryKeys = importQuery.stream().map(QueryParam::getKey).toList();
if (!paramsIsSame(dbQueryKeys, importQueryKeys)) {
return false;
}
}
List<RestParam> dbRest = dbRequest.getRest();
List<RestParam> importRest = importRequest.getRest();
if (CollectionUtils.isNotEmpty(dbRest) || CollectionUtils.isNotEmpty(importRest)) {
List<String> dbRestKeys = dbRest.stream().map(RestParam::getKey).toList();
List<String> importRestKeys = importRest.stream().map(RestParam::getKey).toList();
if (!paramsIsSame(dbRestKeys, importRestKeys)) {
return false;
}
}
//判断请求体是否一样
if (dbRequest.getBody() != null || importRequest.getBody() != null) {
//判断请求体的参数
Body dbbody = dbRequest.getBody();
Body importBody = importRequest.getBody();
if (dbbody != null && importBody != null) {
//判断null类型
StringUtils.equals(String.valueOf(dbbody.getNoneBody()), String.valueOf(importBody.getNoneBody()));
//判断form类型
FormDataBody formDataBody = dbbody.getFormDataBody();
FormDataBody importFormDataBody = importBody.getFormDataBody();
if (ObjectUtils.isNotEmpty(formDataBody) || ObjectUtils.isNotEmpty(importFormDataBody)) {
List<FormDataKV> fromValues = formDataBody.getFromValues();
List<FormDataKV> importFromValues = importFormDataBody.getFromValues();
if (CollectionUtils.isNotEmpty(fromValues) || CollectionUtils.isNotEmpty(importFromValues)) {
List<String> dbFormKeys = fromValues.stream().map(FormDataKV::getKey).toList();
List<String> importFormKeys = importFromValues.stream().map(FormDataKV::getKey).toList();
if (!paramsIsSame(dbFormKeys, importFormKeys)) {
return false;
}
}
}
//判读www类型
WWWFormBody wwwBody = dbbody.getWwwFormBody();
WWWFormBody importWwwBody = importBody.getWwwFormBody();
if (ObjectUtils.isNotEmpty(wwwBody) || ObjectUtils.isNotEmpty(importWwwBody)) {
List<FormDataKV> wwwValues = wwwBody.getFromValues();
List<FormDataKV> importWwwValues = importWwwBody.getFromValues();
if (CollectionUtils.isNotEmpty(wwwValues) || CollectionUtils.isNotEmpty(importWwwValues)) {
List<String> dbWwwKeys = wwwValues.stream().map(FormDataKV::getKey).toList();
List<String> importWwwKeys = importWwwValues.stream().map(FormDataKV::getKey).toList();
if (!paramsIsSame(dbWwwKeys, importWwwKeys)) {
return false;
}
}
}
//TODO 判断binary类型
//判断raw类型
RawBody rawBody = dbbody.getRawBody();
RawBody importRawBody = importBody.getRawBody();
if (ObjectUtils.isNotEmpty(rawBody) || ObjectUtils.isNotEmpty(importRawBody)) {
return false;
}
//判断json类型
JsonBody jsonBody = dbbody.getJsonBody();
JsonBody importJsonBody = importBody.getJsonBody();
if (ObjectUtils.isNotEmpty(jsonBody) || ObjectUtils.isNotEmpty(importJsonBody)) {
if (StringUtils.isNotBlank(jsonBody.getJsonValue()) || StringUtils.isNotBlank(importJsonBody.getJsonValue())) {
return false;
}
//判断jsonschema
JsonSchemaItem jsonSchema = jsonBody.getJsonSchema();
JsonSchemaItem importJsonSchema = importJsonBody.getJsonSchema();
if (jsonSchema != null && importJsonSchema != null) {
return jsonSchemaIsSame(jsonSchema, importJsonSchema);
}
}
}
}
return same;
}
//判断jsonschema的参数是否一样
private static boolean jsonSchemaIsSame(JsonSchemaItem jsonSchema, JsonSchemaItem importJsonSchema) {
boolean same = true;
if (jsonSchema == null && importJsonSchema == null) {
return true;
}
if (!StringUtils.equals(jsonSchema.getType(), importJsonSchema.getType())) {
return false;
}
if (StringUtils.equals(jsonSchema.getType(), PropertyConstant.OBJECT)) {
Map<String, JsonSchemaItem> properties = jsonSchema.getProperties();
Map<String, JsonSchemaItem> importProperties = importJsonSchema.getProperties();
if (MapUtils.isNotEmpty(properties) || MapUtils.isNotEmpty(importProperties)) {
List<String> dbJsonKeys = properties.keySet().stream().toList();
List<String> importJsonKeys = importProperties.keySet().stream().toList();
if (!paramsIsSame(dbJsonKeys, importJsonKeys)) {
return false;
}
//遍历判断每个参数是否一样
for (String key : dbJsonKeys) {
JsonSchemaItem jsonSchemaItem = properties.get(key);
JsonSchemaItem importJsonSchemaItem = importProperties.get(key);
if (!jsonSchemaIsSame(jsonSchemaItem, importJsonSchemaItem)) {
same = false;
break;
}
}
}
}
if (StringUtils.equals(jsonSchema.getType(), PropertyConstant.ARRAY)) {
JsonSchemaItem items = jsonSchema.getItems();
JsonSchemaItem importItems = importJsonSchema.getItems();
if (items != null && importItems != null) {
if (!jsonSchemaIsSame(items, importItems)) {
return false;
}
}
}
return same;
}
private static boolean paramsIsSame(List<String> dbRestKeys, List<String> importRestKeys) {
if (dbRestKeys.size() != importRestKeys.size()) {
return false;
}
//看看是否有差集
List<String> differenceRest = dbRestKeys.stream().filter(t -> !importRestKeys.contains(t)).toList();
return CollectionUtils.isEmpty(differenceRest);
}
private Boolean getFullCoverage(Boolean fullCoverage) {
if (fullCoverage == null) {
fullCoverage = false;
}
return fullCoverage;
}
/**
* 构造树结构 但是这里需要module的path
*
* @param projectId
* @param protocol
* @return
*/
public List<BaseTreeNode> buildTreeData(String projectId, String protocol) {
ApiModuleRequest request = new ApiModuleRequest();
request.setProjectId(projectId);
request.setProtocol(protocol);
List<BaseTreeNode> fileModuleList = extApiDefinitionModuleMapper.selectBaseByRequest(request);
return this.buildTreeAndCountResource(fileModuleList, true, Translator.get(UNPLANNED_API));
}
public List<BaseTreeNode> buildTreeAndCountResource(List<BaseTreeNode> traverseList, boolean haveVirtualRootNode, String virtualRootName) {
List<BaseTreeNode> baseTreeNodeList = new ArrayList<>();
if (haveVirtualRootNode) {
BaseTreeNode defaultNode = new BaseTreeNode(ModuleConstants.DEFAULT_NODE_ID, virtualRootName, ModuleConstants.NODE_TYPE_DEFAULT, ModuleConstants.ROOT_NODE_PARENT_ID);
defaultNode.setAttachInfo(Map.of(MODULE, StringUtils.join("/", defaultNode.getName())));
baseTreeNodeList.add(defaultNode);
}
int lastSize = 0;
Map<String, BaseTreeNode> baseTreeNodeMap = new HashMap<>();
while (CollectionUtils.isNotEmpty(traverseList) && traverseList.size() != lastSize) {
lastSize = traverseList.size();
List<BaseTreeNode> notMatchedList = new ArrayList<>();
for (BaseTreeNode treeNode : traverseList) {
if (StringUtils.equalsIgnoreCase(treeNode.getParentId(), ModuleConstants.ROOT_NODE_PARENT_ID)) {
BaseTreeNode node = new BaseTreeNode(treeNode.getId(), treeNode.getName(), treeNode.getType(), treeNode.getParentId());
node.setAttachInfo(Map.of(MODULE, StringUtils.join("/", node.getName())));
baseTreeNodeList.add(node);
baseTreeNodeMap.put(treeNode.getId(), node);
} else {
if (baseTreeNodeMap.containsKey(treeNode.getParentId())) {
BaseTreeNode node = new BaseTreeNode(treeNode.getId(), treeNode.getName(), treeNode.getType(), treeNode.getParentId());
node.setAttachInfo(Map.of(MODULE, StringUtils.join(baseTreeNodeMap.get(treeNode.getParentId()).getAttachInfo().get(MODULE), "/", node.getName())));
baseTreeNodeList.add(node);
}
}
}
traverseList = notMatchedList;
}
return baseTreeNodeList;
}
}

View File

@ -6,11 +6,17 @@ 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.dto.definition.importdto.ApiDefinitionImport;
import io.metersphere.api.dto.definition.importdto.ApiDefinitionImportDTO;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.mapper.*;
import io.metersphere.api.parser.ImportParser;
import io.metersphere.api.parser.ImportParserFactory;
import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.project.service.ProjectService;
import io.metersphere.sdk.constants.ApiReportStatus;
import io.metersphere.sdk.constants.ApplicationNumScope;
@ -93,6 +99,10 @@ public class ApiDefinitionService {
@Resource
private ApiDefinitionLogService apiDefinitionLogService;
@Resource
private ApiDefinitionImportUtilService apiDefinitionImportUtilService;
@Resource
private ProjectMapper projectMapper;
@Resource
private ApiDefinitionMockService apiDefinitionMockService;
@ -655,6 +665,7 @@ public class ApiDefinitionService {
// 恢复接口到接口列表
handleRecoverApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId(), false);
}
public void handleRecoverApiDefinition(List<String> ids, String userId, String projectId, boolean isBatch) {
if (CollectionUtils.isNotEmpty(ids)) {
SubListUtils.dealForSubList(ids, 2000, subList -> doRecover(subList, userId, projectId, isBatch));
@ -722,6 +733,7 @@ public class ApiDefinitionService {
apiTestCaseService.batchRecover(caseLists, userId, projectId);
}
}
public void trashDel(ApiDefinitionDeleteRequest request, String userId) {
handleTrashDelApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId(), false);
}
@ -780,6 +792,7 @@ public class ApiDefinitionService {
List<String> caseIds = caseLists.stream().map(ApiTestCase::getId).distinct().toList();
// case 批量删除回收站
apiTestCaseService.deleteResourceByIds(caseIds, projectId, userId);
}
// 删除 mock
apiDefinitionMockService.deleteByApiIds(apiIds, userId);
@ -856,4 +869,45 @@ public class ApiDefinitionService {
return apiDefinitionDocDTO;
}
public ApiDefinitionImport apiTestImport(MultipartFile file, ImportRequest request) {
if (file != null) {
String originalFilename = file.getOriginalFilename();
if (StringUtils.isNotBlank(originalFilename)) {
String suffixName = originalFilename.substring(originalFilename.indexOf(".") + 1);
apiDefinitionImportUtilService.checkFileSuffixName(request, suffixName);
}
}
ImportParser<?> runService = ImportParserFactory.getImportParser(request.getPlatform());
ApiDefinitionImport apiImport = null;
if (StringUtils.equals(request.getType(), "SCHEDULE")) {
request.setProtocol("HTTP");
}
try {
apiImport = (ApiDefinitionImport) Objects.requireNonNull(runService).parse(file == null ? null : file.getInputStream(), request);
//TODO 处理mock数据
// 发送通知
} catch (Exception e) {
// TODO 发送通知
LogUtils.error(e.getMessage(), e);
throw new MSException(Translator.get("parse_data_error"));
}
try {
apiDefinitionImportUtilService.importApi(request, apiImport);
apiDefinitionMapper.selectByExample(new ApiDefinitionExample());
if (CollectionUtils.isNotEmpty(apiImport.getData())) {
List<String> names = apiImport.getData().stream().map(ApiDefinitionImportDTO::getName).collect(Collectors.toList());
request.setName(String.join(",", names));
List<String> ids = apiImport.getData().stream().map(ApiDefinitionImportDTO::getId).collect(Collectors.toList());
request.setId(JSON.toJSONString(ids));
}
// 发送通知
//apiDefinitionImportUtilService.sendImportNotice(request, apiImportSendNoticeDTOS, project);
} catch (Exception e) {
//apiDefinitionImportUtilService.sendFailMessage(request, project);
LogUtils.error(e);
throw new MSException(Translator.get("user_import_format_wrong"));
}
return apiImport;
}
}

View File

@ -5,6 +5,7 @@ import io.metersphere.api.constants.ApiDefinitionStatus;
import io.metersphere.api.controller.result.ApiResultCode;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.mapper.*;
import io.metersphere.api.model.CheckLogModel;
@ -18,6 +19,7 @@ import io.metersphere.project.service.FileAssociationService;
import io.metersphere.project.service.FileMetadataService;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRequest;
@ -34,16 +36,29 @@ 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 org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureMockMvc
@ -382,6 +397,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
/**
* 校验上传的文件
*
* @param id
* @param fileIds 全部的文件ID
*/
@ -411,6 +427,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
/**
* 校验上传的文件
*
* @param id
* @param fileIds 全部的文件ID
*/
@ -655,7 +672,8 @@ public class ApiDefinitionControllerTests extends BaseTest {
case "FILTER" -> configureFilterSearch(request);
case "COMBINE" -> configureCombineSearch(request);
case "DELETED" -> configureDeleteSearch(request);
default -> {}
default -> {
}
}
MvcResult mvcResult = this.requestPostWithOkAndReturn(url, request);
@ -1188,5 +1206,123 @@ public class ApiDefinitionControllerTests extends BaseTest {
}
}
@Test
@Order(102)
public void testImport() throws Exception {
LogUtils.info("import api test");
ApiDefinitionModule apiDefinitionModule = new ApiDefinitionModule();
apiDefinitionModule.setId("test-import");
apiDefinitionModule.setName("test-import");
apiDefinitionModule.setProjectId(DEFAULT_PROJECT_ID);
apiDefinitionModule.setCreateUser("admin");
apiDefinitionModule.setUpdateUser("admin");
apiDefinitionModule.setPos(1L);
apiDefinitionModule.setCreateTime(System.currentTimeMillis());
apiDefinitionModule.setUpdateTime(System.currentTimeMillis());
apiDefinitionModuleMapper.insertSelective(apiDefinitionModule);
ImportRequest request = new ImportRequest();
request.setProjectId(DEFAULT_PROJECT_ID);
request.setPlatform("Swagger3");
request.setCoverData(true);
request.setCoverModule(true);
request.setModuleId(apiDefinitionModule.getId());
request.setProtocol("HTTP");
request.setUserId("admin");
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
FileInputStream inputStream = new FileInputStream(new File(
this.getClass().getClassLoader().getResource("file/openapi.json")
.getPath()));
MockMultipartFile file = new MockMultipartFile("file", "openapi.json", MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn("/api/definition/import", paramMap);
request.setCoverModule(false);
request.setCoverData(false);
this.requestMultipartWithOkAndReturn("/api/definition/import", paramMap);
paramMap.clear();
inputStream = new FileInputStream(new File(
this.getClass().getClassLoader().getResource("file/openapi1.json")
.getPath()));
file = new MockMultipartFile("file", "openapi1.json", MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap.add("file", file);
request.setCoverModule(true);
request.setCoverData(true);
paramMap.add("request", JSON.toJSONString(request));
this.requestMultipartWithOkAndReturn("/api/definition/import", paramMap);
paramMap.clear();
inputStream = new FileInputStream(new File(
this.getClass().getClassLoader().getResource("file/openapi2.json")
.getPath()));
file = new MockMultipartFile("file", "openapi2.json", MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap.add("file", file);
request.setCoverModule(false);
request.setCoverData(false);
paramMap.add("request", JSON.toJSONString(request));
this.requestMultipart("/api/definition/import", paramMap, status().is5xxServerError());
paramMap.clear();
inputStream = new FileInputStream(new File(
this.getClass().getClassLoader().getResource("file/openapi3.json")
.getPath()));
file = new MockMultipartFile("file", "openapi3.json", MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap.add("file", file);
request.setCoverModule(false);
request.setCoverData(false);
paramMap.add("request", JSON.toJSONString(request));
this.requestMultipartWithOkAndReturn("/api/definition/import", paramMap);
paramMap.clear();
paramMap.add("file", file);
request.setCoverModule(false);
request.setCoverData(false);
request.setSwaggerUrl("http://localhost:8080/v2/api-docs");
paramMap.add("request", JSON.toJSONString(request));
this.requestMultipart("/api/definition/import", paramMap, status().is5xxServerError());
}
protected MvcResult requestMultipart(String url, MultiValueMap<String, Object> paramMap, ResultMatcher resultMatcher) throws Exception {
MockMultipartHttpServletRequestBuilder requestBuilder = getMultipartRequestBuilderWithParam(url, paramMap);
MockHttpServletRequestBuilder header = requestBuilder
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.header(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN");
return mockMvc.perform(header)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(resultMatcher).andReturn();
}
private MockMultipartHttpServletRequestBuilder getMultipartRequestBuilderWithParam(String url, MultiValueMap<String, Object> paramMap) {
MockMultipartHttpServletRequestBuilder requestBuilder =
MockMvcRequestBuilders.multipart(getBasePath() + url);
paramMap.forEach((key, value) -> {
List list = value;
for (Object o : list) {
if (o instanceof List fileList) {
fileList.forEach(o1 -> {
if (o1 instanceof MockMultipartFile mockMultipartFile) {
try {
MockMultipartFile mockMultipartFile1 = null;
mockMultipartFile1 = new MockMultipartFile(key, mockMultipartFile.getOriginalFilename(),
MediaType.APPLICATION_OCTET_STREAM_VALUE, mockMultipartFile.getBytes());
requestBuilder.file(mockMultipartFile1);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
} else {
MockMultipartFile multipartFile = null;
multipartFile = new MockMultipartFile(key, null,
MediaType.APPLICATION_JSON_VALUE, o.toString().getBytes());
requestBuilder.file(multipartFile);
}
}
});
return requestBuilder;
}
}

View File

@ -15,6 +15,7 @@ import io.metersphere.api.dto.request.processors.extract.JSONPathExtract;
import io.metersphere.api.dto.request.processors.extract.RegexExtract;
import io.metersphere.api.dto.request.processors.extract.ResultMatchingExtract;
import io.metersphere.api.dto.request.processors.extract.XPathExtract;
import io.metersphere.api.dto.schema.JsonSchemaItem;
import io.metersphere.api.parser.TestElementParser;
import io.metersphere.api.parser.TestElementParserFactory;
import io.metersphere.api.utils.ApiDataUtils;
@ -76,7 +77,7 @@ public class MsHTTPElementTest {
body.setWwwFormBody(wwwFormBody);
JsonBody jsonBody = new JsonBody();
jsonBody.setJsonSchema("{}");
jsonBody.setJsonSchema(new JsonSchemaItem());
jsonBody.setEnableJsonSchema(false);
body.setJsonBody(jsonBody);

View File

@ -16,12 +16,6 @@ import java.util.Objects;
@AutoConfigureMockMvc
public class ParserTests {
@Test
@Order(1)
public void testImportParserSwagger() throws Exception {
Objects.requireNonNull(ImportParserFactory.getImportParser(ApiImportPlatform.Swagger3.name())).parse(null, null);
}
@Test
@Order(2)
public void testImportParserPostman() throws Exception {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,772 @@
{
"openapi": "3.1.0",
"info": {
"title": "合规问询",
"version": "1.0"
},
"paths": {
"/tasks/start/enquiry": {
"post": {
"summary": "开始问询",
"description": "**Request parameters**\n\n| 参数 | 必须 | 类型 | 说明 |\n|:---|:---|:---|:---|\n|customer_ids|是|list| 问询用户 customer_id 列表|\n|user_ids|是|list| 问询用户 user_ids 列表会查询用户的Omnibus账户|\n|name|是|String|问询名称|\n|type|是|String| 问询类型 CDD ST ONGOING TM|\n|reason|是|String| 问询原因(需要把用户勾选的原因拼起来)|\n|remark|是|String| 备注|\n|restrict|是|boolean| 是否清算 true:是|\n|content|是|json| 问询内容|\n|pre\\_answer_id|否|String| 上次问询answer_id|\n|pre_status|否|String| 上次问询结果UNSUBMIT,SUCCESS,REJECTED|\n\n**Response fields**\n\n\n\n\n\n```json\n\n\n{\n \"status\": \"ok\",\n \"msg\": \"ok\",\n \"timestamp\": 1606477343900\n}\n\n\n\n```\n",
"operationId": "post--tasks-start-enquiry",
"tags": [
"合规问询"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"customer_ids": {
"type": "array",
"x-stoplight": {
"id": "kbjft55jellci"
},
"description": "问询客户Id列表与user_ids二选一",
"items": {
"x-stoplight": {
"id": "48rmyqxcgaenj"
},
"type": "string"
}
},
"user_ids": {
"type": "array",
"x-stoplight": {
"id": "k2vz6vj9bcb1i"
},
"description": "问询用户Id列表与customer_ids二选一",
"items": {
"x-stoplight": {
"id": "sv3rgfiwghwlz"
},
"type": "string"
}
},
"name": {
"type": "string",
"x-stoplight": {
"id": "k0e99u1kks179"
},
"description": "问询名称"
},
"pre_answer_id": {
"type": "string",
"x-stoplight": {
"id": "7fg4vu00xa2xa"
},
"description": "前一个问询id"
},
"type": {
"type": "string",
"x-stoplight": {
"id": "ejbuowi29ntqm"
}
},
"reason": {
"type": "string",
"x-stoplight": {
"id": "mykt2dh4d7c78"
}
},
"pre_answer_status": {
"type": "string",
"x-stoplight": {
"id": "nok8zrvkugh85"
}
},
"content": {
"type": "object",
"x-stoplight": {
"id": "pbk4zgl8aq3vk"
},
"description": "问询内容",
"required": [
"questions"
],
"properties": {
"questions": {
"type": "array",
"x-stoplight": {
"id": "5a0g6bpavd3p9"
},
"items": {
"x-stoplight": {
"id": "tzanfcdtus3zk"
},
"type": "object",
"properties": {
"type": {
"type": "string",
"x-stoplight": {
"id": "dadmpn80vj277"
}
},
"title": {
"type": "object",
"x-stoplight": {
"id": "vxxr5fco5x5qd"
},
"properties": {
"en_US": {
"type": "string",
"x-stoplight": {
"id": "ynddq0wrd59yl"
}
},
"zh_TW": {
"type": "string",
"x-stoplight": {
"id": "gq1soegk1bv1z"
}
},
"zh_CN": {
"type": "string",
"x-stoplight": {
"id": "skxaai7a1tgbn"
}
}
}
}
},
"required": [
"type",
"title"
]
}
},
"": {
"type": "string",
"x-stoplight": {
"id": "lyxhp63oqftol"
}
}
}
}
},
"required": [
"type",
"reason",
"content"
]
}
}
},
"description": ""
},
"responses": {},
"security": [],
"parameters": [
{
"$ref": "#/components/parameters/Authorization"
}
]
}
},
"/tasks/count/enquire": {
"get": {
"summary": "任务计数",
"description": "**权限** \npage:accounts:enquiry\n\n\n**Request parameters**\n\n| 参数 | 必须 | 类型 | 说明 |\n|:---|:---|:---|:---|\n|target|是|String| ACCOUNT|\n\n\n**Response fields**\n\n\n```json\n\n\n{\n \"status\": \"ok\",\n \"msg\": \"ok\",\n \"data\": {\n \"ENQUIRY_CDD\": 10,\n \"ENQUIRY_ECDD\": 140,\n \"ENQUIRY_TM\": 20,\n \"ENQUIRY_ST\": 1,\n \"MINE\": 11\n },\n \"timestamp\": 1620908408415\n}\n\n\n```\n",
"operationId": "get--tasks-count-enquire",
"tags": [
"合规问询"
],
"requestBody": {},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"x-stoplight": {
"id": "8ef7olao3cjcr"
}
},
"msg": {
"type": "string",
"x-stoplight": {
"id": "cxmgh6dzrif3a"
}
},
"data": {
"type": "object",
"x-stoplight": {
"id": "809qtdhwnaaua"
},
"properties": {
"ENQUIRY_CDD": {
"type": "integer",
"x-stoplight": {
"id": "xmsl5222h8sro"
}
},
"ENQUIRY_ECDD": {
"type": "integer",
"x-stoplight": {
"id": "hgibemphi4r8j"
}
},
"ENQUIRY_TM": {
"type": "integer",
"x-stoplight": {
"id": "12ftglelyzcon"
}
},
"ENQUIRY_ST": {
"type": "integer",
"x-stoplight": {
"id": "3la5n5yagh6nq"
}
},
"ENQUIRY_ONGOING": {
"type": "integer",
"x-stoplight": {
"id": "tagt0zejkzmaa"
}
},
"MINE": {
"type": "integer",
"x-stoplight": {
"id": "3p2iub9ajdblw"
}
}
}
}
}
}
}
}
}
},
"parameters": [
{
"$ref": "#/components/parameters/Authorization"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "target",
"description": "审核目标"
}
]
}
},
"/tasks/{answerId}/approve/enquiry/offline": {
"post": {
"summary": "线下通过问询",
"description": "**Request parameters**\n\n| 参数 | 必须 | 类型 | 说明 |\n|:---|:---|:---|:---|\n|answerId|是|String| 问询id|\n|content|是|Object| 问询内容|\n\n\n\n```\n\n{\n \"answerId\": 1,\n \"content\": {\n \"answers\": [\n {\n \"value\": \"第一个问题的回答\"\n },\n {\n \"value\": [\"/file/1.jpg\", \"/file/2.jpg\"]\n }\n ]\n }\n}\n```\n\n**Response fields**\n\n```json\n\n\n{\n \"status\": \"ok\",\n \"msg\": \"ok\",\n \"timestamp\": 1606477343900\n}\n\n\n```\n",
"operationId": "post--tasks-{answerId}-approve-enquiry-offline",
"tags": [
"合规问询"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"answer_id": {
"type": "string",
"x-stoplight": {
"id": "hdza6stym83jj"
}
},
"content": {
"type": "object",
"x-stoplight": {
"id": "308kahe1s8nk0"
},
"properties": {
"answers": {
"type": "array",
"x-stoplight": {
"id": "h309c4jqgkduz"
},
"items": {
"x-stoplight": {
"id": "h87o04ulbgs3e"
},
"type": "object",
"properties": {
"value": {
"type": "string",
"x-stoplight": {
"id": "h0ehc5t1ckc87"
}
}
}
}
}
}
}
}
}
}
},
"description": ""
},
"responses": {},
"parameters": [
{
"$ref": "#/components/parameters/Authorization"
}
]
},
"parameters": [
{
"schema": {
"type": "string"
},
"name": "answerId",
"in": "path",
"required": true,
"description": "问询ID"
}
]
},
"/enquiry/list": {
"get": {
"summary": "问询历史记录列表",
"tags": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"itemCount": {
"type": "number"
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"customerId": {
"type": "number"
},
"userId": {
"type": "number"
},
"realName": {
"type": "string"
},
"accountId": {
"type": "string"
},
"accountType": {
"type": "string"
},
"enquiryType": {
"type": "string"
},
"answerId": {
"type": "string"
},
"preAnswerId": {
"type": "string"
},
"operator": {
"type": "string"
},
"enquiryStartDate": {
"type": "number"
},
"enquiryDeadline": {
"type": "number"
},
"enquiryReplyDate": {
"type": "number"
}
}
}
}
}
},
"msg": {
"type": "string"
},
"status": {
"type": "string"
}
}
}
}
}
}
},
"operationId": "get-enquiry-list",
"parameters": [
{
"schema": {
"type": "string",
"enum": [
"UNSUBMIT",
"SUCCESS",
"REJECTED",
"PENDING"
]
},
"in": "query",
"name": "enquiryStatus",
"description": "问询状态"
},
{
"schema": {
"type": "string",
"enum": [
"CDD",
"ONGOING",
"TM",
"ST"
]
},
"in": "query",
"name": "enquiryType",
"description": "问询类型"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "email",
"description": "邮箱"
},
{
"schema": {
"type": "number"
},
"in": "query",
"name": "userIds",
"description": "支持多个userid逗号隔开"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "phone",
"description": "手机号"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "telCode",
"description": "区号"
},
{
"schema": {
"type": "number"
},
"in": "query",
"name": "customerId"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "accountId",
"description": "账号id"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "tag",
"description": "用户标签"
},
{
"schema": {
"type": "string",
"enum": [
"OPENED",
"AUDITING"
]
},
"in": "query",
"name": "accountStatus",
"description": "开户状态"
},
{
"schema": {
"type": "string",
"enum": [
"ENQUIRY_DATE",
"OPEN_DATE",
"APPLICATION_DATE",
"ENQUIRE_DEADLINE_DATE"
]
},
"in": "query",
"name": "dateRangeType",
"description": "时间范围类型"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "dateStart",
"description": "开始时间"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "dateEnd",
"description": "结束时间"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "offset",
"description": "数据偏移量"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "limit",
"description": "每页数量"
},
{
"schema": {
"type": "string",
"enum": [
"ASCENDING",
"DESCENDING"
]
},
"in": "query",
"name": "order",
"description": "排序类型"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "realName",
"description": "姓名"
},
{
"$ref": "#/components/parameters/Authorization"
}
]
}
},
"/tasks/{taskId}/enquiry": {
"get": {
"summary": "问询详情",
"description": "**Request parameters**\n\n| 参数 | 必须 | 类型 | 说明 |\n|:---|:---|:---|:---|\n|taskId|是|int| 任务ID|\n\n**Response fields**\n\n| 字段 | 类型 | 说明 |\n|:------|:-----|:------|\n| answerId | string | 答案ID |\n| realName | string | 姓名 |\n\n\n```json\n\n\n{\n \"data\": {\n \"accountInfo\": {\n \"userInfo\": {\n \"realName\": \"张三\"\n }\n },\n \"enquiryInfo\": {\n \"answerId\": \"123123\"\n }\n }\n}\n\n```\n",
"operationId": "get--tasks-{taskId}-enquiry",
"tags": [
"合规问询"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
}
},
"responses": {},
"parameters": [
{
"$ref": "#/components/parameters/Authorization"
}
]
},
"parameters": [
{
"schema": {
"type": "integer"
},
"name": "taskId",
"in": "path",
"required": true,
"description": "任务ID"
}
]
},
"/tasks/{taskId}/claim/enquiry": {
"post": {
"summary": "认领任务",
"description": "**权限** \naccounts:enquiry:cdd \naccounts:enquiry:st \naccounts:enquiry:ecdd \naccounts:enquiry:tm \n\n\n**Response fields**\n\n\n```json\n\n\n{\n \"status\": \"ok\",\n \"msg\": \"ok\",\n \"timestamp\": 1606477343900\n}\n\n\n```\n",
"operationId": "post--tasks-{taskId}-claim-enquiry",
"tags": [
"合规问询"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
},
"responses": {},
"parameters": [
{
"$ref": "#/components/parameters/Authorization"
}
]
},
"parameters": [
{
"schema": {
"type": "string"
},
"name": "taskId",
"in": "path",
"required": true,
"description": "任务ID"
}
]
},
"/tasks/{taskId}/abort": {
"post": {
"summary": "放回任务",
"description": "**权限** \naccounts:enquiry:cdd \naccounts:enquiry:st \naccounts:enquiry:ecdd \naccounts:enquiry:tm \n\n**Request parameters**\n\n| 参数 | 必须 | 类型 | 说明 |\n|:---|:---|:---|:---|\n|taskId|是|int| 任务ID|\n\n**Response fields**\n\n\n```json\n\n\n{\n \"status\": \"ok\",\n \"msg\": \"ok\",\n \"timestamp\": 1606477343900\n}\n\n\n```",
"operationId": "post--tasks-{taskId}-abort",
"tags": [
"合规问询"
],
"requestBody": {},
"responses": {},
"parameters": [
{
"$ref": "#/components/parameters/Authorization"
}
]
},
"parameters": [
{
"schema": {
"type": "string"
},
"name": "taskId",
"in": "path",
"required": true
}
]
},
"/tasks/{taskId}/approve/enquiry": {
"post": {
"summary": "问询通过",
"description": "**权限** \naccounts:enquiry:cdd \naccounts:enquiry:st \naccounts:enquiry:ecdd \naccounts:enquiry:tm \n\n**Request parameters**\n\n| 参数 | 必须 | 类型 | 说明 |\n|:---|:---|:---|:---|\n|taskId|是|int| 任务ID|\n\n**Response fields**\n\n\n```json\n\n\n{\n \"status\": \"ok\",\n \"msg\": \"ok\",\n \"timestamp\": 1606477343900\n}\n\n\n```",
"operationId": "post--tasks-{taskId}-approve-enquiry",
"tags": [
"合规问询"
],
"requestBody": {},
"responses": {},
"parameters": [
{
"$ref": "#/components/parameters/Authorization"
}
]
},
"parameters": [
{
"schema": {
"type": "string"
},
"name": "taskId",
"in": "path",
"required": true,
"description": "任务Id"
}
]
},
"/tasks/{taskId}/reject/enquiry": {
"post": {
"summary": "问询打回",
"description": "**权限** \naccounts:enquiry:cdd \naccounts:enquiry:st \naccounts:enquiry:ecdd \naccounts:enquiry:tm \n\n\n**Request parameters**\n\n| 参数 | 必须 | 类型 | 说明 |\n|:---|:---|:---|:---|\n|taskId|是|int| ID|\n|rejectReasons|是|String| 打回理由|\n|comments|否|String| 备注|\n\n**打回原因对应操作**\n\n| 原因短语 | 操作 | \n|:------|:-----|\n| REJECT\\_CUSTOMER | 再次询问/打回用户申请 |\n| REJECT\\_FIRST\\_REVIEW | 打回初审(限制用户未开户) |\n| REJECT\\_OPEN | 拒绝开户 |\n\n\n**Response fields**\n\n```json\n\n\n{\n \"status\": \"ok\",\n \"msg\": \"ok\",\n \"timestamp\": 1606477343900\n}\n\n\n```\n",
"operationId": "post--tasks-{taskId}-reject-enquiry",
"tags": [
"合规问询"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"rejectReasons": {
"type": "string",
"x-stoplight": {
"id": "uqsyov82j5jks"
}
},
"comments": {
"type": "string",
"x-stoplight": {
"id": "rv9d5xuujthmm"
}
}
}
}
}
}
},
"responses": {},
"parameters": [
{
"$ref": "#/components/parameters/Authorization"
}
]
},
"parameters": [
{
"schema": {
"type": "string"
},
"name": "taskId",
"in": "path",
"required": true,
"description": "任务ID"
}
]
}
},
"components": {
"securitySchemes": {},
"parameters": {
"Authorization": {
"name": "Authorization",
"in": "header",
"required": false,
"schema": {
"type": "string"
}
}
}
}
}

View File

@ -0,0 +1 @@
{"openapi":"3.0.1","info":{"title":"个人项目","description":"","version":"1.0.0"},"tags":[],"paths":{"/ad/config_v2":{"post":{"summary":"config_v2","x-apifox-folder":"","x-apifox-status":"developing","deprecated":false,"description":"","tags":[],"parameters":[{"name":"Content-Type","in":"header","description":"","required":true,"example":"application/json; charset=UTF-8","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"disable_personal_recommend":{"type":"integer"},"appid":{"type":"string"},"h_dt":{"type":"integer"},"sdk_ver":{"type":"string"},"h_mac":{"type":"string"},"third_sdk_ver":{"type":"object","properties":{"tencent_sdk":{"type":"string"},"zgtech_sdk":{"type":"string"},"baidu_sdk":{"type":"string"},"tanx_sdk":{"type":"string"},"qumeng_sdk":{"type":"string"},"homemade_sdk":{"type":"string"},"kuaishou_sdk":{"type":"string"},"jd_sdk":{"type":"string"},"xingu_sdk":{"type":"string"},"toutiao_sdk":{"type":"string"},"mimo_sdk":{"type":"string"}},"required":["tencent_sdk","zgtech_sdk","baidu_sdk","tanx_sdk","qumeng_sdk","homemade_sdk","kuaishou_sdk","jd_sdk","xingu_sdk","toutiao_sdk","mimo_sdk"],"x-apifox-orders":["tencent_sdk","zgtech_sdk","baidu_sdk","tanx_sdk","qumeng_sdk","homemade_sdk","kuaishou_sdk","jd_sdk","xingu_sdk","toutiao_sdk","mimo_sdk"],"x-apifox-ignore-properties":[]}},"required":["disable_personal_recommend","appid","h_dt","sdk_ver","h_mac","third_sdk_ver"],"x-apifox-orders":["disable_personal_recommend","appid","h_dt","sdk_ver","h_mac","third_sdk_ver"],"x-apifox-ignore-properties":[]},"example":{"disable_personal_recommend":0,"ext":{"mid":"7466112968446","did":"9e17fa1e40b52024","app_ver":"2.89.10"},"h_carrier":"","h_ids":{"android_id":"9e17fa1e40b52024","oaid":"dfb91568eb92cf4e"},"h_model":"MI 8 Lite","h_os_ver":"10","h_os":"29","resolution":"1080x2154","h_nt":1,"h_did":"9e17fa1e40b52024","manufacturer":"Xiaomi","appid":"100028","h_dt":0,"sdk_ver":"4.0.07.19","h_mac":"70:BB:E9:9C:49:19","third_sdk_ver":{"tencent_sdk":"4.542.1412","zgtech_sdk":"2.2.5-1-20231122","baidu_sdk":"9.324","tanx_sdk":"2.9.3","qumeng_sdk":"3.457.13.422x","homemade_sdk":"1.0.68.6","kuaishou_sdk":"3.3.55","jd_sdk":"2.2.16_20230515","xingu_sdk":"12103_202309211453","toutiao_sdk":"5.6.1.5","mimo_sdk":"5.2.7"}}}}},"responses":{"200":{"description":"成功","content":{"application/json":{"schema":{"type":"object","properties":{},"x-apifox-orders":[],"x-apifox-ignore-properties":[]}}}}},"x-run-in-apifox":"https://www.apifox.cn/web/project/2037509/apis/api-135591150-run","security":[]}}},"components":{"schemas":{},"securitySchemes":{}},"servers":[]}

View File

@ -120,7 +120,7 @@ public class BaseUserRoleService {
put("READ+RECOVER", "permission.recover");
put("READ+EXPORT", "permission.export");
put("READ+EXECUTE", "permission.execute");
put("READ+DEBUG", "permission.download");
put("READ+DEBUG", "permission.debug");
}};
return Translator.get(translationMap.get(permissionKey));
}