From 3e97f8da6c7663fdffb5266966b537f3171077ce Mon Sep 17 00:00:00 2001 From: Coooder-X <55648333+Coooder-X@users.noreply.github.com> Date: Tue, 30 Mar 2021 13:07:09 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8Dswagger=E5=AF=BC=E5=85=A5=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E5=80=BC=E7=BC=BA=E5=A4=B1=20&=20=E8=AF=B7=E6=B1=82=E4=BD=93?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E6=94=AF=E6=8C=81json=E3=80=81xml=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=20(#1751)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(测试跟踪): 测试用例下载模版增加标签列 * fix(接口定义): 扩大请求头键长度 * fix: schedule表对旧数据name字段兼容的补充 * fix(接口定义): 修复swagger导入一些值缺失 & 请求体导出支持json、xml格式 --- .../dto/definition/parse/Swagger3Parser.java | 84 +++++++++++- .../metersphere/commons/utils/XMLUtils.java | 120 ++++++++++++++++-- .../resources/i18n/messages_en_US.properties | 1 + .../resources/i18n/messages_zh_CN.properties | 1 + .../resources/i18n/messages_zh_TW.properties | 1 + 5 files changed, 192 insertions(+), 15 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger3Parser.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger3Parser.java index 5e96e31159..dd25a0c8b5 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger3Parser.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger3Parser.java @@ -26,12 +26,15 @@ import io.swagger.v3.oas.models.parameters.*; import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.responses.ApiResponses; import io.swagger.v3.parser.core.models.SwaggerParseResult; +import net.sf.saxon.ma.json.XMLToJsonFn; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; +import org.fife.ui.rsyntaxtextarea.parser.XmlParser; import org.springframework.http.HttpMethod; import java.io.InputStream; +import java.math.BigDecimal; import java.util.*; @@ -334,6 +337,16 @@ public class Swagger3Parser extends SwaggerAbstractParser { } else if (schema instanceof BinarySchema) { return getDefaultValueByPropertyType(schema); } else { + if(schema.getType() != null) { // 特判属性不是对象的情况,直接将基本类型赋值进去 + if(StringUtils.equals(schema.getType(), "string")) { + String example = (String) schema.getExample(); + return example == null ? "" : example; + } else if(StringUtils.equals(schema.getType(), "boolean")) { + return schema.getExample(); + } else if(StringUtils.equals(schema.getType(), "double")) { + return schema.getExample(); + } + } Object propertiesResult = parseSchemaProperties(schema, refSet, infoMap); return propertiesResult == null ? getDefaultValueByPropertyType(schema) : propertiesResult; } @@ -432,7 +445,9 @@ public class Swagger3Parser extends SwaggerAbstractParser { JSONObject requestBody = buildRequestBody(requestObject); swaggerApiInfo.setRequestBody(requestBody); // 设置响应体 - swaggerApiInfo.setResponses(new JSONObject()); +// JSONObject reponseObject = JSON.parseObject(apiDefinition.getResponse()); +// swaggerApiInfo.setResponses(buildResponseBody(reponseObject)); + //..... // 设置请求参数列表 List paramsList = buildParameters(requestObject); swaggerApiInfo.setParameters(paramsList); @@ -459,6 +474,9 @@ public class Swagger3Parser extends SwaggerAbstractParser { if(params != null) { for(int i = 0; i < params.size(); ++i) { JSONObject param = params.getJSONObject(i); // 对于每个参数: + if(param.get("name") == null || StringUtils.isEmpty(((String) param.get("name")))) { + continue; + } // 否则无参数的情况,可能多出一行空行 SwaggerParams swaggerParam = new SwaggerParams(); swaggerParam.setIn(typeMap.get(type)); // 利用 map,根据 request 的 key 设置对应的参数类型 swaggerParam.setDescription((String) param.get("description")); @@ -480,9 +498,20 @@ public class Swagger3Parser extends SwaggerAbstractParser { put("Form Data", org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE); put("WWW_FORM", org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE); }}; + JSONObject bodyInfo = new JSONObject(); + if(request.getJSONObject("body") != null && request.getJSONObject("body").containsKey("raw")) { + String bodyType = request.getJSONObject("body").getString("type"); + if(bodyType.equals("JSON")) { + bodyInfo = buildRequestBodyJsonInfo(request.getJSONObject("body").getJSONObject("raw")); + } else if(bodyType.equals("XML")) { + String xmlText = request.getJSONObject("body").getString("raw"); + JSONObject xmlToJson = XMLUtils.XmlToJson(xmlText); + bodyInfo = buildRequestBodyJsonInfo(xmlToJson); + } + } String type = request.getJSONObject("body").getString("type"); JSONObject requestBody = new JSONObject(); - JSONObject schema = new JSONObject(); + JSONObject schema = bodyInfo; // 需要转换导出 JSONObject typeName = new JSONObject(); schema.put("type", null); schema.put("format", null); @@ -494,4 +523,55 @@ public class Swagger3Parser extends SwaggerAbstractParser { requestBody.put("content", content); return requestBody; } + + // 将请求体中的一个 json 对象转换成 swagger 格式的 json 对象返回 + private JSONObject buildRequestBodyJsonInfo(JSONObject requestBody) { + JSONObject schema = new JSONObject(); + schema.put("type", "object"); + JSONObject properties = buildSchema(requestBody); + schema.put("properties", properties); + return schema; + } + + // 设置一个 json 对象的属性在 swagger 格式中的类型、值 + 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 java.lang.String) { + parsedParam.put("type", "string"); + parsedParam.put("example", param == null? "" : param); + } else if(param instanceof java.lang.Integer) { + parsedParam.put("type", "integer"); + parsedParam.put("format", "int64"); + parsedParam.put("example", param); + } else if(param instanceof JSONObject) { + parsedParam = buildRequestBodyJsonInfo((JSONObject) param); + } else if(param instanceof java.lang.Boolean) { + parsedParam.put("type", "boolean"); + parsedParam.put("example", param); + } else if(param instanceof java.math.BigDecimal) { // double 类型会被 fastJson 转换为 BigDecimal + parsedParam.put("type", "double"); + parsedParam.put("example", param); + } else { // JSONOArray + parsedParam.put("type", "array"); + JSONObject item = new JSONObject(); + if(((JSONArray) param).size() > 0) { + if(((JSONArray) param).get(0) instanceof JSONObject) { /// + item = buildRequestBodyJsonInfo((JSONObject) ((JSONArray) param).get(0)); + } + } + parsedParam.put("items", item); + } + schema.put(key, parsedParam); + } + return schema; + } + + private JSONObject buildResponseBody(JSONObject reponse) { + JSONObject responseBody = new JSONObject(); + + return responseBody; + } } diff --git a/backend/src/main/java/io/metersphere/commons/utils/XMLUtils.java b/backend/src/main/java/io/metersphere/commons/utils/XMLUtils.java index 10eb61872c..f8ab892f8c 100644 --- a/backend/src/main/java/io/metersphere/commons/utils/XMLUtils.java +++ b/backend/src/main/java/io/metersphere/commons/utils/XMLUtils.java @@ -2,46 +2,140 @@ package io.metersphere.commons.utils; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import io.metersphere.commons.exception.MSException; +import io.metersphere.i18n.Translator; import org.apache.commons.lang3.StringUtils; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.regex.*; public class XMLUtils { - private static void jsonToXmlStr(JSONObject jObj, StringBuffer buffer) { + private static void jsonToXmlStr(JSONObject jObj, StringBuffer buffer, StringBuffer tab) { Set> se = jObj.entrySet(); + StringBuffer nowTab = new StringBuffer(tab.toString()); for (Map.Entry en : se) { if ("com.alibaba.fastjson.JSONObject".equals(en.getValue().getClass().getName())) { - buffer.append("<").append(en.getKey()).append(">"); + buffer.append(tab).append("<").append(en.getKey()).append(">\n"); JSONObject jo = jObj.getJSONObject(en.getKey()); - jsonToXmlStr(jo, buffer); - buffer.append(""); + jsonToXmlStr(jo, buffer, nowTab.append("\t")); + buffer.append(tab).append("\n"); } else if ("com.alibaba.fastjson.JSONArray".equals(en.getValue().getClass().getName())) { JSONArray jarray = jObj.getJSONArray(en.getKey()); for (int i = 0; i < jarray.size(); i++) { - buffer.append("<").append(en.getKey()).append(">"); + buffer.append(tab).append("<").append(en.getKey()).append(">\n"); if (StringUtils.isNotBlank(jarray.getString(i))) { JSONObject jsonobject = jarray.getJSONObject(i); - jsonToXmlStr(jsonobject, buffer); - buffer.append(""); + jsonToXmlStr(jsonobject, buffer, nowTab.append("\t")); + buffer.append(tab).append("\n"); } } } else if ("java.lang.String".equals(en.getValue().getClass().getName())) { - buffer.append("<").append(en.getKey()).append(">").append(en.getValue()); - buffer.append(""); + buffer.append(tab).append("<").append(en.getKey()).append(">").append(en.getValue()); + buffer.append("\n"); } } } public static String jsonToXmlStr(JSONObject jObj) { StringBuffer buffer = new StringBuffer(); - buffer.append(""); + buffer.append("\n"); try { - jsonToXmlStr(jObj, buffer); + jsonToXmlStr(jObj, buffer, new StringBuffer("")); } catch (Exception e) { LogUtil.error(e.getMessage(), e); } return buffer.toString(); } + + // 传入完整的 xml 文本,转换成 json 对象 + public static JSONObject XmlToJson(String xml) { + JSONObject result = new JSONObject(); + List list = preProcessXml(xml); + try { + result = (JSONObject) XmlTagToJsonObject(list); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(Translator.get("illegal_xml_format")); + } + return result; + } + + // 预处理 xml 文本,转换成 tag + data 的列表 + private static List preProcessXml(String xml) { + int begin = xml.indexOf("?>"); + if(begin != -1) { + if(begin + 2 >= xml.length()) { + return null; + } + xml = xml.substring(begin + 2); + } // 若存在,则去除 + String rgex = "\\s*"; + Pattern pattern = Pattern.compile(rgex); + Matcher m = pattern.matcher(xml); + xml = m.replaceAll(""); + rgex = ">"; + pattern = Pattern.compile(rgex); + m = pattern.matcher(xml); + xml = m.replaceAll("> "); + rgex = "\\s* list) { + if(list == null || list.size() == 0) + return null; + Stack tagStack = new Stack<>(); // tag 栈 + Stack valueStack = new Stack<>(); // 数据栈 + valueStack.push(new JSONObject()); // 最终结果将存放在第一个入栈的元素中 + for(String item : list) { + String beginTag = isBeginTag(item), endTag = isEndTag(item); // 判断当前 tag 是开始还是结尾 + if(beginTag != null) { + tagStack.push(beginTag); + valueStack.push(new JSONObject()); + } else if(endTag != null) { + if(endTag.equals(tagStack.peek())) { // 是一对 tag + Object topValue = valueStack.peek(); + if(topValue instanceof String) { // 栈顶是纯数据 xml 节点 + valueStack.pop(); + } + valueStack.pop(); + if(valueStack.peek() instanceof JSONObject) { + ((JSONObject) valueStack.peek()).put(tagStack.peek(), topValue); + } + tagStack.pop(); + } + } else { + valueStack.push(item); + } + } + if(valueStack.empty()) + return null; + return valueStack.peek(); + } + + private static String isEndTag(String tagLine) { + String rgex = ""; + Pattern pattern = Pattern.compile(rgex);// 匹配的模式     + Matcher m = pattern.matcher(tagLine); + if (m.find()) { + return m.group(1); + } + return null; + } + + private static String isBeginTag(String tagLine) { + String rgex = "<(\\w*)>"; + Pattern pattern = Pattern.compile(rgex);// 匹配的模式     + Matcher m = pattern.matcher(tagLine); + if (m.find()) { + return m.group(1); + } + return null; + } + } diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index 7983790370..ecb733d2cd 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -75,6 +75,7 @@ organization_does_not_belong_to_user=The current organization does not belong to organization_id_is_null=Organization ID cannot be null #api api_load_script_error=Load script error +illegal_xml_format=illegal XML format api_report_is_null="Report is null, can't update" api_test_environment_already_exists="Api test environment already exists" api_test=API Test diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index 9d335a49e3..ef493c103e 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -75,6 +75,7 @@ organization_does_not_belong_to_user=当前组织不属于当前用户 organization_id_is_null=组织 ID 不能为空 #api api_load_script_error=读取脚本失败 +illegal_xml_format=不合法的 XML 格式 api_report_is_null="测试报告是未生成,无法更新" api_test_environment_already_exists="已存在该名称的环境配置" api_test=接口测试 diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties index 6cab6f1a81..85650bc3c6 100644 --- a/backend/src/main/resources/i18n/messages_zh_TW.properties +++ b/backend/src/main/resources/i18n/messages_zh_TW.properties @@ -75,6 +75,7 @@ organization_does_not_belong_to_user=當前組織不屬於當前用戶 organization_id_is_null=組織 ID 不能為空 #api api_load_script_error=讀取腳本失敗 +illegal_xml_format=不合法的 XML 格式 api_report_is_null="測試報告是未生成,無法更新" api_test_environment_already_exists="已存在該名稱的環境配置" api_test=接口測試