前言

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

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

    Top1.任务脚本初始化

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

-- 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信息,脚本如下:

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工程,并且导入依赖:

<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,如下:

/*** @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文件,如下:

spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=UTCusername: rootpassword: rootjpa:hibernate:ddl-auto: update #ddl-auto:设为update表示每次都不会重新建表show-sql: trueapplication:name: common-quartz
server:port: 8081

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

Top3.主要实现

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

/*** @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 implementsApplicationContextAware {private transient AutowireCapableBeanFactory beanFactory;@Overridepublic void setApplicationContext(final ApplicationContext context) {beanFactory = context.getAutowireCapableBeanFactory();}/*** 将job实例交给spring ioc托管* 我们在job实例实现类内可以直接使用spring注入的调用被spring ioc管理的实例** @param bundle* @return* @throws Exception*/@Overrideprotected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {final Object job = super.createJobInstance(bundle);/*** 将job实例交付给spring ioc*/beanFactory.autowireBean(job);return job;}}/*** 配置任务工厂实例** @return*/@Beanpublic 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 nameschedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");//设置配置文件位置schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));return schedulerFactoryBean;}
}

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

/*** @author:伍梓涛* @version:1.0.0* @Modified By:SimpleWu* @CopyRright (c)2019-:YUM架构平台*/
@Configuration
@EnableSwagger2
public class Swagger2Config {@Beanpublic 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扫描。
接下来创建任务信息实体类(对应我们自己创建的那张表,来进行操作任务),如下:

@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的接口即可,简单粗暴:

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

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

@Service
public class QuartzService {@Autowiredprivate Scheduler scheduler;@PostConstructpublic 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层了,现在我们继续完善它,给他增加一个业务逻辑层

@Service
public class JobServiceImpl implements JobService {private Logger logger = LoggerFactory.getLogger(JobServiceImpl.class);@Autowiredprivate JobRepository jobRepository;@Autowiredprivate QuartzService quartzService;/*** 初始化Job数据*/@Override@PostConstructpublic 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@Transactionalpublic 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 进行调用如下:

@RestController
@RequestMapping("/quartz/")
@Api(description = "* Quartz任务管理中心", value = "job服务")
public class JobApiController {private final Logger logger = LoggerFactory.getLogger(JobApiController.class);@Autowiredprivate QuartzService quartzService;@Autowiredprivate 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如下:

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

创建用户JOB:

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

然后编写数据库脚本:

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包。启动后我们可以看到控制台每十秒进行一次打印:

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/SpringBoot/common-quartz

转载于:https://www.cnblogs.com/SimpleWu/p/11341453.html

简单设计企业级JOB平台相关推荐

  1. 蚂蚁金服资深总监韩鸿源:企业级数据库平台的持续与创新

    2019年11月19日,蚂蚁金服在北京举办"巅峰洞见·聚焦金融新技术"发布会,介绍2019双11支付宝背后的技术,并重磅发布全新OceanBase 2.2版本.欢迎持续关注- 蚂蚁 ...

  2. 企业级监控平台,监控系统选型

    企业级监控平台,监控系统选型 一.监控基础知识 1.1 监控系统的7大作用 1.2 使用监控系统的正确姿势 1.3 监控的对象和指标都有哪些? 1.4 监控系统的基本流程 1.5 监控目标 1.6 监 ...

  3. 全网开源快速开发平台,低代码平台,企业级开发平台,开源系统,私活平台,学习平台,毕设平台,企业级应用开发平台资源整理

    个人比较喜欢开源技术,经常在开源社区寻找一些优质的开源项目. 这是一个巨人的时代,唯有站在巨人的肩膀,与巨人同行,我们才能够走的更快. 现在技术迭代升级速度比较快,大学上学时,热火朝天的还在宣扬SSM ...

  4. 云社区 博客 博客详情 如何设计实时数据平台(技术篇)

    https://github.com/BriData/DBus [摘要] 实时数据平台(RTDP,Real-time Data Platform)是一个重要且常见的大数据基础设施平台.在上篇(设计篇) ...

  5. 【云原生-DevOps】企业级DevOps平台搭建及技术选型-项目管理篇

    开篇 为什么要搭建企业级的DevOps 如果产品研发团队相对比较迷你,比如在100人以内,个人觉得是不需要企业级DevOps的,大家简单快捷的安装一些常用的协作软件就能正常运转 本篇文章主要简述主要是 ...

  6. 阿里云大数据认证——基于阿里云数加构建企业级数据分析平台-课堂笔记

    阿里云Clouder认证 六.基于阿里云数加构建企业级数据分析平台 1. 课程目标 (1) 了解数据分析的步骤和目的 (2) 熟悉数据分析平台搭建的组成部分 (3) 掌握阿里云数加不同产品及其使用场景 ...

  7. 如何设计可视化搭建平台的组件商店?

    相关文章: 如何搭积木式的快速开发H5页面? 演示地址: H5-Dooring页面制作平台 关注并将「趣谈前端」设为星标 每早08:30按时推送技术干货/优秀开源/技术思维 之前一直在做 lowcod ...

  8. 天云数据中标某股份制证券公司项目 提供国产企业级人工智能平台服务

    今年4月,某证券有限公司决定就"企业级"人工智能平台产品及技术服务进行招标采购,天云数据中标.标书发出日期为4月1日,投标截止时间为4月21日15:00.不足21天的准备,什么样的 ...

  9. 浅谈企业级物联网平台

    随著越来越多的公司开始涉足物联网相关领域,这也意味着会有很多原来是做互联网项目开发的同学也不得不开始学习物联网的开发,同样的对于项目经理来说,如何确定一个满足业务需求的物联网技术架构则至关重要,笔者根 ...

最新文章

  1. epoch,iteration,batch,batch_size
  2. 云南大学网络课程作业计算机,云南大学842计算机程序设计考研复习经验
  3. java链接mysql出问题_java连接MySQL出现问题
  4. openpyxl删除添加excel列_Python | 如何使用Python操作Excel(二)
  5. Angularjs 观察者模式 理解
  6. JavaScript 插件的书页翻转效果
  7. 使用ArcMap将txt数据转换成shp数据
  8. PE启动盘制作,电脑PE工具制作教程(可能是最详细的制作方法)小白推荐
  9. 设计模式-适配器模式(类适配器、对象适配器、接口适配器详解)
  10. Technorati 2008 年度博客状况报告(第一部分)
  11. python图片后缀转换---统一转换成.jpg
  12. 【Godot】组合键的实现
  13. 汉中至巴中至南充铁路(汉巴南线)顺利开通
  14. 学习淘宝分享出来的链接web检测打开原生App
  15. 洛谷P4043 费用流
  16. Java 后端技术清单 2023版
  17. Hadoop配置—完全分布式
  18. 【笔记】unity渲染类名词术语概念总结(30个点)
  19. 如何使用IDM分类管理下载文件
  20. 显微镜下的白细胞 捉到一群正在撒欢de小可爱,哇哇~

热门文章

  1. mysql 2003 10038_关于MySql10038错误的完美解决方法(三种)
  2. ddd架构 无法重构_DDD有什么用?
  3. python图像resize_Python图像resize前后颜色不一致问题
  4. 从 SQL Server 到 MySQL (三):愚公移山 - 开源力量
  5. Python基础知识(3)
  6. springboot thymeleaf模板使用
  7. 企业之HA~cluster
  8. 关于tomcat的思考
  9. jQuery插件开发全解析(转)
  10. python字符串取消转义_python取消转义,除了r还有什么?可以调用函数取消转义吗?...