2021-2-6-定时任务
快速开始、核心概念、Job和JobDetail、上下文、有状态Job和无状态Job、Trigger、整合到springboot中、监听器、Quartz外的其他定时器、完整案例
快速开始
1)定义一个JOB
package com.example.test;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;
public class PrintJob implements Job{
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("currentTime:" + new Date());
}
}
2)定义任务
package com.example.test;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class Test {
public static void main(String[] args) throws SchedulerException {
//创建一个scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//创建一个job
JobDetail job = JobBuilder.newJob(PrintJob.class)
.usingJobData("j1", "jv1")
.withIdentity("myjob", "mygroup").build();// name "myjob", group "mygroup"
//创建一个Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")// name "trigger1", group "group1"
.usingJobData("t1", "tv1")
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)//设置定时
.repeatForever()//设置重复
).build();
scheduler.scheduleJob(job,trigger);
scheduler.start();
}
}
核心概念
1)任务Job
每个job必须实现org.quarts.job接口,且需实现接口定义的execute方法
2)触发器Trigger
出发执行job,有两种,分别为SimpleTrigger和CronTrigger
3)调度器Scheduler
任务调度器,将任务和触发器整合起来,负责基于Trigger设定的时间来执行job
Job和JobDetail
1)Job实例在Quarts生命周期:每次调度器执行job时,在execute方法前会创建一个新的job实例,当调用完成后,关联的job对象实例会被释放,释放的实例会被垃圾回收
2)JobDetail,为job实例提供许多设置属性,以及JobDataMap成员变量属性,用来存储特定job实例的状态信息,调度器需要借助JobDetail对象来添加job实例。其重要属性:name、group、jobClass、jobDataMap
package com.example.test;
import org.quartz.*;
public class Test {
public static void main(String[] args) throws SchedulerException {
JobDetail job = JobBuilder.newJob(PrintJob.class)
.usingJobData("j1", "jv1")
.withIdentity("myjob", "mygroup").build();
System.out.println(job.getKey().getName());//myjob
System.out.println(job.getKey().getGroup());//mygroup,如果未设置为DEFAULT
System.out.println(job.getKey().getClass());//class org.quartz.JobKey
}
}
上下文
1)JobExecutionContext
当Scheduler调用一个Job,就会将JobExecutionContext传递给Job的execute()方法
Job能通过JobExecutionContext对象访问到Quarts运行时的环境以及job本身的明细数据
2)JobDataMap
在进行任务调度时,JobDataMap存储在JobExecutionContext中。可用来装载任何可序列化的数据对象,当Job实例对象被执行时这些参数对象会传递给它。JobDataMap实现了JDK的Map接口,并添加了非常方便的方法来存取基本数据类型
package com.example.demo;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class Test {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
.usingJobData("j1","jv1")//往JobDataMap中存数据
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1","default")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
.build();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
}
package com.example.demo;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;
public class PrintJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//获取JobDataMap的数据
System.out.println(context.getJobDetail().getJobDataMap().get("j1"));
}
}
有状态Job和无状态Job
默认的Job是无状态的,如果要有状态需要添加注解
1)无状态情况下
package com.example.demo;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class PrintJob implements Job {
private Integer count;
public void setCount(Integer count) {
this.count = count;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
count++;
System.out.println(count);//一直打印1
context.getJobDetail().getJobDataMap().put("count",count);
}
}
package com.example.demo;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class Test {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
.usingJobData("count",0)//往JobDataMap中存数据
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1","default")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
.build();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
}
2)有状态
package com.example.demo;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
@PersistJobDataAfterExecution//加上这个注解
public class PrintJob implements Job {
private Integer count;
public void setCount(Integer count) {
this.count = count;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
count++;
System.out.println(count);//一直打印1
context.getJobDetail().getJobDataMap().put("count",count);
}
}
省略构造定时任务的main函数
Trigger
1)设置任务开始时间、结束时间
package com.example.demo;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Date;
public class Test {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
.usingJobData("count",0)
.build();
//开始时间
Date start = new Date();
//结束时间
Date end = new Date();
end.setTime(end.getTime()+10000);//开始和结束时间间隔10秒
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1","default")
.startAt(start)
.endAt(end)
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())//每5秒执行一次
.build();
//最终任务执行两次
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
}
2)设置重复次数
package com.example.test;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class Test {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail job = JobBuilder.newJob(PrintJob.class)
.withIdentity("myjob", "mygroup").build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","deafult")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
.withRepeatCount(5) //设置重复次数,总共执行6次
)
.build();
scheduler.scheduleJob(job,trigger);
scheduler.start();
}
}
3)CronTrigger
基于日历来触发任务
package com.example.demo;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class Test {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
.usingJobData("count",0)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1","default")
.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
.build();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
}
4)triggerh和job的关系
一个trigger只能对应一个job,一个job可对应多个trigger
5)挂起
package com.example.demo;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class Test {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
.usingJobData("count",0)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1","default")
.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
.build();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
scheduler.standby();//挂起
try {
Thread.sleep(50000L);//等待50秒
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduler.start();//重新启动
}
}
6)关闭
关闭后,不可重启
7)获取任务开始时间
package com.example.demo;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Date;
public class Test {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
.usingJobData("count",0)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1","default")
.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
.build();
Date date = scheduler.scheduleJob(jobDetail,trigger);//获取任务执行开始的时间
System.out.println(date);
scheduler.start();
}
}
整合到springboot中
1
开始
1)依赖
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz
compile group: 'org.springframework.boot', name: 'spring-boot-starter-quartz', version: '2.4.2'
2)创建Job
package com.example.demo.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class PrintJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Integer count =(Integer) context.getJobDetail().getJobDataMap().get("count");
System.out.println(count);
count++;
context.getJobDetail().getJobDataMap().put("count",count);
}
}
3)创建配置类
package com.example.demo.config;
import com.example.demo.job.PrintJob;
import org.quartz.JobDataMap;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;
@Configuration
public class QuartzConfig {
@Bean
public JobDetailFactoryBean getJob(){
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setJobClass(PrintJob.class);
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("count",0);
factoryBean.setJobDataMap(jobDataMap);
return factoryBean;
}
@Bean
public SimpleTriggerFactoryBean getTrigger(JobDetailFactoryBean jobDetailFactoryBean){
SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
factoryBean.setJobDetail(jobDetailFactoryBean.getObject());
factoryBean.setRepeatInterval(1000);
factoryBean.setRepeatCount(100);
return factoryBean;
}
@Bean
public SchedulerFactoryBean getSchedule(SimpleTriggerFactoryBean simpleTriggerFactoryBean){
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setTriggers(simpleTriggerFactoryBean.getObject());
return schedulerFactoryBean;
}
}
2
自动创建表
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///test?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
quartz:
job-store-type: jdbc //job保存方式设置为jdbc
jdbc:
initialize-schema: always //启动时总是初始化表
3
通用方式
1)配置
package com.zhanghuan.scheduler.job.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
public class ScheduleConfig {
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
//quartz参数
Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName", "RenrenScheduler");
prop.put("org.quartz.scheduler.instanceId", "AUTO");
//线程池配置
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "20");
prop.put("org.quartz.threadPool.threadPriority", "5");
//JobStore配置
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
//集群配置
prop.put("org.quartz.jobStore.isClustered", "true");
prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
prop.put("org.quartz.jobStore.misfireThreshold", "12000");
prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
//PostgreSQL数据库,需要打开此注释
//prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");
factory.setQuartzProperties(prop);
factory.setSchedulerName("RenrenScheduler");
//延时启动
factory.setStartupDelay(30);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
//可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
//设置自动启动,默认为true
factory.setAutoStartup(true);
return factory;
}
}
2)注入Schedule
package com.zhanghuan.scheduler.job.controller;
import com.zhanghuan.scheduler.job.entity.R;
import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import com.zhanghuan.scheduler.job.service.ScheduleJobService;
import com.zhanghuan.scheduler.job.utils.TestJob;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.Map;
@RestController
@RequestMapping("/sys/schedule")
public class ScheduleJobController {
@Autowired
Scheduler scheduler;
@RequestMapping("/s")
public R getScheduler() throws SchedulerException {
JobDetail jobDetail = JobBuilder.newJob(TestJob.class).build();
Trigger trigger = TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ? ")).build();
//使用注入的scheduler绑定任务和触发器
Date date = scheduler.scheduleJob(jobDetail,trigger);
return R.ok().put("scheduler",date);
}
}
监听器
1
JobListener监听器
1)定义监听器
package com.example.demo;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
public class MyJobListener implements JobListener {
@Override
public String getName() {
String name = this.getClass().getSimpleName();
System.out.println(name);
return name;
}
@Override
public void jobToBeExecuted(JobExecutionContext context) {
//Scheduler在jobDetail将要被执行时调用
String name = context.getJobDetail().getKey().getName();
System.out.println(name+"will execute");
}
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
//Scheduler在jobDetail将要被执行,但又被TriggerLister否决时执行
System.out.println("2");
}
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
//Scheduler在jobDetail执行后时调用
System.out.println("3");
}
}
2)添加监听器
package com.example.demo;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.EverythingMatcher;
import java.util.Properties;
public class Test {
public static void main(String[] args) throws SchedulerException {
Properties properties = new Properties();
properties.put("org.quartz.threadPool.threadCount","10");
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
.usingJobData("count",0)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1","default")
.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
.build();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());//添加监听器
scheduler.start();
}
}
2
TriggerListener监听器
1)定义监听器
package com.example.demo;
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
public class MyTriggerListener implements TriggerListener {
@Override
public String getName() {
return this.getClass().getSimpleName();//必须返回,否则抛出异常
}
@Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
//Trigger被触发时执行
}
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
//假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行
return false;
}
@Override
public void triggerMisfired(Trigger trigger) {
//Trigger 错过触发时
}
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
//Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法
System.out.println("Trigger complete Job");
}
}
2)添加监听器
package com.example.demo;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.EverythingMatcher;
import org.quartz.impl.matchers.KeyMatcher;
import java.util.Properties;
public class Test {
public static void main(String[] args) throws SchedulerException {
Properties properties = new Properties();
properties.put("org.quartz.threadPool.threadCount","10");
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("print","default")
.usingJobData("count",0)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("t1","default")
.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
.build();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("t1","default")));
scheduler.start();
}
}
Quartz外的其他定时器
1)Timer
package com.example.test;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TestTimer {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date());
}
},1000,5000);////延时1s,之后每隔5s运行一次
}
}
2)spingboot项目自带的定时任务
(1)方法上加注解
package com.example.test.controller;
import com.example.test.entity.Book;
import com.example.test.mapper.BookMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
@RestController
public class BBB {
@Autowired
BookMapper bookMapper;
@GetMapping("/book/list")
@Scheduled(cron = "1/5 * * * * ?")//定时任务的注解
public List<Book> getBooks(){
return bookMapper.selectList(null);
}
@GetMapping("/book/insert")
@Scheduled(cron = "1/5 * * * * ?")//定时任务的注解,只支持6位,年份是不支持的,带年份的7位格式会报错
public String insertBook(){
String uuid = UUID.randomUUID().toString().replace("-","");
Book book = new Book(uuid,"老人与海","海明威");
bookMapper.insert(book);
return "ok";
}
}
- @Scheduled(fixedRate = 6000):上一次开始执行时间点之后 6 秒再执行。
- @Scheduled(fixedDelay = 6000):上一次执行完毕时间点之后 6 秒再执行。
- @Scheduled(initialDelay=1000, fixedRate=6000):第一次延迟 1 秒后执行,之后按 fixedRate 的规则每 6 秒执行一次。
(2)启动类开启定时任务
package com.example.test;
import com.example.test.util.DatabaseSnapShot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.sql.SQLException;
@SpringBootApplication
@MapperScan("com.example.test.mapper")
@EnableScheduling//启动类加这个注解
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
完整案例
1
设计思想
1)创建任务
使用一张表存放bean信息,以表中的id作为唯一标识,每次创建任务时将bean信息通过contex传入任务模板,执行任务模板时,会读取contex中的bean,反射形式创建bean对象并执行其run方法
2)任务管理
以数据库为中心,id作为当前任务唯一标识,同步管理quartz中的任务实例
3)任务操作
暂停任务,调用scheduler.pauseJob(getJobKey(jobId))
恢复任务,调用scheduler.resumeJob(getJobKey(jobId))
创建任务,调用scheduler.scheduleJob(jobDetail, trigger)
更新任务,调用scheduler.rescheduleJob(triggerKey, trigger)
立即执行任务,调用scheduler.triggerJob(getJobKey(scheduleJob.getJobId()),dataMap)
删除任务,调用scheduler.deleteJob(getJobKey(jobId))
任务操作完成后,会将状态同步更新到数据库中
4)启动初始化
每次springboot启动时,会从数据库中读取所有任务信息,并根据任务的状态标识,将任务初始化
2
表
1)定义任务表
CREATE TABLE `schedule_job` (
`job_id` varchar(255) NOT NULL COMMENT '任务id',
`bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称',
`params` varchar(2000) DEFAULT NULL COMMENT '参数',
`cron_expression` varchar(100) DEFAULT NULL COMMENT 'cron表达式',
`status` tinyint(4) DEFAULT NULL COMMENT '任务状态 0:正常 1:暂停',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`job_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='定时任务'
2)任务日志表
CREATE TABLE `schedule_job_log` (
`log_id` bigint(255) NOT NULL AUTO_INCREMENT COMMENT '任务日志id',
`job_id` varchar(255) NOT NULL COMMENT '任务id',
`bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称',
`params` varchar(2000) DEFAULT NULL COMMENT '参数',
`status` tinyint(4) NOT NULL COMMENT '任务状态 0:成功 1:失败',
`error` varchar(2000) DEFAULT NULL COMMENT '失败信息',
`times` int(11) NOT NULL COMMENT '耗时(单位:毫秒)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`log_id`) USING BTREE,
KEY `job_id` (`job_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=23979 DEFAULT CHARSET=utf8 COMMENT='定时任务日志'
3
代码
1)配置类
package com.zhanghuan.scheduler.job.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
public class ScheduleConfig {
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
//quartz参数
Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName", "RenrenScheduler");
prop.put("org.quartz.scheduler.instanceId", "AUTO");
//线程池配置
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "20");
prop.put("org.quartz.threadPool.threadPriority", "5");
//JobStore配置
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
//集群配置
prop.put("org.quartz.jobStore.isClustered", "true");
prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
prop.put("org.quartz.jobStore.misfireThreshold", "12000");
prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
//PostgreSQL数据库,需要打开此注释
//prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");
factory.setQuartzProperties(prop);
factory.setSchedulerName("RenrenScheduler");
//延时启动
factory.setStartupDelay(1);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
//可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
//设置自动启动,默认为true
factory.setAutoStartup(true);
return factory;
}
}
2)定义工具类
(1)spring中获取bean对象的工具类
package com.zhanghuan.scheduler.job.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class BaseHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 服务器启动,Spring容器初始化时,当加载了当前类为bean组件后,
* 将会调用下面方法注入ApplicationContext实例
*/
@Override
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
System.out.println("初始化了");
BaseHolder.applicationContext = arg0;
}
public static ApplicationContext getApplicationContext(){
return applicationContext;
}
/**
* 外部调用这个getBean方法就可以手动获取到bean
* 用bean组件的name来获取bean
* @param beanName
* @return
*/
@SuppressWarnings("unchecked")
public static <T>T getBean(String beanName){
return (T) applicationContext.getBean(beanName);
}
}
(2)定义通用定时任务
package com.zhanghuan.scheduler.job.utils;
import com.zhanghuan.scheduler.job.dao.ScheduleJobLogDao;
import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import com.zhanghuan.scheduler.job.entity.ScheduleJobLogEntity;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.lang.reflect.Method;
import java.util.Date;
/**
* 定时任务
*
*/
public class ScheduleJob extends QuartzJobBean {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected void executeInternal(JobExecutionContext context) {
ScheduleJobEntity scheduleJob = (ScheduleJobEntity) context.getMergedJobDataMap()
.get(ScheduleJobEntity.JOB_PARAM_KEY);
ScheduleJobLogDao scheduleJobLogDao = BaseHolder.getBean("scheduleJobLogDao");
//任务开始时间
long startTime = System.currentTimeMillis();
ScheduleJobLogEntity scheduleJobLogEntity = new ScheduleJobLogEntity();
scheduleJobLogEntity.setJobId(scheduleJob.getJobId());
scheduleJobLogEntity.setBeanName(scheduleJob.getBeanName());
scheduleJobLogEntity.setParams(scheduleJob.getParams());
scheduleJobLogEntity.setCreateTime(new Date());
try {
//执行任务
logger.debug("任务准备执行,任务ID:" + scheduleJob.getJobId());
Object target = BaseHolder.getBean(scheduleJob.getBeanName());
Method method = target.getClass().getDeclaredMethod("run", String.class);
method.invoke(target, scheduleJob.getParams());
long times = System.currentTimeMillis()-startTime;
scheduleJobLogEntity.setTimes((int)times);
//失败:1 成功:0
scheduleJobLogEntity.setStatus(0);
} catch (Exception e) {
logger.error("任务执行失败,任务ID:" + scheduleJob.getJobId(), e);
long times = System.currentTimeMillis()-startTime;
scheduleJobLogEntity.setTimes((int)times);
//失败:1 成功:0
scheduleJobLogEntity.setStatus(1);
scheduleJobLogEntity.setError(e.toString());
}finally {
scheduleJobLogDao.save(scheduleJobLogEntity);
}
}
}
(3)定义定时任务工具类
package com.zhanghuan.scheduler.job.utils;
import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import com.zhanghuan.scheduler.job.enums.ScheduleJobStatus;
import org.quartz.*;
/**
* 定时任务工具类
*
*/
public class ScheduleUtils {
private final static String JOB_NAME = "TASK_";
/**
* 获取触发器key
*/
public static TriggerKey getTriggerKey(String jobId) {
return TriggerKey.triggerKey(JOB_NAME + jobId);
}
/**
* 获取jobKey
*/
public static JobKey getJobKey(String jobId) {
return JobKey.jobKey(JOB_NAME + jobId);
}
/**
* 获取表达式触发器
*/
public static CronTrigger getCronTrigger(Scheduler scheduler, String jobId) {
try {
return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
} catch (SchedulerException e) {
throw new RuntimeException("获取定时任务CronTrigger出现异常",e);
}
}
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
try {
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getJobId())).build();
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
.withMisfireHandlingInstructionDoNothing();
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob.getJobId())).withSchedule(scheduleBuilder).build();
//放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob);
scheduler.scheduleJob(jobDetail, trigger);
//暂停任务
if(scheduleJob.getStatus() == ScheduleJobStatus.PAUSE.getValue()){
pauseJob(scheduler, scheduleJob.getJobId());
}
} catch (SchedulerException e) {
throw new RuntimeException("创建定时任务失败", e);
}
}
/**
* 更新定时任务
*/
public static void updateScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
try {
TriggerKey triggerKey = getTriggerKey(scheduleJob.getJobId());
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
.withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getJobId());
//按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//参数
trigger.getJobDataMap().put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob);
scheduler.rescheduleJob(triggerKey, trigger);
//暂停任务
if(scheduleJob.getStatus() == ScheduleJobStatus.PAUSE.getValue()){
pauseJob(scheduler, scheduleJob.getJobId());
}
} catch (SchedulerException e) {
throw new RuntimeException("更新定时任务失败", e);
}
}
/**
* 立即执行任务
*/
public static void run(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
try {
//参数
JobDataMap dataMap = new JobDataMap();
dataMap.put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob);
scheduler.triggerJob(getJobKey(scheduleJob.getJobId()),dataMap);
} catch (SchedulerException e) {
throw new RuntimeException("立即执行定时任务失败", e);
}
}
/**
* 暂停任务
*/
public static void pauseJob(Scheduler scheduler, String jobId) {
try {
scheduler.pauseJob(getJobKey(jobId));
} catch (SchedulerException e) {
throw new RuntimeException("暂停定时任务失败", e);
}
}
/**
* 恢复任务
*/
public static void resumeJob(Scheduler scheduler, String jobId) {
try {
scheduler.resumeJob(getJobKey(jobId));
} catch (SchedulerException e) {
throw new RuntimeException("暂停定时任务失败", e);
}
}
/**
* 删除定时任务
*/
public static void deleteScheduleJob(Scheduler scheduler, String jobId) {
try {
scheduler.deleteJob(getJobKey(jobId));
} catch (SchedulerException e) {
throw new RuntimeException("删除定时任务失败", e);
}
}
}
4)定义实体类
(1)任务实体
package com.zhanghuan.scheduler.job.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* 定时任务
*
*/
@Data
@Table(name = "schedule_job")
@Entity
public class ScheduleJobEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 任务调度参数key
*/
public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY";
/**
* 任务id
*/
@Id
private String jobId;
/**
* spring bean名称
*/
@Column(name = "bean_name")
private String beanName;
/**
* 参数
*/
@Column(name = "params")
private String params;
/**
* cron表达式
*/
@Column(name = "cron_expression")
private String cronExpression;
/**
* 任务状态
*/
@Column(name = "status")
private Integer status;
/**
* 备注
*/
@Column(name = "remark")
private String remark;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Column(name = "create_time")
private Date createTime;
}
(2)任务日志实体
package com.zhanghuan.scheduler.job.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* 定时任务日志
*
*/
@Data
@Table(name = "schedule_job_log")
@Entity
public class ScheduleJobLogEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 日志id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long logId;
/**
* 任务id
*/
private String jobId;
/**
* spring bean名称
*/
private String beanName;
/**
* 参数
*/
private String params;
/**
* 任务状态 0:成功 1:失败
*/
private Integer status;
/**
* 失败信息
*/
private String error;
/**
* 耗时(单位:毫秒)
*/
private Integer times;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}
(3)response实体
package com.zhanghuan.scheduler.job.entity;
import java.util.HashMap;
import java.util.Map;
/**
* 返回数据
*
*/
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R() {
put("code", 0);
put("msg", "success");
}
public Integer getCode() {
return (Integer) this.get("code");
}
public String getMsg() {
return (String) this.get("msg");
}
public static R error() {
return error(500, "未知异常,请联系管理员");
}
public static R error(String msg) {
return error(500, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}
public static R ok() {
return new R();
}
@Override
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}
5)定义任务状态的枚举类
package com.zhanghuan.scheduler.job.enums;
public enum ScheduleJobStatus {
NORMAL(0),
/**
* 暂停
*/
PAUSE(1);
private int value;
ScheduleJobStatus(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
6)定义持久层
(1)定时任务
package com.zhanghuan.scheduler.job.dao;
import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
/**
* 定时任务
*
*/
public interface ScheduleJobDao extends JpaRepository<ScheduleJobEntity,String> {
//分页按照beanName查找
@Query(value="select * from schedule_job where bean_name like concat('%',?1,'%') limit ?2,?3",nativeQuery=true)
List<ScheduleJobEntity> findAllByBeanNameLike(String beanName,int offset,int limit);
//返回beanName查找的记录数
long countAllByBeanNameLike(String beanName);
}
(2)任务日志
package com.zhanghuan.scheduler.job.dao;
import com.zhanghuan.scheduler.job.entity.ScheduleJobLogEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
/**
* 任务日志
*
*/
public interface ScheduleJobLogDao extends JpaRepository<ScheduleJobLogEntity,Long> {
//分页按照beanName查找
@Query(value="select * from schedule_job_log where bean_name like concat('%',?1,'%') order by create_time desc limit ?2,?3",nativeQuery=true)
List<ScheduleJobLogEntity> findAllByBeanNameLikeOrderByCreateTimeDesc(String beanName, int offset, int limit);
//返回beanName查找的记录数
long countAllByBeanNameLike(String beanName);
}
7)定义服务层
(1)定时任务
package com.zhanghuan.scheduler.job.service.impl;
import com.zhanghuan.scheduler.job.dao.ScheduleJobDao;
import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import com.zhanghuan.scheduler.job.enums.ScheduleJobStatus;
import com.zhanghuan.scheduler.job.service.ScheduleJobService;
import com.zhanghuan.scheduler.job.utils.ScheduleUtils;
import org.quartz.CronTrigger;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.util.*;
@Service("scheduleJobService")
public class ScheduleJobServiceImpl implements ScheduleJobService {
@Autowired
ScheduleJobDao scheduleJobDao;
@Autowired
private Scheduler scheduler;
/**
* 项目启动时,初始化定时器
*/
@PostConstruct
public void init(){
List<ScheduleJobEntity> scheduleJobList = scheduleJobDao.findAll();
for(ScheduleJobEntity scheduleJob : scheduleJobList){
CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getJobId());
//如果不存在,则创建
if(cronTrigger == null) {
ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
}else {
ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
}
}
}
@Override
public Map<String,Object> findAll(Map<String,Object> map){
int page = Integer.valueOf((String) map.get("page")) ;
int limit = Integer.valueOf((String) map.get("limit"));
String beanName = (String) map.get("beanName");
if(beanName==null){
beanName="";
}
if( StringUtils.hasLength(beanName.trim()) ){
int offset = (page-1)*limit;
List<ScheduleJobEntity> content = scheduleJobDao.findAllByBeanNameLike(beanName,offset,limit);
long total = scheduleJobDao.countAllByBeanNameLike("%"+beanName+"%");
map.put("content",content);
map.put("total",total);
}else{
PageRequest pageRequest = PageRequest.of(page-1,limit);
Page pageObj = scheduleJobDao.findAll(pageRequest);
List<ScheduleJobEntity> content = pageObj.getContent();
long total = pageObj.getTotalElements();
System.out.println(total);
map.put("content",content);
map.put("total",total);
}
return map;
}
@Override
public ScheduleJobEntity findById(String id) {
return scheduleJobDao.getOne(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveJob(ScheduleJobEntity scheduleJob) {
scheduleJob.setJobId(UUID.randomUUID().toString().replace("-",""));
scheduleJob.setCreateTime(new Date());
scheduleJob.setStatus(ScheduleJobStatus.NORMAL.getValue());
ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
scheduleJobDao.save(scheduleJob);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(ScheduleJobEntity scheduleJob) {
ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
scheduleJobDao.save(scheduleJob);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteBatch(String[] jobIds) {
for(String jobId : jobIds){
ScheduleUtils.deleteScheduleJob(scheduler, jobId);
}
List<ScheduleJobEntity> list = scheduleJobDao.findAllById(Arrays.asList(jobIds));
scheduleJobDao.deleteInBatch(list);
}
@Override
public int updateBatch(String[] jobIds, int status){
List<ScheduleJobEntity> list = scheduleJobDao.findAllById(Arrays.asList(jobIds));
Map<String, Object> map = new HashMap<>(2);
int i = 0;
for (ScheduleJobEntity job:list
) {
job.setStatus(status);
scheduleJobDao.save(job);
i++;
}
return i;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void run(String[] jobIds) {
ScheduleJobEntity jobEntity;
for(String jobId : jobIds){
jobEntity = scheduleJobDao.findById(jobId).get();
ScheduleUtils.run(scheduler, jobEntity );
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void pause(String[] jobIds) {
for(String jobId : jobIds){
ScheduleUtils.pauseJob(scheduler, jobId);
}
updateBatch(jobIds, ScheduleJobStatus.PAUSE.getValue());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void resume(String[] jobIds) {
for(String jobId : jobIds){
ScheduleUtils.resumeJob(scheduler, jobId);
}
updateBatch(jobIds,ScheduleJobStatus.NORMAL.getValue());
}
}
(2)任务日志
package com.zhanghuan.scheduler.job.service.impl;
import com.zhanghuan.scheduler.job.dao.ScheduleJobLogDao;
import com.zhanghuan.scheduler.job.entity.ScheduleJobLogEntity;
import com.zhanghuan.scheduler.job.service.ScheduleJobLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
@Service("scheduleJobLogService")
public class ScheduleJobLogServiceImpl implements ScheduleJobLogService {
@Autowired
ScheduleJobLogDao scheduleJobLogDao;
@Override
public Map<String,Object> queryPage(Map<String, Object> map) {
int page = Integer.valueOf((String) map.get("page")) ;
int limit = Integer.valueOf((String) map.get("limit"));
String beanName = (String) map.get("beanName");
if(beanName==null){
beanName="";
}
if( StringUtils.hasLength(beanName.trim()) ){
int offset = (page-1)*limit;
List<ScheduleJobLogEntity> content = scheduleJobLogDao.findAllByBeanNameLikeOrderByCreateTimeDesc(beanName,offset,limit);
long total =scheduleJobLogDao.countAllByBeanNameLike("%"+beanName+"%");
map.put("content",content);
map.put("total",total);
}else{
PageRequest pageRequest = PageRequest.of(page-1,limit, Sort.by(Sort.Direction.DESC,"createTime"));
Page pageObj = scheduleJobLogDao.findAll(pageRequest);
List<ScheduleJobLogEntity> content = pageObj.getContent();
long total = pageObj.getTotalElements();
map.put("content",content);
map.put("total",total);
}
return map;
}
}
8)定义控制器
(1)定时任务
package com.zhanghuan.scheduler.job.controller;
import com.zhanghuan.scheduler.job.entity.R;
import com.zhanghuan.scheduler.job.entity.ScheduleJobEntity;
import com.zhanghuan.scheduler.job.service.ScheduleJobService;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 定时任务
*
*/
@RestController
@RequestMapping("/sys/schedule")
@CrossOrigin
public class ScheduleJobController {
@Autowired
private ScheduleJobService scheduleJobService;
@Autowired
Scheduler scheduler;
/**
* 定时任务列表
*/
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params){
if( params==null || params.isEmpty() ){
params = new HashMap<>();
params.put("page","0");
params.put("limit","10");
}
return R.ok().put("page", scheduleJobService.findAll(params));
}
/**
* 定时任务信息
*/
@RequestMapping("/info/{jobId}")
public R info(@PathVariable("jobId") String jobId){
ScheduleJobEntity schedule = scheduleJobService.findById(jobId);
return R.ok().put("schedule", schedule);
}
/**
* 保存定时任务
*/
@RequestMapping("/save")
public R save(@RequestBody ScheduleJobEntity scheduleJob){
scheduleJobService.saveJob(scheduleJob);
return R.ok();
}
/**
* 修改定时任务
*/
@RequestMapping("/update")
public R update(@RequestBody ScheduleJobEntity scheduleJob){
scheduleJobService.update(scheduleJob);
return R.ok();
}
/**
* 删除定时任务
*/
@RequestMapping("/delete")
public R delete(@RequestBody String[] jobIds){
scheduleJobService.deleteBatch(jobIds);
return R.ok();
}
/**
* 立即执行任务
*/
@RequestMapping("/run")
public R run(@RequestBody String[] jobIds){
scheduleJobService.run(jobIds);
return R.ok();
}
/**
* 暂停定时任务
*/
@RequestMapping("/pause")
public R pause(@RequestBody String[] jobIds){
scheduleJobService.pause(jobIds);
return R.ok();
}
/**
* 恢复定时任务
*/
@RequestMapping("/resume")
public R resume(@RequestBody String[] jobIds){
scheduleJobService.resume(jobIds);
return R.ok();
}
}
(2)任务日志
package com.zhanghuan.scheduler.job.controller;
import com.zhanghuan.scheduler.job.entity.R;
import com.zhanghuan.scheduler.job.service.ScheduleJobLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/sys/schedulelog")
@CrossOrigin
public class ScheduleJobLogController {
@Autowired
ScheduleJobLogService scheduleJobLogService;
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params){
if( params==null || params.isEmpty() ){
params = new HashMap<>();
params.put("page",0);
params.put("limit",10);
}
return R.ok().put("page", scheduleJobLogService.queryPage(params));
}
}
9)定义任务bean的接口
所有任务必须实现该接口,并且注入到容器中
package com.zhanghuan.scheduler.job.task;
/**
* 定时任务接口,所有定时任务都要实现该接口
*
*/
public interface ITask {
/**
* 执行定时任务接口
*
* @param params 参数,多参数使用JSON数据
*/
void run(String params);
}
10)前端
<template>
<div>
<el-row>
<el-col :span="20" :offset="2">
<el-row>
<el-col :span="6" style="background:green">
<el-input v-model="q.beanName" placeholder="请输入beanname"></el-input>
</el-col>
<el-col :span="10" class="text-left">
<el-button @click="query()">查询</el-button>
<el-button type="primary" @click="openDiolog('add')">新增</el-button>
<el-button type="primary" @click="openDiolog('edit')">修改</el-button>
<el-button type="primary" @click="del()">删除</el-button>
<el-button type="primary" @click="pause()">暂停</el-button>
<el-button type="primary" @click="resume()">恢复</el-button>
<el-button type="primary" @click="run()">立即执行</el-button>
</el-col>
<el-col :span="8">
<el-button type="danger" style="float:right" @click="openLogDiolog()">日志列表</el-button>
</el-col>
</el-row>
<el-row style="margin-top:2rem;">
<el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55">
</el-table-column>
<el-table-column prop="jobId" label="任务ID"></el-table-column>
<el-table-column prop="beanName" label="bean名称"></el-table-column>
<el-table-column prop="params" label="参数"></el-table-column>
<el-table-column prop="cronExpression" label="cron表达式"></el-table-column>
<el-table-column prop="remark" label="备注"></el-table-column>
<el-table-column prop="status" label="状态">
<template slot-scope="scope">
<el-tag type="success" v-if="scope.row.status==0">正常</el-tag>
<el-tag type="danger" v-if="scope.row.status==1">暂停</el-tag>
</template>
</el-table-column>
</el-table>
</el-row>
</el-col>
</el-row>
<el-row style="margin-top:2rem;">
<el-col :span="20" :offset="2">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="q.page"
:page-sizes="[10, 20, 30, 40]" :page-size="q.limit" layout="total, sizes, prev, pager, next, jumper"
:total="q.total">
</el-pagination>
</el-col>
</el-row>
<el-dialog title="新增或编辑" :visible.sync="dialogFormVisible">
<el-row>
<el-col :span="4" :offset="2">
<p style="text-align:center">bean名称</p>
</el-col>
<el-col :span="16">
<el-input placeholder="spring bean名称" v-model="task.beanName"></el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="4" :offset="2">
<p style="text-align:center">参数</p>
</el-col>
<el-col :span="16">
<el-input placeholder="参数" v-model="task.params"></el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="4" :offset="2">
<p style="text-align:center">cron表达式</p>
</el-col>
<el-col :span="16">
<el-input placeholder="如:0 0 1 2 * * ?" v-model="task.cronExpression"></el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="4" :offset="2">
<p style="text-align:center">备注</p>
</el-col>
<el-col :span="16">
<el-input placeholder="备注" v-model="task.remark"></el-input>
</el-col>
</el-row>
<div slot="footer" class="dialog-footer">
<el-button @click="closeDiolog()">取 消</el-button>
<el-button type="primary" @click="submit()">确 定</el-button>
</div>
</el-dialog>
<el-dialog title="日志列表" :visible.sync="dialogLogVisible" width="100%">
<el-row>
<el-col :span="6" style="background:green">
<el-input v-model="q_log.beanName" placeholder="请输入beanname"></el-input>
</el-col>
<el-col :span="2">
<el-button @click="querylog()">查询</el-button>
</el-col>
</el-row>
<el-row style="margin-top:1rem;">
<el-col>
<el-table :data="logData">
<el-table-column prop="logId" label="id"></el-table-column>
<el-table-column prop="jobId" label="任务id"></el-table-column>
<el-table-column prop="beanName" label="beanName"></el-table-column>
<el-table-column prop="params" label="参数"></el-table-column>
<el-table-column prop="error" label="错误信息"></el-table-column>
<el-table-column prop="times" label="耗时(毫秒)"></el-table-column>
<el-table-column prop="createTime" label="执行时间"></el-table-column>
</el-table>
</el-col>
</el-row>
<el-row style="margin-top:2rem;">
<el-col>
<el-pagination @size-change="handleLogSizeChange" @current-change="handleLogCurrentChange"
:current-page="q_log.page" :page-sizes="[8, 20, 30, 40]" :page-size="q_log.limit"
layout="total, sizes, prev, pager, next, jumper" :total="q_log.total">
</el-pagination>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script>
//import func from '../../vue-temp/vue-editor-bridge';
// @ is an alias to /src
//import HelloWorld from '@/components/HelloWorld.vue'
import _ from "lodash"
export default {
name: 'Home',
data() {
return {
q: {
beanName: "",
page: 1,
limit: 10,
total: 1
},
q_log: {
beanName: "",
page: 1,
limit: 8,
total: 1
},
baseurl: this.$store.state.url,
tableData: [],
logData: [],
multipleSelection: [],
dialogFormVisible: false,
dialogLogVisible: false,
diologType: "add",
task: {
jobId: "",
beanName: "",
params: "",
cronExpression: "",
remark: ""
}
}
},
components: {
//HelloWorld
},
methods: {
toggleSelection(rows) {
if (rows) {
rows.forEach(row => {
this.$refs.multipleTable.toggleRowSelection(row);
});
} else {
this.$refs.multipleTable.clearSelection();
}
},
handleSelectionChange(val) {
this.multipleSelection = val;
},
handleSizeChange(limit) {
this.q.limit = limit
this.reload()
},
handleLogSizeChange(limit) {
this.q_log.limit = limit
this.reloadLog()
},
handleCurrentChange(page) {
this.q.page = page
this.reload()
},
handleLogCurrentChange(page) {
this.q_log.page = page
this.reloadLog()
},
query() {
this.reload()
},
querylog() {
this.reloadLog()
},
clearTask() {
this.task.jobId = ""
this.task.beanName = ""
this.task.params = ""
this.task.cronExpression = ""
this.task.remark = ""
},
closeDiolog() {
this.dialogFormVisible = false
this.clearTask()
},
openDiolog(type) {
if (type == "edit") {
if (this.multipleSelection.length == 0) {
this.$message({
message: '未选择任何任务',
type: 'warning'
});
} else if (this.multipleSelection.length > 1) {
this.$message({
message: '只可选择一条任务',
type: 'warning'
});
} else {
this.dialogFormVisible = true
this.diologType = "edit"
this.task = _.cloneDeep(this.multipleSelection[0])
}
} else if (type == "add") {
this.diologType = "add"
this.clearTask()
this.dialogFormVisible = true
}
},
openLogDiolog() {
this.q_log.beanName=""
this.reloadLog()
this.dialogLogVisible = true
},
submit() {
this.dialogFormVisible = false
if (this.diologType == "add") {
this.axios.post(this.baseurl + "/sys/schedule/save", this.task).then(res => {
if (res.data.code == 0) {
this.$message({
type: 'success',
message: '新增成功!'
});
} else {
this.$message({
type: 'error',
message: '新增失败!'
});
}
this.reload()
})
} else {
this.axios.post(this.baseurl + "/sys/schedule/update", this.task).then(res => {
if (res.data.code == 0) {
this.$message({
type: 'success',
message: '编辑成功!'
});
} else {
this.$message({
type: 'error',
message: '编辑失败!'
});
}
this.reload()
})
}
},
del() {
let ids = _.map(this.multipleSelection, "jobId")
if (ids.length == 0) {
this.$message({
message: '未选择任何任务',
type: 'warning'
});
} else {
this.$confirm('此操作将永久删除该任务, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.axios.post(this.baseurl + "/sys/schedule/delete", ids).then(res => {
if (res.data.code == 0) {
this.$message({
type: 'success',
message: '删除成功!'
});
} else {
this.$message({
type: 'error',
message: '删除失败!'
});
}
this.reload()
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
run() {
let ids = _.map(this.multipleSelection, "jobId")
if (ids.length == 0) {
this.$message({
message: '未选择任何任务',
type: 'warning'
});
} else {
this.axios.post(this.baseurl + "/sys/schedule/run", ids).then(res => {
if (res.data.code == 0) {
this.$message({
type: 'success',
message: '运行成功!'
});
} else {
this.$message({
type: 'error',
message: '运行失败!'
});
}
this.reload()
})
}
},
resume() {
let ids = _.map(this.multipleSelection, "jobId")
if (ids.length == 0) {
this.$message({
message: '未选择任何任务',
type: 'warning'
});
} else {
this.axios.post(this.baseurl + "/sys/schedule/resume", ids).then(res => {
if (res.data.code == 0) {
this.$message({
type: 'success',
message: '恢复成功!'
});
} else {
this.$message({
type: 'error',
message: '恢复失败!'
});
}
this.reload()
})
}
},
pause() {
let ids = _.map(this.multipleSelection, "jobId")
if (ids.length == 0) {
this.$message({
message: '未选择任何任务',
type: 'warning'
});
} else {
this.axios.post(this.baseurl + "/sys/schedule/pause", ids).then(res => {
if (res.data.code == 0) {
this.$message({
type: 'success',
message: '暂停成功!'
});
} else {
this.$message({
type: 'error',
message: '暂停失败!'
});
}
this.reload()
this.reload()
})
}
},
reload() {
let that = this
this.axios.get(this.baseurl + "/sys/schedule/list?" + "beanName=" + this.q.beanName + "&page=" + this.q.page +
"&limit=" + this.q.limit).then(res => {
that.$set(that.$data, 'tableData', res.data.page.content)
that.q.total = res.data.page.total
})
},
reloadLog() {
let that = this
this.axios.get(this.baseurl + "/sys/schedulelog/list?" + "beanName=" + this.q_log.beanName + "&page=" + this
.q_log.page +
"&limit=" + this.q_log.limit).then(res => {
that.$set(that.$data, 'logData', res.data.page.content)
that.q_log.total = res.data.page.total
})
}
},
created() {
this.reload()
}
}
</script>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)