From d13ee84e2b5f9f4cfc441132e83db507e4d9b461 Mon Sep 17 00:00:00 2001 From: Jianguo-Genius Date: Tue, 10 Sep 2024 17:50:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95):=20?= =?UTF-8?q?=E5=9C=BA=E6=99=AFJMX=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/constants/ApiScenarioStepType.java | 7 +- .../ApiScenarioBatchOperationController.java | 1 + .../scenario/ApiScenarioController.java | 23 + ...piDefinitionImportDataAnalysisResult.java} | 2 +- ...> ApiDefinitionImportFileParseResult.java} | 2 +- .../ApiScenarioPreImportAnalysisResult.java | 26 + .../ApiScenarioBatchExportRequest.java | 48 ++ .../api/dto/request/MsJMeterComponent.java | 11 + .../dto/scenario/ApiScenarioImportDetail.java | 22 + .../scenario/ApiScenarioImportRequest.java | 26 + .../dto/scenario/ApiScenarioStepRequest.java | 4 - .../api/mapper/ExtApiScenarioMapper.java | 2 + .../api/mapper/ExtApiScenarioMapper.xml | 5 + .../api/parser/ApiDefinitionImportParser.java | 6 +- .../api/parser/ApiScenarioImportParser.java | 21 + .../api/parser/ImportParserFactory.java | 13 +- .../HarParserApiDefinition.java | 16 +- ...HttpApiDefinitionImportAbstractParser.java | 10 +- .../JmeterParserApiDefinition.java | 24 +- .../dataimport/JmeterParserApiScenario.java | 151 +++++ .../MetersphereParserApiDefinition.java | 20 +- .../MetersphereParserApiScenario.java | 29 + ...tmanAbstractParserParserApiDefinition.java | 2 +- .../PostmanParserApiDefinition.java | 14 +- .../Swagger3ParserApiDefinition.java | 14 +- .../api/parser/ms/MsTestElementParser.java | 25 + .../api/parser/ms/ThreadGroupConverter.java | 10 +- .../ApiScenarioDataTransferService.java | 547 ++++++++++++++++++ .../ApiDefinitionImportService.java | 20 +- .../scenario/ApiScenarioModuleService.java | 8 + .../service/scenario/ApiScenarioService.java | 20 +- .../api/utils/ApiScenarioImportUtils.java | 25 + .../ApiDefinitionControllerTests.java | 3 +- ...cenarioControllerImportAndExportTests.java | 97 ++++ .../metersphere/api/parser/ParserTests.java | 2 +- .../file/import-scenario/jmeter/simple.jmx | 267 +++++++++ .../system/constants/ExportConstants.java | 2 +- 37 files changed, 1434 insertions(+), 91 deletions(-) rename backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/{ApiImportDataAnalysisResult.java => ApiDefinitionImportDataAnalysisResult.java} (95%) rename backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/{ApiImportFileParseResult.java => ApiDefinitionImportFileParseResult.java} (95%) create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiScenarioPreImportAnalysisResult.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiScenarioBatchExportRequest.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/dto/request/MsJMeterComponent.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioImportDetail.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioImportRequest.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/parser/ApiScenarioImportParser.java rename backend/services/api-test/src/main/java/io/metersphere/api/parser/api/{ => dataimport}/HarParserApiDefinition.java (96%) rename backend/services/api-test/src/main/java/io/metersphere/api/parser/api/{ => dataimport}/HttpApiDefinitionImportAbstractParser.java (92%) rename backend/services/api-test/src/main/java/io/metersphere/api/parser/api/{ => dataimport}/JmeterParserApiDefinition.java (87%) create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/JmeterParserApiScenario.java rename backend/services/api-test/src/main/java/io/metersphere/api/parser/api/{ => dataimport}/MetersphereParserApiDefinition.java (89%) create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/MetersphereParserApiScenario.java rename backend/services/api-test/src/main/java/io/metersphere/api/parser/api/{ => dataimport}/PostmanAbstractParserParserApiDefinition.java (99%) rename backend/services/api-test/src/main/java/io/metersphere/api/parser/api/{ => dataimport}/PostmanParserApiDefinition.java (88%) rename backend/services/api-test/src/main/java/io/metersphere/api/parser/api/{ => dataimport}/Swagger3ParserApiDefinition.java (98%) create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/utils/ApiScenarioImportUtils.java create mode 100644 backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java create mode 100644 backend/services/api-test/src/test/resources/file/import-scenario/jmeter/simple.jmx diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiScenarioStepType.java b/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiScenarioStepType.java index d4978692a4..37ec8a7ec3 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiScenarioStepType.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiScenarioStepType.java @@ -42,7 +42,12 @@ public enum ApiScenarioStepType { /** * 脚本操作 */ - SCRIPT(StepTypeGroup.REQUEST); + SCRIPT(StepTypeGroup.REQUEST), + + /** + * JMeter插件 + */ + JMETER_COMPONENT(StepTypeGroup.REQUEST); private enum StepTypeGroup { diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioBatchOperationController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioBatchOperationController.java index 66cb98c405..0ba1034e31 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioBatchOperationController.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioBatchOperationController.java @@ -98,6 +98,7 @@ public class ApiScenarioBatchOperationController { @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE) public void batchRun(@Validated @RequestBody ApiScenarioBatchRunRequest request) { + apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name()); apiScenarioBatchRunService.asyncBatchRun(request, SessionUtils.getUserId()); } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioController.java index ccd32ba66d..ac5b42b204 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioController.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioController.java @@ -6,11 +6,13 @@ import io.metersphere.api.constants.ApiResource; import io.metersphere.api.domain.ApiScenario; import io.metersphere.api.dto.ReferenceDTO; import io.metersphere.api.dto.ReferenceRequest; +import io.metersphere.api.dto.definition.ApiScenarioBatchExportRequest; import io.metersphere.api.dto.definition.ExecutePageRequest; import io.metersphere.api.dto.definition.ExecuteReportDTO; import io.metersphere.api.dto.request.ApiTransferRequest; import io.metersphere.api.dto.scenario.*; import io.metersphere.api.service.ApiFileResourceService; +import io.metersphere.api.service.ApiScenarioDataTransferService; import io.metersphere.api.service.ApiValidateService; import io.metersphere.api.service.scenario.ApiScenarioLogService; import io.metersphere.api.service.scenario.ApiScenarioNoticeService; @@ -58,6 +60,8 @@ public class ApiScenarioController { private FileModuleService fileModuleService; @Resource private ApiFileResourceService apiFileResourceService; + @Resource + private ApiScenarioDataTransferService apiScenarioDataTransferService; @PostMapping("/page") @Operation(summary = "接口测试-接口场景管理-场景列表(deleted 状态为 1 时为回收站数据)") @@ -307,4 +311,23 @@ public class ApiScenarioController { StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "id desc"); return PageUtils.setPageInfo(page, apiScenarioService.getReference(request)); } + + + @PostMapping(value = "/export/{type}") + @Operation(summary = "接口测试-接口场景管理-场景-导出场景") + @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_EXPORT) + @CheckOwner(resourceId = "#request.getResourceId()", resourceType = "api_scenario") + public String export(@RequestBody ApiScenarioBatchExportRequest request, @PathVariable String type) { + return apiScenarioDataTransferService.exportScenario(request, type, SessionUtils.getUserId()); + } + + + @PostMapping(value = "/import", consumes = {"multipart/form-data"}) + @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_IMPORT) + @Operation(summary = "接口测试-接口场景管理-场景-导入场景") + @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") + public void testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiScenarioImportRequest request) { + request.setOperator(SessionUtils.getUserId()); + apiScenarioDataTransferService.importScenario(file, request); + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiImportDataAnalysisResult.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiDefinitionImportDataAnalysisResult.java similarity index 95% rename from backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiImportDataAnalysisResult.java rename to backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiDefinitionImportDataAnalysisResult.java index 89d31158cc..222db825db 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiImportDataAnalysisResult.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiDefinitionImportDataAnalysisResult.java @@ -14,7 +14,7 @@ import java.util.Map; * api导入数据分析结果 */ @Data -public class ApiImportDataAnalysisResult { +public class ApiDefinitionImportDataAnalysisResult { // 新增接口数据 List insertApiList = new ArrayList<>(); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiImportFileParseResult.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiDefinitionImportFileParseResult.java similarity index 95% rename from backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiImportFileParseResult.java rename to backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiDefinitionImportFileParseResult.java index 78acee3bff..fc88e13f6e 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiImportFileParseResult.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiDefinitionImportFileParseResult.java @@ -13,7 +13,7 @@ import java.util.Map; * api导入文件解析结果 */ @Data -public class ApiImportFileParseResult { +public class ApiDefinitionImportFileParseResult { // 接口定义数据 private List data = new ArrayList<>(); // 用例数据 diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiScenarioPreImportAnalysisResult.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiScenarioPreImportAnalysisResult.java new file mode 100644 index 0000000000..e31b6b9e7a --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiScenarioPreImportAnalysisResult.java @@ -0,0 +1,26 @@ +package io.metersphere.api.dto.converter; + +import io.metersphere.api.dto.scenario.ApiScenarioImportDetail; +import io.metersphere.system.dto.sdk.BaseTreeNode; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 接口导入数据准备结果 + */ +@Data +public class ApiScenarioPreImportAnalysisResult { + + @Schema(description = "需要创建的模块数据") + List insertModuleList = new ArrayList<>(); + + @Schema(description = "需要新增的场景") + List insertApiScenarioData = new ArrayList<>(); + + @Schema(description = "需要更新的场景") + List updateApiScenarioData = new ArrayList<>(); + +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiScenarioBatchExportRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiScenarioBatchExportRequest.java new file mode 100644 index 0000000000..3122fde2e6 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiScenarioBatchExportRequest.java @@ -0,0 +1,48 @@ +package io.metersphere.api.dto.definition; + +import com.google.common.base.CaseFormat; +import io.metersphere.api.dto.scenario.ApiScenarioBatchRequest; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.commons.lang3.StringUtils; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Map; + +/** + * @author lan + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class ApiScenarioBatchExportRequest extends ApiScenarioBatchRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "文件id") + @NotBlank + private String fileId; + + @Schema(description = "排序字段(model中的字段 : asc/desc)") + private Map<@Valid @Pattern(regexp = "^[A-Za-z]+$") String, @Valid @NotBlank String> sort; + + public String getSortString() { + if (sort == null || sort.isEmpty()) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : sort.entrySet()) { + String column = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, entry.getKey()); + sb.append(column) + .append(StringUtils.SPACE) + .append(StringUtils.equalsIgnoreCase(entry.getValue(), "DESC") ? "DESC" : "ASC") + .append(","); + } + return sb.substring(0, sb.length() - 1); + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/request/MsJMeterComponent.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/request/MsJMeterComponent.java new file mode 100644 index 0000000000..e7a01fef17 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/request/MsJMeterComponent.java @@ -0,0 +1,11 @@ +package io.metersphere.api.dto.request; + +import io.metersphere.plugin.api.spi.AbstractMsTestElement; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class MsJMeterComponent extends AbstractMsTestElement { + +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioImportDetail.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioImportDetail.java new file mode 100644 index 0000000000..9fb6d94435 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioImportDetail.java @@ -0,0 +1,22 @@ +package io.metersphere.api.dto.scenario; + +import io.metersphere.api.domain.ApiScenario; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; +import java.util.Map; + +@Data +@EqualsAndHashCode(callSuper = false) +public class ApiScenarioImportDetail extends ApiScenario { + @Schema(description = "场景配置信息") + private ScenarioConfig scenarioConfig; + @Schema(description = "模块路径") + private String modulePath; + @Schema(description = "步骤详情") + private Map stepDetails; + @Schema(description = "步骤集合") + private List steps; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioImportRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioImportRequest.java new file mode 100644 index 0000000000..15b51450e3 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioImportRequest.java @@ -0,0 +1,26 @@ +package io.metersphere.api.dto.scenario; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class ApiScenarioImportRequest { + + @Schema(description = "导入的模块id") + private String moduleId; + + @Schema(description = "导入的项目id") + @NotBlank + private String projectId; + + @Schema(description = "导入的类型 暂定 METERSPHERE JMETER") + @NotBlank + private String type; + + @Schema(description = "是否覆盖数据") + private boolean coverData; + + @Schema(description = "操作人") + private String operator; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioStepRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioStepRequest.java index fd49d41ce7..589e80f2d9 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioStepRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioStepRequest.java @@ -2,10 +2,6 @@ package io.metersphere.api.dto.scenario; import lombok.Data; -/** - * @Author: jianxing - * @CreateTime: 2024-01-10 11:24 - */ @Data public class ApiScenarioStepRequest extends ApiScenarioStepCommonDTO { /** diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiScenarioMapper.java b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiScenarioMapper.java index d66582d5d1..c4eac081eb 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiScenarioMapper.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiScenarioMapper.java @@ -94,4 +94,6 @@ public interface ExtApiScenarioMapper { List getListBySelectModules(@Param("isRepeat") boolean isRepeat, @Param("projectId") String projectId, @Param("moduleIds") List moduleIds, @Param("testPlanId") String testPlanId); List getListBySelectIds(@Param("projectId") String projectId, @Param("ids") List ids, @Param("testPlanId") String testPlanId); + + List selectBaseInfoByModuleId(String id); } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiScenarioMapper.xml b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiScenarioMapper.xml index 7230529e37..7ccdfe4ffc 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiScenarioMapper.xml +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiScenarioMapper.xml @@ -748,5 +748,10 @@ #{id} + diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/ApiDefinitionImportParser.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/ApiDefinitionImportParser.java index 27a069c5e0..636e9780b8 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/ApiDefinitionImportParser.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/ApiDefinitionImportParser.java @@ -2,8 +2,8 @@ package io.metersphere.api.parser; import io.metersphere.api.dto.converter.ApiDefinitionDetail; -import io.metersphere.api.dto.converter.ApiImportDataAnalysisResult; -import io.metersphere.api.dto.converter.ApiImportFileParseResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportDataAnalysisResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportFileParseResult; import io.metersphere.api.dto.request.ImportRequest; import java.io.InputStream; @@ -28,6 +28,6 @@ public interface ApiDefinitionImportParser { * @param existenceApiDefinitionList 数据库中已存在的数据 * @return 需要入库的模块、需要入库的接口、需要更新的接口 */ - ApiImportDataAnalysisResult generateInsertAndUpdateData(ApiImportFileParseResult importParser, List existenceApiDefinitionList); + ApiDefinitionImportDataAnalysisResult generateInsertAndUpdateData(ApiDefinitionImportFileParseResult importParser, List existenceApiDefinitionList); } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/ApiScenarioImportParser.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/ApiScenarioImportParser.java new file mode 100644 index 0000000000..7cc7715ee8 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/ApiScenarioImportParser.java @@ -0,0 +1,21 @@ +package io.metersphere.api.parser; + + +import io.metersphere.api.dto.scenario.ApiScenarioImportDetail; +import io.metersphere.api.dto.scenario.ApiScenarioImportRequest; + +import java.io.InputStream; +import java.util.List; + +public interface ApiScenarioImportParser { + + /** + * 解析导入文件 + * + * @param source 导入文件流 + * @param request 导入的请求参数 + * @return 解析后的数据 + */ + List parse(InputStream source, ApiScenarioImportRequest request) throws Exception; + +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/ImportParserFactory.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/ImportParserFactory.java index cef23d0c1d..f5edc76e64 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/ImportParserFactory.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/ImportParserFactory.java @@ -1,11 +1,11 @@ package io.metersphere.api.parser; import io.metersphere.api.constants.ApiImportPlatform; -import io.metersphere.api.parser.api.*; +import io.metersphere.api.parser.api.dataimport.*; import org.apache.commons.lang3.StringUtils; public class ImportParserFactory { - public static ApiDefinitionImportParser getImportParser(String platform) { + public static ApiDefinitionImportParser getApiDefinitionImportParser(String platform) { if (StringUtils.equalsIgnoreCase(ApiImportPlatform.Swagger3.name(), platform)) { return new Swagger3ParserApiDefinition(); } else if (StringUtils.equalsIgnoreCase(ApiImportPlatform.Postman.name(), platform)) { @@ -19,4 +19,13 @@ public class ImportParserFactory { } return null; } + + public static ApiScenarioImportParser getApiScenarioImportParser(String platform) { + if (StringUtils.equalsIgnoreCase(ApiImportPlatform.MeterSphere.name(), platform)) { + return new MetersphereParserApiScenario(); + } else if (StringUtils.equalsIgnoreCase(ApiImportPlatform.Jmeter.name(), platform)) { + return new JmeterParserApiScenario(); + } + return null; + } } \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/HarParserApiDefinition.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/HarParserApiDefinition.java similarity index 96% rename from backend/services/api-test/src/main/java/io/metersphere/api/parser/api/HarParserApiDefinition.java rename to backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/HarParserApiDefinition.java index 6039ec85c6..145bb8aa55 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/HarParserApiDefinition.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/HarParserApiDefinition.java @@ -1,10 +1,10 @@ -package io.metersphere.api.parser.api; +package io.metersphere.api.parser.api.dataimport; import io.metersphere.api.domain.ApiDefinitionBlob; import io.metersphere.api.dto.converter.ApiDefinitionDetail; -import io.metersphere.api.dto.converter.ApiImportDataAnalysisResult; -import io.metersphere.api.dto.converter.ApiImportFileParseResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportDataAnalysisResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportFileParseResult; import io.metersphere.api.dto.converter.ExistenceApiDefinitionDetail; import io.metersphere.api.dto.definition.HttpResponse; import io.metersphere.api.dto.definition.ResponseBody; @@ -39,10 +39,10 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -public class HarParserApiDefinition extends HttpApiDefinitionImportAbstractParser { +public class HarParserApiDefinition extends HttpApiDefinitionImportAbstractParser { @Override - public ApiImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception { + public ApiDefinitionImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception { Har har = null; try { har = HarUtils.read(source); @@ -53,14 +53,14 @@ public class HarParserApiDefinition extends HttpApiDefinitionImportAbstractParse if (ObjectUtils.isEmpty(har) || har.log == null) { throw new MSException("解析失败,请确认选择的是 Har 格式!"); } - ApiImportFileParseResult definitionImport = new ApiImportFileParseResult(); + ApiDefinitionImportFileParseResult definitionImport = new ApiDefinitionImportFileParseResult(); definitionImport.setData(parseRequests(har, request)); return definitionImport; } @Override - public ApiImportDataAnalysisResult generateInsertAndUpdateData(ApiImportFileParseResult importParser, List existenceApiDefinitionList) { - ApiImportDataAnalysisResult insertAndUpdateData = super.generateInsertAndUpdateData(importParser, existenceApiDefinitionList); + public ApiDefinitionImportDataAnalysisResult generateInsertAndUpdateData(ApiDefinitionImportFileParseResult importParser, List existenceApiDefinitionList) { + ApiDefinitionImportDataAnalysisResult insertAndUpdateData = super.generateInsertAndUpdateData(importParser, existenceApiDefinitionList); ApiDefinitionBlobMapper apiDefinitionBlobMapper = CommonBeanFactory.getBean(ApiDefinitionBlobMapper.class); for (ExistenceApiDefinitionDetail definitionDetail : insertAndUpdateData.getExistenceApiList()) { diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/HttpApiDefinitionImportAbstractParser.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/HttpApiDefinitionImportAbstractParser.java similarity index 92% rename from backend/services/api-test/src/main/java/io/metersphere/api/parser/api/HttpApiDefinitionImportAbstractParser.java rename to backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/HttpApiDefinitionImportAbstractParser.java index ecb1521bcc..3c30ab20b5 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/HttpApiDefinitionImportAbstractParser.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/HttpApiDefinitionImportAbstractParser.java @@ -1,9 +1,9 @@ -package io.metersphere.api.parser.api; +package io.metersphere.api.parser.api.dataimport; import io.metersphere.api.dto.converter.ApiDefinitionDetail; -import io.metersphere.api.dto.converter.ApiImportDataAnalysisResult; -import io.metersphere.api.dto.converter.ApiImportFileParseResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportDataAnalysisResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportFileParseResult; import io.metersphere.api.dto.definition.ApiDefinitionMockDTO; import io.metersphere.api.dto.definition.ApiTestCaseDTO; import io.metersphere.api.dto.request.ImportRequest; @@ -34,12 +34,12 @@ import java.util.stream.Collectors; public abstract class HttpApiDefinitionImportAbstractParser implements ApiDefinitionImportParser { @Override - public ApiImportDataAnalysisResult generateInsertAndUpdateData(ApiImportFileParseResult importParser, List existenceApiDefinitionList) { + public ApiDefinitionImportDataAnalysisResult generateInsertAndUpdateData(ApiDefinitionImportFileParseResult importParser, List existenceApiDefinitionList) { // API类型,通过 Method & Path 组合判断,接口是否存在 Map savedApiDefinitionMap = existenceApiDefinitionList.stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t, (oldValue, newValue) -> newValue)); Map importDataMap = importParser.getData().stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t, (oldValue, newValue) -> newValue)); - ApiImportDataAnalysisResult insertAndUpdateData = new ApiImportDataAnalysisResult(); + ApiDefinitionImportDataAnalysisResult insertAndUpdateData = new ApiDefinitionImportDataAnalysisResult(); importDataMap.forEach((key, api) -> { if (savedApiDefinitionMap.containsKey(key)) { diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/JmeterParserApiDefinition.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/JmeterParserApiDefinition.java similarity index 87% rename from backend/services/api-test/src/main/java/io/metersphere/api/parser/api/JmeterParserApiDefinition.java rename to backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/JmeterParserApiDefinition.java index 6d6b0a7f5b..67eca77146 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/JmeterParserApiDefinition.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/JmeterParserApiDefinition.java @@ -1,10 +1,10 @@ -package io.metersphere.api.parser.api; +package io.metersphere.api.parser.api.dataimport; import io.metersphere.api.constants.ApiConstants; import io.metersphere.api.domain.ApiDefinition; import io.metersphere.api.dto.converter.ApiDefinitionDetail; -import io.metersphere.api.dto.converter.ApiImportDataAnalysisResult; -import io.metersphere.api.dto.converter.ApiImportFileParseResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportDataAnalysisResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportFileParseResult; import io.metersphere.api.dto.definition.ApiTestCaseDTO; import io.metersphere.api.dto.request.ImportRequest; import io.metersphere.api.dto.request.http.MsHTTPElement; @@ -29,11 +29,11 @@ import java.lang.reflect.Field; import java.util.*; import java.util.stream.Collectors; -public class JmeterParserApiDefinition implements ApiDefinitionImportParser { +public class JmeterParserApiDefinition implements ApiDefinitionImportParser { @Override - public ApiImportFileParseResult parse(InputStream inputSource, ImportRequest request) throws Exception { + public ApiDefinitionImportFileParseResult parse(InputStream inputSource, ImportRequest request) throws Exception { try { Object scriptWrapper = MsSaveService.loadElement(inputSource); HashTree hashTree = this.getHashTree(scriptWrapper); @@ -49,9 +49,9 @@ public class JmeterParserApiDefinition implements ApiDefinitionImportParser> allImportDetails, String moduleName) { + private ApiDefinitionImportFileParseResult genApiDefinitionImport(LinkedHashMap> allImportDetails, String moduleName) { Map> groupWithUniqueIdentification = this.mergeApiCaseWithUniqueIdentification(allImportDetails); - ApiImportFileParseResult returnDTO = new ApiImportFileParseResult(); + ApiDefinitionImportFileParseResult returnDTO = new ApiDefinitionImportFileParseResult(); groupWithUniqueIdentification.forEach((definitionImportDetail, caseData) -> { String apiID = IDGenerator.nextStr(); definitionImportDetail.setId(apiID); @@ -142,17 +142,17 @@ public class JmeterParserApiDefinition implements ApiDefinitionImportParser existenceApiDefinitionList) { + public ApiDefinitionImportDataAnalysisResult generateInsertAndUpdateData(ApiDefinitionImportFileParseResult importParser, List existenceApiDefinitionList) { List importDataList = importParser.getData(); Map> protocolImportMap = importDataList.stream().collect(Collectors.groupingBy(ApiDefinitionDetail::getProtocol)); Map> existenceProtocolMap = existenceApiDefinitionList.stream().collect(Collectors.groupingBy(ApiDefinitionDetail::getProtocol)); - ApiImportDataAnalysisResult insertAndUpdateData = new ApiImportDataAnalysisResult(); + ApiDefinitionImportDataAnalysisResult insertAndUpdateData = new ApiDefinitionImportDataAnalysisResult(); for (Map.Entry> entry : protocolImportMap.entrySet()) { List importList = entry.getValue(); List existenceList = existenceProtocolMap.get(entry.getKey()); - ApiImportDataAnalysisResult httpResult = compareApiData(importList, existenceList, entry.getKey()); + ApiDefinitionImportDataAnalysisResult httpResult = compareApiData(importList, existenceList, entry.getKey()); insertAndUpdateData.getInsertApiList().addAll(httpResult.getInsertApiList()); insertAndUpdateData.getExistenceApiList().addAll(httpResult.getExistenceApiList()); } @@ -160,8 +160,8 @@ public class JmeterParserApiDefinition implements ApiDefinitionImportParser importData, List existenceApiData, String protocol) { - ApiImportDataAnalysisResult insertAndUpdateData = new ApiImportDataAnalysisResult(); + private ApiDefinitionImportDataAnalysisResult compareApiData(List importData, List existenceApiData, String protocol) { + ApiDefinitionImportDataAnalysisResult insertAndUpdateData = new ApiDefinitionImportDataAnalysisResult(); if (CollectionUtils.isEmpty(importData)) { return insertAndUpdateData; diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/JmeterParserApiScenario.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/JmeterParserApiScenario.java new file mode 100644 index 0000000000..1201fa942c --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/JmeterParserApiScenario.java @@ -0,0 +1,151 @@ +package io.metersphere.api.parser.api.dataimport; + +import io.metersphere.api.constants.ApiScenarioStatus; +import io.metersphere.api.constants.ApiScenarioStepType; +import io.metersphere.api.dto.request.MsJMeterComponent; +import io.metersphere.api.dto.request.controller.*; +import io.metersphere.api.dto.request.http.MsHTTPElement; +import io.metersphere.api.dto.scenario.ApiScenarioImportDetail; +import io.metersphere.api.dto.scenario.ApiScenarioImportRequest; +import io.metersphere.api.dto.scenario.ApiScenarioStepRequest; +import io.metersphere.api.parser.ApiScenarioImportParser; +import io.metersphere.api.parser.jmeter.xstream.MsSaveService; +import io.metersphere.api.parser.ms.MsTestElementParser; +import io.metersphere.plugin.api.spi.AbstractMsProtocolTestElement; +import io.metersphere.plugin.api.spi.AbstractMsTestElement; +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.LogUtils; +import io.metersphere.system.uid.IDGenerator; +import lombok.Data; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jorphan.collections.HashTree; + +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.*; + +public class JmeterParserApiScenario implements ApiScenarioImportParser { + + + @Override + public List parse(InputStream inputSource, ApiScenarioImportRequest request) throws Exception { + try { + Object scriptWrapper = MsSaveService.loadElement(inputSource); + HashTree hashTree = this.getHashTree(scriptWrapper); + MsTestElementParser parser = new MsTestElementParser(); + AbstractMsTestElement msTestElement = parser.parse(hashTree); + Map polymorphicNameMap = parser.getPolymorphicNameMap(request.getProjectId()); + String scenarioName = StringUtils.trim(parser.parseTestPlanName(hashTree)); + return Collections.singletonList(this.parseImportFile(request.getProjectId(), msTestElement, polymorphicNameMap, scenarioName)); + } catch (Exception e) { + LogUtils.error(e); + throw new MSException("当前JMX版本不兼容"); + } + } + + private ApiScenarioImportDetail parseImportFile(String projectId, AbstractMsTestElement msElementList, Map polymorphicNameMap, String scenarioName) { + ApiScenarioImportDetail apiScenarioDetail = new ApiScenarioImportDetail(); + apiScenarioDetail.setName(scenarioName); + apiScenarioDetail.setPriority("P0"); + apiScenarioDetail.setStatus(ApiScenarioStatus.UNDERWAY.name()); + apiScenarioDetail.setDeleted(false); + apiScenarioDetail.setLatest(true); + apiScenarioDetail.setProjectId(projectId); + + ApiScenarioStepParseResult stepParseResult = this.parseScenarioStep(msElementList.getChildren(), projectId, polymorphicNameMap); + + apiScenarioDetail.setSteps(stepParseResult.getStepList()); + apiScenarioDetail.setStepDetails(stepParseResult.getStepDetails()); + + apiScenarioDetail.setStepTotal(CollectionUtils.size(apiScenarioDetail.getSteps())); + return apiScenarioDetail; + } + + private ApiScenarioStepParseResult parseScenarioStep(List msElementList, String projectId, Map polymorphicNameMap) { + ApiScenarioStepParseResult parseResult = new ApiScenarioStepParseResult(); + for (AbstractMsTestElement msTestElement : msElementList) { + ApiScenarioStepRequest apiScenarioStep = new ApiScenarioStepRequest(); + apiScenarioStep.setId(IDGenerator.nextStr()); + apiScenarioStep.setProjectId(projectId); + apiScenarioStep.setName(msTestElement.getName()); + apiScenarioStep.setUniqueId(IDGenerator.nextStr()); + + byte[] stepBlobContent = null; + if (msTestElement instanceof MsHTTPElement msHTTPElement) { + apiScenarioStep.setConfig(JSON.toJSONString(new ProtocolConfig("HTTP", msHTTPElement.getMethod()))); + apiScenarioStep.setStepType(ApiScenarioStepType.CUSTOM_REQUEST.name()); + stepBlobContent = JSON.toJSONString(msTestElement).getBytes(); + } else if (msTestElement instanceof AbstractMsProtocolTestElement) { + apiScenarioStep.setStepType(ApiScenarioStepType.CUSTOM_REQUEST.name()); + String protocol = polymorphicNameMap.get(msTestElement.getClass().getSimpleName()); + apiScenarioStep.setConfig(JSON.toJSONString(new ProtocolConfig(protocol, protocol))); + stepBlobContent = JSON.toJSONString(msTestElement).getBytes(); + } else if (msTestElement instanceof MsJMeterComponent) { + apiScenarioStep.setStepType(this.getStepType(msTestElement)); + apiScenarioStep.setConfig("{}"); + } else { + apiScenarioStep.setStepType(this.getStepType(msTestElement)); + apiScenarioStep.setConfig(JSON.toJSONString(msTestElement)); + } + + parseResult.getStepList().add(apiScenarioStep); + if (stepBlobContent != null) { + parseResult.getStepDetails().put(apiScenarioStep.getId(), stepBlobContent); + } + + if (!(msTestElement instanceof AbstractMsProtocolTestElement) && CollectionUtils.isNotEmpty(msTestElement.getChildren())) { + //非请求类型组件,继续处理子组件 + ApiScenarioStepParseResult childParseResult = parseScenarioStep(msTestElement.getChildren(), projectId, polymorphicNameMap); + apiScenarioStep.setChildren(childParseResult.getStepList()); + if (MapUtils.isNotEmpty(childParseResult.getStepDetails())) { + parseResult.getStepDetails().putAll(childParseResult.getStepDetails()); + } + } + } + return parseResult; + } + + private String getStepType(AbstractMsTestElement msTestElement) { + if (msTestElement instanceof MsLoopController) { + return ApiScenarioStepType.LOOP_CONTROLLER.name(); + } else if (msTestElement instanceof MsOnceOnlyController) { + return ApiScenarioStepType.ONCE_ONLY_CONTROLLER.name(); + } else if (msTestElement instanceof MsIfController) { + return ApiScenarioStepType.IF_CONTROLLER.name(); + } else if (msTestElement instanceof MsConstantTimerController) { + return ApiScenarioStepType.CONSTANT_TIMER.name(); + } else if (msTestElement instanceof MsScriptElement) { + return ApiScenarioStepType.SCRIPT.name(); + } else { + return ApiScenarioStepType.JMETER_COMPONENT.name(); + } + } + + private HashTree getHashTree(Object scriptWrapper) throws Exception { + Field field = scriptWrapper.getClass().getDeclaredField("testPlan"); + field.setAccessible(true); + return (HashTree) field.get(scriptWrapper); + } +} + +@Data +class ApiScenarioStepParseResult { + private List stepList = new ArrayList<>(); + private Map stepDetails = new HashMap<>(); +} + +class ProtocolConfig { + String id; + String name; + boolean enable = true; + String protocol; + String method; + + public ProtocolConfig(String protocol, String method) { + this.protocol = protocol; + this.method = method; + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/MetersphereParserApiDefinition.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/MetersphereParserApiDefinition.java similarity index 89% rename from backend/services/api-test/src/main/java/io/metersphere/api/parser/api/MetersphereParserApiDefinition.java rename to backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/MetersphereParserApiDefinition.java index 2369a554a0..ecab845443 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/MetersphereParserApiDefinition.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/MetersphereParserApiDefinition.java @@ -1,10 +1,10 @@ -package io.metersphere.api.parser.api; +package io.metersphere.api.parser.api.dataimport; import io.metersphere.api.dto.converter.ApiDefinitionDetail; import io.metersphere.api.dto.converter.ApiDefinitionExportDetail; -import io.metersphere.api.dto.converter.ApiImportDataAnalysisResult; -import io.metersphere.api.dto.converter.ApiImportFileParseResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportDataAnalysisResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportFileParseResult; import io.metersphere.api.dto.definition.ApiDefinitionMockDTO; import io.metersphere.api.dto.definition.ApiTestCaseDTO; import io.metersphere.api.dto.export.MetersphereApiExportResponse; @@ -25,10 +25,10 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -public class MetersphereParserApiDefinition implements ApiDefinitionImportParser { +public class MetersphereParserApiDefinition implements ApiDefinitionImportParser { @Override - public ApiImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception { + public ApiDefinitionImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception { MetersphereApiExportResponse metersphereApiExportResponse = null; try { metersphereApiExportResponse = ApiDataUtils.parseObject(source, MetersphereApiExportResponse.class); @@ -43,10 +43,10 @@ public class MetersphereParserApiDefinition implements ApiDefinitionImportParser } @Override - public ApiImportDataAnalysisResult generateInsertAndUpdateData(ApiImportFileParseResult importParser, List existenceAllApiList) { + public ApiDefinitionImportDataAnalysisResult generateInsertAndUpdateData(ApiDefinitionImportFileParseResult importParser, List existenceAllApiList) { Map> existenceProtocolMap = new HashMap<>(); - ApiImportDataAnalysisResult insertAndUpdateData = new ApiImportDataAnalysisResult(); + ApiDefinitionImportDataAnalysisResult insertAndUpdateData = new ApiDefinitionImportDataAnalysisResult(); for (ApiDefinitionDetail apiDefinitionDetail : existenceAllApiList) { String protocol = apiDefinitionDetail.getProtocol().toUpperCase(); @@ -109,7 +109,7 @@ public class MetersphereParserApiDefinition implements ApiDefinitionImportParser return insertAndUpdateData; } - private void addTestCaseAndMock(ApiImportDataAnalysisResult insertAndUpdateData, String apiId, List caseList, List mockDTOList) { + private void addTestCaseAndMock(ApiDefinitionImportDataAnalysisResult insertAndUpdateData, String apiId, List caseList, List mockDTOList) { if (CollectionUtils.isNotEmpty(caseList)) { insertAndUpdateData.getApiIdAndTestCaseMap().put(apiId, caseList); } @@ -118,9 +118,9 @@ public class MetersphereParserApiDefinition implements ApiDefinitionImportParser } } - private ApiImportFileParseResult genApiDefinitionImport(List apiDefinitions) { + private ApiDefinitionImportFileParseResult genApiDefinitionImport(List apiDefinitions) { List distinctImportList = this.mergeApiCaseWithUniqueIdentification(apiDefinitions); - ApiImportFileParseResult returnDTO = new ApiImportFileParseResult(); + ApiDefinitionImportFileParseResult returnDTO = new ApiDefinitionImportFileParseResult(); distinctImportList.forEach(definitionImportDetail -> { String apiID = IDGenerator.nextStr(); definitionImportDetail.setId(apiID); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/MetersphereParserApiScenario.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/MetersphereParserApiScenario.java new file mode 100644 index 0000000000..519e5e70fc --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/MetersphereParserApiScenario.java @@ -0,0 +1,29 @@ +package io.metersphere.api.parser.api.dataimport; + + +import io.metersphere.api.dto.scenario.ApiScenarioImportDetail; +import io.metersphere.api.dto.scenario.ApiScenarioImportRequest; +import io.metersphere.api.parser.ApiScenarioImportParser; + +import java.io.InputStream; +import java.util.List; + +public class MetersphereParserApiScenario implements ApiScenarioImportParser { + + @Override + public List parse(InputStream source, ApiScenarioImportRequest request) throws Exception { + return null; + // MetersphereApiExportResponse metersphereApiExportResponse = null; + // try { + // metersphereApiExportResponse = ApiDataUtils.parseObject(source, MetersphereApiExportResponse.class); + // } catch (Exception e) { + // LogUtils.error(e.getMessage(), e); + // throw new MSException(e.getMessage()); + // } + // if (metersphereApiExportResponse == null) { + // throw new MSException("解析失败,请确认选择的是 Metersphere 格式!"); + // } + // return this.genApiDefinitionImport(metersphereApiExportResponse.getApiDefinitions()); + } + +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/PostmanAbstractParserParserApiDefinition.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/PostmanAbstractParserParserApiDefinition.java similarity index 99% rename from backend/services/api-test/src/main/java/io/metersphere/api/parser/api/PostmanAbstractParserParserApiDefinition.java rename to backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/PostmanAbstractParserParserApiDefinition.java index d7bd33ddd6..51c6ae74d2 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/PostmanAbstractParserParserApiDefinition.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/PostmanAbstractParserParserApiDefinition.java @@ -1,4 +1,4 @@ -package io.metersphere.api.parser.api; +package io.metersphere.api.parser.api.dataimport; import com.fasterxml.jackson.databind.JsonNode; diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/PostmanParserApiDefinition.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/PostmanParserApiDefinition.java similarity index 88% rename from backend/services/api-test/src/main/java/io/metersphere/api/parser/api/PostmanParserApiDefinition.java rename to backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/PostmanParserApiDefinition.java index ca354a09b0..85cd7c222c 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/PostmanParserApiDefinition.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/PostmanParserApiDefinition.java @@ -1,9 +1,9 @@ -package io.metersphere.api.parser.api; +package io.metersphere.api.parser.api.dataimport; import com.fasterxml.jackson.core.JsonProcessingException; import io.metersphere.api.dto.converter.ApiDefinitionDetail; -import io.metersphere.api.dto.converter.ApiImportFileParseResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportFileParseResult; import io.metersphere.api.dto.definition.ApiTestCaseDTO; import io.metersphere.api.dto.request.ImportRequest; import io.metersphere.api.parser.api.postman.PostmanCollection; @@ -19,10 +19,10 @@ import org.apache.commons.lang3.StringUtils; import java.io.InputStream; import java.util.*; -public class PostmanParserApiDefinition extends PostmanAbstractParserParserApiDefinition { +public class PostmanParserApiDefinition extends PostmanAbstractParserParserApiDefinition { @Override - public ApiImportFileParseResult parse(InputStream source, ImportRequest request) throws JsonProcessingException { + public ApiDefinitionImportFileParseResult parse(InputStream source, ImportRequest request) throws JsonProcessingException { LogUtils.info("PostmanParser parse"); String testStr = getApiTestStr(source); PostmanCollection postmanCollection = JSON.parseObject(testStr, PostmanCollection.class); @@ -37,7 +37,7 @@ public class PostmanParserApiDefinition extends PostmanAbstractParserParserApiDe LinkedHashMap> allImportDetails = this.parseItem(postmanCollection.getItem(), modulePath, request); // 对于postman的导入: 本质就是将postman的数据导入为用例 - ApiImportFileParseResult apiImport = this.genApiDefinitionImport(allImportDetails); + ApiDefinitionImportFileParseResult apiImport = this.genApiDefinitionImport(allImportDetails); LogUtils.info("PostmanParser parse end"); return apiImport; } @@ -61,9 +61,9 @@ public class PostmanParserApiDefinition extends PostmanAbstractParserParserApiDe return results; } - private ApiImportFileParseResult genApiDefinitionImport(LinkedHashMap> allImportDetails) { + private ApiDefinitionImportFileParseResult genApiDefinitionImport(LinkedHashMap> allImportDetails) { Map> groupWithUniqueIdentification = this.mergeApiCaseWithUniqueIdentification(allImportDetails); - ApiImportFileParseResult returnDTO = new ApiImportFileParseResult(); + ApiDefinitionImportFileParseResult returnDTO = new ApiDefinitionImportFileParseResult(); groupWithUniqueIdentification.forEach((definitionImportDetail, caseData) -> { String apiID = IDGenerator.nextStr(); definitionImportDetail.setId(apiID); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3ParserApiDefinition.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/Swagger3ParserApiDefinition.java similarity index 98% rename from backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3ParserApiDefinition.java rename to backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/Swagger3ParserApiDefinition.java index eda2e67b2f..06c101a2e4 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3ParserApiDefinition.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/dataimport/Swagger3ParserApiDefinition.java @@ -1,8 +1,8 @@ -package io.metersphere.api.parser.api; +package io.metersphere.api.parser.api.dataimport; import io.metersphere.api.constants.ApiConstants; import io.metersphere.api.dto.converter.ApiDefinitionDetail; -import io.metersphere.api.dto.converter.ApiImportFileParseResult; +import io.metersphere.api.dto.converter.ApiDefinitionImportFileParseResult; import io.metersphere.api.dto.definition.HttpResponse; import io.metersphere.api.dto.definition.ResponseBody; import io.metersphere.api.dto.request.ImportRequest; @@ -44,7 +44,7 @@ import java.net.URI; import java.util.*; -public class Swagger3ParserApiDefinition extends HttpApiDefinitionImportAbstractParser { +public class Swagger3ParserApiDefinition extends HttpApiDefinitionImportAbstractParser { protected String projectId; private Components components; @@ -72,7 +72,7 @@ public class Swagger3ParserApiDefinition extends HttpApiDefinitionImportAbstract } } - public ApiImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception { + public ApiDefinitionImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception { //将之前在service中的swagger地址判断放在这里。 if (StringUtils.isNotBlank(request.getSwaggerUrl())) { @@ -101,10 +101,10 @@ public class Swagger3ParserApiDefinition extends HttpApiDefinitionImportAbstract throw new MSException(Translator.get("swagger_parse_error")); } } - ApiImportFileParseResult apiImportFileParseResult = new ApiImportFileParseResult(); + ApiDefinitionImportFileParseResult apiDefinitionImportFileParseResult = new ApiDefinitionImportFileParseResult(); OpenAPI openAPI = result.getOpenAPI(); - apiImportFileParseResult.setData(parseRequests(openAPI, request)); - return apiImportFileParseResult; + apiDefinitionImportFileParseResult.setData(parseRequests(openAPI, request)); + return apiDefinitionImportFileParseResult; } private List setAuths(ImportRequest request) { diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/ms/MsTestElementParser.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/ms/MsTestElementParser.java index 8dcb40bc01..7fcd059e47 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/ms/MsTestElementParser.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/ms/MsTestElementParser.java @@ -3,12 +3,17 @@ package io.metersphere.api.parser.ms; import io.metersphere.api.dto.request.MsScenario; import io.metersphere.plugin.api.spi.AbstractMsProtocolTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement; +import io.metersphere.sdk.util.CommonBeanFactory; +import io.metersphere.system.dto.ProtocolDTO; +import io.metersphere.system.service.ApiPluginService; import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.testelement.TestPlan; import org.apache.jorphan.collections.HashTree; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * @Author: jianxing @@ -48,4 +53,24 @@ public class MsTestElementParser { } return result; } + + // public List getAbstractMsTestElement(AbstractMsTestElement msTestElement) { + // List result = new ArrayList<>(); + // if (msTestElement instanceof AbstractMsProtocolTestElement abstractMsProtocolTestElement) { + // result.add(abstractMsProtocolTestElement); + // } else { + // for (AbstractMsTestElement child : msTestElement.getChildren()) { + // result.addAll(this.getAbstractMsTestElement(child)); + // } + // } + // return result; + // } + + public Map getPolymorphicNameMap(String projectId) { + ApiPluginService apiPluginService = CommonBeanFactory.getBean(ApiPluginService.class); + assert apiPluginService != null; + List protocolDTOList = apiPluginService.getProtocolsByProjectId(projectId); + return protocolDTOList.stream().collect(Collectors.toMap(ProtocolDTO::getPolymorphicName, ProtocolDTO::getProtocol)); + } + } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/ms/ThreadGroupConverter.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/ms/ThreadGroupConverter.java index c9f8f4ac49..92ea43f714 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/ms/ThreadGroupConverter.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/ms/ThreadGroupConverter.java @@ -1,7 +1,7 @@ package io.metersphere.api.parser.ms; -import io.metersphere.api.dto.request.MsScenario; +import io.metersphere.api.dto.request.MsJMeterComponent; import io.metersphere.plugin.api.spi.AbstractMsElementConverter; import io.metersphere.plugin.api.spi.AbstractMsTestElement; import org.apache.jmeter.threads.ThreadGroup; @@ -16,9 +16,9 @@ import org.apache.jorphan.collections.HashTree; public class ThreadGroupConverter extends AbstractMsElementConverter { @Override public void toMsElement(AbstractMsTestElement parent, ThreadGroup element, HashTree hashTree) { - MsScenario msScenario = new MsScenario(); - // todo 解析线程组 - parent.getChildren().add(msScenario); - parseChild(msScenario, element, hashTree); + MsJMeterComponent msJMeterComponent = new MsJMeterComponent(); + msJMeterComponent.setName(element.getName()); + parent.getChildren().add(msJMeterComponent); + parseChild(msJMeterComponent, element, hashTree); } } 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 new file mode 100644 index 0000000000..87a41a6e7e --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java @@ -0,0 +1,547 @@ +package io.metersphere.api.service; + +import io.metersphere.api.domain.*; +import io.metersphere.api.dto.ApiFile; +import io.metersphere.api.dto.converter.ApiScenarioPreImportAnalysisResult; +import io.metersphere.api.dto.definition.ApiScenarioBatchExportRequest; +import io.metersphere.api.dto.scenario.*; +import io.metersphere.api.mapper.*; +import io.metersphere.api.parser.ApiScenarioImportParser; +import io.metersphere.api.parser.ImportParserFactory; +import io.metersphere.api.service.scenario.ApiScenarioModuleService; +import io.metersphere.api.service.scenario.ApiScenarioService; +import io.metersphere.api.utils.ApiDefinitionImportUtils; +import io.metersphere.api.utils.ApiScenarioImportUtils; +import io.metersphere.project.domain.Project; +import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; +import io.metersphere.project.mapper.ProjectMapper; +import io.metersphere.sdk.constants.DefaultRepositoryDir; +import io.metersphere.sdk.constants.ModuleConstants; +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.*; +import io.metersphere.system.constants.ExportConstants; +import io.metersphere.system.domain.User; +import io.metersphere.system.dto.sdk.BaseTreeNode; +import io.metersphere.system.log.constants.OperationLogModule; +import io.metersphere.system.log.constants.OperationLogType; +import io.metersphere.system.log.dto.LogDTO; +import io.metersphere.system.log.service.OperationLogService; +import io.metersphere.system.manager.ExportTaskManager; +import io.metersphere.system.mapper.UserMapper; +import io.metersphere.system.notice.constants.NoticeConstants; +import io.metersphere.system.service.CommonNoticeSendService; +import io.metersphere.system.uid.IDGenerator; +import io.metersphere.system.utils.TreeNodeParseUtils; +import jakarta.annotation.Resource; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static io.metersphere.project.utils.NodeSortUtils.DEFAULT_NODE_INTERVAL_POS; + +@Service +@Transactional(rollbackFor = Exception.class) +public class ApiScenarioDataTransferService { + + @Resource + private ProjectMapper projectMapper; + @Resource + private UserMapper userMapper; + @Resource + private ExtBaseProjectVersionMapper extBaseProjectVersionMapper; + @Resource + private ExportTaskManager exportTaskManager; + @Resource + private ExtApiScenarioMapper extApiScenarioMapper; + + @Resource + private CommonNoticeSendService commonNoticeSendService; + @Resource + private ApiScenarioModuleService apiScenarioModuleService; + @Resource + private ApiScenarioService apiScenarioService; + @Resource + private ApiFileResourceService apiFileResourceService; + @Resource + private SqlSessionFactory sqlSessionFactory; + @Resource + private OperationLogService operationLogService; + + private final ThreadLocal currentApiScenarioOrder = new ThreadLocal<>(); + + private final ThreadLocal currentModuleOrder = new ThreadLocal<>(); + + + public String exportScenario(ApiScenarioBatchExportRequest request, String type, String userId) { + String returnId; + try { + exportTaskManager.exportCheck(request.getProjectId(), ExportConstants.ExportType.API_SCENARIO.toString(), userId); + + returnId = exportTaskManager.exportAsyncTask( + request.getProjectId(), + request.getFileId(), userId, + ExportConstants.ExportType.API_SCENARIO.name(), request, t -> { + try { + return exportApiScenarioZip(request, type, userId); + } catch (Exception e) { + throw new MSException(e); + } + }).getId(); + } catch (InterruptedException e) { + LogUtils.error("导出失败:" + e); + throw new MSException(e); + } + return returnId; + } + + + public void importScenario(MultipartFile file, ApiScenarioImportRequest request) { + ApiScenarioImportParser parser = ImportParserFactory.getApiScenarioImportParser(request.getType()); + List importScenarios; + try { + assert parser != null; + importScenarios = parser.parse(file.getInputStream(), request); + } catch (Exception e) { + LogUtils.error(e.getMessage(), e); + throw new MSException(Translator.get("parse_data_error")); + } + + if (CollectionUtils.isEmpty(importScenarios)) { + throw new MSException(Translator.get("parse_empty_data")); + } + //解析 + ApiScenarioPreImportAnalysisResult preImportAnalysisResult = this.importAnalysis( + importScenarios, request.getModuleId(), apiScenarioModuleService.getTree(request.getProjectId())); + + //存储 + this.save(preImportAnalysisResult, request.getProjectId(), request.getOperator(), request.isCoverData()); + } + + private void save(ApiScenarioPreImportAnalysisResult preImportAnalysisResult, String projectId, String operator, boolean isCoverData) { + List operationLogs = new ArrayList<>(); + currentModuleOrder.remove(); + currentApiScenarioOrder.remove(); + // 更新、修改数据 + { + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + this.insertModule(projectId, operator, preImportAnalysisResult.getInsertModuleList(), sqlSession); + if (isCoverData) { + this.updateScenarios(projectId, operator, preImportAnalysisResult.getUpdateApiScenarioData(), sqlSession); + } + String versionId = extBaseProjectVersionMapper.getDefaultVersion(projectId); + this.insertScenarios(projectId, operator, versionId, preImportAnalysisResult.getInsertApiScenarioData(), sqlSession); + sqlSession.flushStatements(); + SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); + } + //记录log以及发送通知 + { + Project project = projectMapper.selectByPrimaryKey(projectId); + List noticeCreateLists = new ArrayList<>(); + List noticeUpdateLists = new ArrayList<>(); + + preImportAnalysisResult.getInsertModuleList().forEach(t -> + operationLogs.add(ApiDefinitionImportUtils.genImportLog(project, t.getId(), t.getName(), t, OperationLogModule.API_SCENARIO_MANAGEMENT_MODULE, operator, OperationLogType.ADD.name())) + ); + + preImportAnalysisResult.getInsertApiScenarioData().forEach(t -> { + ApiScenarioImportDetail scenarioImportDetail = new ApiScenarioImportDetail(); + BeanUtils.copyBean(scenarioImportDetail, t); + noticeCreateLists.add(scenarioImportDetail); + operationLogs.add(ApiScenarioImportUtils.genImportLog(project, t.getId(), t.getName(), scenarioImportDetail, OperationLogModule.API_SCENARIO_MANAGEMENT_SCENARIO, operator, OperationLogType.IMPORT.name())); + }); + + preImportAnalysisResult.getUpdateApiScenarioData().forEach(t -> { + ApiScenarioImportDetail scenarioImportDetail = new ApiScenarioImportDetail(); + BeanUtils.copyBean(scenarioImportDetail, t); + noticeUpdateLists.add(scenarioImportDetail); + operationLogs.add(ApiScenarioImportUtils.genImportLog(project, t.getId(), t.getName(), scenarioImportDetail, OperationLogModule.API_SCENARIO_MANAGEMENT_SCENARIO, operator, OperationLogType.UPDATE.name())); + }); + + //发送通知 + User user = userMapper.selectByPrimaryKey(operator); + commonNoticeSendService.sendNotice(NoticeConstants.TaskType.API_SCENARIO_TASK, NoticeConstants.Event.CREATE, + new ArrayList<>(JSON.parseArray(JSON.toJSONString(noticeCreateLists), Map.class)), user, projectId); + commonNoticeSendService.sendNotice(NoticeConstants.TaskType.API_SCENARIO_TASK, NoticeConstants.Event.UPDATE, + new ArrayList<>(JSON.parseArray(JSON.toJSONString(noticeUpdateLists), Map.class)), user, projectId); + } + operationLogService.batchAdd(operationLogs); + } + + private void updateScenarios(String projectId, String operator, List updateApiScenarioData, SqlSession sqlSession) { + // 创建场景 + if (CollectionUtils.isEmpty(updateApiScenarioData)) { + return; + } + ApiScenarioMapper scenarioBatchMapper = sqlSession.getMapper(ApiScenarioMapper.class); + ApiScenarioBlobMapper scenarioBlobBatchMapper = sqlSession.getMapper(ApiScenarioBlobMapper.class); + ApiScenarioCsvMapper csvBatchMapper = sqlSession.getMapper(ApiScenarioCsvMapper.class); + ApiScenarioCsvStepMapper csvStepBatchMapper = sqlSession.getMapper(ApiScenarioCsvStepMapper.class); + ApiScenarioStepMapper stepBatchMapper = sqlSession.getMapper(ApiScenarioStepMapper.class); + ApiScenarioStepBlobMapper stepBlobBatchMapper = sqlSession.getMapper(ApiScenarioStepBlobMapper.class); + ExtApiScenarioStepMapper extStepMapper = sqlSession.getMapper(ExtApiScenarioStepMapper.class); + SubListUtils.dealForSubList(updateApiScenarioData, 100, list -> { + //首先筛选出空步骤的场景,用于删除已有步骤 + List emptyStepScenarioIds = new ArrayList<>(); + { + list.forEach(t -> { + if (t.getSteps() != null && CollectionUtils.isEmpty(t.getSteps())) { + emptyStepScenarioIds.add(t.getId()); + } + }); + if (!emptyStepScenarioIds.isEmpty()) { + ApiScenarioStepExample deleteStepExample = new ApiScenarioStepExample(); + deleteStepExample.createCriteria().andScenarioIdIn(emptyStepScenarioIds); + stepBatchMapper.deleteByExample(deleteStepExample); + ApiScenarioStepBlobExample deleteStepBlobExample = new ApiScenarioStepBlobExample(); + deleteStepBlobExample.createCriteria().andScenarioIdIn(emptyStepScenarioIds); + stepBlobBatchMapper.deleteByExample(deleteStepBlobExample); + } + } + + //更新场景 + list.forEach(request -> { + // 更新基础信息 + ApiScenario scenario = BeanUtils.copyBean(new ApiScenario(), request); + scenario.setUpdateUser(operator); + scenario.setUpdateTime(System.currentTimeMillis()); + scenario.setStepTotal(CollectionUtils.isNotEmpty(request.getSteps()) ? request.getSteps().size() : 0); + scenarioBatchMapper.updateByPrimaryKeySelective(scenario); + if (request.getScenarioConfig() != null) { + // 更新场景配置 + ApiScenarioBlob apiScenarioBlob = new ApiScenarioBlob(); + apiScenarioBlob.setId(scenario.getId()); + apiScenarioBlob.setConfig(JSON.toJSONString(request.getScenarioConfig()).getBytes()); + scenarioBlobBatchMapper.updateByPrimaryKeyWithBLOBs(apiScenarioBlob); + } + + + if (!emptyStepScenarioIds.contains(scenario.getId())) { + List originStepDetailIds = extStepMapper.getStepIdsByScenarioId(scenario.getId()); + // 更新场景步骤 + this.updateApiScenarioStep(request, operator, originStepDetailIds, csvStepBatchMapper, stepBatchMapper, stepBlobBatchMapper); + } + + // 处理 csv 文件 + apiScenarioService.handleCsvUpdate(request.getScenarioConfig(), scenario, operator); + + }); + sqlSession.flushStatements(); + }); + } + + private void updateApiScenarioStep(ApiScenarioImportDetail request, String userId, List originStepDetailIds, + ApiScenarioCsvStepMapper csvStepBatchMapper, ApiScenarioStepMapper apiScenarioStepBatchMapper, ApiScenarioStepBlobMapper stepBlobBatchMapper) { + List steps = request.getSteps(); + String scenarioId = request.getId(); + String projectId = request.getProjectId(); + // steps 不为 null 则修改 + if (steps != null) { + if (CollectionUtils.isEmpty(steps)) { + return; + } + apiScenarioService.checkCircularRef(scenarioId, steps); + + List scenarioCsvSteps = new ArrayList<>(); + // 获取待更新的步骤 + List apiScenarioSteps = apiScenarioService.getApiScenarioSteps(null, steps, scenarioCsvSteps); + apiScenarioSteps.forEach(step -> step.setScenarioId(scenarioId)); + scenarioCsvSteps.forEach(step -> step.setScenarioId(scenarioId)); + + scenarioCsvSteps = apiScenarioService.filterNotExistCsv(request.getScenarioConfig(), scenarioCsvSteps); + this.saveStepCsv(scenarioId, scenarioCsvSteps, csvStepBatchMapper); + // apiScenarioService. + // 获取待更新的步骤详情 + apiScenarioService.addSpecialStepDetails(steps, request.getStepDetails()); + List updateStepBlobs = apiScenarioService.getUpdateStepBlobs(apiScenarioSteps, request.getStepDetails()); + updateStepBlobs.forEach(step -> step.setScenarioId(scenarioId)); + + List stepIds = apiScenarioSteps.stream().map(ApiScenarioStep::getId).collect(Collectors.toList()); + List deleteStepIds = ListUtils.subtract(originStepDetailIds, stepIds); + + // 步骤表-全部先删除再插入 + ApiScenarioStepExample deleteStepExample = new ApiScenarioStepExample(); + deleteStepExample.createCriteria().andScenarioIdEqualTo(scenarioId); + apiScenarioStepBatchMapper.deleteByExample(deleteStepExample); + apiScenarioStepBatchMapper.batchInsert(apiScenarioSteps); + + // 详情表-删除已经删除的步骤详情 + SubListUtils.dealForSubList(deleteStepIds, 100, subIds -> { + ApiScenarioStepBlobExample stepBlobExample = new ApiScenarioStepBlobExample(); + stepBlobExample.createCriteria().andIdIn(subIds); + stepBlobBatchMapper.deleteByExample(stepBlobExample); + // 批量删除关联文件 + String scenarioStepDirPrefix = DefaultRepositoryDir.getApiScenarioStepDir(projectId, scenarioId, StringUtils.EMPTY); + apiFileResourceService.deleteByResourceIds(scenarioStepDirPrefix, subIds, projectId, userId, OperationLogModule.API_SCENARIO_MANAGEMENT_SCENARIO); + }); + + // 添加新增的步骤详情 + List addApiScenarioStepsDetails = updateStepBlobs.stream() + .filter(step -> !originStepDetailIds.contains(step.getId())) + .collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(addApiScenarioStepsDetails)) { + stepBlobBatchMapper.batchInsert(addApiScenarioStepsDetails); + } + // 更新原有的步骤详情 + updateStepBlobs.stream() + .filter(step -> originStepDetailIds.contains(step.getId())) + .forEach(stepBlobBatchMapper::updateByPrimaryKeySelective); + } else if (MapUtils.isNotEmpty(request.getStepDetails())) { + // steps 为 null,stepDetails 不为 null,则只更新详情 + // 更新原有的步骤详情 + request.getStepDetails().forEach((stepId, stepDetail) -> { + if (originStepDetailIds.contains(stepId)) { + ApiScenarioStepBlob apiScenarioStepBlob = apiScenarioService.getApiScenarioStepBlob(stepId, stepDetail); + stepBlobBatchMapper.updateByPrimaryKeySelective(apiScenarioStepBlob); + } + }); + } + } + + private void insertModule(String projectId, String operator, List insertModuleList, SqlSession sqlSession) { + if (CollectionUtils.isEmpty(insertModuleList)) { + return; + } + ApiScenarioModuleMapper batchApiDefinitionMapper = sqlSession.getMapper(ApiScenarioModuleMapper.class); + SubListUtils.dealForSubList(insertModuleList, 100, list -> { + list.forEach(t -> { + ApiScenarioModule module = new ApiScenarioModule(); + module.setId(t.getId()); + module.setName(t.getName()); + module.setParentId(t.getParentId()); + module.setProjectId(projectId); + module.setCreateUser(operator); + module.setPos(getImportNextModuleOrder(apiScenarioModuleService::getNextOrder, projectId)); + module.setCreateTime(System.currentTimeMillis()); + module.setUpdateUser(operator); + module.setUpdateTime(module.getCreateTime()); + batchApiDefinitionMapper.insertSelective(module); + }); + sqlSession.flushStatements(); + }); + } + + private void insertScenarios(String projectId, String operator, String versionId, List insertScenarioList, SqlSession sqlSession) { + // 创建场景 + if (CollectionUtils.isEmpty(insertScenarioList)) { + return; + } + + ApiScenarioMapper apiScenarioBatchMapper = sqlSession.getMapper(ApiScenarioMapper.class); + ApiScenarioBlobMapper apiScenarioBlobBatchMapper = sqlSession.getMapper(ApiScenarioBlobMapper.class); + ApiScenarioCsvMapper csvBatchMapper = sqlSession.getMapper(ApiScenarioCsvMapper.class); + ApiScenarioCsvStepMapper csvStepBatchMapper = sqlSession.getMapper(ApiScenarioCsvStepMapper.class); + ApiScenarioStepMapper stepBatchMapper = sqlSession.getMapper(ApiScenarioStepMapper.class); + ApiScenarioStepBlobMapper stepBlobBatchMapper = sqlSession.getMapper(ApiScenarioStepBlobMapper.class); + + SubListUtils.dealForSubList(insertScenarioList, 100, list -> { + list.forEach(t -> { + t.setId(IDGenerator.nextStr()); + ApiScenario scenario = new ApiScenario(); + BeanUtils.copyBean(scenario, t); + scenario.setNum(apiScenarioService.getNextNum(projectId)); + scenario.setPos(getImportNextModuleOrder(apiScenarioService::getNextOrder, projectId)); + scenario.setLatest(true); + scenario.setCreateUser(operator); + scenario.setUpdateUser(operator); + scenario.setCreateTime(System.currentTimeMillis()); + scenario.setUpdateTime(System.currentTimeMillis()); + scenario.setVersionId(versionId); + scenario.setRefId(scenario.getId()); + scenario.setLastReportStatus(StringUtils.EMPTY); + scenario.setDeleted(false); + scenario.setRequestPassRate("0"); + scenario.setStepTotal(CollectionUtils.size(t.getSteps())); + apiScenarioBatchMapper.insert(scenario); + + // 更新场景配置 + ApiScenarioBlob apiScenarioBlob = new ApiScenarioBlob(); + apiScenarioBlob.setId(scenario.getId()); + if (t.getScenarioConfig() == null) { + apiScenarioBlob.setConfig(JSON.toJSONString(new ScenarioConfig()).getBytes()); + } else { + apiScenarioBlob.setConfig(JSON.toJSONString(t.getScenarioConfig()).getBytes()); + } + apiScenarioBlobBatchMapper.insert(apiScenarioBlob); + // 处理csv文件 + this.handCsvFilesAdd(t, operator, scenario, csvBatchMapper, csvStepBatchMapper); + // 处理添加的步骤 + this.handleStepAdd(t, scenario, csvStepBatchMapper, stepBatchMapper, stepBlobBatchMapper); + }); + sqlSession.flushStatements(); + }); + } + + private void handleStepAdd(ApiScenarioImportDetail t, ApiScenario scenario, + ApiScenarioCsvStepMapper csvStepBatchMapper, ApiScenarioStepMapper stepBatchMapper, ApiScenarioStepBlobMapper stepBlobBatchMapper) { + // 插入步骤 + if (CollectionUtils.isNotEmpty(t.getSteps())) { + //检测循环引用 + apiScenarioService.checkCircularRef(scenario.getId(), t.getSteps()); + + // 获取待添加的步骤 + List csvSteps = new ArrayList<>(); + List steps = apiScenarioService.getApiScenarioSteps(null, t.getSteps(), csvSteps); + steps.forEach(step -> step.setScenarioId(scenario.getId())); + // 处理特殊的步骤详情 + apiScenarioService.addSpecialStepDetails(t.getSteps(), t.getStepDetails()); + List apiScenarioStepBlobs = apiScenarioService.getUpdateStepBlobs(steps, t.getStepDetails()); + apiScenarioStepBlobs.forEach(step -> step.setScenarioId(scenario.getId())); + + //保存步骤 + if (CollectionUtils.isNotEmpty(steps)) { + stepBatchMapper.batchInsert(steps); + } + if (CollectionUtils.isNotEmpty(apiScenarioStepBlobs)) { + stepBlobBatchMapper.batchInsert(apiScenarioStepBlobs); + } + csvSteps = apiScenarioService.filterNotExistCsv(t.getScenarioConfig(), csvSteps); + this.saveStepCsv(scenario.getId(), csvSteps, csvStepBatchMapper); + } + } + + private void saveStepCsv(String scenarioId, List csvSteps, ApiScenarioCsvStepMapper csvStepBatchMapper) { + // 先删除 + ApiScenarioCsvStepExample csvStepExample = new ApiScenarioCsvStepExample(); + csvStepExample.createCriteria().andScenarioIdEqualTo(scenarioId); + csvStepBatchMapper.deleteByExample(csvStepExample); + // 再添加 + if (CollectionUtils.isNotEmpty(csvSteps)) { + csvStepBatchMapper.batchInsert(csvSteps); + } + } + + private void handCsvFilesAdd(ApiScenarioImportDetail t, String operator, ApiScenario scenario, + ApiScenarioCsvMapper batchCsvMapper, ApiScenarioCsvStepMapper batchCsvStepMapper) { + List csvVariables = apiScenarioService.getCsvVariables(t.getScenarioConfig()); + + if (CollectionUtils.isEmpty(csvVariables)) { + return; + } + // 处理 csv 相关数据表 + this.handleCsvDataUpdate(csvVariables, scenario, List.of(), batchCsvMapper, batchCsvStepMapper); + // 处理文件的上传 (调用流程很长,目前没想到有好的批量处理方法。暂时直接调用Service) + apiScenarioService.handleCsvFileAdd(csvVariables, List.of(), scenario, operator); + } + + private void handleCsvDataUpdate(List csvVariables, ApiScenario scenario, List dbCsvIds, + ApiScenarioCsvMapper batchCsvMapper, ApiScenarioCsvStepMapper batchCsvStepMapper) { + List csvIds = csvVariables.stream() + .map(CsvVariable::getId) + .toList(); + + List deleteCsvIds = ListUtils.subtract(dbCsvIds, csvIds); + + //删除不存在的数据 + if (CollectionUtils.isNotEmpty(deleteCsvIds)) { + ApiScenarioCsvExample example = new ApiScenarioCsvExample(); + example.createCriteria().andIdIn(deleteCsvIds); + batchCsvMapper.deleteByExample(example); + + ApiScenarioCsvStepExample stepExample = new ApiScenarioCsvStepExample(); + stepExample.createCriteria().andIdIn(deleteCsvIds); + batchCsvStepMapper.deleteByExample(stepExample); + } + + Set dbCsvIdSet = new HashSet<>(dbCsvIds); + List addCsvList = new ArrayList<>(); + csvVariables.forEach(item -> { + ApiScenarioCsv scenarioCsv = new ApiScenarioCsv(); + BeanUtils.copyBean(scenarioCsv, item); + scenarioCsv.setScenarioId(scenario.getId()); + scenarioCsv.setProjectId(scenario.getProjectId()); + + ApiFile file = item.getFile(); + scenarioCsv.setFileId(file.getFileId()); + scenarioCsv.setFileName(file.getFileName()); + scenarioCsv.setAssociation(BooleanUtils.isFalse(file.getLocal())); + if (!dbCsvIdSet.contains(item.getId())) { + addCsvList.add(scenarioCsv); + } else { + batchCsvMapper.updateByPrimaryKey(scenarioCsv); + } + }); + + if (CollectionUtils.isNotEmpty(addCsvList)) { + batchCsvMapper.batchInsert(addCsvList); + } + } + + private Long getImportNextModuleOrder(Function subFunc, String projectId) { + Long order = currentModuleOrder.get(); + if (order == null) { + order = subFunc.apply(projectId); + } + order = order + DEFAULT_NODE_INTERVAL_POS; + currentModuleOrder.set(order); + return order; + } + + private ApiScenarioPreImportAnalysisResult importAnalysis(List importScenarios, String moduleId, List apiScenarioModules) { + ApiScenarioPreImportAnalysisResult analysisResult = new ApiScenarioPreImportAnalysisResult(); + + Map moduleIdPathMap = apiScenarioModules.stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath)); + Map modulePathMap = apiScenarioModules.stream().collect(Collectors.toMap(BaseTreeNode::getPath, k -> k, (k1, k2) -> k1)); + + for (ApiScenarioImportDetail importScenario : importScenarios) { + if (StringUtils.isBlank(moduleId) || StringUtils.equalsIgnoreCase(moduleId, ModuleConstants.DEFAULT_NODE_ID) || !moduleIdPathMap.containsKey(moduleId)) { + importScenario.setModuleId(ModuleConstants.DEFAULT_NODE_ID); + importScenario.setModulePath(moduleIdPathMap.get(ModuleConstants.DEFAULT_NODE_ID)); + } else { + if (StringUtils.isBlank(importScenario.getModulePath())) { + importScenario.setModulePath(moduleIdPathMap.get(moduleId)); + } else if (StringUtils.startsWith(importScenario.getModulePath(), "/")) { + importScenario.setModulePath(moduleIdPathMap.get(moduleId) + importScenario.getModulePath()); + } else { + importScenario.setModulePath(moduleIdPathMap.get(moduleId) + "/" + importScenario.getModulePath()); + } + } + } + + //检查重复的场景(模块+名称) + Map> modulePathScenario = importScenarios.stream().collect(Collectors.groupingBy(ApiScenarioImportDetail::getModulePath)); + modulePathScenario.forEach((modulePath, scenarios) -> { + if (!StringUtils.startsWith(modulePath, "/")) { + modulePath = "/" + modulePath; + } + if (modulePathMap.containsKey(modulePath)) { + List existenceScenarios = extApiScenarioMapper.selectBaseInfoByModuleId(modulePathMap.get(modulePath).getId()); + Map existenceNameIdMap = existenceScenarios.stream().collect(Collectors.toMap(ApiScenario::getName, ApiScenario::getId)); + scenarios.forEach(scenario -> { + if (existenceNameIdMap.containsKey(scenario.getName())) { + scenario.setId(existenceNameIdMap.get(scenario.getName())); + analysisResult.getUpdateApiScenarioData().add(scenario); + } else { + analysisResult.getInsertApiScenarioData().add(scenario); + } + }); + } else { + //模块不存在的必定是新建 + analysisResult.getInsertModuleList().addAll(TreeNodeParseUtils.getInsertNodeByPath(modulePathMap, modulePath)); + String finalModulePath = modulePath; + scenarios.forEach(scenario -> + scenario.setModuleId(modulePathMap.get(finalModulePath).getId()) + ); + analysisResult.getInsertApiScenarioData().addAll(scenarios); + } + }); + return analysisResult; + } + + private String exportApiScenarioZip(ApiScenarioBatchExportRequest request, String type, String userId) { + // todo 场景导出 + return null; + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionImportService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionImportService.java index 4955cda7f8..0d9b3367e5 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionImportService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionImportService.java @@ -118,26 +118,26 @@ public class ApiDefinitionImportService { @Transactional(rollbackFor = Exception.class) public void apiDefinitionImport(MultipartFile file, ImportRequest request, String projectId) { this.initImportRequestAndCheck(file, request, projectId); - ApiDefinitionImportParser runService = ImportParserFactory.getImportParser(request.getPlatform()); + ApiDefinitionImportParser runService = ImportParserFactory.getApiDefinitionImportParser(request.getPlatform()); assert runService != null; - ApiImportDataAnalysisResult apiImportDataAnalysisResult = new ApiImportDataAnalysisResult(); + ApiDefinitionImportDataAnalysisResult apiDefinitionImportDataAnalysisResult = new ApiDefinitionImportDataAnalysisResult(); try { //解析文件 - ApiImportFileParseResult fileParseResult = (ApiImportFileParseResult) runService.parse(file == null ? null : file.getInputStream(), request); + ApiDefinitionImportFileParseResult fileParseResult = (ApiDefinitionImportFileParseResult) runService.parse(file == null ? null : file.getInputStream(), request); if (!CollectionUtils.isEmpty(fileParseResult.getData())) { ApiDefinitionPageRequest pageRequest = new ApiDefinitionPageRequest(); pageRequest.setProjectId(request.getProjectId()); pageRequest.setProtocols(fileParseResult.getApiProtocols()); List existenceApiDefinitionList = extApiDefinitionMapper.importList(pageRequest); //分析有哪些数据需要新增、有哪些数据需要更新 - apiImportDataAnalysisResult = runService.generateInsertAndUpdateData(fileParseResult, existenceApiDefinitionList); + apiDefinitionImportDataAnalysisResult = runService.generateInsertAndUpdateData(fileParseResult, existenceApiDefinitionList); } } catch (Exception e) { LogUtils.error(e.getMessage(), e); throw new MSException(Translator.get("parse_data_error")); } - if (apiImportDataAnalysisResult.isEmpty()) { + if (apiDefinitionImportDataAnalysisResult.isEmpty()) { throw new MSException(Translator.get("parse_empty_data")); } @@ -149,7 +149,7 @@ public class ApiDefinitionImportService { request.setVersionId(defaultVersion); } //通过导入配置,预处理数据,确定哪些要创建、哪些要修改 - ApiDefinitionPreImportAnalysisResult preImportAnalysisResult = this.preImportAnalysis(request, apiImportDataAnalysisResult); + ApiDefinitionPreImportAnalysisResult preImportAnalysisResult = this.preImportAnalysis(request, apiDefinitionImportDataAnalysisResult); //入库 List operationLogs = this.insertData(preImportAnalysisResult, request); operationLogService.batchAdd(operationLogs); @@ -513,7 +513,7 @@ public class ApiDefinitionImportService { /** * 预导入数据分析 根据请求配置,判断哪些数据新增、哪些数据修改且修改它的哪部分数据、 */ - private ApiDefinitionPreImportAnalysisResult preImportAnalysis(ImportRequest request, ApiImportDataAnalysisResult insertAndUpdateData) { + private ApiDefinitionPreImportAnalysisResult preImportAnalysis(ImportRequest request, ApiDefinitionImportDataAnalysisResult insertAndUpdateData) { ApiDefinitionPreImportAnalysisResult preImportAnalysisResult = new ApiDefinitionPreImportAnalysisResult(); // api模块树查询 @@ -548,7 +548,7 @@ public class ApiDefinitionImportService { */ private void furtherProcessingExistenceApiData(String selectModuleId, String selectModulePath, boolean isCoverModule, - ApiImportDataAnalysisResult insertAndUpdateData, + ApiDefinitionImportDataAnalysisResult insertAndUpdateData, ApiDefinitionPreImportAnalysisResult apiDefinitionPreImportAnalysisResult, Map modulePathMap) { for (ApiDefinitionDetail importApi : insertAndUpdateData.getInsertApiList()) { @@ -600,7 +600,7 @@ public class ApiDefinitionImportService { 指定了导入模块: 直接塞入指定模块中。 未指定导入模块: 接口有模块,就放在那个模块下。 接口没模块就放在未规划模块内 */ - private void inertDataAnalysis(ApiDefinitionPreImportAnalysisResult apiDefinitionPreImportAnalysisResult, ImportRequest request, String selectModulePath, Map modulePathMap, ApiImportDataAnalysisResult analysisResult) { + private void inertDataAnalysis(ApiDefinitionPreImportAnalysisResult apiDefinitionPreImportAnalysisResult, ImportRequest request, String selectModulePath, Map modulePathMap, ApiDefinitionImportDataAnalysisResult analysisResult) { for (ApiDefinitionDetail apiData : analysisResult.getInsertApiList()) { apiDefinitionPreImportAnalysisResult.getInsertApiData().add(apiData); //判断是否更新用例 @@ -619,7 +619,7 @@ public class ApiDefinitionImportService { } // 已有数据处理 - private void existenceDataAnalysis(ApiDefinitionPreImportAnalysisResult apiDefinitionPreImportAnalysisResult, ImportRequest request, String selectModulePath, Map modulePathMap, ApiImportDataAnalysisResult analysisResult) { + private void existenceDataAnalysis(ApiDefinitionPreImportAnalysisResult apiDefinitionPreImportAnalysisResult, ImportRequest request, String selectModulePath, Map modulePathMap, ApiDefinitionImportDataAnalysisResult analysisResult) { //不选择覆盖接口或者数据为空:终止操作 if (CollectionUtils.isEmpty(analysisResult.getExistenceApiList()) || !request.isCoverData()) { return; diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioModuleService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioModuleService.java index 0e9b81f0d8..d23634fc73 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioModuleService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioModuleService.java @@ -65,6 +65,14 @@ public class ApiScenarioModuleService extends ModuleTreeService { return super.buildTreeAndCountResource(fileModuleList, true, Translator.get(UNPLANNED_SCENARIO)); } + public List getTree(String projectId) { + //接口的树结构是 模块:子模块+接口 接口为非delete状态的 + List fileModuleList = extApiScenarioModuleMapper.selectBaseByRequest(new ApiScenarioModuleRequest() {{ + this.setProjectId(projectId); + }}); + return super.buildTreeAndCountResource(fileModuleList, true, Translator.get(UNPLANNED_SCENARIO)); + } + public List getTreeOnlyIdsAndResourceCount(ApiScenarioModuleRequest request, List moduleCountDTOList) { //节点内容只有Id和parentId List fileModuleList = extApiScenarioModuleMapper.selectIdAndParentIdByRequest(request); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java index 7416a6049e..e289948ee2 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java @@ -482,7 +482,7 @@ public class ApiScenarioService extends MoveNodeService { } } - private List filterNotExistCsv(ScenarioConfig scenarioConfig, List csvSteps) { + public List filterNotExistCsv(ScenarioConfig scenarioConfig, List csvSteps) { Set csvIdSet = getCsvVariables(scenarioConfig) .stream() @@ -560,7 +560,7 @@ public class ApiScenarioService extends MoveNodeService { } } - private void handleCsvUpdate(ScenarioConfig scenarioConfig, ApiScenario scenario, String userId) { + public void handleCsvUpdate(ScenarioConfig scenarioConfig, ApiScenario scenario, String userId) { if (scenarioConfig == null) { return; } @@ -687,7 +687,7 @@ public class ApiScenarioService extends MoveNodeService { } } - private void handleCsvFileAdd(List csvVariables, List dbCsv, ApiScenario scenario, String userId) { + public void handleCsvFileAdd(List csvVariables, List dbCsv, ApiScenario scenario, String userId) { ApiFileResourceUpdateRequest resourceUpdateRequest = getApiFileResourceUpdateRequest(scenario.getId(), scenario.getProjectId(), userId); // 设置本地文件相关参数 setCsvLocalFileParam(csvVariables, dbCsv, resourceUpdateRequest); @@ -705,7 +705,7 @@ public class ApiScenarioService extends MoveNodeService { apiFileResourceService.updateFileResource(resourceUpdateRequest); } - private void setCsvLinkFileParam(List csvVariables, List dbCsv, ApiFileResourceUpdateRequest resourceUpdateRequest) { + public void setCsvLinkFileParam(List csvVariables, List dbCsv, ApiFileResourceUpdateRequest resourceUpdateRequest) { // 获取数据库中关联的文件id List dbRefFileIds = dbCsv.stream() .filter(c -> BooleanUtils.isTrue(c.getAssociation()) && StringUtils.isNotBlank(c.getFileId())) @@ -724,7 +724,7 @@ public class ApiScenarioService extends MoveNodeService { resourceUpdateRequest.setLinkFileIds(linkFileIds); } - private void setCsvLocalFileParam(List csvVariables, List dbCsv, ApiFileResourceUpdateRequest resourceUpdateRequest) { + public void setCsvLocalFileParam(List csvVariables, List dbCsv, ApiFileResourceUpdateRequest resourceUpdateRequest) { // 获取数据库中的本地文件 List dbLocalFileIds = dbCsv.stream() .filter(c -> BooleanUtils.isFalse(c.getAssociation())) @@ -896,7 +896,7 @@ public class ApiScenarioService extends MoveNodeService { * @param scenarioId * @param steps */ - private void checkCircularRef(String scenarioId, List steps) { + public void checkCircularRef(String scenarioId, List steps) { traversalStepTree(steps, step -> { if (isRefOrPartialRef(step.getRefType()) && StringUtils.equals(step.getResourceId(), scenarioId)) { throw new MSException(API_SCENARIO_CIRCULAR_REFERENCE); @@ -905,7 +905,7 @@ public class ApiScenarioService extends MoveNodeService { }); } - private ApiScenarioStepBlob getApiScenarioStepBlob(String stepId, Object stepDetail) { + public ApiScenarioStepBlob getApiScenarioStepBlob(String stepId, Object stepDetail) { ApiScenarioStepBlob apiScenarioStepBlob = new ApiScenarioStepBlob(); apiScenarioStepBlob.setId(stepId); apiScenarioStepBlob.setContent(JSON.toJSONString(stepDetail).getBytes()); @@ -927,7 +927,7 @@ public class ApiScenarioService extends MoveNodeService { /** * 获取待更新的 ApiScenarioStepBlob 列表 */ - private List getUpdateStepBlobs(List apiScenarioSteps, Map stepDetails) { + public List getUpdateStepBlobs(List apiScenarioSteps, Map stepDetails) { if (MapUtils.isEmpty(stepDetails)) { return Collections.emptyList(); } @@ -989,7 +989,7 @@ public class ApiScenarioService extends MoveNodeService { * 解析步骤树结构 * 获取待更新的 ApiScenarioStep 列表 */ - private List getApiScenarioSteps(ApiScenarioStepCommonDTO parent, + public List getApiScenarioSteps(ApiScenarioStepCommonDTO parent, List steps, List csvSteps) { if (CollectionUtils.isEmpty(steps)) { return Collections.emptyList(); @@ -1185,7 +1185,7 @@ public class ApiScenarioService extends MoveNodeService { return partialRefStepDetail; } - private ApiFileResourceUpdateRequest getApiFileResourceUpdateRequest(String sourceId, String projectId, String operator) { + public ApiFileResourceUpdateRequest getApiFileResourceUpdateRequest(String sourceId, String projectId, String operator) { String apiScenarioDir = DefaultRepositoryDir.getApiScenarioDir(projectId, sourceId); ApiFileResourceUpdateRequest resourceUpdateRequest = new ApiFileResourceUpdateRequest(); resourceUpdateRequest.setProjectId(projectId); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/utils/ApiScenarioImportUtils.java b/backend/services/api-test/src/main/java/io/metersphere/api/utils/ApiScenarioImportUtils.java new file mode 100644 index 0000000000..33f8f8b1ae --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/utils/ApiScenarioImportUtils.java @@ -0,0 +1,25 @@ +package io.metersphere.api.utils; + +import io.metersphere.project.domain.Project; +import io.metersphere.sdk.constants.HttpMethodConstants; +import io.metersphere.sdk.util.JSON; +import io.metersphere.system.log.dto.LogDTO; + + +public class ApiScenarioImportUtils { + public static LogDTO genImportLog(Project project, String dataId, String dataName, Object importData, String module, String operator, String operationType) { + LogDTO dto = new LogDTO( + project.getId(), + project.getOrganizationId(), + dataId, + operator, + operationType, + module, + dataName); + dto.setHistory(true); + dto.setPath("/api/scenario/import"); + dto.setMethod(HttpMethodConstants.POST.name()); + dto.setOriginalValue(JSON.toJSONBytes(importData)); + return dto; + } +} diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java index 95e6fd1fa9..d107b899d9 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java @@ -1745,7 +1745,7 @@ public class ApiDefinitionControllerTests extends BaseTest { private void importTest() throws Exception { //测试ImportParserFactory不按规定获取会返回null - Assertions.assertNull(ImportParserFactory.getImportParser("test")); + Assertions.assertNull(ImportParserFactory.getApiDefinitionImportParser("test")); // 创建用于导入的项目 //测试计划专用项目 AddProjectRequest initProject = new AddProjectRequest(); @@ -1798,7 +1798,6 @@ public class ApiDefinitionControllerTests extends BaseTest { //导入类型以及文件后缀 Map importTypeAndSuffix = new LinkedHashMap<>(); - // importTypeAndSuffix.put("jmeter", "jmx"); importTypeAndSuffix.put("metersphere", "json"); importTypeAndSuffix.put("postman", "json"); importTypeAndSuffix.put("har", "har"); 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 new file mode 100644 index 0000000000..fb0b34555b --- /dev/null +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java @@ -0,0 +1,97 @@ +package io.metersphere.api.controller; + +import io.metersphere.api.dto.definition.ApiDefinitionBatchExportRequest; +import io.metersphere.api.dto.scenario.ApiScenarioImportRequest; +import io.metersphere.project.domain.Project; +import io.metersphere.sdk.util.JSON; +import io.metersphere.system.base.BaseTest; +import io.metersphere.system.dto.AddProjectRequest; +import io.metersphere.system.log.constants.OperationLogModule; +import io.metersphere.system.service.CommonProjectService; +import io.metersphere.system.uid.IDGenerator; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.*; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ApiScenarioControllerImportAndExportTests extends BaseTest { + + private static final String URL_POST_IMPORT = "/api/scenario/import"; + + private static final String URL_POST_EXPORT = "/api/scenario/export/"; + + private static Project project; + + @Resource + private CommonProjectService commonProjectService; + + @BeforeEach + public void initTestData() { + //文件管理专用项目 + if (project == null) { + AddProjectRequest initProject = new AddProjectRequest(); + initProject.setOrganizationId("100001"); + initProject.setName("场景导入专用"); + initProject.setDescription("场景导入专用项目"); + initProject.setEnable(true); + initProject.setUserIds(List.of("admin")); + project = commonProjectService.add(initProject, "admin", "/organization-project/add", OperationLogModule.SETTING_ORGANIZATION_PROJECT); + // ArrayList moduleList = new ArrayList<>(List.of("workstation", "testPlan", "bugManagement", "caseManagement", "apiTest", "uiTest", "loadTest")); + // Project updateProject = new Project(); + // updateProject.setId(importProject.getId()); + // updateProject.setModuleSetting(JSON.toJSONString(moduleList)); + // projectMapper.updateByPrimaryKeySelective(updateProject); + } + } + + @Test + @Order(1) + public void testImport() throws Exception { + Map importTypeAndSuffix = new LinkedHashMap<>(); + // importTypeAndSuffix.put("metersphere", "json"); + importTypeAndSuffix.put("jmeter", "jmx"); + for (Map.Entry entry : importTypeAndSuffix.entrySet()) { + ApiScenarioImportRequest request = new ApiScenarioImportRequest(); + request.setProjectId(project.getId()); + request.setType(entry.getKey()); + String importType = entry.getKey(); + String fileSuffix = entry.getValue(); + FileInputStream inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import-scenario/" + importType + "/simple." + fileSuffix)).getPath())); + MockMultipartFile file = new MockMultipartFile("file", "simple." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream); + MultiValueMap paramMap = new LinkedMultiValueMap<>(); + paramMap.add("request", JSON.toJSONString(request)); + paramMap.add("file", file); + this.requestMultipartWithOkAndReturn(URL_POST_IMPORT, paramMap); + } + } + + @Test + @Order(1) + public void testExport() throws Exception { + ApiDefinitionBatchExportRequest exportRequest = new ApiDefinitionBatchExportRequest(); + String fileId = IDGenerator.nextStr(); + exportRequest.setProjectId(project.getId()); + exportRequest.setFileId(fileId); + exportRequest.setSelectAll(true); + exportRequest.setExportApiCase(true); + exportRequest.setExportApiMock(true); + MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_POST_EXPORT + "metersphere", exportRequest); + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + } +} \ No newline at end of file diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/parser/ParserTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/parser/ParserTests.java index 43dd03c75a..2d0b98cee1 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/parser/ParserTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/parser/ParserTests.java @@ -17,6 +17,6 @@ public class ParserTests { @Test @Order(3) public void testImportParserMs() throws Exception { - ImportParserFactory.getImportParser(ApiImportPlatform.MeterSphere.name()); + ImportParserFactory.getApiDefinitionImportParser(ApiImportPlatform.MeterSphere.name()); } } diff --git a/backend/services/api-test/src/test/resources/file/import-scenario/jmeter/simple.jmx b/backend/services/api-test/src/test/resources/file/import-scenario/jmeter/simple.jmx new file mode 100644 index 0000000000..158faf88b5 --- /dev/null +++ b/backend/services/api-test/src/test/resources/file/import-scenario/jmeter/simple.jmx @@ -0,0 +1,267 @@ + + + + + + false + true + false + + + + + + + + continue + + false + 1 + + 1 + 1 + false + + + true + + + + + + + www.baidu.com + + + + /test1111 + GET + true + false + true + false + + + + + + + + test + + + Assertion.response_data + false + 20 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + continue + + false + 1 + + 1 + 1 + false + + + true + + + + + + + www.baidu.com + + + + /test2222 + GET + true + false + true + false + + + + + + + + test + + + Assertion.response_data + false + 20 + + + + + + continue + + false + 1 + + 1 + 1 + false + + + true + + + + true + + + + false + { + "username" : "apitester@fit2cloud.com", + "password" : "apitester@fit2cloud.com", + "authenticate" : "local" +} + = + + + + qadevtest.fit2cloud.com + + https + + /login + POST + true + false + true + false + + + + + + + + + Content-Type + application/json + + + Accept + */* + + + + + + csrfToken + $.data.csrfToken + + + + + sessionId + $.data.sessionId + + + + + + + false + log.info("output the token and x-auth====="+ "${csrfToken}"); +log.info("output the token and x-auth====="+ "${sessionId}"); + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + + + + + + false + + + + + + + + diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/constants/ExportConstants.java b/backend/services/system-setting/src/main/java/io/metersphere/system/constants/ExportConstants.java index e55783f121..2a86e32af9 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/constants/ExportConstants.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/constants/ExportConstants.java @@ -3,7 +3,7 @@ package io.metersphere.system.constants; public class ExportConstants { public enum ExportType { - API_DEFINITION, CASE + API_SCENARIO, API_DEFINITION, CASE } public enum ExportState {