Quartz定时任务框架经常用于系统后台业务异步处理。平常我们使用时,主要是通过手工编写配置代码文件方式添加修改定时任务,然后重启系统。有时候我们需要根据业务运营需要,动态添加修改定时任务,比如添加新的定时任务、修改任务执行时间、暂停定时任务、删除定时任务等,并且监控定时任务状态,而又不想重启系统,这时就需要系统具备动态管理定时任务的功能。
Quartz提供了一系列组件,支持动态管理定时任务的功能。
Quartz定时任务主要由Scheduler、JobDetail、CronTrigger、Cron组成,实现动态管理定时任务,主要就是通过管理上述对象来实现的。
1、数据库设计
主要将我们平时配置的任务计划放入数据库中保存。在启动任务是,从数据库中查找任务计划信息,并动态配置进去即可。
DROP TABLE IF EXISTS `cc_task_info`;
CREATE TABLE `cc_task_info` (
`TID` int(11) NOT NULL AUTO_INCREMENT,
`TASK_ANME` varchar(50) NOT NULL,
`TASK_CODE` varchar(50) NOT NULL,
`JOB_CLASS` varchar(200) NOT NULL,
`JOB_GROUP` varchar(50) NOT NULL,
`CRON` varchar(50) NOT NULL,
`DEL_STATUS` varchar(2) DEFAULT '1' NULL,
`CRT_TIME` datetime DEFAULT NULL,
PRIMARY KEY (`TID`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='定时任务管理表';
DROP TABLE IF EXISTS `cc_task_record`;
CREATE TABLE `cc_task_record` (
`RID` int(11) NOT NULL AUTO_INCREMENT,
`TASK_CODE` varchar(50) NOT NULL,
`RUN_TIME` datetime NOT NULL,
`RUN_CODE` char(1) NOT NULL,
`RUN_MSG` varchar(100) NULL,
PRIMARY KEY (`RID`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='定时任务运行记录表';
DROP TABLE IF EXISTS `cc_task_status`;
CREATE TABLE `cc_task_status` (
`TASK_CODE` varchar(50) NOT NULL,
`TASK_STATUS` varchar(10) NOT NULL,
`LST_SUCC_TIME` datetime NOT NULL,
`LST_TIME` datetime NOT NULL,
PRIMARY KEY (`TASK_CODE`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='定时任务运行状态表';
2、定时任务管理
定时任务管理主要是通过Scheduler的方法来实现。Scheduler提供了一系列方法来管理定时任务的执行状态。主要包括:
scheduleJob():添加定时任务
rescheduleJob():修改定时任务
pauseJob():暂停定时任务执行
resumeJob():恢复定时任务执行
deleteJob():删除定时任务执行
针对上述方法,我们只需要传入对应参数即可。
这里我建了一个QuartzService来管理定时任务,供业务层调用。
详细代码如下:
/**
* 定时任务管理服务
*/
@Service
public class QuartzService {
public static String SCHEDULER_OPR_START = "start";
public static String SCHEDULER_OPR_PAUSE = "pause";
public static String SCHEDULER_OPR_RESUME = "resume";
public static String SCHEDULER_OPR_REMOVE = "remove";
@Autowired
private Scheduler scheduler;
/**
* 启动任务
*/
public void startJob(String taskCode, String taskAnme, String cron, String jobGroup,
String className) throws Exception{
Class<Job> jobClass = null;
try {
jobClass = (Class<Job>) Class.forName(className);//获取任务执行类
} catch (ClassNotFoundException e) {
throw new Exception("任务类不存在");
}
//创建job,指定job名称和分组
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(taskCode, jobGroup).build();
//创建表达式工作计划
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
//创建触发器
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(taskCode, jobGroup)
.withSchedule(cronScheduleBuilder).build();
scheduler.scheduleJob(jobDetail, cronTrigger);
}
/**
* 修改定时任务执行时间
* @param taskCode
* @param jobGroup
* @param cron 新的时间
* @throws Exception
*/
public void modifyJob(String taskCode, String jobGroup, String cron) throws Exception{
TriggerKey triggerKey = new TriggerKey(taskCode, jobGroup);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
String oldCron = trigger.getCronExpression();
if(!oldCron.equals(cron)){
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(taskCode, jobGroup)
.withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
Date date = scheduler.rescheduleJob(triggerKey, cronTrigger);
if(date == null){
throw new Exception("修改定时任务执行时间报错");
}
}
}
/**
* 暂停某个定时任务(任务恢复后,暂停时间段内未执行的任务会继续执行,如暂停时间段内有2次,则会执行2次)
* @param taskCode
* @param jobGroup
* @throws Exception
*/
public void pauseJob(String taskCode, String jobGroup) throws Exception{
JobKey jobKey = new JobKey(taskCode, jobGroup);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if(jobDetail == null){
return;
}
scheduler.pauseJob(jobKey);
}
/**
* 恢复某个定时任务
* @param taskCode
* @param jobGroup
* @throws Exception
*/
public void resumeJob(String taskCode, String jobGroup) throws Exception{
JobKey jobKey = new JobKey(taskCode, jobGroup);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if(jobDetail == null){
return;
}
scheduler.resumeJob(jobKey);
}
/**
* 删除某个定时任务
* @param taskCode
* @param jobGroup
* @throws Exception
*/
public void deleteJob(String taskCode, String jobGroup) throws Exception{
JobKey jobKey = new JobKey(taskCode, jobGroup);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if(jobDetail == null){
return;
}
scheduler.deleteJob(jobKey);
}
}
3、编写任务类JOB
任务类JOB就是定时任务具体要处理的系统业务逻辑,需要实现Job接口。在任务启动时,通过jobClass传入JobDetail。
public class DemoJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String taskCode = jobExecutionContext.getJobDetail().getKey().getName();
System.out.println("执行定时任务:" + taskCode);
}
}
4、配置Scheduler
在Configuration中配置Scheduler实例,并启动。
@Configuration
public class QuartzConfig {
@Bean
public Scheduler scheduler(){
Scheduler scheduler = null;
SchedulerFactory factory = new StdSchedulerFactory();
try {
scheduler = factory.getScheduler();
} catch (SchedulerException e) {
e.printStackTrace();
}
if(scheduler != null){
try {
//启动定时任务
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
return scheduler;
}
}
5、编写API接口
通过Controller提供API接口,这里我的TaskService调用了QartzService的对应接口,并做了一个写数据库读写操作,主要记录定时任务状态、执行记录信息的等。
@RestController
@RequestMapping("/api/task")
public class TaskController {
@Autowired
private TaskService service;
@RequestMapping("/start")
public Object start(int id){
try {
service.startJob(id);
return RtnData.ok();
} catch (Exception e) {
return RtnData.fail(e.getMessage());
}
}
@RequestMapping("/pause")
public Object pause(int id){
try {
service.pauseJob(id);
return RtnData.ok();
} catch (Exception e) {
return RtnData.fail(e.getMessage());
}
}
@RequestMapping("/resume")
public Object resume(int id){
try {
service.resumeJob(id);
return RtnData.ok();
} catch (Exception e) {
return RtnData.fail(e.getMessage());
}
}
@RequestMapping("/remove")
public Object remove(int id){
try {
service.deleteJob(id);
return RtnData.ok();
} catch (Exception e) {
return RtnData.fail(e.getMessage());
}
}
}
6、接口测试
先在数据库中将DemoJob添加到任务管理表中,然后使用postman调用api进行任务启动、修改、暂停、恢复、删除等操作,观察系统后台日志打印情况查看效果。
数据库初始化如下: