diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioBatchOperationController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioBatchOperationController.java index eaca18a643..f515445acf 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioBatchOperationController.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioBatchOperationController.java @@ -2,11 +2,7 @@ package io.metersphere.api.controller.scenario; import io.metersphere.api.constants.ApiResource; import io.metersphere.api.dto.response.ApiScenarioBatchOperationResponse; -import io.metersphere.api.dto.scenario.ApiScenarioBatchCopyMoveRequest; -import io.metersphere.api.dto.scenario.ApiScenarioBatchEditRequest; -import io.metersphere.api.dto.scenario.ApiScenarioBatchRequest; -import io.metersphere.api.dto.scenario.ApiScenarioBatchRunRequest; -import io.metersphere.api.service.ApiBatchRunBaseService; +import io.metersphere.api.dto.scenario.*; import io.metersphere.api.service.ApiValidateService; import io.metersphere.api.service.scenario.ApiScenarioBatchRunService; import io.metersphere.api.service.scenario.ApiScenarioNoticeService; @@ -22,7 +18,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import org.apache.shiro.authz.annotation.RequiresPermissions; -import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -103,4 +98,13 @@ public class ApiScenarioBatchOperationController { apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name()); apiScenarioBatchRunService.asyncBatchRun(request, SessionUtils.getUserId()); } + + @PostMapping(value = "/batch-operation/schedule-config") + @Operation(summary = "接口测试-接口场景管理-定时任务批量配置") + @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE) + @CheckOwner(resourceId = "#request.getProject()", resourceType = "project") + public void scheduleConfig(@Validated @RequestBody ApiScenarioBatchScheduleConfigRequest request) { + apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name()); + apiScenarioService.batchScheduleConfig(request, SessionUtils.getUserId()); + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioBatchEditRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioBatchEditRequest.java index 7ccdd59253..c32e0d8e98 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioBatchEditRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioBatchEditRequest.java @@ -19,7 +19,7 @@ public class ApiScenarioBatchEditRequest extends ApiScenarioBatchRequest impleme @Schema(description = "标签") private LinkedHashSet tags; - @Schema(description = "批量编辑的类型 用例等级: Priority,状态 :Status,标签: Tags,用例环境: Environment") + @Schema(description = "批量编辑的类型 用例等级: Priority,状态 :Status,标签: Tags,用例环境: Environment, 定时任务:Schedule") @NotBlank private String type; @Schema(description = "默认覆盖原标签") @@ -40,7 +40,8 @@ public class ApiScenarioBatchEditRequest extends ApiScenarioBatchRequest impleme @Schema(description = "用例等级") @Size(max = 50, message = "{api_test_case.priority.length_range}") private String priority; - + @Schema(description = "定时任务是否开启") + private boolean scheduleOpen; public List getTags() { if (tags == null) { return new ArrayList<>(0); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioBatchScheduleConfigRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioBatchScheduleConfigRequest.java new file mode 100644 index 0000000000..c36e3fb3db --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioBatchScheduleConfigRequest.java @@ -0,0 +1,26 @@ +package io.metersphere.api.dto.scenario; + +import io.metersphere.sdk.dto.api.task.ApiRunModeConfigDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +@Data +@EqualsAndHashCode(callSuper = false) +public class ApiScenarioBatchScheduleConfigRequest extends ApiScenarioBatchRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "启用/禁用") + private boolean enable; + + @Schema(description = "Cron表达式") + @NotBlank + private String cron; + + @Schema(description = "定时任务配置") + private ApiRunModeConfigDTO config; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioLogService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioLogService.java index b571dc32bc..59ea63e60a 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioLogService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioLogService.java @@ -235,6 +235,27 @@ public class ApiScenarioLogService { operationLogService.batchAdd(logs); } + public void batchScheduleConfigLog(String projectId, List scenarioList, String operator) { + Project project = projectMapper.selectByPrimaryKey(projectId); + List logs = new ArrayList<>(); + scenarioList.forEach(apiScenario -> { + LogDTO dto = LogDTOBuilder.builder() + .projectId(project.getId()) + .organizationId(project.getOrganizationId()) + .type(OperationLogType.UPDATE.name()) + .module(OperationLogModule.API_SCENARIO_MANAGEMENT_SCENARIO) + .sourceId(apiScenario.getId()) + .method("POST") + .path("/api/scenario/batch-operation/schedule-config") + .createUser(operator) + .content(Translator.get("api_automation_schedule") + ":" + apiScenario.getName()) + .build().getLogDTO(); + logs.add(dto); + } + ); + operationLogService.batchAdd(logs); + } + public LogDTO exportExcelLog(String sourceId, String exportType, String userId, @NotNull Project project) { LogDTO dto = new LogDTO( project.getId(), 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 80d0092906..508a7133dc 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 @@ -173,6 +173,7 @@ public class ApiScenarioService extends MoveNodeService { public static final String STATUS = "Status"; public static final String TAGS = "Tags"; public static final String ENVIRONMENT = "Environment"; + public static final String SCHEDULE = "Schedule"; private static final String SCENARIO_TABLE = "api_scenario"; private static final String SCENARIO = "SCENARIO"; @@ -273,6 +274,7 @@ public class ApiScenarioService extends MoveNodeService { case STATUS -> batchUpdateStatus(example, updateScenario, request.getStatus(), mapper); case TAGS -> batchUpdateTags(example, updateScenario, request, ids, mapper); case ENVIRONMENT -> batchUpdateEnvironment(example, updateScenario, request, mapper); + case SCHEDULE -> batchUpdateSchedule(example, request, mapper, userId); default -> throw new MSException(Translator.get("batch_edit_type_error")); } sqlSession.flushStatements(); @@ -282,6 +284,15 @@ public class ApiScenarioService extends MoveNodeService { apiScenarioNoticeService.batchSendNotice(ids, userId, projectId, NoticeConstants.Event.UPDATE); } + private void batchUpdateSchedule(ApiScenarioExample example, ApiScenarioBatchEditRequest request, ApiScenarioMapper mapper, String userId) { + List apiScenarioList = mapper.selectByExample(example); + //批量编辑定时任务 + for (ApiScenario apiScenario : apiScenarioList) { + scheduleService.updateIfExist(apiScenario.getId(), request.isScheduleOpen(), ApiScenarioScheduleJob.getJobKey(apiScenario.getId()), + ApiScenarioScheduleJob.getTriggerKey(apiScenario.getId()), ApiScenarioScheduleJob.class, userId); + } + } + private void batchUpdateEnvironment(ApiScenarioExample example, ApiScenario updateScenario, ApiScenarioBatchEditRequest request, ApiScenarioMapper mapper) { if (BooleanUtils.isFalse(request.isGrouped())) { @@ -2576,4 +2587,36 @@ public class ApiScenarioService extends MoveNodeService { return response; } + + public void batchScheduleConfig(ApiScenarioBatchScheduleConfigRequest request, String operator) { + List scenarioIds = doSelectIds(request, false); + if (CollectionUtils.isNotEmpty(scenarioIds)) { + ApiScenarioExample example = new ApiScenarioExample(); + example.createCriteria().andIdIn(scenarioIds).andDeletedEqualTo(false); + List apiScenarios = apiScenarioMapper.selectByExample(example); + + if (CollectionUtils.isNotEmpty(apiScenarios)) { + apiScenarios.forEach(apiScenario -> { + ScheduleConfig scheduleConfig = ScheduleConfig.builder() + .resourceId(apiScenario.getId()) + .key(apiScenario.getId()) + .projectId(apiScenario.getProjectId()) + .name(apiScenario.getName()) + .enable(request.isEnable()) + .cron(request.getCron()) + .resourceType(ScheduleResourceType.API_SCENARIO.name()) + .config(JSON.toJSONString(request.getConfig())) + .build(); + + scheduleService.scheduleConfig( + scheduleConfig, + ApiScenarioScheduleJob.getJobKey(apiScenario.getId()), + ApiScenarioScheduleJob.getTriggerKey(apiScenario.getId()), + ApiScenarioScheduleJob.class, + operator); + }); + apiScenarioLogService.batchScheduleConfigLog(request.getProjectId(), apiScenarios, operator); + } + } + } } diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerTests.java index bc945b72fe..7f3c09fbfd 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerTests.java @@ -2102,6 +2102,7 @@ public class ApiScenarioControllerTests extends BaseTest { @Order(23) void scheduleTest() throws Exception { String testUrl = "/schedule-config"; + String batchUrl = "/batch-operation/schedule-config"; String deleteUrl = "/schedule-config-delete/"; if (CollectionUtils.isEmpty(BATCH_OPERATION_SCENARIO_ID)) { @@ -2112,14 +2113,18 @@ public class ApiScenarioControllerTests extends BaseTest { String scenarioId = BATCH_OPERATION_SCENARIO_ID.getLast(); deleteUrl += scenarioId; ApiScenarioScheduleConfigRequest request = new ApiScenarioScheduleConfigRequest(); - request.setScenarioId(scenarioId); request.setEnable(true); request.setCron("0 0 0 * * ?"); - + ApiScenarioBatchScheduleConfigRequest batchRequest = new ApiScenarioBatchScheduleConfigRequest(); + batchRequest.setEnable(false); + batchRequest.setCron("0 0 0 * * ?"); + batchRequest.setProjectId(DEFAULT_PROJECT_ID); + batchRequest.setSelectIds(List.of(BATCH_OPERATION_SCENARIO_ID.getFirst())); //先测试一下没有开启模块时接口能否使用 apiScenarioBatchOperationTestService.removeApiModule(DEFAULT_PROJECT_ID); this.requestPost(testUrl, request).andExpect(status().is5xxServerError()); + this.requestPost(batchUrl, batchRequest).andExpect(status().is5xxServerError()); this.requestGet(deleteUrl, request).andExpect(status().is5xxServerError()); //恢复 apiScenarioBatchOperationTestService.resetProjectModule(DEFAULT_PROJECT_ID); @@ -2128,11 +2133,29 @@ public class ApiScenarioControllerTests extends BaseTest { String scheduleId = resultHolder.getData().toString(); apiScenarioBatchOperationTestService.checkSchedule(scheduleId, scenarioId, request.isEnable()); + this.requestPostWithOk(batchUrl, batchRequest); + apiScenarioBatchOperationTestService.checkSchedule(BATCH_OPERATION_SCENARIO_ID.getFirst(), batchRequest.isEnable()); + batchRequest.setEnable(false); + this.requestPostWithOk(batchUrl, batchRequest); + apiScenarioBatchOperationTestService.checkSchedule(BATCH_OPERATION_SCENARIO_ID.getFirst(), batchRequest.isEnable()); //增加日志检查 LOG_CHECK_LIST.add( new CheckLogModel(scenarioId, OperationLogType.UPDATE, "/api/scenario/schedule-config") ); - + LOG_CHECK_LIST.add( + new CheckLogModel(BATCH_OPERATION_SCENARIO_ID.getFirst(), OperationLogType.UPDATE, "/api/scenario/batch-operation/schedule-config") + ); + // 批量定时任务的开关 + ApiScenarioBatchEditRequest batchEditRequest = new ApiScenarioBatchEditRequest(); + batchEditRequest.setProjectId(DEFAULT_PROJECT_ID); + batchEditRequest.setType("Schedule"); + batchEditRequest.setScheduleOpen(true); + batchEditRequest.setSelectIds(List.of(BATCH_OPERATION_SCENARIO_ID.getFirst())); + requestPostAndReturn(BATCH_EDIT, batchEditRequest); + apiScenarioBatchOperationTestService.checkSchedule(BATCH_OPERATION_SCENARIO_ID.getFirst(), batchEditRequest.isScheduleOpen()); + batchEditRequest.setScheduleOpen(false); + requestPostAndReturn(BATCH_EDIT, batchEditRequest); + apiScenarioBatchOperationTestService.checkSchedule(BATCH_OPERATION_SCENARIO_ID.getFirst(), batchEditRequest.isScheduleOpen()); //关闭 request.setEnable(false); result = this.requestPostAndReturn(testUrl, request); @@ -2203,6 +2226,7 @@ public class ApiScenarioControllerTests extends BaseTest { //校验权限 this.requestPostPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE, testUrl, request); + //反例:scenarioId不存在 request = new ApiScenarioScheduleConfigRequest(); request.setCron("0 0 0 * * ?"); diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/service/ApiScenarioBatchOperationTestService.java b/backend/services/api-test/src/test/java/io/metersphere/api/service/ApiScenarioBatchOperationTestService.java index e47d9ade04..f16250eb74 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/service/ApiScenarioBatchOperationTestService.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/service/ApiScenarioBatchOperationTestService.java @@ -209,6 +209,11 @@ public class ApiScenarioBatchOperationTestService { Assertions.assertEquals(scheduler.checkExists(ApiScenarioScheduleJob.getJobKey(resourceId)), isEnable); } + public void checkSchedule(String resourceId, boolean isEnable) throws Exception { + Assertions.assertEquals(extScheduleMapper.countByResourceId(resourceId), 1L); + Assertions.assertEquals(scheduler.checkExists(ApiScenarioScheduleJob.getJobKey(resourceId)), isEnable); + } + public void checkScheduleIsRemove(String resourceId) throws Exception { Assertions.assertEquals(extScheduleMapper.countByResourceId(resourceId), 0L); Assertions.assertEquals(scheduler.checkExists(ApiScenarioScheduleJob.getJobKey(resourceId)), false); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java index 5dd3fe8a4f..69e97e38e8 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java @@ -233,7 +233,7 @@ public class TestPlanController { } @PostMapping(value = "/batch-schedule-config") - @Operation(summary = "接口测试-接口场景管理-定时任务配置") + @Operation(summary = "接口测试-接口场景管理-定时任务批量配置") @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_EXECUTE) @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") public void batchScheduleConfig(@Validated @RequestBody TestPlanScheduleBatchConfigRequest request) {