diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/TempFileUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/TempFileUtils.java index d46d995c9d..f2680c01cd 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/TempFileUtils.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/TempFileUtils.java @@ -151,7 +151,11 @@ public class TempFileUtils { } } try { - FileUtils.writeByteArrayToFile(createFile, JSON.toJSONString(exportResponse).getBytes()); + if (exportResponse instanceof String exportResponseStr) { + FileUtils.writeByteArrayToFile(createFile, exportResponseStr.getBytes()); + } else { + FileUtils.writeByteArrayToFile(createFile, JSON.toJSONString(exportResponse).getBytes()); + } } catch (Exception e) { LogUtils.error(e); } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/export/JMeterApiScenarioExportResponse.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/export/JMeterApiScenarioExportResponse.java new file mode 100644 index 0000000000..e92905a6b9 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/export/JMeterApiScenarioExportResponse.java @@ -0,0 +1,18 @@ +package io.metersphere.api.dto.export; + +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author wx + */ +@Data +public class JMeterApiScenarioExportResponse extends ApiScenarioExportResponse { + Map scenarioJmxMap = new HashMap<>(); + + public void addJmx(String key, String jmx) { + scenarioJmxMap.put(key, jmx); + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java index 413ce82dd8..3d2146ec50 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java @@ -6,14 +6,19 @@ import io.metersphere.api.constants.ApiScenarioStepRefType; import io.metersphere.api.constants.ApiScenarioStepType; import io.metersphere.api.domain.*; import io.metersphere.api.dto.ApiFile; +import io.metersphere.api.dto.ApiScenarioParamConfig; +import io.metersphere.api.dto.ApiScenarioParseTmpParam; import io.metersphere.api.dto.converter.ApiDefinitionDetail; import io.metersphere.api.dto.converter.ApiDefinitionExportDetail; import io.metersphere.api.dto.converter.ApiScenarioImportParseResult; import io.metersphere.api.dto.converter.ApiScenarioPreImportAnalysisResult; import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest; +import io.metersphere.api.dto.debug.ApiResourceRunRequest; import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.export.ApiScenarioExportResponse; +import io.metersphere.api.dto.export.JMeterApiScenarioExportResponse; import io.metersphere.api.dto.export.MetersphereApiScenarioExportResponse; +import io.metersphere.api.dto.request.MsScenario; import io.metersphere.api.dto.scenario.*; import io.metersphere.api.mapper.*; import io.metersphere.api.parser.ApiScenarioImportParser; @@ -24,6 +29,7 @@ import io.metersphere.api.service.definition.ApiDefinitionModuleService; import io.metersphere.api.service.definition.ApiTestCaseService; import io.metersphere.api.service.scenario.ApiScenarioLogService; import io.metersphere.api.service.scenario.ApiScenarioModuleService; +import io.metersphere.api.service.scenario.ApiScenarioRunService; import io.metersphere.api.service.scenario.ApiScenarioService; import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDefinitionImportUtils; @@ -39,6 +45,7 @@ import io.metersphere.project.service.PermissionCheckService; import io.metersphere.project.utils.FileDownloadUtils; import io.metersphere.sdk.constants.*; import io.metersphere.sdk.dto.ExportMsgDTO; +import io.metersphere.sdk.dto.api.task.ApiRunRetryConfig; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.file.FileRequest; import io.metersphere.sdk.util.*; @@ -118,6 +125,12 @@ public class ApiScenarioDataTransferService { private ExtApiTestCaseMapper extApiTestCaseMapper; @Resource private ExtFileAssociationMapper extFileAssociationMapper; + @Resource + private ApiDefinitionBlobMapper apiDefinitionBlobMapper; + @Resource + private ApiTestCaseBlobMapper apiTestCaseBlobMapper; + @Resource + private ApiScenarioBlobMapper apiScenarioBlobMapper; @Resource private FileService fileService; @@ -1129,14 +1142,25 @@ public class ApiScenarioDataTransferService { if (CollectionUtils.isEmpty(ids)) { return null; } - Map moduleMap = this.apiScenarioModuleService.getImportTreeNodeList(request.getProjectId()).stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath)); - + Map moduleMap; + if (StringUtils.equalsIgnoreCase(exportType, "metersphere")) { + moduleMap = this.apiScenarioModuleService.getImportTreeNodeList(request.getProjectId()).stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath)); + } else { + moduleMap = null; + } String fileFolder = tmpDir.getPath() + File.separatorChar + request.getFileId(); AtomicInteger fileIndex = new AtomicInteger(1); SubListUtils.dealForSubList(ids, 500, subList -> { request.setSelectIds(subList); - ApiScenarioExportResponse exportResponse = this.genMetersphereExportResponse(request, moduleMap); - TempFileUtils.writeExportFile(fileFolder + File.separatorChar + "scenario_" + fileIndex.getAndIncrement() + ".ms", exportResponse); + if (StringUtils.equalsIgnoreCase(exportType, "metersphere")) { + ApiScenarioExportResponse exportResponse = this.genMetersphereExportResponseByMetersphere(request, moduleMap); + TempFileUtils.writeExportFile(fileFolder + File.separatorChar + "scenario_" + fileIndex.getAndIncrement() + ".ms", exportResponse); + } else { + JMeterApiScenarioExportResponse exportResponse = this.genMetersphereExportResponseByJmx(request); + exportResponse.getScenarioJmxMap().forEach((k, v) -> { + TempFileUtils.writeExportFile(fileFolder + File.separatorChar + k + ".jmx", v); + }); + } }); File zipFile = MsFileUtils.zipFile(tmpDir.getPath(), request.getFileId()); if (zipFile == null) { @@ -1167,7 +1191,93 @@ public class ApiScenarioDataTransferService { } } - private ApiScenarioExportResponse genMetersphereExportResponse(ApiScenarioBatchExportRequest request, Map moduleMap) { + @Resource + private ApiScenarioRunService apiScenarioRunService; + @Resource + private ApiExecuteService apiExecuteService; + + private JMeterApiScenarioExportResponse genMetersphereExportResponseByJmx(ApiScenarioBatchExportRequest request) { + JMeterApiScenarioExportResponse response = new JMeterApiScenarioExportResponse(); + List apiIdList = new ArrayList<>(); + List apiCaseList = new ArrayList<>(); + List scenarioList = new ArrayList<>(); + + List apiScenarioDetailList = new ArrayList<>(); + + request.getSelectIds().forEach(id -> { + ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRun(id); + apiScenarioDetailList.add(apiScenarioDetail); + if (CollectionUtils.isNotEmpty(apiScenarioDetail.getSteps())) { + apiScenarioDetail.getSteps().forEach(step -> { + if (StringUtils.equalsIgnoreCase(step.getStepType(), ApiScenarioStepType.API.name())) { + apiIdList.add(step.getResourceId()); + } else if (StringUtils.equalsIgnoreCase(step.getStepType(), ApiScenarioStepType.API_CASE.name())) { + apiCaseList.add(step.getResourceId()); + } else if (StringUtils.equalsIgnoreCase(step.getStepType(), ApiScenarioStepType.API_SCENARIO.name())) { + scenarioList.add(step.getResourceId()); + } + }); + } + }); + Map apiBlobMap = new HashMap<>(); + Map apiTestCaseBlobMap = new HashMap<>(); + Map scenarioBlobMap = new HashMap<>(); + + if (CollectionUtils.isNotEmpty(apiIdList)) { + ApiDefinitionBlobExample apiDefinitionBlobExample = new ApiDefinitionBlobExample(); + apiDefinitionBlobExample.createCriteria().andIdIn(apiIdList); + apiBlobMap = apiDefinitionBlobMapper.selectByExampleWithBLOBs(apiDefinitionBlobExample) + .stream().collect(Collectors.toMap(ApiDefinitionBlob::getId, Function.identity())); + } + + if (CollectionUtils.isNotEmpty(apiCaseList)) { + ApiTestCaseBlobExample example = new ApiTestCaseBlobExample(); + example.createCriteria().andIdIn(apiCaseList); + apiTestCaseBlobMap = apiTestCaseBlobMapper.selectByExampleWithBLOBs(example) + .stream().collect(Collectors.toMap(ApiTestCaseBlob::getId, Function.identity())); + } + + if (CollectionUtils.isNotEmpty(scenarioList)) { + ApiScenarioBlobExample example = new ApiScenarioBlobExample(); + example.createCriteria().andIdIn(scenarioList); + scenarioBlobMap = apiScenarioBlobMapper.selectByExampleWithBLOBs(example) + .stream().collect(Collectors.toMap(ApiScenarioBlob::getId, Function.identity())); + } + + for (ApiScenarioDetail apiScenarioDetail : apiScenarioDetailList) { + String jmx = getJmx(apiScenarioDetail, apiBlobMap, apiTestCaseBlobMap, scenarioBlobMap); + response.addJmx(apiScenarioDetail.getNum() + "_" + apiScenarioDetail.getName(), jmx); + } + return response; + } + + private String getJmx(ApiScenarioDetail apiScenarioDetail, Map apiBlobMap, + Map apiTestCaseBlobMap, Map scenarioBlobMap) { + + String envId = apiScenarioDetail.getEnvironmentId(); + boolean envGroup = apiScenarioDetail.getGrouped(); + + // 解析生成待执行的场景树 + MsScenario msScenario = apiScenarioRunService.getMsScenario(apiScenarioDetail); + + ApiScenarioParseParam parseParam = apiScenarioRunService.getApiScenarioParseParam(apiScenarioDetail); + parseParam.setEnvironmentId(envId); + parseParam.setGrouped(envGroup); + msScenario.setResourceId(apiScenarioDetail.getId()); + + ApiScenarioParseTmpParam tmpParam = apiScenarioRunService.parse(msScenario, apiBlobMap, apiTestCaseBlobMap, scenarioBlobMap, apiScenarioDetail.getSteps(), parseParam); + + ApiResourceRunRequest runRequest = apiScenarioRunService.getApiResourceRunRequest(msScenario, tmpParam); + + ApiScenarioParamConfig parseConfig = apiScenarioRunService.getApiScenarioParamConfig(apiScenarioDetail.getProjectId(), parseParam, tmpParam.getScenarioParseEnvInfo()); + parseConfig.setReportId(StringUtils.EMPTY); + parseConfig.setTaskItemId(StringUtils.EMPTY); + parseConfig.setRetryConfig(new ApiRunRetryConfig()); + + return apiExecuteService.parseExecuteScript(runRequest.getTestElement(), parseConfig); + } + + private ApiScenarioExportResponse genMetersphereExportResponseByMetersphere(ApiScenarioBatchExportRequest request, Map moduleMap) { Project project = projectMapper.selectByPrimaryKey(request.getProjectId()); MetersphereApiScenarioExportResponse response = apiScenarioService.selectAndSortScenarioDetailWithIds(request.getSelectIds(), moduleMap); response.setProjectId(project.getId()); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java index 6e4b0a9458..f22025e833 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java @@ -646,6 +646,59 @@ public class ApiScenarioRunService { return tmpParam; } + public ApiScenarioParseTmpParam parse(MsScenario msScenario, + Map apiBlobMap, + Map apiTestCaseBlobMap, + Map scenarioBlobMap, + List steps, + ApiScenarioParseParam parseParam) { + ApiScenarioParseTmpParam tmpParam = new ApiScenarioParseTmpParam(); + + Map> refResourceMap = new HashMap<>(); + buildRefResourceIdMap(steps, refResourceMap); + Map resourceBlobMap = new HashMap<>(); + List apiIds = refResourceMap.get(ApiScenarioStepType.API.name()); + if (CollectionUtils.isNotEmpty(apiIds)) { + apiIds.forEach(apiId -> { + ApiDefinitionBlob blob = apiBlobMap.get(apiId); + if (blob != null) { + resourceBlobMap.put(blob.getId(), new String(blob.getRequest())); + } + }); + } + List apiCaseIds = refResourceMap.get(ApiScenarioStepType.API_CASE.name()); + if (CollectionUtils.isNotEmpty(apiCaseIds)) { + apiCaseIds.forEach(apiCaseId -> { + ApiTestCaseBlob blob = apiTestCaseBlobMap.get(apiCaseId); + if (blob != null) { + resourceBlobMap.put(blob.getId(), new String(blob.getRequest())); + } + }); + } + List apiScenarioIds = refResourceMap.get(ApiScenarioStepType.API_SCENARIO.name()); + if (CollectionUtils.isNotEmpty(apiScenarioIds)) { + apiScenarioIds.forEach(apiScenarioId -> { + ApiScenarioBlob blob = scenarioBlobMap.get(apiScenarioId); + if (blob != null) { + resourceBlobMap.put(blob.getId(), new String(blob.getConfig())); + } + }); + } + tmpParam.setResourceDetailMap(resourceBlobMap); + // 查询复制的步骤详情 + tmpParam.setStepDetailMap(getStepDetailMap(steps, parseParam.getStepDetails())); + // 获取场景环境相关配置 + tmpParam.setScenarioParseEnvInfo(getScenarioParseEnvInfo(refResourceMap, parseParam.getEnvironmentId(), parseParam.getGrouped())); + parseStep2MsElement(msScenario, steps, tmpParam, msScenario.getResourceId()); + // 设置 HttpElement 的模块信息 + setApiDefinitionExecuteInfo(tmpParam.getUniqueIdStepMap(), tmpParam.getStepTypeHttpElementMap()); + // 设置使用脚本前后置的公共脚本信息 + apiCommonService.setCommonElementEnableCommonScriptInfo(tmpParam.getCommonElements()); + apiCommonService.setScriptElementEnableCommonScriptInfo(tmpParam.getScriptElements()); + + return tmpParam; + } + private void buildRefResourceIdMap(List steps, Map> refResourceIdMap) { for (ApiScenarioStepCommonDTO step : steps) { if (apiScenarioService.isRefOrPartialRef(step.getRefType()) && BooleanUtils.isTrue(step.getEnable())) { diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java index e0549686fd..2ac55ed5ff 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java @@ -169,6 +169,40 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest { MsFileUtils.deleteDir("/tmp/api-scenario-export/"); } + + // jmx export + ApiScenarioBatchExportRequest exportRequest = new ApiScenarioBatchExportRequest(); + String fileId = IDGenerator.nextStr(); + exportRequest.setProjectId(project.getId()); + exportRequest.setFileId(fileId); + exportRequest.setSelectAll(true); + exportRequest.setExportAllRelatedData(false); + MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_POST_EXPORT + "jmeter", exportRequest); + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + JSON.parseObject(returnData, ResultHolder.class).getData().toString(); + Assertions.assertTrue(StringUtils.isNotBlank(fileId)); + List taskList = exportTaskManager.getExportTasks(exportRequest.getProjectId(), null, null, "admin", fileId); + while (CollectionUtils.isEmpty(taskList)) { + Thread.sleep(1000); + taskList = exportTaskManager.getExportTasks(exportRequest.getProjectId(), null, null, "admin", fileId); + } + ExportTask task = taskList.getFirst(); + while (!StringUtils.equalsIgnoreCase(task.getState(), ExportConstants.ExportState.SUCCESS.name())) { + Thread.sleep(1000); + task = exportTaskManager.getExportTasks(exportRequest.getProjectId(), null, null, "admin", fileId).getFirst(); + } + + mvcResult = this.download(exportRequest.getProjectId(), fileId); + + byte[] fileBytes = mvcResult.getResponse().getContentAsByteArray(); + + File zipFile = new File("/tmp/api-scenario-export/downloadFiles.zip"); + FileUtils.writeByteArrayToFile(zipFile, fileBytes); + + File[] files = MsFileUtils.unZipFile(zipFile, "/tmp/api-scenario-export/unzip/"); + assert files != null; + Assertions.assertEquals(files.length, 6); + MsFileUtils.deleteDir("/tmp/api-scenario-export/"); } private MvcResult download(String projectId, String fileId) throws Exception { diff --git a/frontend/src/views/api-test/scenario/components/common/exportScenario/scenarioExportModal.vue b/frontend/src/views/api-test/scenario/components/common/exportScenario/scenarioExportModal.vue index 1c298fbaed..983147cc0c 100644 --- a/frontend/src/views/api-test/scenario/components/common/exportScenario/scenarioExportModal.vue +++ b/frontend/src/views/api-test/scenario/components/common/exportScenario/scenarioExportModal.vue @@ -6,7 +6,18 @@ class="ms-modal-upload ms-modal-medium" :width="400" > -
+
+
+
{{ item.name }}
+
+
+ +
{{ t('apiScenario.export.type.all') }} @@ -47,6 +58,18 @@ import useAppStore from '@/store/modules/app'; import { downloadByteFile, getGenerateId } from '@/utils'; + import { RequestImportFormat } from '@/enums/apiEnum'; + + const platformList: { name: string; value: RequestImportFormat.MeterSphere | RequestImportFormat.Jmeter }[] = [ + { + name: 'MeterSphere', + value: RequestImportFormat.MeterSphere, + }, + { + name: 'Jmeter', + value: RequestImportFormat.Jmeter, + }, + ]; const appStore = useAppStore(); const { t } = useI18n(); @@ -61,11 +84,13 @@ const exportLoading = ref(false); const exportTypeRadio = ref(false); - + const exportPlatform = ref(RequestImportFormat.MeterSphere); function cancelExport() { visible.value = false; } - + function setActiveImportFormat(format: RequestImportFormat.MeterSphere | RequestImportFormat.Jmeter) { + exportPlatform.value = format; + } const websocket = ref(); const reportId = ref(''); const isShowExportingMessage = ref(false); // 正在导出提示显示中 @@ -192,7 +217,7 @@ sort: props.sorter || {}, fileId: reportId.value, }, - 'METERSPHERE' + exportPlatform.value ); showExportingMessage(res); visible.value = false;