diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCaseExecuteCallbackService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCaseExecuteCallbackService.java index 4114ae2659..293d1f7b96 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCaseExecuteCallbackService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCaseExecuteCallbackService.java @@ -1,7 +1,6 @@ package io.metersphere.api.service; import io.metersphere.api.domain.ApiTestCase; -import io.metersphere.api.domain.ApiTestCaseRecord; import io.metersphere.api.invoker.ApiExecuteCallbackServiceInvoker; import io.metersphere.api.mapper.ApiTestCaseMapper; import io.metersphere.api.service.definition.ApiTestCaseBatchRunService; @@ -15,11 +14,11 @@ import io.metersphere.sdk.dto.api.task.TaskItem; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.LogUtils; import io.metersphere.sdk.util.Translator; -import io.metersphere.system.uid.IDGenerator; import jakarta.annotation.Resource; import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.StringUtils; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -68,12 +67,17 @@ public class ApiCaseExecuteCallbackService implements ApiExecuteCallbackService private String initReport(GetRunScriptRequest request, ApiTestCase apiTestCase) { TaskItem taskItem = request.getTaskItem(); String reportId = taskItem.getReportId(); - if (BooleanUtils.isTrue(request.getBatch()) && !request.getRunModeConfig().isIntegratedReport()) { - // 批量执行,生成独立报告 - reportId = apiTestCaseBatchRunService.initApiReport(taskItem.getId(), request.getRunModeConfig(), apiTestCase, request.getUserId()); - } else if (BooleanUtils.isFalse(request.getBatch()) && !ApiExecuteRunMode.isDebug(request.getRunMode())) { - // 单用例执行,非调试,生成报告 - return apiTestCaseRunService.initApiReport(taskItem.getId(), apiTestCase, request); + try { + if (BooleanUtils.isTrue(request.getBatch()) && !request.getRunModeConfig().isIntegratedReport()) { + // 批量执行,生成独立报告 + reportId = apiTestCaseBatchRunService.initApiReport(taskItem.getId(), taskItem.getReportId(), request.getRunModeConfig(), apiTestCase, request.getUserId()); + } else if (BooleanUtils.isFalse(request.getBatch()) && !ApiExecuteRunMode.isDebug(request.getRunMode())) { + // 单用例执行,非调试,生成报告 + return apiTestCaseRunService.initApiReport(taskItem.getId(), apiTestCase, request); + } + } catch (DuplicateKeyException e) { + // 避免重试,报告ID重复,导致执行失败 + LogUtils.error(e); } return reportId; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteCallbackService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteCallbackService.java index 976dea31bd..fdf43f6a0c 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteCallbackService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteCallbackService.java @@ -19,7 +19,9 @@ public interface ApiExecuteCallbackService { /** * 解析并返回执行脚本 */ - String initReport(GetRunScriptRequest request); + default String initReport(GetRunScriptRequest request) { + return request.getTaskItem().getReportId(); + } /** * 串行时,执行下一个任务 diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteResourceService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteResourceService.java index ae6df71546..6964e17c53 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteResourceService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteResourceService.java @@ -1,16 +1,11 @@ package io.metersphere.api.service; import io.metersphere.api.invoker.ApiExecuteCallbackServiceInvoker; -import io.metersphere.api.service.definition.ApiReportService; -import io.metersphere.api.service.scenario.ApiScenarioReportService; import io.metersphere.api.utils.TaskRunningCache; -import io.metersphere.sdk.constants.ApiExecuteResourceType; import io.metersphere.sdk.constants.ApiExecuteRunMode; import io.metersphere.sdk.dto.api.task.GetRunScriptRequest; import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.api.task.TaskItem; -import io.metersphere.sdk.exception.MSException; -import io.metersphere.sdk.util.EnumValidator; import io.metersphere.sdk.util.LogUtils; import jakarta.annotation.Resource; import org.apache.commons.lang3.BooleanUtils; @@ -24,11 +19,6 @@ import java.util.Optional; @Service @Transactional(rollbackFor = Exception.class) public class ApiExecuteResourceService { - - @Resource - private ApiReportService apiReportService; - @Resource - private ApiScenarioReportService apiScenarioReportService; @Resource private StringRedisTemplate stringRedisTemplate; @Resource @@ -64,12 +54,8 @@ public class ApiExecuteResourceService { } private void updateRunningReportStatus(GetRunScriptRequest request) { - TaskItem taskItem = request.getTaskItem(); String taskId = request.getTaskId(); - String reportId = taskItem.getReportId(); - ApiExecuteResourceType apiExecuteResourceType = EnumValidator.validateEnum(ApiExecuteResourceType.class, request.getResourceType()); - // 重跑不更新任务状态 if (BooleanUtils.isFalse(request.getRerun())) { if (request.getBatch()) { @@ -86,14 +72,5 @@ public class ApiExecuteResourceService { // 更新任务项状态 apiCommonService.updateTaskItemRunningStatus(request); - - // 非调试执行,更新报告状态 - switch (apiExecuteResourceType) { - case API_SCENARIO, TEST_PLAN_API_SCENARIO, PLAN_RUN_API_SCENARIO -> - apiScenarioReportService.updateReportRunningStatus(reportId); - case API_CASE, TEST_PLAN_API_CASE, PLAN_RUN_API_CASE -> - apiReportService.updateReportRunningStatus(reportId); - default -> throw new MSException("不支持的资源类型: " + request.getResourceType()); - } } } \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java index 0e09cdfa22..53519772f3 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java @@ -40,6 +40,7 @@ import io.metersphere.system.dto.pool.TestResourceNodeDTO; import io.metersphere.system.dto.pool.TestResourcePoolReturnDTO; import io.metersphere.system.mapper.ExecTaskItemMapper; import io.metersphere.system.service.*; +import io.metersphere.system.uid.IDGenerator; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; @@ -214,6 +215,7 @@ public class ApiExecuteService { */ public TaskRequestDTO execute(TaskRequestDTO taskRequest) { TaskInfo taskInfo = taskRequest.getTaskInfo(); + TaskItem taskItem = taskRequest.getTaskItem(); try { taskInfo = setTaskRequestParams(taskInfo); @@ -226,6 +228,11 @@ public class ApiExecuteService { } taskInfo.setPoolId(testResourcePoolDTO.getId()); + if (StringUtils.isBlank(taskItem.getReportId())) { + // 预先生成报告ID,避免资源池获取执行脚本时,超时重试,导致数据重复创建 + taskItem.setReportId(IDGenerator.nextStr()); + } + // 判断是否为 K8S 资源池 boolean isK8SResourcePool = StringUtils.equals(testResourcePoolDTO.getType(), ResourcePoolTypeEnum.K8S.getName()); if (isK8SResourcePool) { @@ -360,6 +367,12 @@ public class ApiExecuteService { taskInfo.setMsUrl(testResourcePool.getServerUrl()); } taskInfo.setPoolId(testResourcePool.getId()); + taskRequest.getTaskItems().forEach(taskItem -> { + if (StringUtils.isBlank(taskItem.getReportId())) { + // 预先生成报告ID,避免资源池获取执行脚本时,超时重试,导致数据重复创建 + taskItem.setReportId(IDGenerator.nextStr()); + } + }); // 判断是否为 K8S 资源池 boolean isK8SResourcePool = StringUtils.equals(testResourcePool.getType(), ResourcePoolTypeEnum.K8S.getName()); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioExecuteCallbackService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioExecuteCallbackService.java index 96d5f3dc86..71f2910eae 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioExecuteCallbackService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioExecuteCallbackService.java @@ -12,9 +12,10 @@ import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.api.task.TaskItem; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; +import io.metersphere.sdk.util.LogUtils; import jakarta.annotation.Resource; import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.StringUtils; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -46,34 +47,33 @@ public class ApiScenarioExecuteCallbackService implements ApiExecuteCallbackServ return result; } - @Override - public String initReport(GetRunScriptRequest request) { - String reportId = request.getTaskItem().getReportId(); - // reportId 不为空,则已经预生成了报告 - if (StringUtils.isBlank(reportId)) { - ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRun(request.getTaskItem().getResourceId()); - return initReport(request, apiScenarioDetail); - } - return reportId; - } - private String initReport(GetRunScriptRequest request, ApiScenarioDetail apiScenarioDetail) { TaskItem taskItem = request.getTaskItem(); String reportId = taskItem.getReportId(); - if (BooleanUtils.isTrue(request.getBatch())) { - if (request.getRunModeConfig().isIntegratedReport()) { - // 集合报告,生成一级步骤的子步骤 - apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getId(), apiScenarioDetail.getSteps(), reportId); - } else { - // 批量执行,生成独立报告 - reportId = apiScenarioBatchRunService.initScenarioReport(taskItem.getId(), request.getRunModeConfig(), apiScenarioDetail, request.getUserId()); + try { + if (BooleanUtils.isTrue(request.getBatch())) { + if (request.getRunModeConfig().isIntegratedReport()) { + // 集合报告,生成一级步骤的子步骤 + apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getId(), apiScenarioDetail.getSteps(), reportId); + } else { + // 批量执行,生成独立报告 + reportId = apiScenarioBatchRunService.initScenarioReport(taskItem.getId(), reportId, request.getRunModeConfig(), apiScenarioDetail, request.getUserId()); + // 初始化报告步骤 + apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId); + } + } else if (!ApiExecuteRunMode.isDebug(request.getRunMode())) { + reportId = apiScenarioRunService.initApiScenarioReport(taskItem.getId(), apiScenarioDetail, request); // 初始化报告步骤 apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId); } - } else if (!ApiExecuteRunMode.isDebug(request.getRunMode())) { - reportId = apiScenarioRunService.initApiScenarioReport(taskItem.getId(), apiScenarioDetail, request); - // 初始化报告步骤 - apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId); + } catch (DuplicateKeyException e) { + if (!request.getRunModeConfig().isIntegratedReport()) { + // 步骤中的 stepId 是执行时时随机生成的,如果重试,需要删除原有的步骤,重新生成,跟执行脚本匹配 + apiScenarioRunService.deleteStepsByReportId(reportId); + apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId); + } + // 避免重试,报告ID重复,导致执行失败 + LogUtils.error(e); } return reportId; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseBatchRunService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseBatchRunService.java index dabb58642a..016fdf63a5 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseBatchRunService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseBatchRunService.java @@ -24,6 +24,7 @@ import jakarta.annotation.Resource; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; @@ -427,10 +428,12 @@ public class ApiTestCaseBatchRunService { * @param apiTestCase * @return */ - public String initApiReport(String taskItemId, ApiRunModeConfigDTO runModeConfig, + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + public String initApiReport(String taskItemId, String reportId, ApiRunModeConfigDTO runModeConfig, ApiTestCase apiTestCase, String userId) { // 初始化报告 ApiReport apiReport = getApiReport(runModeConfig, apiTestCase, userId); + apiReport.setId(reportId); apiReportService.insertApiReport(apiReport); return apiTestCaseRunService.initApiReportDetail(taskItemId, apiTestCase, apiReport.getId()); } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseRunService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseRunService.java index e884befc65..0552138fcc 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseRunService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseRunService.java @@ -24,6 +24,7 @@ import io.metersphere.system.uid.IDGenerator; import jakarta.annotation.Resource; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.UUID; @@ -252,6 +253,7 @@ public class ApiTestCaseRunService { * @param apiTestCase * @return */ + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public String initApiReport(String taskItemId, ApiTestCase apiTestCase, GetRunScriptRequest request) { // 初始化报告 ApiReport apiReport = getApiReport(apiTestCase, request); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioBatchRunService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioBatchRunService.java index ea54fa8d63..1eaa14f94a 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioBatchRunService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioBatchRunService.java @@ -25,12 +25,12 @@ import io.metersphere.system.domain.ExecTask; import io.metersphere.system.domain.ExecTaskItem; import io.metersphere.system.mapper.ExtExecTaskItemMapper; import io.metersphere.system.service.BaseTaskHubService; -import io.metersphere.system.uid.IDGenerator; import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; @@ -374,11 +374,12 @@ public class ApiScenarioBatchRunService { return apiBatchRunBaseService.setBatchRunTaskInfoParam(runModeConfig, taskInfo); } - public String initScenarioReport(String taskItemId, ApiRunModeConfigDTO runModeConfig, + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + public String initScenarioReport(String taskItemId, String reportId, ApiRunModeConfigDTO runModeConfig, ApiScenario apiScenario, String userId) { // 初始化报告 ApiScenarioReport apiScenarioReport = getScenarioReport(runModeConfig, apiScenario, userId); - apiScenarioReport.setId(IDGenerator.nextStr()); + apiScenarioReport.setId(reportId); apiScenarioReportService.insertApiScenarioReport(apiScenarioReport); return apiScenarioRunService.initApiScenarioReportDetail(taskItemId, apiScenario.getId(), apiScenarioReport.getId()); } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioReportService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioReportService.java index 9569a1b91f..ce9ee804ee 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioReportService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioReportService.java @@ -414,33 +414,6 @@ public class ApiScenarioReportService { return apiReportDetails; } - /** - * 更新进行中的报告状态和开始执行时间 - * - * @param reportId - */ - public void updateReportRunningStatus(String reportId) { - ApiScenarioReport scenarioReport = new ApiScenarioReport(); - scenarioReport.setId(reportId); - scenarioReport.setExecStatus(ExecStatus.RUNNING.name()); - scenarioReport.setStartTime(System.currentTimeMillis()); - scenarioReport.setUpdateTime(System.currentTimeMillis()); - apiScenarioReportMapper.updateByPrimaryKeySelective(scenarioReport); - } - - /** - * 更新执行中的场景报告 - * - * @param reportId - */ - public void updateReportStatus(String reportId, String status) { - ApiScenarioReport scenarioReport = new ApiScenarioReport(); - scenarioReport.setId(reportId); - scenarioReport.setExecStatus(status); - scenarioReport.setUpdateTime(System.currentTimeMillis()); - apiScenarioReportMapper.updateByPrimaryKeySelective(scenarioReport); - } - public List getApiScenarioReportByIds(List reportIds) { if (CollectionUtils.isEmpty(reportIds)) { return List.of(); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java index 2be707e503..e6432756b4 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java @@ -14,6 +14,7 @@ import io.metersphere.api.dto.scenario.*; import io.metersphere.api.mapper.ApiScenarioBlobMapper; import io.metersphere.api.mapper.ApiScenarioMapper; import io.metersphere.api.mapper.ApiScenarioReportMapper; +import io.metersphere.api.mapper.ApiScenarioReportStepMapper; import io.metersphere.api.parser.step.StepParser; import io.metersphere.api.parser.step.StepParserFactory; import io.metersphere.api.service.ApiCommonService; @@ -50,6 +51,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.*; @@ -92,6 +94,8 @@ public class ApiScenarioRunService { private ProjectMapper projectMapper; @Resource private BaseTaskHubService baseTaskHubService; + @Resource + private ApiScenarioReportStepMapper apiScenarioReportStepMapper; public TaskRequestDTO run(String id, String reportId, String userId) { ApiScenarioDetail apiScenarioDetail = getForRun(id); @@ -234,21 +238,21 @@ public class ApiScenarioRunService { } else { // 如果传了报告ID,则实时获取结果 taskInfo.setRealTime(true); - - // 传了报告ID,则预生成报告 - ApiScenarioReport scenarioReport = getScenarioReport(apiScenario, userId); - scenarioReport.setId(reportId); - scenarioReport.setTriggerMode(TaskTriggerMode.MANUAL.name()); - scenarioReport.setRunMode(ApiBatchRunMode.PARALLEL.name()); - scenarioReport.setPoolId(poolId); - scenarioReport.setEnvironmentId(parseParam.getEnvironmentId()); - scenarioReport.setWaitingTime(getGlobalWaitTime(parseParam.getScenarioConfig())); - initApiScenarioReport(taskItem.getId(), apiScenario, scenarioReport); - - // 初始化报告步骤 - initScenarioReportSteps(steps, taskItem.getReportId()); } + // 传了报告ID,则预生成报告 + ApiScenarioReport scenarioReport = getScenarioReport(apiScenario, userId); + scenarioReport.setId(reportId); + scenarioReport.setTriggerMode(TaskTriggerMode.MANUAL.name()); + scenarioReport.setRunMode(ApiBatchRunMode.PARALLEL.name()); + scenarioReport.setPoolId(poolId); + scenarioReport.setEnvironmentId(parseParam.getEnvironmentId()); + scenarioReport.setWaitingTime(getGlobalWaitTime(parseParam.getScenarioConfig())); + initApiScenarioReport(taskItem.getId(), apiScenario, scenarioReport); + + // 初始化报告步骤 + initScenarioReportSteps(steps, taskItem.getReportId()); + ApiScenarioParamConfig parseConfig = getApiScenarioParamConfig(apiScenario.getProjectId(), parseParam, tmpParam.getScenarioParseEnvInfo()); parseConfig.setTaskItemId(taskItem.getId()); return apiExecuteService.execute(runRequest, taskRequest, parseConfig); @@ -464,6 +468,7 @@ public class ApiScenarioRunService { return waitTime; } + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public String initApiScenarioReport(String taskItemId, ApiScenario apiScenario, GetRunScriptRequest request) { // 初始化报告 ApiScenarioReport scenarioReport = getScenarioReport(apiScenario, request); @@ -590,6 +595,9 @@ public class ApiScenarioRunService { scenarioReport.setRunMode(request.getRunMode()); scenarioReport.setTriggerMode(request.getTriggerMode()); scenarioReport.setPoolId(request.getPoolId()); + if (StringUtils.isNotBlank(request.getTaskItem().getReportId())) { + scenarioReport.setId(request.getTaskItem().getReportId()); + } return scenarioReport; } @@ -1116,4 +1124,10 @@ public class ApiScenarioRunService { apiExecuteService.execute(taskRequest); } + + public void deleteStepsByReportId(String reportId) { + ApiScenarioReportStepExample example = new ApiScenarioReportStepExample(); + example.createCriteria().andReportIdEqualTo(reportId); + apiScenarioReportStepMapper.deleteByExample(example); + } } diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/BaseTaskHubService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/BaseTaskHubService.java index 50dad4b94d..23e9a17513 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/BaseTaskHubService.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/BaseTaskHubService.java @@ -57,10 +57,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -213,7 +210,8 @@ public class BaseTaskHubService { ApiReportRelateTaskExample example = new ApiReportRelateTaskExample(); example.createCriteria().andTaskResourceIdIn(resourceIds); List reportRelateTasks = apiReportRelateTaskMapper.selectByExample(example); - Map reportMap = reportRelateTasks.stream().collect(Collectors.toMap(ApiReportRelateTask::getTaskResourceId, ApiReportRelateTask::getReportId)); + Map reportMap = new HashMap<>(); + reportRelateTasks.forEach(item -> reportMap.put(item.getTaskResourceId(), item.getReportId())); reportTasks.forEach(task -> { if (integratedTaskIds.contains(task.getId())) { task.setReportId(reportMap.get(task.getId())); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiCaseExecuteCallbackService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiCaseExecuteCallbackService.java index dceddc3ad1..62c21c4dc9 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiCaseExecuteCallbackService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiCaseExecuteCallbackService.java @@ -17,7 +17,9 @@ import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.LogUtils; import jakarta.annotation.Resource; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -67,15 +69,14 @@ public class PlanRunApiCaseExecuteCallbackService implements ApiExecuteCallbackS return result; } - @Override - public String initReport(GetRunScriptRequest request) { - TestPlanReportApiCase testPlanReportApiCase = testPlanReportApiCaseMapper.selectByPrimaryKey(request.getTaskItem().getResourceId()); - ApiTestCase apiTestCase = apiTestCaseMapper.selectByPrimaryKey(testPlanReportApiCase.getApiCaseId()); - return planRunTestPlanApiCaseService.initApiReport(request, testPlanReportApiCase, apiTestCase); - } - public String initReport(GetRunScriptRequest request, TestPlanReportApiCase testPlanReportApiCase, ApiTestCase apiTestCase) { - return planRunTestPlanApiCaseService.initApiReport(request, testPlanReportApiCase, apiTestCase); + try { + return planRunTestPlanApiCaseService.initApiReport(request, testPlanReportApiCase, apiTestCase); + } catch (DuplicateKeyException e) { + // 避免重试,报告ID重复,导致执行失败 + LogUtils.error(e); + } + return request.getTaskItem().getReportId(); } /** diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiScenarioExecuteCallbackService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiScenarioExecuteCallbackService.java index ae84d7f2d3..6e6ca403b4 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiScenarioExecuteCallbackService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiScenarioExecuteCallbackService.java @@ -16,7 +16,9 @@ import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.LogUtils; import jakarta.annotation.Resource; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -62,23 +64,25 @@ public class PlanRunApiScenarioExecuteCallbackService implements ApiExecuteCallb return result; } - @Override - public String initReport(GetRunScriptRequest request) { - String resourceId = request.getTaskItem().getResourceId(); - TestPlanReportApiScenario testPlanReportApiScenario = testPlanReportApiScenarioMapper.selectByPrimaryKey(resourceId); - ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRun(testPlanReportApiScenario.getApiScenarioId()); - return initReport(request, testPlanReportApiScenario, apiScenarioDetail); - } - public String initReport(GetRunScriptRequest request, TestPlanReportApiScenario testPlanReportApiScenario, ApiScenarioDetail apiScenarioDetail) { - String reportId = planRunTestPlanApiScenarioService.initReport(request, testPlanReportApiScenario, apiScenarioDetail); - // 初始化报告步骤 - apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId); - return reportId; + String reportId = request.getTaskItem().getReportId(); + try { + planRunTestPlanApiScenarioService.initReport(request, testPlanReportApiScenario, apiScenarioDetail); + // 初始化报告步骤 + apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId); + } catch (DuplicateKeyException e) { + // 避免重试,报告ID重复,导致执行失败 + // 步骤中的 stepId 是执行时时随机生成的,如果重试,需要删除原有的步骤,重新生成,跟执行脚本匹配 + apiScenarioRunService.deleteStepsByReportId(reportId); + apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId); + LogUtils.error(e); + } + return request.getTaskItem().getReportId(); } /** * 串行时,执行下一个任务 + * * @param queue * @param queueDetail */ @@ -90,6 +94,7 @@ public class PlanRunApiScenarioExecuteCallbackService implements ApiExecuteCallb /** * 批量串行的测试集执行时 * 测试集下用例执行完成时回调 + * * @param apiNoticeDTO */ @Override diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunTestPlanApiCaseService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunTestPlanApiCaseService.java index f779c857ee..29a0a939dd 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunTestPlanApiCaseService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunTestPlanApiCaseService.java @@ -27,6 +27,7 @@ import io.metersphere.system.domain.ExecTaskItem; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -182,6 +183,7 @@ public class PlanRunTestPlanApiCaseService { * * @return */ + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public String initApiReport(GetRunScriptRequest request, TestPlanReportApiCase testPlanReportApiCase, ApiTestCase apiTestCase) { // 初始化报告 ApiReport apiReport = apiTestCaseRunService.getApiReport(apiTestCase, request); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunTestPlanApiScenarioService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunTestPlanApiScenarioService.java index f899870801..86e78cc6ed 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunTestPlanApiScenarioService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunTestPlanApiScenarioService.java @@ -28,6 +28,7 @@ import io.metersphere.system.domain.ExecTaskItem; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -138,6 +139,7 @@ public class PlanRunTestPlanApiScenarioService { return false; } + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public String initReport(GetRunScriptRequest request, TestPlanReportApiScenario testPlanReportApiScenario, ApiScenario apiScenario) { diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseExecuteCallbackService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseExecuteCallbackService.java index d8355b0e5b..2950e27598 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseExecuteCallbackService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseExecuteCallbackService.java @@ -18,8 +18,10 @@ import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.LogUtils; import jakarta.annotation.Resource; import org.apache.commons.lang3.StringUtils; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -64,20 +66,19 @@ public class TestPlanApiCaseExecuteCallbackService implements ApiExecuteCallback return result; } - @Override - public String initReport(GetRunScriptRequest request) { - TestPlanApiCase testPlanApiCase = testPlanApiCaseMapper.selectByPrimaryKey(request.getTaskItem().getResourceId()); - ApiTestCase apiTestCase = apiTestCaseMapper.selectByPrimaryKey(testPlanApiCase.getApiCaseId()); - return initReport(request, testPlanApiCase, apiTestCase); - } - public String initReport(GetRunScriptRequest request, TestPlanApiCase testPlanApiCase, ApiTestCase apiTestCase) { - ApiTestCaseRecord apiTestCaseRecord = testPlanApiCaseService.initApiReport(apiTestCase, testPlanApiCase, request); - return apiTestCaseRecord.getApiReportId(); + try { + return testPlanApiCaseService.initApiReport(apiTestCase, testPlanApiCase, request); + } catch (DuplicateKeyException e) { + // 避免重试,报告ID重复,导致执行失败 + LogUtils.error(e); + } + return request.getTaskItem().getReportId(); } /** * 串行时,执行下一个任务 + * * @param queue * @param queueDetail */ @@ -95,7 +96,7 @@ public class TestPlanApiCaseExecuteCallbackService implements ApiExecuteCallback if (StringUtils.isNotBlank(apiNoticeDTO.getParentQueueId())) { testPlanApiCaseBatchRunService.executeNextCollection(apiNoticeDTO.getParentQueueId(), apiNoticeDTO.getRerun()); } else if (StringUtils.isNotBlank(apiNoticeDTO.getParentSetId())) { - String queueIdOrSetId = StringUtils.isBlank(apiNoticeDTO.getQueueId()) ? apiNoticeDTO.getSetId() : apiNoticeDTO.getQueueId(); + String queueIdOrSetId = StringUtils.isBlank(apiNoticeDTO.getQueueId()) ? apiNoticeDTO.getSetId() : apiNoticeDTO.getQueueId(); String[] setIdSplit = queueIdOrSetId.split("_"); String collectionId = setIdSplit[setIdSplit.length - 1]; testPlanApiCaseBatchRunService.finishParallelCollection(apiNoticeDTO.getParentSetId(), collectionId); @@ -104,6 +105,7 @@ public class TestPlanApiCaseExecuteCallbackService implements ApiExecuteCallback /** * 失败停止时,删除测试集合队列 + * * @param parentQueueId */ @Override diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseService.java index c4ff5728b3..5f592f2fb4 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseService.java @@ -63,6 +63,7 @@ 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.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -831,7 +832,8 @@ public class TestPlanApiCaseService extends TestPlanResourceService { * * @return */ - public ApiTestCaseRecord initApiReport(ApiTestCase apiTestCase, TestPlanApiCase testPlanApiCase, GetRunScriptRequest request) { + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + public String initApiReport(ApiTestCase apiTestCase, TestPlanApiCase testPlanApiCase, GetRunScriptRequest request) { // 初始化报告 ApiRunModeConfigDTO runModeConfig = request.getRunModeConfig(); ApiReport apiReport = apiTestCaseRunService.getApiReport(apiTestCase, request); @@ -847,7 +849,7 @@ public class TestPlanApiCaseService extends TestPlanResourceService { ApiReportStep apiReportStep = getApiReportStep(testPlanApiCase, apiTestCase, apiReport.getId()); apiReportService.insertApiReportDetail(apiReportStep, apiTestCaseRecord, apiReportRelateTask); - return apiTestCaseRecord; + return apiTestCaseRecord.getApiReportId(); } public ApiReportStep getApiReportStep(TestPlanApiCase testPlanApiCase, ApiTestCase apiTestCase, String reportId) { diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioExecuteCallbackService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioExecuteCallbackService.java index 0270ce7f5c..c4280b67e2 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioExecuteCallbackService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioExecuteCallbackService.java @@ -17,8 +17,10 @@ import io.metersphere.sdk.dto.api.task.TaskItem; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.LogUtils; import jakarta.annotation.Resource; import org.apache.commons.lang3.StringUtils; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -64,20 +66,21 @@ public class TestPlanApiScenarioExecuteCallbackService implements ApiExecuteCall return result; } - @Override - public String initReport(GetRunScriptRequest request) { - TaskItem taskItem = request.getTaskItem(); - TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(taskItem.getResourceId()); - ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRun(testPlanApiScenario.getApiScenarioId()); - return initReport(request, testPlanApiScenario, apiScenarioDetail); - } - public String initReport(GetRunScriptRequest request, TestPlanApiScenario testPlanApiScenario, ApiScenarioDetail apiScenarioDetail) { - // 批量执行,生成独立报告 - String reportId = testPlanApiScenarioService.initApiScenarioReport(testPlanApiScenario, apiScenarioDetail, request); - // 初始化报告步骤 - apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId); - return reportId; + String reportId = request.getTaskItem().getReportId(); + try { + // 批量执行,生成独立报告 + testPlanApiScenarioService.initApiScenarioReport(testPlanApiScenario, apiScenarioDetail, request); + // 初始化报告步骤 + apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId); + } catch (DuplicateKeyException e) { + // 避免重试,报告ID重复,导致执行失败 + // 步骤中的 stepId 是执行时时随机生成的,如果重试,需要删除原有的步骤,重新生成,跟执行脚本匹配 + apiScenarioRunService.deleteStepsByReportId(reportId); + apiScenarioRunService.initScenarioReportSteps(apiScenarioDetail.getSteps(), reportId); + LogUtils.error(e); + } + return request.getTaskItem().getReportId(); } /** diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java index 780daf8377..99606b96ab 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java @@ -60,6 +60,7 @@ 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.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -420,6 +421,7 @@ public class TestPlanApiScenarioService extends TestPlanResourceService { * * @return */ + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public String initApiScenarioReport(TestPlanApiScenario testPlanApiScenario, ApiScenario apiScenario, GetRunScriptRequest request) { // 初始化报告 ApiRunModeConfigDTO runModeConfig = request.getRunModeConfig();