diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java index 916f3feab4..a34f222688 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java @@ -206,6 +206,7 @@ public abstract class MsTestElement { } csvDataSet.setIgnoreFirstLine(false); csvDataSet.setRecycle(true); + csvDataSet.setProperty("shareMode","shareMode.thread"); csvDataSet.setProperty("recycle", true); csvDataSet.setProperty("delimiter", item.getDelimiter()); csvDataSet.setComment(StringUtils.isEmpty(item.getDescription()) ? "" : item.getDescription()); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsJDBCSampler.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsJDBCSampler.java index 896e701068..d7a42a6cdf 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsJDBCSampler.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsJDBCSampler.java @@ -111,8 +111,16 @@ public class MsJDBCSampler extends MsTestElement { this.dataSource = config.getConfig().get(this.getProjectId()).getDatabaseConfigs().get(0); } } + if (this.dataSource == null) { - MSException.throwException("数据源为空无法执行"); + // 用自身的数据 + if (StringUtils.isNotEmpty(dataSourceId)) { + this.dataSource = null; + this.initDataSource(); + } + if (this.dataSource == null) { + MSException.throwException("数据源为空无法执行"); + } } final HashTree samplerHashTree = tree.add(jdbcSampler(config)); tree.add(jdbcDataSource()); 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 1d8f550ef2..1fe94f0da6 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -21,6 +21,7 @@ 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.service.task.ParallelExecTask; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.ext.*; @@ -57,6 +58,8 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.stream.Collectors; @Service @@ -743,6 +746,8 @@ public class ApiAutomationService { SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); ApiScenarioReportMapper batchMapper = sqlSession.getMapper(ApiScenarioReportMapper.class); String reportId = request.getId(); + + Map map = new LinkedHashMap<>(); // 按照场景执行 for (ApiScenarioWithBLOBs item : apiScenarios) { if (item.getStepTotal() == null || item.getStepTotal() == 0) { @@ -780,11 +785,18 @@ public class ApiAutomationService { batchMapper.insert(report); // 调用执行方法 - jMeterService.runDefinition(report.getId(), hashTree, request.getReportId(), request.getRunMode()); + // jMeterService.runDefinition(report.getId(), hashTree, request.getReportId(), request.getRunMode()); + map.put(report.getId(), hashTree); // 重置报告ID reportId = UUID.randomUUID().toString(); } sqlSession.flushStatements(); + // 开始执行 + ExecutorService executorService = Executors.newFixedThreadPool(map.size()); + for (String key : map.keySet()) { + executorService.submit(new ParallelExecTask(jMeterService, key, map.get(key), request)); + } + return request.getId(); } @@ -884,7 +896,7 @@ public class ApiAutomationService { // 生成集成报告 if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && StringUtils.isNotEmpty(request.getConfig().getReportName())) { request.getConfig().setReportId(UUID.randomUUID().toString()); - APIScenarioReportResult report = createScenarioReport(request.getConfig().getReportId(), JSON.toJSONString(reportList), request.getConfig().getReportName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(), + APIScenarioReportResult report = createScenarioReport(request.getConfig().getReportId(), JSON.toJSONString(reportList), request.getConfig().getReportName(), ReportTriggerMode.MANUAL.name(), ExecuteType.Saved.name(), request.getProjectId(), request.getReportUserID(), request.getConfig()); batchMapper.insert(report); } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java b/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java index 78b33db56b..8522140ab1 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java @@ -361,7 +361,6 @@ public class ApiScenarioReportService { } else { report.setUserId(SessionUtils.getUserId()); } - report.setTriggerMode(runMode); report.setExecuteType(ExecuteType.Saved.name()); report.setProjectId(projectId); report.setScenarioName(scenarioNames.toString().substring(0, scenarioNames.toString().length() - 1)); diff --git a/backend/src/main/java/io/metersphere/api/service/task/ParallelExecTask.java b/backend/src/main/java/io/metersphere/api/service/task/ParallelExecTask.java new file mode 100644 index 0000000000..a2f762cd25 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/service/task/ParallelExecTask.java @@ -0,0 +1,38 @@ +/** + * + */ +package io.metersphere.api.service.task; + +import io.metersphere.api.dto.automation.RunScenarioRequest; +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.LogUtil; +import org.apache.jorphan.collections.HashTree; + +import java.util.concurrent.Callable; + +public class ParallelExecTask implements Callable { + private RunScenarioRequest request; + private JMeterService jMeterService; + private HashTree hashTree; + private String id; + + public ParallelExecTask(JMeterService jMeterService, String id, HashTree hashTree, RunScenarioRequest request) { + this.jMeterService = jMeterService; + this.request = request; + this.hashTree = hashTree; + this.id = id; + } + + @Override + public T call() { + try { + jMeterService.runDefinition(id, hashTree, request.getReportId(), request.getRunMode()); + return null; + } catch (Exception ex) { + LogUtil.error(ex.getMessage()); + MSException.throwException(ex.getMessage()); + return null; + } + } +} diff --git a/backend/src/main/java/io/metersphere/commons/json/JSONSchemaGenerator.java b/backend/src/main/java/io/metersphere/commons/json/JSONSchemaGenerator.java index 41e251740f..e9fbf06ba3 100644 --- a/backend/src/main/java/io/metersphere/commons/json/JSONSchemaGenerator.java +++ b/backend/src/main/java/io/metersphere/commons/json/JSONSchemaGenerator.java @@ -84,6 +84,8 @@ public class JSONSchemaGenerator { JsonObject propertyObj = propertiesObj.get(propertyKey).getAsJsonObject(); analyzeProperty(rootObj, propertyKey, propertyObj); } + } else if (object.has("type") && object.get("type").getAsString().equals("array")) { + analyzeProperty(rootObj, "MS-OBJECT", object); } else if (object.has("type") && !object.get("type").getAsString().equals("object")) { analyzeProperty(rootObj, object.getAsString(), object); } @@ -298,6 +300,9 @@ public class JSONSchemaGenerator { generator(jsonSchema, root); // 格式化返回 Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create(); + if (root.get("MS-OBJECT") != null) { + return gson.toJson(root.get("MS-OBJECT")); + } return gson.toJson(root); } else { return report.getExceptionThreshold().toString(); diff --git a/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue b/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue index 1695880d42..ae19cfbb4a 100644 --- a/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue +++ b/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue @@ -45,7 +45,7 @@ :request="request" :showScript="false"/> 0) { // 创建items对象的基本信息 let objectItem = this._object[0] - result["items"] = this._value2object(objectItem, `#/items`, 'items') + result["items"] = this._value2object(objectItem, `#/items`, 'items'); if (isObject(objectItem) && !isEmpty(objectItem)) { // 递归遍历 - let objectItemSchema = this._json2schema(objectItem, `#/items`) + let objectItemSchema = this._json2schema(objectItem, `#/items`); // 合并对象 - result["items"] = Object.assign(result["items"], objectItemSchema) + result["items"] = Object.assign(result["items"], objectItemSchema); } } return result @@ -90,9 +90,9 @@ class Convert { let result = {}; // 判断传入object是对象还是数组。 if (isArray(object)) { - result.items = {} + result.items = {}; } else { - result.properties = {} + result.properties = {}; } // 遍历传入的对象 for (const key in object) { @@ -100,7 +100,7 @@ class Convert { const element = object[key]; // 如果只是undefined。跳过 if (element === undefined) { - continue + continue; } let $id = `${name}/properties/${key}` // 判断当前 element 的值 是否也是对象,如果是就继续递归,不是就赋值给result @@ -111,22 +111,22 @@ class Convert { // 针对空数组和有值的数组做不同处理 if (element.length > 0) { // 如果是数组,那么就取第一项 - let elementItem = element[0] + let elementItem = element[0]; // 创建items对象的基本信息 - result["properties"][key]["items"] = this._value2object(elementItem, `${$id}/items`, key + 'items') + result["properties"][key]["items"] = this._value2object(elementItem, `${$id}/items`, key + 'items'); // 判断第一项是否是对象,且对象属性不为空 if (isObject(elementItem) && !isEmpty(elementItem)) { // 新增的properties才合并进来 - result["properties"][key]["items"] = Object.assign(result["properties"][key]["items"], this._json2schema(elementItem, `${$id}/items`)) + result["properties"][key]["items"] = Object.assign(result["properties"][key]["items"], this._json2schema(elementItem, `${$id}/items`)); } } } else { // 不是数组,递归遍历获取,然后合并对象属性 - result["properties"][key] = Object.assign(result["properties"][key], this._json2schema(element, $id)) + result["properties"][key] = Object.assign(result["properties"][key], this._json2schema(element, $id)); } } else { // 一般属性直接获取基本信息 - result["properties"][key] = this._value2object(element, $id, key) + result["properties"][key] = this._value2object(element, $id, key); } } } @@ -150,29 +150,31 @@ class Convert { // 判断是否为初始化root数据 if (root) { - objectTemplate["$schema"] = this._option.$schema - objectTemplate["title"] = `The Root Schema` - objectTemplate["mock"] = undefined + objectTemplate["$schema"] = this._option.$schema; + objectTemplate["title"] = `The Root Schema`; + objectTemplate["mock"] = undefined; } if (isInteger(value)) { - objectTemplate.type = "integer" + objectTemplate.type = "integer"; } else if (isNumber(value)) { - objectTemplate.type = "number" + objectTemplate.type = "number"; } else if (isString(value)) { - objectTemplate.type = "string" + objectTemplate.type = "string"; } else if (isBoolean(value)) { - objectTemplate.type = "boolean" + objectTemplate.type = "boolean"; } else if (isNull(value)) { - objectTemplate.type = "null" + objectTemplate.type = "null"; } else if (isArray(value)) { - objectTemplate.type = "array" + objectTemplate.type = "array"; + objectTemplate["mock"] = undefined; } else if (isObject(value)) { objectTemplate.type = "object" + objectTemplate["mock"] = undefined; } - return objectTemplate + return objectTemplate; } } -module.exports = Convert +module.exports = Convert; diff --git a/frontend/src/business/components/xpack b/frontend/src/business/components/xpack index e50f046382..e7709b9a34 160000 --- a/frontend/src/business/components/xpack +++ b/frontend/src/business/components/xpack @@ -1 +1 @@ -Subproject commit e50f0463826ac4d7837ea3a237333827774a1b19 +Subproject commit e7709b9a340394e78610b91105b2cec0f1b8289d