Springboot 2.x 整合 quartz 定时任务 实现动态添加、暂停、删除等功能
2019-10-17
阅读 {{counts.readCount}}
评论 {{counts.commentCount}}
## 简介
Springboot 2 整合 quartz 定时任务
(非原创,全文都是参考的简书作者的代码)
记录一下折腾的过程,防止以后忘记
参考:[https://www.jianshu.com/p/b9955ee663b5](https://www.jianshu.com/p/b9955ee663b5)
## 基本代码
### pom.xml
```xml
<!-- quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
```
### 数据库 sys_schedule_job
```sql
CREATE TABLE `sys_schedule_job` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '任务id',
`job_name` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '任务名称',
`cron_expression` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT 'cron表达式',
`bean_name` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '服务名称',
`method_name` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '方法名称',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态 0 启动 1 停止',
`user_id` int(11) NOT NULL COMMENT '创建人id 关联sys_user',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '软删除标记 0 正常 1 删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='定时任务';
```
### sys_schedule_job 表对应的类
包括:
`SysScheduleJob.java`,
`ISysScheduleJobService.java`,
`SysScheduleJobServiceImpl.java`,
`SysScheduleJobMapper.java`,
`SysScheduleJobMapper.xml`
(由mybatis plus生成,这里就全部略过了... )
### 配置 QuartzConfig.java
```java
@Configuration
public class QuartzConfig {
@Bean
public SchedulerFactoryBean schedulerFactoryBean(){
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
//覆盖已存在的任务
schedulerFactoryBean.setOverwriteExistingJobs(true);
//延时60秒启动定时任务,避免系统未完全启动却开始执行定时任务的情况
schedulerFactoryBean.setStartupDelay(60);
return schedulerFactoryBean;
}
/** 创建schedule **/
@Bean(name = "scheduler")
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
```
### 枚举类:JobOperateEnum.java
```java
public enum JobOperateEnum {
START(0, "启动"),
PAUSE(1, "暂停"),
DELETE(2, "删除");
private final Integer value;
private final String desc;
JobOperateEnum(final Integer value, final String desc) {
this.value = value;
this.desc = desc;
}
public Serializable getValue() {
return this.value;
}
public String getDesc() {
return this.desc;
}
public String getEnumName() {
return name();
}
}
```
### 工厂类:QuartzFactory.java
```java
public class QuartzFactory implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//获取调度数据
SysScheduleJob scheduleJob = (SysScheduleJob) jobExecutionContext.getMergedJobDataMap().get("scheduleJob");
//获取对应的Bean
Object object = SpringUtil.getBean(scheduleJob.getBeanName());
try {
//利用反射执行对应方法
Method method = object.getClass().getMethod(scheduleJob.getMethodName());
method.invoke(object);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
### 服务层 IQuartzService.java
```java
public interface IQuartzService {
/**
* 服务器启动执行定时任务
*/
void timingTask();
/**
* 新增定时任务
* @param job 任务
*/
void addJob(SysScheduleJob job);
/**
* 操作定时任务
* @param jobOperateEnum 操作枚举
* @param job 任务
*/
void operateJob(JobOperateEnum jobOperateEnum, SysScheduleJob job) throws SchedulerException;
/**
* 启动所有任务
*/
void startAllJob() throws SchedulerException;
/**
* 暂停所有任务
*/
void pauseAllJob() throws SchedulerException;
}
```
### 服务层实现类 QuartzServiceImpl.java
```java
@Service
public class QuartzServiceImpl implements IQuartzService {
/**
* 调度器
*/
@Autowired
private Scheduler scheduler;
@Autowired
private ISysScheduleJobService jobService;
@Override
public void timingTask() {
//查询数据库是否存在需要定时的任务
List<SysScheduleJob> scheduleJobs = jobService.list();
if (scheduleJobs != null) {
scheduleJobs.forEach(this::addJob);
}
}
@Override
public void addJob(SysScheduleJob job) {
try {
//创建触发器
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName())
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.startNow()
.build();
//创建任务
JobDetail jobDetail = JobBuilder.newJob(QuartzFactory.class)
.withIdentity(job.getJobName())
.build();
//传入调度的数据,在QuartzFactory中需要使用
jobDetail.getJobDataMap().put("scheduleJob", job);
//调度作业
scheduler.scheduleJob(jobDetail, trigger);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void operateJob(JobOperateEnum jobOperateEnum, SysScheduleJob job) throws SchedulerException {
JobKey jobKey = new JobKey(job.getJobName());
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if (jobDetail == null) {
//抛异常
}
switch (jobOperateEnum) {
case START:
scheduler.resumeJob(jobKey);
break;
case PAUSE:
scheduler.pauseJob(jobKey);
break;
case DELETE:
scheduler.deleteJob(jobKey);
break;
}
}
@Override
public void startAllJob() throws SchedulerException {
scheduler.start();
}
@Override
public void pauseAllJob() throws SchedulerException {
scheduler.standby();
}
}
```
### JobSchedule.java 实现开机自运行
```java
/**
* 实现项目启动后自运行
*/
@Component
public class JobSchedule implements CommandLineRunner {
@Autowired
private IQuartzService taskService;
/**
* 任务调度开始
* @param strings
* @throws Exception
*/
@Override
public void run(String... strings) throws Exception {
System.out.println("任务调度开始==============任务调度开始");
taskService.timingTask();
System.out.println("任务调度结束==============任务调度结束");
}
}
```
### 目录结构
(除去了sys_schedule_job表用mybatis plus生成的5个文件)
```
├── quartz
│ ├── job
│ │ ├── Job.java
│ ├── IQuartzService.java
│ ├── JobOperateEnum.java
│ ├── JobSchedule.java
│ ├── QuartzConfig.java
│ ├── QuartzFactory.java
│ ├── QuartzServiceImpl.java
```
**以上这些都是基本款,具体实现还要加入执行任务方法类和数据库任务数据**
## 功能实现
### 静态部分
顾名思义,静态部分就是写一个方法,数据库录入一条数据,当项目启动的时候,就会触发定时任务,等到了时间就会运行。
#### Job.java
```java
@Component("Job4Log")
public class Job {
public void test() {
System.out.println("-------------------Job4Log任务执行开始-------------------");
System.out.println(new Date().toLocaleString());
System.out.println("-------------------Job4Log任务执行结束-------------------");
}
}
```
#### 数据库表 sys_schedule_job
id | job_name | cron_expression | bean_name | method_name | status | user_id | create_time | update_time | del_flag
-|-|-|-|-|-|-|-|-|-
1 | test job | 0/2 * * * * ? | Job4Log | test | 0 | 1 | 2019-08-21 16:07:53 | 2019-08-21 16:13:44 | 0
#### 效果
```java
-------------------Job任务执行开始-------------------
2019年8月21日 下午4:31:20
-------------------Job任务执行结束-------------------
-------------------Job任务执行开始-------------------
2019年8月21日 下午4:31:22
-------------------Job任务执行结束-------------------
-------------------Job任务执行开始-------------------
2019年8月21日 下午4:31:24
-------------------Job任务执行结束-------------------
-------------------Job任务执行开始-------------------
2019年8月21日 下午4:31:26
-------------------Job任务执行结束-------------------
-------------------Job任务执行开始-------------------
2019年8月21日 下午4:31:28
-------------------Job任务执行结束-------------------
-------------------Job任务执行开始-------------------
2019年8月21日 下午4:31:30
-------------------Job任务执行结束-------------------
-------------------Job任务执行开始-------------------
2019年8月21日 下午4:31:32
-------------------Job任务执行结束-------------------
-------------------Job任务执行开始-------------------
2019年8月21日 下午4:31:34
-------------------Job任务执行结束-------------------
-------------------Job任务执行开始-------------------
2019年8月21日 下午4:31:36
-------------------Job任务执行结束-------------------
```
### 动态部分
动态部分就是在静态部分的基础上,加入controller,直接通过接口来实时操作定时任务
#### ScheduleJobController.java
```java
@PostMapping("addJob")
@ApiOperation("新增定时任务")
@Transactional(rollbackFor = Exception.class)
public R add(@ApiIgnore @RequestAttribute("sysUser") SysUser user, @RequestBody SysScheduleJobForm form) {
SysScheduleJob job = new SysScheduleJob();
job.setJobName(form.getJobName());
job.setCronExpression(form.getCronExpression());
job.setBeanName(form.getBeanName());
job.setMethodName(form.getMethodName());
job.setUserId(user.getId());
job.setStatus(0);
jobService.add(job);
return R.ok();
}
@PostMapping("/startJob")
@ApiOperation("启动定时任务")
public R start(@RequestBody SysScheduleJobIdForm form) {
jobService.start(form.getId());
return R.ok();
}
@PostMapping("/pauseJob")
@ApiOperation("暂停定时任务")
public R pause(@RequestBody SysScheduleJobIdForm form) {
jobService.pause(form.getId());
return R.ok();
}
@PostMapping("/deleteJob")
@ApiOperation("删除定时任务")
public R delete(@RequestBody SysScheduleJobIdForm form) {
jobService.delete(form.getId());
return R.ok();
}
@PostMapping("/startAllJob")
@ApiOperation("开始所有定时任务")
public R startAllJob() {
jobService.startAllJob();
return R.ok();
}
@PostMapping("/pauseAllJob")
@ApiOperation("暂停所有定时任务")
public R pauseAllJob() {
jobService.pauseAllJob();
return R.ok();
}
```
#### 效果
调用接口:
```
/scheduleJob/addJob
```
传入参数:
```json
{
"beanName": "Job",
"cronExpression": "0/2 * * * * ?",
"jobName": "test job",
"methodName": "test"
}
```
返回参数:
```json
{
"message": "success",
"status": 0
}
```
控制台输出
```
-------------------Job任务执行开始-------------------
2019年8月21日 下午5:01:32
-------------------Job任务执行结束-------------------
-------------------Job任务执行开始-------------------
2019年8月21日 下午5:01:34
-------------------Job任务执行结束-------------------
-------------------Job任务执行开始-------------------
2019年8月21日 下午5:01:36
-------------------Job任务执行结束-------------------
```
## CronTrigger配置完整格式:
[秒] [分] [小时] [日] [月] [周] [年]
### 参数
1. 秒(0~59)
2. 分钟(0~59)
3. 小时(0~23)
4. 天(0~31,但是你需要考虑你月的天数)
5. 月(0~11)
6. 周(1~7)
7. 年份(1970-2099)(可省略)
### 例子
```crontab
0 0 0/1 * * ? // 每小时执行一次
0 0 12 * * ? // 每天12点触发
0 0 22 L * ? // 每月最后一天的22点触发
```
## END