增加服务器管理认证方式,增加测试连接接口

This commit is contained in:
dengqichen 2025-10-30 17:07:31 +08:00
parent 10bfa7bcbd
commit bd7733d7d7
4 changed files with 160 additions and 104 deletions

View File

@ -89,6 +89,18 @@ public class ScheduleJobApiController extends BaseController<ScheduleJob, Schedu
return Response.success();
}
/**
* 恢复启用禁用任务
*/
@Operation(summary = "启用定时任务", description = "恢复被禁用DISABLED的定时任务重新注册调度状态变为ENABLED")
@PostMapping("/{id}/enable")
public Response<Void> enableJob(
@Parameter(description = "任务ID", required = true) @PathVariable Long id
) {
scheduleJobService.enableJob(id);
return Response.success();
}
/**
* 立即触发任务
*/

View File

@ -81,7 +81,7 @@ public class ScheduleJob extends Entity<Long> {
*/
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private ScheduleJobStatusEnum status = ScheduleJobStatusEnum.ENABLED;
private ScheduleJobStatusEnum status;
/**
* 是否允许并发执行

View File

@ -41,6 +41,12 @@ public interface IScheduleJobService extends IBaseService<ScheduleJob, ScheduleJ
*/
void disableJob(Long jobId);
/**
* 恢复启用解除禁用任务
* @param jobId 任务ID
*/
void enableJob(Long jobId);
/**
* 立即执行一次任务仅限ENABLED和PAUSED状态
*

View File

@ -40,13 +40,13 @@ import java.util.stream.Collectors;
/**
* 定时任务Service实现
*
*
* @author qichen
*/
@Slf4j
@Service
public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, ScheduleJobDTO, ScheduleJobQuery, Long>
implements IScheduleJobService {
implements IScheduleJobService {
@Resource
private IScheduleJobRepository jobRepository;
@ -80,7 +80,7 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
public ScheduleJobDTO create(ScheduleJobDTO dto) {
// 检查任务名称唯一性
if (jobRepository.existsByJobNameAndDeletedFalse(dto.getJobName())) {
throw new BusinessException(ResponseCode.DATA_ALREADY_EXISTS, new Object[]{"任务名称", dto.getJobName()});
throw new BusinessException(ResponseCode.DATA_ALREADY_EXISTS, new Object[] {"任务名称", dto.getJobName()});
}
return super.create(dto);
}
@ -91,34 +91,34 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
// 1. 查询旧数据
ScheduleJob oldJob = findEntityById(id);
ScheduleJobStatusEnum oldStatus = oldJob.getStatus();
// 2. 检查是否试图修改 status强制拦截
if (dto.getStatus() != null && dto.getStatus() != oldStatus) {
throw new BusinessException(ResponseCode.SCHEDULE_JOB_STATUS_CANNOT_UPDATE,
new Object[]{"请使用 /start、/pause、/resume、/disable 接口管理任务状态"});
new Object[] {"请使用 /start、/pause、/resume、/disable 接口管理任务状态"});
}
// 3. 强制保持原状态防止前端传入
dto.setStatus(oldStatus);
// 4. 更新数据库
ScheduleJobDTO updated = super.update(id, dto);
// 5. 只有非 DISABLED 状态才需要重新调度
if (oldStatus != ScheduleJobStatusEnum.DISABLED) {
try {
JobKey jobKey = getJobKey(id);
if (scheduler.checkExists(jobKey)) {
ScheduleJob job = findEntityById(id);
// 先删除旧的Job
scheduler.deleteJob(jobKey);
// 重新创建JobDetail使用最新参数
JobDetail jobDetail = JobBuilder.newJob(DynamicJob.class)
.withIdentity(jobKey)
.withDescription(job.getJobDescription())
.build();
.withIdentity(jobKey)
.withDescription(job.getJobDescription())
.build();
// 设置最新的JobDataMap
jobDetail.getJobDataMap().put("jobId", job.getId());
@ -129,18 +129,18 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
// 重新创建Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(getTriggerKey(id))
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.build();
.withIdentity(getTriggerKey(id))
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.build();
// 重新调度
scheduler.scheduleJob(jobDetail, trigger);
// 如果原来是 PAUSED重新调度后也要暂停
if (oldStatus == ScheduleJobStatusEnum.PAUSED) {
scheduler.pauseJob(jobKey);
}
log.info("任务配置已更新并重新调度jobId={}, jobName={}", job.getId(), job.getJobName());
}
} catch (SchedulerException e) {
@ -150,7 +150,7 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
} else {
log.info("任务配置已更新DISABLED状态不调度jobId={}", id);
}
return updated;
}
@ -160,34 +160,35 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
// 填充关联信息
List<ScheduleJobDTO> content = page.getContent().stream()
.peek(job -> {
// 填充分类信息
if (job.getCategoryId() != null) {
Optional<ScheduleJobCategory> categoryOptional = categoryRepository.findById(job.getCategoryId());
categoryOptional.ifPresent(category -> job.setCategory(categoryConverter.toDto(category)));
}
.peek(job -> {
// 填充分类信息
if (job.getCategoryId() != null) {
Optional<ScheduleJobCategory> categoryOptional = categoryRepository.findById(job.getCategoryId());
categoryOptional.ifPresent(category -> job.setCategory(categoryConverter.toDto(category)));
}
// 填充表单信息
if (job.getFormDefinitionId() != null) {
Optional<FormDefinition> formOptional = formDefinitionRepository.findById(job.getFormDefinitionId());
formOptional.ifPresent(form -> job.setFormDefinition(formDefinitionConverter.toDto(form)));
}
})
.collect(Collectors.toList());
// 填充表单信息
if (job.getFormDefinitionId() != null) {
Optional<FormDefinition> formOptional = formDefinitionRepository.findById(job.getFormDefinitionId());
formOptional.ifPresent(form -> job.setFormDefinition(formDefinitionConverter.toDto(form)));
}
})
.collect(Collectors.toList());
return new PageImpl<>(content, page.getPageable(), page.getTotalElements());
}
@Override
@Transactional
public void startJob(Long jobId) {
try {
ScheduleJob job = findEntityById(jobId);
// 创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(DynamicJob.class)
.withIdentity(getJobKey(jobId))
.withDescription(job.getJobDescription())
.build();
.withIdentity(getJobKey(jobId))
.withDescription(job.getJobDescription())
.build();
// 设置JobDataMap
jobDetail.getJobDataMap().put("jobId", job.getId());
@ -198,19 +199,19 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
// 创建Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(getTriggerKey(jobId))
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.build();
.withIdentity(getTriggerKey(jobId))
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.build();
// 调度任务
scheduler.scheduleJob(jobDetail, trigger);
// 更新任务状态
job.setStatus(ScheduleJobStatusEnum.ENABLED);
jobRepository.save(job);
log.info("任务启动成功jobId={}, jobName={}", jobId, job.getJobName());
} catch (SchedulerException e) {
log.error("启动任务失败jobId={}", jobId, e);
throw new BusinessException(ResponseCode.SCHEDULE_JOB_START_FAILED);
@ -218,17 +219,18 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
}
@Override
@Transactional
public void pauseJob(Long jobId) {
try {
scheduler.pauseJob(getJobKey(jobId));
// 更新任务状态
ScheduleJob job = findEntityById(jobId);
job.setStatus(ScheduleJobStatusEnum.PAUSED);
jobRepository.save(job);
log.info("任务暂停成功jobId={}", jobId);
} catch (SchedulerException e) {
log.error("暂停任务失败jobId={}", jobId, e);
throw new BusinessException(ResponseCode.SCHEDULE_JOB_PAUSE_FAILED);
@ -236,17 +238,18 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
}
@Override
@Transactional
public void resumeJob(Long jobId) {
try {
scheduler.resumeJob(getJobKey(jobId));
// 更新任务状态
ScheduleJob job = findEntityById(jobId);
job.setStatus(ScheduleJobStatusEnum.ENABLED);
jobRepository.save(job);
log.info("任务恢复成功jobId={}", jobId);
} catch (SchedulerException e) {
log.error("恢复任务失败jobId={}", jobId, e);
throw new BusinessException(ResponseCode.SCHEDULE_JOB_RESUME_FAILED);
@ -254,15 +257,16 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
}
@Override
@Transactional
public void disableJob(Long jobId) {
ScheduleJob job = findEntityById(jobId);
// 幂等性检查
if (job.getStatus() == ScheduleJobStatusEnum.DISABLED) {
log.info("任务已经是禁用状态jobId={}, jobName={}", jobId, job.getJobName());
return;
}
try {
// Quartz 删除任务
JobKey jobKey = getJobKey(jobId);
@ -270,19 +274,53 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
scheduler.deleteJob(jobKey);
log.info("从Quartz删除任务jobId={}, jobName={}", jobId, job.getJobName());
}
// 更新数据库状态
job.setStatus(ScheduleJobStatusEnum.DISABLED);
jobRepository.save(job);
log.info("任务已禁用jobId={}, jobName={}", jobId, job.getJobName());
} catch (SchedulerException e) {
log.error("禁用任务失败jobId={}, jobName={}", jobId, job.getJobName(), e);
throw new BusinessException(ResponseCode.SCHEDULE_JOB_DISABLE_FAILED);
}
}
@Override
@Transactional
public void enableJob(Long jobId) {
ScheduleJob job = findEntityById(jobId);
if (job.getStatus() != ScheduleJobStatusEnum.DISABLED) {
throw new BusinessException(ResponseCode.SCHEDULE_JOB_STATUS_CANNOT_UPDATE, new Object[] {"仅禁用状态任务可启用"});
}
try {
// 创建并调度新Job
JobDetail jobDetail = JobBuilder.newJob(DynamicJob.class)
.withIdentity(getJobKey(jobId))
.withDescription(job.getJobDescription())
.build();
jobDetail.getJobDataMap().put("jobId", job.getId());
jobDetail.getJobDataMap().put("jobName", job.getJobName());
jobDetail.getJobDataMap().put("beanName", job.getBeanName());
jobDetail.getJobDataMap().put("methodName", job.getMethodName());
jobDetail.getJobDataMap().put("methodParams", job.getMethodParams());
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(getTriggerKey(jobId))
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.build();
scheduler.scheduleJob(jobDetail, trigger);
job.setStatus(ScheduleJobStatusEnum.ENABLED);
jobRepository.save(job);
log.info("任务从DISABLED恢复启用成功jobId={}, jobName={}", jobId, job.getJobName());
} catch (SchedulerException e) {
log.error("恢复启用任务失败jobId={}, jobName={}", jobId, job.getJobName(), e);
throw new BusinessException(ResponseCode.SCHEDULE_JOB_START_FAILED);
}
}
@Override
public void triggerJob(Long jobId) {
// 检查任务状态
@ -291,11 +329,11 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
log.warn("禁用状态的任务不允许触发jobId={}, jobName={}", jobId, job.getJobName());
throw new BusinessException(ResponseCode.SCHEDULE_JOB_DISABLED_CANNOT_TRIGGER);
}
try {
scheduler.triggerJob(getJobKey(jobId));
log.info("立即触发任务jobId={}, jobName={}", jobId, job.getJobName());
} catch (SchedulerException e) {
log.error("触发任务失败jobId={}, jobName={}", jobId, job.getJobName(), e);
throw new BusinessException(ResponseCode.SCHEDULE_JOB_TRIGGER_FAILED);
@ -307,27 +345,27 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
try {
TriggerKey triggerKey = getTriggerKey(jobId);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (trigger == null) {
throw new BusinessException(ResponseCode.SCHEDULE_JOB_TRIGGER_NOT_FOUND);
}
// 创建新的Trigger
CronTrigger newTrigger = trigger.getTriggerBuilder()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
// 重新调度
scheduler.rescheduleJob(triggerKey, newTrigger);
// 更新数据库
ScheduleJob job = findEntityById(jobId);
job.setCronExpression(cronExpression);
jobRepository.save(job);
log.info("更新任务Cron表达式成功jobId={}, cron={}", jobId, cronExpression);
} catch (SchedulerException e) {
log.error("更新任务Cron表达式失败jobId={}", jobId, e);
throw new BusinessException(ResponseCode.SCHEDULE_JOB_UPDATE_CRON_FAILED);
@ -337,9 +375,9 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
@Override
public void loadAllEnabledJobs() {
log.info("开始加载所有启用的定时任务...");
List<ScheduleJob> enabledJobs = jobRepository.findByStatusAndDeletedFalse(ScheduleJobStatusEnum.ENABLED);
for (ScheduleJob job : enabledJobs) {
try {
startJob(job.getId());
@ -348,7 +386,7 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
log.error("加载任务失败jobId={}, jobName={}", job.getId(), job.getJobName(), e);
}
}
log.info("定时任务加载完成,成功加载 {} 个任务", enabledJobs.size());
}
@ -359,63 +397,63 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJob, Schedul
long enabled = jobRepository.countByStatusAndDeletedFalse(ScheduleJobStatusEnum.ENABLED);
long disabled = jobRepository.countByStatusAndDeletedFalse(ScheduleJobStatusEnum.DISABLED);
long paused = jobRepository.countByStatusAndDeletedFalse(ScheduleJobStatusEnum.PAUSED);
// 2. 获取正在执行的任务从Redis
List<JobDashboardDTO.RunningJobDTO> runningJobs = new ArrayList<>();
List<ScheduleJob> allJobs = jobRepository.findAll();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
for (ScheduleJob job : allJobs) {
// 使用 JobStatusRedisService 读取 Hash 类型的状态数据
JobStatusDTO statusDTO = jobStatusRedisService.getJobStatus(job.getId());
if (statusDTO != null && "RUNNING".equals(statusDTO.getStatus())) {
JobDashboardDTO.RunningJobDTO runningJob = JobDashboardDTO.RunningJobDTO.builder()
.jobId(job.getId())
.jobName(job.getJobName())
.startTime(statusDTO.getStartTime() != null ?
statusDTO.getStartTime().format(formatter) : null)
.progress(statusDTO.getProgress())
.message(statusDTO.getMessage())
.status(statusDTO.getStatus())
.build();
.jobId(job.getId())
.jobName(job.getJobName())
.startTime(statusDTO.getStartTime() != null ?
statusDTO.getStartTime().format(formatter) : null)
.progress(statusDTO.getProgress())
.message(statusDTO.getMessage())
.status(statusDTO.getStatus())
.build();
runningJobs.add(runningJob);
}
}
// 3. 获取最近10条执行日志
PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "executeTime"));
Page<ScheduleJobLog> logPage = jobLogRepository.findAll(pageRequest);
List<ScheduleJobLogDTO> recentLogs = logPage.getContent().stream()
.map(log -> {
ScheduleJobLogDTO dto = new ScheduleJobLogDTO();
dto.setId(log.getId());
dto.setJobId(log.getJobId());
dto.setJobName(log.getJobName());
dto.setExecuteTime(log.getExecuteTime());
dto.setFinishTime(log.getFinishTime());
dto.setStatus(log.getStatus());
dto.setResultMessage(log.getResultMessage());
dto.setExceptionInfo(log.getExceptionInfo());
return dto;
})
.collect(Collectors.toList());
.map(log -> {
ScheduleJobLogDTO dto = new ScheduleJobLogDTO();
dto.setId(log.getId());
dto.setJobId(log.getJobId());
dto.setJobName(log.getJobName());
dto.setExecuteTime(log.getExecuteTime());
dto.setFinishTime(log.getFinishTime());
dto.setStatus(log.getStatus());
dto.setResultMessage(log.getResultMessage());
dto.setExceptionInfo(log.getExceptionInfo());
return dto;
})
.collect(Collectors.toList());
// 4. 构建返回结果
JobDashboardDTO.JobSummaryDTO summary = JobDashboardDTO.JobSummaryDTO.builder()
.total(total)
.enabled(enabled)
.disabled(disabled)
.paused(paused)
.running((long) runningJobs.size())
.build();
.total(total)
.enabled(enabled)
.disabled(disabled)
.paused(paused)
.running((long) runningJobs.size())
.build();
return JobDashboardDTO.builder()
.summary(summary)
.runningJobs(runningJobs)
.recentLogs(recentLogs)
.build();
.summary(summary)
.runningJobs(runningJobs)
.recentLogs(recentLogs)
.build();
}
/**