概述
本文介绍如何把Quartz定时任务做成接口,实现以下功能的动态配置:
- 添加任务
- 修改任务
- 暂停任务
- 恢复任务
- 删除任务
- 任务列表
- 任务详情
注:添加任务接口仍然需要开发者提前准备好任务类,接口的目的是实现定时任务的动态调整,按需进行开关和修改,请注意这点。
Spring Boot整合Quartz
简单说下Quartz的整合,做一下准备工作。
导入Quartz依赖
org.springframework.boot
spring-boot-starter-quartz
配置文件中增加Quartz的支持
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: xxx
username: xxx
password: xxx
quartz:
job-store-type: jdbc # 定时任务的数据保存到jdbc即数据库中
jdbc:
# embedded:默认
# always:启动的时候初始化表,我们只在第一次启动的时候用它来自动创建表,然后改回embedded即可,不然数据每次都会被清空
# never:启动的时候不初始化表,也不知道和embedded有什么不同
initialize-schema: embedded
第一次启动的时候请把上面的initialize-schema设置为always,这会在数据库里面自动建表,然后第二次启动时改回embedded即可。
如果不需要定时任务的持久化就可以不管。
写一个测试用的任务类
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Component
public class QuartzTestJob extends QuartzJobBean {
@Override
protected void executeInternal(org.quartz.JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("Quartz Test Job");
}
}
为这个任务类写一个配置类
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzTestJobConfig {
@Bean
public JobDetail quartzTestJobDetail() {
return JobBuilder.newJob(QuartzTestJob.class)
.withIdentity(QuartzTestJob.class.getSimpleName())
.storeDurably()
.usingJobData("data", "test")
.build();
}
@Bean
public Trigger quartzTestJobTrigger() {
// 0/1 * * * * ?
return TriggerBuilder.newTrigger()
.forJob(QuartzTestJob.class.getSimpleName())
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
.build();
}
}
结论
以上是使用Quartz写一个定时任务的步骤,很简单,问题是配置写死了,没有办法动态调整,所以我们开始写接口,把上面这个任务配置类去掉。
定时任务动态配置实现
我们还是用上面的任务类QuartzTestJob做测试,这里再说明一次,我们需要有一个任务类作为基础,本文的目的只是去掉上面的QuartzTestJobConfig。
剩下的内容没有什么需要多说明的,我直接贴代码了。
业务层
public interface QuartzService {
/**
* 添加定时任务
*/
void addJob(QuartzCreateParam param) throws SchedulerException;
/**
* 修改定时任务
*/
void updateJob(QuartzUpdateParam param) throws SchedulerException;
/**
* 暂停定时任务
*/
void pauseJob(QuartzDetailParam param) throws SchedulerException;
/**
* 恢复定时任务
*/
void resumeJob(QuartzDetailParam param) throws SchedulerException;
/**
* 删除定时任务
*/
void deleteJob(QuartzDetailParam param) throws SchedulerException;
/**
* 定时任务列表
* @return
*/
List jobList() throws SchedulerException;
/**
* 定时任务详情
*/
QuartzJobDetailDto jobDetail(QuartzDetailParam param) throws SchedulerException;
}
业务层实现
@Service
public class QuartzServiceImpl implements QuartzService {
@Autowired
private Scheduler scheduler;
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
@Override
public void addJob(QuartzCreateParam param) throws SchedulerException {
String clazzName = param.getJobClazz();
String jobName = param.getJobName();
String jobGroup = param.getJobGroup();
String cron = param.getCron();
String description = param.getDescription();
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
checkJobExist(jobKey);
Class triggerBuilder = TriggerBuilder.newTrigger()
.withSchedule(scheduleBuilder)
.withIdentity(triggerId);
Trigger trigger = triggerBuilder.build();
scheduler.rescheduleJob(triggerKey, trigger);
}
@Override
public void pauseJob(QuartzDetailParam param) throws SchedulerException {
String jobName = param.getJobName();
String jobGroup = param.getJobGroup();
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
checkJobExist(jobKey);
scheduler.pauseJob(jobKey);
}
@Override
public void resumeJob(QuartzDetailParam param) throws SchedulerException {
String jobName = param.getJobName();
String jobGroup = param.getJobGroup();
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
checkJobExist(jobKey);
scheduler.resumeJob(jobKey);
}
@Override
public void deleteJob(QuartzDetailParam param) throws SchedulerException {
String jobName = param.getJobName();
String jobGroup = param.getJobGroup();
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
checkJobExist(jobKey);
// 先暂停再删除
scheduler.pauseJob(jobKey);
scheduler.deleteJob(jobKey);
}
@Override
public List jobList() throws SchedulerException {
GroupMatcher matcher = GroupMatcher.anyJobGroup();
List jobDtoList = new ArrayList();
for (JobKey jobKey : scheduler.getJobKeys(matcher)) {
QuartzJobDetailDto jobDto = getJobDtoByJobKey(jobKey);
jobDtoList.add(jobDto);
}
return jobDtoList;
}
@Override
public QuartzJobDetailDto jobDetail(QuartzDetailParam param) throws SchedulerException {
String jobName = param.getJobName();
String jobGroup = param.getJobGroup();
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
return getJobDtoByJobKey(jobKey);
}
/*************** 私有方法 ***************/
private void checkJobExist(JobKey jobKey) throws SchedulerException {
if (!scheduler.checkExists(jobKey)) {
throw new BaseException("该定时任务不存在:" + jobKey.getName());
}
}
public QuartzJobDetailDto getJobDtoByJobKey(JobKey jobKey) throws SchedulerException {
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
List triggerList = (List) scheduler.getTriggersOfJob(jobKey);
QuartzJobDetailDto jobDto = new QuartzJobDetailDto();
jobDto.setJobClazz(jobDetail.getJobClass().toString());
jobDto.setJobName(jobKey.getName());
jobDto.setJobGroup(jobKey.getGroup());
jobDto.setJobDataMap(jobDetail.getJobDataMap());
List triggerDtoList = new ArrayList();
for (Trigger trigger : triggerList) {
QuartzTriggerDetailDto triggerDto = new QuartzTriggerDetailDto();
triggerDto.setTriggerName(trigger.getKey().getName());
triggerDto.setTriggerGroup(trigger.getKey().getGroup());
triggerDto.setDescription(trigger.getDescription());
if (trigger instanceof CronTriggerImpl) {
CronTriggerImpl cronTriggerImpl = (CronTriggerImpl) trigger;
String cronExpression = cronTriggerImpl.getCronExpression();
triggerDto.setCron(cronExpression);
// 最近10次的触发时间
List dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null, 10);
triggerDto.setRecentFireTimeList(dates);
}
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
triggerDto.setTriggerState(triggerState.toString());
triggerDtoList.add(triggerDto);
}
jobDto.setTriggerDetailDtoList(triggerDtoList);
return jobDto;
}
}
接口层
@RestController
public class QuartzController {
@Autowired
private QuartzServiceImpl quartzService;
@PostMapping("/quartz/addJob")
public void addJob(@RequestBody QuartzCreateParam param) throws SchedulerException {
quartzService.addJob(param);
}
@PostMapping("/quartz/updateJob")
public void updateJob(@RequestBody QuartzUpdateParam param) throws SchedulerException {
quartzService.updateJob(param);
}
@PostMapping("/quartz/pauseJob")
public void pauseJob(@RequestBody QuartzDetailParam param) throws SchedulerException {
quartzService.pauseJob(param);
}
@PostMapping("/quartz/resumeJob")
public void resumeJob(@RequestBody QuartzDetailParam param) throws SchedulerException {
quartzService.resumeJob(param);
}
@PostMapping("/quartz/deleteJob")
public void deleteJob(@RequestBody QuartzDetailParam param) throws SchedulerException {
quartzService.deleteJob(param);
}
@PostMapping("/quartz/jobList")
public List jobList() throws SchedulerException {
return quartzService.jobList();
}
@PostMapping("/quartz/jobDetail")
public QuartzJobDetailDto jobDetail(@RequestBody QuartzDetailParam param) throws SchedulerException {
return quartzService.jobDetail(param);
}
}
接口请求参数
@ApiModel(value = "Quartz任务添加请求参数")
public class QuartzCreateParam extends BaseParam {
@NotBlank(message = "任务类不能为空")
@ApiModelProperty(value = "任务类路径", required = true)
private String jobClazz;
@NotBlank(message = "任务类名不能为空")
@ApiModelProperty(value = "任务类名", required = true)
private String jobName;
/**
* 组名+任务类key组成唯一标识,所以如果这个参数为空,那么默认以任务类key作为组名
*/
@ApiModelProperty(value = "任务组名,命名空间")
private String jobGroup;
@ApiModelProperty(value = "任务数据")
private Map jobDataMap;
@ApiModelProperty(value = "cron表达式")
private String cron;
@ApiModelProperty(value = "描述")
private String description;
}
@ApiModel(value = "Quartz任务更新请求参数")
public class QuartzUpdateParam extends BaseParam {
@NotBlank(message = "任务类名不能为空")
@ApiModelProperty(value = "任务类名", required = true)
private String jobName;
@ApiModelProperty(value = "任务组名,命名空间")
private String jobGroup;
@ApiModelProperty(value = "cron表达式")
private String cron;
}
@ApiModel(value = "Quartz任务详情请求参数")
public class QuartzDetailParam extends BaseParam {
@NotBlank(message = "任务类名不能为空")
@ApiModelProperty(value = "任务类名", required = true)
private String jobName;
@ApiModelProperty(value = "任务组名,命名空间")
private String jobGroup;
}
接口返回结果类
@ApiModel(value = "Quartz定时任务详情类")
public class QuartzJobDetailDto {
@ApiModelProperty(value = "任务类路径")
private String jobClazz;
@ApiModelProperty(value = "任务类名")
private String jobName;
@ApiModelProperty(value = "任务组名,命名空间")
private String jobGroup;
@ApiModelProperty(value = "任务数据")
private Map jobDataMap;
@ApiModelProperty(value = "触发器列表")
private List triggerDetailDtoList;
}
@ApiModel(value = "Quartz定时任务触发器详情类")
public class QuartzTriggerDetailDto {
private String triggerName;
private String triggerGroup;
private String cron;
private String description;
private String triggerState;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private List recentFireTimeList;
}
调用接口进行测试
写完接口代码后,我们来测试一下
添加任务接口:/quartz/addJob
{
"jobClazz": "com.cc.job.QuartzTestJob",
"jobName": "QuartzTestJob",
"cron": "1/2 * * * * ? ",
"description": "测试定时任务",
"jobDataMap": {
"key": "value"
}
}
修改任务接口:/quartz/updateJob
{
"jobName": "QuartzTestJob",
"cron": "0/2 * * * * ?"
}
修改任务只能修改cron时间,如果想要修改其他内容,只能删除任务后重新添加。
删除任务接口:/quartz/updateJob
{
"jobName": "QuartzTestJob"
}
暂停、恢复、详情接口同删除任务接口的请求参数,就不赘述了。
任务列表:/quartz/jobList
{}
返回结果:
{
"code": 10000,
"msg": "请求成功",
"data": [
{
"jobClazz": "class com.cc.job.QuartzTestJob",
"jobName": "QuartzTestJob",
"jobGroup": "DEFAULT",
"jobDataMap": {
"key": "value"
},
"triggerDetailDtoList": [
{
"triggerName": "DEFAULTQuartzTestJob",
"triggerGroup": "DEFAULT",
"cron": "0/2 * * * * ?",
"description": null,
"triggerState": "NORMAL",
"recentFireTimeList": [
"2023-07-19 09:23:16",
"2023-07-19 09:23:18",
"2023-07-19 09:23:20",
"2023-07-19 09:23:22",
"2023-07-19 09:23:24",
"2023-07-19 09:23:26",
"2023-07-19 09:23:28",
"2023-07-19 09:23:30",
"2023-07-19 09:23:32",
"2023-07-19 09:23:34"
]
}
]
}
],
"traceId": null,
"success": true
}