From 561c9fea715490b551dee8b418e054547f5c4ee7 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 1 Feb 2021 14:35:53 +0800 Subject: [PATCH 01/23] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96):=20=E6=B7=BB=E5=8A=A0=E5=AF=BC=E5=85=A5=E5=AF=BC?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../automation/scenario/ApiScenarioModule.vue | 50 ++- .../scenario/common/ScenarioImport.vue | 299 ++++++++++++++++++ frontend/src/i18n/en-US.js | 2 + frontend/src/i18n/zh-CN.js | 6 +- frontend/src/i18n/zh-TW.js | 2 + 5 files changed, 353 insertions(+), 6 deletions(-) create mode 100644 frontend/src/business/components/api/automation/scenario/common/ScenarioImport.vue diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue index 0e1401ebc6..8d6e06a246 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue @@ -15,11 +15,28 @@ ref="nodeTree"> @@ -29,8 +46,9 @@ @saveAsEdit="saveAsEdit" @refresh="refresh" ref="basisScenario"/> - + + + + diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index b57556fa94..26fa648f50 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -839,6 +839,8 @@ export default { postman_tip: "Only Postman Collection V2.1 json files are supported", postman_export_tip: "Export the test collection by Postman", swagger_export_tip: "Export jSON-formatted files via Swagger website", + jmeter_export_tip: "Generating JMX file through JMeter", + jmeter_tip: "JMX files supporting JMeter 5.2", suffixFormatErr: "The file format does not meet the requirements", swagger_url_import: "Import using URL", timing_synchronization: "Timing synchronization", diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index 01c88a49b7..e1ee5aa355 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -1,7 +1,7 @@ export default { commons: { - cover:'覆盖', - not_cover:'不覆盖', + cover: '覆盖', + not_cover: '不覆盖', import_mode: '导入模式', import_module: '导入模块', please_fill_in_the_template: '请填写模版内容', @@ -842,6 +842,8 @@ export default { swagger_tip: "支持 Swagger 2.0 与 3.0 版本的 json 文件", post_export_tip: "通过 Postman 导出测试集合", swagger_export_tip: "通过 Swagger 页面导出", + jmeter_export_tip: "通过 Jmeter 生成JMX文件", + jmeter_tip: "支持 Jmeter 5.2版本的JMX 文件", suffixFormatErr: "文件格式不符合要求", swagger_url_import: "使用URL导入", timing_synchronization: "定时同步", diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index 6d81c01325..800d669171 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -841,6 +841,8 @@ export default { swagger_tip: "支持 Swagger 2.0 與 3.0 版本的 json 文件", post_export_tip: "通過 Postman 導出測試集合", swagger_export_tip: "通過 Swagger 頁面導出", + jmeter_export_tip: "通過 Jmeter 生成JMX文件", + jmeter_tip: "支持 Jmeter 5.2版本的JMX 文件", suffixFormatErr: "文件格式不符合要求", swagger_url_import: "使用URL導入", timing_synchronization: "定時同步", From c74e88c3f35ded84389672565d2519e02146ec94 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 2 Feb 2021 17:46:00 +0800 Subject: [PATCH 02/23] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E5=9C=BA=E6=99=AF=E5=AF=BC=E5=85=A5=20m?= =?UTF-8?q?s=EF=BC=8Cpostman=20=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ApiAutomationController.java | 17 +- .../dto/automation/parse/MsPostmanParser.java | 205 ++++++++++++++++++ .../automation/parse/MsScenarioParser.java | 65 ++++++ .../dto/automation/parse/ScenarioImport.java | 12 + .../parse/ScenarioImportAbstractParser.java | 169 +++++++++++++++ .../parse/ScenarioImportParser.java | 9 + .../parse/ScenarioImportParserFactory.java | 18 ++ .../request/variable/ScenarioVariable.java | 7 + .../api/service/ApiAutomationService.java | 122 ++++++++++- .../api/service/ApiScenarioModuleService.java | 27 +++ .../api/automation/ApiAutomation.vue | 38 +++- .../automation/scenario/ApiScenarioModule.vue | 1 + .../scenario/common/ScenarioImport.vue | 19 +- 13 files changed, 689 insertions(+), 20 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/api/dto/automation/parse/MsPostmanParser.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/automation/parse/MsScenarioParser.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImport.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportAbstractParser.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParser.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParserFactory.java diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java index c23f768f05..85b20fa4e4 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java @@ -2,14 +2,15 @@ package io.metersphere.api.controller; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; +import io.metersphere.api.dto.ApiTestImportRequest; import io.metersphere.api.dto.JmxInfoDTO; import io.metersphere.api.dto.automation.*; +import io.metersphere.api.dto.automation.parse.ScenarioImport; import io.metersphere.api.dto.definition.RunDefinitionRequest; import io.metersphere.api.service.ApiAutomationService; import io.metersphere.base.domain.ApiScenario; import io.metersphere.base.domain.ApiScenarioWithBLOBs; import io.metersphere.base.domain.Schedule; -import io.metersphere.commons.constants.ApiRunMode; import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.Pager; @@ -43,6 +44,13 @@ public class ApiAutomationController { return PageUtils.setPageInfo(page, apiAutomationService.list(request)); } + @PostMapping("/list/all") + @RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) + public List list(@RequestBody ApiScenarioRequest request) { + request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); + return apiAutomationService.get(request); + } + @PostMapping(value = "/create") public ApiScenario create(@RequestPart("request") SaveApiScenarioRequest request, @RequestPart(value = "files") List bodyFiles) { return apiAutomationService.create(request, bodyFiles); @@ -146,5 +154,12 @@ public class ApiAutomationController { .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileOperationRequest.getName() + "\"") .body(bytes); } + + @PostMapping(value = "/import", consumes = {"multipart/form-data"}) + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public ScenarioImport scenarioImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) { + return apiAutomationService.scenarioImport(file, request); + } + } diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsPostmanParser.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsPostmanParser.java new file mode 100644 index 0000000000..f88bfc339d --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsPostmanParser.java @@ -0,0 +1,205 @@ +package io.metersphere.api.dto.automation.parse; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import io.metersphere.api.dto.ApiTestImportRequest; +import io.metersphere.api.dto.definition.request.MsScenario; +import io.metersphere.api.dto.definition.request.MsTestElement; +import io.metersphere.api.dto.definition.request.processors.pre.MsJSR223PreProcessor; +import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; +import io.metersphere.api.dto.definition.request.variable.ScenarioVariable; +import io.metersphere.api.dto.parse.postman.*; +import io.metersphere.api.dto.scenario.Body; +import io.metersphere.api.dto.scenario.KeyValue; +import io.metersphere.base.domain.ApiScenarioModule; +import io.metersphere.base.domain.ApiScenarioWithBLOBs; +import io.metersphere.commons.constants.MsRequestBodyType; +import io.metersphere.commons.constants.PostmanRequestBodyMode; +import io.metersphere.commons.constants.VariableTypeConstants; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class MsPostmanParser extends ScenarioImportAbstractParser { + + @Override + public ScenarioImport parse(InputStream source, ApiTestImportRequest request) { + String testStr = getApiTestStr(source); + PostmanCollection postmanCollection = JSON.parseObject(testStr, PostmanCollection.class); + List variables = postmanCollection.getVariable(); + ScenarioImport scenarioImport = new ScenarioImport(); + // 场景步骤 + LinkedList apiScenarioWithBLOBs = new LinkedList<>(); + PostmanCollectionInfo info = postmanCollection.getInfo(); + ApiScenarioWithBLOBs scenario = new ApiScenarioWithBLOBs(); + scenario.setName(info.getName()); + + MsScenario msScenario = new MsScenario(); + msScenario.setName(info.getName()); + this.projectId = request.getProjectId(); + parseItem(postmanCollection.getItem(), variables, msScenario, apiScenarioWithBLOBs); + // 生成场景对象 + List scenarioWithBLOBs = new LinkedList<>(); + paseScenario(scenarioWithBLOBs, msScenario, request); + scenarioImport.setData(scenarioWithBLOBs); + return scenarioImport; + } + + private void paseScenario(List scenarioWithBLOBsList, MsScenario msScenario, ApiTestImportRequest request) { + ApiScenarioModule module = buildModule(getSelectModule(request.getModuleId()), msScenario.getName()); + ApiScenarioWithBLOBs scenarioWithBLOBs = new ApiScenarioWithBLOBs(); + scenarioWithBLOBs.setName(msScenario.getName()); + scenarioWithBLOBs.setProjectId(request.getProjectId()); + if (msScenario != null && CollectionUtils.isNotEmpty(msScenario.getHashTree())) { + scenarioWithBLOBs.setStepTotal(msScenario.getHashTree().size()); + } + if (module != null) { + scenarioWithBLOBs.setApiScenarioModuleId(module.getId()); + scenarioWithBLOBs.setModulePath("/" + module.getName()); + } + scenarioWithBLOBs.setId(UUID.randomUUID().toString()); + scenarioWithBLOBs.setScenarioDefinition(JSON.toJSONString(msScenario)); + scenarioWithBLOBsList.add(scenarioWithBLOBs); + } + + private void parseItem(List items, List variables, MsScenario scenario, LinkedList results) { + for (PostmanItem item : items) { + List childItems = item.getItem(); + if (childItems != null) { + parseItem(childItems, variables, scenario, results); + } else { + MsHTTPSamplerProxy request = parsePostman(item); + if (request != null) { + results.add(request); + } + } + } + scenario.setVariables(parseScenarioVariable(variables)); + scenario.setHashTree(results); + } + + private MsHTTPSamplerProxy parsePostman(PostmanItem requestItem) { + PostmanRequest requestDesc = requestItem.getRequest(); + if (requestDesc == null) { + return null; + } + requestDesc.getAuth(); // todo 认证方式等待优化 + PostmanUrl url = requestDesc.getUrl(); + MsHTTPSamplerProxy request = buildRequest(requestItem.getName(), url.getRaw(), requestDesc.getMethod()); + if (StringUtils.isNotBlank(request.getPath())) { + String path = request.getPath().split("\\?")[0]; + path = path.replace("{{", "${"); + path = path.replace("}}", "}"); + request.setPath(path); + } else { + request.setPath("/"); + } + parseBody(request.getBody(), requestDesc); + request.setArguments(parseKeyValue(url.getQuery())); + request.setHeaders(parseKeyValue(requestDesc.getHeader())); + addBodyHeader(request); + addPreScript(request, requestItem.getEvent()); + return request; + } + + private void addPreScript(MsHTTPSamplerProxy request, List event) { + if (request != null && CollectionUtils.isNotEmpty(event)) { + StringBuilder scriptStr = new StringBuilder(); + event = event.stream() + .filter(item -> item.getScript() != null) + .collect(Collectors.toList()); + event.forEach(item -> { + PostmanScript script = item.getScript(); + if (script != null) { + List exec = script.getExec(); + if (CollectionUtils.isNotEmpty(exec)) { + exec.forEach(col -> { + if (StringUtils.isNotEmpty(col)) { + scriptStr.append(col + "/n"); + } + }); + } + } + }); + if (StringUtils.isNotBlank(scriptStr)) { + MsJSR223PreProcessor jsr223PreProcessor = new MsJSR223PreProcessor(); + jsr223PreProcessor.setName("JSR223PreProcessor"); + jsr223PreProcessor.setScriptLanguage("javascript"); + jsr223PreProcessor.setScript(scriptStr.toString()); + LinkedList hashTree = new LinkedList<>(); + hashTree.add(jsr223PreProcessor); + request.setHashTree(hashTree); + } + } + } + + private List parseKeyValue(List postmanKeyValues) { + if (postmanKeyValues == null) { + return null; + } + List keyValues = new ArrayList<>(); + postmanKeyValues.forEach(item -> keyValues.add(new KeyValue(item.getKey(), item.getValue(), item.getDescription(), item.getContentType()))); + return keyValues; + } + + private List parseScenarioVariable(List postmanKeyValues) { + if (postmanKeyValues == null) { + return null; + } + List keyValues = new ArrayList<>(); + postmanKeyValues.forEach(item -> keyValues.add(new ScenarioVariable(item.getKey(), item.getValue(), item.getDescription(), VariableTypeConstants.CONSTANT.name()))); + return keyValues; + } + + private void parseBody(Body body, PostmanRequest requestDesc) { + JSONObject postmanBody = requestDesc.getBody(); + if (postmanBody == null) { + return; + } + String bodyMode = postmanBody.getString("mode"); + if (StringUtils.isNotEmpty(bodyMode) && StringUtils.equals(bodyMode, PostmanRequestBodyMode.RAW.value())) { + parseRawBody(body, postmanBody, bodyMode); + } else if (StringUtils.isNotEmpty(bodyMode) && StringUtils.equalsAny(bodyMode, PostmanRequestBodyMode.FORM_DATA.value(), PostmanRequestBodyMode.URLENCODED.value())) { + List postmanKeyValues = JSON.parseArray(postmanBody.getString(bodyMode), PostmanKeyValue.class); + body.setKvs(parseKeyValue(postmanKeyValues)); + if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FORM_DATA.value())) { + body.setType(Body.FORM_DATA); + } else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.URLENCODED.value())) { + body.setType(Body.WWW_FROM); + } + } else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FILE.value())) { + body.setType(Body.BINARY); + body.setKvs(new ArrayList<>()); + } + } + + private void parseRawBody(Body body, JSONObject postmanBody, String bodyMode) { + body.setRaw(postmanBody.getString(bodyMode)); + body.setType(MsRequestBodyType.RAW.value()); + JSONObject options = postmanBody.getJSONObject("options"); + if (options != null) { + JSONObject raw = options.getJSONObject(PostmanRequestBodyMode.RAW.value()); + if (raw != null && raw.getString("language") != null) { + String bodyType = ""; + switch (raw.getString("language")) { + case "json": + bodyType = Body.JSON; + break; + case "xml": + bodyType = Body.XML; + break; + default: + bodyType = Body.RAW; + } + body.setType(bodyType); + } + } + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsScenarioParser.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsScenarioParser.java new file mode 100644 index 0000000000..b3b5104971 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsScenarioParser.java @@ -0,0 +1,65 @@ +package io.metersphere.api.dto.automation.parse; + +import com.alibaba.fastjson.JSON; +import io.metersphere.api.dto.ApiTestImportRequest; +import io.metersphere.base.domain.ApiScenarioModule; +import io.metersphere.base.domain.ApiScenarioWithBLOBs; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +public class MsScenarioParser extends ScenarioImportAbstractParser { + + @Override + public ScenarioImport parse(InputStream source, ApiTestImportRequest request) { + String testStr = getApiTestStr(source); + this.projectId = request.getProjectId(); + ScenarioImport scenarioImport = parseMsFormat(testStr, request); + return scenarioImport; + } + + private ScenarioImport parseMsFormat(String testStr, ApiTestImportRequest importRequest) { + ScenarioImport apiDefinitionImport = JSON.parseObject(testStr, ScenarioImport.class); + List data = apiDefinitionImport.getData(); + if (CollectionUtils.isNotEmpty(data)) { + data.forEach(item -> { + if (StringUtils.isBlank(item.getModulePath())) { + item.setApiScenarioModuleId(null); + } + parseModule(item, importRequest); + item.setId(UUID.randomUUID().toString()); + item.setProjectId(this.projectId); + }); + } + return apiDefinitionImport; + } + + private void parseModule(ApiScenarioWithBLOBs apiDefinition, ApiTestImportRequest importRequest) { + String modulePath = apiDefinition.getModulePath(); + if (StringUtils.isEmpty(modulePath)) { + return; + } + if (modulePath.startsWith("/")) { + modulePath = modulePath.substring(1, modulePath.length()); + } + if (modulePath.endsWith("/")) { + modulePath = modulePath.substring(0, modulePath.length() - 1); + } + List modules = Arrays.asList(modulePath.split("/")); + ApiScenarioModule parent = getSelectModule(importRequest.getModuleId()); + Iterator iterator = modules.iterator(); + while (iterator.hasNext()) { + String item = iterator.next(); + parent = buildModule(parent, item); + if (!iterator.hasNext()) { + apiDefinition.setApiScenarioModuleId(parent.getId()); + } + } + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImport.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImport.java new file mode 100644 index 0000000000..81b6c08139 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImport.java @@ -0,0 +1,12 @@ +package io.metersphere.api.dto.automation.parse; + +import io.metersphere.base.domain.ApiScenarioWithBLOBs; +import lombok.Data; + +import java.util.List; + +@Data +public class ScenarioImport { + private String projectid; + private List data; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportAbstractParser.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportAbstractParser.java new file mode 100644 index 0000000000..3ba5bc40ac --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportAbstractParser.java @@ -0,0 +1,169 @@ +package io.metersphere.api.dto.automation.parse; + +import io.metersphere.api.dto.automation.ApiScenarioModuleDTO; +import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; +import io.metersphere.api.dto.scenario.Body; +import io.metersphere.api.dto.scenario.KeyValue; +import io.metersphere.api.dto.scenario.request.RequestType; +import io.metersphere.api.service.ApiScenarioModuleService; +import io.metersphere.base.domain.ApiScenarioModule; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.BeanUtils; +import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.LogUtil; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public abstract class ScenarioImportAbstractParser implements ScenarioImportParser { + protected ApiScenarioModuleService apiModuleService; + protected String projectId; + + protected ApiScenarioModule getSelectModule(String moduleId) { + apiModuleService = CommonBeanFactory.getBean(ApiScenarioModuleService.class); + if (StringUtils.isNotBlank(moduleId) && !StringUtils.equals("root", moduleId)) { + ApiScenarioModule module = new ApiScenarioModule(); + ApiScenarioModuleDTO moduleDTO = apiModuleService.getNode(moduleId); + if (moduleDTO != null) { + BeanUtils.copyBean(module, moduleDTO); + } + return module; + } + return null; + } + + protected ApiScenarioModule buildModule(ApiScenarioModule parentModule, String name) { + apiModuleService = CommonBeanFactory.getBean(ApiScenarioModuleService.class); + ApiScenarioModule module; + if (parentModule != null) { + module = apiModuleService.getNewModule(name, this.projectId, parentModule.getLevel() + 1); + module.setParentId(parentModule.getId()); + } else { + module = apiModuleService.getNewModule(name, this.projectId, 1); + } + createModule(module); + return module; + } + + protected void createModule(ApiScenarioModule module) { + List apiModules = apiModuleService.selectSameModule(module); + if (CollectionUtils.isEmpty(apiModules)) { + apiModuleService.addNode(module); + } else { + module.setId(apiModules.get(0).getId()); + } + } + + 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); + } + } catch (Exception e) { + MSException.throwException(e.getMessage()); + LogUtil.error(e.getMessage(), e); + } finally { + try { + if (source != null) { + source.close(); + } + } catch (IOException e) { + MSException.throwException(e.getMessage()); + LogUtil.error(e.getMessage(), e); + } + } + return testStr.toString(); + } + + private String formatPath(String url) { + try { + URL urlObject = new URL(url); + StringBuilder pathBuffer = new StringBuilder(urlObject.getPath()); + if (StringUtils.isNotEmpty(urlObject.getQuery())) { + pathBuffer.append("?").append(urlObject.getQuery()); + } + return pathBuffer.toString(); + } catch (Exception ex) { + return url; + } + } + + protected MsHTTPSamplerProxy buildRequest(String name, String path, String method) { + MsHTTPSamplerProxy request = new MsHTTPSamplerProxy(); + request.setName(name); + // 路径去掉域名/IP 地址,保留方法名称及参数 + request.setPath(formatPath(path)); + request.setMethod(method); + request.setProtocol(RequestType.HTTP); + request.setId(UUID.randomUUID().toString()); + request.setHeaders(new ArrayList<>()); + request.setArguments(new ArrayList<>()); + request.setRest(new ArrayList<>()); + request.setAuthManager(null); + Body body = new Body(); + body.initKvs(); + body.initBinary(); + request.setBody(body); + return request; + } + + protected void addHeader(List headers, String key, String value, String description, String contentType, boolean required) { + boolean hasContentType = false; + for (KeyValue header : headers) { + if (StringUtils.equalsIgnoreCase(header.getName(), key)) { + hasContentType = true; + } + } + if (!hasContentType) { + headers.add(new KeyValue(key, value, description, contentType, required)); + } + } + + protected void addHeader(List headers, String key, String value) { + addHeader(headers, key, value, "", "", true); + } + + protected void addContentType(List headers, String contentType) { + addHeader(headers, "Content-Type", contentType); + } + + protected void addBodyHeader(MsHTTPSamplerProxy request) { + String contentType = ""; + if (request.getBody() != null && StringUtils.isNotBlank(request.getBody().getType())) { + switch (request.getBody().getType()) { + case Body.JSON: + contentType = "application/json"; + break; + case Body.WWW_FROM: + contentType = "application/x-www-form-urlencoded"; + break; + case Body.XML: + contentType = "application/xml"; + break; + case Body.BINARY: + contentType = "application/octet-stream"; + break; + } + List headers = request.getHeaders(); + if (headers == null) { + headers = new ArrayList<>(); + request.setHeaders(headers); + } + addContentType(request.getHeaders(), contentType); + + } + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParser.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParser.java new file mode 100644 index 0000000000..4586440473 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParser.java @@ -0,0 +1,9 @@ +package io.metersphere.api.dto.automation.parse; + +import io.metersphere.api.dto.ApiTestImportRequest; + +import java.io.InputStream; + +public interface ScenarioImportParser { + ScenarioImport parse(InputStream source, ApiTestImportRequest request); +} diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParserFactory.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParserFactory.java new file mode 100644 index 0000000000..f425fd1dd0 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParserFactory.java @@ -0,0 +1,18 @@ +package io.metersphere.api.dto.automation.parse; + +import io.metersphere.commons.constants.ApiImportPlatform; +import org.apache.commons.lang3.StringUtils; + +public class ScenarioImportParserFactory { + public static ScenarioImportParser getImportParser(String platform) { + if (StringUtils.equals(ApiImportPlatform.Metersphere.name(), platform)) { + return new MsScenarioParser(); + } else if (StringUtils.equals(ApiImportPlatform.Postman.name(), platform)) { + return new MsPostmanParser(); + } +// else if (StringUtils.equals(ApiImportPlatform.Swagger2.name(), platform)) { +// return new Swagger2Parser(); +// } + return null; + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/variable/ScenarioVariable.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/variable/ScenarioVariable.java index 417711dfa2..9333699bc6 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/variable/ScenarioVariable.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/variable/ScenarioVariable.java @@ -40,6 +40,13 @@ public class ScenarioVariable { private String minNumber; private String maxNumber; + public ScenarioVariable(String key, String value, String description, String type) { + this.name = key; + this.value = value; + this.description = description; + this.type = type; + } + public boolean isConstantValid() { if (StringUtils.equals(this.type, VariableTypeConstants.CONSTANT.name()) && StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(value)) { return true; diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index 7f15aff6e6..f9d1779d31 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -5,9 +5,13 @@ import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import io.metersphere.api.dto.ApiTestImportRequest; import io.metersphere.api.dto.DeleteAPIReportRequest; import io.metersphere.api.dto.JmxInfoDTO; import io.metersphere.api.dto.automation.*; +import io.metersphere.api.dto.automation.parse.ScenarioImport; +import io.metersphere.api.dto.automation.parse.ScenarioImportParser; +import io.metersphere.api.dto.automation.parse.ScenarioImportParserFactory; import io.metersphere.api.dto.datacount.ApiDataCountResult; import io.metersphere.api.dto.definition.RunDefinitionRequest; import io.metersphere.api.dto.definition.request.*; @@ -86,23 +90,34 @@ public class ApiAutomationService { private TestPlanScenarioCaseService testPlanScenarioCaseService; public List list(ApiScenarioRequest request) { - request = this.initRequest(request,true,true); + request = this.initRequest(request, true, true); List list = extApiScenarioMapper.list(request); return list; } + public List get(ApiScenarioRequest request) { + ApiScenarioExample example = new ApiScenarioExample(); + if (CollectionUtils.isNotEmpty(request.getIds())) { + example.createCriteria().andIdIn(request.getIds()); + } else { + example.createCriteria().andProjectIdEqualTo(request.getProjectId()); + } + return apiScenarioMapper.selectByExampleWithBLOBs(example); + } + /** * 初始化部分参数 + * * @param request * @param setDefultOrders * @param checkThisWeekData * @return */ - private ApiScenarioRequest initRequest(ApiScenarioRequest request,boolean setDefultOrders, boolean checkThisWeekData) { - if(setDefultOrders){ + private ApiScenarioRequest initRequest(ApiScenarioRequest request, boolean setDefultOrders, boolean checkThisWeekData) { + if (setDefultOrders) { request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders())); } - if(checkThisWeekData){ + if (checkThisWeekData) { if (request.isSelectThisWeedData()) { Map weekFirstTimeAndLastTime = DateUtils.getWeedFirstTimeAndLastTime(new Date()); Date weekFirstTime = weekFirstTimeAndLastTime.get("firstTime"); @@ -217,7 +232,7 @@ public class ApiAutomationService { ids.add(scenarioId); deleteApiScenarioReport(ids); - scheduleService.deleteScheduleAndJobByResourceId(scenarioId,ScheduleGroup.API_SCENARIO_TEST.name()); + scheduleService.deleteScheduleAndJobByResourceId(scenarioId, ScheduleGroup.API_SCENARIO_TEST.name()); TestPlanApiScenarioExample example = new TestPlanApiScenarioExample(); example.createCriteria().andApiScenarioIdEqualTo(scenarioId); List testPlanApiScenarioList = testPlanApiScenarioMapper.selectByExample(example); @@ -284,7 +299,7 @@ public class ApiAutomationService { extApiScenarioMapper.removeToGc(apiIds); //将这些场景的定时任务删除掉 for (String id : apiIds) { - scheduleService.deleteScheduleAndJobByResourceId(id,ScheduleGroup.API_SCENARIO_TEST.name()); + scheduleService.deleteScheduleAndJobByResourceId(id, ScheduleGroup.API_SCENARIO_TEST.name()); } } @@ -412,14 +427,14 @@ public class ApiAutomationService { // 创建场景报告 if (reportIds != null) { //如果是测试计划页面触发的执行方式,生成报告时createScenarioReport第二个参数需要特殊处理 - if(StringUtils.equals(request.getRunMode(),ApiRunMode.SCENARIO_PLAN.name())){ + if (StringUtils.equals(request.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) { String testPlanScenarioId = item.getId(); - if(request.getScenarioTestPlanIdMap()!=null&&request.getScenarioTestPlanIdMap().containsKey(item.getId())){ + if (request.getScenarioTestPlanIdMap() != null && request.getScenarioTestPlanIdMap().containsKey(item.getId())) { testPlanScenarioId = request.getScenarioTestPlanIdMap().get(item.getId()); } createScenarioReport(group.getName(), testPlanScenarioId, item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(), request.getExecuteType(), item.getProjectId(), request.getReportUserID()); - }else{ + } else { createScenarioReport(group.getName(), item.getId(), item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(), request.getExecuteType(), item.getProjectId(), request.getReportUserID()); } @@ -635,10 +650,10 @@ public class ApiAutomationService { } private void addOrUpdateApiScenarioCronJob(Schedule request) { - if(StringUtils.equals(request.getGroup(),ScheduleGroup.TEST_PLAN_TEST.name())){ + if (StringUtils.equals(request.getGroup(), ScheduleGroup.TEST_PLAN_TEST.name())) { scheduleService.addOrUpdateCronJob( request, TestPlanTestJob.getJobKey(request.getResourceId()), TestPlanTestJob.getTriggerKey(request.getResourceId()), TestPlanTestJob.class); - }else{ + } else { scheduleService.addOrUpdateCronJob( request, ApiScenarioTestJob.getJobKey(request.getResourceId()), ApiScenarioTestJob.getTriggerKey(request.getResourceId()), ApiScenarioTestJob.class); } @@ -712,4 +727,89 @@ public class ApiAutomationService { }); } } + + public List getWithBLOBs(ApiScenarioWithBLOBs request) { + ApiScenarioExample example = new ApiScenarioExample(); + example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId()).andStatusNotEqualTo("Trash").andIdNotEqualTo(request.getId()); + return apiScenarioMapper.selectByExampleWithBLOBs(example); + } + + private void _importCreate(List sameRequest, ApiScenarioMapper batchMapper, ApiScenarioWithBLOBs scenarioWithBLOBs, ApiTestImportRequest apiTestImportRequest) { + if (CollectionUtils.isEmpty(sameRequest)) { + scenarioWithBLOBs.setId(UUID.randomUUID().toString()); + batchMapper.insert(scenarioWithBLOBs); + } else { + //如果存在则修改 + scenarioWithBLOBs.setId(sameRequest.get(0).getId()); + batchMapper.updateByPrimaryKeyWithBLOBs(scenarioWithBLOBs); + } + } + + private ApiScenarioWithBLOBs importCreate(ApiScenarioWithBLOBs request, ApiScenarioMapper batchMapper, ApiTestImportRequest apiTestImportRequest) { + final ApiScenarioWithBLOBs scenarioWithBLOBs = new ApiScenarioWithBLOBs(); + BeanUtils.copyBean(scenarioWithBLOBs, request); + scenarioWithBLOBs.setCreateTime(System.currentTimeMillis()); + scenarioWithBLOBs.setUpdateTime(System.currentTimeMillis()); + scenarioWithBLOBs.setStatus(APITestStatus.Underway.name()); + scenarioWithBLOBs.setProjectId(apiTestImportRequest.getProjectId()); + if (StringUtils.isEmpty(request.getPrincipal())) { + scenarioWithBLOBs.setPrincipal(Objects.requireNonNull(SessionUtils.getUser()).getId()); + } + if (request.getUserId() == null) { + scenarioWithBLOBs.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId()); + } else { + scenarioWithBLOBs.setUserId(request.getUserId()); + } + scenarioWithBLOBs.setDescription(request.getDescription()); + + List sameRequest = getWithBLOBs(scenarioWithBLOBs); + if (StringUtils.equals("fullCoverage", apiTestImportRequest.getModeId())) { + _importCreate(sameRequest, batchMapper, scenarioWithBLOBs, apiTestImportRequest); + } else if (StringUtils.equals("incrementalMerge", apiTestImportRequest.getModeId())) { + if (CollectionUtils.isEmpty(sameRequest)) { + batchMapper.insert(scenarioWithBLOBs); + } + + } else { + _importCreate(sameRequest, batchMapper, scenarioWithBLOBs, apiTestImportRequest); + } + return scenarioWithBLOBs; + } + + private void editScenario(ApiTestImportRequest request, ScenarioImport apiImport) { + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + ApiScenarioMapper batchMapper = sqlSession.getMapper(ApiScenarioMapper.class); + List data = apiImport.getData(); + int num = 0; + if (!CollectionUtils.isEmpty(data) && data.get(0) != null && data.get(0).getProjectId() != null) { + num = getNextNum(data.get(0).getProjectId()); + } + for (int i = 0; i < data.size(); i++) { + ApiScenarioWithBLOBs item = data.get(i); + if (item.getName().length() > 255) { + item.setName(item.getName().substring(0, 255)); + } + item.setNum(num++); + importCreate(item, batchMapper, request); + if (i % 300 == 0) { + sqlSession.flushStatements(); + } + } + sqlSession.flushStatements(); + } + + public ScenarioImport scenarioImport(MultipartFile file, ApiTestImportRequest request) { + ScenarioImportParser apiImportParser = ScenarioImportParserFactory.getImportParser(request.getPlatform()); + ScenarioImport apiImport = null; + try { + apiImport = Objects.requireNonNull(apiImportParser).parse(file == null ? null : file.getInputStream(), request); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(Translator.get("parse_data_error")); + } + if (apiImport != null) { + editScenario(request, apiImport); + } + return apiImport; + } } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiScenarioModuleService.java b/backend/src/main/java/io/metersphere/api/service/ApiScenarioModuleService.java index 6445f7d13a..6f4ffcb369 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiScenarioModuleService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiScenarioModuleService.java @@ -215,6 +215,33 @@ public class ApiScenarioModuleService extends NodeTreeService selectSameModule(ApiScenarioModule node) { + ApiScenarioModuleExample example = new ApiScenarioModuleExample(); + ApiScenarioModuleExample.Criteria criteria = example.createCriteria(); + criteria.andNameEqualTo(node.getName()) + .andProjectIdEqualTo(node.getProjectId()); + if (StringUtils.isNotBlank(node.getParentId())) { + criteria.andParentIdEqualTo(node.getParentId()); + } else { + criteria.andParentIdIsNull(); + } + if (StringUtils.isNotBlank(node.getId())) { + criteria.andIdNotEqualTo(node.getId()); + } + return apiScenarioModuleMapper.selectByExample(example); + } + @Override public void updatePos(String id, Double pos) { extApiScenarioModuleMapper.updatePos(id, pos); diff --git a/frontend/src/business/components/api/automation/ApiAutomation.vue b/frontend/src/business/components/api/automation/ApiAutomation.vue index e806819a7c..05f048cdd0 100644 --- a/frontend/src/business/components/api/automation/ApiAutomation.vue +++ b/frontend/src/business/components/api/automation/ApiAutomation.vue @@ -1,5 +1,5 @@ + + From 4e148354741819581b14a303c5af69a30d45785d Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Sun, 7 Feb 2021 09:47:16 +0800 Subject: [PATCH 08/23] Merge branch 'master' of https://github.com/metersphere/metersphere # Conflicts: # frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue --- .../api/automation/scenario/ApiScenarioModule.vue | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue index 595ab1b639..e26a05ebaf 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue @@ -46,17 +46,11 @@ From d1789dd154e9c2ab0dd4be081bb312b08cf89d7a Mon Sep 17 00:00:00 2001 From: "song.tianyang" Date: Fri, 19 Feb 2021 18:29:47 +0800 Subject: [PATCH 18/23] =?UTF-8?q?feat:=20=E6=8E=A5=E5=8F=A3=E5=AE=9A?= =?UTF-8?q?=E4=B9=89->=E6=96=87=E6=A1=A3=20=E5=A2=9E=E5=8A=A0JSON-Schema?= =?UTF-8?q?=E7=9A=84=E6=98=BE=E7=A4=BA=E5=92=8C=E6=9F=A5=E6=89=BE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接口定义->文档 增加JSON-Schema的显示和查找功能 --- .../api/dto/ApiDocumentInfoDTO.java | 2 + .../api/dto/ApiDocumentRequest.java | 3 + .../api/service/ApiDocumentService.java | 62 ++- .../base/mapper/ext/ExtApiDocumentMapper.xml | 17 +- .../components/document/ApiDocumentItem.vue | 513 +++++++++++------- frontend/src/i18n/en-US.js | 30 + frontend/src/i18n/zh-CN.js | 32 +- frontend/src/i18n/zh-TW.js | 30 + 8 files changed, 488 insertions(+), 201 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/dto/ApiDocumentInfoDTO.java b/backend/src/main/java/io/metersphere/api/dto/ApiDocumentInfoDTO.java index 507bce94a1..ab0e50c00c 100644 --- a/backend/src/main/java/io/metersphere/api/dto/ApiDocumentInfoDTO.java +++ b/backend/src/main/java/io/metersphere/api/dto/ApiDocumentInfoDTO.java @@ -22,6 +22,8 @@ public class ApiDocumentInfoDTO { private String requestBodyParamType; private String requestBodyFormData; private String requestBodyStrutureData; + private Object requestPreviewData; + private Object jsonSchemaBody; private String responseHead; private String responseBody; diff --git a/backend/src/main/java/io/metersphere/api/dto/ApiDocumentRequest.java b/backend/src/main/java/io/metersphere/api/dto/ApiDocumentRequest.java index 87f3e3d53e..e517a1f130 100644 --- a/backend/src/main/java/io/metersphere/api/dto/ApiDocumentRequest.java +++ b/backend/src/main/java/io/metersphere/api/dto/ApiDocumentRequest.java @@ -16,4 +16,7 @@ public class ApiDocumentRequest { private String projectId; private List moduleIds; private String shareId; + private String name; + private String type; + private String orderCondition; } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDocumentService.java b/backend/src/main/java/io/metersphere/api/service/ApiDocumentService.java index d15312e0c9..08a67d60c0 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDocumentService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDocumentService.java @@ -35,6 +35,7 @@ public class ApiDocumentService { public ApiDocumentInfoDTO conversionModelToDTO(ApiDefinitionWithBLOBs apiModel) { ApiDocumentInfoDTO apiInfoDTO = new ApiDocumentInfoDTO(); + JSONObject previewObj = new JSONObject(); if (apiModel != null) { apiInfoDTO.setId(apiModel.getId()); apiInfoDTO.setName(apiModel.getName()); @@ -43,7 +44,6 @@ public class ApiDocumentService { apiInfoDTO.setStatus(apiModel.getStatus()); JSONObject requestJsonObj = JSONObject.parseObject(apiModel.getRequest()); - //head赋值 if (requestJsonObj.containsKey("headers")) { JSONArray requestHeadDataArr = new JSONArray(); @@ -55,9 +55,8 @@ public class ApiDocumentService { requestHeadDataArr.add(headObj); } } - apiInfoDTO.setRequestHead(requestHeadDataArr.toJSONString()); + apiInfoDTO.setRequestHead(requestHeadDataArr.toJSONString()); } - //url参数赋值 JSONArray urlParamArr = new JSONArray(); if (requestJsonObj.containsKey("arguments")) { @@ -81,17 +80,43 @@ public class ApiDocumentService { } } apiInfoDTO.setUrlParams(urlParamArr.toJSONString()); - //请求体参数类型 if (requestJsonObj.containsKey("body")) { JSONObject bodyObj = requestJsonObj.getJSONObject("body"); if (bodyObj.containsKey("type")) { String type = bodyObj.getString("type"); - apiInfoDTO.setRequestBodyParamType(type); - if (StringUtils.equalsAny(type, "JSON", "XML", "Raw")) { + if(StringUtils.equals(type,"WWW_FORM")){ + apiInfoDTO.setRequestBodyParamType("x-www-from-urlencoded"); + }else if(StringUtils.equals(type,"Form Data")) { + apiInfoDTO.setRequestBodyParamType("form-data"); + }else { + apiInfoDTO.setRequestBodyParamType(type); + } + + if (StringUtils.equals(type, "JSON")) { + //判断是否是JsonSchema + boolean isJsonSchema = false; + if(bodyObj.containsKey("format")){ + String foramtValue = String.valueOf(bodyObj.get("format")); + if(StringUtils.equals("JSON-SCHEMA",foramtValue)){ + isJsonSchema = true; + } + } + if(isJsonSchema){ + apiInfoDTO.setRequestBodyParamType("JSON-SCHEMA"); + apiInfoDTO.setJsonSchemaBody(bodyObj); + }else { + if (bodyObj.containsKey("raw")) { + String raw = bodyObj.getString("raw"); + apiInfoDTO.setRequestBodyStrutureData(raw); + previewObj = JSONObject.parseObject(raw); + } + } + } else if (StringUtils.equalsAny(type, "XML", "Raw")) { if (bodyObj.containsKey("raw")) { String raw = bodyObj.getString("raw"); apiInfoDTO.setRequestBodyStrutureData(raw); + previewObj = JSONObject.parseObject(raw); } } else if (StringUtils.equalsAny(type, "Form Data", "WWW_FORM")) { if (bodyObj.containsKey("kvs")) { @@ -99,8 +124,9 @@ public class ApiDocumentService { JSONArray kvsArr = bodyObj.getJSONArray("kvs"); for (int i = 0; i < kvsArr.size(); i++) { JSONObject kv = kvsArr.getJSONObject(i); - if (kv.containsKey("name")) { + if (kv.containsKey("name")&&kv.containsKey("value")) { bodyParamArr.add(kv); + previewObj.put(String.valueOf(kv.get("name")),String.valueOf(kv.get("value"))); } } apiInfoDTO.setRequestBodyFormData(bodyParamArr.toJSONString()); @@ -108,12 +134,11 @@ public class ApiDocumentService { } else if (StringUtils.equals(type, "BINARY")) { if (bodyObj.containsKey("binary")) { List> bodyParamList = new ArrayList<>(); - JSONArray kvsArr = bodyObj.getJSONArray("kvs"); + JSONArray kvsArr = bodyObj.getJSONArray("binary"); for (int i = 0; i < kvsArr.size(); i++) { JSONObject kv = kvsArr.getJSONObject(i); if (kv.containsKey("description") && kv.containsKey("files")) { Map bodyMap = new HashMap<>(); - String name = kv.getString("description"); JSONArray fileArr = kv.getJSONArray("files"); String value = ""; @@ -123,11 +148,12 @@ public class ApiDocumentService { value += fileObj.getString("name") + " ;"; } } - bodyMap.put("name", name); bodyMap.put("value", value); - + bodyMap.put("contentType", "File"); bodyParamList.add(bodyMap); + + previewObj.put(String.valueOf(name),String.valueOf(value)); } } apiInfoDTO.setRequestBodyFormData(JSONArray.toJSONString(bodyParamList)); @@ -135,7 +161,6 @@ public class ApiDocumentService { } } } - JSONObject responseJsonObj = JSONObject.parseObject(apiModel.getResponse()); //赋值响应头 if (responseJsonObj.containsKey("headers")) { @@ -149,13 +174,18 @@ public class ApiDocumentService { } apiInfoDTO.setResponseHead(responseHeadDataArr.toJSONString()); } - // 赋值响应体 if (responseJsonObj.containsKey("body")) { JSONObject bodyObj = responseJsonObj.getJSONObject("body"); if (bodyObj.containsKey("type")) { String type = bodyObj.getString("type"); - apiInfoDTO.setResponseBodyParamType(type); + if(StringUtils.equals(type,"WWW_FORM")){ + apiInfoDTO.setResponseBodyParamType("x-www-from-urlencoded"); + }else if(StringUtils.equals(type,"Form Data")) { + apiInfoDTO.setResponseBodyParamType("form-data"); + }else { + apiInfoDTO.setResponseBodyParamType(type); + } if (StringUtils.equalsAny(type, "JSON", "XML", "Raw")) { if (bodyObj.containsKey("raw")) { String raw = bodyObj.getString("raw"); @@ -191,10 +221,8 @@ public class ApiDocumentService { value += fileObj.getString("name") + " ;"; } } - bodyMap.put("name", name); bodyMap.put("value", value); - bodyParamList.add(bodyMap); } } @@ -203,7 +231,6 @@ public class ApiDocumentService { } } } - // 赋值响应码 if (responseJsonObj.containsKey("statusCode")) { JSONArray responseStatusDataArr = new JSONArray(); @@ -217,6 +244,7 @@ public class ApiDocumentService { apiInfoDTO.setResponseCode(responseStatusDataArr.toJSONString()); } } + apiInfoDTO.setRequestPreviewData(previewObj); return apiInfoDTO; } } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDocumentMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDocumentMapper.xml index ddf50ac9e4..d0e9515f31 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDocumentMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDocumentMapper.xml @@ -7,6 +7,13 @@ api.project_Id = #{request.projectId} + + api.project_Id like CONCAT('%', #{request.name},'%') + + + api.method = #{request.type} + + AND api.module_id in @@ -14,6 +21,14 @@ - ORDER BY api.create_time DESC + + ORDER BY api.create_time DESC + + + ORDER BY api.update_time + + + ORDER BY api.update_time DESC + \ No newline at end of file diff --git a/frontend/src/business/components/api/definition/components/document/ApiDocumentItem.vue b/frontend/src/business/components/api/definition/components/document/ApiDocumentItem.vue index 43e64658c5..6b2c0e1925 100644 --- a/frontend/src/business/components/api/definition/components/document/ApiDocumentItem.vue +++ b/frontend/src/business/components/api/definition/components/document/ApiDocumentItem.vue @@ -1,17 +1,44 @@ diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index 423905fd6f..0203ccccd9 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -596,6 +596,36 @@ export default { other_config: "Other Config", message_template: "Message Template", tcp_parameter_tip: "The request parameters can be referenced in the request template ${XXX}", + }, + document: { + order: "Order", + create_time_sort: "From back to front by create time", + edit_time_positive_sequence: "From front to back by update time", + edit_time_Reverse_order: "From back to front by update time", + request_method: "Request method", + request_interface: "Request interface", + search_by_api_name : "Search by api name", + request_info: "Request info", + request_head: "Request head", + request_param: "Param", + request_body: "Request body", + example_presentation: "Example presentation", + response_info: "Response info", + response_head: "Response head", + response_body: "Response body", + response_code: "Response code", + table_coloum:{ + name: "name", + value: "value", + is_required: "Is it required", + desc: "Description", + type: "Type", + default_value: "Default value", + }, + data_set: { + all: "All", + none: "None" + }, } }, automation: { diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index fac1d09d8a..2a10360dbe 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -597,6 +597,36 @@ export default { other_config: "其他设置", message_template: "报文模版", tcp_parameter_tip: "请求参数可以在请求模版通过${xxx}引用", + }, + document: { + order: "排序方式", + create_time_sort: "按创建时间从后到前", + edit_time_positive_sequence: "按更新时间从前到后", + edit_time_Reverse_order: "按更新时间从后到前", + request_method: "请求方式", + request_interface: "请求接口", + search_by_api_name : "名称搜索", + request_info: "请求信息", + request_head: "请求头", + request_param: "参数", + request_body: "请求体", + example_presentation: "范例展示", + response_info: "响应信息", + response_head: "响应头", + response_body: "响应体", + response_code: "响应码", + table_coloum:{ + name: "名称", + value: "值", + is_required: "是否必填", + desc: "描述", + type: "类型", + default_value: "默认值", + }, + data_set: { + all: "全部", + none: "无" + }, } }, automation: { @@ -714,7 +744,7 @@ export default { jmeter_func: "Jmeter 方法", parameters_filter_example: "示例", parameters_filter_tips: "只支持 MockJs 函数结果预览", - parameters_advance: "高级参数设置", + parameters_advance: "return", parameters_preview: "预览", parameters_mock_filter_tips: "请输入关键字进行过滤", parameters_pre_request: "前置请求提取", diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index 14cb737e8d..a27366c00c 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -596,6 +596,36 @@ export default { other_config: "其他設置", message_template: "報文模版", tcp_parameter_tip: "請求參數可以在請求模版通過${xxx}引用", + }, + document: { + order: "排序方式", + create_time_sort: "按創建時間從後到前", + edit_time_positive_sequence: "按更新時間從前到後", + edit_time_Reverse_order: "按更新時間從後到前", + request_method: "請求方式", + request_interface: "請求接口e", + search_by_api_name : "API名稱搜索", + request_info: "請求信息", + request_head: "請求頭", + request_param: "參數", + request_body: "請求體", + example_presentation: "範例展示", + response_info: "響應信息", + response_head: "響應頭", + response_body: "響應體", + response_code: "響應碼", + table_coloum:{ + name: "名稱", + value: "值", + is_required: "是否必填", + desc: "描述", + type: "類型", + default_value: "默認值", + }, + data_set: { + all: "全部", + none: "無" + }, } }, automation: { From 217800b24d8077f606b597de39806a72746fdcc2 Mon Sep 17 00:00:00 2001 From: "song.tianyang" Date: Fri, 19 Feb 2021 18:45:36 +0800 Subject: [PATCH 19/23] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E4=BB=BB=E5=8A=A1=E8=A1=A8key=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E9=95=BF=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改定时任务表key字段长度 --- .../resources/db/migration/V77__modify_schedule_test_id.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 backend/src/main/resources/db/migration/V77__modify_schedule_test_id.sql diff --git a/backend/src/main/resources/db/migration/V77__modify_schedule_test_id.sql b/backend/src/main/resources/db/migration/V77__modify_schedule_test_id.sql new file mode 100644 index 0000000000..15a8e1591e --- /dev/null +++ b/backend/src/main/resources/db/migration/V77__modify_schedule_test_id.sql @@ -0,0 +1,2 @@ +ALTER TABLE schedule + MODIFY COLUMN `key` VARCHAR(255); From 63b8801aa98240407b79b7939c3d4b633623d3ab Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Sat, 20 Feb 2021 11:20:58 +0800 Subject: [PATCH 20/23] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96):=20=E5=AE=8C=E5=96=84jmx=E5=AF=BC=E5=85=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E5=A2=9E=E5=8A=A0jmeter=E7=9B=B8=E5=85=B3Sam?= =?UTF-8?q?pler=E4=BE=9D=E8=B5=96jar=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/pom.xml | 43 ++++++++++++++++++- .../dto/automation/parse/MsJmeterParser.java | 3 ++ .../request/sampler/MsDubboSampler.java | 18 ++++---- .../api/automation/ApiAutomation.vue | 5 +++ .../automation/scenario/ApiScenarioModule.vue | 2 +- .../scenario/common/ScenarioImport.vue | 12 +++++- 6 files changed, 71 insertions(+), 12 deletions(-) diff --git a/backend/pom.xml b/backend/pom.xml index aee9f307b3..ddb8681c7b 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -380,7 +380,48 @@ json-schema-validator 2.2.6 - + + + org.apache.jmeter + ApacheJMeter_bolt + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_jms + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_ftp + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_junit + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_ldap + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_mail + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_components + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_native + ${jmeter.version} + + diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java index d8d4a7e573..067a2478d2 100644 --- a/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java @@ -43,6 +43,7 @@ import io.metersphere.base.domain.ApiScenarioWithBLOBs; import io.metersphere.base.domain.ApiTestEnvironmentExample; import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs; import io.metersphere.commons.constants.LoopConstants; +import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.LogUtil; @@ -96,6 +97,7 @@ public class MsJmeterParser extends ScenarioImportAbstractParser { return scenarioImport; } catch (Exception e) { e.printStackTrace(); + MSException.throwException("当前JMX版本不兼容"); } return null; } @@ -195,6 +197,7 @@ public class MsJmeterParser extends ScenarioImportAbstractParser { } private void convertDubboSample(MsDubboSampler elementNode, DubboSample sampler) { + elementNode.setName(sampler.getName()); elementNode.setType("DubboSampler"); elementNode.setProtocol("dubbo://"); elementNode.set_interface(sampler.getPropertyAsString("FIELD_DUBBO_INTERFACE")); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsDubboSampler.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsDubboSampler.java index 7fdb15d3b9..88be6b3369 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsDubboSampler.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsDubboSampler.java @@ -93,14 +93,16 @@ public class MsDubboSampler extends MsTestElement { Constants.setInterfaceName(this.get_interface(), sampler); Constants.setMethod(this.getMethod(), sampler); - List methodArgs = this.getArgs().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable) - .map(keyValue -> new MethodArgument(keyValue.getName(), keyValue.getValue())).collect(Collectors.toList()); - Constants.setMethodArgs(methodArgs, sampler); - - List attachmentArgs = this.getAttachmentArgs().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable) - .map(keyValue -> new MethodArgument(keyValue.getName(), keyValue.getValue())).collect(Collectors.toList()); - Constants.setAttachmentArgs(attachmentArgs, sampler); - + if (CollectionUtils.isNotEmpty(this.getArgs())) { + List methodArgs = this.getArgs().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable) + .map(keyValue -> new MethodArgument(keyValue.getName(), keyValue.getValue())).collect(Collectors.toList()); + Constants.setMethodArgs(methodArgs, sampler); + } + if (CollectionUtils.isNotEmpty(this.getAttachmentArgs())) { + List attachmentArgs = this.getAttachmentArgs().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable) + .map(keyValue -> new MethodArgument(keyValue.getName(), keyValue.getValue())).collect(Collectors.toList()); + Constants.setAttachmentArgs(attachmentArgs, sampler); + } return sampler; } diff --git a/frontend/src/business/components/api/automation/ApiAutomation.vue b/frontend/src/business/components/api/automation/ApiAutomation.vue index 120e2f038c..58b6240c65 100644 --- a/frontend/src/business/components/api/automation/ApiAutomation.vue +++ b/frontend/src/business/components/api/automation/ApiAutomation.vue @@ -9,6 +9,7 @@ @setNodeTree="setNodeTree" @enableTrash="enableTrash" @exportAPI="exportAPI" + @refreshAll="refreshAll" :type="'edit'" ref="nodeTree"/> @@ -264,6 +265,10 @@ this.setTabTitle(data); this.$refs.apiScenarioList.search(data); }, + refreshAll() { + this.$refs.nodeTree.list(); + this.$refs.apiScenarioList.search(); + }, setTabTitle(data) { for (let index in this.tabs) { let tab = this.tabs[index]; diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue index e26a05ebaf..c603c83b3e 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue @@ -40,7 +40,7 @@ @refresh="refresh" ref="basisScenario"/> - + diff --git a/frontend/src/business/components/api/automation/scenario/common/ScenarioImport.vue b/frontend/src/business/components/api/automation/scenario/common/ScenarioImport.vue index b739caabda..b0f9ec854d 100644 --- a/frontend/src/business/components/api/automation/scenario/common/ScenarioImport.vue +++ b/frontend/src/business/components/api/automation/scenario/common/ScenarioImport.vue @@ -192,6 +192,15 @@ return true; }, save() { + if (!this.formData.file) { + this.$warning("请添加一个文件"); + return; + } + let suffix = this.formData.file.name.substring(this.formData.file.name.lastIndexOf('.') + 1); + if (this.selectedPlatform.suffixes && !this.selectedPlatform.suffixes.has(suffix)) { + this.$warning(this.$t('api_test.api_import.suffixFormatErr')); + return false; + } this.$refs.form.validate(valid => { if (valid) { let param = this.buildParam(); @@ -199,7 +208,7 @@ let res = response.data; this.$success(this.$t('test_track.case.import.success')); this.visible = false; - this.$emit('refresh', res); + this.$emit('refreshAll', res); }); } else { return false; @@ -211,7 +220,6 @@ Object.assign(param, this.formData); param.platform = this.selectedPlatformValue; param.saved = this.saved; - console.log(this.formData.moduleId) if (this.currentModule) { param.moduleId = this.formData.moduleId this.moduleOptions.filter(item => { From 567ae41aa3f4af60f5af97af04cae8d010fdcf6b Mon Sep 17 00:00:00 2001 From: "song.tianyang" Date: Sat, 20 Feb 2021 11:27:04 +0800 Subject: [PATCH 21/23] =?UTF-8?q?fix:=20=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E3=80=81=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8=E5=8C=96?= =?UTF-8?q?=E8=BD=AC=E6=80=A7=E8=83=BD=E6=B5=8B=E8=AF=95=E6=97=B6=E8=87=AA?= =?UTF-8?q?=E5=B8=A6=E5=87=BD=E6=95=B0=E4=BC=9A=E5=B8=A6=E6=9D=A5jmeter?= =?UTF-8?q?=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复接口测试、接口自动化转性能测试时自带函数会带来jmeter报错的问题 --- .../api/controller/APITestController.java | 89 ++++++++----------- .../api/service/APITestService.java | 62 +++++++++++++ .../api/service/ApiAutomationService.java | 19 ++-- 3 files changed, 110 insertions(+), 60 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/controller/APITestController.java b/backend/src/main/java/io/metersphere/api/controller/APITestController.java index 82e2ec81e8..61eb6e2fef 100644 --- a/backend/src/main/java/io/metersphere/api/controller/APITestController.java +++ b/backend/src/main/java/io/metersphere/api/controller/APITestController.java @@ -31,6 +31,7 @@ import io.metersphere.service.CheckPermissionService; import io.metersphere.service.FileService; import io.metersphere.service.ScheduleService; import io.metersphere.track.request.testplan.SaveTestPlanRequest; +import org.apache.commons.lang3.StringUtils; import org.apache.http.entity.ContentType; import org.apache.jorphan.collections.HashTree; import org.apache.shiro.authz.annotation.Logical; @@ -212,19 +213,19 @@ public class APITestController { //查询完成率、进行中、已完成 List countResultByStatelList = apiDefinitionService.countStateByProjectID(projectId); apiCountResult.countStatus(countResultByStatelList); - long allCount = apiCountResult.getFinishedCount()+apiCountResult.getRunningCount()+apiCountResult.getNotStartedCount(); + long allCount = apiCountResult.getFinishedCount() + apiCountResult.getRunningCount() + apiCountResult.getNotStartedCount(); - if(allCount!=0){ - float complateRageNumber =(float)apiCountResult.getFinishedCount()*100/allCount; + if (allCount != 0) { + float complateRageNumber = (float) apiCountResult.getFinishedCount() * 100 / allCount; DecimalFormat df = new DecimalFormat("0.0"); - apiCountResult.setCompletionRage(df.format(complateRageNumber)+"%"); + apiCountResult.setCompletionRage(df.format(complateRageNumber) + "%"); } - apiCountResult.setHttpCountStr("HTTP  

"+apiCountResult.getHttpApiDataCountNumber()); - apiCountResult.setRpcCountStr("RPC  

"+apiCountResult.getRpcApiDataCountNumber()); - apiCountResult.setTcpCountStr("TCP  

"+apiCountResult.getTcpApiDataCountNumber()); - apiCountResult.setSqlCountStr("SQL  

"+apiCountResult.getSqlApiDataCountNumber()); - return apiCountResult; + apiCountResult.setHttpCountStr("HTTP  

" + apiCountResult.getHttpApiDataCountNumber()); + apiCountResult.setRpcCountStr("RPC  

" + apiCountResult.getRpcApiDataCountNumber()); + apiCountResult.setTcpCountStr("TCP  

" + apiCountResult.getTcpApiDataCountNumber()); + apiCountResult.setSqlCountStr("SQL  

" + apiCountResult.getSqlApiDataCountNumber()); + return apiCountResult; } @GetMapping("/testCaseInfoCount/{projectId}") @@ -245,21 +246,21 @@ public class APITestController { //未覆盖 已覆盖: 统计当前接口下是否含有案例 List countResultByApiCoverageList = apiDefinitionService.countApiCoverageByProjectID(projectId); apiCountResult.countApiCoverage(countResultByApiCoverageList); - long allCount = apiCountResult.getCoverageCount()+apiCountResult.getUncoverageCount(); + long allCount = apiCountResult.getCoverageCount() + apiCountResult.getUncoverageCount(); - if(allCount!=0){ - float coverageRageNumber =(float)apiCountResult.getCoverageCount()*100/allCount; + if (allCount != 0) { + float coverageRageNumber = (float) apiCountResult.getCoverageCount() * 100 / allCount; DecimalFormat df = new DecimalFormat("0.0"); - apiCountResult.setCoverageRage(df.format(coverageRageNumber)+"%"); + apiCountResult.setCoverageRage(df.format(coverageRageNumber) + "%"); } - apiCountResult.setHttpCountStr("HTTP  

"+apiCountResult.getHttpApiDataCountNumber()); - apiCountResult.setRpcCountStr("RPC  

"+apiCountResult.getRpcApiDataCountNumber()); - apiCountResult.setTcpCountStr("TCP  

"+apiCountResult.getTcpApiDataCountNumber()); - apiCountResult.setSqlCountStr("SQL  

"+apiCountResult.getSqlApiDataCountNumber()); + apiCountResult.setHttpCountStr("HTTP  

" + apiCountResult.getHttpApiDataCountNumber()); + apiCountResult.setRpcCountStr("RPC  

" + apiCountResult.getRpcApiDataCountNumber()); + apiCountResult.setTcpCountStr("TCP  

" + apiCountResult.getTcpApiDataCountNumber()); + apiCountResult.setSqlCountStr("SQL  

" + apiCountResult.getSqlApiDataCountNumber()); - return apiCountResult; + return apiCountResult; } @GetMapping("/testSceneInfoCount/{projectId}") @@ -318,28 +319,28 @@ public class APITestController { apiCountResult.countScheduleExecute(allExecuteResult); long allCount = apiCountResult.getExecutedCount(); - if(allCount!=0){ - float coverageRageNumber =(float)apiCountResult.getSuccessCount()*100/allCount; + if (allCount != 0) { + float coverageRageNumber = (float) apiCountResult.getSuccessCount() * 100 / allCount; DecimalFormat df = new DecimalFormat("0.0"); - apiCountResult.setSuccessRage(df.format(coverageRageNumber)+"%"); + apiCountResult.setSuccessRage(df.format(coverageRageNumber) + "%"); } - return apiCountResult; + return apiCountResult; } @GetMapping("/faliureCaseAboutTestPlan/{projectId}/{limitNumber}") public List faliureCaseAboutTestPlan(@PathVariable String projectId, @PathVariable int limitNumber) { - List selectDataList = apiDefinitionExecResultService.findFaliureCaseInfoByProjectIDAndLimitNumberInSevenDays(projectId,limitNumber); + List selectDataList = apiDefinitionExecResultService.findFaliureCaseInfoByProjectIDAndLimitNumberInSevenDays(projectId, limitNumber); List returnList = new ArrayList<>(limitNumber); - for(int dataIndex = 0;dataIndex < limitNumber;dataIndex ++){ + for (int dataIndex = 0; dataIndex < limitNumber; dataIndex++) { ExecutedCaseInfoDTO dataDTO = new ExecutedCaseInfoDTO(); - dataDTO.setSortIndex(dataIndex+1); + dataDTO.setSortIndex(dataIndex + 1); - if(dataIndex thirdHashTreeElementList =innerHashTreeElement.elements("hashTree"); - for (Element element:thirdHashTreeElementList) { - List sampleProxyElementList = element.elements("HTTPSamplerProxy"); - for (Element itemElement: sampleProxyElementList) { - itemElement.attribute("testname").setText(testName); - } - } - - jmxString = root.asXML(); - }catch (Exception e){ - e.printStackTrace(); - } + //将jmx处理封装为通用方法 + jmxString = apiTestService.updateJmxString(jmxString,testName,true); JmxInfoDTO dto = new JmxInfoDTO(); - dto.setName(runRequest.getName()+".jmx"); + dto.setName(runRequest.getName() + ".jmx"); dto.setXml(jmxString); - return dto; + return dto; } } diff --git a/backend/src/main/java/io/metersphere/api/service/APITestService.java b/backend/src/main/java/io/metersphere/api/service/APITestService.java index 23ae3400e9..10cf3ab666 100644 --- a/backend/src/main/java/io/metersphere/api/service/APITestService.java +++ b/backend/src/main/java/io/metersphere/api/service/APITestService.java @@ -32,6 +32,9 @@ import io.metersphere.track.service.TestCaseService; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.constants.CommonConstants; import org.aspectj.util.FileUtil; +import org.dom4j.Document; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -457,4 +460,63 @@ public class APITestService { copyBodyFiles(test.getId(), sourceId); }); } + + public String updateJmxString(String jmxString,String testName,boolean updateHTTPSamplerProxyName) { + try { + //将ThreadGroup的testname改为接口名称 + Document doc = DocumentHelper.parseText(jmxString);// 获取可续保保单列表报文模板 + Element root = doc.getRootElement(); + Element rootHashTreeElement = root.element("hashTree"); + Element innerHashTreeElement = rootHashTreeElement.elements("hashTree").get(0); + Element theadGroupElement = innerHashTreeElement.elements("ThreadGroup").get(0); + theadGroupElement.attribute("testname").setText(testName); + + List thirdHashTreeElementList = innerHashTreeElement.elements("hashTree"); + for (Element element : thirdHashTreeElementList) { + if(updateHTTPSamplerProxyName){ + List sampleProxyElementList = element.elements("HTTPSamplerProxy"); + for (Element itemElement : sampleProxyElementList) { + itemElement.attribute("testname").setText(testName); + } + } + //检查有没有自定义参数 + List scriptHashTreeElementList = element.elements("hashTree"); + for (Element scriptHashTreeElement : scriptHashTreeElementList) { + boolean isRemove = false; + List removeElement = new ArrayList<>(); + List scriptElementItemList = scriptHashTreeElement.elements(); + for (Element hashTreeItemElement : scriptElementItemList) { + String className = hashTreeItemElement.attributeValue("testclass"); + String qname = hashTreeItemElement.getQName().getName(); + + if (isRemove) { + if (org.apache.commons.lang3.StringUtils.equals("hashTree", qname)) { + removeElement.add(hashTreeItemElement); + } + } + isRemove = false; + if (org.apache.commons.lang3.StringUtils.equals(className, "JSR223PostProcessor")) { + List scriptElements = hashTreeItemElement.elements("stringProp"); + for (Element scriptElement : scriptElements) { + String scriptName = scriptElement.attributeValue("name"); + String contentValue = scriptElement.getStringValue(); + + if ("script".equals(scriptName) && contentValue.startsWith("io.metersphere.api.jmeter.JMeterVars.addVars")) { + isRemove = true; + removeElement.add(hashTreeItemElement); + } + } + } + } + for (Element itemElement : removeElement) { + scriptHashTreeElement.remove(itemElement); + } + } + } + jmxString = root.asXML(); + } catch (Exception e) { + e.printStackTrace(); + } + return jmxString; + } } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index 3de0a658c2..6882ef38a5 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -62,6 +62,8 @@ public class ApiAutomationService { @Resource private ApiScenarioMapper apiScenarioMapper; @Resource + private APITestService apiTestService; + @Resource private ExtApiScenarioMapper extApiScenarioMapper; @Resource private TestPlanApiScenarioMapper testPlanApiScenarioMapper; @@ -680,14 +682,17 @@ public class ApiAutomationService { HashTree jmeterHashTree = generateHashTree(apiScenarios, request, null); String jmx = testPlan.getJmx(jmeterHashTree); + + jmx = apiTestService.updateJmxString(jmx,testName,false); + //将ThreadGroup的testname改为接口名称 - Document doc = DocumentHelper.parseText(jmx);// 获取可续保保单列表报文模板 - Element root = doc.getRootElement(); - Element rootHashTreeElement = root.element("hashTree"); - Element innerHashTreeElement = rootHashTreeElement.elements("hashTree").get(0); - Element theadGroupElement = innerHashTreeElement.elements("ThreadGroup").get(0); - theadGroupElement.attribute("testname").setText(testName); - jmx = root.asXML(); +// Document doc = DocumentHelper.parseText(jmx);// 获取可续保保单列表报文模板 +// Element root = doc.getRootElement(); +// Element rootHashTreeElement = root.element("hashTree"); +// Element innerHashTreeElement = rootHashTreeElement.elements("hashTree").get(0); +// Element theadGroupElement = innerHashTreeElement.elements("ThreadGroup").get(0); +// theadGroupElement.attribute("testname").setText(testName); +// jmx = root.asXML(); String name = request.getName() + ".jmx"; From 1270eb8b2954f28945a758a305a32f65843f308b Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Sat, 20 Feb 2021 11:53:04 +0800 Subject: [PATCH 22/23] Merge branch 'master' of https://github.com/metersphere/metersphere # Conflicts: # backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java # backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java # backend/src/main/java/io/metersphere/base/mapper/SwaggerUrlProjectMapper.xml # frontend/src/business/components/api/automation/ApiAutomation.vue # frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue --- .../controller/ApiAutomationController.java | 12 ++--- .../api/service/ApiAutomationService.java | 47 +++++++++---------- .../automation/scenario/ApiScenarioModule.vue | 3 +- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java index 66e30946fb..e2b1e09909 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java @@ -111,7 +111,7 @@ public class ApiAutomationController { @PostMapping("/batch/edit") @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) - public void bathEdit(@RequestBody SaveApiScenarioRequest request) { + public void bathEdit(@RequestBody ApiScenarioBatchRequest request) { apiAutomationService.bathEdit(request); } @@ -162,11 +162,11 @@ public class ApiAutomationController { } - @PostMapping(value = "/import", consumes = {"multipart/form-data"}) - @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) - public ApiDefinitionImport testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) { - return apiAutomationService.scenarioImport(file, request); - } +// @PostMapping(value = "/import", consumes = {"multipart/form-data"}) +// @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) +// public ApiDefinitionImport testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) { +// return apiAutomationService.scenarioImport(file, request); +// } @PostMapping(value = "/export") @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index ff9a1a2b11..8c96738a1e 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -14,13 +14,10 @@ import io.metersphere.api.dto.automation.parse.ScenarioImportParser; import io.metersphere.api.dto.automation.parse.ScenarioImportParserFactory; import io.metersphere.api.dto.datacount.ApiDataCountResult; import io.metersphere.api.dto.definition.RunDefinitionRequest; -import io.metersphere.api.dto.definition.parse.ApiDefinitionImport; import io.metersphere.api.dto.definition.request.*; import io.metersphere.api.dto.definition.request.variable.ScenarioVariable; import io.metersphere.api.dto.scenario.environment.EnvironmentConfig; import io.metersphere.api.jmeter.JMeterService; -import io.metersphere.api.parse.ApiImportParser; -import io.metersphere.api.parse.ApiScenarioImportParserFactory; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.ApiScenarioMapper; import io.metersphere.base.mapper.ApiScenarioReportMapper; @@ -816,28 +813,28 @@ public class ApiAutomationService { return apiImport; } - public ApiDefinitionImport scenarioImport(MultipartFile file, ApiTestImportRequest request) { - ApiImportParser apiImportParser = ApiScenarioImportParserFactory.getApiImportParser(request.getPlatform()); - ApiDefinitionImport apiImport = null; - try { - apiImport = apiImportParser.parse(file == null ? null : file.getInputStream(), request); - } catch (Exception e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException(Translator.get("parse_data_error")); - } - SaveApiScenarioRequest saveReq = new SaveApiScenarioRequest(); - saveReq.setScenarioDefinition(apiImport.getScenarioDefinition()); - saveReq.setName(saveReq.getScenarioDefinition().getName()); - saveReq.setProjectId(request.getProjectId()); - saveReq.setApiScenarioModuleId(request.getModuleId()); - if (StringUtils.isNotBlank(request.getUserId())) { - saveReq.setPrincipal(request.getUserId()); - } else { - saveReq.setPrincipal(SessionUtils.getUserId()); - } - create(saveReq, new ArrayList<>()); - return apiImport; - } +// public ApiDefinitionImport scenarioImport(MultipartFile file, ApiTestImportRequest request) { +// ApiImportParser apiImportParser = ApiScenarioImportParserFactory.getApiImportParser(request.getPlatform()); +// ApiDefinitionImport apiImport = null; +// try { +// apiImport = apiImportParser.parse(file == null ? null : file.getInputStream(), request); +// } catch (Exception e) { +// LogUtil.error(e.getMessage(), e); +// MSException.throwException(Translator.get("parse_data_error")); +// } +// SaveApiScenarioRequest saveReq = new SaveApiScenarioRequest(); +// saveReq.setScenarioDefinition(apiImport.getScenarioDefinition()); +// saveReq.setName(saveReq.getScenarioDefinition().getName()); +// saveReq.setProjectId(request.getProjectId()); +// saveReq.setApiScenarioModuleId(request.getModuleId()); +// if (StringUtils.isNotBlank(request.getUserId())) { +// saveReq.setPrincipal(request.getUserId()); +// } else { +// saveReq.setPrincipal(SessionUtils.getUserId()); +// } +// create(saveReq, new ArrayList<>()); +// return apiImport; +// } public ApiScenrioExportResult export(ApiScenarioBatchRequest request) { ServiceUtils.getSelectAllIds(request, request.getCondition(), diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue index 33f5a4f272..2edc1672ef 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue @@ -62,11 +62,12 @@ import MsNodeTree from "../../../track/common/NodeTree"; import {buildNodePath} from "../../definition/model/NodeTree"; import ModuleTrashButton from "../../definition/components/module/ModuleTrashButton"; import ApiScenarioModuleHeader from "@/business/components/api/automation/scenario/module/ApiScenarioModuleHeader"; +import ApiImport from "./common/ScenarioImport"; export default { name: 'MsApiScenarioModule', components: { - ApiScenarioModuleHeader, + ApiImport, ModuleTrashButton, MsNodeTree, MsAddBasisScenario, From 35d87006e051ab105dd6c51aa1585525fe2942fd Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Sat, 20 Feb 2021 11:56:18 +0800 Subject: [PATCH 23/23] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96):=20=E6=B3=A8=E9=87=8A=E4=B8=80=E9=83=A8=E5=88=86?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../automation/scenario/ApiScenarioModule.vue | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue index 2edc1672ef..a841f44564 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue @@ -31,15 +31,16 @@ - + + + + + + + + + +