七、使用调度框架quartz,为12306系统增加定时调度功能
为什么要有定时调度
- 定时调度在企业级系统中非常重要(统计报表、功能补偿、不紧急的大批量任务)
- 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 }
调度方法的实现类
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来执行,类需要实现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 );
增加两个数据库模式的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 }
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 }
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 }
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 }
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 }
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 }
通过控台界面操作定时任务
the-sider.vue增加定时任务管理功能
1 <a-menu-item key="/batch/job"> 2 <router-link to="/batch/job"> 3 <MenuUnfoldOutlined /> 任务管理 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> 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>
增加任务手工补偿功能
手动执行一次功能
1 <template> 2 <div class="job"> 3 <p> 4 <a-button type="primary" @click="handleAdd()"> 5 新增 6 </a-button> 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>
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 }