基于Quartz实现动态定时任务

生命无罪,健康万岁,我是laity。

我曾七次鄙视自己的灵魂:

第一次,当它本可进取时,却故作谦卑;

第二次,当它在空虚时,用爱欲来填充;

第三次,在困难和容易之间,它选择了容易;

第四次,它犯了错,却借由别人也会犯错来宽慰自己;

第五次,它自由软弱,却把它认为是生命的坚韧;

第六次,当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副;

第七次,它侧身于生活的污泥中,虽不甘心,却又畏首畏尾。

本文带各位学习下Quartz的基本使用及业务中的整合,包括基本概念以及如何动态地对定时任务进行CRUD,并且如何实现定时任务的持久化以及任务恢复;其中分享下本人在使用时遇到的问题,和解决方案。

Quartz的基本使用

Quartz 是一个开源的作业调度框架,支持分布式定时任务,Quartz定时任务据我了解可分为Trigger(触发器)Job(任务)和Scheduler(调度器),定时任务的逻辑大体为:创建触发器和任务,并将其加入到调度器中。

Quartz 的核心类有以下三部分:

  • 任务 Job : 需要实现的任务类,实现 execute() 方法,执行后完成任务;
  • 触发器 Trigger : 包括 SimpleTriggerCronTrigger;
  • 调度器 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 {@Overridepublic 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.创建调度器 SchedulerSchedulerFactory 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-8datasource:dynamic:druid:initial-size: 10# 初始化大小,最小,最大min-idle: 20maxActive: 500# 配置获取连接等待超时的时间maxWait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒minEvictableIdleTimeMillis: 300000testWhileIdle: truetestOnBorrow: truevalidation-query: SELECT 1testOnReturn: false# 打开PSCache,并且指定每个连接上PSCache的大小poolPreparedStatements: truemaxPoolPreparedStatementPerConnectionSize: 20filters: stat,wallfilter:wall:config:multi-statement-allow: truenone-base-statement-allow: trueenabled: true# 配置DruidStatFilterweb-stat-filter:enabled: trueurl-pattern: "/*"exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"# 配置DruidStatViewServletstat-view-servlet:enabled: trueurl-pattern: "/druid/*"allow:deny:reset-enable: falselogin-username: adminlogin-password: 111111query-timeout: 36000primary: slavestrict: falsedatasource:master:url: jdbc:mysql://127.0.0.1:3306/jxgl?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: wang9264driver-class-name: com.mysql.jdbc.Driverslave:url: jdbc:mysql://127.0.0.1:3306/java?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: wang9264driver-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 {@Resourceprivate QuartzDao quartzMapper;@Overridepublic void save(QuartzEntity entity) {quartzMapper.insert(entity);}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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();@Autowiredprivate 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 {// 第一步: 定义一个JobDetailJobDetail 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 {@Autowiredprivate QuartzUtil quartzUtil;@Autowiredprivate 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 || trueif (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 {@Resourceprivate QuartzUtil quartzUtil;public static void main(String[] args) {SpringApplication.run(ControlApplication.class, args);}@Overridepublic 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());@Overridepublic 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 {@Overridepublic void jobScheduled(Trigger trigger) {// 用于部署JobDetail时调用String jobName = trigger.getJobKey().getName();System.out.println("用于部署JobDetail时调用==>" + jobName);}@Overridepublic void jobUnscheduled(TriggerKey triggerKey) {// 用于卸载JobDetail时调用System.out.println(triggerKey + "完成卸载");}@Overridepublic void triggerFinalized(Trigger trigger) {// 当endTime到了就会执行System.out.println("触发器被移除:" + trigger.getJobKey().getName());QuartzWebService quartzService = (QuartzWebService) SpringContextJobUtil.getBean("quartzService");quartzService.modifyTaskStatus(trigger.getJobKey().getName(), "2");}@Overridepublic void triggerPaused(TriggerKey triggerKey) {System.out.println(triggerKey + "正在被暂停");}@Overridepublic void triggersPaused(String s) {// s = triggerGroupSystem.out.println("触发器组:" + s + ",正在被暂停");}@Overridepublic void triggerResumed(TriggerKey triggerKey) {System.out.println(triggerKey + "正在从暂停中恢复");}@Overridepublic void triggersResumed(String s) {System.out.println("触发器组:" + s + ",正在从暂停中恢复");}@Overridepublic void jobAdded(JobDetail jobDetail) {System.out.println(jobDetail.getKey() + "=>已添加工作任务");}@Overridepublic void jobDeleted(JobKey jobKey) {System.out.println(jobKey + "=> 已删除该工作任务");}@Overridepublic void jobPaused(JobKey jobKey) {System.out.println(jobKey + "=> 工作任务正在被暂停");}@Overridepublic void jobsPaused(String s) {System.out.println("工作任务组:" + s + ",正在被暂停");}@Overridepublic void jobResumed(JobKey jobKey) {System.out.println(jobKey + "jobKey正在从暂停中恢复");}@Overridepublic void jobsResumed(String s) {System.out.println("工作任务组:" + s + ",正在从暂停中恢复");}@Overridepublic void schedulerError(String s, SchedulerException e) {}@Overridepublic void schedulerInStandbyMode() {}@Overridepublic void schedulerStarted() {}@Overridepublic void schedulerStarting() {System.out.println("=============================开启监听===========================");}@Overridepublic void schedulerShutdown() {}@Overridepublic void schedulerShuttingdown() {}@Overridepublic 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中存jobDetailJobDetail 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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/187942.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Google Firebase PHP实现消息推送

获取key的方法&#xff1a; 登录谷歌开发者后台 https://console.firebase.google.com/?hlzh-cn function firebaseNotice($title,$body){$token_arr[token1,token2]; //用户的firebasetoken列表$notify_msg ["notification" > ["title" > $title…

第四节(2):修改WORD中表格数据的方案

《VBA信息获取与处理》教程(10178984)是我推出第六套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。这部教程给大家讲解的内容有&#xff1a;跨应用程序信息获得、随机信息的利用、电子邮件的发送、VBA互联网…

【2023CANN训练营第二季】——Ascend C算子开发进阶—Ascend C Tiling计算

了解Tiling基本概念 在这一小节中接触到了一个新的概念&#xff0c;叫Tiling计算&#xff0c;指的是在Ascend C 算子开发过程中&#xff0c;矢量的算子流程分为3个基本任务&#xff1a;CopyIn&#xff0c;Compute&#xff0c;CopyOut。CopyIn任务负责将Global Memory上的输入T…

计算机毕业设计选题推荐-农产品销售微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

【狂神说Java】Dubbo + Zookeeper

✅作者简介&#xff1a;CSDN内容合伙人、信息安全专业在校大学生&#x1f3c6; &#x1f525;系列专栏 &#xff1a;狂神说Java &#x1f4c3;新人博主 &#xff1a;欢迎点赞收藏关注&#xff0c;会回访&#xff01; &#x1f4ac;舞台再大&#xff0c;你不上台&#xff0c;永远…

基于VPLC711的曲面外观检测XYR运动控制解决方案

市场应用背景 随着消费升级&#xff0c;产品形态正在朝着多样性和精细化方向迅速发展。这导致了对于复杂曲面轨迹加工的需求&#xff0c;包括外观检测、打磨抛光和点胶工艺控制&#xff0c;要求更高的精密度。企业必须主动满足市场需求&#xff0c;不断改进工艺&#xff0c;以…

从零开始开发抖音小程序:与餐饮团购的完美融合

本文将探讨如何从零开始开发一个创新的抖音小程序&#xff0c;以其独特的特性与餐饮团购进行完美融合。 一、什么是抖音小程序&#xff1f; 抖音小程序为开发者提供了在用户观看视频时进行无缝体验的机会。通过借助抖音的庞大用户基础&#xff0c;开发者可以将自己的创意呈现给…

k8s二进制(ETCD的部署安装)

角色ip组件k8s-master192.168.11.169kube-apiserver,kube-controller-manager,kube-scheduler,etcdk8s-node1192.168.11.164kubelet,kube-proxy,docker,etcdk8s-node2192.168.11.166kubelet,kube-proxy,docker,etcd 1、为etcd签发证书 1、证书的下载(任意机器上执行都可以) …

利用Python代码提取shp中每个区域的图像

import geopandas as gpd import rasterio from rasterio.mask import mask import matplotlib.pyplot as plt import numpy as np# 载入shp文件 - 它只包含几何对象 shapefile_path rD:\Desktop\新建文件夹 (3)\01.shp shapes gpd.read_file(shapefile_path)# 打开图像 imag…

WebSocket魔法师:打造实时应用的无限可能

1、背景 在开发一些前端页面的时候&#xff0c;总是能接收到这样的需求&#xff1a;如何保持页面并实现自动更新数据呢&#xff1f;以往的常规做法&#xff0c;是前端使用定时轮询后端接口&#xff0c;获取响应后重新渲染前端页面&#xff0c;这种做法虽然能达到类似的效果&…

开源DB-GPT实现连接数据库详细步骤

官方文档&#xff1a;欢迎来到DB-GPT中文文档 — DB-GPT &#x1f44f;&#x1f44f; 0.4.1 第一步&#xff1a;安装Minicoda https://docs.conda.io/en/latest/miniconda.html 第二步&#xff1a;安装Git Git - Downloading Package 第三步&#xff1a;安装embedding 模型到…

Python爬虫——入门爬取网页数据

目录 前言 一、Python爬虫入门 二、使用代理IP 三、反爬虫技术 1. 间隔时间 2. 随机UA 3. 使用Cookies 四、总结 前言 本文介绍Python爬虫入门教程&#xff0c;主要讲解如何使用Python爬取网页数据&#xff0c;包括基本的网页数据抓取、使用代理IP和反爬虫技术。 一、…

Javaweb之javascript的BOM对象的详细解析

1.5.2 BOM对象 接下来我们学习BOM对象&#xff0c;BOM的全称是Browser Object Model,翻译过来是浏览器对象模型。也就是JavaScript将浏览器的各个组成部分封装成了对象。我们要操作浏览器的部分功能&#xff0c;可以通过操作BOM对象的相关属性或者函数来完成。例如&#xff1a…

Cordova插件开发三:通过广播实现应用间跨进程通信

文章目录 1.最终效果预览2.数据发送3.插件接受数据4.JS页面中点击获取数据返回1.最终效果预览 场景说明:我们给自来水公司开发了一个h5应用,需要对接第三方厂家支持硬件设备以便于获取到高精度定位数据,之前几篇文件写过,我已经集成过南方测绘RTK和高精度定位模块的设备,厂…

百度智能云正式上线Python SDK版本并全面开源!

文章目录 1. SDK的优势2. 千帆SDK&#xff1a;快速落地LLM应用3. 如何快速上手千帆SDK3.1 SDK快速启动3.2 SDK进阶指引3.3 通过Langchain接入千帆SDK 4. 开源社区 百度智能云千帆大模型平台再次升级&#xff01;在原有API基础上&#xff0c;百度智能云正式上线Python SDK&#…

Easyui DataGrid combobox联动下拉框内容

发票信息下拉框联动&#xff0c;更具不同的发票类型&#xff0c;显示不同的税率 专票 普票 下拉框选择事件 function onSelectType(rec){//选中值if (rec2){//普通发票对应税率pmsPlanList.pmsInvoiceTaxRatepmsPlanList.pmsInvoiceTaxRateT}else {//专用发票对应税率pmsPlan…

改进YOLOv8:结合ICCV2023|动态蛇形卷积,构建不规则目标识别网络

🔥🔥🔥 提升多尺度、不规则目标检测,创新提升 🔥🔥🔥 🔥🔥🔥 捕捉图像特征和处理复杂图像特征 🔥🔥🔥 👉👉👉: 本专栏包含大量的新设计的创新想法,包含详细的代码和说明,具备有效的创新组合,可以有效应用到改进创新当中 👉👉👉: �…

《算法通关村——透彻理解二叉树中序遍历的应用》

《算法通关村——透彻理解二叉树中序遍历的应用》 直接上题 108. 将有序数组转换为二叉搜索树 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 高度平衡 二叉搜索树。 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高…

屏幕提词软件Presentation Prompter mac中文版使用方法

Presentation Prompter for mac是一款屏幕提词器软件&#xff0c;它可以将您的Mac电脑快速变成提词器&#xff0c;支持编写或导入&#xff0c;可以在一个或多个屏幕上平滑地滚动&#xff0c;Presentation Prompter 下载是为适用于现场表演者&#xff0c;新闻广播员&#xff0c;…

【Hadoop实战】Hadoop指标系统V2分析

Hadoop指标系统V2分析 文章目录 Hadoop指标系统V2分析架构主要组成部分根据图表解释数据流向指标过滤JMX的应用开启指标系统的组件指标项说明 使用HTTP&#xff08;JMXJsonServlet&#xff09;获取指标接口调用方式GET查询的逻辑数据的来源&#xff0c;以及更新的原理 架构 在…