在日常项目研发中,定时任务可谓是必不可少的一环,如果面对任务执行周期固定,业务简单的场景,可直接使用 Spring Boot 内置注解方式实现任务;而如果考虑更为复杂的管理任务信息,在可以通过集成 Quartz 等开源轮子来助力业务研发。
本次主要分享一下 Spring Boot 集成 Quartz 任务框架后,如何实现任务的动态管理,更能够让研发人员专注业务任务的研发,那么就要逐一解决如下疑问。
疑问:是否可以通过 API 动态创建任务呢?
疑问:是否可以通过 API 编辑任务的执行时间呢?
疑问:是否可以通过 API 暂停/恢复任务呢?
疑问:是否可以通过 API 删除任务呢?
疑问:是否可以通过页面完成任务的 CRUD 呢?
考虑到下面的操作是一个大工程,为了方便,重新开启一个 Spring Boot 项目,为了进一步熟练使用 Spring Boot 相关各种 starter,本次选用 MyBatis 作为持久层框架。
1. 核心管理代码
1.1 任务控制器
定义 TaskController,提供用户操作任务的相关 API,例如查询任务列表、添加任务、暂停任务、恢复任务、删除任务。
package com.example.demo.quartz.controller;
import com.example.demo.quartz.common.Result;
import com.example.demo.quartz.service.TaskInfoService;
import com.example.demo.quartz.vo.TaskInfoReq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 定时任务管理
**/
@RestController
@RequestMapping("/task")
public class TaskController {
@Autowired
private TaskInfoService taskInfoService;
/**定时器列表*/
@PostMapping("/list")
public Result list(@RequestBody TaskInfoReq reqVo) {
return taskInfoService.selectTaskListByPage(reqVo);
}
/**定时器修改*/
@PostMapping("/edit")
public Result edit(@RequestBody TaskInfoReq reqVo) {
return taskInfoService.updateJob(reqVo);
}
/**暂停任务*/
@PostMapping("/pause")
public Result pause(Integer taskId) {
return taskInfoService.pauseJob(taskId);
}
/**增加任务*/
@PostMapping("/add")
public Result add(@RequestBody TaskInfoReq taskInfoReq) {
return taskInfoService.addJob(taskInfoReq);
}
/**恢复任务*/
@PostMapping("/resume")
public Result resume(Integer taskId) {
return taskInfoService.resumeJob(taskId);
}
/**删除任务*/
@PostMapping("/del")
public Result delete(@RequestBody TaskInfoReq reqVo) {
return taskInfoService.delete(reqVo);
}
}
1.2 任务管理
TaskManager 任务管理器,主要接收业务指令,来完成对 Quartz 容器进行操作。
package com.example.demo.quartz.task;
import com.example.demo.quartz.entity.TaskInfo;
import com.example.demo.quartz.utils.SpringContextUtils;
import com.example.demo.quartz.vo.TaskInfoReq;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 任务管理
* 1、添加任务 2、更新任务 3、暂停任务 4、恢复任务
**/
@Component
public class TaskManager {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskManager.class);
public static final String JOB_DEFAULT_GROUP_NAME = "JOB_DEFAULT_GROUP_NAME";
public static final String TRIGGER_DEFAULT_GROUP_NAME = "TRIGGER_DEFAULT_GROUP_NAME";
@Autowired
private Scheduler scheduler;
@Autowired
private SpringContextUtils springContextUtils;
/**
* 添加任务
*/
public boolean addJob(TaskInfoReq taskInfoReq) {
boolean flag = true;
if (!CronExpression.isValidExpression(taskInfoReq.getCron())) {
LOGGER.error("定时任务表达式有误:{}", taskInfoReq.getCron());
return false;
}
try {
String className = springContextUtils.getBean(taskInfoReq.getJobName()).getClass().getName();
JobDetail jobDetail = JobBuilder.newJob().withIdentity(new JobKey(taskInfoReq.getJobName(), JOB_DEFAULT_GROUP_NAME))
.ofType((Class<Job>) Class.forName(className))
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withSchedule(CronScheduleBuilder.cronSchedule(taskInfoReq.getCron()))
.withIdentity(new TriggerKey(taskInfoReq.getJobName(), TRIGGER_DEFAULT_GROUP_NAME))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (Exception e) {
LOGGER.error("添加定时任务异常:{}", e.getMessage(), e);
flag = false;
}
return flag;
}
/**
* 更新任务
*/
public boolean updateJob(TaskInfo taskInfo) {
boolean flag = true;
try {
JobKey jobKey = new JobKey(taskInfo.getJobName(), JOB_DEFAULT_GROUP_NAME);
TriggerKey triggerKey = new TriggerKey(taskInfo.getJobName(), TRIGGER_DEFAULT_GROUP_NAME);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if (scheduler.checkExists(jobKey) && scheduler.checkExists(triggerKey)) {
Trigger newTrigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withSchedule(CronScheduleBuilder.cronSchedule(taskInfo.getCron()))
.withIdentity(triggerKey)
.build();
scheduler.rescheduleJob(triggerKey, newTrigger);
} else {
LOGGER.info("更新任务失败,任务不存在,任务名称:{},表达式:{}", taskInfo.getJobName(), taskInfo.getCron());
}
LOGGER.info("更新任务成功,任务名称:{},表达式:{}", taskInfo.getJobName(), taskInfo.getCron());
} catch (SchedulerException e) {
LOGGER.error("更新定时任务失败:{}", e.getMessage(), e);
flag = false;
}
return flag;
}
/**
* 暂停任务
*/
public boolean pauseJob(TaskInfo taskInfo) {
try {
scheduler.pauseJob(JobKey.jobKey(taskInfo.getJobName(), JOB_DEFAULT_GROUP_NAME));
LOGGER.info("任务暂停成功:{}", taskInfo.getId());
return true;
} catch (SchedulerException e) {
LOGGER.error("暂停定时任务失败:{}", e.getMessage(), e);
return false;
}
}
/**
* 恢复任务
*/
public boolean resumeJob(TaskInfo taskInfo) {
try {
scheduler.resumeJob(JobKey.jobKey(taskInfo.getJobName(), JOB_DEFAULT_GROUP_NAME));
LOGGER.info("任务恢复成功:{}", taskInfo.getId());
return true;
} catch (SchedulerException e) {
LOGGER.error("恢复定时任务失败:{}", e.getMessage(), e);
return false;
}
}
}
1.3 启动管理
1.3.1 QuartzManager
Spring Boot 容器启动时,加载启动所有任务。
package com.example.demo.quartz.config;
import com.example.demo.quartz.common.EnumTaskEnable;
import com.example.demo.quartz.entity.TaskInfo;
import com.example.demo.quartz.service.TaskInfoService;
import com.example.demo.quartz.vo.TaskInfoReq;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Com