生命无罪,健康万岁,我是laity。
我曾七次鄙视自己的灵魂:
第一次,当它本可进取时,却故作谦卑;
第二次,当它在空虚时,用爱欲来填充;
第三次,在困难和容易之间,它选择了容易;
第四次,它犯了错,却借由别人也会犯错来宽慰自己;
第五次,它自由软弱,却把它认为是生命的坚韧;
第六次,当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副;
第七次,它侧身于生活的污泥中,虽不甘心,却又畏首畏尾。
本文带各位学习下Quartz的基本使用及业务中的整合,包括基本概念以及如何动态地对定时任务进行CRUD,并且如何实现定时任务的持久化以及任务恢复;其中分享下本人在使用时遇到的问题,和解决方案。
Quartz的基本使用
Quartz 是一个开源的作业调度框架,支持分布式定时任务,Quartz定时任务据我了解可分为Trigger(触发器)、Job(任务)和Scheduler(调度器),定时任务的逻辑大体为:创建触发器和任务,并将其加入到调度器中。
Quartz 的核心类有以下三部分:
- 任务 Job : 需要实现的任务类,实现
execute()
方法,执行后完成任务; - 触发器 Trigger : 包括
SimpleTrigger
和CronTrigger
; - 调度器 Scheduler : 任务调度器,负责基于
Trigger
触发器,来执行 Job任务.
Trigger 有五种触发器:
- SimpleTrigger 触发器:需要在特定的日期/时间启动,且以指定的间隔时间(单位毫秒)重复执行 n 次任务,如 :在 9:00 开始,每隔1小时,每隔几分钟,每隔几秒钟执行一次 。没办法指定每隔一个月执行一次(每月的时间间隔不是固定值)。
- CalendarIntervalTrigger 触发器:指定从某一个时间开始,以一定的时间间隔(单位有秒,分钟,小时,天,月,年,星期)执行的任务。
- DailyTimeIntervalTrigger 触发器:指定每天的某个时间段内,以一定的时间间隔执行任务。并且支持指定星期。如:指定每天 9:00 至 18:00 ,每隔 70 秒执行一次,并且只要周一至周五执行。
- CronTrigger 触发器:基于日历的任务调度器,即指定星期、日期的某时间执行任务。
- NthIncludedDayTrigger 触发器:不同时间间隔的第 n 天执行任务。比如,在每个月的第 15 日处理财务发票记帐,同样设定双休日或者假期。
使用场景
- 发布消息、问卷等信息时,发布者可以指定星期、月份的具体时间进行定时发布(cron 触发器)
- 设置当天或指定日期的时间范围内,指定时间间隔执行任务。
- 其他定时功能可根据不同的任务触发器进行实现。
依赖的引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
简单的测试
将job封装给jobDetail,由调度器scheudler根据触发器trggier条件触发相应的jobDetail,每次触发都会让jobDetail重新创建job对象,并且jobDetail会将数据传给job
有两种方式:
1.jobDetail会根据自己usingJobData中的参数主动调用job对应的set方法,设置给job使用。
2.*job可以从重写方法传过来的参数jobExecutionContext中获取jobDetail,*然后从jobDetail中获取到jobDataMap。
/**
* @author: Laity
* @Project: JavaLaity
* @Description: 测试定时任务并获取自定义参数
*/
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext content) throws JobExecutionException {
long count = (long) content.getJobDetail().getJobDataMap().get("count");
System.out.println("当前执行,第" + count + "次");
content.getJobDetail().getJobDataMap().put("count", ++count);
System.out.println("任务执行.....");
}
public static void main(String[] args) throws Exception {
// 1.创建调度器 Scheduler
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
// 2.创建JobDetail实例,并与MyJob类绑定(Job执行内容)
JobDetail job = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1")
.usingJobData("count", 1L)
.build();
// 3.构建Trigger实例,每隔3s执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
// 4.执行,开启调度器
scheduler.scheduleJob(job, trigger);
System.out.println(System.currentTimeMillis());
scheduler.start();
//主线程睡眠1分钟,然后关闭调度器
TimeUnit.MINUTES.sleep(1);
scheduler.shutdown();
System.out.println(System.currentTimeMillis());
}
}
Quartz高级使用
当遇到更新版本等情况时,肯定要将程序给停了,但是程序停止后那些还未开始或者没执行完的定时任务就没了。所以我们需要将任务持久化到数据库中,然后在程序启动时将这些任务进行恢复。
数据库表设计
- 官方提供了一份数据库表设计,有兴趣的小伙伴可以去下载
DROP TABLE IF EXISTS `quartz_entity`;
CREATE TABLE `quartz_entity` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`job_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务名',
`group_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '任务分组',
`start_time` timestamp DEFAULT NULL COMMENT '任务开始时间',
`end_time` timestamp DEFAULT NULL COMMENT '任务结束时间',
`job_class` varchar(255) DEFAULT NULL COMMENT '定时任务所在的类',
`cron` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'cron表达式',
`job_data_map_json` varchar(255) DEFAULT NULL COMMENT 'json格式的jobDataMap',
`status` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0' COMMENT '任务状态。0-进行中;1-已完成;2-取消',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务信息';
SET FOREIGN_KEY_CHECKS = 1;
application-local.yml配置
spring:
info:
build:
encoding: UTF-8
datasource:
dynamic:
druid:
initial-size: 10
# 初始化大小,最小,最大
min-idle: 20
maxActive: 500
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
testWhileIdle: true
testOnBorrow: true
validation-query: SELECT 1
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall
filter:
wall:
config:
multi-statement-allow: true
none-base-statement-allow: true
enabled: true
# 配置DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
# 配置DruidStatViewServlet
stat-view-servlet:
enabled: true
url-pattern: "/druid/*"
allow:
deny:
reset-enable: false
login-username: admin
login-password: 111111
query-timeout: 36000
primary: slave
strict: false
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/jxgl?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: wang9264
driver-class-name: com.mysql.jdbc.Driver
slave:
url: jdbc:mysql://127.0.0.1:3306/java?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: wang9264
driver-class-name: com.mysql.jdbc.Driver
实体类的创建
/**
* @author: Laity
* @Project: JavaLaity
*/
@Data
public class QuartzEntity {
@TableId(value = "id",type = IdType.AUTO)
private Long id;
private String jobName;
private String groupName;
private Date startTime;
private Date endTime;
private String jobClass;
private String cron;
private String jobDataMapJson;
private String status;
}
service层
/**
* @author: Laity
* @Project: JavaLaity
*/
public interface QuartzService {
void save(QuartzEntity entity);
boolean modifyJob(QuartzEntity entity);
boolean modifyTaskStatus(String jobName,String status);
List<QuartzEntity> notStartOrNotEndJobs();
}
serviceImpl层
/**
* @author: Laity
* @Project: JavaLaity
*/
@Service("quartzService")
public class QuartzServiceImpl implements QuartzService {
@Resource
private QuartzDao quartzMapper;
@Override
public void save(QuartzEntity entity) {
quartzMapper.insert(entity);
}
@Override
public boolean modifyJob(QuartzEntity entity) {
LambdaQueryWrapper<QuartzEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(QuartzEntity::getJobName, entity.getJobName());
QuartzEntity one = quartzMapper.selectOne(wrapper);
if (one != null) {
entity.setId(one.getId());
return quartzMapper.updateById(entity) > 0;
}
return false;
}
@Override
public boolean modifyTaskStatus(String jobName, String status) {
LambdaQueryWrapper<QuartzEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(QuartzEntity::getJobName, jobName);
QuartzEntity one = quartzMapper.selectOne(wrapper);
if (one != null) {
one.setStatus(status);
return quartzMapper.updateById(one) > 0;
}
return false;
}
@Override
public List<QuartzEntity> notStartOrNotEndJobs() {
return quartzMapper.notStartOrNotEndJobs();
}
}
Dao层
/**
* @author: Laity
* @Project: JavaLaity
*/
@Mapper
public interface QuartzDao extends BaseMapper<QuartzEntity> {
@Select("SELECT " +
" * " +
"FROM " +
" quartz_entity " +
"WHERE " +
" ( end_time IS NULL " + // 没有结束时间的
" OR ( start_time < NOW() AND end_time > NOW()) " + // 已经开始但未结束的
" OR start_time > NOW() " + // 还未开始的
" ) " +
" AND `status` = '0'")
List<QuartzEntity> notStartOrNotEndJobs();
}
封装组件
QuartzUtil.java
封装了 定时任务的创建、定时任务的修改、定时任务的结束、定时任务的查询、定时任务的恢复(重启服务的时候使用)
/**
* @author: Laity
* @Project: JavaLaity
*/
@Component
public class QuartzUtil {
private static final SchedulerFactory SCHEDULER_FACTORY = new StdSchedulerFactory();
@Autowired
private QuartzService quartzService;
/**
* 添加一个定时任务
*
* @param name 任务名。每个任务唯一,不能重复。方便起见,触发器名也设为这个
* @param group 任务分组。方便起见,触发器分组也设为这个
* @param jobClass 任务的类类型 eg:MyJob.class
* @param startTime 任务开始时间。传null就是立即开始
* @param endTime 任务结束时间。如果是一次性任务或永久执行的任务就传null
* @param cron 时间设置表达式。传null就是一次性任务
*/
public boolean addJob(String name, String group, Class<? extends Job> jobClass,
Date startTime, Date endTime, String cron, JobDataMap jobDataMap) {
try {
// 第一步: 定义一个JobDetail
JobDetail jobDetail = JobBuilder.newJob(jobClass).
withIdentity(name, group).setJobData(jobDataMap).build();
// 第二步: 设置触发器
TriggerBuilder<Trigger> triggerBuilder = newTrigger();
triggerBuilder.withIdentity(name, group);
triggerBuilder.startAt(toStartDate(startTime));
triggerBuilder.endAt(toEndDate(endTime)); //设为null则表示不会停止
if (StrUtil.isNotEmpty(cron)) {
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
}
Trigger trigger = triggerBuilder.build();
//第三步:调度器设置
Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
//存储到数据库中
QuartzEntity entity = new QuartzEntity();
entity.setJobName(name);
entity.setGroupName(group);
entity.setStartTime(startTime != null ? startTime : new Date());
entity.setEndTime(endTime);
entity.setJobClass(jobClass.getName());
entity.setCron(cron);
entity.setJobDataMapJson(JSONUtil.toJsonStr(jobDataMap));
entity.setStatus("0");
quartzService.save(entity);
return true;
}
/**
* 修改一个任务的开始时间、结束时间、cron。不改的就传null
*
* @param name 任务名。每个任务唯一,不能重复。方便起见,触发器名也设为这个
* @param group 任务分组。方便起见,触发器分组也设为这个
* @param newStartTime 新的开始时间
* @param newEndTime 新的结束时间
* @param cron 新的时间表达式
*/
public boolean modifyJobTime(String name, String group, Date newStartTime,
Date newEndTime, String cron) {
try {
Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
Trigger oldTrigger = scheduler.getTrigger(triggerKey);
if (oldTrigger == null) {
return false;
}
TriggerBuilder<Trigger> triggerBuilder = newTrigger();
triggerBuilder.withIdentity(name, group);
if (newStartTime != null) {
triggerBuilder.startAt(toStartDate(newStartTime)); // 任务开始时间设定
} else if (oldTrigger.getStartTime() != null) {
triggerBuilder.startAt(oldTrigger.getStartTime()); //没有传入新的开始时间就不变
}
if (newEndTime != null) {
triggerBuilder.endAt(toEndDate(newEndTime)); // 任务结束时间设定
} else if (oldTrigger.getEndTime() != null) {
triggerBuilder.endAt(oldTrigger.getEndTime()); //没有传入新的结束时间就不变
}
if (StrUtil.isNotEmpty(cron)) {
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
} else if (oldTrigger instanceof CronTrigger) {
String oldCron = ((CronTrigger) oldTrigger).getCronExpression();
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(oldCron));
}
Trigger newTrigger = triggerBuilder.build();
scheduler.rescheduleJob(triggerKey, newTrigger); // 修改触发时间
} catch (Exception e) {
e.printStackTrace();
return false;
}
// 修改数据库中的记录
QuartzEntity entity = new QuartzEntity();
entity.setJobName(name);
entity.setGroupName(group);
if (newStartTime != null) {
entity.setStartTime(newStartTime);
}
if (newEndTime != null) {
entity.setEndTime(newEndTime);
}
if (StrUtil.isNotEmpty(cron)) {
entity.setCron(cron);
}
return quartzService.modifyJob(entity);
}
/**
* 结束任务
* @param jobName 任务名称
* @param groupName 分组名称
* @return boolean
*/
public boolean cancelJob(String jobName, String groupName) {
try {
Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, groupName);
scheduler.pauseTrigger(triggerKey); // 停止触发器
scheduler.unscheduleJob(triggerKey); // 移除触发器
scheduler.deleteJob(JobKey.jobKey(jobName, groupName)); // 删除任务
} catch (Exception e) {
e.printStackTrace();
return false;
}
//将数据库中的任务状态设为 取消
return quartzService.modifyTaskStatus(jobName, "2");
}
/**
* 获取所有job任务信息
* @return list
* @throws SchedulerException error
*/
public List<QuartzEntity> getAllJobs() throws SchedulerException {
Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
List<QuartzEntity> quartzJobs = new ArrayList<>();
try {
List<String> triggerGroupNames = scheduler.getTriggerGroupNames();
for (String groupName : triggerGroupNames) {
GroupMatcher<TriggerKey> groupMatcher = GroupMatcher.groupEquals(groupName);
Set<TriggerKey> triggerKeySet = scheduler.getTriggerKeys(groupMatcher);
for (TriggerKey triggerKey : triggerKeySet) {
Trigger trigger = scheduler.getTrigger(triggerKey);
JobKey jobKey = trigger.getJobKey();
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
//组装数据
QuartzEntity entity = new QuartzEntity();
entity.setJobName(jobDetail.getKey().getName());
entity.setGroupName(jobDetail.getKey().getGroup());
entity.setStartTime(trigger.getStartTime());
entity.setEndTime(trigger.getStartTime());
entity.setJobClass(jobDetail.getJobClass().getName());
if (trigger instanceof CronTrigger) {
entity.setCron(((CronTrigger) trigger).getCronExpression());
}
entity.setJobDataMapJson(JSONUtil.toJsonStr(jobDetail.getJobDataMap()));
quartzJobs.add(entity);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return quartzJobs;
}
public void recoveryAllJob() {
List<QuartzEntity> tasks = quartzService.notStartOrNotEndJobs();
if (tasks != null && tasks.size() > 0) {
for (QuartzEntity task : tasks) {
try {
JobDataMap jobDataMap = JSONUtil.toBean(task.getJobDataMapJson(), JobDataMap.class);
JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(task.getJobClass()))
.withIdentity(task.getJobName(), task.getGroupName())
.setJobData(jobDataMap).build();
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
triggerBuilder.withIdentity(task.getJobName(), task.getGroupName());
triggerBuilder.startAt(toStartDate(task.getStartTime()));
triggerBuilder.endAt(toEndDate(task.getEndTime()));
if (StrUtil.isNotEmpty(task.getCron())) {
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(task.getCron()));
}
Trigger trigger = triggerBuilder.build();
Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private static Date toEndDate(Date endDateTime) {
// endDateTime为null时转换会报空指针异常,所以需要进行null判断。
// 结束时间可以为null,所以endDateTime为null,直接返回null即可
return endDateTime != null ?
DateUtil.date(endDateTime) : null;
}
private static Date toStartDate(Date startDateTime) {
// startDateTime为空时返回当前时间,表示立即开始
return startDateTime != null ?
DateUtil.date(startDateTime) : new Date();
}
}
SpringContextJobUtil.java
用于获取Bean
/**
* @author: Laity
* @Project: JavaLaity
*/
@Component
public class SpringContextJobUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
@SuppressWarnings("static-access")
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
}
public static Object getBean(String beanName) {
return context.getBean(beanName);
}
}
CronUtil.java
你不可能让用户来输入cron表达式,所以根据用户的选择来解析成cron表达式
package com.ys.control_core.util.job;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author: Laity
* @Project: JavaLaity
* @Description: 用于生成Cron表达式
*/
public class CronUtil {
/**
* 每天
*/
private static final int DAY_JOB_TYPE = 1;
/**
* 每周
*/
private static final int WEEK_JOB_TYPE = 2;
/**
* 每月
*/
private static final int MONTH_JOB_TYPE = 3;
/**
* 构建Cron表达式
*
* @param jobType 作业类型: 1/每天; 2/每周; 3/每月
* @param minute 指定分钟
* @param hour 指定小时
* @param lastDayOfMonth 指定一个月的最后一天:0/不指定;1/指定
* @param weekDays 指定一周哪几天:1/星期天; 2/...3/.. ; 7/星期六
* @param monthDays 指定一个月的哪几天
* @return String
*/
public static String createCronExpression(Integer jobType, Integer minute, Integer hour, Integer lastDayOfMonth, List<Integer> weekDays, List<Integer> monthDays) {
StringBuilder cronExp = new StringBuilder();
// 秒
cronExp.append("0 ");
// 指定分钟,为空则默认0分
cronExp.append(minute == null ? "0" : minute).append(" ");
// 指定小时,为空则默认0时
cronExp.append(hour == null ? "0" : hour).append(" ");
// 每天
if (jobType == DAY_JOB_TYPE) {
// 日
cronExp.append("* ");
// 月
cronExp.append("* ");
// 周
cronExp.append("?");
} else if (lastDayOfMonth != null && lastDayOfMonth == 1) {
// 日
cronExp.append("L ");
// 月
cronExp.append("* ");
// 周
cronExp.append("?");
}
// 按每周
else if (weekDays != null && jobType == WEEK_JOB_TYPE) {
// 日
cronExp.append("? ");
// 月
cronExp.append("* ");
// 一个周的哪几天
cronExp.append(StringUtils.join(weekDays, ","));
}
// 按每月
else if (monthDays != null && jobType == MONTH_JOB_TYPE) {
// 日
cronExp.append(StringUtils.join(monthDays, ",")).append(" ");
// 月
cronExp.append("* ");
// 周
cronExp.append("?");
} else {
cronExp.append("* ").append("* ").append("?");
}
return cronExp.toString();
}
public static void main(String[] args) {
String cronExpression = createCronExpression(1, 26, null, null, null, null);
createCronExpression(2, 26, 9, 0, null, null);
// 0/2 * * * * ?
System.out.println(cronExpression);
}
/*
{
"jobType":2,
"times":[
{
"minute":"30",
"hour":"8"
},
{
"minute":"00",
"hour":"20"
}
],
"weekDays":[1,2]
}
*/
}
ValidCron.java
用于检验Cron表达式的正确性
/**
* @author: Laity
* @Project: JavaLaity
*/
public class ValidCronUtil {
public static boolean isValidCronExpression(String exp) {
if (exp == null || exp.length() ==0) return false;
boolean validExpression = CronExpression.isValidExpression(exp);
if (validExpression) System.out.println("cron expression is valid.");
return validExpression;
}
public static void main(String[] args) {
String cron = "0 26 9 ? * 1,2,3,4,5";
boolean validCronExpression = isValidCronExpression(cron);
System.out.println(validCronExpression);
}
}
Controller层
/**
* @author: Laity
* @Project: JavaLaity
*/
@RestController
@RequestMapping("/quartz/web")
@Api(tags = "定时任务相关接口API")
public class QuartzWebController {
@Autowired
private QuartzUtil quartzUtil;
@Autowired
private QuartzWebService quartzWebService;
@PostMapping("/add-job")
@ApiOperation(value = "添加任务", notes = "添加任务", httpMethod = "POST")
public Rs AddQuartz(@Valid @RequestBody CreateJobParam entity) {
JobDataMap jobDataMap = getJobDataMap(entity);
String exp = CronUtil.createCronExpression(2, (Integer) jobDataMap.get("minute"), (Integer) jobDataMap.get("hour"), null, (List<Integer>) jobDataMap.get("weekDays"), null);
boolean res = ValidCronUtil.isValidCronExpression(exp);
if (!res) GlobalException.cast("参数有误!");
entity.setCron(exp);
boolean result = quartzUtil.addJob(entity.getJobname(), QuartzGroupEnum.T1.getValue(), MyJob.class,
entity.getStarttime(), entity.getEndtime(), entity.getCron(), jobDataMap, entity.getRoleid());
return result ? Rs.success("添加成功") : Rs.error("添加失败");
}
@PostMapping("/modify-job")
@ApiOperation(value = "修改任务", notes = "修改任务", httpMethod = "POST")
public Rs modifyQuartz(@Valid @RequestBody UpdateJobParam entity) {
JobDataMap jobDataMap = new JobDataMap();
// false || false || true
if (entity.getMinute() != null || entity.getHour() != null || entity.getWeekDays() != null) {
String exp = CronUtil.createCronExpression(2, entity.getMinute(), entity.getHour(), null, entity.getWeekDays(), null);
boolean res = ValidCronUtil.isValidCronExpression(exp);
if (!res) GlobalException.cast("参数有误!");
entity.setCron(exp);
jobDataMap.put("minute", entity.getMinute());
jobDataMap.put("hour", entity.getHour());
jobDataMap.put("weekDays", entity.getWeekDays());
}
if (entity.getRoleid() != null) {
jobDataMap.put("roleId", entity.getRoleid());
}
if (entity.getSendMessage() != null) {
jobDataMap.put("megContent", entity.getSendMessage());
}
if (entity.getDayType() != null) {
jobDataMap.put("dayType", entity.getDayType() == null ? null : 1);
}
boolean result = quartzUtil.modifyJobTime(entity.getJobname(), QuartzGroupEnum.T1.getValue(),
entity.getStarttime(), entity.getEndtime(), entity.getCron(), entity.getId(), jobDataMap, entity.getRoleid());
return result ? Rs.success("修改成功") : Rs.success("修改失败");
}
@PostMapping("/cancel-job")
@ApiOperation(value = "停止任务", notes = "停止任务", httpMethod = "POST")
public Rs cancelTimeQuartz(@RequestBody QuartzEntity entity) {
boolean result = quartzUtil.cancelJob(entity.getJobname(), QuartzGroupEnum.T1.getValue());
return result ? Rs.success("操作成功") : Rs.success("操作失败");
}
@GetMapping("/get-all-jobs")
@ApiOperation(value = "查询正在执行的任务", notes = "查询正在执行的任务", httpMethod = "GET")
public Rs getAllJobs() throws SchedulerException {
return Rs.success(quartzUtil.getAllJobs());
}
@GetMapping("/query-all-job")
@ApiOperation(value = "查询所有创建的任务", notes = "查询所有创建的任务", httpMethod = "GET")
public Rs getAllJob() {
return Rs.success(quartzWebService.queryJobAll());
}
private JobDataMap getJobDataMap(CreateJobParam entity) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("megContent", entity.getSendMessage());
jobDataMap.put("roleId", entity.getRoleid());
jobDataMap.put("dayType", entity.getDayType() == null ? null : 1);
jobDataMap.put("minute", entity.getMinute());
jobDataMap.put("hour", entity.getHour());
jobDataMap.put("weekDays", entity.getWeekDays());
return jobDataMap;
}
}
Application启动类配置
/**
* @author Laity
*/
@MapperScan("com.laity.control_core.dao")
@ComponentScan({"com.laity.*"})
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@SpringBootApplication() // exclude = {SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class}
public class ControlApplication implements ApplicationRunner {
@Resource
private QuartzUtil quartzUtil;
public static void main(String[] args) {
SpringApplication.run(ControlApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
quartzUtil.recoveryAllJob();
}
}
MyJob定时业务
/**
* @author: Laity
* @Project: JavaLaity
*/
@Component("MysqlJob")
public class MysqlJob implements Job {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey key = context.getJobDetail().getKey();
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
System.out.println(key.getName());
String megContent = (String) context.getTrigger().getJobDataMap().get("megContent");
Integer roleId = (Integer) context.getTrigger().getJobDataMap().get("roleId");
Integer dayType = (Integer) context.getTrigger().getJobDataMap().get("dayType");
// 需要使用ServiceBean => ARR arr = (ARR) SpringContextJobUtil.getBean("arrWebService");
……
}
SchedulerListener监听器
/**
* @author: Laity
* @Project: JavaLaity
* @Description: 全局监听器 - 接收所有的Trigger/Job的事件通知
*/
public class MyJobListener implements SchedulerListener {
@Override
public void jobScheduled(Trigger trigger) {
// 用于部署JobDetail时调用
String jobName = trigger.getJobKey().getName();
System.out.println("用于部署JobDetail时调用==>" + jobName);
}
@Override
public void jobUnscheduled(TriggerKey triggerKey) {
// 用于卸载JobDetail时调用
System.out.println(triggerKey + "完成卸载");
}
@Override
public void triggerFinalized(Trigger trigger) {
// 当endTime到了就会执行
System.out.println("触发器被移除:" + trigger.getJobKey().getName());
QuartzWebService quartzService = (QuartzWebService) SpringContextJobUtil.getBean("quartzService");
quartzService.modifyTaskStatus(trigger.getJobKey().getName(), "2");
}
@Override
public void triggerPaused(TriggerKey triggerKey) {
System.out.println(triggerKey + "正在被暂停");
}
@Override
public void triggersPaused(String s) {
// s = triggerGroup
System.out.println("触发器组:" + s + ",正在被暂停");
}
@Override
public void triggerResumed(TriggerKey triggerKey) {
System.out.println(triggerKey + "正在从暂停中恢复");
}
@Override
public void triggersResumed(String s) {
System.out.println("触发器组:" + s + ",正在从暂停中恢复");
}
@Override
public void jobAdded(JobDetail jobDetail) {
System.out.println(jobDetail.getKey() + "=>已添加工作任务");
}
@Override
public void jobDeleted(JobKey jobKey) {
System.out.println(jobKey + "=> 已删除该工作任务");
}
@Override
public void jobPaused(JobKey jobKey) {
System.out.println(jobKey + "=> 工作任务正在被暂停");
}
@Override
public void jobsPaused(String s) {
System.out.println("工作任务组:" + s + ",正在被暂停");
}
@Override
public void jobResumed(JobKey jobKey) {
System.out.println(jobKey + "jobKey正在从暂停中恢复");
}
@Override
public void jobsResumed(String s) {
System.out.println("工作任务组:" + s + ",正在从暂停中恢复");
}
@Override
public void schedulerError(String s, SchedulerException e) {
}
@Override
public void schedulerInStandbyMode() {
}
@Override
public void schedulerStarted() {
}
@Override
public void schedulerStarting() {
System.out.println("=============================开启监听===========================");
}
@Override
public void schedulerShutdown() {
}
@Override
public void schedulerShuttingdown() {
}
@Override
public void schedulingDataCleared() {
}
}
监听器使用
-
scheduler.scheduleJob(jobDetail, trigger); scheduler.getListenerManager().addSchedulerListener(new MyJobListener()); // 使用监听器
封装接收前端Param
/**
* @author: Laity
* @Project: JavaLaity
*/
@Data
@ApiModel(value = "创建定时任务")
@Accessors(chain = true)
public class CreateJobParam {
@ApiModelProperty(value = "任务名称")
@NotBlank(message = "任务名称不能为空")
private String jobname;
@ApiModelProperty(value = "开始时间")
private Date starttime;
@ApiModelProperty(value = "结束时间")
private Date endtime;
// @Ignore
@ApiModelProperty(value = "cron表达式")
private String cron;
@ApiModelProperty(value = "角色id")
@NotNull(message = "角色ID不能为空")
private Integer roleid;
@ApiModelProperty(value = "消息内容")
@NotBlank(message = "消息内容不能为空")
private String sendMessage;
@ApiModelProperty(value = "因为有的消息是发给昨日的某人,所以设立此标识符,正常的不用传值,非正常:1")
private Integer dayType;
@ApiModelProperty(value = "指定分钟 0-60")
@Max(60)
@Min(0)
private Integer minute;
@Max(24)
@Min(0)
@ApiModelProperty(value = "指定小时 0-24")
private Integer hour;
@ApiModelProperty(value = "星期列表: 1/星期天、2/星期一、3/...、7/星期六")
private List<Integer> weekDays;
}
测试
说明
可根据自己的需求自行配置其余配置:
多线程、Reids缓存、MySQL、Quartz其余配置等
解决问题
修改Quartz中的JobDetailMap数据
因为我在JobDetailMap中放入了一些数据,但是修改之后数据不发生变化
解决思路:
最早写法是:
// addjob中存jobDetail
JobDetail jobDetail = JobBuilder.newJob(jobClass).
withIdentity(name, group).setJobData(jobDataMap).build();
TriggerBuilder<Trigger> triggerBuilder = newTrigger();
更改后写法:
// 在构建Trigger实例时使用.usingJobData()方法实现
TriggerBuilder<Trigger> triggerBuilder = newTrigger();
triggerBuilder.withIdentity(name, group);
triggerBuilder.startAt(toStartDate(startTime));
triggerBuilder.endAt(toEndDate(endTime));
triggerBuilder.usingJobData(jobDataMap); // usingJobData传入jobDataMap
其中出现的问题:停止服务,查询配置不一致
存数据库最初写法:
// getAllJobs
entity.setJobDataMapJson(JSONUtil.toJsonStr(jobDetail.getJobDataMap()));
现在写法:
// oldTrigger需要自己构建
entity.setJobdatamapjson(JSONUtil.toJsonStr(oldTrigger.getJobDataMap()));
那么任务job的数据呢?
最早的写法:获取jobDataMap数据
String megContent = (String) context.getJobDetail().getJobDataMap().get("megContent");
Integer roleId = (Integer) context.getJobDetail().getJobDataMap().get("roleId");
Integer dayType = (Integer) context.getJobDetail().getJobDataMap().get("dayType");
期间也断点调试使用过其它数据获取方式
String megContent1 = (String) jobDataMap.get("megContent");
Integer roleId1 = (Integer) jobDataMap.get("roleId");
Integer dayType1 = (Integer) jobDataMap.get("dayType");
最终实现写法:数据不论是临时修改还是怎么都可以实时更新
String megContent = (String) context.getTrigger().getJobDataMap().get("megContent");
Integer roleId = (Integer) context.getTrigger().getJobDataMap().get("roleId");
Integer dayType = (Integer) context.getTrigger().getJobDataMap().get("dayType");
昨日之深渊,今日之浅谈;我是Laity,正在前行的Laity。