简单设计企业级JOB平台

前言

在企业级项目中有许多能够用到定时任务的场景例如:

  1. 在某个时间点统一给某些用户发送邮件信息
  2. 接口表数据发送
  3. 某月某日更新报表数据
  4. ......
    目前我们使用SpringBoot快速整合Quartz来进行具体的实现。
Top1.任务脚本初始化

首先我们需要创建官方提供的几张表,脚本如下:

Copy
-- in your Quartz properties file, you'll need to set org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate -- 你需要在你的quartz.properties文件中设置org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate -- StdJDBCDelegate说明支持集群,所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务 -- This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM -- 这是来自quartz的脚本,在MySQL数据库中创建以下的表,修改为使用INNODB而不是MYISAM -- 你需要在数据库中执行以下的sql脚本 DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; -- 存储每一个已配置的Job的详细信息 CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE VARCHAR(1) NOT NULL, IS_NONCONCURRENT VARCHAR(1) NOT NULL, IS_UPDATE_DATA VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) ) ENGINE=InnoDB; -- 存储已配置的Trigger的信息 CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT(13) NULL, PREV_FIRE_TIME BIGINT(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT(13) NOT NULL, END_TIME BIGINT(13) NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT(2) NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP) ) ENGINE=InnoDB; -- 存储已配置的Simple Trigger的信息 CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT BIGINT(7) NOT NULL, REPEAT_INTERVAL BIGINT(12) NOT NULL, TIMES_TRIGGERED BIGINT(10) NOT NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ) ENGINE=InnoDB; -- 存储Cron Trigger,包括Cron表达式和时区信息 CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(120) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ) ENGINE=InnoDB; CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ) ENGINE=InnoDB; -- Trigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore并不知道如何存储实例的时候) CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), INDEX (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ), FOREIGN KEY ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP ) REFERENCES QRTZ_TRIGGERS ( SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP )) ENGINE=InnoDB; -- 以Blob类型存储Quartz的Calendar日历信息,quartz可配置一个日历来指定一个时间范围 CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (SCHED_NAME, CALENDAR_NAME) ) ENGINE=InnoDB; -- 存储已暂停的Trigger组的信息 CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP) ) ENGINE=InnoDB; -- 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息 CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT(13) NOT NULL, SCHED_TIME BIGINT(13) NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT VARCHAR(1) NULL, REQUESTS_RECOVERY VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME, ENTRY_ID) ) ENGINE=InnoDB; -- 存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中) CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT(13) NOT NULL, CHECKIN_INTERVAL BIGINT(13) NOT NULL, PRIMARY KEY (SCHED_NAME, INSTANCE_NAME) ) ENGINE=InnoDB; -- 存储程序的非观锁的信息(假如使用了悲观锁) CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME, LOCK_NAME) ) ENGINE=InnoDB; CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME); CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME); CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); commit;

有了这些表,就能够在程序中很好地保存JOB信息,但是下面我还会创建一张表进行保存我们程序中操作的JOB信息,脚本如下:

Copy
CREATE TABLE `job_info` ( `id` int(11) NOT NULL AUTO_INCREMENT, `job_name` varchar(100) NOT NULL COMMENT 'job名称', `job_class` varchar(255) NOT NULL COMMENT 'job对应类路径', `job_group_name` varchar(100) NOT NULL COMMENT 'job所在组', `job_time` varchar(55) NOT NULL COMMENT 'job执行时间', `job_type` varchar(2) NOT NULL COMMENT '执行时间类型 1(CRON表达式) 2(秒)', `job_count` int(11) NOT NULL DEFAULT '1' COMMENT '执行次数', `is_enable` varchar(2) NOT NULL DEFAULT '1' COMMENT '是否可用', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

这张表主要是用来实时的记录我们的JOB,由于这张表还缺少许多字段,如要使用可以根据业务场景自行增加。

Top2.创建可用工程并且导入依赖

使用IDEA创建名称为common-quartz的maven工程,并且导入依赖:

Copy
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <druid.version>1.1.5</druid.version> <quartz.version>2.3.0</quartz.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <!--quartz相关依赖--> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>${quartz.version}</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>${quartz.version}</version> </dependency> <!--定时任务需要依赖context模块--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Swagger Api --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.2.2</version> </dependency> </dependencies> <!-- 打包插件 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>

创建启动类CommonQuartzApplication.java,如下:

Copy
/** * @author:伍梓涛 * @version:1.0.0 * @Modified By:SimpleWu * @CopyRright (c)2019-:YUM架构平台 */ @SpringBootApplication //多模块加载扫描 //@ComponentScan(basePackages = "") public class CommonQuartzApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(CommonQuartzApplication.class, args); } }

创建Application.yml文件,如下:

Copy
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=UTC username: root password: root jpa: hibernate: ddl-auto: update #ddl-auto:设为update表示每次都不会重新建表 show-sql: true application: name: common-quartz server: port: 8081

这里我们配置数据库连接信息,使用JPA来操作数据库。

Top3.主要实现

采用自定义任务工厂 整合spring实例来完成构建任务;创建Quartz配置QuartzConfiguration.java并且加载Quartz.properties配置文件,如下:

Copy
/** * @author:伍梓涛 * @version:1.0.0 * @Modified By:SimpleWu * @CopyRright (c)2019-:YUM架构平台 */ @Configuration @EnableScheduling public class QuartzConfiguration { /** * 继承org.springframework.scheduling.quartz.SpringBeanJobFactory * 实现任务实例化方式 */ public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private transient AutowireCapableBeanFactory beanFactory; @Override public void setApplicationContext(final ApplicationContext context) { beanFactory = context.getAutowireCapableBeanFactory(); } /** * 将job实例交给spring ioc托管 * 我们在job实例实现类内可以直接使用spring注入的调用被spring ioc管理的实例 * * @param bundle * @return * @throws Exception */ @Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { final Object job = super.createJobInstance(bundle); /** * 将job实例交付给spring ioc */ beanFactory.autowireBean(job); return job; } } /** * 配置任务工厂实例 * * @return */ @Bean public JobFactory jobFactory() { /** * 采用自定义任务工厂 整合spring实例来完成构建任务*/ AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); return jobFactory; } /** * 配置任务调度器 * 使用项目数据源作为quartz数据源 * * @param jobFactory 自定义配置任务工厂 * @param dataSource 数据源实例 * @return * @throws Exception */ @Bean(destroyMethod = "destroy", autowire = Autowire.NO) public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); //将spring管理job自定义工厂交由调度器维护 schedulerFactoryBean.setJobFactory(jobFactory); //设置覆盖已存在的任务 schedulerFactoryBean.setOverwriteExistingJobs(true); //项目启动完成后,等待2秒后开始执行调度器初始化 schedulerFactoryBean.setStartupDelay(2); //设置调度器自动运行 schedulerFactoryBean.setAutoStartup(true); //设置数据源,使用与项目统一数据源 schedulerFactoryBean.setDataSource(dataSource); //设置上下文spring bean name schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext"); //设置配置文件位置 schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties")); return schedulerFactoryBean; } }

应为在企业中有些会单独做个任务管理平台的UI界面,在这里我们已经导入了Swagger的依赖,那么我们就使用Swagger来管理我们的任务达到目的。
现在我们创建Swagger配置,如下:

Copy
/** * @author:伍梓涛 * @version:1.0.0 * @Modified By:SimpleWu * @CopyRright (c)2019-:YUM架构平台 */ @Configuration @EnableSwagger2 public class Swagger2Config { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() ////为当前包路径 .apis(RequestHandlerSelectors.basePackage("com.boot.quartz")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Yum平台任务管理中心") .version("1.0") .build(); } }

在这里basePackage在多模块情况,都会有个统一名称,如com.boot我们直接这样扫描就好;
同时在启动类也需要使用@ComponentScan(basePackages = "")来进行多模块bean扫描。
接下来创建任务信息实体类(对应我们自己创建的那张表,来进行操作任务),如下:

Copy
@Table(name = "JOB_INFO") @Entity public class JobPojo implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Column(name = "job_name") private String jobName; @Column(name = "job_class") private String jobClass; @Column(name = "job_group_name") private String jobGroupName; @Column(name = "job_time") private String jobTime; @Column(name = "is_enable") private String isEnable; @Column(name = "job_count") private Integer jobCount; @Column(name = "job_type") private String jobType; //省略GET SET 方法 }

@Table:指定我们实体类对应的表
@Id:主键
GeneratedValue(strategy = GenerationType.AUTO):主键策略,自增
@Column:字段对应列名称
这个张表只是一个半成品,如果还缺少什么字段可以自行加入,例如什么 JOB状态啊,JOB对应的编码啊,根据业务需要进行加入即可。
接下来我们创建D层,只需要实现Jpa的接口即可,简单粗暴:

Copy
public interface JobRepository extends JpaRepository<JobPojo, Integer> { }

接下来我们创建Quartz管理工具类,这个类主要是用来管理JOB的。

Copy
@Service public class QuartzService { @Autowired private Scheduler scheduler; @PostConstruct public void startScheduler() { try { scheduler.start(); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 增加一个job * * @param jobClass * 任务实现类 * @param jobName * 任务名称 * @param jobGroupName * 任务组名 * @param jobTime * 时间表达式 (这是每隔多少秒为一次任务) * @param jobTimes * 运行的次数 (<0:表示不限次数) */ public void addJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, int jobTime, int jobTimes) { try { JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)// 任务名称和组构成任务key .build(); // 使用simpleTrigger规则 Trigger trigger = null; if (jobTimes < 0) { trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName) .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withIntervalInSeconds(jobTime)) .startNow().build(); } else { trigger = TriggerBuilder .newTrigger().withIdentity(jobName, jobGroupName).withSchedule(SimpleScheduleBuilder .repeatSecondlyForever(1).withIntervalInSeconds(jobTime).withRepeatCount(jobTimes)) .startNow().build(); } scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 增加一个job * * @param jobClass * 任务实现类 * @param jobName * 任务名称 * @param jobGroupName * 任务组名 * @param jobTime * 时间表达式 (如:0/5 * * * * ? ) */ public void addJob(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String jobTime) { try { // 创建jobDetail实例,绑定Job实现类 // 指明job的名称,所在组的名称,以及绑定job类 JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName)// 任务名称和组构成任务key .build(); // 定义调度触发规则 // 使用cornTrigger规则 Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroupName)// 触发器key .startAt(DateBuilder.futureDate(1, DateBuilder.IntervalUnit.SECOND)) .withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).startNow().build(); // 把作业和触发器注册到任务调度中 scheduler.scheduleJob(jobDetail, trigger); } catch (Exception e) { e.printStackTrace(); } } /** * 修改 一个job的 时间表达式 * * @param jobName * @param jobGroupName * @param jobTime */ public void updateJob(String jobName, String jobGroupName, String jobTime) { try { TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); trigger = trigger.getTriggerBuilder().withIdentity(triggerKey) .withSchedule(CronScheduleBuilder.cronSchedule(jobTime)).build(); // 重启触发器 scheduler.rescheduleJob(triggerKey, trigger); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 删除任务一个job * * @param jobName * 任务名称 * @param jobGroupName * 任务组名 */ public void deleteJob(String jobName, String jobGroupName) { try { scheduler.deleteJob(new JobKey(jobName, jobGroupName)); } catch (Exception e) { e.printStackTrace(); } } /** * 暂停一个job * * @param jobName * @param jobGroupName */ public void pauseJob(String jobName, String jobGroupName) { try { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); scheduler.pauseJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 恢复一个job * * @param jobName * @param jobGroupName */ public void resumeJob(String jobName, String jobGroupName) { try { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); scheduler.resumeJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 立即执行一个job * * @param jobName * @param jobGroupName */ public void runAJobNow(String jobName, String jobGroupName) { try { JobKey jobKey = JobKey.jobKey(jobName, jobGroupName); scheduler.triggerJob(jobKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * 获取所有计划中的任务列表 * * @return */ public List<Map<String, Object>> queryAllJob() { List<Map<String, Object>> jobList = null; try { GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup(); Set<JobKey> jobKeys = scheduler.getJobKeys(matcher); jobList = new ArrayList<Map<String, Object>>(); for (JobKey jobKey : jobKeys) { List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey); for (Trigger trigger : triggers) { Map<String, Object> map = new HashMap<>(); map.put("jobName", jobKey.getName()); map.put("jobGroupName", jobKey.getGroup()); map.put("description", "触发器:" + trigger.getKey()); Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey()); map.put("jobStatus", triggerState.name()); if (trigger instanceof CronTrigger) { CronTrigger cronTrigger = (CronTrigger) trigger; String cronExpression = cronTrigger.getCronExpression(); map.put("jobTime", cronExpression); } jobList.add(map); } } } catch (SchedulerException e) { e.printStackTrace(); } return jobList; } /** * 获取所有正在运行的job * * @return */ public List<Map<String, Object>> queryRunJob() { List<Map<String, Object>> jobList = null; try { List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs(); jobList = new ArrayList<Map<String, Object>>(executingJobs.size()); for (JobExecutionContext executingJob : executingJobs) { Map<String, Object> map = new HashMap<String, Object>(); JobDetail jobDetail = executingJob.getJobDetail(); JobKey jobKey = jobDetail.getKey(); Trigger trigger = executingJob.getTrigger(); map.put("jobName", jobKey.getName()); map.put("jobGroupName", jobKey.getGroup()); map.put("description", "触发器:" + trigger.getKey()); Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey()); map.put("jobStatus", triggerState.name()); if (trigger instanceof CronTrigger) { CronTrigger cronTrigger = (CronTrigger) trigger; String cronExpression = cronTrigger.getCronExpression(); map.put("jobTime", cronExpression); } jobList.add(map); } } catch (SchedulerException e) { e.printStackTrace(); } return jobList; } }

@PostConstruct说明:被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Serclet的inti()方法。被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。
好了接下来回到我们之前的那个Job信息管理上,之前已经创建好D层了,现在我们继续完善它,给他增加一个业务逻辑层

Copy
@Service public class JobServiceImpl implements JobService { private Logger logger = LoggerFactory.getLogger(JobServiceImpl.class); @Autowired private JobRepository jobRepository; @Autowired private QuartzService quartzService; /** * 初始化Job数据 */ @Override @PostConstruct public void initJob(){ List<JobPojo> jobAll = jobRepository.findAll(); jobAll.forEach(job->{ String jobClass = job.getJobClass(); try { Class clazz = Class.forName(jobClass); if("1".equals(job.getJobType())){ //CRON表达式方式 quartzService.addJob(clazz,job.getJobName(),job.getJobGroupName(),job.getJobTime()); logger.info("INIT JOB CRON JOB_NAME = " + job.getJobName()); }else if("2".equals(job.getJobType())){ //秒形式 quartzService.addJob(clazz,job.getJobName(),job.getJobGroupName(),Integer.valueOf(job.getJobTime()),job.getJobCount()); logger.info("INIT JOB JOB_NAME = " + job.getJobName()); } } catch (ClassNotFoundException e) { logger.error("JOB INIT ERROR:{}", job); logger.error("JOB INIT ERROR MSG:{}", e); } }); logger.info("SYSTEM DB JOB INIT ALL SUCCESS!!!"); } @Override @Transactional public int deleteAlljob() { List<JobPojo> jobAll = jobRepository.findAll(); jobAll.forEach(job->{ quartzService.deleteJob(job.getJobName(),job.getJobGroupName()); }); return 1; } }

在这里我们调用Quartz的工具类进行操作Quartz信息,initJob主要是为了在启动的时候立即加载所有的JOB(可根据业务场景操作),之前为什么说这个表结构的设计是半成品呢,在这里面
我们通过数据库字段来存储JOB的类路径反射加载类对象进行任务对象的创建,但是在Quartz中如果已经加载过一次的我们在进行加载重复的JOB是会报错的,所以我在这里加了个try catch,可以通过设置一个状态来控制是否被加载过。
到这里我们基本的已经完成的差不多了现在来加入访问接口配合Swagger 进行调用如下:

Copy
@RestController @RequestMapping("/quartz/") @Api(description = "* Quartz任务管理中心", value = "job服务") public class JobApiController { private final Logger logger = LoggerFactory.getLogger(JobApiController.class); @Autowired private QuartzService quartzService; @Autowired private JobService jobService; @Value("${job.default.group}") private String JOB_DEFAULT_GROUP; @ApiOperation(value = "* 获取所有JOB" ) @GetMapping("/getAllJob") public List<Map<String, Object>> getAllJob() { return quartzService.queryAllJob(); } @ApiOperation(value = "* 获取所有正在运行的JOB") @GetMapping("/queryRunJob") public List<Map<String, Object>> queryRunJob() { return quartzService.queryRunJob(); } @ApiOperation(value = "* 立即运行一个JOB") @PostMapping("/runJob") public String getJobByName( @RequestParam(name = "JOB_NAME", required = true) String jobName //@RequestParam(name = "JOB_GROUP" , required = false) String jobGroup ) { /*if(StringHelper.isNullOrEmptyString(jobGroup)){ jobGroup = JOB_DEFAULT_GROUP; }*/ quartzService.runAJobNow(jobName, JOB_DEFAULT_GROUP); logger.info("run job success jobName={}", jobName); return "SUCCESS!!"; } @ApiOperation(value = "* 暂停一个JOB") @PostMapping("/pauseJob") public String pauseJob( @RequestParam(name = "JOB_NAME", required = true) String jobName //@RequestParam(name = "JOB_GROUP" , required = false) String jobGroup ) { /*if(StringHelper.isNullOrEmptyString(jobGroup)){ jobGroup = JOB_DEFAULT_GROUP; }*/ quartzService.pauseJob(jobName, JOB_DEFAULT_GROUP); logger.info("pause job success jobName={}", jobName); return "SUCCESS!!"; } @ApiOperation(value = "* 恢复一个JOB") @PostMapping("/resumeJob") public String resumeJob( @RequestParam(name = "JOB_NAME", required = true) String jobName //@RequestParam(name = "JOB_GROUP" , required = false) String jobGroup ) { /*if(StringHelper.isNullOrEmptyString(jobGroup)){ jobGroup = JOB_DEFAULT_GROUP; }*/ quartzService.pauseJob(jobName, JOB_DEFAULT_GROUP); logger.info("resume job success jobName={}", jobName); return "SUCCESS!!"; } @ApiOperation(value = "* 删除一个JOB") @PostMapping("/deleteJob") public String deleteJob( @RequestParam(name = "JOB_NAME", required = true) String jobName //@RequestParam(name = "JOB_GROUP" , required = false) String jobGroup ) { /*if(StringHelper.isNullOrEmptyString(jobGroup)){ jobGroup = JOB_DEFAULT_GROUP; }*/ quartzService.deleteJob(jobName, JOB_DEFAULT_GROUP); logger.info("delete job success jobName={}", jobName); return "SUCCESS!!"; } @ApiOperation(value = "* 删除所有Job") @PostMapping("/deleteJobAll") public String deleteJobAll() { jobService.deleteAlljob(); logger.info("delete job all success"); return "SUCCESS!!"; } @ApiOperation(value = "* 重新加载所有Job") @PostMapping("/init") public String init() { jobService.deleteAlljob(); jobService.initJob(); logger.info("init job all success"); return "SUCCESS!!"; } }

在我这里所有的JOB都是同一个GROUP NAME ,因为我这里没有给用户增,改,删任务的访问接口只是项目发布时统一加载的配置,所以我没必要设计的那么复杂,如果大家有需要可以基于上面扩展一下就行。
我在application.yml中加入了一个默认的GROUP_NAME如下:

Copy
job: default: group: DEFAULT_JOB_GROUP
Top4.使用定时任务实现业务逻辑

创建用户JOB:

Copy
@Component public class UserJob extends QuartzJobBean { //这里可以注入业务逻辑BEAN //如: //@Autowired //private UserService userService; @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("user job 业务块"); } }

然后编写数据库脚本:

Copy
INSERT INTO `quartz`.`job_info` (`id`, `job_name`, `job_class`, `job_group_name`, `job_time`, `job_type`, `job_count`, `is_enable`) VALUES ('1', 'USER_JOB', 'com.boot.quartz.job.UserJob', 'DEFAULT_JOB_GROUP', '0/10 * * * * ? ', '1', '0', '1');

在这里注意job_name一定不能重复,因为我们用的group name全是一个,并且job_class一定要包名到类名,我们是反射加载的。
我们将工程打包成JAR包,然后首先执行数据库变更脚本,然后启动JAR包。启动后我们可以看到控制台每十秒进行一次打印:

Copy
2019-08-12 17:20:21.207 INFO 9068 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path '' 2019-08-12 17:20:21.210 INFO 9068 --- [ main] com.boot.quartz.CommonQuartzApplication : Started CommonQuartzApplication in 7.888 seconds (JVM running for 9.171) user job 业务块 user job 业务块

现在我们访问Swagger来操作任务:http://localhost:8081/swagger-ui.html
我们运行/quartz/runJob 输入JOB名称主动调用,可以看到控制台立马打印了我们上面写的那一句话,在这里其他的我就不试了。
该文源代码:https://github.com/450255266/open-doubi/tree/master/spring-boot/common-quartz

posted @   日落西风又在吹  阅读(6169)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示

"当前未开放哦"