feat(接口测试): 接口定义导出功能
This commit is contained in:
parent
3c464ed536
commit
e17389c271
|
@ -6,15 +6,13 @@ import io.metersphere.api.domain.ApiDefinition;
|
|||
import io.metersphere.api.dto.ReferenceDTO;
|
||||
import io.metersphere.api.dto.ReferenceRequest;
|
||||
import io.metersphere.api.dto.definition.*;
|
||||
import io.metersphere.api.dto.export.ApiExportResponse;
|
||||
import io.metersphere.api.dto.request.ApiEditPosRequest;
|
||||
import io.metersphere.api.dto.request.ApiTransferRequest;
|
||||
import io.metersphere.api.dto.request.ImportRequest;
|
||||
import io.metersphere.api.dto.schema.JsonSchemaItem;
|
||||
import io.metersphere.api.service.ApiFileResourceService;
|
||||
import io.metersphere.api.service.definition.ApiDefinitionImportService;
|
||||
import io.metersphere.api.service.definition.ApiDefinitionLogService;
|
||||
import io.metersphere.api.service.definition.ApiDefinitionNoticeService;
|
||||
import io.metersphere.api.service.definition.ApiDefinitionService;
|
||||
import io.metersphere.api.service.definition.*;
|
||||
import io.metersphere.project.service.FileModuleService;
|
||||
import io.metersphere.sdk.constants.DefaultRepositoryDir;
|
||||
import io.metersphere.sdk.constants.PermissionConstants;
|
||||
|
@ -60,6 +58,8 @@ public class ApiDefinitionController {
|
|||
private ApiFileResourceService apiFileResourceService;
|
||||
@Resource
|
||||
private ApiDefinitionImportService apiDefinitionImportService;
|
||||
@Resource
|
||||
private ApiDefinitionExportService apiDefinitionExportService;
|
||||
|
||||
@PostMapping(value = "/add")
|
||||
@Operation(summary = "接口测试-接口管理-添加接口定义")
|
||||
|
@ -301,4 +301,10 @@ public class ApiDefinitionController {
|
|||
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "id desc");
|
||||
return PageUtils.setPageInfo(page, apiDefinitionService.getReference(request));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/export/{type}")
|
||||
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_EXPORT)
|
||||
public ApiExportResponse export(@RequestBody ApiDefinitionBatchRequest request, @PathVariable String type) {
|
||||
return apiDefinitionExportService.export(request, type, SessionUtils.getUserId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package io.metersphere.api.dto.definition;
|
||||
|
||||
import io.metersphere.api.domain.ApiDefinitionBlob;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Data
|
||||
public class ApiDefinitionWithBlob extends ApiDefinitionBlob {
|
||||
|
||||
@Schema(description = "接口pk", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String id;
|
||||
|
||||
@Schema(description = "接口名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String name;
|
||||
|
||||
@Schema(description = "接口协议", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String protocol;
|
||||
|
||||
@Schema(description = "http协议类型post/get/其它协议则是协议名(mqtt)")
|
||||
private String method;
|
||||
|
||||
@Schema(description = "http协议路径/其它协议则为空")
|
||||
private String path;
|
||||
|
||||
@Schema(description = "接口状态/进行中/已完成", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String status;
|
||||
|
||||
@Schema(description = "自定义id")
|
||||
private Long num;
|
||||
|
||||
@Schema(description = "标签")
|
||||
private java.util.List<String> tags;
|
||||
|
||||
@Schema(description = "自定义排序", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long pos;
|
||||
|
||||
@Schema(description = "项目fk", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String projectId;
|
||||
|
||||
@Schema(description = "模块fk", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String moduleId;
|
||||
|
||||
@Schema(description = "是否为最新版本 0:否,1:是", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Boolean latest;
|
||||
|
||||
@Schema(description = "版本fk", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String versionId;
|
||||
|
||||
@Schema(description = "版本引用fk", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String refId;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Long createTime;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String createUser;
|
||||
|
||||
@Schema(description = "修改时间")
|
||||
private Long updateTime;
|
||||
|
||||
@Schema(description = "修改人")
|
||||
private String updateUser;
|
||||
|
||||
@Schema(description = "删除人")
|
||||
private String deleteUser;
|
||||
|
||||
@Schema(description = "删除时间")
|
||||
private Long deleteTime;
|
||||
|
||||
@Schema(description = "删除状态", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Boolean deleted;
|
||||
|
||||
@Schema(description = "模块名称")
|
||||
private String moduleName;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package io.metersphere.api.dto.export;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Data
|
||||
public class ApiExportResponse implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.metersphere.api.dto.export;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerApiExportResponse extends ApiExportResponse{
|
||||
|
||||
private String openapi;
|
||||
private SwaggerInfo info;
|
||||
private JsonNode externalDocs;
|
||||
private List<String> servers;
|
||||
private List<SwaggerTag> tags;
|
||||
private JsonNode paths;
|
||||
private JsonNode components;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package io.metersphere.api.dto.export;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
该类表示 swagger3 的 paths 字段下每个请求类型中的 value,即表示一个 api 定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerApiInfo {
|
||||
private List<String> tags; // 对应一个 API 在MS项目中所在的 module 名称
|
||||
private String summary; // 对应 API 的名字
|
||||
private List<JsonNode> parameters; // 对应 API 的请求参数
|
||||
private JsonNode requestBody;
|
||||
private JsonNode responses;
|
||||
private String description;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.metersphere.api.dto.export;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerInfo {
|
||||
private String version;
|
||||
private String title;
|
||||
private String description;
|
||||
private String termsOfService;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.metersphere.api.dto.export;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SwaggerParams {
|
||||
//对应 API 请求参数名
|
||||
private String name;
|
||||
//参数值
|
||||
private String value;
|
||||
//参数类型,可选值为 path,header,query 等
|
||||
private String in;
|
||||
private String description;
|
||||
//是否是必填参数
|
||||
private boolean enable;
|
||||
private JsonNode schema;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.metersphere.api.dto.export;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerTag {
|
||||
private String name;
|
||||
private String description;
|
||||
}
|
|
@ -83,4 +83,6 @@ public interface ExtApiDefinitionMapper {
|
|||
List<ReferenceDTO> getReference(@Param("request") ReferenceRequest request);
|
||||
|
||||
List<ApiDefinition> selectByProjectNum(String projectNum);
|
||||
|
||||
List<ApiDefinitionWithBlob> selectApiDefinitionWithBlob(@Param("ids") List<String> ids);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,13 @@
|
|||
<resultMap id="BaseResultMap" type="io.metersphere.api.domain.ApiDefinition">
|
||||
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="ApiResultMap" type="io.metersphere.api.dto.definition.ApiDefinitionWithBlob">
|
||||
<result column="request" jdbcType="LONGVARBINARY" property="request" />
|
||||
<result column="response" jdbcType="LONGVARBINARY" property="response" />
|
||||
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler" />
|
||||
</resultMap>
|
||||
|
||||
<update id="deleteApiToGc">
|
||||
update api_definition
|
||||
set delete_user = #{userId},delete_time = #{time}, deleted = 1 , module_id = 'root'
|
||||
|
@ -631,4 +638,21 @@
|
|||
</where>
|
||||
group by a.id, ass.ref_type
|
||||
</select>
|
||||
|
||||
|
||||
<select id="selectApiDefinitionWithBlob" resultMap="ApiResultMap">
|
||||
SELECT
|
||||
api_definition.*,
|
||||
api_definition_blob.request,
|
||||
api_definition_blob.response,
|
||||
api_definition_module.name as moduleName
|
||||
FROM
|
||||
api_definition
|
||||
INNER JOIN api_definition_blob ON api_definition.id = api_definition_blob.id
|
||||
inner join api_definition_module on api_definition.module_id = api_definition_module.id
|
||||
where api_definition.id in
|
||||
<foreach collection="ids" item="id" separator="," open="(" close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package io.metersphere.api.parser;
|
||||
|
||||
import io.metersphere.api.dto.request.ExportRequest;
|
||||
import io.metersphere.api.dto.definition.ApiDefinitionWithBlob;
|
||||
import io.metersphere.project.domain.Project;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ExportParser<T> {
|
||||
T parse(ExportRequest request) throws Exception;
|
||||
T parse(List<ApiDefinitionWithBlob> list, Project project) throws Exception;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,575 @@
|
|||
package io.metersphere.api.parser.api;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import io.metersphere.api.dto.definition.ApiDefinitionWithBlob;
|
||||
import io.metersphere.api.dto.export.*;
|
||||
import io.metersphere.api.dto.request.http.body.Body;
|
||||
import io.metersphere.api.parser.ExportParser;
|
||||
import io.metersphere.api.utils.JSONUtil;
|
||||
import io.metersphere.api.utils.XMLUtil;
|
||||
import io.metersphere.project.constants.PropertyConstant;
|
||||
import io.metersphere.project.domain.Project;
|
||||
import io.metersphere.sdk.util.JSON;
|
||||
import io.swagger.v3.oas.models.responses.ApiResponse;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
public class Swagger3ExportParser implements ExportParser<ApiExportResponse> {
|
||||
|
||||
|
||||
@Override
|
||||
public ApiExportResponse parse(List<ApiDefinitionWithBlob> list, Project project) throws Exception {
|
||||
SwaggerApiExportResponse response = new SwaggerApiExportResponse();
|
||||
//openapi
|
||||
response.setOpenapi("3.0.2");
|
||||
//info
|
||||
SwaggerInfo swaggerInfo = new SwaggerInfo();
|
||||
swaggerInfo.setVersion("3.0");
|
||||
swaggerInfo.setTitle("ms-" + project.getName());
|
||||
swaggerInfo.setDescription(StringUtils.EMPTY);
|
||||
swaggerInfo.setTermsOfService(StringUtils.EMPTY);
|
||||
response.setInfo(swaggerInfo);
|
||||
//servers
|
||||
response.setServers(new ArrayList<>());
|
||||
//tags
|
||||
response.setTags(new ArrayList<>());
|
||||
|
||||
response.setComponents(JSONUtil.createObj());
|
||||
response.setExternalDocs(JSONUtil.createObj());
|
||||
|
||||
//path
|
||||
JSONObject paths = new JSONObject();
|
||||
JSONObject components = new JSONObject();
|
||||
List<JSONObject> schemas = new LinkedList<>();
|
||||
for (ApiDefinitionWithBlob apiDefinition : list) {
|
||||
SwaggerApiInfo swaggerApiInfo = new SwaggerApiInfo();
|
||||
swaggerApiInfo.setSummary(apiDefinition.getName());
|
||||
swaggerApiInfo.setTags(Arrays.asList(apiDefinition.getModuleName()));
|
||||
//请求体
|
||||
JSONObject requestObject = JSONUtil.parseObject(new String(apiDefinition.getRequest() == null ? new byte[0] : apiDefinition.getRequest(), StandardCharsets.UTF_8));
|
||||
JSONObject requestBody = buildRequestBody(requestObject, schemas);
|
||||
|
||||
swaggerApiInfo.setRequestBody(JSONUtil.parseObjectNode(requestBody.toString()));
|
||||
JSONArray responseObject = new JSONArray();
|
||||
try {
|
||||
// 设置响应体
|
||||
responseObject = JSONUtil.parseArray(new String(apiDefinition.getResponse() == null ? new byte[0] : apiDefinition.getResponse(), StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
responseObject = new JSONArray(new ApiResponse());
|
||||
}
|
||||
JSONObject jsonObject = buildResponseBody(responseObject, schemas);
|
||||
swaggerApiInfo.setResponses(JSONUtil.parseObjectNode(jsonObject.toString()));
|
||||
// 设置请求参数列表
|
||||
List<JSONObject> paramsList = buildParameters(requestObject);
|
||||
List<JsonNode> nodes = new LinkedList<>();
|
||||
paramsList.forEach(item -> {
|
||||
nodes.add(JSONUtil.parseObjectNode(item.toString()));
|
||||
});
|
||||
swaggerApiInfo.setParameters(nodes);
|
||||
swaggerApiInfo.setDescription(apiDefinition.getDescription());
|
||||
JSONObject methodDetail = JSONUtil.parseObject(JSON.toJSONString(swaggerApiInfo));
|
||||
if (paths.optJSONObject(apiDefinition.getPath()) == null) {
|
||||
paths.put(apiDefinition.getPath(), new JSONObject());
|
||||
} // 一个路径下有多个发方法,如post,get,因此是一个 JSONObject 类型
|
||||
paths.optJSONObject(apiDefinition.getPath()).put(apiDefinition.getMethod().toLowerCase(), methodDetail);
|
||||
|
||||
}
|
||||
response.setPaths(JSONUtil.parseObjectNode(paths.toString()));
|
||||
if (CollectionUtils.isNotEmpty(schemas)) {
|
||||
components.put("schemas", schemas.get(0));
|
||||
}
|
||||
response.setComponents(JSONUtil.parseObjectNode(components.toString()));
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
private List<JSONObject> buildParameters(JSONObject request) {
|
||||
List<JSONObject> paramsList = new ArrayList<>();
|
||||
Hashtable<String, String> typeMap = new Hashtable<String, String>() {{
|
||||
put("headers", "header");
|
||||
put("rest", "path");
|
||||
put("arguments", "query");
|
||||
}};
|
||||
Set<String> typeKeys = typeMap.keySet();
|
||||
for (String type : typeKeys) {
|
||||
JSONArray params = request.optJSONArray(type); // 获得请求参数列表
|
||||
if (params != null) {
|
||||
for (int i = 0; i < params.length(); ++i) {
|
||||
JSONObject param = params.optJSONObject(i); // 对于每个参数:
|
||||
if (StringUtils.isEmpty(param.optString("key"))) {
|
||||
continue;
|
||||
} // 否则无参数的情况,可能多出一行空行
|
||||
SwaggerParams swaggerParam = new SwaggerParams();
|
||||
swaggerParam.setIn(typeMap.get(type)); // 利用 map,根据 request 的 key 设置对应的参数类型
|
||||
swaggerParam.setDescription(param.optString("description"));
|
||||
swaggerParam.setName(param.optString("key"));
|
||||
swaggerParam.setEnable(param.optBoolean(PropertyConstant.ENABLE));
|
||||
swaggerParam.setValue(param.optString("value"));
|
||||
JSONObject schema = new JSONObject();
|
||||
schema.put(PropertyConstant.TYPE, PropertyConstant.STRING);
|
||||
swaggerParam.setSchema(JSONUtil.parseObjectNode(schema.toString()));
|
||||
paramsList.add(JSONUtil.parseObject(JSON.toJSONString(swaggerParam)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return paramsList;
|
||||
}
|
||||
|
||||
private JSONObject buildResponseBody(JSONArray response, List<JSONObject> schemas) {
|
||||
if (response.length() == 0) {
|
||||
return new JSONObject();
|
||||
}
|
||||
JSONObject responseBody = new JSONObject();
|
||||
for (int i = 0; i < response.length(); i++) {
|
||||
JSONObject responseJSONObject = response.getJSONObject(i);
|
||||
JSONObject headers = new JSONObject();
|
||||
JSONArray headValueList = responseJSONObject.optJSONArray("headers");
|
||||
if (headValueList != null) {
|
||||
for (Object item : headValueList) {
|
||||
if (item instanceof JSONObject && ((JSONObject) item).optString("key") != null) {
|
||||
JSONObject head = new JSONObject(), headSchema = new JSONObject();
|
||||
head.put("description", ((JSONObject) item).optString("description"));
|
||||
head.put("example", ((JSONObject) item).optString("value"));
|
||||
headSchema.put(PropertyConstant.TYPE, PropertyConstant.STRING);
|
||||
head.put("schema", headSchema);
|
||||
headers.put(((JSONObject) item).optString("key"), head);
|
||||
}
|
||||
}
|
||||
}
|
||||
String statusCode = responseJSONObject.optString("statusCode");
|
||||
if (StringUtils.isNotBlank(statusCode)) {
|
||||
JSONObject statusCodeInfo = new JSONObject();
|
||||
statusCodeInfo.put("headers", headers);
|
||||
statusCodeInfo.put("content", buildContent(responseJSONObject, schemas));
|
||||
statusCodeInfo.put("description", StringUtils.EMPTY);
|
||||
if (StringUtils.isNotBlank(responseJSONObject.optString("value"))) {
|
||||
statusCodeInfo.put("description", responseJSONObject.optString("value"));
|
||||
}
|
||||
if (StringUtils.isNotBlank(responseJSONObject.optString("name"))) {
|
||||
responseBody.put(responseJSONObject.optString("name"), statusCodeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
private JSONObject buildRequestBody(JSONObject request, List<JSONObject> schemas) {
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.put("content", buildContent(request, schemas));
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
private JSONObject buildContent(JSONObject respOrReq, List<JSONObject> schemas) {
|
||||
Hashtable<String, String> typeMap = new Hashtable<String, String>() {{
|
||||
put(Body.BodyType.XML.name(), org.springframework.http.MediaType.APPLICATION_XML_VALUE);
|
||||
put(Body.BodyType.JSON.name(), org.springframework.http.MediaType.APPLICATION_JSON_VALUE);
|
||||
put(Body.BodyType.RAW.name(), "application/urlencoded");
|
||||
put(Body.BodyType.BINARY.name(), org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
put(Body.BodyType.FORM_DATA.name(), org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE);
|
||||
put(Body.BodyType.WWW_FORM.name(), org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
}};
|
||||
Object bodyInfo = null;
|
||||
Object jsonInfo = null;
|
||||
JSONObject body = respOrReq.optJSONObject("body");
|
||||
|
||||
if (body != null) { // 将请求体转换成相应的格式导出
|
||||
String bodyType = body.optString(PropertyConstant.BODYTYPE);
|
||||
if (StringUtils.isNotBlank(bodyType) && bodyType.equalsIgnoreCase(Body.BodyType.JSON.name())) {
|
||||
try {
|
||||
// json
|
||||
String jsonValue = body.optJSONObject("jsonBody").optString("jsonValue");
|
||||
if (StringUtils.isNotBlank(jsonValue)) {
|
||||
jsonInfo = buildJson(jsonValue);
|
||||
}
|
||||
// jsonSchema
|
||||
String jsonSchema = body.optJSONObject("jsonBody").optString("jsonSchema");
|
||||
if (StringUtils.isNotBlank(jsonSchema)) {
|
||||
JSONObject jsonSchemaObject = JSONUtil.parseObject(jsonSchema);
|
||||
bodyInfo = buildJsonSchema(jsonSchemaObject);
|
||||
}
|
||||
} catch (Exception e1) { // 若请求体 json 不合法,则忽略错误,原样字符串导出/导入
|
||||
bodyInfo = new JSONObject();
|
||||
((JSONObject) bodyInfo).put(PropertyConstant.TYPE, PropertyConstant.STRING);
|
||||
if (body != null && body.optString("rawBody") != null) {
|
||||
((JSONObject) bodyInfo).put("example", body.optString("rawBody"));
|
||||
}
|
||||
}
|
||||
} else if (bodyType != null && bodyType.equalsIgnoreCase(Body.BodyType.RAW.name())) {
|
||||
bodyInfo = new JSONObject();
|
||||
((JSONObject) bodyInfo).put(PropertyConstant.TYPE, PropertyConstant.STRING);
|
||||
if (body != null && body.optString("rawBody") != null) {
|
||||
((JSONObject) bodyInfo).put("example", body.optString("rawBody"));
|
||||
}
|
||||
} else if (bodyType != null && bodyType.equalsIgnoreCase(Body.BodyType.XML.name())) {
|
||||
String xmlText = body.optString("xmlBody");
|
||||
JSONObject xmlObject = JSONUtil.parseObject(xmlText);
|
||||
xmlText = xmlObject.optString("value");
|
||||
String xml = XMLUtil.delXmlHeader(xmlText);
|
||||
int startIndex = xml.indexOf("<", 0);
|
||||
int endIndex = xml.indexOf(">", 0);
|
||||
if (endIndex > startIndex + 1) {
|
||||
String substring = xml.substring(startIndex + 1, endIndex);
|
||||
bodyInfo = buildRefSchema(substring);
|
||||
}
|
||||
JSONObject xmlToJson = XMLUtil.xmlConvertJson(xmlText);
|
||||
JSONObject jsonObject = buildRequestBodyXmlSchema(xmlToJson);
|
||||
if (schemas == null) {
|
||||
schemas = new LinkedList<>();
|
||||
}
|
||||
schemas.add(jsonObject);
|
||||
} else if (bodyType != null && (bodyType.equalsIgnoreCase(Body.BodyType.WWW_FORM.name()) || bodyType.equalsIgnoreCase(Body.BodyType.FORM_DATA.name()))) { // key-value 类格式
|
||||
String wwwFormBody = body.optString("wwwFormBody");
|
||||
JSONObject wwwFormObject = JSONUtil.parseObject(wwwFormBody);
|
||||
JSONObject formData = getformDataProperties(wwwFormObject.optJSONArray("formValues"));
|
||||
bodyInfo = buildFormDataSchema(formData);
|
||||
} else if (bodyType != null && bodyType.equalsIgnoreCase(Body.BodyType.BINARY.name())) {
|
||||
bodyInfo = buildBinary();
|
||||
}
|
||||
}
|
||||
|
||||
String type = null;
|
||||
if (respOrReq.optJSONObject("body") != null) {
|
||||
type = respOrReq.optJSONObject("body").optString(PropertyConstant.BODYTYPE);
|
||||
}
|
||||
JSONObject content = new JSONObject();
|
||||
Object schema = bodyInfo; // 请求体部分
|
||||
JSONObject typeName = new JSONObject();
|
||||
if (schema != null) {
|
||||
typeName.put("schema", schema);
|
||||
}
|
||||
if (jsonInfo != null) {
|
||||
typeName.put("example", jsonInfo);
|
||||
}
|
||||
if (StringUtils.isNotBlank(type) && typeMap.containsKey(type)) {
|
||||
content.put(typeMap.get(type), typeName);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private JSONObject buildBinary() {
|
||||
JSONObject parsedParam = new JSONObject();
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.STRING);
|
||||
parsedParam.put("format", "binary");
|
||||
return parsedParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* requestBody 中jsonSchema
|
||||
*
|
||||
* @param jsonSchemaObject
|
||||
* @return
|
||||
*/
|
||||
private JSONObject buildJsonSchema(JSONObject jsonSchemaObject) {
|
||||
JSONObject parsedParam = new JSONObject();
|
||||
String type = jsonSchemaObject.optString(PropertyConstant.TYPE);
|
||||
if (StringUtils.isNotBlank(type)) {
|
||||
if (StringUtils.equals(type, PropertyConstant.OBJECT)) {
|
||||
parsedParam = jsonSchemaObject;
|
||||
} else if (StringUtils.equals(type, PropertyConstant.ARRAY)) {
|
||||
JSONArray items = jsonSchemaObject.optJSONArray(PropertyConstant.ITEMS);
|
||||
JSONObject itemProperties = new JSONObject();
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.ARRAY);
|
||||
if (items != null) {
|
||||
JSONObject itemsObject = new JSONObject();
|
||||
if (items.length() > 0) {
|
||||
items.forEach(item -> {
|
||||
if (item instanceof JSONObject) {
|
||||
JSONObject itemJson = buildJsonSchema((JSONObject) item);
|
||||
if (itemJson != null) {
|
||||
Set<String> keys = itemJson.keySet();
|
||||
for (String key : keys) {
|
||||
itemProperties.put(key, itemJson.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
itemsObject.put(PropertyConstant.PROPERTIES, itemProperties);
|
||||
parsedParam.put(PropertyConstant.ITEMS, itemsObject.optJSONObject(PropertyConstant.PROPERTIES));
|
||||
} else {
|
||||
parsedParam.put(PropertyConstant.ITEMS, new JSONObject());
|
||||
}
|
||||
} else if (StringUtils.equals(type, PropertyConstant.INTEGER)) {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.INTEGER);
|
||||
parsedParam.put("format", "int64");
|
||||
setCommonJsonSchemaParam(parsedParam, jsonSchemaObject);
|
||||
|
||||
} else if (StringUtils.equals(type, PropertyConstant.BOOLEAN)) {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.BOOLEAN);
|
||||
setCommonJsonSchemaParam(parsedParam, jsonSchemaObject);
|
||||
} else if (StringUtils.equals(type, PropertyConstant.NUMBER)) {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.NUMBER);
|
||||
setCommonJsonSchemaParam(parsedParam, jsonSchemaObject);
|
||||
} else {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.STRING);
|
||||
setCommonJsonSchemaParam(parsedParam, jsonSchemaObject);
|
||||
}
|
||||
|
||||
}
|
||||
return parsedParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* requestBody 中json
|
||||
*
|
||||
* @param jsonValue
|
||||
* @return
|
||||
*/
|
||||
private JSONObject buildJson(String jsonValue) {
|
||||
JSONObject jsonObject = JSONUtil.parseObject(jsonValue);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private JSONObject getformDataProperties(JSONArray requestBody) {
|
||||
JSONObject result = new JSONObject();
|
||||
for (Object item : requestBody) {
|
||||
if (item instanceof JSONObject) {
|
||||
String name = ((JSONObject) item).optString("key");
|
||||
if (name != null) {
|
||||
result.put(name, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static JSONObject buildRequestBodyXmlSchema(JSONObject requestBody) {
|
||||
if (requestBody == null) return null;
|
||||
JSONObject schema = new JSONObject();
|
||||
for (String key : requestBody.keySet()) {
|
||||
Object param = requestBody.get(key);
|
||||
JSONObject parsedParam = new JSONObject();
|
||||
if (param instanceof String) {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.STRING);
|
||||
parsedParam.put("example", param == null ? StringUtils.EMPTY : param);
|
||||
} else if (param instanceof Integer) {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.INTEGER);
|
||||
parsedParam.put("format", "int64");
|
||||
parsedParam.put("example", param);
|
||||
} else if (param instanceof JSONObject) {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.OBJECT);
|
||||
Object attribute = ((JSONObject) param).opt("attribute");
|
||||
//build properties
|
||||
JSONObject paramObject = buildRequestBodyXmlSchema((JSONObject) param);
|
||||
if (attribute != null && attribute instanceof JSONArray) {
|
||||
JSONObject jsonObject = buildXmlProperties(((JSONArray) attribute).getJSONObject(0));
|
||||
paramObject.remove("attribute");
|
||||
for (String paramKey : paramObject.keySet()) {
|
||||
Object paramChild = paramObject.get(paramKey);
|
||||
if (paramChild instanceof String) {
|
||||
JSONObject one = new JSONObject();
|
||||
one.put(PropertyConstant.TYPE, PropertyConstant.OBJECT);
|
||||
one.put("properties", jsonObject);
|
||||
paramObject.remove("example");
|
||||
paramObject.remove(paramKey);
|
||||
paramObject.put(paramKey, one);
|
||||
}
|
||||
if (paramChild instanceof JSONObject) {
|
||||
Object properties = ((JSONObject) paramChild).opt("properties");
|
||||
if (properties != null) {
|
||||
for (String aa : jsonObject.keySet()) {
|
||||
Object value = jsonObject.get(aa);
|
||||
if (((JSONObject) properties).opt(aa) == null) {
|
||||
((JSONObject) properties).put(aa, value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
((JSONObject) paramChild).put("properties", jsonObject);
|
||||
}
|
||||
if (((JSONObject) paramChild).opt("type") == "string") {
|
||||
((JSONObject) paramChild).put("type", "object");
|
||||
((JSONObject) paramChild).remove("example");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parsedParam.put("properties", paramObject);
|
||||
if (StringUtils.isNotBlank(requestBody.optString("description"))) {
|
||||
parsedParam.put("description", requestBody.optString("description"));
|
||||
}
|
||||
} else if (param instanceof Boolean) {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.BOOLEAN);
|
||||
parsedParam.put("example", param);
|
||||
} else if (param instanceof java.math.BigDecimal) { // double 类型会被 fastJson 转换为 BigDecimal
|
||||
parsedParam.put(PropertyConstant.TYPE, "double");
|
||||
parsedParam.put("example", param);
|
||||
} else { // JSONOArray
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.OBJECT);
|
||||
|
||||
if (param == null) {
|
||||
param = new JSONArray();
|
||||
}
|
||||
JSONObject jsonObjects = new JSONObject();
|
||||
if (((JSONArray) param).length() > 0) {
|
||||
((JSONArray) param).forEach(t -> {
|
||||
JSONObject item = buildRequestBodyXmlSchema((JSONObject) t);
|
||||
for (String s : item.keySet()) {
|
||||
jsonObjects.put(s, item.get(s));
|
||||
}
|
||||
});
|
||||
}
|
||||
parsedParam.put(PropertyConstant.PROPERTIES, jsonObjects);
|
||||
}
|
||||
schema.put(key, parsedParam);
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
private static JSONObject buildXmlProperties(JSONObject kvs) {
|
||||
JSONObject properties = new JSONObject();
|
||||
for (String key : kvs.keySet()) {
|
||||
JSONObject property = new JSONObject();
|
||||
Object param = kvs.opt(key);
|
||||
if (param instanceof String) {
|
||||
property.put(PropertyConstant.TYPE, PropertyConstant.STRING);
|
||||
property.put("example", param == null ? StringUtils.EMPTY : param);
|
||||
}
|
||||
if (param instanceof JSONObject) {
|
||||
JSONObject obj = ((JSONObject) param);
|
||||
property.put(PropertyConstant.TYPE, StringUtils.isNotEmpty(obj.optString(PropertyConstant.TYPE)) ? obj.optString(PropertyConstant.TYPE) : PropertyConstant.STRING);
|
||||
String value = obj.optString("value");
|
||||
if (StringUtils.isBlank(value)) {
|
||||
JSONObject mock = obj.optJSONObject(PropertyConstant.MOCK);
|
||||
if (mock != null) {
|
||||
Object mockValue = mock.get(PropertyConstant.MOCK);
|
||||
property.put("example", mockValue);
|
||||
} else {
|
||||
property.put("example", value);
|
||||
}
|
||||
} else {
|
||||
property.put("example", value);
|
||||
}
|
||||
}
|
||||
JSONObject xml = new JSONObject();
|
||||
xml.put("attribute", true);
|
||||
property.put("xml", xml);
|
||||
properties.put(key, property);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private Object buildRefSchema(String substring) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("$ref", "#/components/schemas/" + substring);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private JSONObject buildRequestBodyJsonInfo(JSONObject requestBody) {
|
||||
if (requestBody == null) return null;
|
||||
JSONObject schema = new JSONObject();
|
||||
schema.put(PropertyConstant.TYPE, PropertyConstant.OBJECT);
|
||||
JSONObject properties = buildSchema(requestBody);
|
||||
schema.put(PropertyConstant.PROPERTIES, properties);
|
||||
return schema;
|
||||
}
|
||||
|
||||
private JSONObject buildSchema(JSONObject requestBody) {
|
||||
JSONObject schema = new JSONObject();
|
||||
for (String key : requestBody.keySet()) {
|
||||
Object param = requestBody.get(key);
|
||||
JSONObject parsedParam = new JSONObject();
|
||||
if (param instanceof String) {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.STRING);
|
||||
parsedParam.put("example", param == null ? StringUtils.EMPTY : param);
|
||||
} else if (param instanceof Integer) {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.INTEGER);
|
||||
parsedParam.put("format", "int64");
|
||||
parsedParam.put("example", param);
|
||||
} else if (param instanceof JSONObject) {
|
||||
parsedParam = buildRequestBodyJsonInfo((JSONObject) param);
|
||||
} else if (param instanceof Boolean) {
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.BOOLEAN);
|
||||
parsedParam.put("example", param);
|
||||
} else if (param instanceof java.math.BigDecimal) { // double 类型会被 fastJson 转换为 BigDecimal
|
||||
parsedParam.put(PropertyConstant.TYPE, "double");
|
||||
parsedParam.put("example", param);
|
||||
} else { // JSONOArray
|
||||
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.ARRAY);
|
||||
JSONObject item = new JSONObject();
|
||||
if (param == null) {
|
||||
param = new JSONArray();
|
||||
}
|
||||
if (((JSONArray) param).length() > 0) {
|
||||
if (((JSONArray) param).get(0) instanceof JSONObject) { ///
|
||||
item = buildRequestBodyJsonInfo((JSONObject) ((JSONArray) param).get(0));
|
||||
}
|
||||
}
|
||||
parsedParam.put(PropertyConstant.ITEMS, item);
|
||||
}
|
||||
schema.put(key, parsedParam);
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
|
||||
private JSONObject buildFormDataSchema(JSONObject kvs) {
|
||||
JSONObject schema = new JSONObject();
|
||||
JSONObject properties = new JSONObject();
|
||||
for (String key : kvs.keySet()) {
|
||||
JSONObject property = new JSONObject();
|
||||
JSONObject obj = ((JSONObject) kvs.get(key));
|
||||
property.put(PropertyConstant.TYPE, StringUtils.isNotEmpty(obj.optString(PropertyConstant.PARAMTYPE)) ? obj.optString(PropertyConstant.PARAMTYPE) : PropertyConstant.STRING);
|
||||
String value = obj.optString("value");
|
||||
if (StringUtils.isBlank(value)) {
|
||||
JSONObject mock = obj.optJSONObject(PropertyConstant.MOCK);
|
||||
if (mock != null && StringUtils.isNotBlank(mock.optString("mock"))) {
|
||||
Object mockValue = mock.get(PropertyConstant.MOCK);
|
||||
property.put("example", mockValue);
|
||||
} else {
|
||||
property.put("example", value);
|
||||
}
|
||||
} else {
|
||||
property.put("example", value);
|
||||
}
|
||||
property.put("description", obj.optString("description"));
|
||||
property.put(PropertyConstant.REQUIRED, obj.optString(PropertyConstant.REQUIRED));
|
||||
if (obj.optJSONObject(PropertyConstant.PROPERTIES) != null) {
|
||||
JSONObject childProperties = buildFormDataSchema(obj.optJSONObject(PropertyConstant.PROPERTIES));
|
||||
property.put(PropertyConstant.PROPERTIES, childProperties.optJSONObject(PropertyConstant.PROPERTIES));
|
||||
} else {
|
||||
JSONObject childProperties = buildJsonSchema(obj);
|
||||
if (StringUtils.equalsIgnoreCase(obj.optString(PropertyConstant.PARAMTYPE), PropertyConstant.ARRAY)) {
|
||||
if (childProperties.optJSONObject(PropertyConstant.ITEMS) != null) {
|
||||
property.put(PropertyConstant.ITEMS, childProperties.optJSONObject(PropertyConstant.ITEMS));
|
||||
}
|
||||
} else {
|
||||
if (childProperties.optJSONObject(PropertyConstant.PROPERTIES) != null) {
|
||||
property.put(PropertyConstant.PROPERTIES, childProperties.optJSONObject(PropertyConstant.PROPERTIES));
|
||||
}
|
||||
}
|
||||
}
|
||||
properties.put(key, property);
|
||||
}
|
||||
schema.put(PropertyConstant.PROPERTIES, properties);
|
||||
return schema;
|
||||
}
|
||||
|
||||
public void setCommonJsonSchemaParam(JSONObject parsedParam, JSONObject requestBody) {
|
||||
if (StringUtils.isNotBlank(requestBody.optString("description"))) {
|
||||
parsedParam.put("description", requestBody.optString("description"));
|
||||
}
|
||||
Object jsonSchemaValue = getJsonSchemaValue(requestBody);
|
||||
if (jsonSchemaValue != null) {
|
||||
parsedParam.put("example", jsonSchemaValue);
|
||||
}
|
||||
}
|
||||
|
||||
public Object getJsonSchemaValue(JSONObject item) {
|
||||
JSONObject mock = item.optJSONObject(PropertyConstant.MOCK);
|
||||
if (mock != null) {
|
||||
if (StringUtils.isNotBlank(mock.optString("mock"))) {
|
||||
Object value = mock.get(PropertyConstant.MOCK);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package io.metersphere.api.service.definition;
|
||||
|
||||
import io.metersphere.api.dto.definition.ApiDefinitionBatchRequest;
|
||||
import io.metersphere.api.dto.definition.ApiDefinitionWithBlob;
|
||||
import io.metersphere.api.dto.export.ApiExportResponse;
|
||||
import io.metersphere.api.mapper.ExtApiDefinitionMapper;
|
||||
import io.metersphere.api.parser.api.Swagger3ExportParser;
|
||||
import io.metersphere.project.domain.Project;
|
||||
import io.metersphere.project.mapper.ProjectMapper;
|
||||
import io.metersphere.sdk.exception.MSException;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Service
|
||||
public class ApiDefinitionExportService {
|
||||
|
||||
@Resource
|
||||
private ApiDefinitionService apiDefinitionService;
|
||||
@Resource
|
||||
private ExtApiDefinitionMapper extApiDefinitionMapper;
|
||||
@Resource
|
||||
private ProjectMapper projectMapper;
|
||||
|
||||
public ApiExportResponse export(ApiDefinitionBatchRequest request, String type, String userId) {
|
||||
List<String> ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocols(), false, userId);
|
||||
List<ApiDefinitionWithBlob> list = extApiDefinitionMapper.selectApiDefinitionWithBlob(ids);
|
||||
switch (type) {
|
||||
case "swagger":
|
||||
return exportSwagger(request, list);
|
||||
default:
|
||||
return new ApiExportResponse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ApiExportResponse exportSwagger(ApiDefinitionBatchRequest request, List<ApiDefinitionWithBlob> list) {
|
||||
Project project = projectMapper.selectByPrimaryKey(request.getProjectId());
|
||||
Swagger3ExportParser swagger3Parser = new Swagger3ExportParser();
|
||||
try {
|
||||
return swagger3Parser.parse(list, project);
|
||||
} catch (Exception e) {
|
||||
throw new MSException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package io.metersphere.api.utils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.core.StreamReadConstraints;
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import io.metersphere.sdk.exception.MSException;
|
||||
import io.metersphere.sdk.util.JSON;
|
||||
import io.metersphere.sdk.util.LogUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
public class JSONUtil {
|
||||
private static final ObjectMapper objectMapper = JsonMapper.builder()
|
||||
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
|
||||
.build();
|
||||
private static final TypeFactory typeFactory = objectMapper.getTypeFactory();
|
||||
public static final int DEFAULT_MAX_STRING_LEN = Integer.MAX_VALUE;
|
||||
|
||||
static {
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
// 自动检测所有类的全部属性
|
||||
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
// 如果一个对象中没有任何的属性,那么在序列化的时候就会报错
|
||||
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
||||
// 使用BigDecimal来序列化
|
||||
objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
|
||||
// 设置JSON处理字符长度限制
|
||||
objectMapper.getFactory()
|
||||
.setStreamReadConstraints(StreamReadConstraints.builder().maxStringLength(DEFAULT_MAX_STRING_LEN).build());
|
||||
// 处理时间格式
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
}
|
||||
|
||||
|
||||
public static JSONObject parseObject(String value) {
|
||||
try {
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
throw new MSException("value is null");
|
||||
}
|
||||
Map<String, Object> map = JSON.parseObject(value, Map.class);
|
||||
return new JSONObject(map);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static JSONArray parseArray(String text) {
|
||||
List<Object> list = JSON.parseObject(text, List.class);
|
||||
return new JSONArray(list);
|
||||
}
|
||||
|
||||
public static ObjectNode parseObjectNode(String text) {
|
||||
try {
|
||||
return (ObjectNode) objectMapper.readTree(text);
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e);
|
||||
}
|
||||
return objectMapper.createObjectNode();
|
||||
}
|
||||
|
||||
public static ObjectNode createObj() {
|
||||
return objectMapper.createObjectNode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package io.metersphere.api.utils;
|
||||
|
||||
import io.metersphere.sdk.util.LogUtils;
|
||||
import io.metersphere.sdk.util.XMLUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dom4j.Attribute;
|
||||
import org.dom4j.Document;
|
||||
import org.dom4j.Element;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
public class XMLUtil {
|
||||
|
||||
@Nullable
|
||||
public static String delXmlHeader(String xml) {
|
||||
int begin = xml.indexOf("?>");
|
||||
if (begin != -1) {
|
||||
if (begin + 2 >= xml.length()) {
|
||||
return null;
|
||||
}
|
||||
xml = xml.substring(begin + 2);
|
||||
} // <?xml version="1.0" encoding="utf-8"?> 若存在,则去除
|
||||
String rgex = ">";
|
||||
Pattern pattern = Pattern.compile(rgex);
|
||||
Matcher m = pattern.matcher(xml);
|
||||
xml = m.replaceAll("> ");
|
||||
rgex = "\\s*</";
|
||||
pattern = Pattern.compile(rgex);
|
||||
m = pattern.matcher(xml);
|
||||
xml = m.replaceAll(" </");
|
||||
return xml;
|
||||
}
|
||||
|
||||
// 传入完整的 xml 文本,转换成 json 对象
|
||||
public static JSONObject xmlConvertJson(String xml) {
|
||||
if (StringUtils.isBlank(xml)) return null;
|
||||
xml = delXmlHeader(xml);
|
||||
if (xml == null) return null;
|
||||
if (stringToDocument(xml) == null) {
|
||||
LogUtils.error("xml内容转换失败!");
|
||||
return null;
|
||||
}
|
||||
Element node = stringToDocument(xml).getRootElement();
|
||||
JSONObject result = getJsonObjectByDC(node);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Document stringToDocument(String xml) {
|
||||
try {
|
||||
return XMLUtils.getDocument(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8.name())));
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static JSONObject getJsonObjectByDC(Element node) {
|
||||
JSONObject result = new JSONObject();
|
||||
List<Element> listElement = node.elements();// 所有一级子节点的list
|
||||
if (!listElement.isEmpty()) {
|
||||
List<JSONObject> list = new LinkedList<>();
|
||||
for (Element e : listElement) {// 遍历所有一级子节点
|
||||
JSONObject jsonObject = getJsonObjectByDC(e);
|
||||
//加xml标签上的属性 eg: <field length="2" scale="0" type="string">RB</field>
|
||||
//这里添加 length scale type
|
||||
if (!e.attributes().isEmpty()) {
|
||||
JSONObject attributeJson = new JSONObject();
|
||||
for (Attribute attribute : e.attributes()) {
|
||||
attributeJson.put(attribute.getName(), attribute.getValue());
|
||||
}
|
||||
jsonObject.append("attribute", attributeJson);
|
||||
}
|
||||
list.add(jsonObject);
|
||||
}
|
||||
if (list.size() == 1) {
|
||||
result.put(node.getName(), list.get(0));
|
||||
} else {
|
||||
result.put(node.getName(), list);
|
||||
}
|
||||
} else {
|
||||
if (!StringUtils.isAllBlank(node.getName(), node.getText())) {
|
||||
result.put(node.getName(), node.getText());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -117,6 +117,8 @@ public class ApiDefinitionControllerTests extends BaseTest {
|
|||
|
||||
private static final String ALL_API = "api_definition_module.api.all";
|
||||
private static final String UNPLANNED_API = "api_unplanned_request";
|
||||
|
||||
private static final String EXPORT = "/export/";
|
||||
private static ApiDefinition apiDefinition;
|
||||
|
||||
@Resource
|
||||
|
@ -1854,4 +1856,21 @@ public class ApiDefinitionControllerTests extends BaseTest {
|
|||
Assertions.assertNotNull(pageData);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(104)
|
||||
public void testExport() throws Exception {
|
||||
ApiDefinitionBatchRequest request = new ApiDefinitionBatchRequest();
|
||||
request.setProjectId(DEFAULT_PROJECT_ID);
|
||||
request.setProtocols(List.of("HTTP"));
|
||||
request.setSelectAll(false);
|
||||
request.setSelectIds(List.of("1001"));
|
||||
MvcResult mvcResult = this.requestPostWithOkAndReturn(EXPORT + "swagger", request);
|
||||
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
|
||||
// 返回请求正常
|
||||
Assertions.assertNotNull(resultHolder);
|
||||
Pager<?> pageData = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
|
||||
Assertions.assertNotNull(pageData);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,5 +19,8 @@ public class PropertyConstant {
|
|||
public final static String ITEMS = "items";
|
||||
public final static String PROPERTIES = "properties";
|
||||
public final static String ENABLE = "enable";
|
||||
public final static String MOCK = "mock";
|
||||
public final static String BODYTYPE = "bodyType";
|
||||
public final static String PARAMTYPE = "paramtype";
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import {
|
|||
DeleteRecycleApiUrl,
|
||||
DeleteRecycleCaseUrl,
|
||||
ExecuteCaseUrl,
|
||||
ExportDefinitionUrl,
|
||||
GetCaseDetailUrl,
|
||||
GetCaseReportByIdUrl,
|
||||
GetCaseReportDetailUrl,
|
||||
|
@ -101,6 +102,7 @@ import {
|
|||
ApiCasePageParams,
|
||||
ApiDefinitionBatchDeleteParams,
|
||||
ApiDefinitionBatchMoveParams,
|
||||
ApiDefinitionBatchParams,
|
||||
ApiDefinitionBatchUpdateParams,
|
||||
ApiDefinitionCreateParams,
|
||||
ApiDefinitionDeleteParams,
|
||||
|
@ -556,3 +558,8 @@ export function getReportById(id: string) {
|
|||
export function getCaseReportDetail(reportId: string, stepId: string) {
|
||||
return MSR.get<ApiCaseReportDetail[]>({ url: `${GetCaseReportDetailUrl + reportId}/${stepId}` });
|
||||
}
|
||||
|
||||
// 导出定义
|
||||
export function exportApiDefinition(data: ApiDefinitionBatchParams, type: string) {
|
||||
return MSR.post({ url: `${ExportDefinitionUrl}/${type}`, data });
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ export const TransferFileModuleOptionUrl = '/api/definition/transfer/options'; /
|
|||
export const UploadTempFileUrl = '/api/definition/upload/temp/file'; // 临时文件上传
|
||||
export const DeleteDefinitionUrl = '/api/definition/delete-to-gc'; // 删除接口定义
|
||||
export const ImportDefinitionUrl = '/api/definition/import'; // 导入接口定义
|
||||
export const ExportDefinitionUrl = '/api/definition/export'; // 导入接口定义
|
||||
export const SortDefinitionUrl = '/api/definition/edit/pos'; // 接口定义拖拽
|
||||
export const CopyDefinitionUrl = '/api/definition/copy'; // 复制接口定义
|
||||
export const BatchUpdateDefinitionUrl = '/api/definition/batch-update'; // 批量更新接口定义
|
||||
|
|
|
@ -276,6 +276,7 @@
|
|||
batchMoveDefinition,
|
||||
batchUpdateDefinition,
|
||||
deleteDefinition,
|
||||
exportApiDefinition,
|
||||
getDefinitionPage,
|
||||
sortDefinition,
|
||||
updateDefinition,
|
||||
|
@ -284,7 +285,7 @@
|
|||
import useModal from '@/hooks/useModal';
|
||||
import useTableStore from '@/hooks/useTableStore';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { characterLimit, operationWidth } from '@/utils';
|
||||
import { characterLimit, downloadByteFile, operationWidth } from '@/utils';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import { ProtocolItem } from '@/models/apiTest/common';
|
||||
|
@ -555,10 +556,16 @@
|
|||
);
|
||||
const batchActions = {
|
||||
baseAction: [
|
||||
// {
|
||||
// label: 'common.export',
|
||||
// eventTag: 'export',
|
||||
// },
|
||||
{
|
||||
label: 'common.export',
|
||||
eventTag: 'export',
|
||||
children: [
|
||||
{
|
||||
label: 'apiTestManagement.swagger.export',
|
||||
eventTag: 'exportSwagger',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'common.edit',
|
||||
eventTag: 'edit',
|
||||
|
@ -906,6 +913,28 @@
|
|||
selectedModuleKeys.value = keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出接口
|
||||
*/
|
||||
async function exportApi(type: string, record?: ApiDefinitionDetail, params?: BatchActionQueryParams) {
|
||||
const result = await exportApiDefinition(
|
||||
{
|
||||
selectIds: tableSelected.value as string[],
|
||||
selectAll: !!params?.selectAll,
|
||||
excludeIds: params?.excludeIds || [],
|
||||
condition: {
|
||||
keyword: keyword.value,
|
||||
filter: propsRes.value.filter,
|
||||
},
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: await getModuleIds(),
|
||||
protocols: props.selectedProtocols,
|
||||
},
|
||||
type
|
||||
);
|
||||
downloadByteFile(new Blob([JSON.stringify(result)]), 'Swagger_Api_Case.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理表格选中后批量操作
|
||||
* @param event 批量操作事件对象
|
||||
|
@ -914,6 +943,9 @@
|
|||
tableSelected.value = params?.selectedIds || [];
|
||||
batchParams.value = params;
|
||||
switch (event.eventTag) {
|
||||
case 'exportSwagger':
|
||||
exportApi('swagger', undefined, params);
|
||||
break;
|
||||
case 'delete':
|
||||
deleteApi(undefined, true, params);
|
||||
break;
|
||||
|
|
|
@ -80,6 +80,7 @@ export default {
|
|||
'apiTestManagement.moreSetting': 'More Settings',
|
||||
'apiTestManagement.importType': 'Import Type',
|
||||
'apiTestManagement.urlImport': 'URL Import',
|
||||
'apiTestManagement.swagger.export': 'Export Swagger3.0',
|
||||
'apiTestManagement.syncImportCase': 'Sync Import API Cases',
|
||||
'apiTestManagement.syncUpdateDirectory': 'Sync Update API Directory',
|
||||
'apiTestManagement.importSwaggerFileTip1': 'Supports Swagger 3.0 version JSON files,',
|
||||
|
|
|
@ -75,6 +75,7 @@ export default {
|
|||
'apiTestManagement.moreSetting': '更多设置',
|
||||
'apiTestManagement.importType': '导入方式',
|
||||
'apiTestManagement.urlImport': 'URL 导入',
|
||||
'apiTestManagement.swagger.export': '导出 Swagger3.0 格式',
|
||||
'apiTestManagement.syncImportCase': '同步导入接口用例',
|
||||
'apiTestManagement.syncUpdateDirectory': '同步更新接口所在目录',
|
||||
'apiTestManagement.importSwaggerFileTip1': '支持 Swagger 3.0 版本的 json 文件,',
|
||||
|
|
Loading…
Reference in New Issue