zoukankan      html  css  js  c++  java
  • 简单设计企业级JOB平台

    前言

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

    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.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配置文件,如下:

    /**
     * @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配置,如下:

    /**
     * @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扫描。
    接下来创建任务信息实体类(对应我们自己创建的那张表,来进行操作任务),如下:

    @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 {
    
        @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层了,现在我们继续完善它,给他增加一个业务逻辑层

    @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 进行调用如下:

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

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

    创建用户JOB:

    @Component
    public class UserJob extends QuartzJobBean {
    
        //这里可以注入业务逻辑BEAN
        //如:
        //@Autowired
        //private UserService userService;
    
        @Override
        protected 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/spring-boot/common-quartz

  • 相关阅读:
    Sendkeys 和 Sendmessage 使用技巧一例
    和菜鸟一起学算法之二分法求极值问题
    和菜鸟一起学算法之三分法求极值问题
    和菜鸟一起学证券投资之国内生产总值GDP
    和菜鸟一起学OK6410之Led字符驱动
    和菜鸟一起学OK6410之最简单驱动模块hello world
    和菜鸟一起学OK6410之交叉编译hello world
    和菜鸟一起学android4.0.3源码之touchscreen配置+调试记录
    和菜鸟一起学android4.0.3源码之红外遥控器适配
    和菜鸟一起学OK6410之最简单字符驱动
  • 原文地址:https://www.cnblogs.com/SimpleWu/p/11341453.html
Copyright © 2011-2022 走看看