From d315868c0ca597f2df3ad25bee4f7943e4111f48 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 8 May 2023 14:00:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):?= =?UTF-8?q?=20=E4=BC=98=E5=8C=96=E5=90=8C=E4=B8=80=E4=B8=AA=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E8=AE=A1=E5=88=92=E5=B9=B6=E5=8F=91=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=81=B6=E5=8F=91Block=20Waiting=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: fit2-zhao --- .../api/exec/api/ApiCaseExecuteService.java | 7 ++- .../scenario/ApiScenarioExecuteService.java | 5 ++ .../metersphere/commons/enums/LockEnum.java | 5 ++ .../service/RedisTemplateService.java | 37 ++++++++++++ .../service/RemakeReportService.java | 20 +++++-- .../service/TestResultService.java | 4 +- .../ApiDefinitionExecResultService.java | 56 ++++++++++++------- .../scenario/ApiScenarioReportService.java | 15 +++-- 8 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 api-test/backend/src/main/java/io/metersphere/commons/enums/LockEnum.java diff --git a/api-test/backend/src/main/java/io/metersphere/api/exec/api/ApiCaseExecuteService.java b/api-test/backend/src/main/java/io/metersphere/api/exec/api/ApiCaseExecuteService.java index 5afda8e973..a245059e40 100644 --- a/api-test/backend/src/main/java/io/metersphere/api/exec/api/ApiCaseExecuteService.java +++ b/api-test/backend/src/main/java/io/metersphere/api/exec/api/ApiCaseExecuteService.java @@ -26,6 +26,7 @@ import io.metersphere.dto.RunModeConfigDTO; import io.metersphere.environment.service.BaseEnvGroupProjectService; import io.metersphere.environment.service.BaseEnvironmentService; import io.metersphere.service.ApiExecutionQueueService; +import io.metersphere.service.RedisTemplateService; import io.metersphere.service.ServiceUtils; import io.metersphere.service.definition.ApiCaseResultService; import io.metersphere.service.scenario.ApiScenarioReportStructureService; @@ -68,7 +69,8 @@ public class ApiCaseExecuteService { private BaseEnvironmentService baseEnvironmentService; @Resource private ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper; - + @Resource + private RedisTemplateService redisTemplateService; /** * 测试计划case执行 * @@ -131,6 +133,9 @@ public class ApiCaseExecuteService { ApiDefinitionExecResultWithBLOBs report = ApiDefinitionExecResultUtil.addResult(request, runModeConfigDTO, testPlanApiCase, status, testCase, resourcePoolId); executeQueue.put(testPlanApiCase.getId(), report); responseDTOS.add(new MsExecResponseDTO(testPlanApiCase.getId(), report.getId(), request.getTriggerMode())); + // 执行中资源锁住,防止重复更新造成LOCK WAIT + redisTemplateService.lock(testPlanApiCase.getId()); + LoggerUtil.info("预生成测试用例结果报告:" + report.getName(), report.getId()); } apiCaseResultService.batchSave(executeQueue); diff --git a/api-test/backend/src/main/java/io/metersphere/api/exec/scenario/ApiScenarioExecuteService.java b/api-test/backend/src/main/java/io/metersphere/api/exec/scenario/ApiScenarioExecuteService.java index f4cb9b52ec..89eacd118e 100644 --- a/api-test/backend/src/main/java/io/metersphere/api/exec/scenario/ApiScenarioExecuteService.java +++ b/api-test/backend/src/main/java/io/metersphere/api/exec/scenario/ApiScenarioExecuteService.java @@ -35,6 +35,7 @@ import io.metersphere.environment.service.BaseEnvGroupProjectService; import io.metersphere.i18n.Translator; import io.metersphere.plugin.core.MsTestElement; import io.metersphere.service.ApiExecutionQueueService; +import io.metersphere.service.RedisTemplateService; import io.metersphere.service.ServiceUtils; import io.metersphere.service.SystemParameterService; import io.metersphere.service.definition.TcpApiParamService; @@ -91,6 +92,8 @@ public class ApiScenarioExecuteService { private ExtTestPlanScenarioCaseMapper extTestPlanScenarioCaseMapper; @Resource private SystemParameterService systemParameterService; + @Resource + private RedisTemplateService redisTemplateService; public List run(RunScenarioRequest request) { if (LoggerUtil.getLogger().isDebugEnabled()) { @@ -343,6 +346,8 @@ public class ApiScenarioExecuteService { if (!StringUtils.equals(request.getConfig().getReportType(), RunModeConstants.SET_REPORT.toString())) { apiScenarioReportStructureService.save(scenario, report.getId(), request.getConfig() != null ? request.getConfig().getReportType() : null); } + // 执行中资源锁住,防止重复更新造成LOCK WAIT + redisTemplateService.lock(planApiScenario.getId()); // 重置报告ID reportId = UUID.randomUUID().toString(); } diff --git a/api-test/backend/src/main/java/io/metersphere/commons/enums/LockEnum.java b/api-test/backend/src/main/java/io/metersphere/commons/enums/LockEnum.java new file mode 100644 index 0000000000..14cf1bc822 --- /dev/null +++ b/api-test/backend/src/main/java/io/metersphere/commons/enums/LockEnum.java @@ -0,0 +1,5 @@ +package io.metersphere.commons.enums; + +public enum LockEnum { + LOCK, WAITING +} diff --git a/api-test/backend/src/main/java/io/metersphere/service/RedisTemplateService.java b/api-test/backend/src/main/java/io/metersphere/service/RedisTemplateService.java index 25ced0fa31..cc52facc8d 100644 --- a/api-test/backend/src/main/java/io/metersphere/service/RedisTemplateService.java +++ b/api-test/backend/src/main/java/io/metersphere/service/RedisTemplateService.java @@ -1,13 +1,21 @@ package io.metersphere.service; import io.metersphere.api.jmeter.utils.JmxFileUtil; +import io.metersphere.commons.enums.LockEnum; +import io.metersphere.commons.utils.LogUtil; import io.metersphere.utils.LoggerUtil; import jakarta.annotation.Resource; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import java.util.concurrent.TimeUnit; + @Service public class RedisTemplateService { + private static final String PRX = "TEST_PLAN_"; + public static final long TIME_OUT = 60; @Resource private RedisTemplate redisTemplate; @@ -46,4 +54,33 @@ public class RedisTemplateService { public void delFilePath(String reportId) { delete(JmxFileUtil.getExecuteFileKeyInRedis(reportId)); } + + /** + * 加锁 + */ + public boolean lock(String key) { + return redisTemplate.opsForValue().setIfAbsent(StringUtils.join(PRX, key), LockEnum.LOCK.name()); + } + + public boolean has(String key) { + try { + Object value = redisTemplate.opsForValue().get(StringUtils.join(PRX, key)); + if (ObjectUtils.isNotEmpty(value)) { + if (StringUtils.equals(LockEnum.LOCK.name(), String.valueOf(value))) { + // 设置一分钟超时 + redisTemplate.opsForValue().setIfPresent(StringUtils.join(PRX, key), + LockEnum.WAITING.name(), TIME_OUT, TimeUnit.SECONDS); + return false; + } + } else { + redisTemplate.opsForValue().setIfAbsent(StringUtils.join(PRX, key), + LockEnum.WAITING.name(), TIME_OUT, TimeUnit.SECONDS); + return false; + } + return true; + } catch (Exception e) { + LogUtil.error(e); + } + return false; + } } diff --git a/api-test/backend/src/main/java/io/metersphere/service/RemakeReportService.java b/api-test/backend/src/main/java/io/metersphere/service/RemakeReportService.java index 0e92fa155e..690bd89010 100644 --- a/api-test/backend/src/main/java/io/metersphere/service/RemakeReportService.java +++ b/api-test/backend/src/main/java/io/metersphere/service/RemakeReportService.java @@ -2,18 +2,25 @@ package io.metersphere.service; import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil; import io.metersphere.api.jmeter.ApiLocalRunner; -import io.metersphere.api.jmeter.utils.JmxFileUtil; import io.metersphere.commons.utils.BeanUtils; -import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.FixedCapacityUtil; import io.metersphere.dto.JmeterRunRequestDTO; import io.metersphere.dto.ResultDTO; import io.metersphere.utils.LoggerUtil; +import jakarta.annotation.Resource; import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @Service public class RemakeReportService { + @Resource + @Lazy + private ApiExecutionQueueService queueService; + @Resource + @Lazy + private TestResultService testResultService; + public void queueNext(JmeterRunRequestDTO request, String errorMsg) { try { ResultDTO dto = new ResultDTO(); @@ -25,11 +32,12 @@ public class RemakeReportService { PoolExecBlockingQueueUtil.offer(dto.getReportId()); LoggerUtil.error("执行异常处理:" + errorMsg, request.getReportId()); if (StringUtils.isNotEmpty(dto.getQueueId())) { - CommonBeanFactory.getBean(ApiExecutionQueueService.class).queueNext(dto); + queueService.queueNext(dto); } // 更新测试计划报告 - LoggerUtil.info("Check Processing Test Plan report status.queueId:" + dto.getQueueId() + ",runMode:" + dto.getRunMode() + ",testId:" + dto.getTestId(), dto.getReportId()); - CommonBeanFactory.getBean(ApiExecutionQueueService.class).checkTestPlanCaseTestEnd(dto.getTestId(), dto.getRunMode(), dto.getTestPlanReportId()); + LoggerUtil.info("Check Processing Test Plan report status.queueId:" + + dto.getQueueId() + ",runMode:" + dto.getRunMode() + ",testId:" + dto.getTestId(), dto.getReportId()); + queueService.checkTestPlanCaseTestEnd(dto.getTestId(), dto.getRunMode(), dto.getTestPlanReportId()); } catch (Exception e) { ApiLocalRunner.clearCache(request.getReportId()); LoggerUtil.error("回退报告异常", request.getReportId(), e); @@ -43,7 +51,7 @@ public class RemakeReportService { dto.setTestId(request.getTestId()); String consoleMsg = FixedCapacityUtil.getJmeterLogger(dto.getReportId(), true); dto.setConsole(consoleMsg + StringUtils.LF + errorMsg); - CommonBeanFactory.getBean(TestResultService.class).testEnded(dto); + testResultService.testEnded(dto); } public void testEnded(JmeterRunRequestDTO request, String errorMsg) { diff --git a/api-test/backend/src/main/java/io/metersphere/service/TestResultService.java b/api-test/backend/src/main/java/io/metersphere/service/TestResultService.java index 367e91aa7e..e39d0a4483 100644 --- a/api-test/backend/src/main/java/io/metersphere/service/TestResultService.java +++ b/api-test/backend/src/main/java/io/metersphere/service/TestResultService.java @@ -196,7 +196,9 @@ public class TestResultService { } if (StringUtils.equals(dto.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) { return apiScenarioReportService.updatePlanCase(dto); - } else if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) { + } else if (StringUtils.equalsAny(dto.getRunMode(), + ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), + ApiRunMode.JENKINS_SCENARIO_PLAN.name())) { return apiScenarioReportService.updateSchedulePlanCase(dto); } else { return this.editReport(dto); diff --git a/api-test/backend/src/main/java/io/metersphere/service/definition/ApiDefinitionExecResultService.java b/api-test/backend/src/main/java/io/metersphere/service/definition/ApiDefinitionExecResultService.java index 1738f6442c..28abd880fc 100644 --- a/api-test/backend/src/main/java/io/metersphere/service/definition/ApiDefinitionExecResultService.java +++ b/api-test/backend/src/main/java/io/metersphere/service/definition/ApiDefinitionExecResultService.java @@ -20,6 +20,7 @@ import io.metersphere.dto.RequestResult; import io.metersphere.dto.ResultDTO; import io.metersphere.notice.sender.NoticeModel; import io.metersphere.notice.service.NoticeSendService; +import io.metersphere.service.RedisTemplateService; import io.metersphere.service.ServiceUtils; import io.metersphere.utils.LoggerUtil; import jakarta.annotation.Resource; @@ -66,6 +67,8 @@ public class ApiDefinitionExecResultService { private ExtApiTestCaseMapper extApiTestCaseMapper; @Resource private ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper; + @Resource + private RedisTemplateService redisTemplateService; /** * API/CASE 重试结果保留一条 @@ -100,7 +103,7 @@ public class ApiDefinitionExecResultService { User user = getUser(dto, result); //如果是测试计划用例,更新接口用例的上次执行结果 TestPlanApiCase testPlanApiCase = testPlanApiCaseMapper.selectByPrimaryKey(dto.getTestId()); - if (testPlanApiCase != null) { + if (testPlanApiCase != null && !redisTemplateService.has(dto.getTestId())) { ApiTestCaseWithBLOBs apiTestCase = apiTestCaseMapper.selectByPrimaryKey(testPlanApiCase.getApiCaseId()); if (apiTestCase != null) { apiTestCase.setLastResultId(dto.getReportId()); @@ -219,21 +222,27 @@ public class ApiDefinitionExecResultService { } public void setExecResult(String id, String status, Long time) { - TestPlanApiCase apiCase = new TestPlanApiCase(); - apiCase.setId(id); - apiCase.setStatus(status); - apiCase.setUpdateTime(time); - testPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase); + if (!redisTemplateService.has(id)) { + TestPlanApiCase apiCase = new TestPlanApiCase(); + apiCase.setId(id); + apiCase.setStatus(status); + apiCase.setUpdateTime(time); + testPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase); + } } public void editStatus(ApiDefinitionExecResult saveResult, String type, String status, Long time, String reportId, String testId) { String name = testId; String version = StringUtils.EMPTY; String projectId = StringUtils.EMPTY; - if (StringUtils.equalsAnyIgnoreCase(type, ApiRunMode.API_PLAN.name(), ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name(), ApiRunMode.MANUAL_PLAN.name())) { + if (StringUtils.equalsAnyIgnoreCase(type, + ApiRunMode.API_PLAN.name(), + ApiRunMode.SCHEDULE_API_PLAN.name(), + ApiRunMode.JENKINS_API_PLAN.name(), + ApiRunMode.MANUAL_PLAN.name())) { TestPlanApiCase testPlanApiCase = testPlanApiCaseMapper.selectByPrimaryKey(testId); ApiTestCaseWithBLOBs caseWithBLOBs = null; - if (testPlanApiCase != null) { + if (testPlanApiCase != null && !redisTemplateService.has(testId)) { this.setExecResult(testId, status, time); caseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(testPlanApiCase.getApiCaseId()); testPlanApiCase.setStatus(status); @@ -255,7 +264,7 @@ public class ApiDefinitionExecResultService { projectId = apiDefinition.getProjectId(); } else { ApiTestCaseWithBLOBs caseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(testId); - if (caseWithBLOBs != null) { + if (caseWithBLOBs != null && !redisTemplateService.has(testId)) { // 更新用例最后执行结果 caseWithBLOBs.setLastResultId(reportId); caseWithBLOBs.setStatus(status); @@ -281,18 +290,23 @@ public class ApiDefinitionExecResultService { public void batchEditStatus(String type, String status, String reportId, String testId, TestPlanApiCaseMapper batchTestPlanApiCaseMapper, ApiTestCaseMapper batchApiTestCaseMapper) { - if (StringUtils.equalsAnyIgnoreCase(type, ApiRunMode.API_PLAN.name(), ApiRunMode.SCHEDULE_API_PLAN.name(), - ApiRunMode.JENKINS_API_PLAN.name(), ApiRunMode.MANUAL_PLAN.name())) { - TestPlanApiCase apiCase = new TestPlanApiCase(); - apiCase.setId(testId); - apiCase.setStatus(status); - apiCase.setUpdateTime(System.currentTimeMillis()); - batchTestPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase); + if (StringUtils.equalsAnyIgnoreCase(type, + ApiRunMode.API_PLAN.name(), + ApiRunMode.SCHEDULE_API_PLAN.name(), + ApiRunMode.JENKINS_API_PLAN.name(), + ApiRunMode.MANUAL_PLAN.name())) { + if (!redisTemplateService.has(testId)) { + TestPlanApiCase apiCase = new TestPlanApiCase(); + apiCase.setId(testId); + apiCase.setStatus(status); + apiCase.setUpdateTime(System.currentTimeMillis()); + batchTestPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase); - TestCaseReviewApiCase reviewApiCase = new TestCaseReviewApiCase(); - reviewApiCase.setId(testId); - reviewApiCase.setStatus(status); - reviewApiCase.setUpdateTime(System.currentTimeMillis()); + TestCaseReviewApiCase reviewApiCase = new TestCaseReviewApiCase(); + reviewApiCase.setId(testId); + reviewApiCase.setStatus(status); + reviewApiCase.setUpdateTime(System.currentTimeMillis()); + } } else { // 更新用例最后执行结果 ApiTestCaseWithBLOBs caseWithBLOBs = new ApiTestCaseWithBLOBs(); @@ -324,7 +338,7 @@ public class ApiDefinitionExecResultService { } if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name())) { TestPlanApiCase apiCase = testPlanApiCaseMapper.selectByPrimaryKey(dto.getTestId()); - if (apiCase != null) { + if (apiCase != null && !redisTemplateService.has(dto.getTestId())) { String projectId = extTestPlanApiCaseMapper.selectProjectId(apiCase.getId()); ApiDefinition apiDefinition = extApiTestCaseMapper.selectApiBasicInfoByCaseId(apiCase.getId()); String version = apiDefinition == null ? "" : apiDefinition.getVersionId(); diff --git a/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioReportService.java b/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioReportService.java index 7ab168434e..d7203efe48 100644 --- a/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioReportService.java +++ b/api-test/backend/src/main/java/io/metersphere/service/scenario/ApiScenarioReportService.java @@ -7,6 +7,7 @@ import io.metersphere.api.dto.automation.ExecuteType; import io.metersphere.api.dto.automation.RunScenarioRequest; import io.metersphere.api.dto.datacount.ApiDataCountResult; import io.metersphere.api.dto.definition.RunDefinitionRequest; +import io.metersphere.service.*; import io.metersphere.utils.ReportStatusUtil; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.*; @@ -30,10 +31,6 @@ import io.metersphere.log.vo.OperatingLogDetails; import io.metersphere.log.vo.api.ModuleReference; import io.metersphere.notice.sender.NoticeModel; import io.metersphere.notice.service.NoticeSendService; -import io.metersphere.service.BaseShareInfoService; -import io.metersphere.service.BaseUserService; -import io.metersphere.service.ServiceUtils; -import io.metersphere.service.SystemParameterService; import jakarta.annotation.Resource; import org.apache.commons.beanutils.BeanMap; import org.apache.commons.collections4.CollectionUtils; @@ -89,6 +86,8 @@ public class ApiScenarioReportService { private TestPlanApiScenarioMapper testPlanApiScenarioMapper; @Resource BaseShareInfoService baseShareInfoService; + @Resource + private RedisTemplateService redisTemplateService; public void saveResult(ResultDTO dto) { // 报告详情内容 @@ -305,6 +304,10 @@ public class ApiScenarioReportService { public ApiScenarioReport updatePlanCase(ResultDTO dto) { ResultVO resultVO = ReportStatusUtil.computedProcess(dto); ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), resultVO.getStatus(), dto.getRunMode()); + // 当前资源正在执行中 + if (redisTemplateService.has(dto.getTestId())) { + return report; + } TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(dto.getTestId()); if (testPlanApiScenario != null) { if (report != null) { @@ -341,6 +344,10 @@ public class ApiScenarioReportService { ResultVO resultVO = ReportStatusUtil.computedProcess(dto); ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), resultVO.getStatus(), dto.getRunMode()); + // 当前资源正在执行中 + if (redisTemplateService.has(dto.getTestId())) { + return report; + } if (report != null) { if (StringUtils.isNotEmpty(dto.getTestPlanReportId()) && !testPlanReportIdList.contains(dto.getTestPlanReportId())) { testPlanReportIdList.add(dto.getTestPlanReportId());