feat(接口测试): 接口场景的定时任务功能开发
This commit is contained in:
parent
0d85df85c7
commit
f546b29ade
|
@ -1,7 +1,9 @@
|
||||||
excel.parse.error=Excel解析失败
|
excel.parse.error=Excel解析失败
|
||||||
id.not_blank=ID不能为空
|
id.not_blank=ID不能为空
|
||||||
|
permission.system_user.invite=邀请用户
|
||||||
role.not.global.system=角色不是全局系统角色
|
role.not.global.system=角色不是全局系统角色
|
||||||
role.not.contains.member=角色不包含系统成员角色
|
role.not.contains.member=角色不包含系统成员角色
|
||||||
|
schedule.cron.error=Cron表达式错误
|
||||||
user.not.login=未获取到登录用户
|
user.not.login=未获取到登录用户
|
||||||
user.not.empty=用户不呢为空
|
user.not.empty=用户不呢为空
|
||||||
user.not.exist=用户不存在
|
user.not.exist=用户不存在
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
excel.parse.error=Excel parse error
|
excel.parse.error=Excel parse error
|
||||||
id.not_blank=Id must not be blank
|
id.not_blank=Id must not be blank
|
||||||
|
permission.system_user.invite=Invite user
|
||||||
role.not.global.system=Role is not global system role
|
role.not.global.system=Role is not global system role
|
||||||
role.not.contains.member=Role not contains member
|
role.not.contains.member=Role not contains member
|
||||||
|
schedule.cron.error=Cron is error
|
||||||
user.not.login=User not login
|
user.not.login=User not login
|
||||||
user.not.exist=User not exist
|
user.not.exist=User not exist
|
||||||
personal.no.permission=No permission
|
personal.no.permission=No permission
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
excel.parse.error=Excel解析失败
|
excel.parse.error=Excel解析失败
|
||||||
id.not_blank=ID不能为空
|
id.not_blank=ID不能为空
|
||||||
|
permission.system_user.invite=邀请用户
|
||||||
role.not.global.system=角色不是全局系统角色
|
role.not.global.system=角色不是全局系统角色
|
||||||
role.not.contains.member=角色不包含系统成员角色
|
role.not.contains.member=角色不包含系统成员角色
|
||||||
|
schedule.cron.error=Cron表达式错误
|
||||||
user.not.login=未获取到登录用户
|
user.not.login=未获取到登录用户
|
||||||
user.not.empty=用户不呢为空
|
user.not.empty=用户不呢为空
|
||||||
user.not.exist=用户不存在
|
user.not.exist=用户不存在
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
excel.parse.error=Excel解析失敗
|
excel.parse.error=Excel解析失敗
|
||||||
id.not_blank=ID不能為空
|
id.not_blank=ID不能為空
|
||||||
|
permission.system_user.invite=邀請用戶
|
||||||
role.not.global.system=角色不是為全局系統角色
|
role.not.global.system=角色不是為全局系統角色
|
||||||
role.not.contains.member=角色不包含系統成員角色
|
role.not.contains.member=角色不包含系統成員角色
|
||||||
|
schedule.cron.error=Cron表達式錯誤
|
||||||
user.not.login=未獲取到登錄用戶
|
user.not.login=未獲取到登錄用戶
|
||||||
user.not.empty=用戶不呢為空
|
user.not.empty=用戶不呢為空
|
||||||
user.not.exist=用戶不存在
|
user.not.exist=用戶不存在
|
||||||
|
|
|
@ -6,11 +6,14 @@ import io.metersphere.api.constants.ApiResource;
|
||||||
import io.metersphere.api.domain.ApiScenario;
|
import io.metersphere.api.domain.ApiScenario;
|
||||||
import io.metersphere.api.dto.scenario.*;
|
import io.metersphere.api.dto.scenario.*;
|
||||||
import io.metersphere.api.service.ApiValidateService;
|
import io.metersphere.api.service.ApiValidateService;
|
||||||
|
import io.metersphere.api.service.definition.ApiScenarioNoticeService;
|
||||||
import io.metersphere.api.service.scenario.ApiScenarioLogService;
|
import io.metersphere.api.service.scenario.ApiScenarioLogService;
|
||||||
import io.metersphere.api.service.scenario.ApiScenarioService;
|
import io.metersphere.api.service.scenario.ApiScenarioService;
|
||||||
import io.metersphere.sdk.constants.PermissionConstants;
|
import io.metersphere.sdk.constants.PermissionConstants;
|
||||||
import io.metersphere.system.log.annotation.Log;
|
import io.metersphere.system.log.annotation.Log;
|
||||||
import io.metersphere.system.log.constants.OperationLogType;
|
import io.metersphere.system.log.constants.OperationLogType;
|
||||||
|
import io.metersphere.system.notice.annotation.SendNotice;
|
||||||
|
import io.metersphere.system.notice.constants.NoticeConstants;
|
||||||
import io.metersphere.system.security.CheckOwner;
|
import io.metersphere.system.security.CheckOwner;
|
||||||
import io.metersphere.system.utils.PageUtils;
|
import io.metersphere.system.utils.PageUtils;
|
||||||
import io.metersphere.system.utils.Pager;
|
import io.metersphere.system.utils.Pager;
|
||||||
|
@ -159,4 +162,19 @@ public class ApiScenarioController {
|
||||||
apiScenarioService.updatePriority(id, priority, SessionUtils.getUserId());
|
apiScenarioService.updatePriority(id, priority, SessionUtils.getUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/schedule-config")
|
||||||
|
@Operation(summary = "接口测试-接口场景管理-定时任务配置")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE)
|
||||||
|
@Log(type = OperationLogType.UPDATE, expression = "#msClass.scheduleLog(#request.getScenarioId())", msClass = ApiScenarioLogService.class)
|
||||||
|
@CheckOwner(resourceId = "#request.getScenarioId()", resourceType = "api_scenario")
|
||||||
|
@SendNotice(taskType = NoticeConstants.TaskType.SCHEDULE_TASK, event = NoticeConstants.Event.UPDATE, target = "#targetClass.getScheduleNotice(#request)", targetClass = ApiScenarioNoticeService.class)
|
||||||
|
public String scheduleConfig(@Validated @RequestBody ApiScenarioScheduleConfigRequest request) {
|
||||||
|
/*
|
||||||
|
TODO to Chen Jianxing:
|
||||||
|
request.configMap 中需要补充场景的执行信息,比如环境、资源池、是否失败停止等配置。
|
||||||
|
在触发定时任务的APIScenarioScheduleJob中会用到
|
||||||
|
*/
|
||||||
|
apiValidateService.validateApiMenuInProject(request.getScenarioId(), ApiResource.API_SCENARIO.name());
|
||||||
|
return apiScenarioService.scheduleConfig(request, SessionUtils.getUserId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package io.metersphere.api.dto.scenario;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ApiScenarioScheduleConfigRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "{api_scenario.id.not_blank}")
|
||||||
|
@Schema(description = "场景ID")
|
||||||
|
private String scenarioId;
|
||||||
|
|
||||||
|
@Schema(description = "启用/禁用")
|
||||||
|
private boolean enable;
|
||||||
|
|
||||||
|
@Schema(description = "Cron表达式")
|
||||||
|
@NotBlank
|
||||||
|
private String cron;
|
||||||
|
|
||||||
|
@Schema(description = "定时任务配置 (如果配置不更改,不需要传入这个参数。 如果要清空配置,传入一个空数据{} )")
|
||||||
|
Map<String, Object> configMap;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package io.metersphere.api.job;
|
||||||
|
|
||||||
|
import io.metersphere.system.schedule.BaseScheduleJob;
|
||||||
|
import org.quartz.JobExecutionContext;
|
||||||
|
import org.quartz.JobKey;
|
||||||
|
import org.quartz.TriggerKey;
|
||||||
|
|
||||||
|
public class ApiScenarioScheduleJob extends BaseScheduleJob {
|
||||||
|
@Override
|
||||||
|
protected void businessExecute(JobExecutionContext context) {
|
||||||
|
/*
|
||||||
|
TODO to Chen Jianxing:
|
||||||
|
这里需要补充执行逻辑
|
||||||
|
记得执行信息(环境、资源池、是否失败停止等配置)在jobDataMap里面
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JobKey getJobKey(String scenarioId) {
|
||||||
|
return new JobKey(scenarioId, ApiScenarioScheduleJob.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TriggerKey getTriggerKey(String scenarioId) {
|
||||||
|
return new TriggerKey(scenarioId, ApiScenarioScheduleJob.class.getName());
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,9 +3,14 @@ package io.metersphere.api.service.definition;
|
||||||
import io.metersphere.api.domain.ApiScenario;
|
import io.metersphere.api.domain.ApiScenario;
|
||||||
import io.metersphere.api.domain.ApiScenarioExample;
|
import io.metersphere.api.domain.ApiScenarioExample;
|
||||||
import io.metersphere.api.dto.scenario.ApiScenarioBatchRequest;
|
import io.metersphere.api.dto.scenario.ApiScenarioBatchRequest;
|
||||||
|
import io.metersphere.api.dto.scenario.ApiScenarioScheduleConfigRequest;
|
||||||
|
import io.metersphere.api.job.ApiScenarioScheduleJob;
|
||||||
import io.metersphere.api.mapper.ApiScenarioMapper;
|
import io.metersphere.api.mapper.ApiScenarioMapper;
|
||||||
import io.metersphere.api.service.scenario.ApiScenarioService;
|
import io.metersphere.api.service.scenario.ApiScenarioService;
|
||||||
import io.metersphere.sdk.util.SubListUtils;
|
import io.metersphere.sdk.util.SubListUtils;
|
||||||
|
import io.metersphere.system.domain.Schedule;
|
||||||
|
import io.metersphere.system.domain.ScheduleExample;
|
||||||
|
import io.metersphere.system.mapper.ScheduleMapper;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
@ -21,6 +26,14 @@ public class ApiScenarioNoticeService {
|
||||||
@Resource
|
@Resource
|
||||||
private ApiScenarioMapper apiScenarioMapper;
|
private ApiScenarioMapper apiScenarioMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ScheduleMapper scheduleMapper;
|
||||||
|
|
||||||
|
public List<Schedule> getScheduleNotice(ApiScenarioScheduleConfigRequest request) {
|
||||||
|
ScheduleExample example = new ScheduleExample();
|
||||||
|
example.createCriteria().andResourceIdEqualTo(request.getScenarioId()).andJobEqualTo(ApiScenarioScheduleJob.class.getName());
|
||||||
|
return scheduleMapper.selectByExample(example);
|
||||||
|
}
|
||||||
|
|
||||||
public List<ApiScenario> getBatchOptionScenarios(ApiScenarioBatchRequest request) {
|
public List<ApiScenario> getBatchOptionScenarios(ApiScenarioBatchRequest request) {
|
||||||
List<String> ids = apiScenarioService.doSelectIds(request, false);
|
List<String> ids = apiScenarioService.doSelectIds(request, false);
|
||||||
|
|
|
@ -133,6 +133,20 @@ public class ApiScenarioLogService {
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LogDTO scheduleLog(String id) {
|
||||||
|
ApiScenario apiScenario = apiScenarioMapper.selectByPrimaryKey(id);
|
||||||
|
Project project = projectMapper.selectByPrimaryKey(apiScenario.getProjectId());
|
||||||
|
LogDTO dto = LogDTOBuilder.builder()
|
||||||
|
.projectId(project.getId())
|
||||||
|
.organizationId(project.getOrganizationId())
|
||||||
|
.type(OperationLogType.UPDATE.name())
|
||||||
|
.module(OperationLogModule.API_SCENARIO)
|
||||||
|
.sourceId(apiScenario.getId())
|
||||||
|
.content(Translator.get("api_automation_schedule") + ":" + apiScenario.getName())
|
||||||
|
.build().getLogDTO();
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
public LogDTO updateLog(String id) {
|
public LogDTO updateLog(String id) {
|
||||||
ApiScenario apiScenario = apiScenarioMapper.selectByPrimaryKey(id);
|
ApiScenario apiScenario = apiScenarioMapper.selectByPrimaryKey(id);
|
||||||
// todo 记录完整的场景信息
|
// todo 记录完整的场景信息
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.metersphere.api.dto.debug.ApiResourceRunRequest;
|
||||||
import io.metersphere.api.dto.request.MsScenario;
|
import io.metersphere.api.dto.request.MsScenario;
|
||||||
import io.metersphere.api.dto.response.ApiScenarioBatchOperationResponse;
|
import io.metersphere.api.dto.response.ApiScenarioBatchOperationResponse;
|
||||||
import io.metersphere.api.dto.scenario.*;
|
import io.metersphere.api.dto.scenario.*;
|
||||||
|
import io.metersphere.api.job.ApiScenarioScheduleJob;
|
||||||
import io.metersphere.api.mapper.*;
|
import io.metersphere.api.mapper.*;
|
||||||
import io.metersphere.api.parser.step.StepParser;
|
import io.metersphere.api.parser.step.StepParser;
|
||||||
import io.metersphere.api.parser.step.StepParserFactory;
|
import io.metersphere.api.parser.step.StepParserFactory;
|
||||||
|
@ -23,10 +24,7 @@ import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
|
||||||
import io.metersphere.project.service.FileAssociationService;
|
import io.metersphere.project.service.FileAssociationService;
|
||||||
import io.metersphere.project.service.FileMetadataService;
|
import io.metersphere.project.service.FileMetadataService;
|
||||||
import io.metersphere.project.service.ProjectService;
|
import io.metersphere.project.service.ProjectService;
|
||||||
import io.metersphere.sdk.constants.ApiExecuteRunMode;
|
import io.metersphere.sdk.constants.*;
|
||||||
import io.metersphere.sdk.constants.ApplicationNumScope;
|
|
||||||
import io.metersphere.sdk.constants.DefaultRepositoryDir;
|
|
||||||
import io.metersphere.sdk.constants.ModuleConstants;
|
|
||||||
import io.metersphere.sdk.domain.Environment;
|
import io.metersphere.sdk.domain.Environment;
|
||||||
import io.metersphere.sdk.domain.EnvironmentExample;
|
import io.metersphere.sdk.domain.EnvironmentExample;
|
||||||
import io.metersphere.sdk.domain.EnvironmentGroup;
|
import io.metersphere.sdk.domain.EnvironmentGroup;
|
||||||
|
@ -40,8 +38,10 @@ import io.metersphere.sdk.mapper.EnvironmentGroupMapper;
|
||||||
import io.metersphere.sdk.mapper.EnvironmentMapper;
|
import io.metersphere.sdk.mapper.EnvironmentMapper;
|
||||||
import io.metersphere.sdk.util.*;
|
import io.metersphere.sdk.util.*;
|
||||||
import io.metersphere.system.dto.LogInsertModule;
|
import io.metersphere.system.dto.LogInsertModule;
|
||||||
|
import io.metersphere.system.dto.request.ScheduleConfig;
|
||||||
import io.metersphere.system.log.constants.OperationLogModule;
|
import io.metersphere.system.log.constants.OperationLogModule;
|
||||||
import io.metersphere.system.log.constants.OperationLogType;
|
import io.metersphere.system.log.constants.OperationLogType;
|
||||||
|
import io.metersphere.system.schedule.ScheduleService;
|
||||||
import io.metersphere.system.service.UserLoginService;
|
import io.metersphere.system.service.UserLoginService;
|
||||||
import io.metersphere.system.uid.IDGenerator;
|
import io.metersphere.system.uid.IDGenerator;
|
||||||
import io.metersphere.system.uid.NumGenerator;
|
import io.metersphere.system.uid.NumGenerator;
|
||||||
|
@ -121,6 +121,9 @@ public class ApiScenarioService {
|
||||||
private ApiScenarioCsvMapper apiScenarioCsvMapper;
|
private ApiScenarioCsvMapper apiScenarioCsvMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private ApiScenarioCsvStepMapper apiScenarioCsvStepMapper;
|
private ApiScenarioCsvStepMapper apiScenarioCsvStepMapper;
|
||||||
|
@Resource
|
||||||
|
private ScheduleService scheduleService;
|
||||||
|
|
||||||
public static final String PRIORITY = "Priority";
|
public static final String PRIORITY = "Priority";
|
||||||
public static final String STATUS = "Status";
|
public static final String STATUS = "Status";
|
||||||
public static final String TAGS = "Tags";
|
public static final String TAGS = "Tags";
|
||||||
|
@ -836,6 +839,9 @@ public class ApiScenarioService {
|
||||||
apiFileResourceService.deleteByResourceId(scenarioDir, scenario.getId(), scenario.getProjectId(), operator, OperationLogModule.API_DEBUG);
|
apiFileResourceService.deleteByResourceId(scenarioDir, scenario.getId(), scenario.getProjectId(), operator, OperationLogModule.API_DEBUG);
|
||||||
}catch (Exception ignore){}
|
}catch (Exception ignore){}
|
||||||
|
|
||||||
|
//删除定时任务
|
||||||
|
scheduleService.deleteByResourceId(scenario.getId(), ApiScenarioScheduleJob.class.getName());
|
||||||
|
|
||||||
//todo wang xiao gang: 删除csv相关东西
|
//todo wang xiao gang: 删除csv相关东西
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -858,13 +864,14 @@ public class ApiScenarioService {
|
||||||
blobExample.createCriteria().andScenarioIdIn(scenarioIdList);
|
blobExample.createCriteria().andScenarioIdIn(scenarioIdList);
|
||||||
apiScenarioStepBlobMapper.deleteByExample(blobExample);
|
apiScenarioStepBlobMapper.deleteByExample(blobExample);
|
||||||
|
|
||||||
//删除文件
|
|
||||||
scenarioList.forEach(scenario -> {
|
scenarioList.forEach(scenario -> {
|
||||||
|
//删除文件
|
||||||
String scenarioDir = DefaultRepositoryDir.getApiDebugDir(scenario.getProjectId(), scenario.getId());
|
String scenarioDir = DefaultRepositoryDir.getApiDebugDir(scenario.getProjectId(), scenario.getId());
|
||||||
try {
|
try {
|
||||||
apiFileResourceService.deleteByResourceId(scenarioDir, scenario.getId(), scenario.getProjectId(), operator, OperationLogModule.API_DEBUG);
|
apiFileResourceService.deleteByResourceId(scenarioDir, scenario.getId(), scenario.getProjectId(), operator, OperationLogModule.API_DEBUG);
|
||||||
}catch (Exception ignore){}
|
}catch (Exception ignore){}
|
||||||
|
//删除定时任务
|
||||||
|
scheduleService.deleteByResourceId(scenario.getId(), ApiScenarioScheduleJob.class.getName());
|
||||||
});
|
});
|
||||||
|
|
||||||
//todo wang xiao gang: 删除csv相关东西
|
//todo wang xiao gang: 删除csv相关东西
|
||||||
|
@ -884,6 +891,9 @@ public class ApiScenarioService {
|
||||||
apiScenario.setId(id);
|
apiScenario.setId(id);
|
||||||
apiScenario.setDeleted(true);
|
apiScenario.setDeleted(true);
|
||||||
apiScenarioMapper.updateByPrimaryKeySelective(apiScenario);
|
apiScenarioMapper.updateByPrimaryKeySelective(apiScenario);
|
||||||
|
|
||||||
|
//删除定时任务
|
||||||
|
scheduleService.deleteByResourceId(id, ApiScenarioScheduleJob.class.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAddExist(ApiScenarioAddRequest apiScenario) {
|
private void checkAddExist(ApiScenarioAddRequest apiScenario) {
|
||||||
|
@ -1574,6 +1584,9 @@ public class ApiScenarioService {
|
||||||
|
|
||||||
for (ApiScenario scenario : apiScenarioList) {
|
for (ApiScenario scenario : apiScenarioList) {
|
||||||
response.addSuccessData(scenario.getId(), scenario.getNum(), scenario.getName());
|
response.addSuccessData(scenario.getId(), scenario.getNum(), scenario.getName());
|
||||||
|
|
||||||
|
//删除定时任务
|
||||||
|
scheduleService.deleteByResourceId(scenario.getId(), ApiScenarioScheduleJob.class.getName());
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -1592,4 +1605,24 @@ public class ApiScenarioService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String scheduleConfig(ApiScenarioScheduleConfigRequest scheduleRequest, String operator) {
|
||||||
|
ApiScenario apiScenario = apiScenarioMapper.selectByPrimaryKey(scheduleRequest.getScenarioId());
|
||||||
|
ScheduleConfig scheduleConfig = ScheduleConfig.builder()
|
||||||
|
.resourceId(apiScenario.getId())
|
||||||
|
.key(apiScenario.getId())
|
||||||
|
.projectId(apiScenario.getProjectId())
|
||||||
|
.name(apiScenario.getName())
|
||||||
|
.enable(scheduleRequest.isEnable())
|
||||||
|
.cron(scheduleRequest.getCron())
|
||||||
|
.resourceType(ScheduleResourceType.API_SCENARIO.name())
|
||||||
|
.configMap(scheduleRequest.getConfigMap())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return scheduleService.scheduleConfig(
|
||||||
|
scheduleConfig,
|
||||||
|
ApiScenarioScheduleJob.getJobKey(apiScenario.getId()),
|
||||||
|
ApiScenarioScheduleJob.getTriggerKey(apiScenario.getId()),
|
||||||
|
ApiScenarioScheduleJob.class,
|
||||||
|
operator);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -98,6 +98,8 @@ public class ApiScenarioControllerTests extends BaseTest {
|
||||||
@Resource
|
@Resource
|
||||||
private ApiScenarioStepBlobMapper apiScenarioStepBlobMapper;
|
private ApiScenarioStepBlobMapper apiScenarioStepBlobMapper;
|
||||||
@Resource
|
@Resource
|
||||||
|
private ApiFileResourceMapper apiFileResourceMapper;
|
||||||
|
@Resource
|
||||||
private ApiScenarioBlobMapper apiScenarioBlobMapper;
|
private ApiScenarioBlobMapper apiScenarioBlobMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private ExtBaseProjectVersionMapper extBaseProjectVersionMapper;
|
private ExtBaseProjectVersionMapper extBaseProjectVersionMapper;
|
||||||
|
@ -1122,7 +1124,7 @@ public class ApiScenarioControllerTests extends BaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(21)
|
@Order(22)
|
||||||
void batchMove() throws Exception {
|
void batchMove() throws Exception {
|
||||||
String testUrl = "/batch-operation/move";
|
String testUrl = "/batch-operation/move";
|
||||||
if (CollectionUtils.isEmpty(BATCH_OPERATION_SCENARIO_ID)) {
|
if (CollectionUtils.isEmpty(BATCH_OPERATION_SCENARIO_ID)) {
|
||||||
|
@ -1252,14 +1254,155 @@ public class ApiScenarioControllerTests extends BaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(22)
|
@Order(23)
|
||||||
|
void scheduleTest() throws Exception {
|
||||||
|
String testUrl = "/schedule-config";
|
||||||
|
|
||||||
|
if (CollectionUtils.isEmpty(BATCH_OPERATION_SCENARIO_ID)) {
|
||||||
|
this.batchCreateScenarios();
|
||||||
|
}
|
||||||
|
|
||||||
|
//使用最后一个场景ID用于做定时任务的测试
|
||||||
|
String scenarioId = BATCH_OPERATION_SCENARIO_ID.getLast();
|
||||||
|
ApiScenarioScheduleConfigRequest request = new ApiScenarioScheduleConfigRequest();
|
||||||
|
|
||||||
|
request.setScenarioId(scenarioId);
|
||||||
|
request.setEnable(true);
|
||||||
|
request.setCron("0 0 0 * * ?");
|
||||||
|
|
||||||
|
//先测试一下没有开启模块时接口能否使用
|
||||||
|
apiScenarioBatchOperationTestService.removeApiModule(DEFAULT_PROJECT_ID);
|
||||||
|
this.requestPost(testUrl, request).andExpect(status().is5xxServerError());
|
||||||
|
//恢复
|
||||||
|
apiScenarioBatchOperationTestService.resetProjectModule(DEFAULT_PROJECT_ID);
|
||||||
|
MvcResult result = this.requestPostAndReturn(testUrl, request);
|
||||||
|
ResultHolder resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
|
||||||
|
String scheduleId = resultHolder.getData().toString();
|
||||||
|
apiScenarioBatchOperationTestService.checkSchedule(scheduleId, scenarioId, request.isEnable());
|
||||||
|
|
||||||
|
//增加日志检查
|
||||||
|
LOG_CHECK_LIST.add(
|
||||||
|
new CheckLogModel(scenarioId, OperationLogType.UPDATE, "/api/scenario/schedule-config")
|
||||||
|
);
|
||||||
|
|
||||||
|
//关闭
|
||||||
|
request.setEnable(false);
|
||||||
|
result = this.requestPostAndReturn(testUrl, request);
|
||||||
|
resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
|
||||||
|
String newScheduleId = resultHolder.getData().toString();
|
||||||
|
//检查两个scheduleId是否相同
|
||||||
|
Assertions.assertEquals(scheduleId, newScheduleId);
|
||||||
|
apiScenarioBatchOperationTestService.checkSchedule(newScheduleId, scenarioId, request.isEnable());
|
||||||
|
|
||||||
|
//配置configMap
|
||||||
|
request.setEnable(true);
|
||||||
|
request.setConfigMap(new HashMap<>() {{
|
||||||
|
this.put("envId", "testEnv");
|
||||||
|
this.put("resourcePoolId", "testResourcePool");
|
||||||
|
}});
|
||||||
|
result = this.requestPostAndReturn(testUrl, request);
|
||||||
|
resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
|
||||||
|
newScheduleId = resultHolder.getData().toString();
|
||||||
|
apiScenarioBatchOperationTestService.checkSchedule(newScheduleId, scenarioId, request.isEnable());
|
||||||
|
|
||||||
|
//清空configMap
|
||||||
|
request.setConfigMap(new HashMap<>());
|
||||||
|
request.setEnable(false);
|
||||||
|
result = this.requestPostAndReturn(testUrl, request);
|
||||||
|
resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
|
||||||
|
newScheduleId = resultHolder.getData().toString();
|
||||||
|
apiScenarioBatchOperationTestService.checkSchedule(newScheduleId, scenarioId, request.isEnable());
|
||||||
|
|
||||||
|
//测试各种corn表达式用于校验正则的准确性
|
||||||
|
String[] cornStrArr = new String[]{
|
||||||
|
"0 0 12 * * ?", //每天中午12点触发
|
||||||
|
"0 15 10 ? * *", //每天上午10:15触发
|
||||||
|
"0 15 10 * * ?", //每天上午10:15触发
|
||||||
|
"0 15 10 * * ? *",//每天上午10:15触发
|
||||||
|
"0 15 10 * * ? 2048",//2008年的每天上午10:15触发
|
||||||
|
"0 * 10 * * ?",//每天上午10:00至10:59期间的每1分钟触发
|
||||||
|
"0 0/5 10 * * ?",//每天上午10:00至10:55期间的每5分钟触发
|
||||||
|
"0 0/5 10,16 * * ?",//每天上午10:00至10:55期间和下午4:00至4:55期间的每5分钟触发
|
||||||
|
"0 0-5 10 * * ?",//每天上午10:00至10:05期间的每1分钟触发
|
||||||
|
"0 10,14,18 15 ? 3 WED",//每年三月的星期三的下午2:10和2:18触发
|
||||||
|
"0 10 15 ? * MON-FRI",//每个周一、周二、周三、周四、周五的下午3:10触发
|
||||||
|
"0 15 10 15 * ?",//每月15日上午10:15触发
|
||||||
|
"0 15 10 L * ?", //每月最后一日的上午10:15触发
|
||||||
|
"0 15 10 ? * 6L", //每月的最后一个星期五上午10:15触发
|
||||||
|
"0 15 10 ? * 6L 2024-2026", //从2024年至2026年每月的最后一个星期五上午10:15触发
|
||||||
|
"0 15 10 ? * 6#3", //每月的第三个星期五上午10:15触发
|
||||||
|
};
|
||||||
|
|
||||||
|
//每种corn表达式开启、关闭都测试一遍,检查是否能正常开关定时任务
|
||||||
|
for (String corn : cornStrArr) {
|
||||||
|
request = new ApiScenarioScheduleConfigRequest();
|
||||||
|
request.setScenarioId(scenarioId);
|
||||||
|
request.setEnable(true);
|
||||||
|
request.setCron(corn);
|
||||||
|
result = this.requestPostAndReturn(testUrl, request);
|
||||||
|
resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
|
||||||
|
scheduleId = resultHolder.getData().toString();
|
||||||
|
apiScenarioBatchOperationTestService.checkSchedule(scheduleId, scenarioId, request.isEnable());
|
||||||
|
|
||||||
|
request = new ApiScenarioScheduleConfigRequest();
|
||||||
|
request.setScenarioId(scenarioId);
|
||||||
|
request.setEnable(false);
|
||||||
|
request.setCron(corn);
|
||||||
|
result = this.requestPostAndReturn(testUrl, request);
|
||||||
|
resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
|
||||||
|
scheduleId = resultHolder.getData().toString();
|
||||||
|
apiScenarioBatchOperationTestService.checkSchedule(scheduleId, scenarioId, request.isEnable());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//校验权限
|
||||||
|
this.requestPostPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE, testUrl, request);
|
||||||
|
|
||||||
|
//反例:scenarioId不存在
|
||||||
|
request = new ApiScenarioScheduleConfigRequest();
|
||||||
|
request.setCron("0 0 0 * * ?");
|
||||||
|
this.requestPost(testUrl, request).andExpect(status().isBadRequest());
|
||||||
|
request.setScenarioId(IDGenerator.nextStr());
|
||||||
|
this.requestPost(testUrl, request).andExpect(status().is5xxServerError());
|
||||||
|
|
||||||
|
//反例:不配置cron表达式
|
||||||
|
request = new ApiScenarioScheduleConfigRequest();
|
||||||
|
request.setScenarioId(scenarioId);
|
||||||
|
this.requestPost(testUrl, request).andExpect(status().isBadRequest());
|
||||||
|
|
||||||
|
//反例:配置错误的cron表达式,测试是否会关闭定时任务
|
||||||
|
request = new ApiScenarioScheduleConfigRequest();
|
||||||
|
request.setScenarioId(scenarioId);
|
||||||
|
request.setEnable(true);
|
||||||
|
request.setCron(IDGenerator.nextStr());
|
||||||
|
this.requestPost(testUrl, request).andExpect(status().is5xxServerError());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//30开始是关于删除和恢复的
|
||||||
|
@Test
|
||||||
|
@Order(31)
|
||||||
void batchRemoveToGc() throws Exception {
|
void batchRemoveToGc() throws Exception {
|
||||||
String testUrl = "/batch-operation/delete-gc";
|
String testUrl = "/batch-operation/delete-gc";
|
||||||
if (CollectionUtils.isEmpty(BATCH_OPERATION_SCENARIO_ID)) {
|
if (CollectionUtils.isEmpty(BATCH_OPERATION_SCENARIO_ID)) {
|
||||||
this.batchCopy();
|
this.scheduleTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//使用最后一个场景ID用于做定时任务的测试
|
||||||
|
String scenarioId = BATCH_OPERATION_SCENARIO_ID.getLast();
|
||||||
|
|
||||||
//本次测试涉及到的场景ID
|
//本次测试涉及到的场景ID
|
||||||
List<String> operationScenarioIds = new ArrayList<>(BATCH_OPERATION_SCENARIO_ID.subList(200, 500));
|
List<String> operationScenarioIds = new ArrayList<>(BATCH_OPERATION_SCENARIO_ID.subList(200, 500));
|
||||||
|
//给最后一个场景创建定时任务,测试batchToGc时,是否会删除定时任务
|
||||||
|
ApiScenarioScheduleConfigRequest scheduleRequest = new ApiScenarioScheduleConfigRequest();
|
||||||
|
scheduleRequest.setScenarioId(scenarioId);
|
||||||
|
scheduleRequest.setEnable(true);
|
||||||
|
scheduleRequest.setCron("0 0 0 * * ?");
|
||||||
|
MvcResult scheduleResult = this.requestPostAndReturn("/schedule-config", scheduleRequest);
|
||||||
|
ResultHolder scheduleResultHolder = JSON.parseObject(scheduleResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
|
||||||
|
String scheduleId = scheduleResultHolder.getData().toString();
|
||||||
|
apiScenarioBatchOperationTestService.checkSchedule(scheduleId, scenarioId, scheduleRequest.isEnable());
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
正例测试
|
正例测试
|
||||||
|
@ -1283,7 +1426,8 @@ public class ApiScenarioControllerTests extends BaseTest {
|
||||||
ApiScenarioBatchOperationResponse resultResponse = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), ApiScenarioBatchOperationResponse.class);
|
ApiScenarioBatchOperationResponse resultResponse = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), ApiScenarioBatchOperationResponse.class);
|
||||||
//检查返回值
|
//检查返回值
|
||||||
Assertions.assertEquals(resultResponse.getSuccess(), 300);
|
Assertions.assertEquals(resultResponse.getSuccess(), 300);
|
||||||
|
//检查定时任务是否删除
|
||||||
|
apiScenarioBatchOperationTestService.checkScheduleIsRemove(scenarioId);
|
||||||
//数据库级别的检查
|
//数据库级别的检查
|
||||||
apiScenarioBatchOperationTestService.checkBatchGCOperation
|
apiScenarioBatchOperationTestService.checkBatchGCOperation
|
||||||
(BATCH_OPERATION_SCENARIO_ID.subList(200, 500), true);
|
(BATCH_OPERATION_SCENARIO_ID.subList(200, 500), true);
|
||||||
|
@ -1320,7 +1464,7 @@ public class ApiScenarioControllerTests extends BaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(23)
|
@Order(32)
|
||||||
//todo
|
//todo
|
||||||
void batchRecoverToGc() throws Exception {
|
void batchRecoverToGc() throws Exception {
|
||||||
String testUrl = "/batch-operation/recover-gc";
|
String testUrl = "/batch-operation/recover-gc";
|
||||||
|
@ -1393,7 +1537,7 @@ public class ApiScenarioControllerTests extends BaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(24)
|
@Order(33)
|
||||||
//todo
|
//todo
|
||||||
void batchDelete() throws Exception {
|
void batchDelete() throws Exception {
|
||||||
String testUrl = "/batch-operation/delete";
|
String testUrl = "/batch-operation/delete";
|
||||||
|
@ -1467,9 +1611,8 @@ public class ApiScenarioControllerTests extends BaseTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//30开始是关于删除和恢复的
|
|
||||||
@Test
|
@Test
|
||||||
@Order(30)
|
@Order(34)
|
||||||
void recover() throws Exception {
|
void recover() throws Exception {
|
||||||
if (CollectionUtils.isEmpty(BATCH_OPERATION_SCENARIO_ID)) {
|
if (CollectionUtils.isEmpty(BATCH_OPERATION_SCENARIO_ID)) {
|
||||||
this.batchCreateScenarios();
|
this.batchCreateScenarios();
|
||||||
|
@ -1651,6 +1794,7 @@ public class ApiScenarioControllerTests extends BaseTest {
|
||||||
apiFileResource.setResourceType("API_SCENARIO");
|
apiFileResource.setResourceType("API_SCENARIO");
|
||||||
apiFileResource.setCreateTime(System.currentTimeMillis());
|
apiFileResource.setCreateTime(System.currentTimeMillis());
|
||||||
apiFileResource.setProjectId(apiScenario.getProjectId());
|
apiFileResource.setProjectId(apiScenario.getProjectId());
|
||||||
|
apiFileResourceMapper.insertSelective(apiFileResource);
|
||||||
}
|
}
|
||||||
apiScenarioMapper.insertSelective(apiScenario);
|
apiScenarioMapper.insertSelective(apiScenario);
|
||||||
BATCH_OPERATION_SCENARIO_ID.add(apiScenario.getId());
|
BATCH_OPERATION_SCENARIO_ID.add(apiScenario.getId());
|
||||||
|
|
|
@ -2,13 +2,16 @@ package io.metersphere.api.service;
|
||||||
|
|
||||||
import io.metersphere.api.domain.*;
|
import io.metersphere.api.domain.*;
|
||||||
import io.metersphere.api.dto.scenario.ApiScenarioBatchCopyMoveRequest;
|
import io.metersphere.api.dto.scenario.ApiScenarioBatchCopyMoveRequest;
|
||||||
|
import io.metersphere.api.job.ApiScenarioScheduleJob;
|
||||||
import io.metersphere.api.mapper.*;
|
import io.metersphere.api.mapper.*;
|
||||||
import io.metersphere.project.domain.Project;
|
import io.metersphere.project.domain.Project;
|
||||||
import io.metersphere.project.mapper.ProjectMapper;
|
import io.metersphere.project.mapper.ProjectMapper;
|
||||||
import io.metersphere.sdk.util.JSON;
|
import io.metersphere.sdk.util.JSON;
|
||||||
|
import io.metersphere.system.mapper.ExtScheduleMapper;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.commons.collections.MapUtils;
|
import org.apache.commons.collections.MapUtils;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.quartz.Scheduler;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@ -30,6 +33,11 @@ public class ApiScenarioBatchOperationTestService {
|
||||||
private ApiScenarioStepBlobMapper apiScenarioStepBlobMapper;
|
private ApiScenarioStepBlobMapper apiScenarioStepBlobMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private ApiFileResourceMapper apiFileResourceMapper;
|
private ApiFileResourceMapper apiFileResourceMapper;
|
||||||
|
@Resource
|
||||||
|
private ExtScheduleMapper extScheduleMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private Scheduler scheduler;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ProjectMapper projectMapper;
|
private ProjectMapper projectMapper;
|
||||||
|
@ -189,4 +197,20 @@ public class ApiScenarioBatchOperationTestService {
|
||||||
sourceFileExample.createCriteria().andResourceIdIn(deleteScenarioIds);
|
sourceFileExample.createCriteria().andResourceIdIn(deleteScenarioIds);
|
||||||
Assertions.assertEquals(apiFileResourceMapper.countByExample(sourceFileExample), 0);
|
Assertions.assertEquals(apiFileResourceMapper.countByExample(sourceFileExample), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
校验定时任务是否成功开启:
|
||||||
|
1.schedule表中存在数据,且开启状态符合isEnable
|
||||||
|
2.开启状态下: qrtz_triggers 和 qrtz_cron_triggers 表存在对应的数据
|
||||||
|
3.关闭状态下: qrtz_triggers 和 qrtz_cron_triggers 表不存在对应的数据
|
||||||
|
*/
|
||||||
|
public void checkSchedule(String scheduleId, String resourceId, boolean isEnable) throws Exception {
|
||||||
|
Assertions.assertEquals(extScheduleMapper.countByIdAndEnable(scheduleId, isEnable), 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class EnvironmentService {
|
||||||
private static final String USERNAME = "user";
|
private static final String USERNAME = "user";
|
||||||
private static final String PASSWORD = "password";
|
private static final String PASSWORD = "password";
|
||||||
private static final String PATH = "/project/environment/import";
|
private static final String PATH = "/project/environment/import";
|
||||||
private static final String MOCK_EVN_SOCKET = "/api/mock/";
|
private static final String MOCK_EVN_SOCKET = "/mock-server/";
|
||||||
|
|
||||||
public List<OptionDTO> getDriverOptions(String organizationId) {
|
public List<OptionDTO> getDriverOptions(String organizationId) {
|
||||||
return jdbcDriverPluginService.getJdbcDriverOption(organizationId);
|
return jdbcDriverPluginService.getJdbcDriverOption(organizationId);
|
||||||
|
|
|
@ -19,7 +19,8 @@ import org.springframework.web.bind.annotation.*;
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "个人中心")
|
@Tag(name = "个人中心")
|
||||||
@RequestMapping("/personal")
|
@RequestMapping("/personal")
|
||||||
public class PersonalCenterController {
|
public class
|
||||||
|
PersonalCenterController {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package io.metersphere.system.dto.request;
|
||||||
|
|
||||||
|
import io.metersphere.sdk.constants.ScheduleType;
|
||||||
|
import io.metersphere.sdk.util.JSON;
|
||||||
|
import io.metersphere.system.domain.Schedule;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class ScheduleConfig {
|
||||||
|
|
||||||
|
private String resourceId;
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
private String projectId;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Boolean enable;
|
||||||
|
|
||||||
|
private String cron;
|
||||||
|
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
Map<String, Object> configMap;
|
||||||
|
|
||||||
|
public Schedule genCronSchedule(Schedule schedule) {
|
||||||
|
if (schedule == null) {
|
||||||
|
schedule = new Schedule();
|
||||||
|
}
|
||||||
|
schedule.setName(this.getName());
|
||||||
|
schedule.setResourceId(this.getResourceId());
|
||||||
|
schedule.setType(ScheduleType.CRON.name());
|
||||||
|
schedule.setKey(this.getKey());
|
||||||
|
schedule.setEnable(this.getEnable());
|
||||||
|
schedule.setProjectId(this.getProjectId());
|
||||||
|
schedule.setValue(this.getCron());
|
||||||
|
schedule.setResourceType(this.getResourceType());
|
||||||
|
//配置数据为null,意味着不更改; 如果要清空配置,需要传入空对象
|
||||||
|
if (configMap != null) {
|
||||||
|
schedule.setConfig(JSON.toJSONString(configMap));
|
||||||
|
}
|
||||||
|
return schedule;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,4 +22,11 @@ public interface ExtScheduleMapper {
|
||||||
|
|
||||||
List<ApiScenario> getApiScenarioListByIds(@Param("ids") List<String> ids);
|
List<ApiScenario> getApiScenarioListByIds(@Param("ids") List<String> ids);
|
||||||
|
|
||||||
|
long countByResourceId(String resourceId);
|
||||||
|
|
||||||
|
long countByIdAndEnable(@Param("id") String scheduleId, @Param("enable") boolean isEnable);
|
||||||
|
|
||||||
|
long countQuartzTriggersByResourceId(String scheduleId);
|
||||||
|
|
||||||
|
long countQuartzCronTriggersByResourceId(String scheduleId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,28 @@
|
||||||
#{id}
|
#{id}
|
||||||
</foreach>
|
</foreach>
|
||||||
</select>
|
</select>
|
||||||
|
<select id="countByIdAndEnable" resultType="java.lang.Long">
|
||||||
|
select count(*)
|
||||||
|
from schedule
|
||||||
|
where id = #{id}
|
||||||
|
and enable = #{enable}
|
||||||
|
</select>
|
||||||
|
<select id="countQuartzTriggersByResourceId" resultType="java.lang.Long">
|
||||||
|
SELECT *
|
||||||
|
FROM QRTZ_TRIGGERS
|
||||||
|
WHERE TRIGGER_NAME = #{0}
|
||||||
|
|
||||||
|
</select>
|
||||||
|
<select id="countQuartzCronTriggersByResourceId" resultType="java.lang.Long">
|
||||||
|
SELECT *
|
||||||
|
FROM QRTZ_CRON_TRIGGERS
|
||||||
|
WHERE TRIGGER_NAME = #{0}
|
||||||
|
</select>
|
||||||
|
<select id="countByResourceId" resultType="java.lang.Long">
|
||||||
|
SELECT count(*)
|
||||||
|
FROM schedule
|
||||||
|
WHERE resource_id = #{0}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
|
@ -1,20 +1,25 @@
|
||||||
package io.metersphere.system.schedule;
|
package io.metersphere.system.schedule;
|
||||||
|
|
||||||
import io.metersphere.sdk.exception.MSException;
|
import io.metersphere.sdk.exception.MSException;
|
||||||
|
import io.metersphere.sdk.util.JSON;
|
||||||
import io.metersphere.system.domain.Schedule;
|
import io.metersphere.system.domain.Schedule;
|
||||||
import io.metersphere.system.domain.ScheduleExample;
|
import io.metersphere.system.domain.ScheduleExample;
|
||||||
|
import io.metersphere.system.dto.request.ScheduleConfig;
|
||||||
import io.metersphere.system.mapper.ScheduleMapper;
|
import io.metersphere.system.mapper.ScheduleMapper;
|
||||||
import io.metersphere.system.uid.IDGenerator;
|
import io.metersphere.system.uid.IDGenerator;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.quartz.JobDataMap;
|
||||||
import org.quartz.JobKey;
|
import org.quartz.JobKey;
|
||||||
import org.quartz.SchedulerException;
|
import org.quartz.SchedulerException;
|
||||||
import org.quartz.TriggerKey;
|
import org.quartz.TriggerKey;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public class ScheduleService {
|
public class ScheduleService {
|
||||||
|
@ -100,4 +105,44 @@ public class ScheduleService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String scheduleConfig(ScheduleConfig scheduleConfig, JobKey jobKey, TriggerKey triggerKey, Class clazz, String operator) {
|
||||||
|
Schedule schedule;
|
||||||
|
ScheduleExample example = new ScheduleExample();
|
||||||
|
example.createCriteria().andResourceIdEqualTo(scheduleConfig.getResourceId()).andJobEqualTo(clazz.getName());
|
||||||
|
List<Schedule> scheduleList = scheduleMapper.selectByExample(example);
|
||||||
|
if (CollectionUtils.isNotEmpty(scheduleList)) {
|
||||||
|
schedule = scheduleConfig.genCronSchedule(scheduleList.getFirst());
|
||||||
|
schedule.setUpdateTime(System.currentTimeMillis());
|
||||||
|
schedule.setJob(clazz.getName());
|
||||||
|
scheduleMapper.updateByExampleSelective(schedule, example);
|
||||||
|
} else {
|
||||||
|
schedule = scheduleConfig.genCronSchedule(null);
|
||||||
|
schedule.setJob(clazz.getName());
|
||||||
|
schedule.setId(IDGenerator.nextStr());
|
||||||
|
schedule.setCreateUser(operator);
|
||||||
|
schedule.setCreateTime(System.currentTimeMillis());
|
||||||
|
schedule.setUpdateTime(System.currentTimeMillis());
|
||||||
|
scheduleMapper.insert(schedule);
|
||||||
|
}
|
||||||
|
|
||||||
|
JobDataMap jobDataMap = scheduleManager.getDefaultJobDataMap(schedule, scheduleConfig.getCron(), operator);
|
||||||
|
if (StringUtils.isNotEmpty(schedule.getConfig())) {
|
||||||
|
Map<String, Object> configMap = JSON.parseObject(schedule.getConfig(), Map.class);
|
||||||
|
jobDataMap.putAll(configMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
scheduleManager.modifyCronJobTime方法如同它的方法名所说,只能修改定时任务的触发时间。
|
||||||
|
如果定时任务的配置数据jobData发生了变化,上面方法是无法更新配置数据的。
|
||||||
|
所以,如果配置数据发生了变化,做法就是先删除运行中的定时任务,再重新添加定时任务。
|
||||||
|
|
||||||
|
以上的更新逻辑配合 enable 开关,可以简化为下列写法:
|
||||||
|
*/
|
||||||
|
scheduleManager.removeJob(jobKey, triggerKey);
|
||||||
|
if (BooleanUtils.isTrue(schedule.getEnable())) {
|
||||||
|
scheduleManager.addCronJob(jobKey, triggerKey, clazz, scheduleConfig.getCron(), jobDataMap);
|
||||||
|
}
|
||||||
|
return schedule.getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
"id": "SYSTEM_USER:READ+UPDATE"
|
"id": "SYSTEM_USER:READ+UPDATE"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "SYSTEM_USER:READ+INVITE"
|
"id": "SYSTEM_USER:READ+INVITE",
|
||||||
|
"name": "permission.system_user.invite"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "SYSTEM_USER:READ+DELETE"
|
"id": "SYSTEM_USER:READ+DELETE"
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
package io.metersphere.system.controller;
|
||||||
|
|
||||||
|
import io.metersphere.sdk.constants.ScheduleResourceType;
|
||||||
|
import io.metersphere.sdk.constants.ScheduleType;
|
||||||
|
import io.metersphere.system.base.BaseTest;
|
||||||
|
import io.metersphere.system.domain.Schedule;
|
||||||
|
import io.metersphere.system.dto.request.ScheduleConfig;
|
||||||
|
import io.metersphere.system.job.ApiScenarioScheduleJob;
|
||||||
|
import io.metersphere.system.schedule.ScheduleService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
import org.quartz.JobKey;
|
||||||
|
import org.quartz.TriggerKey;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
public class ScheduleControllerTests extends BaseTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ScheduleService scheduleService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
Schedule schedule = new Schedule();
|
||||||
|
schedule.setName("test-schedule");
|
||||||
|
schedule.setResourceId("test-resource-id");
|
||||||
|
schedule.setEnable(true);
|
||||||
|
schedule.setValue("0 0/1 * * * ?");
|
||||||
|
schedule.setKey("test-resource-id");
|
||||||
|
schedule.setCreateUser("admin");
|
||||||
|
schedule.setProjectId(DEFAULT_PROJECT_ID);
|
||||||
|
schedule.setConfig("{}");
|
||||||
|
schedule.setJob(ApiScenarioScheduleJob.class.getName());
|
||||||
|
schedule.setType(ScheduleType.CRON.name());
|
||||||
|
schedule.setResourceType(ScheduleResourceType.API_IMPORT.name());
|
||||||
|
|
||||||
|
scheduleService.addSchedule(schedule);
|
||||||
|
scheduleService.getSchedule(schedule.getId());
|
||||||
|
scheduleService.editSchedule(schedule);
|
||||||
|
scheduleService.getScheduleByResource(schedule.getResourceId(), schedule.getJob());
|
||||||
|
scheduleService.deleteByResourceId(schedule.getResourceId(), schedule.getJob());
|
||||||
|
schedule = new Schedule();
|
||||||
|
schedule.setName("test-schedule-1");
|
||||||
|
schedule.setResourceId("test-resource-id-1");
|
||||||
|
schedule.setEnable(true);
|
||||||
|
schedule.setValue("0 0/1 * * * ?");
|
||||||
|
schedule.setKey("test-resource-id-1");
|
||||||
|
schedule.setCreateUser("admin");
|
||||||
|
schedule.setProjectId(DEFAULT_PROJECT_ID);
|
||||||
|
schedule.setConfig("{}");
|
||||||
|
schedule.setJob(ApiScenarioScheduleJob.class.getName());
|
||||||
|
schedule.setType(ScheduleType.CRON.name());
|
||||||
|
schedule.setResourceType(ScheduleResourceType.API_SCENARIO.name());
|
||||||
|
scheduleService.addSchedule(schedule);
|
||||||
|
scheduleService.deleteByResourceIds(List.of(schedule.getResourceId()), schedule.getJob());
|
||||||
|
schedule = new Schedule();
|
||||||
|
schedule.setName("test-schedule-2");
|
||||||
|
schedule.setResourceId("test-resource-id-2");
|
||||||
|
schedule.setEnable(true);
|
||||||
|
schedule.setValue("0 0/1 * * * ?");
|
||||||
|
schedule.setKey("test-resource-id-2");
|
||||||
|
schedule.setCreateUser("admin");
|
||||||
|
schedule.setProjectId(DEFAULT_PROJECT_ID);
|
||||||
|
schedule.setConfig("{}");
|
||||||
|
schedule.setJob("test-job");
|
||||||
|
schedule.setType(ScheduleType.CRON.name());
|
||||||
|
schedule.setResourceType(ScheduleResourceType.API_SCENARIO.name());
|
||||||
|
scheduleService.addSchedule(schedule);
|
||||||
|
scheduleService.addOrUpdateCronJob(schedule,
|
||||||
|
new JobKey(schedule.getResourceId(), ApiScenarioScheduleJob.class.getName()),
|
||||||
|
new TriggerKey(schedule.getResourceId(), ApiScenarioScheduleJob.class.getName()),
|
||||||
|
ApiScenarioScheduleJob.class);
|
||||||
|
scheduleService.deleteByProjectId(schedule.getProjectId());
|
||||||
|
|
||||||
|
ScheduleConfig scheduleConfig = ScheduleConfig.builder()
|
||||||
|
.resourceId("test-resource-id-3")
|
||||||
|
.key("test-resource-id-3")
|
||||||
|
.projectId(DEFAULT_PROJECT_ID)
|
||||||
|
.name("test-schedule-3")
|
||||||
|
.enable(true)
|
||||||
|
.cron("0 0/1 * * * ?")
|
||||||
|
.resourceType(ScheduleResourceType.API_SCENARIO.name())
|
||||||
|
.configMap(new HashMap<>() {{
|
||||||
|
this.put("envId", "testEnv");
|
||||||
|
this.put("resourcePoolId", "testResourcePool");
|
||||||
|
}})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
scheduleService.scheduleConfig(
|
||||||
|
scheduleConfig,
|
||||||
|
new JobKey(scheduleConfig.getResourceId(), ApiScenarioScheduleJob.class.getName()),
|
||||||
|
new TriggerKey(scheduleConfig.getResourceId(), ApiScenarioScheduleJob.class.getName()),
|
||||||
|
ApiScenarioScheduleJob.class,
|
||||||
|
"admin");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package io.metersphere.system.job;
|
||||||
|
|
||||||
|
import io.metersphere.system.schedule.BaseScheduleJob;
|
||||||
|
import org.quartz.JobExecutionContext;
|
||||||
|
import org.quartz.JobKey;
|
||||||
|
import org.quartz.TriggerKey;
|
||||||
|
|
||||||
|
public class ApiScenarioScheduleJob extends BaseScheduleJob {
|
||||||
|
@Override
|
||||||
|
protected void businessExecute(JobExecutionContext context) {
|
||||||
|
/*
|
||||||
|
TODO to Chen Jianxing:
|
||||||
|
这里需要补充执行逻辑
|
||||||
|
记得执行信息(环境、资源池、是否失败停止等配置)在jobDataMap里面
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JobKey getJobKey(String scenarioId) {
|
||||||
|
return new JobKey(scenarioId, ApiScenarioScheduleJob.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TriggerKey getTriggerKey(String scenarioId) {
|
||||||
|
return new TriggerKey(scenarioId, ApiScenarioScheduleJob.class.getName());
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import io.metersphere.sdk.constants.ModuleConstants;
|
||||||
import io.metersphere.sdk.constants.TestPlanConstants;
|
import io.metersphere.sdk.constants.TestPlanConstants;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.Max;
|
import jakarta.validation.constraints.Max;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -58,6 +59,7 @@ public class TestPlanCreateRequest {
|
||||||
|
|
||||||
@Schema(description = "测试计划通过阈值;0-100", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "测试计划通过阈值;0-100", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
@Max(value = 100, message = "{test_plan.pass_threshold.max}")
|
@Max(value = 100, message = "{test_plan.pass_threshold.max}")
|
||||||
|
@Min(value = 0)
|
||||||
private double passThreshold = 100;
|
private double passThreshold = 100;
|
||||||
@Schema(description = "测试计划类型")
|
@Schema(description = "测试计划类型")
|
||||||
private String type = TestPlanConstants.TEST_PLAN_TYPE_PLAN;
|
private String type = TestPlanConstants.TEST_PLAN_TYPE_PLAN;
|
||||||
|
|
|
@ -619,7 +619,7 @@ public class TestPlanTests extends BaseTest {
|
||||||
3.group_id
|
3.group_id
|
||||||
3.1 group_id不存在
|
3.1 group_id不存在
|
||||||
3.2 group_id对应的测试计划type不是group
|
3.2 group_id对应的测试计划type不是group
|
||||||
4.参数校验:passThreshold大于100
|
4.参数校验:passThreshold大于100 、 小于0
|
||||||
5.重名校验
|
5.重名校验
|
||||||
*/
|
*/
|
||||||
request.setName(null);
|
request.setName(null);
|
||||||
|
@ -633,6 +633,8 @@ public class TestPlanTests extends BaseTest {
|
||||||
request.setGroupId(TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID);
|
request.setGroupId(TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID);
|
||||||
request.setPassThreshold(100.111);
|
request.setPassThreshold(100.111);
|
||||||
this.requestPost(URL_POST_TEST_PLAN_ADD, request).andExpect(status().isBadRequest());
|
this.requestPost(URL_POST_TEST_PLAN_ADD, request).andExpect(status().isBadRequest());
|
||||||
|
request.setPassThreshold(-0.12);
|
||||||
|
this.requestPost(URL_POST_TEST_PLAN_ADD, request).andExpect(status().isBadRequest());
|
||||||
|
|
||||||
//测试权限
|
//测试权限
|
||||||
request.setProjectId(DEFAULT_PROJECT_ID);
|
request.setProjectId(DEFAULT_PROJECT_ID);
|
||||||
|
|
Loading…
Reference in New Issue