公司最近想做个网站怎么办,南阳微网站建设,电影网站建设需要多少钱,wordpress 订阅号推送说明#xff1a;之前使用Quartz#xff0c;都是写好Job#xff0c;指定一个时间点#xff0c;到点执行。最近有个需求#xff0c;需要根据前端用户设置的时间点去执行#xff0c;也就是说任务执行的时间点是动态变化的。本文介绍如何用Quartz任务调度框架实现任务动态执行…说明之前使用Quartz都是写好Job指定一个时间点到点执行。最近有个需求需要根据前端用户设置的时间点去执行也就是说任务执行的时间点是动态变化的。本文介绍如何用Quartz任务调度框架实现任务动态执行。
基本实现
0思路
基本思路是根据用户传入的时间点转为cron表达式定时去刷新容器内的JOB更新任务的cron表达式。
1搭建项目
创建一个项目pom文件如下
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.7.12/versionrelativePath//parentgroupIdcom.hezy/groupIdartifactIdauto_quartz/artifactIdversion1.0-SNAPSHOT/versionpropertiesmaven.compiler.source8/maven.compiler.sourcemaven.compiler.target8/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-quartz/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependencydependencygroupIdorg.postgresql/groupIdartifactIdpostgresql/artifactIdversion42.7.3/version/dependencydependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.8.6/version/dependencydependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion1.2.8/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependencydependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.2.2/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies
/project我项目的是postgresqlapplication.yml如下
server:port: 8988spring:datasource:url: jdbc:postgresql://localhost:5432/demousername: postgrespassword: 123456driver-class-name: org.postgresql.Driver创建一张任务调度表包含了任务名对应Java类中JOB的Bean名称任务的cron表达式
-- 创建任务调度表
create table public.t_task
(id varchar(32) not nullconstraint pc_task_idprimary key,task_name varchar(50) not null,cron varchar(50) not null,is_deleted integer default 0 not null
);comment on table public.t_task is 任务调度表;
comment on column public.t_task.task_name is 任务名;
comment on column public.t_task.cron is cron表达式;
comment on column public.t_task.is_deleted is 是否删除1是0否默认0;2引入Quartz
创建两个关于Quartz的配置类
MyJobFactory
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;/*** 任务工厂*/
Component
public class MyJobFactory extends AdaptableJobFactory {Autowiredprivate AutowireCapableBeanFactory capableBeanFactory;/*** 创建JOB实例*/Overrideprotected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {Object jobInstance super.createJobInstance(bundle);capableBeanFactory.autowireBean(jobInstance);return jobInstance;}
}TaskConfig
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;/*** 任务配置类*/
Configuration
DependsOn(myJobFactory)
public class TaskConfig {Autowiredprivate MyJobFactory myJobFactory;/*** 加载任务工厂*/Beanpublic SchedulerFactoryBean schedulerFactoryBean() {SchedulerFactoryBean schedulerFactoryBean new SchedulerFactoryBean();schedulerFactoryBean.setJobFactory(myJobFactory);return schedulerFactoryBean;}Beanpublic Scheduler scheduler() {return schedulerFactoryBean().getScheduler();}
}3 数据库操作实现
创建一个TaskDTO
import lombok.Data;Data
public class TaskDTO {private String id;/*** 任务名*/private String taskName;/*** CRON表达式*/private String cron;
}写一个对应的Mapper里面创建一个查询方法
import com.hezy.pojo.TaskDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;Mapper
public interface TaskMapper {Select(select id, task_name taskName, cron from t_task where is_deleted 0)ListTaskDTO getAllTask();
}插入两条数据如下
insert into public.t_task (id, task_name, cron, is_deleted)
values (1, SimpleTask1, 0/5 * * * * ?, 0);insert into public.t_task (id, task_name, cron, is_deleted)
values (2, SimpleTask2, 0/30 * * * * ?, 0);测试没得问题
import com.hezy.mapper.TaskMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;SpringBootTest
public class TaskMapperTest {Autowiredprivate TaskMapper taskMapper;Testpublic void testInsert() {System.out.println(taskMapper.getAllTask());}
}4创建任务容器
创建一个任务容器每10秒去刷新容器内的所有Job的cron表达式
import com.hezy.constant.TaskConst;
import com.hezy.mapper.TaskMapper;
import com.hezy.pojo.TaskDTO;
import lombok.extern.log4j.Log4j2;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.List;/*** 任务容器*/
Component
DependsOn(value {quartzManager})
Log4j2
public class TaskContext {Autowiredprivate QuartzManager quartzManager;Autowiredprivate TaskMapper taskMapper;/*** 更新任务* 刷新频率10秒*/Scheduled(fixedRate 10000)public void update() {try {// 查出所有任务ListTaskDTO taskDTOS taskMapper.getAllTask();// 遍历操作for (TaskDTO taskDto : taskDTOS) {this.quartzManager.startJobTask(taskDto.getTaskName(), TaskConst.GroupEnum.SYSTEM.name(), taskDto.getCron());}} catch (SchedulerException e) {log.error(初始化定时任务异常, e);}}
}需要注意Scheduled注解需要在启动类上加EnableScheduling注解才能生效
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;SpringBootApplication
EnableScheduling
public class Start {public static void main(String[] args) {SpringApplication.run(Start.class, args);}
}创建一个常量表示任务的组名
TaskConst
/*** 任务常量*/
public class TaskConst {/*** 任务分组*/public enum GroupEnum {SYSTEM;}
}任务管理器对比传入的任务名、任务cron更新或创建对应的任务
import lombok.extern.log4j.Log4j2;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** 定时任务管理器* author hezhongying* create 2024/12/11*/
Configuration
Log4j2
public class QuartzManager {Autowiredprivate Scheduler scheduler;Autowiredprivate ListJob taskList;private MapString, Job taskMap;PostConstructpublic void init() {this.taskMap taskList.stream().collect(Collectors.toMap(t - t.getClass().getSimpleName(), t - t));}/*** 启动任务* param jobName 任务名* param group 组名* param cron cron表达式* throws SchedulerException*/public void startJobTask(String jobName, String group, String cron) throws SchedulerException {// 创建JobJobKey jobKey new JobKey(jobName, group);// 判断任务是否已存在不存在新增存在更新if (scheduler.checkExists(jobKey)) {if (modifyJob(jobName, group, cron)) {log.info(任务{} 修改成功, jobName);}} else {if (createJob(jobName, group, cron)) {log.info(任务{} 新增成功, jobName);}}}/*** 新增任务* param jobName 任务名称* param group 分组* param cron CRON表达式* throws SchedulerException*/private boolean createJob(String jobName, String group, String cron) throws SchedulerException {// 任务新增if (taskMap.containsKey(jobName)) {// 执行任务Class? extends Job taskClazz taskMap.get(jobName).getClass();JobDetail jobDetail JobBuilder.newJob(taskClazz).withIdentity(jobName, group).build();// 执行时间正则CronScheduleBuilder cronBuilder CronScheduleBuilder.cronSchedule(cron);CronTrigger cronTrigger TriggerBuilder.newTrigger().withIdentity(jobName, group).withSchedule(cronBuilder).build();scheduler.scheduleJob(jobDetail, cronTrigger);return true;} else {log.debug(任务没有配置执行配置{}, jobName);}return false;}/*** 修改** param jobName 任务名* param group 组名* param cron cron表达式* return true修改成功false修改失败* throws SchedulerException*/public boolean modifyJob(String jobName, String group, String cron) throws SchedulerException {TriggerKey triggerKey new TriggerKey(jobName, group);Trigger trigger scheduler.getTrigger(triggerKey);if (trigger null) {log.info(未存在的触发器[{}-{}], jobName, group);return false;}String oldCron ((CronTrigger) trigger).getCronExpression();// 判断cron是否相等相等就不用改if (!cron.equals(oldCron)) {CronScheduleBuilder cronScheduleBuilder CronScheduleBuilder.cronSchedule(cron);CronTrigger newTrigger TriggerBuilder.newTrigger().withIdentity(jobName, group).withSchedule(cronScheduleBuilder).build();Date date scheduler.rescheduleJob(triggerKey, newTrigger);return date ! null;} else {log.info(任务{} CRON表达式没有变化, jobName);}return false;}/*** 暂停所有任务** throws SchedulerException*/public void pauseAll() throws SchedulerException {this.scheduler.pauseAll();}/*** 指定任务暂停** param jobName 任务名* param group 组名* throws SchedulerException*/public void pause(String jobName, String group) throws SchedulerException {JobKey jobKey new JobKey(jobName, group);JobDetail jobDetail this.scheduler.getJobDetail(jobKey);if (jobDetail null) {return;}this.scheduler.pauseJob(jobKey);}/*** 恢复所有任务** throws SchedulerException*/public void resumeAllJob() throws SchedulerException {this.scheduler.resumeAll();}/*** 删除指定任务** param jobName 任务名* param group 组名* throws SchedulerException*/public void delete(String jobName, String group) throws SchedulerException {JobKey jobKey new JobKey(jobName, group);JobDetail jobDetail this.scheduler.getJobDetail(jobKey);if (jobDetail null) {return;}this.scheduler.deleteJob(jobKey);}
}注意这行代码可自动注入容器内所有的Job也就是所有实现了Quartz框架Job接口的类 Autowiredprivate ListJob taskList;5创建两个Job
创建两个简单的Job
SimpleTask1
import lombok.extern.log4j.Log4j2;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;/*** 简单任务1*/
Component
Log4j2
public class SimpleTask1 implements Job {Overridepublic void execute(JobExecutionContext jobExecutionContext) {log.info({} 定时任务执行时间{}, SimpleTask1, new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()));}
}SimpleTask2
import lombok.extern.log4j.Log4j2;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;/*** 简单任务2*/
Component
Log4j2
public class SimpleTask2 implements Job {Overridepublic void execute(JobExecutionContext jobExecutionContext) {log.info({} 定时任务执行时间{}, SimpleTask2, new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()));}
}6启动测试
启动项目查看控制台 此时项目不停修改数据对应任务的cron表达式 查看控制台 到这动态调度任务就实现了。
更进一步
回到我最开始提到的需求再来考虑一些细节。 前端传递的时间如何转为cron表达式 如何实现不同任务对同一个Job的动态执行 第一个问题前端传递的任务数据可能是这样的任务“每天、每周、每月”然后设置一个时间点要求任务按照这个周期来执行如果将这些信息转为对应的cron表达式需要有一段代码来实现这些映射关系。
如下 /*** 生成CRON表达式* param type 类型每天、每周、每月* param hour 时* param minute 分* param dayOfWeek 星期* param dayOfMonth 日期* return CRON表达式或null*/public static String generateCronExpression(int type, int hour, int minute, int dayOfWeek, int dayOfMonth) {switch (type) {case 1:return String.format(0 %d %d * * ?, minute, hour);case 2:return String.format(0 %d %d ? * %s, minute, hour, TaskConst.DayOfWeekEnum.getByCode(dayOfWeek).getName());case 3:return String.format(0 %d %d %d * ?, minute, hour, dayOfMonth);default:return null;}}星期映射
cron表达式末尾不能填对应星期的数字而需要填星期几的英文简称 /*** 星期枚举*/public enum DayOfWeekEnum {SUNDAY(1, SUN),MONDAY(2, MON),TUESDAY(3, TUE),WEDNESDAY(4, WED),THURSDAY(5, THU),FRIDAY(6, FRI),SATURDAY(7, SAT);private final Integer code;private final String name;DayOfWeekEnum(Integer code, String name) {this.code code;this.name name;}public Integer getCode() {return code;}public String getName() {return name;}/*** 根据code查询枚举*/public static DayOfWeekEnum getByCode(Integer code) {for (DayOfWeekEnum value : DayOfWeekEnum.values()) {if (value.getCode().equals(code)) {return value;}}return null;}/*** 根据name查询枚举*/public static DayOfWeekEnum getByName(String name) {for (DayOfWeekEnum value : DayOfWeekEnum.values()) {if (value.getName().equals(name)) {return value;}}return null;}}但这种方式局限于我上面说的只能“每天、每周、每月”的某时、某分执行而不能更精准到秒、或者每月的最后一周这样更灵活的设置。思考或许可以考虑直接将cron表达式的设置开放给前端由用户直接来设置后端只需校验cron的合法性即可。 第二个问题有这样的情况我的任务调度表中的多条记录执行的是同一个Job我的Job中会根据记录的唯一ID去查找这个记录对应的数据来处理。这种情况要怎样还能实现动态调度。
如下两条记录对应同一个Job但是cron不同执行周期不同 首先在定时刷新这里就不能只传入taskName再传入一个唯一标识我这里就传入ID /*** 更新任务* 刷新频率10秒*/Scheduled(fixedRate 10000)public void update() {try {// 查出所有任务ListTaskDTO taskDTOS taskMapper.taskInfo();// 遍历操作for (TaskDTO taskDto : taskDTOS) {// 带上唯一标识this.quartzManager.startJobTask(taskDto.getTaskName(), taskDto.getId(), TaskConst.GroupEnum.SYSTEM.name(), taskDto.getCron());}} catch (SchedulerException e) {log.error(初始化定时任务异常, e);}}任务管理器这里判断是否有当前任务就用任务名_唯一标识判断绑定Job的时候就用Job名即数据库记录的taskName如下 /*** 启动任务* param name Job名即Job类名* param id 唯一标识* param group 组名* param cron cron表达式* throws SchedulerException*/public void startJobTask(String name, String id, String group, String cron) throws SchedulerException {// 任务名用Job名和唯一标识拼接String jobName String.format(%s_%s, name, id);// 创建JobJobKey jobKey new JobKey(jobName, group);// 判断任务是否已存在不存在新增存在更新if (scheduler.checkExists(jobKey)) {if (modifyJob(jobName, id, group, cron)) {log.info(任务{} 修改成功, jobName);}} else {if (createJob(name, jobName, id, group, cron)) {log.info(任务{} 新增成功, jobName);}}}新增Job /*** 新增任务* param name JOB名* param jobName 任务名称* param id 唯一标识* param group 分组* param cron CRON表达式* throws SchedulerException*/private boolean createJob(String name, String jobName, String id, String group, String cron) throws SchedulerException {// 任务新增用Job名判断当前容器中是否存在这个Jobif (taskMap.containsKey(name)) {// 执行任务获取Job类时也用Job名Class? extends Job taskClazz taskMap.get(name).getClass();// 创建任务式用Job和唯一标识拼接作为任务名JobDetail jobDetail JobBuilder.newJob(taskClazz).withIdentity(jobName, group).build();// 创建 JobDataMap 并放入唯一标识 idJobDataMap jobDataMap new JobDataMap();jobDataMap.put(id, id);// 执行时间正则CronScheduleBuilder cronBuilder CronScheduleBuilder.cronSchedule(cron);CronTrigger cronTrigger TriggerBuilder.newTrigger().withIdentity(jobName, group).usingJobData(jobDataMap).withSchedule(cronBuilder).build();scheduler.scheduleJob(jobDetail, cronTrigger);return true;} else {log.debug(任务没有配置执行配置{}, jobName);}return false;}修改Job /*** 修改** param jobName 任务名* param group 组名* param cron cron表达式* return true修改成功false修改失败* throws SchedulerException*/public boolean modifyJob(String jobName, String id, String group, String cron) throws SchedulerException {TriggerKey triggerKey new TriggerKey(jobName, group);Trigger trigger scheduler.getTrigger(triggerKey);if (trigger null) {log.info(未存在的触发器[{}-{}], jobName, group);return false;}// 创建 JobDataMap 并放入唯一标识 idJobDataMap jobDataMap new JobDataMap();jobDataMap.put(id, id);String oldCron ((CronTrigger) trigger).getCronExpression();// 判断cron是否相等相等就不用改if (!cron.equals(oldCron)) {CronScheduleBuilder cronScheduleBuilder CronScheduleBuilder.cronSchedule(cron);CronTrigger newTrigger TriggerBuilder.newTrigger().withIdentity(jobName, group).usingJobData(jobDataMap).withSchedule(cronScheduleBuilder).build();Date date scheduler.rescheduleJob(triggerKey, newTrigger);return date ! null;} else {log.info(任务{} CRON表达式没有变化, jobName);}return false;}Job实现里打印唯一标识用于区分。如果你有业务需求完全可以用这个唯一标识去查询数据库找出这个唯一标识所拥有的数据然后做一系列操作。
/*** 简单任务1*/
Component
Log4j2
public class SimpleTask1 implements Job {Overridepublic void execute(JobExecutionContext jobExecutionContext) {log.info({} 定时任务执行时间{}, jobExecutionContext.getMergedJobDataMap().get(id), new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()));}
}启动程序可见两条记录各自的执行周期。 一个Job各自运行让博主想起黑塞 《悉达多》中的一句话可是也有另一些人一些为数不多的人像沿着一条固定轨道运行的星星没有风吹得到它们它们有自身的规律和轨道。
总结
本文介绍了如何使用Quartz任务调度框架实现任务动态执行参考下面这篇文章
SpringBoot整合quartz完成定时任务执行配置热修改