diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiScenarioImportParseResult.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiScenarioImportParseResult.java index 791c8886ea..b9f185d6c6 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiScenarioImportParseResult.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/converter/ApiScenarioImportParseResult.java @@ -22,7 +22,6 @@ public class ApiScenarioImportParseResult { @Schema(description = "有关联关系的场景") List relatedScenarioList = new ArrayList<>(); - @Schema(description = "场景CSV相关的数据") private List apiScenarioCsvList = 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 index 13c84539b0..0246474fb4 100644 --- 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 @@ -1,5 +1,6 @@ package io.metersphere.api.dto.converter; +import io.metersphere.api.dto.definition.ApiTestCaseDTO; import io.metersphere.api.dto.scenario.ApiScenarioImportDetail; import io.metersphere.system.dto.sdk.BaseTreeNode; import io.swagger.v3.oas.annotations.media.Schema; @@ -12,7 +13,7 @@ import java.util.List; public class ApiScenarioPreImportAnalysisResult { @Schema(description = "需要创建的模块数据") - List insertModuleList = new ArrayList<>(); + List insertScenarioModuleList = new ArrayList<>(); @Schema(description = "需要新增的场景") List insertApiScenarioData = new ArrayList<>(); @@ -20,4 +21,24 @@ public class ApiScenarioPreImportAnalysisResult { @Schema(description = "需要更新的场景") List updateApiScenarioData = new ArrayList<>(); + @Schema(description = "需要新增的接口定义, ID已经生成好") + private List insertApiDefinitions = new ArrayList<>(); + + @Schema(description = "需要新增的接口定义模块") + private List insertApiModuleList = new ArrayList<>(); + + @Schema(description = "需要新增的接口用例, ID已经生成好") + private List insertApiTestCaseList = new ArrayList<>(); + + public void setApiDefinition(ApiDefinitionDetail insertApi) { + this.insertApiDefinitions.add(insertApi); + } + + public void setApiTestCase(ApiTestCaseDTO insertCase) { + this.insertApiTestCaseList.add(insertCase); + } + + public void setApiScenario(ApiScenarioImportDetail insertScenario) { + this.insertApiScenarioData.add(insertScenario); + } } \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiTestCaseWithBlob.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiTestCaseWithBlob.java index a871477c8a..7b8976796d 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiTestCaseWithBlob.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiTestCaseWithBlob.java @@ -30,9 +30,30 @@ public class ApiTestCaseWithBlob extends ApiTestCaseBlob { @Size(min = 1, max = 20, message = "{api_test_case.status.length_range}", groups = {Created.class, Updated.class}) private String status; + @Schema(description = "api的协议") + private String protocol; + @Schema(description = "api的路径") + private String path; + @Schema(description = "api的方法") + private String method; + @Schema(description = "模块ID") + private String moduleId; + @Schema(description = "最新执行结果状态") private String lastReportStatus; @Schema(description = "接口定义ID") private String apiDefinitionId; + + @Schema(description = "接口定义名称") + private String apiDefinitionName; + + @Schema(description = "接口用例编号id") + private Long num; + + @Schema(description = "项目fk", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{api_test_case.project_id.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 50, message = "{api_test_case.project_id.length_range}", groups = {Created.class, Updated.class}) + private String projectId; + } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java index f51a8e4020..0bde5944a6 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java @@ -103,4 +103,6 @@ public interface ExtApiDefinitionMapper { List getListBySelectIds(@Param("projectId") String projectId, @Param("ids") List ids, @Param("protocols") List protocols); List getIdsByShareParam(@Param("projectId") String projectId, @Param("condition") String condition); + + long countByProjectAndId(@Param("projectId") String projectId, @Param("id") String id); } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml index 991e7abbf8..aa21d21ea0 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml @@ -159,7 +159,8 @@ + diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.java b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.java index 93d86a7693..7c32864bb5 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.java @@ -129,4 +129,6 @@ public interface ExtApiTestCaseMapper { List selectAllDetailByApiIds(@Param("apiIds") List apiIds); List selectAllDetailByIds(@Param("ids") List apiIds); + + List selectBaseInfoByProjectIdAndApiId(@Param("projectId") String projectId, @Param("apiId") String apiId); } \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.xml b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.xml index 7b6baf404a..afb021082e 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.xml +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.xml @@ -978,6 +978,7 @@ and api_scenario.deleted = 0; + @@ -992,9 +993,11 @@ + \ No newline at end of file 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 index 86864bc26c..cac989ead5 100644 --- 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 @@ -12,6 +12,7 @@ import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.LogUtils; import io.metersphere.system.uid.IDGenerator; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.io.InputStream; @@ -85,9 +86,21 @@ public class MetersphereParserApiScenario implements ApiScenarioImportParser { ApiScenarioStepParseResult apiScenarioStepParseResult = new ApiScenarioStepParseResult(); List stepList = apiScenarioStepMap.getOrDefault(scenarioId, new ArrayList<>()); - for (ApiScenarioStepDTO stepDTO : stepList) { + if (stepList.isEmpty()) { + return apiScenarioStepParseResult; + } + List firstStepList = stepList.stream().filter(step -> StringUtils.isEmpty(step.getParentId())).toList(); + if (CollectionUtils.isEmpty(firstStepList)) { + // 需要补充的场景数据中,它的步骤很可能存在于要导入的场景数据里,所以他们的parentId不一定为null。这时候要去parentId + // 去parentId方案: 将sort为1的步骤作为基点,它的parentId不起效果,所有以此为parentId的数据是第一层数据 + List sort1ScenarioList = stepList.stream().filter(step -> step.getSort() == 1).toList(); + if (CollectionUtils.isNotEmpty(sort1ScenarioList)) { + ApiScenarioStepDTO firstScenario = sort1ScenarioList.getFirst(); + firstStepList = stepList.stream().filter(step -> StringUtils.equals(step.getParentId(), firstScenario.getParentId())).toList(); + } + } + for (ApiScenarioStepDTO stepDTO : firstStepList) { String oldStepId = stepDTO.getId(); - ApiScenarioStepRequest stepRequest = new ApiScenarioStepRequest(); BeanUtils.copyBean(stepRequest, stepDTO); // 赋值新ID防止和库内已有数据重复 diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java index 24ca872bd1..3085a4149c 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java @@ -1,9 +1,11 @@ package io.metersphere.api.service; +import io.metersphere.api.constants.ApiDefinitionStatus; import io.metersphere.api.constants.ApiScenarioExportType; import io.metersphere.api.constants.ApiScenarioStepType; import io.metersphere.api.domain.*; import io.metersphere.api.dto.ApiFile; +import io.metersphere.api.dto.converter.ApiDefinitionDetail; import io.metersphere.api.dto.converter.ApiDefinitionExportDetail; import io.metersphere.api.dto.converter.ApiScenarioImportParseResult; import io.metersphere.api.dto.converter.ApiScenarioPreImportAnalysisResult; @@ -15,6 +17,9 @@ import io.metersphere.api.mapper.*; import io.metersphere.api.parser.ApiScenarioImportParser; import io.metersphere.api.parser.ImportParserFactory; import io.metersphere.api.service.definition.ApiDefinitionExportService; +import io.metersphere.api.service.definition.ApiDefinitionImportService; +import io.metersphere.api.service.definition.ApiDefinitionModuleService; +import io.metersphere.api.service.definition.ApiTestCaseService; import io.metersphere.api.service.scenario.ApiScenarioLogService; import io.metersphere.api.service.scenario.ApiScenarioModuleService; import io.metersphere.api.service.scenario.ApiScenarioService; @@ -26,6 +31,7 @@ import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.project.domain.Project; import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; import io.metersphere.project.mapper.ProjectMapper; +import io.metersphere.project.service.PermissionCheckService; import io.metersphere.project.utils.FileDownloadUtils; import io.metersphere.sdk.constants.*; import io.metersphere.sdk.dto.ExportMsgDTO; @@ -47,9 +53,12 @@ import io.metersphere.system.service.FileService; import io.metersphere.system.service.NoticeSendService; import io.metersphere.system.socket.ExportWebSocketHandler; import io.metersphere.system.uid.IDGenerator; +import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.utils.TreeNodeParseUtils; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; +import lombok.Data; import org.apache.commons.collections.MapUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; @@ -65,6 +74,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -77,7 +87,13 @@ public class ApiScenarioDataTransferService { private final ThreadLocal currentApiScenarioOrder = new ThreadLocal<>(); - private final ThreadLocal currentModuleOrder = new ThreadLocal<>(); + private final ThreadLocal currentScenarioModuleOrder = new ThreadLocal<>(); + + private final ThreadLocal currentApiModuleOrder = new ThreadLocal<>(); + + private final ThreadLocal currentApiOrder = new ThreadLocal<>(); + + private final ThreadLocal currentApiTestCaseOrder = new ThreadLocal<>(); private static final String EXPORT_CASE_TMP_DIR = "apiScenario"; @@ -116,6 +132,14 @@ public class ApiScenarioDataTransferService { private ApiScenarioLogService apiScenarioLogService; @Resource private ApiDefinitionExportService apiDefinitionExportService; + @Resource + private PermissionCheckService permissionCheckService; + @Resource + private ApiDefinitionImportService apiDefinitionImportService; + @Resource + private ApiDefinitionModuleService apiDefinitionModuleService; + @Resource + private ApiTestCaseService apiTestCaseService; public String exportScenario(ApiScenarioBatchExportRequest request, String type, String userId) { String returnId; @@ -155,25 +179,114 @@ public class ApiScenarioDataTransferService { } //解析 ApiScenarioPreImportAnalysisResult preImportAnalysisResult = this.importAnalysis( - parseResult, request.getProjectId(), request.getModuleId(), apiScenarioModuleService.getTree(request.getProjectId())); - + parseResult, request.getOperator(), request.getProjectId(), 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 (CollectionUtils.isNotEmpty(preImportAnalysisResult.getInsertApiModuleList())) { + Map> projectMap = preImportAnalysisResult.getInsertApiModuleList().stream().collect(Collectors.groupingBy(BaseTreeNode::getProjectId)); + ApiDefinitionModuleMapper batchApiModuleMapper = sqlSession.getMapper(ApiDefinitionModuleMapper.class); + projectMap.forEach((targetProjectId, nodeList) -> { + currentApiModuleOrder.remove(); + nodeList.forEach(t -> { + ApiDefinitionModule module = new ApiDefinitionModule(); + module.setId(t.getId()); + module.setName(t.getName()); + module.setParentId(t.getParentId()); + module.setProjectId(t.getProjectId()); + module.setCreateUser(operator); + module.setPos(getImportNextOrder(apiDefinitionModuleService::getNextOrder, currentApiModuleOrder, targetProjectId)); + module.setCreateTime(System.currentTimeMillis()); + module.setUpdateUser(operator); + module.setUpdateTime(System.currentTimeMillis()); + batchApiModuleMapper.insertSelective(module); + }); + sqlSession.flushStatements(); + }); + } + // 接口定义 + if (CollectionUtils.isNotEmpty(preImportAnalysisResult.getInsertApiDefinitions())) { + Map> projectMap = preImportAnalysisResult.getInsertApiDefinitions().stream().collect(Collectors.groupingBy(ApiDefinitionDetail::getProjectId)); + ApiDefinitionMapper batchApiMapper = sqlSession.getMapper(ApiDefinitionMapper.class); + ApiDefinitionBlobMapper batchApiBlobMapper = sqlSession.getMapper(ApiDefinitionBlobMapper.class); + projectMap.forEach((targetProjectId, apiList) -> { + currentApiOrder.remove(); + String versionId = extBaseProjectVersionMapper.getDefaultVersion(targetProjectId); + for (ApiDefinitionDetail t : apiList) { + ApiDefinition apiDefinition = new ApiDefinition(); + BeanUtils.copyBean(apiDefinition, t); + apiDefinition.setPos(getImportNextOrder(apiDefinitionImportService::getNextOrder, currentApiOrder, targetProjectId)); + apiDefinition.setLatest(true); + apiDefinition.setStatus(ApiDefinitionStatus.PROCESSING.name()); + apiDefinition.setRefId(apiDefinition.getId()); + apiDefinition.setVersionId(versionId); + apiDefinition.setCreateUser(operator); + apiDefinition.setCreateTime(System.currentTimeMillis()); + apiDefinition.setUpdateUser(operator); + apiDefinition.setUpdateTime(System.currentTimeMillis()); + batchApiMapper.insertSelective(apiDefinition); + //插入blob数据 + ApiDefinitionBlob apiDefinitionBlob = new ApiDefinitionBlob(); + apiDefinitionBlob.setId(apiDefinition.getId()); + apiDefinitionBlob.setRequest(ApiDataUtils.toJSONString(t.getRequest()).getBytes(StandardCharsets.UTF_8)); + apiDefinitionBlob.setResponse(ApiDataUtils.toJSONString(t.getResponse()).getBytes(StandardCharsets.UTF_8)); + batchApiBlobMapper.insertSelective(apiDefinitionBlob); + } + sqlSession.flushStatements(); + }); + + + } + // 接口用例 + if (CollectionUtils.isNotEmpty(preImportAnalysisResult.getInsertApiTestCaseList())) { + Map> projectMap = preImportAnalysisResult.getInsertApiTestCaseList().stream().collect(Collectors.groupingBy(ApiTestCaseDTO::getProjectId)); + + ApiTestCaseMapper batchApiCaseMapper = sqlSession.getMapper(ApiTestCaseMapper.class); + ApiTestCaseBlobMapper batchApiBlobCaseMapper = sqlSession.getMapper(ApiTestCaseBlobMapper.class); + + projectMap.forEach((targetProjectId, testCaseList) -> { + currentApiTestCaseOrder.remove(); + String versionId = extBaseProjectVersionMapper.getDefaultVersion(targetProjectId); + testCaseList.forEach(t -> { + ApiTestCase apiTestCase = new ApiTestCase(); + BeanUtils.copyBean(apiTestCase, t); + apiTestCase.setProjectId(targetProjectId); + apiTestCase.setPos(getImportNextOrder(apiTestCaseService::getNextOrder, currentApiTestCaseOrder, targetProjectId)); + apiTestCase.setNum(NumGenerator.nextNum(targetProjectId, ApplicationNumScope.API_DEFINITION)); + apiTestCase.setStatus(ApiDefinitionStatus.PROCESSING.name()); + apiTestCase.setVersionId(versionId); + apiTestCase.setCreateUser(operator); + apiTestCase.setCreateTime(System.currentTimeMillis()); + apiTestCase.setUpdateUser(operator); + apiTestCase.setUpdateTime(System.currentTimeMillis()); + batchApiCaseMapper.insertSelective(apiTestCase); + //插入blob数据 + ApiTestCaseBlob apiTestCaseBlob = new ApiTestCaseBlob(); + apiTestCaseBlob.setId(apiTestCase.getId()); + + apiTestCaseBlob.setRequest(apiDefinitionImportService.getMsTestElementStr(t.getRequest()).getBytes()); + batchApiBlobCaseMapper.insertSelective(apiTestCaseBlob); + }); + sqlSession.flushStatements(); + }); + } + + //场景模块 + this.insertModule(operator, preImportAnalysisResult.getInsertScenarioModuleList(), sqlSession); if (isCoverData) { this.updateScenarios(projectId, operator, preImportAnalysisResult.getUpdateApiScenarioData(), sqlSession); } String versionId = extBaseProjectVersionMapper.getDefaultVersion(projectId); - this.insertScenarios(projectId, operator, versionId, preImportAnalysisResult.getInsertApiScenarioData(), sqlSession); + //场景 + this.insertScenarios(operator, versionId, preImportAnalysisResult.getInsertApiScenarioData(), sqlSession); + sqlSession.flushStatements(); SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); } @@ -183,7 +296,7 @@ public class ApiScenarioDataTransferService { List noticeCreateLists = new ArrayList<>(); List noticeUpdateLists = new ArrayList<>(); - preImportAnalysisResult.getInsertModuleList().forEach(t -> + preImportAnalysisResult.getInsertScenarioModuleList().forEach(t -> operationLogs.add(ApiDefinitionImportUtils.genImportLog(project, t.getId(), t.getName(), t, OperationLogModule.API_SCENARIO_MANAGEMENT_MODULE, operator, OperationLogType.ADD.name())) ); @@ -339,20 +452,23 @@ public class ApiScenarioDataTransferService { } } - private void insertModule(String projectId, String operator, List insertModuleList, SqlSession sqlSession) { + private void insertModule(String operator, List insertModuleList, SqlSession sqlSession) { if (CollectionUtils.isEmpty(insertModuleList)) { return; } ApiScenarioModuleMapper batchApiDefinitionMapper = sqlSession.getMapper(ApiScenarioModuleMapper.class); - SubListUtils.dealForSubList(insertModuleList, 100, list -> { + + Map> projectMap = insertModuleList.stream().collect(Collectors.groupingBy(BaseTreeNode::getProjectId)); + + projectMap.forEach((targetProjectId, list) -> { list.forEach(t -> { ApiScenarioModule module = new ApiScenarioModule(); module.setId(t.getId()); module.setName(t.getName()); module.setParentId(t.getParentId()); - module.setProjectId(projectId); + module.setProjectId(t.getProjectId()); module.setCreateUser(operator); - module.setPos(getImportNextOrder(apiScenarioModuleService::getNextOrder, currentModuleOrder, projectId)); + module.setPos(getImportNextOrder(apiScenarioModuleService::getNextOrder, currentScenarioModuleOrder, targetProjectId)); module.setCreateTime(System.currentTimeMillis()); module.setUpdateUser(operator); module.setUpdateTime(module.getCreateTime()); @@ -362,7 +478,7 @@ public class ApiScenarioDataTransferService { }); } - private void insertScenarios(String projectId, String operator, String versionId, List insertScenarioList, SqlSession sqlSession) { + private void insertScenarios(String operator, String versionId, List insertScenarioList, SqlSession sqlSession) { // 创建场景 if (CollectionUtils.isEmpty(insertScenarioList)) { return; @@ -375,42 +491,48 @@ public class ApiScenarioDataTransferService { 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(getImportNextOrder(apiScenarioService::getNextOrder, currentApiScenarioOrder, 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); + Map> projectMap = insertScenarioList.stream().collect(Collectors.groupingBy(ApiScenarioImportDetail::getProjectId)); - // 更新场景配置 - 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); + projectMap.forEach((targetProjectId, targetList) -> { + currentApiScenarioOrder.remove(); + SubListUtils.dealForSubList(targetList, 100, list -> { + list.forEach(t -> { + ApiScenario scenario = new ApiScenario(); + BeanUtils.copyBean(scenario, t); + scenario.setNum(apiScenarioService.getNextNum(targetProjectId)); + scenario.setPos(getImportNextOrder(apiScenarioService::getNextOrder, currentApiScenarioOrder, targetProjectId)); + 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(); }); - sqlSession.flushStatements(); }); + + } private void handleStepAdd(ApiScenarioImportDetail t, ApiScenario scenario, @@ -518,14 +640,17 @@ public class ApiScenarioDataTransferService { return order; } - private ApiScenarioPreImportAnalysisResult importAnalysis(ApiScenarioImportParseResult parseResult, String projectId, String moduleId, List apiScenarioModules) { + private ApiScenarioPreImportAnalysisResult importAnalysis(ApiScenarioImportParseResult parseResult, String operator, String projectId, 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)); + ReplaceScenarioResource replaceScenarioResource = this.parseRelatedDataToAnalysisResult(operator, projectId, parseResult, analysisResult); + List importScenarios = parseResult.getImportScenarioList(); 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)); @@ -555,23 +680,329 @@ public class ApiScenarioDataTransferService { scenario.setId(existenceNameIdMap.get(scenario.getName())); analysisResult.getUpdateApiScenarioData().add(scenario); } else { + scenario.setId(IDGenerator.nextStr()); scenario.setModuleId(modulePathMap.get(finalModulePath).getId()); analysisResult.getInsertApiScenarioData().add(scenario); } }); } else { //模块不存在的必定是新建 - analysisResult.getInsertModuleList().addAll(TreeNodeParseUtils.getInsertNodeByPath(modulePathMap, modulePath)); + List insertModuleList = TreeNodeParseUtils.getInsertNodeByPath(modulePathMap, modulePath); + insertModuleList.forEach(item -> item.setProjectId(projectId)); + analysisResult.getInsertScenarioModuleList().addAll(insertModuleList); String finalModulePath = modulePath; - scenarios.forEach(scenario -> - scenario.setModuleId(modulePathMap.get(finalModulePath).getId()) - ); + scenarios.forEach(scenario -> { + scenario.setId(IDGenerator.nextStr()); + scenario.setModuleId(modulePathMap.get(finalModulePath).getId()); + }); analysisResult.getInsertApiScenarioData().addAll(scenarios); } }); + //将要补的场景数据导入进来 + analysisResult.getInsertApiScenarioData().addAll(replaceScenarioResource.getInsertRelatedApiScenarioData()); + + for (ApiScenarioImportDetail importScenario : analysisResult.getInsertApiScenarioData()) { + // 处理步骤里的关联资源文件 + importScenario.getSteps().forEach(item -> { + if (StringUtils.equalsIgnoreCase(item.getStepType(), ApiScenarioStepType.API.name())) { + ApiDefinitionDetail apiDetail = replaceScenarioResource.getApi(item.getResourceId()); + if (apiDetail != null) { + item.setResourceId(apiDetail.getId()); + item.setProjectId(importScenario.getProjectId()); + item.setOriginProjectId(apiDetail.getProjectId()); + } + } else if (StringUtils.equalsIgnoreCase(item.getStepType(), ApiScenarioStepType.API_CASE.name())) { + ApiTestCaseDTO newData = replaceScenarioResource.getApiCase(item.getResourceId()); + if (newData != null) { + item.setResourceId(newData.getId()); + item.setProjectId(importScenario.getProjectId()); + item.setOriginProjectId(newData.getProjectId()); + } + } else if (StringUtils.equalsIgnoreCase(item.getStepType(), ApiScenarioStepType.API_SCENARIO.name())) { + ApiScenarioImportDetail newData = replaceScenarioResource.getApiScenario(item.getResourceId()); + if (newData != null) { + item.setResourceId(newData.getId()); + item.setProjectId(importScenario.getProjectId()); + item.setOriginProjectId(newData.getProjectId()); + } + } + }); + } + for (ApiScenarioImportDetail updateScenario : analysisResult.getUpdateApiScenarioData()) { + // 处理步骤里的关联资源文件 + updateScenario.getSteps().forEach(item -> { + if (StringUtils.equalsIgnoreCase(item.getStepType(), ApiScenarioStepType.API.name())) { + ApiDefinitionDetail apiDetail = replaceScenarioResource.getApi(item.getResourceId()); + if (apiDetail != null) { + item.setResourceId(apiDetail.getId()); + item.setProjectId(updateScenario.getProjectId()); + item.setOriginProjectId(apiDetail.getProjectId()); + } + } else if (StringUtils.equalsIgnoreCase(item.getStepType(), ApiScenarioStepType.API_CASE.name())) { + ApiTestCaseDTO newData = replaceScenarioResource.getApiCase(item.getResourceId()); + if (newData != null) { + item.setResourceId(newData.getId()); + item.setProjectId(updateScenario.getProjectId()); + item.setOriginProjectId(newData.getProjectId()); + } + } else if (StringUtils.equalsIgnoreCase(item.getStepType(), ApiScenarioStepType.API_SCENARIO.name())) { + ApiScenarioImportDetail newData = replaceScenarioResource.getApiScenario(item.getResourceId()); + if (newData != null) { + item.setResourceId(newData.getId()); + item.setProjectId(updateScenario.getProjectId()); + item.setOriginProjectId(newData.getProjectId()); + } + } + }); + } return analysisResult; } + private ReplaceScenarioResource parseRelatedDataToAnalysisResult(String operator, String projectId, ApiScenarioImportParseResult parseResult, ApiScenarioPreImportAnalysisResult analysisResult) { + /* + 1.判断接口定义中要创建的部分,然后进行新旧ID映射 + 2.判断用例中的接口定义是否在上一步中创建,如果没有则进行筛选判断符合条件的接口定义(若没有则新建)并关联到其下面。 + */ + Map> projectApiMap = new HashMap<>(); + Map> projectApiCaseMap = new HashMap<>(); + Map> projectScenarioMap = new HashMap<>(); + // 对导入文件中的数据进行分组 + { + for (Map.Entry> entry : parseResult.getRelatedApiDefinitions().stream().collect(Collectors.groupingBy(ApiDefinitionDetail::getProjectId)).entrySet()) { + String targetProjectId = entry.getKey(); + List apiDefinitionDetails = entry.getValue(); + // 如果没有权限,需要在当前项目进行校验 + if (projectMapper.selectByPrimaryKey(targetProjectId) == null) { + targetProjectId = projectId; + } else if (!permissionCheckService.userHasProjectPermission(operator, targetProjectId, PermissionConstants.PROJECT_API_DEFINITION_ADD)) { + targetProjectId = projectId; + } + if (projectApiMap.containsKey(targetProjectId)) { + projectApiMap.get(targetProjectId).addAll(apiDefinitionDetails); + } else { + projectApiMap.put(targetProjectId, apiDefinitionDetails); + } + } + for (Map.Entry> entry : parseResult.getRelatedApiTestCaseList().stream().collect(Collectors.groupingBy(ApiTestCaseDTO::getProjectId)).entrySet()) { + String targetProjectId = entry.getKey(); + List apiTestCaseList = entry.getValue(); + // 如果没有权限,需要在当前项目进行校验 + if (projectMapper.selectByPrimaryKey(targetProjectId) == null) { + targetProjectId = projectId; + } else if (!permissionCheckService.userHasProjectPermission(operator, targetProjectId, PermissionConstants.PROJECT_API_DEFINITION_CASE_ADD)) { + targetProjectId = projectId; + } + if (projectApiCaseMap.containsKey(targetProjectId)) { + projectApiCaseMap.get(targetProjectId).addAll(apiTestCaseList); + } else { + projectApiCaseMap.put(targetProjectId, apiTestCaseList); + } + } + for (Map.Entry> entry : parseResult.getRelatedScenarioList().stream().collect(Collectors.groupingBy(ApiScenarioImportDetail::getProjectId)).entrySet()) { + String targetProjectId = entry.getKey(); + List scenarioList = entry.getValue(); + // 如果没有权限,需要在当前项目进行校验 + if (projectMapper.selectByPrimaryKey(targetProjectId) == null) { + targetProjectId = projectId; + } else if (!permissionCheckService.userHasProjectPermission(operator, targetProjectId, PermissionConstants.PROJECT_API_DEFINITION_CASE_ADD)) { + targetProjectId = projectId; + } + if (projectScenarioMap.containsKey(targetProjectId)) { + projectScenarioMap.get(targetProjectId).addAll(scenarioList); + } else { + projectScenarioMap.put(targetProjectId, scenarioList); + } + } + } + + ReplaceScenarioResource returnResource = new ReplaceScenarioResource(); + // 1.处理接口定义 + { + for (Map.Entry> entry : projectApiMap.entrySet()) { + String targetProjectId = entry.getKey(); + List apiDefinitionDetails = entry.getValue(); + + Map> protocolImportMap = apiDefinitionDetails.stream().collect(Collectors.groupingBy(ApiDefinitionDetail::getProtocol)); + + ApiDefinitionPageRequest pageRequest = new ApiDefinitionPageRequest(); + pageRequest.setProjectId(targetProjectId); + pageRequest.setProtocols(new ArrayList<>(protocolImportMap.keySet())); + List existenceApiDefinitionList = extApiDefinitionMapper.importList(pageRequest); + + Map> existenceProtocolMap = existenceApiDefinitionList.stream().collect(Collectors.groupingBy(ApiDefinitionDetail::getProtocol)); + + for (Map.Entry> protocolEntry : protocolImportMap.entrySet()) { + returnResource.getApiDefinitionIdMap().putAll(ApiScenarioImportUtils.getApiIdInTargetList( + protocolEntry.getValue(), existenceProtocolMap.get(protocolEntry.getKey()), protocolEntry.getKey(), projectId, analysisResult)); + } + + // 解析接口定义的模块 + List apiModules = apiDefinitionImportService.buildTreeData(targetProjectId, null); + Map modulePathMap = apiModules.stream().collect(Collectors.toMap(BaseTreeNode::getPath, k -> k, (k1, k2) -> k1)); + for (ApiDefinitionDetail apiDefinitionDetail : analysisResult.getInsertApiDefinitions()) { + List insertModuleList = TreeNodeParseUtils.getInsertNodeByPath(modulePathMap, apiDefinitionDetail.getModulePath()); + insertModuleList.forEach(item -> item.setProjectId(targetProjectId)); + analysisResult.getInsertApiModuleList().addAll(insertModuleList); + } + } + } + // 2.处理接口用例 + { + for (Map.Entry> entry : projectApiCaseMap.entrySet()) { + String targetProjectId = entry.getKey(); + List apiTestCaseDTOS = entry.getValue(); + Map> protocolMap = apiTestCaseDTOS.stream().collect(Collectors.groupingBy(ApiTestCaseDTO::getProtocol)); + //将项目下的用例,通过协议、关键词进行分组处理。 + + ApiDefinitionPageRequest pageRequest = new ApiDefinitionPageRequest(); + pageRequest.setProjectId(targetProjectId); + pageRequest.setProtocols(new ArrayList<>(protocolMap.keySet())); + List existenceApiDefinitionList = extApiDefinitionMapper.importList(pageRequest); + + for (Map.Entry> protocolEntry : protocolMap.entrySet()) { + String protocol = protocolEntry.getKey(); + List protocolList = protocolEntry.getValue(); + + Map> apiIdMap = protocolList.stream().collect(Collectors.groupingBy(ApiTestCaseDTO::getApiDefinitionId)); + for (Map.Entry> apiIdEntry : apiIdMap.entrySet()) { + String replaceApiDefinitionId = returnResource.getApi(apiIdEntry.getKey()) == null ? StringUtils.EMPTY : returnResource.getApi(apiIdEntry.getKey()).getId(); + + List testCaseList = apiIdEntry.getValue(); + + if (StringUtils.isBlank(replaceApiDefinitionId)) { + // 用例对应的接口在上述步骤中未处理 + boolean apiExistence = ApiScenarioImportUtils.isApiExistence( + protocol, testCaseList.getFirst().getMethod(), testCaseList.getFirst().getPath(), + testCaseList.getFirst().getModulePath(), testCaseList.getFirst().getApiDefinitionName(), existenceApiDefinitionList); + + if (apiExistence) { + Map existenceApiCaseNumMap = extApiTestCaseMapper.selectBaseInfoByProjectIdAndApiId(targetProjectId, apiIdEntry.getKey()) + .stream().collect(Collectors.toMap(ApiTestCaseDTO::getNum, Function.identity(), (k1, k2) -> k1)); + for (ApiTestCaseDTO apiTestCaseDTO : apiIdEntry.getValue()) { + if (existenceApiCaseNumMap.containsKey(apiTestCaseDTO.getNum())) { + returnResource.putApiTestCase(apiTestCaseDTO.getId(), existenceApiCaseNumMap.get(apiTestCaseDTO.getNum())); + } else { + apiTestCaseDTO.setId(IDGenerator.nextStr()); + apiTestCaseDTO.setProjectId(targetProjectId); + returnResource.putApiTestCase(apiTestCaseDTO.getId(), apiTestCaseDTO); + analysisResult.setApiTestCase(apiTestCaseDTO); + } + } + } else { + // 同步创建API + ApiDefinitionDetail apiDefinitionDetail = new ApiDefinitionDetail(); + apiDefinitionDetail.setProjectId(targetProjectId); + apiDefinitionDetail.setModulePath(testCaseList.getFirst().getModulePath()); + apiDefinitionDetail.setId(IDGenerator.nextStr()); + apiDefinitionDetail.setName(testCaseList.getFirst().getApiDefinitionName()); + apiDefinitionDetail.setRequest(testCaseList.getFirst().getRequest()); + apiDefinitionDetail.setProtocol(testCaseList.getFirst().getProtocol()); + apiDefinitionDetail.setPath(testCaseList.getFirst().getPath()); + apiDefinitionDetail.setMethod(testCaseList.getFirst().getMethod()); + apiDefinitionDetail.setResponse(new ArrayList<>()); + returnResource.putApiDefinitionId(apiIdEntry.getKey(), apiDefinitionDetail); + analysisResult.setApiDefinition(apiDefinitionDetail); + + for (ApiTestCaseDTO apiTestCaseDTO : testCaseList) { + apiTestCaseDTO.setId(IDGenerator.nextStr()); + apiTestCaseDTO.setProjectId(targetProjectId); + apiTestCaseDTO.setApiDefinitionId(apiDefinitionDetail.getId()); + returnResource.putApiTestCase(apiTestCaseDTO.getId(), apiTestCaseDTO); + analysisResult.setApiTestCase(apiTestCaseDTO); + } + } + + } else { + Map existenceApiCaseNumMap = extApiTestCaseMapper.selectBaseInfoByProjectIdAndApiId(targetProjectId, replaceApiDefinitionId) + .stream().collect(Collectors.toMap(ApiTestCaseDTO::getNum, Function.identity(), (k1, k2) -> k1)); + for (ApiTestCaseDTO apiTestCaseDTO : testCaseList) { + apiTestCaseDTO.setApiDefinitionId(replaceApiDefinitionId); + if (existenceApiCaseNumMap.containsKey(apiTestCaseDTO.getNum())) { + returnResource.putApiTestCase(apiTestCaseDTO.getId(), existenceApiCaseNumMap.get(apiTestCaseDTO.getNum())); + } else { + apiTestCaseDTO.setProjectId(targetProjectId); + returnResource.putApiTestCase(apiTestCaseDTO.getId(), apiTestCaseDTO); + apiTestCaseDTO.setId(IDGenerator.nextStr()); + analysisResult.setApiTestCase(apiTestCaseDTO); + } + } + } + } + } + } + } + // 3. 处理场景 + { + for (Map.Entry> entry : projectScenarioMap.entrySet()) { + String targetProjectId = entry.getKey(); + List apiScenarioModules = apiScenarioModuleService.getTree(targetProjectId); + + 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)); + + List importScenarios = parseResult.getRelatedScenarioList(); + + for (ApiScenarioImportDetail importScenario : importScenarios) { + importScenario.setProjectId(targetProjectId); + if (StringUtils.isBlank(importScenario.getModulePath()) || StringUtils.equals(importScenario.getModulePath().trim(), "/")) { + importScenario.setModuleId(ModuleConstants.DEFAULT_NODE_ID); + importScenario.setModulePath(moduleIdPathMap.get(ModuleConstants.DEFAULT_NODE_ID)); + } else { + if (!StringUtils.startsWith(importScenario.getModulePath(), "/")) { + importScenario.setModulePath("/" + importScenario.getModulePath()); + } + if (StringUtils.endsWith(importScenario.getModulePath(), "/")) { + importScenario.setModulePath(importScenario.getModulePath().substring(0, importScenario.getModulePath().length() - 1)); + } + } + } + + Map> modulePathScenario = importScenarios.stream().collect(Collectors.groupingBy(ApiScenarioImportDetail::getModulePath)); + for (Map.Entry> modulePathEntry : modulePathScenario.entrySet()) { + String modulePath = modulePathEntry.getKey(); + + Map apiScenarioNameMap = new HashMap<>(); + String moduleId = null; + // 设置moduleId + if (!modulePathMap.containsKey(modulePath)) { + List insertModuleList = TreeNodeParseUtils.getInsertNodeByPath(modulePathMap, modulePath); + insertModuleList.forEach(item -> item.setProjectId(targetProjectId)); + analysisResult.getInsertScenarioModuleList().addAll(insertModuleList); + moduleId = modulePathMap.get(modulePath).getId(); + } else { + moduleId = modulePathMap.get(modulePath).getId(); + apiScenarioNameMap = extApiScenarioMapper.selectBaseInfoByModuleIdAndProjectId(moduleId, targetProjectId) + .stream().collect(Collectors.toMap(ApiScenario::getName, Function.identity(), (k1, k2) -> k1)); + } + + Map createdNameOldIds = new HashMap<>(); + + for (ApiScenarioImportDetail scenario : modulePathEntry.getValue()) { + scenario.setModuleId(moduleId); + if (createdNameOldIds.containsKey(scenario.getName())) { + returnResource.putApiScenarioId(scenario.getId(), createdNameOldIds.get(scenario.getName())); + } else { + if (apiScenarioNameMap.containsKey(scenario.getName())) { + ApiScenarioImportDetail oldData = new ApiScenarioImportDetail(); + BeanUtils.copyBean(oldData, apiScenarioNameMap.get(scenario.getName())); + returnResource.putApiScenarioId(scenario.getId(), oldData); + createdNameOldIds.put(scenario.getName(), oldData); + } else { + String oldId = scenario.getId(); + scenario.setId(IDGenerator.nextStr()); + scenario.setProjectId(targetProjectId); + returnResource.getInsertRelatedApiScenarioData().add(scenario); + returnResource.putApiScenarioId(oldId, scenario); + } + } + } + } + + } + } + return returnResource; + } + private String exportApiScenarioZip(ApiScenarioBatchExportRequest request, String exportType, String userId) throws Exception { File tmpDir = new File(LocalRepositoryDir.getSystemTempDir() + File.separatorChar + EXPORT_CASE_TMP_DIR + "_" + IDGenerator.nextStr()); if (!tmpDir.mkdirs()) { @@ -659,29 +1090,21 @@ public class ApiScenarioDataTransferService { } } + Map> projectApiModuleIdMap = new HashMap<>(); if (CollectionUtils.isNotEmpty(apiDefinitionIdList)) { List apiList = extApiDefinitionMapper.selectApiDefinitionWithBlob(apiDefinitionIdList); List projectIdList = apiList.stream().map(ApiDefinitionWithBlob::getProjectId).distinct().toList(); projectIdList.forEach(projectId -> projectApiModuleIdMap.put(projectId, apiDefinitionExportService.buildModuleIdPathMap(request.getProjectId()))); - for (ApiDefinitionWithBlob blob : apiList) { ApiDefinitionExportDetail detail = new ApiDefinitionExportDetail(); + BeanUtils.copyBean(detail, blob); if (blob.getRequest() != null) { detail.setRequest(ApiDataUtils.parseObject(new String(blob.getRequest()), AbstractMsTestElement.class)); } if (blob.getResponse() != null) { detail.setResponse(ApiDataUtils.parseArray(new String(blob.getResponse()), HttpResponse.class)); } - detail.setName(blob.getName()); - detail.setProtocol(blob.getProtocol()); - detail.setMethod(blob.getMethod()); - detail.setPath(blob.getPath()); - detail.setStatus(blob.getStatus()); - detail.setTags(blob.getTags()); - detail.setPos(blob.getPos()); - detail.setDescription(blob.getDescription()); - String moduleName; if (StringUtils.equals(blob.getModuleId(), ModuleConstants.DEFAULT_NODE_ID)) { moduleName = Translator.get("api_unplanned_request"); @@ -698,12 +1121,27 @@ public class ApiScenarioDataTransferService { } if (CollectionUtils.isNotEmpty(apiTestCaseWithBlobs)) { for (ApiTestCaseWithBlob apiTestCaseWithBlob : apiTestCaseWithBlobs) { + String projectId = apiTestCaseWithBlob.getProjectId(); + if (!projectApiModuleIdMap.containsKey(projectId)) { + projectApiModuleIdMap.put(projectId, apiDefinitionExportService.buildModuleIdPathMap(projectId)); + } + String moduleId = apiTestCaseWithBlob.getModuleId(); + ApiTestCaseDTO dto = new ApiTestCaseDTO(); - dto.setName(apiTestCaseWithBlob.getName()); - dto.setPriority(apiTestCaseWithBlob.getPriority()); - dto.setStatus(apiTestCaseWithBlob.getStatus()); + BeanUtils.copyBean(dto, apiTestCaseWithBlob); dto.setTags(apiTestCaseWithBlob.getTags()); dto.setRequest(ApiDataUtils.parseObject(new String(apiTestCaseWithBlob.getRequest()), AbstractMsTestElement.class)); + + String moduleName; + if (StringUtils.equals(moduleId, ModuleConstants.DEFAULT_NODE_ID)) { + moduleName = Translator.get("api_unplanned_request"); + } else if (projectApiModuleIdMap.containsKey(projectId) && projectApiModuleIdMap.get(projectId).containsKey(moduleId)) { + moduleName = projectApiModuleIdMap.get(projectId).get(moduleId); + } else { + dto.setModuleId(ModuleConstants.DEFAULT_NODE_ID); + moduleName = Translator.get("api_unplanned_request"); + } + dto.setModulePath(moduleName); response.addExportApiCase(dto); } } @@ -740,3 +1178,38 @@ public class ApiScenarioDataTransferService { } } } + +@Data +class ReplaceScenarioResource { + private Map apiDefinitionIdMap = new HashMap<>(); + private Map apiTestCaseIdMap = new HashMap<>(); + private Map apiScenarioIdMap = new HashMap<>(); + + @Schema(description = "要新增的关联场景") + List insertRelatedApiScenarioData = new ArrayList<>(); + + public ApiDefinitionDetail getApi(String apiOldId) { + return apiDefinitionIdMap.get(apiOldId); + } + + public ApiTestCaseDTO getApiCase(String oldId) { + return apiTestCaseIdMap.get(oldId); + } + + public ApiScenarioImportDetail getApiScenario(String oldId) { + return apiScenarioIdMap.get(oldId); + } + + + public void putApiDefinitionId(String oldId, ApiDefinitionDetail newData) { + apiDefinitionIdMap.put(oldId, newData); + } + + public void putApiTestCase(String oldId, ApiTestCaseDTO newData) { + apiTestCaseIdMap.put(oldId, newData); + } + + public void putApiScenarioId(String oldId, ApiScenarioImportDetail newData) { + apiScenarioIdMap.put(oldId, newData); + } +} 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 0d9b3367e5..9e3efb3a36 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 @@ -405,7 +405,7 @@ public class ApiDefinitionImportService { sqlSession.flushStatements(); } - private String getMsTestElementStr(Object request) { + public String getMsTestElementStr(Object request) { String requestStr = JSON.toJSONString(request); AbstractMsTestElement msTestElement = ApiDataUtils.parseObject(requestStr, AbstractMsTestElement.class); // 手动校验参数 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 3653839289..dd008cedc0 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 @@ -1823,7 +1823,7 @@ public class ApiScenarioService extends MoveNodeService { steps.forEach(dto -> { ApiScenarioStepDTO returnDTO = new ApiScenarioStepDTO(); BeanUtils.copyBean(returnDTO, dto); - if (!StringUtils.equalsIgnoreCase(parentId, dto.getId())) { + if (StringUtils.isNotBlank(parentId) && !StringUtils.equalsIgnoreCase(parentId, dto.getId())) { returnDTO.setParentId(parentId); } if (returnDTO.getConfig() != null && StringUtils.isNotBlank(returnDTO.getConfig().toString())) { 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 index 33f8f8b1ae..c2bac3bf9e 100644 --- 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 @@ -1,13 +1,25 @@ package io.metersphere.api.utils; +import io.metersphere.api.constants.ApiConstants; +import io.metersphere.api.dto.converter.ApiDefinitionDetail; +import io.metersphere.api.dto.converter.ApiScenarioPreImportAnalysisResult; 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; +import io.metersphere.system.uid.IDGenerator; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class ApiScenarioImportUtils { - public static LogDTO genImportLog(Project project, String dataId, String dataName, Object importData, String module, String operator, String operationType) { + public static LogDTO genImportLog(Project project, String dataId, String dataName, Object targetList, String module, String operator, String operationType) { LogDTO dto = new LogDTO( project.getId(), project.getOrganizationId(), @@ -19,7 +31,59 @@ public class ApiScenarioImportUtils { dto.setHistory(true); dto.setPath("/api/scenario/import"); dto.setMethod(HttpMethodConstants.POST.name()); - dto.setOriginalValue(JSON.toJSONBytes(importData)); + dto.setOriginalValue(JSON.toJSONBytes(targetList)); return dto; } + + public static Map getApiIdInTargetList(List compareList, List targetList, String protocol, String projectId, ApiScenarioPreImportAnalysisResult analysisResult) { + if (CollectionUtils.isEmpty(compareList)) { + return new HashMap<>(); + } + if (targetList == null) { + targetList = new ArrayList<>(); + } + + // API类型,通过 Method & Path 组合判断,接口是否存在 + Map targetApiIdMap = null; + + if (StringUtils.equalsIgnoreCase(protocol, ApiConstants.HTTP_PROTOCOL)) { + targetApiIdMap = targetList.stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t, (oldValue, newValue) -> newValue)); + } else { + targetApiIdMap = targetList.stream().collect(Collectors.toMap(t -> t.getModulePath() + t.getName(), t -> t, (oldValue, newValue) -> newValue)); + } + Map prepareInsertApi = new HashMap<>(); + Map apiIdDic = new HashMap<>(); + for (ApiDefinitionDetail compareApi : compareList) { + String compareKey = StringUtils.equalsIgnoreCase(protocol, ApiConstants.HTTP_PROTOCOL) ? + compareApi.getMethod() + compareApi.getPath() : compareApi.getModulePath() + compareApi.getName(); + // 去除文件中相同类型的接口 + + if (targetApiIdMap.containsKey(compareKey)) { + apiIdDic.put(compareApi.getId(), targetApiIdMap.get(compareKey)); + } else { + if (prepareInsertApi.containsKey(compareKey)) { + apiIdDic.put(compareApi.getId(), prepareInsertApi.get(compareKey)); + } else { + String oldId = compareApi.getId(); + compareApi.setProjectId(projectId); + compareApi.setId(IDGenerator.nextStr()); + analysisResult.setApiDefinition(compareApi); + apiIdDic.put(oldId, compareApi); + prepareInsertApi.put(compareKey, compareApi); + } + } + } + return apiIdDic; + } + + public static boolean isApiExistence(String protocol, String method, String path, String modulePath, String apiDefinitionName, List existenceApiDefinitionList) { + Map existenceMap = null; + if (StringUtils.equalsIgnoreCase(protocol, ApiConstants.HTTP_PROTOCOL)) { + existenceMap = existenceApiDefinitionList.stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t, (oldValue, newValue) -> newValue)); + return existenceMap.containsKey(method + path); + } else { + existenceMap = existenceApiDefinitionList.stream().collect(Collectors.toMap(t -> t.getModulePath() + t.getName(), t -> t, (oldValue, newValue) -> newValue)); + return existenceMap.containsKey(modulePath + apiDefinitionName); + } + } } diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java index a268b56011..0a810660fa 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java @@ -76,14 +76,21 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest { ApiScenarioImportRequest request = new ApiScenarioImportRequest(); request.setProjectId(project.getId()); request.setType("jmeter"); - String importType = "jmeter"; - String fileSuffix = "jmx"; - 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); + FileInputStream inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import-scenario/jmeter/simple.jmx")).getPath())); + MockMultipartFile file = new MockMultipartFile("file", "simple.jmx", 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); + + + request.setType("metersphere"); + inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import-scenario/metersphere/simple.ms")).getPath())); + file = new MockMultipartFile("file", "simple.ms", MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream); + paramMap = new LinkedMultiValueMap<>(); + paramMap.add("request", JSON.toJSONString(request)); + paramMap.add("file", file); + this.requestMultipartWithOkAndReturn(URL_POST_IMPORT, paramMap); } @Resource @@ -132,26 +139,11 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest { MetersphereApiScenarioExportResponse exportResponse = ApiDataUtils.parseObject(fileContent, MetersphereApiScenarioExportResponse.class); - Assertions.assertEquals(exportResponse.getExportScenarioList().size(), 3); + Assertions.assertEquals(exportResponse.getExportScenarioList().size(), 8); MsFileUtils.deleteDir("/tmp/api-scenario-export/"); } - @Test - @Order(3) - public void testImportMs() throws Exception { - ApiScenarioImportRequest request = new ApiScenarioImportRequest(); - request.setProjectId(project.getId()); - request.setType("metersphere"); - String importType = "metersphere"; - FileInputStream inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import-scenario/" + importType + "/all.ms")).getPath())); - MockMultipartFile file = new MockMultipartFile("file", "all.ms", 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); - } - private MvcResult download(String projectId, String fileId) throws Exception { return mockMvc.perform(MockMvcRequestBuilders.get("/api/scenario/download/file/" + projectId + "/" + fileId) .header(SessionConstants.HEADER_TOKEN, sessionId) diff --git a/backend/services/api-test/src/test/resources/file/import-scenario/metersphere/simple.ms b/backend/services/api-test/src/test/resources/file/import-scenario/metersphere/simple.ms new file mode 100644 index 0000000000..f12b477855 --- /dev/null +++ b/backend/services/api-test/src/test/resources/file/import-scenario/metersphere/simple.ms @@ -0,0 +1 @@ +{"organizationId":"717345437786112","projectId":"997050905108480","exportScenarioList":[{"id":"950871430496256","name":"场景复制:复制引用场景二人组","priority":"P0","status":"UNDERWAY","stepTotal":2,"requestPassRate":"0","lastReportStatus":"","lastReportId":null,"num":100003,"deleted":false,"pos":14798848,"versionId":"997050905108527","refId":"950871430496256","latest":true,"projectId":"997050905108480","moduleId":"661562424147968","description":"","tags":[],"grouped":false,"environmentId":"997050905108481","createUser":"724938939965440","createTime":1729236148336,"deleteTime":null,"deleteUser":null,"updateUser":"724938939965440","updateTime":1729236175161,"modulePath":"/正经场景模块","scenarioConfig":{"variable":{"commonVariables":[],"csvVariables":[]},"preProcessorConfig":{"enableGlobal":true,"processors":[]},"postProcessorConfig":{"enableGlobal":true,"processors":[]},"assertionConfig":{"assertions":[]},"otherConfig":{"enableGlobalCookie":true,"enableCookieShare":false,"enableStepWait":false,"failureStrategy":"CONTINUE"}},"steps":[]},{"id":"951661704478720","name":"场景引用:复制引用场景二人组","priority":"P0","status":"UNDERWAY","stepTotal":2,"requestPassRate":"0","lastReportStatus":"","lastReportId":null,"num":100004,"deleted":false,"pos":14802944,"versionId":"997050905108527","refId":"951661704478720","latest":true,"projectId":"997050905108480","moduleId":"661562424147968","description":"","tags":[],"grouped":false,"environmentId":"997050905108481","createUser":"724938939965440","createTime":1729236194110,"deleteTime":null,"deleteUser":null,"updateUser":"724938939965440","updateTime":1729236194110,"modulePath":"/正经场景模块","scenarioConfig":{"variable":{"commonVariables":[],"csvVariables":[]},"preProcessorConfig":{"enableGlobal":true,"processors":[]},"postProcessorConfig":{"enableGlobal":true,"processors":[]},"assertionConfig":{"assertions":[]},"otherConfig":{"enableGlobalCookie":true,"enableCookieShare":false,"enableStepWait":false,"failureStrategy":"CONTINUE"}},"steps":[]},{"id":"999782504308736","name":"TAPD获取信息","priority":"P0","status":"UNDERWAY","stepTotal":1,"requestPassRate":"100.00","lastReportStatus":"SUCCESS","lastReportId":"1124542720991233","num":100001,"deleted":false,"pos":4096,"versionId":"997050905108527","refId":"999782504308736","latest":true,"projectId":"997050905108480","moduleId":"661562424147968","description":"","tags":[],"grouped":false,"environmentId":"997050905108481","createUser":"724938939965440","createTime":1718438995959,"deleteTime":null,"deleteUser":null,"updateUser":"724938939965440","updateTime":1720579324155,"modulePath":"/正经场景模块","scenarioConfig":{"variable":{"commonVariables":[],"csvVariables":[]},"preProcessorConfig":{"enableGlobal":true,"processors":[]},"postProcessorConfig":{"enableGlobal":true,"processors":[]},"assertionConfig":{"assertions":[]},"otherConfig":{"enableGlobalCookie":true,"enableCookieShare":false,"enableStepWait":false,"failureStrategy":"CONTINUE"}},"steps":[]}],"apiScenarioCsvList":[],"scenarioStepList":[{"id":"16973629-e388-49d0-bfb4-0e320fc91af2","enable":true,"resourceId":null,"originProjectId":null,"stepType":"CUSTOM_REQUEST","refType":"DIRECT","config":{"protocol":"HTTP","method":"GET"},"csvIds":null,"projectId":"997050905108480","name":"自定义请求","resourceNum":null,"versionId":null,"children":null,"uniqueId":null,"scenarioId":"999782504308736","sort":1,"parentId":null},{"id":"172923614497700000","enable":true,"resourceId":"949101903970304","originProjectId":"997050905108480","stepType":"API_SCENARIO","refType":"COPY","config":{"id":"","name":"","enable":true,"enableScenarioEnv":false,"useOriginScenarioParamPreferential":true,"useOriginScenarioParam":false},"csvIds":null,"projectId":"997050905108480","name":"场景引用复制接口定义","resourceNum":"100001","versionId":null,"children":null,"uniqueId":null,"scenarioId":"950871430496256","sort":1,"parentId":null},{"id":"172923614497700001","enable":true,"resourceId":"933073076830208","originProjectId":"997050905108480","stepType":"API","refType":"COPY","config":{"id":"","name":"","enable":true,"protocol":"HTTP","method":"GET"},"csvIds":null,"projectId":"997050905108480","name":"可以正常执行的获取tapd-复制","resourceNum":"100003","versionId":null,"children":null,"uniqueId":null,"scenarioId":"950871430496256","sort":1,"parentId":"172923614497700000"},{"id":"172923614497700002","enable":true,"resourceId":"933073076830208","originProjectId":"997050905108480","stepType":"API","refType":"REF","config":{"id":"","name":"","enable":true,"protocol":"HTTP","method":"GET"},"csvIds":null,"projectId":"997050905108480","name":"可以正常执行的获取tapd-引用","resourceNum":"100003","versionId":null,"children":null,"uniqueId":null,"scenarioId":"950871430496256","sort":2,"parentId":"172923614497700000"},{"id":"172923614497800000","enable":true,"resourceId":"950029616906240","originProjectId":"997050905108480","stepType":"API_SCENARIO","refType":"COPY","config":{"id":"","name":"","enable":true,"enableScenarioEnv":false,"useOriginScenarioParamPreferential":true,"useOriginScenarioParam":false},"csvIds":null,"projectId":"997050905108480","name":"场景引用复制接口用例","resourceNum":"100002","versionId":null,"children":null,"uniqueId":null,"scenarioId":"950871430496256","sort":2,"parentId":null},{"id":"172923614497800001","enable":true,"resourceId":"933725911859200","originProjectId":"997050905108480","stepType":"API_CASE","refType":"COPY","config":{"id":"","name":"","enable":true,"protocol":"HTTP","method":"GET"},"csvIds":null,"projectId":"997050905108480","name":"复制的用例","resourceNum":"100003001","versionId":null,"children":null,"uniqueId":null,"scenarioId":"950871430496256","sort":1,"parentId":"172923614497800000"},{"id":"172923614497800002","enable":true,"resourceId":"933725911859200","originProjectId":"997050905108480","stepType":"API_CASE","refType":"REF","config":{"id":"","name":"","enable":true,"protocol":"HTTP","method":"GET"},"csvIds":null,"projectId":"997050905108480","name":"引用的用例","resourceNum":"100003001","versionId":null,"children":null,"uniqueId":null,"scenarioId":"950871430496256","sort":2,"parentId":"172923614497800000"},{"id":"172923619228400000","enable":true,"resourceId":"949101903970304","originProjectId":"997050905108480","stepType":"API_SCENARIO","refType":"REF","config":{"id":"","name":"","enable":true,"enableScenarioEnv":false,"useOriginScenarioParamPreferential":true,"useOriginScenarioParam":false},"csvIds":null,"projectId":"997050905108480","name":"场景引用复制接口定义","resourceNum":"100001","versionId":null,"children":null,"uniqueId":null,"scenarioId":"951661704478720","sort":1,"parentId":null},{"id":"172923619228400003","enable":true,"resourceId":"950029616906240","originProjectId":"997050905108480","stepType":"API_SCENARIO","refType":"REF","config":{"id":"","name":"","enable":true,"enableScenarioEnv":false,"useOriginScenarioParamPreferential":true,"useOriginScenarioParam":false},"csvIds":null,"projectId":"997050905108480","name":"场景引用复制接口用例","resourceNum":"100002","versionId":null,"children":null,"uniqueId":null,"scenarioId":"951661704478720","sort":2,"parentId":null},{"id":"172923599832400000","enable":true,"resourceId":"933073076830208","originProjectId":"997050905108480","stepType":"API","refType":"COPY","config":{"id":"","name":"","enable":true,"protocol":"HTTP","method":"GET"},"csvIds":null,"projectId":"997050905108480","name":"可以正常执行的获取tapd-复制","resourceNum":"100003","versionId":null,"children":null,"uniqueId":null,"scenarioId":"949101903970304","sort":1,"parentId":"172923619228400000"},{"id":"172923601542900000","enable":true,"resourceId":"933073076830208","originProjectId":"997050905108480","stepType":"API","refType":"REF","config":{"id":"","name":"","enable":true,"protocol":"HTTP","method":"GET"},"csvIds":null,"projectId":"997050905108480","name":"可以正常执行的获取tapd-引用","resourceNum":"100003","versionId":null,"children":null,"uniqueId":null,"scenarioId":"949101903970304","sort":2,"parentId":"172923619228400000"},{"id":"172923607239900000","enable":true,"resourceId":"933725911859200","originProjectId":"997050905108480","stepType":"API_CASE","refType":"COPY","config":{"id":"","name":"","enable":true,"protocol":"HTTP","method":"GET"},"csvIds":null,"projectId":"997050905108480","name":"复制的用例","resourceNum":"100003001","versionId":null,"children":null,"uniqueId":null,"scenarioId":"950029616906240","sort":1,"parentId":"172923619228400003"},{"id":"172923609009900000","enable":true,"resourceId":"933725911859200","originProjectId":"997050905108480","stepType":"API_CASE","refType":"REF","config":{"id":"","name":"","enable":true,"protocol":"HTTP","method":"GET"},"csvIds":null,"projectId":"997050905108480","name":"引用的用例","resourceNum":"100003001","versionId":null,"children":null,"uniqueId":null,"scenarioId":"950029616906240","sort":2,"parentId":"172923619228400003"}],"scenarioStepBlobMap":{"16973629-e388-49d0-bfb4-0e320fc91af2":"{\"authConfig\":{\"authType\":\"BASIC\",\"basicAuth\":{\"userName\":\"oOjrikkm\",\"password\":\"9CD9AB02-7497-0B85-2C4F-45CC3EA763F8\"},\"digestAuth\":{\"userName\":\"\",\"password\":\"\"}},\"body\":{\"bodyType\":\"NONE\",\"formDataBody\":{\"formValues\":[]},\"wwwFormBody\":{\"formValues\":[]},\"jsonBody\":{\"jsonValue\":\"\"},\"xmlBody\":{\"value\":\"\"},\"rawBody\":{\"value\":\"\"},\"binaryBody\":{\"description\":\"\",\"sendAsBody\":false}},\"headers\":[],\"otherConfig\":{\"connectTimeout\":60000,\"responseTimeout\":60000,\"certificateAlias\":\"\",\"followRedirects\":true,\"autoRedirects\":false},\"path\":\"https://api.tapd.cn/stories\",\"query\":[{\"id\":\"670da2aa-cad0-4b14-ac13-1e07b7affdf8\",\"enable\":true,\"key\":\"workspace_id\",\"value\":\"55049933\",\"paramType\":\"string\",\"description\":\"\",\"required\":false,\"encode\":false},{\"id\":\"9acdac70-4d00-414d-b767-37b22a3ea8a9\",\"enable\":true,\"key\":\"id\",\"value\":\"1155049933001012963\",\"paramType\":\"string\",\"description\":\"\",\"required\":false,\"encode\":false}],\"rest\":[],\"url\":\"https://api.tapd.cn/stories\",\"polymorphicName\":\"MsHTTPElement\",\"resourceId\":\"\",\"stepId\":\"16973629-e388-49d0-bfb4-0e320fc91af2\",\"uniqueId\":\"16973629-e388-49d0-bfb4-0e320fc91af2\",\"activeTab\":\"HEADER\",\"responseActiveTab\":\"BODY\",\"protocol\":\"HTTP\",\"method\":\"GET\",\"name\":\"\",\"unSaved\":true,\"customizeRequest\":true,\"customizeRequestEnvEnable\":false,\"children\":[{\"polymorphicName\":\"MsCommonElement\",\"assertionConfig\":{\"enableGlobal\":false,\"assertions\":[]},\"postProcessorConfig\":{\"enableGlobal\":false,\"processors\":[]},\"preProcessorConfig\":{\"enableGlobal\":false,\"processors\":[]}}],\"executeLoading\":false,\"uploadFileIds\":[],\"linkFileIds\":[],\"deleteFileIds\":[],\"unLinkFileIds\":[],\"isNew\":false}","172923599832400000":"{\"polymorphicName\":\"MsHTTPElement\",\"stepId\":null,\"resourceId\":null,\"projectId\":null,\"name\":\"https://api.tapd.cn/stories?workspace_id=55049933&id=1155049933001012963\",\"enable\":true,\"children\":[{\"polymorphicName\":\"MsCommonElement\",\"stepId\":null,\"resourceId\":null,\"projectId\":null,\"name\":null,\"enable\":true,\"children\":null,\"parent\":null,\"csvIds\":null,\"preProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]},\"postProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]},\"assertionConfig\":{\"enableGlobal\":true,\"assertions\":[]}}],\"parent\":null,\"csvIds\":null,\"customizeRequest\":false,\"customizeRequestEnvEnable\":false,\"path\":\"/stories\",\"method\":\"GET\",\"body\":{\"bodyType\":\"NONE\",\"noneBody\":{},\"formDataBody\":{\"formValues\":[]},\"wwwFormBody\":{\"formValues\":[]},\"jsonBody\":{\"enableJsonSchema\":false,\"jsonValue\":null,\"jsonSchema\":null},\"xmlBody\":{\"value\":null},\"rawBody\":{\"value\":null},\"binaryBody\":{\"description\":null,\"file\":null},\"bodyClassByType\":\"io.metersphere.api.dto.request.http.body.NoneBody\",\"bodyDataByType\":{}},\"headers\":[],\"rest\":[],\"query\":[{\"key\":\"workspace_id\",\"value\":\"55049933\",\"enable\":true,\"description\":\"\",\"paramType\":\"string\",\"required\":false,\"minLength\":null,\"maxLength\":null,\"encode\":false,\"valid\":true,\"notBlankValue\":true},{\"key\":\"id\",\"value\":\"1155049933001012963\",\"enable\":true,\"description\":\"\",\"paramType\":\"string\",\"required\":false,\"minLength\":null,\"maxLength\":null,\"encode\":false,\"valid\":true,\"notBlankValue\":true}],\"otherConfig\":{\"connectTimeout\":60000,\"responseTimeout\":60000,\"certificateAlias\":null,\"followRedirects\":false,\"autoRedirects\":true},\"authConfig\":{\"authType\":\"BASIC\",\"basicAuth\":{\"userName\":\"oOjrikkm\",\"password\":\"9CD9AB02-7497-0B85-2C4F-45CC3EA763F8\",\"valid\":true},\"digestAuth\":{\"userName\":null,\"password\":null,\"valid\":false},\"httpauthValid\":true},\"moduleId\":null,\"num\":null,\"mockNum\":null}","172923614497800001":"{\"authConfig\":{\"authType\":\"BASIC\",\"basicAuth\":{\"userName\":\"oOjrikkm\",\"password\":\"9CD9AB02-7497-0B85-2C4F-45CC3EA763F8\"},\"digestAuth\":{\"userName\":\"\",\"password\":\"\"}},\"body\":{\"bodyType\":\"NONE\",\"formDataBody\":{\"formValues\":[]},\"wwwFormBody\":{\"formValues\":[]},\"jsonBody\":{\"jsonValue\":\"\",\"enableJsonSchema\":true},\"xmlBody\":{\"value\":\"\"},\"rawBody\":{\"value\":\"\"},\"binaryBody\":{\"description\":\"\",\"sendAsBody\":false}},\"headers\":[],\"method\":\"GET\",\"otherConfig\":{\"connectTimeout\":60000,\"responseTimeout\":60000,\"certificateAlias\":\"\",\"followRedirects\":true,\"autoRedirects\":false},\"path\":\"/stories\",\"query\":[{\"id\":\"172059506570200000\",\"enable\":true,\"key\":\"workspace_id\",\"value\":\"55049933\",\"paramType\":\"string\",\"description\":\"\",\"required\":false,\"encode\":false},{\"id\":\"172059506675100000\",\"enable\":true,\"key\":\"id\",\"value\":\"1155049933001012963\",\"paramType\":\"string\",\"description\":\"\",\"required\":false,\"encode\":false}],\"rest\":[],\"url\":\"/stories\",\"polymorphicName\":\"MsHTTPElement\",\"name\":\"正常执行的但是断言失败的\",\"children\":[{\"polymorphicName\":\"MsCommonElement\",\"assertionConfig\":{\"enableGlobal\":true,\"assertions\":[{\"name\":\"响应体\",\"assertionType\":\"RESPONSE_BODY\",\"id\":\"1720595131649\",\"enable\":true,\"assertionBodyType\":\"JSON_PATH\",\"jsonPathAssertion\":{\"assertions\":[{\"id\":\"172059513165900000\",\"enable\":true,\"variableName\":\"\",\"variableType\":\"TEMPORARY\",\"extractScope\":\"BODY\",\"expression\":\"$.data[0].Story.id\",\"condition\":\"EQUALS\",\"extractType\":\"JSON_PATH\",\"expressionMatchingRule\":\"EXPRESSION\",\"resultMatchingRule\":\"RANDOM\",\"resultMatchingRuleNum\":1,\"responseFormat\":\"XML\",\"moreSettingPopoverVisible\":false,\"expectedValue\":\"aaaavbbb\"}]},\"xpathAssertion\":{\"responseFormat\":\"XML\",\"assertions\":[]},\"regexAssertion\":{\"assertions\":[]},\"bodyAssertionDataByType\":{\"assertions\":[]}}]},\"postProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]},\"preProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]}}]}","172923601542900000":"{\"polymorphicName\":\"MsHTTPElement\",\"stepId\":null,\"resourceId\":null,\"projectId\":null,\"name\":\"https://api.tapd.cn/stories?workspace_id=55049933&id=1155049933001012963\",\"enable\":true,\"children\":[{\"polymorphicName\":\"MsCommonElement\",\"stepId\":null,\"resourceId\":null,\"projectId\":null,\"name\":null,\"enable\":true,\"children\":null,\"parent\":null,\"csvIds\":null,\"preProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]},\"postProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]},\"assertionConfig\":{\"enableGlobal\":true,\"assertions\":[]}}],\"parent\":null,\"csvIds\":null,\"customizeRequest\":false,\"customizeRequestEnvEnable\":false,\"path\":\"/stories\",\"method\":\"GET\",\"body\":{\"bodyType\":\"NONE\",\"noneBody\":{},\"formDataBody\":{\"formValues\":[]},\"wwwFormBody\":{\"formValues\":[]},\"jsonBody\":{\"enableJsonSchema\":false,\"jsonValue\":null,\"jsonSchema\":null},\"xmlBody\":{\"value\":null},\"rawBody\":{\"value\":null},\"binaryBody\":{\"description\":null,\"file\":null},\"bodyClassByType\":\"io.metersphere.api.dto.request.http.body.NoneBody\",\"bodyDataByType\":{}},\"headers\":[],\"rest\":[],\"query\":[{\"key\":\"workspace_id\",\"value\":\"55049933\",\"enable\":true,\"description\":\"\",\"paramType\":\"string\",\"required\":false,\"minLength\":null,\"maxLength\":null,\"encode\":false,\"valid\":true,\"notBlankValue\":true},{\"key\":\"id\",\"value\":\"1155049933001012963\",\"enable\":true,\"description\":\"\",\"paramType\":\"string\",\"required\":false,\"minLength\":null,\"maxLength\":null,\"encode\":false,\"valid\":true,\"notBlankValue\":true}],\"otherConfig\":{\"connectTimeout\":60000,\"responseTimeout\":60000,\"certificateAlias\":null,\"followRedirects\":false,\"autoRedirects\":true},\"authConfig\":{\"authType\":\"BASIC\",\"basicAuth\":{\"userName\":\"oOjrikkm\",\"password\":\"9CD9AB02-7497-0B85-2C4F-45CC3EA763F8\",\"valid\":true},\"digestAuth\":{\"userName\":null,\"password\":null,\"valid\":false},\"httpauthValid\":true},\"moduleId\":null,\"num\":null,\"mockNum\":null}","172923607239900000":"{\"authConfig\":{\"authType\":\"BASIC\",\"basicAuth\":{\"userName\":\"oOjrikkm\",\"password\":\"9CD9AB02-7497-0B85-2C4F-45CC3EA763F8\"},\"digestAuth\":{\"userName\":\"\",\"password\":\"\"}},\"body\":{\"bodyType\":\"NONE\",\"formDataBody\":{\"formValues\":[]},\"wwwFormBody\":{\"formValues\":[]},\"jsonBody\":{\"jsonValue\":\"\",\"enableJsonSchema\":true},\"xmlBody\":{\"value\":\"\"},\"rawBody\":{\"value\":\"\"},\"binaryBody\":{\"description\":\"\",\"sendAsBody\":false}},\"headers\":[],\"method\":\"GET\",\"otherConfig\":{\"connectTimeout\":60000,\"responseTimeout\":60000,\"certificateAlias\":\"\",\"followRedirects\":true,\"autoRedirects\":false},\"path\":\"/stories\",\"query\":[{\"id\":\"172059506570200000\",\"enable\":true,\"key\":\"workspace_id\",\"value\":\"55049933\",\"paramType\":\"string\",\"description\":\"\",\"required\":false,\"encode\":false},{\"id\":\"172059506675100000\",\"enable\":true,\"key\":\"id\",\"value\":\"1155049933001012963\",\"paramType\":\"string\",\"description\":\"\",\"required\":false,\"encode\":false}],\"rest\":[],\"url\":\"/stories\",\"polymorphicName\":\"MsHTTPElement\",\"name\":\"正常执行的但是断言失败的\",\"children\":[{\"polymorphicName\":\"MsCommonElement\",\"assertionConfig\":{\"enableGlobal\":true,\"assertions\":[{\"name\":\"响应体\",\"assertionType\":\"RESPONSE_BODY\",\"id\":\"1720595131649\",\"enable\":true,\"assertionBodyType\":\"JSON_PATH\",\"jsonPathAssertion\":{\"assertions\":[{\"id\":\"172059513165900000\",\"enable\":true,\"variableName\":\"\",\"variableType\":\"TEMPORARY\",\"extractScope\":\"BODY\",\"expression\":\"$.data[0].Story.id\",\"condition\":\"EQUALS\",\"extractType\":\"JSON_PATH\",\"expressionMatchingRule\":\"EXPRESSION\",\"resultMatchingRule\":\"RANDOM\",\"resultMatchingRuleNum\":1,\"responseFormat\":\"XML\",\"moreSettingPopoverVisible\":false,\"expectedValue\":\"aaaavbbb\"}]},\"xpathAssertion\":{\"responseFormat\":\"XML\",\"assertions\":[]},\"regexAssertion\":{\"assertions\":[]},\"bodyAssertionDataByType\":{\"assertions\":[]}}]},\"postProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]},\"preProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]}}]}","172923614497700002":"{\"polymorphicName\":\"MsHTTPElement\",\"stepId\":null,\"resourceId\":null,\"projectId\":null,\"name\":\"https://api.tapd.cn/stories?workspace_id=55049933&id=1155049933001012963\",\"enable\":true,\"children\":[{\"polymorphicName\":\"MsCommonElement\",\"stepId\":null,\"resourceId\":null,\"projectId\":null,\"name\":null,\"enable\":true,\"children\":null,\"parent\":null,\"csvIds\":null,\"preProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]},\"postProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]},\"assertionConfig\":{\"enableGlobal\":true,\"assertions\":[]}}],\"parent\":null,\"csvIds\":null,\"customizeRequest\":false,\"customizeRequestEnvEnable\":false,\"path\":\"/stories\",\"method\":\"GET\",\"body\":{\"bodyType\":\"NONE\",\"noneBody\":{},\"formDataBody\":{\"formValues\":[]},\"wwwFormBody\":{\"formValues\":[]},\"jsonBody\":{\"enableJsonSchema\":false,\"jsonValue\":null,\"jsonSchema\":null},\"xmlBody\":{\"value\":null},\"rawBody\":{\"value\":null},\"binaryBody\":{\"description\":null,\"file\":null},\"bodyClassByType\":\"io.metersphere.api.dto.request.http.body.NoneBody\",\"bodyDataByType\":{}},\"headers\":[],\"rest\":[],\"query\":[{\"key\":\"workspace_id\",\"value\":\"55049933\",\"enable\":true,\"description\":\"\",\"paramType\":\"string\",\"required\":false,\"minLength\":null,\"maxLength\":null,\"encode\":false,\"valid\":true,\"notBlankValue\":true},{\"key\":\"id\",\"value\":\"1155049933001012963\",\"enable\":true,\"description\":\"\",\"paramType\":\"string\",\"required\":false,\"minLength\":null,\"maxLength\":null,\"encode\":false,\"valid\":true,\"notBlankValue\":true}],\"otherConfig\":{\"connectTimeout\":60000,\"responseTimeout\":60000,\"certificateAlias\":null,\"followRedirects\":false,\"autoRedirects\":true},\"authConfig\":{\"authType\":\"BASIC\",\"basicAuth\":{\"userName\":\"oOjrikkm\",\"password\":\"9CD9AB02-7497-0B85-2C4F-45CC3EA763F8\",\"valid\":true},\"digestAuth\":{\"userName\":null,\"password\":null,\"valid\":false},\"httpauthValid\":true},\"moduleId\":null,\"num\":null,\"mockNum\":null}","172923614497700001":"{\"polymorphicName\":\"MsHTTPElement\",\"stepId\":null,\"resourceId\":null,\"projectId\":null,\"name\":\"https://api.tapd.cn/stories?workspace_id=55049933&id=1155049933001012963\",\"enable\":true,\"children\":[{\"polymorphicName\":\"MsCommonElement\",\"stepId\":null,\"resourceId\":null,\"projectId\":null,\"name\":null,\"enable\":true,\"children\":null,\"parent\":null,\"csvIds\":null,\"preProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]},\"postProcessorConfig\":{\"enableGlobal\":true,\"processors\":[]},\"assertionConfig\":{\"enableGlobal\":true,\"assertions\":[]}}],\"parent\":null,\"csvIds\":null,\"customizeRequest\":false,\"customizeRequestEnvEnable\":false,\"path\":\"/stories\",\"method\":\"GET\",\"body\":{\"bodyType\":\"NONE\",\"noneBody\":{},\"formDataBody\":{\"formValues\":[]},\"wwwFormBody\":{\"formValues\":[]},\"jsonBody\":{\"enableJsonSchema\":false,\"jsonValue\":null,\"jsonSchema\":null},\"xmlBody\":{\"value\":null},\"rawBody\":{\"value\":null},\"binaryBody\":{\"description\":null,\"file\":null},\"bodyClassByType\":\"io.metersphere.api.dto.request.http.body.NoneBody\",\"bodyDataByType\":{}},\"headers\":[],\"rest\":[],\"query\":[{\"key\":\"workspace_id\",\"value\":\"55049933\",\"enable\":true,\"description\":\"\",\"paramType\":\"string\",\"required\":false,\"minLength\":null,\"maxLength\":null,\"encode\":false,\"valid\":true,\"notBlankValue\":true},{\"key\":\"id\",\"value\":\"1155049933001012963\",\"enable\":true,\"description\":\"\",\"paramType\":\"string\",\"required\":false,\"minLength\":null,\"maxLength\":null,\"encode\":false,\"valid\":true,\"notBlankValue\":true}],\"otherConfig\":{\"connectTimeout\":60000,\"responseTimeout\":60000,\"certificateAlias\":null,\"followRedirects\":false,\"autoRedirects\":true},\"authConfig\":{\"authType\":\"BASIC\",\"basicAuth\":{\"userName\":\"oOjrikkm\",\"password\":\"9CD9AB02-7497-0B85-2C4F-45CC3EA763F8\",\"valid\":true},\"digestAuth\":{\"userName\":null,\"password\":null,\"valid\":false},\"httpauthValid\":true},\"moduleId\":null,\"num\":null,\"mockNum\":null}"},"relatedApiDefinitions":[{"id":"933073076830208","name":"可以正常执行的获取tapd","protocol":"HTTP","method":"GET","path":"/stories","status":"PROCESSING","num":100003,"tags":[],"pos":12288,"projectId":"997050905108480","moduleId":"1000882015936512","latest":true,"versionId":"997050905108527","refId":"933073076830208","description":"","createTime":1720595112772,"createUser":"724938939965440","updateTime":1722239595523,"updateUser":"724938939965440","deleteUser":null,"deleteTime":null,"deleted":false,"request":{"polymorphicName":"MsHTTPElement","stepId":null,"resourceId":null,"projectId":null,"name":"https://api.tapd.cn/stories?workspace_id=55049933&id=1155049933001012963","enable":true,"children":[{"polymorphicName":"MsCommonElement","stepId":null,"resourceId":null,"projectId":null,"name":null,"enable":true,"children":null,"parent":null,"csvIds":null,"preProcessorConfig":{"enableGlobal":true,"processors":[]},"postProcessorConfig":{"enableGlobal":true,"processors":[]},"assertionConfig":{"enableGlobal":true,"assertions":[]}}],"parent":null,"csvIds":null,"customizeRequest":false,"customizeRequestEnvEnable":false,"path":"/stories","method":"GET","body":{"bodyType":"NONE","noneBody":{},"formDataBody":{"formValues":[]},"wwwFormBody":{"formValues":[]},"jsonBody":{"enableJsonSchema":false,"jsonValue":null,"jsonSchema":null},"xmlBody":{"value":null},"rawBody":{"value":null},"binaryBody":{"description":null,"file":null},"bodyClassByType":"io.metersphere.api.dto.request.http.body.NoneBody","bodyDataByType":{}},"headers":[],"rest":[],"query":[{"key":"workspace_id","value":"55049933","enable":true,"description":"","paramType":"string","required":false,"minLength":null,"maxLength":null,"encode":false,"valid":true,"notBlankValue":true},{"key":"id","value":"1155049933001012963","enable":true,"description":"","paramType":"string","required":false,"minLength":null,"maxLength":null,"encode":false,"valid":true,"notBlankValue":true}],"otherConfig":{"connectTimeout":60000,"responseTimeout":60000,"certificateAlias":null,"followRedirects":false,"autoRedirects":true},"authConfig":{"authType":"BASIC","basicAuth":{"userName":"oOjrikkm","password":"9CD9AB02-7497-0B85-2C4F-45CC3EA763F8","valid":true},"digestAuth":{"userName":null,"password":null,"valid":false},"httpauthValid":true},"moduleId":null,"num":null,"mockNum":null},"response":[],"modulePath":"/可以正常执行的接口","apiTestCaseList":[],"apiMockList":[]}],"relatedApiTestCaseList":[{"id":"933725911859200","name":"正常执行的但是断言失败的","priority":"P0","num":100003001,"status":"PROCESSING","lastReportStatus":"ERROR","lastReportId":null,"projectId":"997050905108480","apiDefinitionId":"933073076830208","apiDefinitionNum":null,"apiDefinitionName":"可以正常执行的获取tapd","environmentId":null,"createTime":null,"createUser":null,"updateTime":null,"updateUser":null,"deleteTime":null,"deleteUser":null,"follow":null,"method":"GET","path":"/stories","environmentName":null,"createName":null,"updateName":null,"deleteName":null,"tags":[],"passRate":null,"request":{"polymorphicName":"MsHTTPElement","stepId":null,"resourceId":null,"projectId":null,"name":"正常执行的但是断言失败的","enable":true,"children":[{"polymorphicName":"MsCommonElement","stepId":null,"resourceId":null,"projectId":null,"name":null,"enable":true,"children":[],"parent":null,"csvIds":null,"preProcessorConfig":{"enableGlobal":true,"processors":[]},"postProcessorConfig":{"enableGlobal":true,"processors":[]},"assertionConfig":{"enableGlobal":true,"assertions":[{"assertionType":"RESPONSE_BODY","enable":true,"name":"响应体","id":"1720595131649","projectId":null,"assertionBodyType":"JSON_PATH","jsonPathAssertion":{"assertions":[{"enable":true,"expression":"$.data[0].Story.id","condition":"EQUALS","expectedValue":"aaaavbbb","valid":true}]},"xpathAssertion":{"responseFormat":"XML","assertions":[]},"documentAssertion":null,"regexAssertion":{"assertions":[]},"bodyAssertionClassByType":"io.metersphere.project.api.assertion.body.MsJSONPathAssertion","bodyAssertionDataByType":{"assertions":[{"enable":true,"expression":"$.data[0].Story.id","condition":"EQUALS","expectedValue":"aaaavbbb","valid":true}]}}]}}],"parent":null,"csvIds":null,"customizeRequest":false,"customizeRequestEnvEnable":false,"path":"/stories","method":"GET","body":{"bodyType":"NONE","noneBody":null,"formDataBody":{"formValues":[]},"wwwFormBody":{"formValues":[]},"jsonBody":{"enableJsonSchema":true,"jsonValue":"","jsonSchema":null},"xmlBody":{"value":""},"rawBody":{"value":""},"binaryBody":{"description":"","file":null},"bodyClassByType":"io.metersphere.api.dto.request.http.body.NoneBody","bodyDataByType":null},"headers":[],"rest":[],"query":[{"key":"workspace_id","value":"55049933","enable":true,"description":"","paramType":"string","required":false,"minLength":null,"maxLength":null,"encode":false,"valid":true,"notBlankValue":true},{"key":"id","value":"1155049933001012963","enable":true,"description":"","paramType":"string","required":false,"minLength":null,"maxLength":null,"encode":false,"valid":true,"notBlankValue":true}],"otherConfig":{"connectTimeout":60000,"responseTimeout":60000,"certificateAlias":"","followRedirects":true,"autoRedirects":false},"authConfig":{"authType":"BASIC","basicAuth":{"userName":"oOjrikkm","password":"9CD9AB02-7497-0B85-2C4F-45CC3EA763F8","valid":true},"digestAuth":{"userName":"","password":"","valid":false},"httpauthValid":true},"moduleId":null,"num":null,"mockNum":null},"modulePath":"/可以正常执行的接口","moduleId":"1000882015936512","protocol":"HTTP","apiChange":null,"inconsistentWithApi":null,"ignoreApiDiff":null,"ignoreApiChange":null}],"relatedScenarioList":[{"id":"949101903970304","name":"场景引用复制接口定义","priority":"P0","status":"UNDERWAY","stepTotal":2,"requestPassRate":"0","lastReportStatus":"","lastReportId":null,"num":100001,"deleted":false,"pos":14790656,"versionId":"997050905108527","refId":"949101903970304","latest":true,"projectId":"997050905108480","moduleId":"661562424147968","description":"","tags":[],"grouped":false,"environmentId":"997050905108481","createUser":"724938939965440","createTime":1729236045861,"deleteTime":null,"deleteUser":null,"updateUser":"724938939965440","updateTime":1729236045861,"modulePath":"/正经场景模块","scenarioConfig":{"variable":{"commonVariables":[],"csvVariables":[]},"preProcessorConfig":{"enableGlobal":true,"processors":[]},"postProcessorConfig":{"enableGlobal":true,"processors":[]},"assertionConfig":{"assertions":[]},"otherConfig":{"enableGlobalCookie":true,"enableCookieShare":false,"enableStepWait":false,"failureStrategy":"CONTINUE"}},"steps":[]},{"id":"950029616906240","name":"场景引用复制接口用例","priority":"P0","status":"UNDERWAY","stepTotal":2,"requestPassRate":"0","lastReportStatus":"","lastReportId":null,"num":100002,"deleted":false,"pos":14794752,"versionId":"997050905108527","refId":"950029616906240","latest":true,"projectId":"997050905108480","moduleId":"661562424147968","description":"","tags":[],"grouped":false,"environmentId":"997050905108481","createUser":"724938939965440","createTime":1729236099525,"deleteTime":null,"deleteUser":null,"updateUser":"724938939965440","updateTime":1729236099525,"modulePath":"/正经场景模块","scenarioConfig":{"variable":{"commonVariables":[],"csvVariables":[]},"preProcessorConfig":{"enableGlobal":true,"processors":[]},"postProcessorConfig":{"enableGlobal":true,"processors":[]},"assertionConfig":{"assertions":[]},"otherConfig":{"enableGlobalCookie":true,"enableCookieShare":false,"enableStepWait":false,"failureStrategy":"CONTINUE"}},"steps":[]}]} \ No newline at end of file diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/BaseTreeNode.java b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/BaseTreeNode.java index a6b44bc64a..c0a2dee483 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/BaseTreeNode.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/BaseTreeNode.java @@ -25,6 +25,9 @@ public class BaseTreeNode { @Schema(description = "父节点ID") private String parentId; + @Schema(description = "项目ID") + private String projectId; + @Schema(description = "子节点") private List children = new ArrayList<>();