七、使用调度框架quartz,为12306系统增加定时调度功能

发布时间 2023-05-02 21:35:57作者: 夏雪冬蝉

为什么要有定时调度

  • 定时调度在企业级系统中非常重要(统计报表、功能补偿、不紧急的大批量任务)
  • 12306每天都需要生成15天后的车次数据

本章内容

  • 集成quartz,比较SpringBoot自带定时任务喝quartz的区别
  • 使用控台来操作定时任务:新增、暂停、重启、删除

项目中增加batch定时调度模块

参照business模块的创建

Springboot自带的定时任务的使用

corn从左到右(用空格隔开):秒、分、时、月份中的日期、月份、星期中的日期、年份

 1 /**
 2  * 适合单体应用,不适合集群
 3  * 没法实时更改定时任务状态和策略
 4  */
 5 @Component
 6 @EnableScheduling
 7 public class SpringBootTestJob {
 8//秒除以5,余数是0就触发
 9     @Scheduled(cron = "0/5 * * * * ?")
10     private void test() {
11         // 增加分布式锁,解决集群问题,如果想暂停一下做不到
12 System.out.println("SpringBootTestJob TEST"); 13 } 14 }

定时任务模块集成quartz

batch模块加入依赖

1         <dependency>
2             <groupId>org.springframework.boot</groupId>
3             <artifactId>spring-boot-starter-quartz</artifactId>
4         </dependency>

写一个quartz的配置类

 1 package com.zihans.train.batch.config;
 2 
 3 import com.zihans.train.batch.job.TestJob;
 4 import org.quartz.*;
 5 import org.springframework.context.annotation.Bean;
 6 import org.springframework.context.annotation.Configuration;
 7 
 8 @Configuration
 9 public class QuartzConfig {
10 
11     /**
12      * 声明一个任务
13      * @return
14      */
15     @Bean
16     public JobDetail jobDetail() {
17         return JobBuilder.newJob(TestJob.class)
18                 .withIdentity("TestJob", "test")
19                 .storeDurably()
20                 .build();
21     }
22 
23     /**
24      * 声明一个触发器,什么时候触发这个任务
25      * @return
26      */
27     @Bean
28     public Trigger trigger() {
29         return TriggerBuilder.newTrigger()
30                 .forJob(jobDetail())
31                 .withIdentity("trigger", "trigger")
32                 .startNow()
33                 .withSchedule(CronScheduleBuilder.cronSchedule("*/2 * * * * ?"))
34                 .build();
35     }
36 }
QuartzConfig

调度方法的实现类

 1 package com.zihans.train.batch.job;
 2 
 3 import org.quartz.Job;
 4 import org.quartz.JobExecutionContext;
 5 import org.quartz.JobExecutionException;
 6 
 7 public class TestJob implements Job {
 8 
 9     @Override
10     public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
11         System.out.println("TestJob TEST");
12     }
13 }
TestJob.java

系统增加一个定时任务,由TestJob来执行,类需要实现Job接口。在配置中声明一个任务,并由触发器来实现。

关于调度任务的并发执行

并发执行:上一个周期还没有执行完,下一周期又开始了

使用@DisallowConcurrentExecution金庸并发执行。

@Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("TestJob TEST开始");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("TestJob TEST结束");
    }

使用数据库配置quartz调度任务

数据库12张表,官方提供

  1 #
  2 # Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
  3 #
  4 # PLEASE consider using mysql with innodb tables to avoid locking issues
  5 #
  6 # In your Quartz properties file, you'll need to set
  7 # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
  8 #
  9 DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
 10 DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
 11 DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
 12 DROP TABLE IF EXISTS QRTZ_LOCKS;
 13 DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
 14 DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
 15 DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
 16 DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
 17 DROP TABLE IF EXISTS QRTZ_TRIGGERS;
 18 DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
 19 DROP TABLE IF EXISTS QRTZ_CALENDARS;
 20 CREATE TABLE QRTZ_JOB_DETAILS
 21 (
 22     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
 23     JOB_NAME  VARCHAR(200) NOT NULL  comment 'job名称',
 24     JOB_GROUP VARCHAR(200) NOT NULL  comment 'job组',
 25     DESCRIPTION VARCHAR(250) NULL  comment '描述',
 26     JOB_CLASS_NAME   VARCHAR(250) NOT NULL  comment 'job类名',
 27     IS_DURABLE VARCHAR(1) NOT NULL  comment '是否持久化',
 28     IS_NONCONCURRENT VARCHAR(1) NOT NULL  comment '是否非同步',
 29     IS_UPDATE_DATA VARCHAR(1) NOT NULL  comment '是否更新数据',
 30     REQUESTS_RECOVERY VARCHAR(1) NOT NULL  comment '请求是否覆盖',
 31     JOB_DATA BLOB NULL  comment 'job数据',
 32     PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
 33 );
 34 CREATE TABLE QRTZ_TRIGGERS
 35 (
 36     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
 37     TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称',
 38     TRIGGER_GROUP VARCHAR(200) NOT NULL  comment '触发器组',
 39     JOB_NAME  VARCHAR(200) NOT NULL  comment 'job名称',
 40     JOB_GROUP VARCHAR(200) NOT NULL  comment 'job组',
 41     DESCRIPTION VARCHAR(250) NULL  comment '描述',
 42     NEXT_FIRE_TIME BIGINT(13) NULL  comment '下一次触发时间',
 43     PREV_FIRE_TIME BIGINT(13) NULL  comment '前一次触发时间',
 44     PRIORITY INTEGER NULL  comment '等级',
 45     TRIGGER_STATE VARCHAR(16) NOT NULL  comment '触发状态',
 46     TRIGGER_TYPE VARCHAR(8) NOT NULL  comment '触发类型',
 47     START_TIME BIGINT(13) NOT NULL  comment '开始时间',
 48     END_TIME BIGINT(13) NULL  comment '结束时间',
 49     CALENDAR_NAME VARCHAR(200) NULL  comment '日程名称',
 50     MISFIRE_INSTR SMALLINT(2) NULL  comment '未触发实例',
 51     JOB_DATA BLOB NULL  comment 'job数据',
 52     PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
 53     FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
 54         REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
 55 );
 56 CREATE TABLE QRTZ_SIMPLE_TRIGGERS
 57 (
 58     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
 59     TRIGGER_NAME VARCHAR(200) NOT NULL  comment '触发器名称',
 60     TRIGGER_GROUP VARCHAR(200) NOT NULL  comment '触发器组',
 61     REPEAT_COUNT BIGINT(7) NOT NULL  comment '重复执行次数',
 62     REPEAT_INTERVAL BIGINT(12) NOT NULL  comment '重复执行间隔',
 63     TIMES_TRIGGERED BIGINT(10) NOT NULL  comment '已经触发次数',
 64     PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
 65     FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
 66         REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
 67 );
 68 CREATE TABLE QRTZ_CRON_TRIGGERS
 69 (
 70     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
 71     TRIGGER_NAME VARCHAR(200) NOT NULL  comment '触发器名称',
 72     TRIGGER_GROUP VARCHAR(200) NOT NULL  comment '触发器组',
 73     CRON_EXPRESSION VARCHAR(200) NOT NULL  comment 'cron表达式',
 74     TIME_ZONE_ID VARCHAR(80)  comment '时区',
 75     PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
 76     FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
 77         REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
 78 );
 79 CREATE TABLE QRTZ_SIMPROP_TRIGGERS
 80 (
 81     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
 82     TRIGGER_NAME VARCHAR(200) NOT NULL  comment '触发器名称',
 83     TRIGGER_GROUP VARCHAR(200) NOT NULL  comment '触发器组',
 84     STR_PROP_1 VARCHAR(512) NULL  comment '开始配置1',
 85     STR_PROP_2 VARCHAR(512) NULL  comment '开始配置2',
 86     STR_PROP_3 VARCHAR(512) NULL  comment '开始配置3',
 87     INT_PROP_1 INT NULL  comment 'int配置1',
 88     INT_PROP_2 INT NULL  comment 'int配置2',
 89     LONG_PROP_1 BIGINT NULL  comment 'long配置1',
 90     LONG_PROP_2 BIGINT NULL  comment 'long配置2',
 91     DEC_PROP_1 NUMERIC(13,4) NULL  comment '配置描述1',
 92     DEC_PROP_2 NUMERIC(13,4) NULL  comment '配置描述2',
 93     BOOL_PROP_1 VARCHAR(1) NULL  comment 'bool配置1',
 94     BOOL_PROP_2 VARCHAR(1) NULL  comment 'bool配置2',
 95     PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
 96     FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
 97         REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
 98 );
 99 CREATE TABLE QRTZ_BLOB_TRIGGERS
100 (
101     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
102     TRIGGER_NAME VARCHAR(200) NOT NULL  comment '触发器名称',
103     TRIGGER_GROUP VARCHAR(200) NOT NULL  comment '触发器组',
104     BLOB_DATA BLOB NULL  comment '数据',
105     PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
106     FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
107         REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
108 );
109 CREATE TABLE QRTZ_CALENDARS
110 (
111     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
112     CALENDAR_NAME  VARCHAR(200) NOT NULL comment '日程名称',
113     CALENDAR BLOB NOT NULL  comment '日程数据',
114     PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
115 );
116 CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
117 (
118     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
119     TRIGGER_GROUP  VARCHAR(200) NOT NULL  comment '触发器组',
120     PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
121 );
122 CREATE TABLE QRTZ_FIRED_TRIGGERS
123 (
124     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
125     ENTRY_ID VARCHAR(95) NOT NULL  comment 'entryId',
126     TRIGGER_NAME VARCHAR(200) NOT NULL  comment '触发器名称',
127     TRIGGER_GROUP VARCHAR(200) NOT NULL  comment '触发器组',
128     INSTANCE_NAME VARCHAR(200) NOT NULL  comment '实例名称',
129     FIRED_TIME BIGINT(13) NOT NULL  comment '执行时间',
130     SCHED_TIME BIGINT(13) NOT NULL  comment '定时任务时间',
131     PRIORITY INTEGER NOT NULL  comment '等级',
132     STATE VARCHAR(16) NOT NULL  comment '状态',
133     JOB_NAME VARCHAR(200) NULL  comment 'job名称',
134     JOB_GROUP VARCHAR(200) NULL  comment 'job组',
135     IS_NONCONCURRENT VARCHAR(1) NULL  comment '是否异步',
136     REQUESTS_RECOVERY VARCHAR(1) NULL  comment '是否请求覆盖',
137     PRIMARY KEY (SCHED_NAME,ENTRY_ID)
138 );
139 CREATE TABLE QRTZ_SCHEDULER_STATE
140 (
141     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
142     INSTANCE_NAME VARCHAR(200) NOT NULL  comment '实例名称',
143     LAST_CHECKIN_TIME BIGINT(13) NOT NULL  comment '最近检入时间',
144     CHECKIN_INTERVAL BIGINT(13) NOT NULL  comment '检入间隔',
145     PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
146 );
147 CREATE TABLE QRTZ_LOCKS
148 (
149     SCHED_NAME VARCHAR(120) NOT NULL  comment '定时任务名称',
150     LOCK_NAME  VARCHAR(40) NOT NULL  comment 'lock名称',
151     PRIMARY KEY (SCHED_NAME,LOCK_NAME)
152 );
batch.sql

增加两个数据库模式的quartz需要的配置类,所有项目都一样,直接复制

 1 package com.zihans.train.batch.config;
 2 
 3 import jakarta.annotation.Resource;
 4 import org.quartz.spi.TriggerFiredBundle;
 5 import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
 6 import org.springframework.scheduling.quartz.SpringBeanJobFactory;
 7 import org.springframework.stereotype.Component;
 8 
 9 @Component
10 public class MyJobFactory extends SpringBeanJobFactory {
11 
12     @Resource
13     private AutowireCapableBeanFactory beanFactory;
14 
15     /**
16      * 这里覆盖了super的createJobInstance方法,对其创建出来的类再进行autowire。
17      */
18     @Override
19     protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
20         Object jobInstance = super.createJobInstance(bundle);
21         beanFactory.autowireBean(jobInstance);
22         return jobInstance;
23     }
24 }
MyJobFactory.java
 1 package com.zihans.train.batch.config;
 2 
 3 import jakarta.annotation.Resource;
 4 import org.springframework.beans.factory.annotation.Qualifier;
 5 import org.springframework.context.annotation.Bean;
 6 import org.springframework.context.annotation.Configuration;
 7 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 8 
 9 import javax.sql.DataSource;
10 import java.io.IOException;
11 
12 @Configuration
13 public class SchedulerConfig {
14 
15     @Resource
16     private MyJobFactory myJobFactory;
17 
18     @Bean
19     public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("dataSource") DataSource dataSource) throws IOException {
20         SchedulerFactoryBean factory = new SchedulerFactoryBean();
21         factory.setDataSource(dataSource);
22         factory.setJobFactory(myJobFactory);
23         factory.setStartupDelay(2);
24         return factory;
25     }
26 }
SchedulerConfig.java

springMvcConfig主要用来打印日志

 1 package com.zihans.train.batch.config;
 2 
 3 import com.zihans.train.common.interceptor.LogInterceptor;
 4 import jakarta.annotation.Resource;
 5 import org.springframework.context.annotation.Configuration;
 6 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 7 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 8 
 9 @Configuration
10 public class SpringMvcConfig implements WebMvcConfigurer {
11 
12     @Resource
13     LogInterceptor logInterceptor;
14 
15     @Override
16     public void addInterceptors(InterceptorRegistry registry) {
17         registry.addInterceptor(logInterceptor)
18                 .addPathPatterns("/**");
19 
20     }
21 }
SpringMvcConfig.java

JobController用来控制定时任务

  1 package com.zihans.train.batch.controller;
  2 
  3 import com.zihans.train.common.resp.CommonResp;
  4 import org.quartz.*;
  5 import org.quartz.impl.matchers.GroupMatcher;
  6 import org.quartz.impl.triggers.CronTriggerImpl;
  7 import org.springframework.beans.factory.annotation.Autowired;
  8 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
  9 import org.springframework.web.bind.annotation.RequestBody;
 10 import org.springframework.web.bind.annotation.RequestMapping;
 11 import org.springframework.web.bind.annotation.RestController;
 12 
 13 import java.util.ArrayList;
 14 import java.util.List;
 15 
 16 @RestController
 17 @RequestMapping(value = "/admin/job")
 18 public class JobController {
 19 
 20     private static Logger LOG = LoggerFactory.getLogger(JobController.class);
 21 
 22     @Autowired
 23     private SchedulerFactoryBean schedulerFactoryBean;
 24 
 25     @RequestMapping(value = "/add")
 26     public CommonResp add(@RequestBody CronJobReq cronJobReq) {
 27         String jobClassName = cronJobReq.getName();
 28         String jobGroupName = cronJobReq.getGroup();
 29         String cronExpression = cronJobReq.getCronExpression();
 30         String description = cronJobReq.getDescription();
 31         LOG.info("创建定时任务开始:{},{},{},{}", jobClassName, jobGroupName, cronExpression, description);
 32         CommonResp commonResp = new CommonResp();
 33 
 34         try {
 35             // 通过SchedulerFactory获取一个调度器实例
 36             Scheduler sched = schedulerFactoryBean.getScheduler();
 37 
 38             // 启动调度器
 39             sched.start();
 40 
 41             //构建job信息
 42             JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(jobClassName)).withIdentity(jobClassName, jobGroupName).build();
 43 
 44             //表达式调度构建器(即任务执行的时间)
 45             CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
 46 
 47             //按新的cronExpression表达式构建一个新的trigger
 48             CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName).withDescription(description).withSchedule(scheduleBuilder).build();
 49 
 50             sched.scheduleJob(jobDetail, trigger);
 51 
 52         } catch (SchedulerException e) {
 53             LOG.error("创建定时任务失败:" + e);
 54             commonResp.setSuccess(false);
 55             commonResp.setMessage("创建定时任务失败:调度异常");
 56         } catch (ClassNotFoundException e) {
 57             LOG.error("创建定时任务失败:" + e);
 58             commonResp.setSuccess(false);
 59             commonResp.setMessage("创建定时任务失败:任务类不存在");
 60         }
 61         LOG.info("创建定时任务结束:{}", commonResp);
 62         return commonResp;
 63     }
 64 
 65     @RequestMapping(value = "/pause")
 66     public CommonResp pause(@RequestBody CronJobReq cronJobReq) {
 67         String jobClassName = cronJobReq.getName();
 68         String jobGroupName = cronJobReq.getGroup();
 69         LOG.info("暂停定时任务开始:{},{}", jobClassName, jobGroupName);
 70         CommonResp commonResp = new CommonResp();
 71         try {
 72             Scheduler sched = schedulerFactoryBean.getScheduler();
 73             sched.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
 74         } catch (SchedulerException e) {
 75             LOG.error("暂停定时任务失败:" + e);
 76             commonResp.setSuccess(false);
 77             commonResp.setMessage("暂停定时任务失败:调度异常");
 78         }
 79         LOG.info("暂停定时任务结束:{}", commonResp);
 80         return commonResp;
 81     }
 82 
 83     @RequestMapping(value = "/resume")
 84     public CommonResp resume(@RequestBody CronJobReq cronJobReq) {
 85         String jobClassName = cronJobReq.getName();
 86         String jobGroupName = cronJobReq.getGroup();
 87         LOG.info("重启定时任务开始:{},{}", jobClassName, jobGroupName);
 88         CommonResp commonResp = new CommonResp();
 89         try {
 90             Scheduler sched = schedulerFactoryBean.getScheduler();
 91             sched.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
 92         } catch (SchedulerException e) {
 93             LOG.error("重启定时任务失败:" + e);
 94             commonResp.setSuccess(false);
 95             commonResp.setMessage("重启定时任务失败:调度异常");
 96         }
 97         LOG.info("重启定时任务结束:{}", commonResp);
 98         return commonResp;
 99     }
100 
101     @RequestMapping(value = "/reschedule")
102     public CommonResp reschedule(@RequestBody CronJobReq cronJobReq) {
103         String jobClassName = cronJobReq.getName();
104         String jobGroupName = cronJobReq.getGroup();
105         String cronExpression = cronJobReq.getCronExpression();
106         String description = cronJobReq.getDescription();
107         LOG.info("更新定时任务开始:{},{},{},{}", jobClassName, jobGroupName, cronExpression, description);
108         CommonResp commonResp = new CommonResp();
109         try {
110             Scheduler scheduler = schedulerFactoryBean.getScheduler();
111             TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
112             // 表达式调度构建器
113             CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
114             CronTriggerImpl trigger1 = (CronTriggerImpl) scheduler.getTrigger(triggerKey);
115             trigger1.setStartTime(new Date()); // 重新设置开始时间
116             CronTrigger trigger = trigger1;
117 
118             // 按新的cronExpression表达式重新构建trigger
119             trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withDescription(description).withSchedule(scheduleBuilder).build();
120 
121             // 按新的trigger重新设置job执行
122             scheduler.rescheduleJob(triggerKey, trigger);
123         } catch (Exception e) {
124             LOG.error("更新定时任务失败:" + e);
125             commonResp.setSuccess(false);
126             commonResp.setMessage("更新定时任务失败:调度异常");
127         }
128         LOG.info("更新定时任务结束:{}", commonResp);
129         return commonResp;
130     }
131 
132     @RequestMapping(value = "/delete")
133     public CommonResp delete(@RequestBody CronJobReq cronJobReq) {
134         String jobClassName = cronJobReq.getName();
135         String jobGroupName = cronJobReq.getGroup();
136         LOG.info("删除定时任务开始:{},{}", jobClassName, jobGroupName);
137         CommonResp commonResp = new CommonResp();
138         try {
139             Scheduler scheduler = schedulerFactoryBean.getScheduler();
140             scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
141             scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
142             scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
143         } catch (SchedulerException e) {
144             LOG.error("删除定时任务失败:" + e);
145             commonResp.setSuccess(false);
146             commonResp.setMessage("删除定时任务失败:调度异常");
147         }
148         LOG.info("删除定时任务结束:{}", commonResp);
149         return commonResp;
150     }
151 
152     @RequestMapping(value="/query")
153     public CommonResp query() {
154         LOG.info("查看所有定时任务开始");
155         CommonResp commonResp = new CommonResp();
156         List<CronJobResp> cronJobDtoList = new ArrayList();
157         try {
158             Scheduler scheduler = schedulerFactoryBean.getScheduler();
159             for (String groupName : scheduler.getJobGroupNames()) {
160                 for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
161                     CronJobResp cronJobResp = new CronJobResp();
162                     cronJobResp.setName(jobKey.getName());
163                     cronJobResp.setGroup(jobKey.getGroup());
164 
165                     //get job's trigger
166                     List<Trigger> triggers = (List<Trigger>) scheduler.getTriggersOfJob(jobKey);
167                     CronTrigger cronTrigger = (CronTrigger) triggers.get(0);
168                     cronJobResp.setNextFireTime(cronTrigger.getNextFireTime());
169                     cronJobResp.setPreFireTime(cronTrigger.getPreviousFireTime());
170                     cronJobResp.setCronExpression(cronTrigger.getCronExpression());
171                     cronJobResp.setDescription(cronTrigger.getDescription());
172                     Trigger.TriggerState triggerState = scheduler.getTriggerState(cronTrigger.getKey());
173                     cronJobResp.setState(triggerState.name());
174 
175                     cronJobDtoList.add(cronJobResp);
176                 }
177 
178             }
179         } catch (SchedulerException e) {
180             LOG.error("查看定时任务失败:" + e);
181             commonResp.setSuccess(false);
182             commonResp.setMessage("查看定时任务失败:调度异常");
183         }
184         commonResp.setContent(cronJobDtoList);
185         LOG.info("查看定时任务结束:{}", commonResp);
186         return commonResp;
187     }
188 
189 }
JobController.java

req和resp

 1 package com.zihans.train.batch.req;
 2 
 3 public class CronJobReq {
 4 
 5     //quartz可以对任务分组,每个分组可以起个名字
 6     private String group;
 7 
 8     private String name;
 9 
10     private String description;
11 
12     private String cronExpression;
13 
14     @Override
15     public String toString() {
16         final StringBuffer sb = new StringBuffer("CronJobDto{");
17         sb.append("cronExpression='").append(cronExpression).append('\'');
18         sb.append(", group='").append(group).append('\'');
19         sb.append(", name='").append(name).append('\'');
20         sb.append(", description='").append(description).append('\'');
21         sb.append('}');
22         return sb.toString();
23     }
24 
25     public String getGroup() {
26         return group;
27     }
28 
29     public void setGroup(String group) {
30         this.group = group;
31     }
32 
33     public String getCronExpression() {
34         return cronExpression;
35     }
36 
37     public void setCronExpression(String cronExpression) {
38         this.cronExpression = cronExpression;
39     }
40 
41     public String getDescription() {
42         return description;
43     }
44 
45     public void setDescription(String description) {
46         this.description = description;
47     }
48 
49     public String getName() {
50         return name;
51     }
52 
53     public void setName(String name) {
54         this.name = name;
55     }
56 }
CronJobReq.java
 1 package com.zihans.train.batch.resp;
 2 
 3 import com.fasterxml.jackson.annotation.JsonFormat;
 4 import com.fasterxml.jackson.annotation.JsonInclude;
 5 
 6 import java.util.Date;
 7 
 8 @JsonInclude(JsonInclude.Include.NON_EMPTY)
 9 public class CronJobResp {
10     private String group;
11 
12     private String name;
13 
14     private String description;
15 
16     private String state;
17 
18     private String cronExpression;
19 
20     @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
21     private Date nextFireTime;
22 
23     @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
24     private Date preFireTime;
25 
26     @Override
27     public String toString() {
28         final StringBuffer sb = new StringBuffer("CronJobDto{");
29         sb.append("cronExpression='").append(cronExpression).append('\'');
30         sb.append(", group='").append(group).append('\'');
31         sb.append(", name='").append(name).append('\'');
32         sb.append(", description='").append(description).append('\'');
33         sb.append(", state='").append(state).append('\'');
34         sb.append(", nextFireTime=").append(nextFireTime);
35         sb.append(", preFireTime=").append(preFireTime);
36         sb.append('}');
37         return sb.toString();
38     }
39 
40     public String getGroup() {
41         return group;
42     }
43 
44     public void setGroup(String group) {
45         this.group = group;
46     }
47 
48     public String getCronExpression() {
49         return cronExpression;
50     }
51 
52     public void setCronExpression(String cronExpression) {
53         this.cronExpression = cronExpression;
54     }
55 
56     public String getDescription() {
57         return description;
58     }
59 
60     public void setDescription(String description) {
61         this.description = description;
62     }
63 
64     public String getName() {
65         return name;
66     }
67 
68     public void setName(String name) {
69         this.name = name;
70     }
71 
72     public Date getNextFireTime() {
73         return nextFireTime;
74     }
75 
76     public void setNextFireTime(Date nextFireTime) {
77         this.nextFireTime = nextFireTime;
78     }
79 
80     public Date getPreFireTime() {
81         return preFireTime;
82     }
83 
84     public void setPreFireTime(Date preFireTime) {
85         this.preFireTime = preFireTime;
86     }
87 
88     public String getState() {
89         return state;
90     }
91 
92     public void setState(String state) {
93         this.state = state;
94     }
95 }
CronJobResp.java

通过控台界面操作定时任务

the-sider.vue增加定时任务管理功能

1       <a-menu-item key="/batch/job">
2         <router-link to="/batch/job">
3           <MenuUnfoldOutlined /> &nbsp; 任务管理
4         </router-link>
5       </a-menu-item>
  1 <template>
  2   <div class="job">
  3     <p>
  4       <a-button type="primary" @click="handleAdd()">
  5         新增
  6       </a-button>&nbsp;
  7       <a-button type="primary" @click="handleQuery()">
  8         刷新
  9       </a-button>
 10     </p>
 11     <a-table :dataSource="jobs"
 12              :columns="columns"
 13              :loading="loading">
 14       <template #bodyCell="{ column, record }">
 15         <template v-if="column.dataIndex === 'operation'">
 16           <a-space>
 17             <a-popconfirm
 18                 title="确定重启?"
 19                 ok-text="是"
 20                 cancel-text="否"
 21                 @confirm="handleResume(record)"
 22             >
 23               <a-button v-show="record.state === 'PAUSED' || record.state === 'ERROR'" type="primary" size="small">
 24                 重启
 25               </a-button>
 26             </a-popconfirm>
 27             <a-popconfirm
 28                 title="确定暂停?"
 29                 ok-text="是"
 30                 cancel-text="否"
 31                 @confirm="handlePause(record)"
 32             >
 33               <a-button v-show="record.state === 'NORMAL' || record.state === 'BLOCKED'" type="primary" size="small">
 34                 暂停
 35               </a-button>
 36             </a-popconfirm>
 37             <a-button type="primary" @click="handleEdit(record)" size="small">
 38               编辑
 39             </a-button>
 40             <a-popconfirm
 41                 title="删除后不可恢复,确认删除?"
 42                 ok-text="是"
 43                 cancel-text="否"
 44                 @confirm="handleDelete(record)"
 45             >
 46               <a-button type="danger" size="small">
 47                 删除
 48               </a-button>
 49             </a-popconfirm>
 50           </a-space>
 51         </template>
 52       </template>
 53     </a-table>
 54 
 55     <a-modal
 56         title="用户"
 57         v-model:visible="modalVisible"
 58         :confirm-loading="modalLoading"
 59         @ok="handleModalOk"
 60     >
 61       <a-form :model="job" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
 62         <a-form-item label="类名">
 63           <a-input v-model:value="job.name" />
 64         </a-form-item>
 65         <a-form-item label="描述">
 66           <a-input v-model:value="job.description" />
 67         </a-form-item>
 68         <a-form-item label="分组">
 69           <a-input v-model:value="job.group" :disabled="!!job.state"/>
 70         </a-form-item>
 71         <a-form-item label="表达式">
 72           <a-input v-model:value="job.cronExpression" />
 73           <div class="ant-alert ant-alert-success">
 74             每5秒执行一次:0/5 * * * * ?
 75             <br>
 76             每5分钟执行一次:* 0/5 * * * ?
 77           </div>
 78         </a-form-item>
 79       </a-form>
 80     </a-modal>
 81   </div>
 82 </template>
 83 
 84 <script>
 85 import { defineComponent, onMounted, ref } from 'vue';
 86 import axios from 'axios';
 87 import { notification } from 'ant-design-vue';
 88 
 89 export default defineComponent({
 90   name: 'batch-job-view',
 91   setup () {
 92     const jobs = ref();
 93     const loading = ref();
 94 
 95     const columns = [{
 96       title: '分组',
 97       dataIndex: 'group',
 98     }, {
 99       title: '类名',
100       dataIndex: 'name',
101     }, {
102       title: '描述',
103       dataIndex: 'description',
104     }, {
105       title: '状态',
106       dataIndex: 'state',
107     }, {
108       title: '表达式',
109       dataIndex: 'cronExpression',
110     }, {
111       title: '上次时间',
112       dataIndex: 'preFireTime',
113     }, {
114       title: '下次时间',
115       dataIndex: 'nextFireTime',
116     }, {
117       title: '操作',
118       dataIndex: 'operation'
119     }];
120 
121     const handleQuery = () => {
122       loading.value = true;
123       jobs.value = [];
124       axios.get('/batch/admin/job/query').then((response) => {
125         loading.value = false;
126         const data = response.data;
127         if (data.success) {
128           jobs.value = data.content;
129         } else {
130           notification.error({description: data.message});
131         }
132       });
133     };
134 
135     // -------- 表单 ---------
136     const job = ref();
137     job.value = {};
138     const modalVisible = ref(false);
139     const modalLoading = ref(false);
140     const handleModalOk = () => {
141       modalLoading.value = true;
142       let url = "add";
143       if (job.value.state) {
144         url = "reschedule";
145       }
146       axios.post('/batch/admin/job/' + url, job.value).then((response) => {
147         modalLoading.value = false;
148         const data = response.data;
149         if (data.success) {
150           modalVisible.value = false;
151           notification.success({description: "保存成功!"});
152           handleQuery();
153         } else {
154           notification.error({description: data.message});
155         }
156       });
157     };
158 
159     /**
160      * 新增
161      */
162     const handleAdd = () => {
163       modalVisible.value = true;
164       job.value = {
165         group: 'DEFAULT'
166       };
167     };
168 
169     /**
170      * 编辑
171      */
172     const handleEdit = (record) => {
173       modalVisible.value = true;
174       job.value = Tool.copy(record);
175     };
176 
177     /**
178      * 删除
179      */
180     const handleDelete = (record) => {
181       axios.post('/batch/admin/job/delete', {
182         name: record.name,
183         group: record.group
184       }).then((response) => {
185         const data = response.data;
186         if (data.success) {
187           notification.success({description: "删除成功!"});
188           handleQuery();
189         } else {
190           notification.error({description: data.message});
191         }
192       });
193     };
194 
195     /**
196      * 暂停
197      */
198     const handlePause = (record) => {
199       axios.post('/batch/admin/job/pause', {
200         name: record.name,
201         group: record.group
202       }).then((response) => {
203         const data = response.data;
204         if (data.success) {
205           notification.success({description: "暂停成功!"});
206           handleQuery();
207         } else {
208           notification.error({description: data.message});
209         }
210       });
211     };
212 
213     /**
214      * 重启
215      */
216     const handleResume = (record) => {
217       axios.post('/batch/admin/job/reschedule', record).then((response) => {
218         modalLoading.value = false;
219         const data = response.data;
220         if (data.success) {
221           modalVisible.value = false;
222           notification.success({description: "重启成功!"});
223           handleQuery();
224         } else {
225           notification.error({description: data.message});
226         }
227       });
228     };
229 
230     const getEnumValue = (key, obj) => {
231       return Tool.getEnumValue(key, obj);
232     };
233 
234     onMounted(() => {
235       console.log('index mounted!');
236       handleQuery();
237     });
238 
239     return {
240       columns,
241       jobs,
242       loading,
243       handleQuery,
244 
245       handleAdd,
246       handleEdit,
247       handleDelete,
248       handleResume,
249       handlePause,
250 
251       job,
252       modalVisible,
253       modalLoading,
254       handleModalOk,
255 
256       getEnumValue
257     };
258   }
259 })
260 </script>
261 
262 <style scoped>
263 </style>
job.vue

增加任务手工补偿功能

手动执行一次功能

  1 <template>
  2   <div class="job">
  3     <p>
  4       <a-button type="primary" @click="handleAdd()">
  5         新增
  6       </a-button>&nbsp;
  7       <a-button type="primary" @click="handleQuery()">
  8         刷新
  9       </a-button>
 10     </p>
 11     <a-table :dataSource="jobs"
 12              :columns="columns"
 13              :loading="loading">
 14       <template #bodyCell="{ column, record }">
 15         <template v-if="column.dataIndex === 'operation'">
 16           <a-space>
 17             <a-popconfirm
 18                 title="手动执行会立即执行一次,确定执行?"
 19                 ok-text="是"
 20                 cancel-text="否"
 21                 @confirm="handleRun(record)"
 22             >
 23               <a-button type="primary" size="small">
 24                 手动执行
 25               </a-button>
 26             </a-popconfirm>
 27             <a-popconfirm
 28                 title="确定重启?"
 29                 ok-text="是"
 30                 cancel-text="否"
 31                 @confirm="handleResume(record)"
 32             >
 33               <a-button v-show="record.state === 'PAUSED' || record.state === 'ERROR'" type="primary" size="small">
 34                 重启
 35               </a-button>
 36             </a-popconfirm>
 37             <a-popconfirm
 38                 title="确定暂停?"
 39                 ok-text="是"
 40                 cancel-text="否"
 41                 @confirm="handlePause(record)"
 42             >
 43               <a-button v-show="record.state === 'NORMAL' || record.state === 'BLOCKED'" type="primary" size="small">
 44                 暂停
 45               </a-button>
 46             </a-popconfirm>
 47             <a-button type="primary" @click="handleEdit(record)" size="small">
 48               编辑
 49             </a-button>
 50             <a-popconfirm
 51                 title="删除后不可恢复,确认删除?"
 52                 ok-text="是"
 53                 cancel-text="否"
 54                 @confirm="handleDelete(record)"
 55             >
 56               <a-button type="danger" size="small">
 57                 删除
 58               </a-button>
 59             </a-popconfirm>
 60           </a-space>
 61         </template>
 62       </template>
 63     </a-table>
 64 
 65     <a-modal
 66         title="用户"
 67         v-model:visible="modalVisible"
 68         :confirm-loading="modalLoading"
 69         @ok="handleModalOk"
 70     >
 71       <a-form :model="job" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
 72         <a-form-item label="类名">
 73           <a-input v-model:value="job.name" />
 74         </a-form-item>
 75         <a-form-item label="描述">
 76           <a-input v-model:value="job.description" />
 77         </a-form-item>
 78         <a-form-item label="分组">
 79           <a-input v-model:value="job.group" :disabled="!!job.state"/>
 80         </a-form-item>
 81         <a-form-item label="表达式">
 82           <a-input v-model:value="job.cronExpression" />
 83           <div class="ant-alert ant-alert-success">
 84             每5秒执行一次:0/5 * * * * ?
 85             <br>
 86             每5分钟执行一次:* 0/5 * * * ?
 87           </div>
 88         </a-form-item>
 89       </a-form>
 90     </a-modal>
 91   </div>
 92 </template>
 93 
 94 <script>
 95 import { defineComponent, onMounted, ref } from 'vue';
 96 import axios from 'axios';
 97 import { notification } from 'ant-design-vue';
 98 
 99 export default defineComponent({
100   name: 'batch-job-view',
101   setup () {
102     const jobs = ref();
103     const loading = ref();
104 
105     const columns = [{
106       title: '分组',
107       dataIndex: 'group',
108     }, {
109       title: '类名',
110       dataIndex: 'name',
111     }, {
112       title: '描述',
113       dataIndex: 'description',
114     }, {
115       title: '状态',
116       dataIndex: 'state',
117     }, {
118       title: '表达式',
119       dataIndex: 'cronExpression',
120     }, {
121       title: '上次时间',
122       dataIndex: 'preFireTime',
123     }, {
124       title: '下次时间',
125       dataIndex: 'nextFireTime',
126     }, {
127       title: '操作',
128       dataIndex: 'operation'
129     }];
130 
131     const handleQuery = () => {
132       loading.value = true;
133       jobs.value = [];
134       axios.get('/batch/admin/job/query').then((response) => {
135         loading.value = false;
136         const data = response.data;
137         if (data.success) {
138           jobs.value = data.content;
139         } else {
140           notification.error({description: data.message});
141         }
142       });
143     };
144 
145     // -------- 表单 ---------
146     const job = ref();
147     job.value = {};
148     const modalVisible = ref(false);
149     const modalLoading = ref(false);
150     const handleModalOk = () => {
151       modalLoading.value = true;
152       let url = "add";
153       if (job.value.state) {
154         url = "reschedule";
155       }
156       axios.post('/batch/admin/job/' + url, job.value).then((response) => {
157         modalLoading.value = false;
158         const data = response.data;
159         if (data.success) {
160           modalVisible.value = false;
161           notification.success({description: "保存成功!"});
162           handleQuery();
163         } else {
164           notification.error({description: data.message});
165         }
166       });
167     };
168 
169     /**
170      * 新增
171      */
172     const handleAdd = () => {
173       modalVisible.value = true;
174       job.value = {
175         group: 'DEFAULT'
176       };
177     };
178 
179     /**
180      * 编辑
181      */
182     const handleEdit = (record) => {
183       modalVisible.value = true;
184       job.value = Tool.copy(record);
185     };
186 
187     /**
188      * 删除
189      */
190     const handleDelete = (record) => {
191       axios.post('/batch/admin/job/delete', {
192         name: record.name,
193         group: record.group
194       }).then((response) => {
195         const data = response.data;
196         if (data.success) {
197           notification.success({description: "删除成功!"});
198           handleQuery();
199         } else {
200           notification.error({description: data.message});
201         }
202       });
203     };
204 
205     /**
206      * 暂停
207      */
208     const handlePause = (record) => {
209       axios.post('/batch/admin/job/pause', {
210         name: record.name,
211         group: record.group
212       }).then((response) => {
213         const data = response.data;
214         if (data.success) {
215           notification.success({description: "暂停成功!"});
216           handleQuery();
217         } else {
218           notification.error({description: data.message});
219         }
220       });
221     };
222 
223     /**
224      * 重启
225      */
226     const handleResume = (record) => {
227       axios.post('/batch/admin/job/reschedule', record).then((response) => {
228         modalLoading.value = false;
229         const data = response.data;
230         if (data.success) {
231           modalVisible.value = false;
232           notification.success({description: "重启成功!"});
233           handleQuery();
234         } else {
235           notification.error({description: data.message});
236         }
237       });
238     };
239 
240     /**
241      * 手动执行
242      */
243     const handleRun = (record) => {
244       axios.post('/batch/admin/job/run', record).then((response) => {
245         const data = response.data;
246         if (data.success) {
247           notification.success({description: "手动执行成功!"});
248         } else {
249           notification.error({description: data.message});
250         }
251       });
252     };
253 
254     const getEnumValue = (key, obj) => {
255       return Tool.getEnumValue(key, obj);
256     };
257 
258     onMounted(() => {
259       console.log('index mounted!');
260       handleQuery();
261     });
262 
263     return {
264       columns,
265       jobs,
266       loading,
267       handleQuery,
268 
269       handleAdd,
270       handleEdit,
271       handleDelete,
272       handleResume,
273       handlePause,
274       job,
275       modalVisible,
276       modalLoading,
277       handleModalOk,
278       getEnumValue,
279       handleRun
280     };
281   }
282 })
283 </script>
284 
285 <style scoped>
286 </style>
job.vue
  1 package com.zihans.train.batch.controller;
  2 
  3 import com.zihans.train.batch.req.CronJobReq;
  4 import com.zihans.train.batch.resp.CronJobResp;
  5 import com.zihans.train.common.resp.CommonResp;
  6 import org.quartz.*;
  7 import org.quartz.impl.matchers.GroupMatcher;
  8 import org.quartz.impl.triggers.CronTriggerImpl;
  9 import org.slf4j.Logger;
 10 import org.slf4j.LoggerFactory;
 11 import org.springframework.beans.factory.annotation.Autowired;
 12 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 13 import org.springframework.web.bind.annotation.RequestBody;
 14 import org.springframework.web.bind.annotation.RequestMapping;
 15 import org.springframework.web.bind.annotation.RestController;
 16 
 17 import java.util.ArrayList;
 18 import java.util.Date;
 19 import java.util.List;
 20 
 21 @RestController
 22 @RequestMapping(value = "/admin/job")
 23 public class JobController {
 24 
 25     private static Logger LOG = LoggerFactory.getLogger(JobController.class);
 26 
 27     @Autowired
 28     private SchedulerFactoryBean schedulerFactoryBean;
 29 
 30     @RequestMapping(value = "/run")
 31     public CommonResp<Object> run(@RequestBody CronJobReq cronJobReq) throws SchedulerException {
 32         String jobClassName = cronJobReq.getName();
 33         String jobGroupName = cronJobReq.getGroup();
 34         LOG.info("手动执行任务开始:{}, {}", jobClassName, jobGroupName);
 35         schedulerFactoryBean.getScheduler().triggerJob(JobKey.jobKey(jobClassName, jobGroupName));
 36         return new CommonResp<>();
 37     }
 38 
 39     @RequestMapping(value = "/add")
 40     public CommonResp add(@RequestBody CronJobReq cronJobReq) {
 41         String jobClassName = cronJobReq.getName();
 42         String jobGroupName = cronJobReq.getGroup();
 43         String cronExpression = cronJobReq.getCronExpression();
 44         String description = cronJobReq.getDescription();
 45         LOG.info("创建定时任务开始:{},{},{},{}", jobClassName, jobGroupName, cronExpression, description);
 46         CommonResp commonResp = new CommonResp();
 47 
 48         try {
 49             // 通过SchedulerFactory获取一个调度器实例
 50             Scheduler sched = schedulerFactoryBean.getScheduler();
 51 
 52             // 启动调度器
 53             sched.start();
 54 
 55             //构建job信息
 56             JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(jobClassName)).withIdentity(jobClassName, jobGroupName).build();
 57 
 58             //表达式调度构建器(即任务执行的时间)
 59             CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
 60 
 61             //按新的cronExpression表达式构建一个新的trigger
 62             CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName).withDescription(description).withSchedule(scheduleBuilder).build();
 63 
 64             sched.scheduleJob(jobDetail, trigger);
 65 
 66         } catch (SchedulerException e) {
 67             LOG.error("创建定时任务失败:" + e);
 68             commonResp.setSuccess(false);
 69             commonResp.setMessage("创建定时任务失败:调度异常");
 70         } catch (ClassNotFoundException e) {
 71             LOG.error("创建定时任务失败:" + e);
 72             commonResp.setSuccess(false);
 73             commonResp.setMessage("创建定时任务失败:任务类不存在");
 74         }
 75         LOG.info("创建定时任务结束:{}", commonResp);
 76         return commonResp;
 77     }
 78 
 79     @RequestMapping(value = "/pause")
 80     public CommonResp pause(@RequestBody CronJobReq cronJobReq) {
 81         String jobClassName = cronJobReq.getName();
 82         String jobGroupName = cronJobReq.getGroup();
 83         LOG.info("暂停定时任务开始:{},{}", jobClassName, jobGroupName);
 84         CommonResp commonResp = new CommonResp();
 85         try {
 86             Scheduler sched = schedulerFactoryBean.getScheduler();
 87             sched.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
 88         } catch (SchedulerException e) {
 89             LOG.error("暂停定时任务失败:" + e);
 90             commonResp.setSuccess(false);
 91             commonResp.setMessage("暂停定时任务失败:调度异常");
 92         }
 93         LOG.info("暂停定时任务结束:{}", commonResp);
 94         return commonResp;
 95     }
 96 
 97     @RequestMapping(value = "/resume")
 98     public CommonResp resume(@RequestBody CronJobReq cronJobReq) {
 99         String jobClassName = cronJobReq.getName();
100         String jobGroupName = cronJobReq.getGroup();
101         LOG.info("重启定时任务开始:{},{}", jobClassName, jobGroupName);
102         CommonResp commonResp = new CommonResp();
103         try {
104             Scheduler sched = schedulerFactoryBean.getScheduler();
105             sched.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
106         } catch (SchedulerException e) {
107             LOG.error("重启定时任务失败:" + e);
108             commonResp.setSuccess(false);
109             commonResp.setMessage("重启定时任务失败:调度异常");
110         }
111         LOG.info("重启定时任务结束:{}", commonResp);
112         return commonResp;
113     }
114 
115     @RequestMapping(value = "/reschedule")
116     public CommonResp reschedule(@RequestBody CronJobReq cronJobReq) {
117         String jobClassName = cronJobReq.getName();
118         String jobGroupName = cronJobReq.getGroup();
119         String cronExpression = cronJobReq.getCronExpression();
120         String description = cronJobReq.getDescription();
121         LOG.info("更新定时任务开始:{},{},{},{}", jobClassName, jobGroupName, cronExpression, description);
122         CommonResp commonResp = new CommonResp();
123         try {
124             Scheduler scheduler = schedulerFactoryBean.getScheduler();
125             TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
126             // 表达式调度构建器
127             CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
128             CronTriggerImpl trigger1 = (CronTriggerImpl) scheduler.getTrigger(triggerKey);
129             trigger1.setStartTime(new Date()); // 重新设置开始时间
130             CronTrigger trigger = trigger1;
131 
132             // 按新的cronExpression表达式重新构建trigger
133             trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withDescription(description).withSchedule(scheduleBuilder).build();
134 
135             // 按新的trigger重新设置job执行
136             scheduler.rescheduleJob(triggerKey, trigger);
137         } catch (Exception e) {
138             LOG.error("更新定时任务失败:" + e);
139             commonResp.setSuccess(false);
140             commonResp.setMessage("更新定时任务失败:调度异常");
141         }
142         LOG.info("更新定时任务结束:{}", commonResp);
143         return commonResp;
144     }
145 
146     @RequestMapping(value = "/delete")
147     public CommonResp delete(@RequestBody CronJobReq cronJobReq) {
148         String jobClassName = cronJobReq.getName();
149         String jobGroupName = cronJobReq.getGroup();
150         LOG.info("删除定时任务开始:{},{}", jobClassName, jobGroupName);
151         CommonResp commonResp = new CommonResp();
152         try {
153             Scheduler scheduler = schedulerFactoryBean.getScheduler();
154             scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
155             scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
156             scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
157         } catch (SchedulerException e) {
158             LOG.error("删除定时任务失败:" + e);
159             commonResp.setSuccess(false);
160             commonResp.setMessage("删除定时任务失败:调度异常");
161         }
162         LOG.info("删除定时任务结束:{}", commonResp);
163         return commonResp;
164     }
165 
166     @RequestMapping(value="/query")
167     public CommonResp query() {
168         LOG.info("查看所有定时任务开始");
169         CommonResp commonResp = new CommonResp();
170         List<CronJobResp> cronJobDtoList = new ArrayList();
171         try {
172             Scheduler scheduler = schedulerFactoryBean.getScheduler();
173             for (String groupName : scheduler.getJobGroupNames()) {
174                 for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
175                     CronJobResp cronJobResp = new CronJobResp();
176                     cronJobResp.setName(jobKey.getName());
177                     cronJobResp.setGroup(jobKey.getGroup());
178 
179                     //get job's trigger
180                     List<Trigger> triggers = (List<Trigger>) scheduler.getTriggersOfJob(jobKey);
181                     CronTrigger cronTrigger = (CronTrigger) triggers.get(0);
182                     cronJobResp.setNextFireTime(cronTrigger.getNextFireTime());
183                     cronJobResp.setPreFireTime(cronTrigger.getPreviousFireTime());
184                     cronJobResp.setCronExpression(cronTrigger.getCronExpression());
185                     cronJobResp.setDescription(cronTrigger.getDescription());
186                     Trigger.TriggerState triggerState = scheduler.getTriggerState(cronTrigger.getKey());
187                     cronJobResp.setState(triggerState.name());
188 
189                     cronJobDtoList.add(cronJobResp);
190                 }
191 
192             }
193         } catch (SchedulerException e) {
194             LOG.error("查看定时任务失败:" + e);
195             commonResp.setSuccess(false);
196             commonResp.setMessage("查看定时任务失败:调度异常");
197         }
198         commonResp.setContent(cronJobDtoList);
199         LOG.info("查看定时任务结束:{}", commonResp);
200         return commonResp;
201     }
202 
203 }
JobController.java