当前位置: 首页 > news >正文

基于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。

相关文章:

基于Quartz实现动态定时任务

生命无罪&#xff0c;健康万岁&#xff0c;我是laity。 我曾七次鄙视自己的灵魂&#xff1a; 第一次&#xff0c;当它本可进取时&#xff0c;却故作谦卑&#xff1b; 第二次&#xff0c;当它在空虚时&#xff0c;用爱欲来填充&#xff1b; 第三次&#xff0c;在困难和容易之…...

hdlbits系列verilog解答(计算向量中1出现次数)-41

文章目录 一、问题描述二、verilog源码三、仿真结果一、问题描述 “频次计数”电路对输入向量中的“1”数进行计数。为 255 位输入向量构建频次计数电路。 Module Declaration module top_module( input [254:0] in, output [7:0] out ); 二、verilog源码 module top_module…...

2023年最新的前端面试收集(1)

2023年最新的前端面试收集 一、你在项目中做的性能优化的事情有哪些网络优化页面渲染优化JS优化图片优化webpack打包优化vue优化react优化 二、webworker中为什么能提升js执行的性能三、微前端 一、你在项目中做的性能优化的事情有哪些 网络优化 DNS预解析 link标签的rel属性设…...

flutter实现上拉加载下拉刷新

效果如下&#xff1a; flutter实现上拉加载下拉刷新 使用到的库是easy_refresh 安装请查看官网 接口用的是提供的接口https://reqres.in/ 请求接口用到的库是dio 下面主要是介绍如何使用easy_refresh实现上拉加载数据&#xff0c;详细学习其它例子请查看easy_refresh main…...

tomcat+idea--如何在idea上发布项目

对应于idea2022以后的版本 &#xff08;一&#xff09;如何配置idea上的tomcat&#xff1f; 1、新建一个项目&#xff0c;左上角File&#xff0c;new&#xff0c;project&#xff0c;新建后就和普通的java项目一样。 2、然后点击项目名&#xff0c;右键选择“Add framework s…...

考研数据结构单链表的增删改查看这一篇就够了

目录 一. 单链表的特点 1.1 解引用拓展 &#x1f916; 二. 单链表的操作 2.1不带头节点的操作 2.1.1 打印 2.1.1.1 创建结点 2.1.2 尾插&#xff08;需要二级指针&#xff09; 注意形参的值不改变实参&#xff1a;&#xff08;精髓部分&#xff09; 2.1.3 头插 2.1.4…...

Windows查看端口占用情况

Windows如何查看端口占用情况 方法1. cmd命令行执行netstat命令&#xff0c;查看端口占用情况 netstat -ano 以上命令输出太多信息&#xff0c;不方便查看&#xff0c;通过如下命令搜索具体端口占用情况&#xff0c;例如&#xff1a;8080端口 netstat -ano | findstr "…...

Python:词法分析(行结构与显式、隐式行拼接)

相关阅读 Pythonhttps://blog.csdn.net/weixin_45791458/category_12403403.html?spm1001.2014.3001.5482 1、逻辑结构 一个Python程序由许多逻辑行组成&#xff0c;字面意义上的一行指的是末尾有换行符(\n)&#xff0c;但在不同的情况下&#xff0c;行末尾的换行符(\n)可能有…...

前端Vue 结合xlxs库实现解析excel文件,并动态组装表头!

目录 1.前言2.数据定义3. 页面布局4.上传之前的事件5.解析excel文件,并组装系统表头与excel表头的对应关系6.下拉框改变事件 1.前言 最近有一个需求&#xff0c;就是用户可以任意导入一个自定义的excel文件&#xff0c;让用户可以自己选择&#xff0c;组装表头的对应关系&…...

RabbitMQ集群配置以及负载均衡配置

RabbitMQ集群配置以及负载均衡配置 环境配置集群配置安装rabbitmq启动rabbitmq开启远程登录添加用户并且授权用户添加数据存放目录和日志存放目录查看端口拷⻉erlang.cookie将mq-2、mq-3作为内存节点加⼊mq-1节点集群中查看集群状态添加一个新的队列 RabbitMq负载均衡配置-HAPr…...

Leetcode Hot100之六:42.接雨水

题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 提示&#xff1a; n height.length 1 < n < 2 * 10^4 0 < height[i] < 10^5 思路 暴力循环&#xff1a; 原本的思路是左边界i从左到…...

electron 主进程 和 渲染进程通信 ipcRenderer 和 mainWindow.webContents

electron 开发时最麻烦就是electron版本和node版本的选择和正确安装 electron 用npm安装时太慢容易报错&#xff0c;建议用cnpm i 进行安装 注意最新版渲染进程使用node nodeIntegration: true, // 渲染进程可用node contextIsolation: false, // 这个值影响nodeIntegration是…...

关于VUE启动内存溢出

安装node v10.14.2 后 启动公司的VUE项目 使用命令npm run dev 命令 报错&#xff1a; <--- Last few GCs --->[20940:00000244699848E0] 215872 ms: Scavenge 1690.2 (1836.4) -> 1679.6 (1836.4) MB, 5.4 / 0.7 ms (average mu 0.266, current mu 0.253) a…...

HBase学习笔记(1)—— 知识点总结

目录 HBase概述 HBase 基本架构 HBase安装部署启动 HBase Shell HBase数据读写流程 HBase 优化 HBase概述 HBase是以 hdfs 为数据存储的&#xff0c;一种分布式、非关系型的、可扩展的 NoSQL 数据库 关系型数据库和非关系型数据库的区别&#xff1a; 关系型数据库和非关…...

【Linux】 awk命令使用

AWK 是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具。 之所以叫 AWK 是因为其取了三位创始人 Alfred Aho&#xff0c;Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符。 语法 awk [选项] [文件] awk [选项] [程序] [文件] awk命令 -Linux手…...

Sentinel网关限流

背景 在微服务架构下&#xff0c;每个服务的性能都不同&#xff0c;为避免出现流量洪峰将服务冲垮&#xff0c;需要依赖限流工具来保护服务的稳定性。sentinel是阿里提供的限流工具&#xff0c;社区活跃&#xff0c;功能也很全面&#xff0c;包含实时监控、流控、熔断等功能。…...

solidworks对电脑要求高吗?2023solidworks配置要求

solidworks对电脑要求高吗&#xff1f;SolidWorks是一款功能强大的三维CAD软件&#xff0c;对电脑配置有一定的要求。一般来说&#xff0c;运行SolidWorks需要的电脑配置包括较高的处理器性能、足够的内存和存储空间&#xff0c;以及一块性能良好的显卡。此外&#xff0c;对于大…...

搭建神经网络(torch.nn的用法)

零零碎碎总结了一些torch框架里面nn模块的用法&#xff0c;尤其是关于搭建神经网络的 nn.ModuleList nn.Module nn.Sequential nn.Linear nn.Dropout nn.Embedding nn.DataParallel() 将模型封装起来&#xff0c;便于在多个gpu上并行计算&#xff0c;训练或者推理 nn.…...

卡码网语言基础课 | 11. 句子缩写

目录 一、 字符串大小的比较 二、 ASCII码值 三、 基本框架代码 四、 解题思路 4.1 首字母问题 4.2 判定小写字母 4.3 小写字母转换为大写字母 五、空格判断 六、 代码模块化 6.1 满足的条件 6.2 代码完善 七、 题目解答 7.1 原始代码 7.2 改进代码 八、 拓展与…...

Surface RT 安装 Linux

零&#xff1a;起因 在家无事找出来一台老旧设备 Surface RT 一代的&#xff0c;系统最新是 Windows 8.1 arm版&#xff0c;应用商店都已经打不开了 虽说有破解方法&#xff0c;能运行些软件&#xff0c;但怎么说也不是任意安装&#xff0c;所以局限性还是相当的大&#xff0…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...