当前位置: 首页 > 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…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

Android Wi-Fi 连接失败日志分析

1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分&#xff1a; 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析&#xff1a; CTR…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

ubuntu22.04 安装docker 和docker-compose

首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...