SpringBoot--实战开发--整合Quartz(十三)

一、Quartz简介

  Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目。
官网:http://www.quartz-scheduler.org/

Quartz

Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
Quartz 允许程序开发人员根据时间的间隔来调度作业。
Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
Quartz 核心概念:

  1. Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context) 
  1. JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
  2. Trigger 代表一个调度参数的配置,什么时候去调。
  3. Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

二、Maven依赖

<!--quartz依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

三、Quartz表

序号 表名 说明
1. qrtz_calendars 以 Blob 类型存储 Quartz 的 Calendar 信息
2. qrtz_cron_triggers 存储 Cron Trigger,包括 Cron 表达式和时区信息
3. qrtz_fired_triggers 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息
4. qrtz_paused_trigger_grps 存储已暂停的 Trigger 组的信息
5. qrtz_scheduler_state 存储少量的有关调度器 (Scheduler) 的状态,和别的 调度器 (Scheduler)实例(假如是用于一个集群中)
6. qrtz_locks 存储程序的非观锁的信息(假如使用了悲观锁)
7. qrtz_job_details 存储每一个已配置的 Job 的详细信息(jobDetail)
8. qrtz_job_listeners 存储有关已配置的 Job 监听器 的信息
9. qrtz_simple_triggers 存储简单的 Trigger,包括重复次数,间隔,以及已触的次数
10. qrtz_blog_triggers 以 Blob 类型存储的Trigger(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候)
11. qrtz_trigger_listeners 存储已配置的触发器监听器 ( Trigger Listener ) 的信息
12. qrtz_triggers 存储已配置的 触发器 (Trigger) 的信息

脚本:

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 DEFAULT CHARSET=utf8 ;


#--存储已配置的 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 DEFAULT CHARSET=utf8 ;


#-- 存储简单的 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 DEFAULT CHARSET=utf8 ;


#-- 存储 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 DEFAULT CHARSET=utf8 ;


#-- 存储简单的 Trigger,包括重复次数,间隔,以及已触的次数
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 DEFAULT CHARSET=utf8 ;

#-- 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 DEFAULT CHARSET=utf8 ;


#-- 以 Blob 类型存储 Quartz 的 Calendar 信息
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  DEFAULT CHARSET=utf8 ;

#-- 存储已暂停的 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  DEFAULT CHARSET=utf8 ;


#-- 存储与已触发的 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  DEFAULT CHARSET=utf8 ;


#-- 存储少量的有关 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  DEFAULT CHARSET=utf8 ;


#-- 存储程序的悲观锁的信息(假如使用了悲观锁)
CREATE TABLE qrtz_locks (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;



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;

四、Quartz配置

# 配置集群时,quartz调度器的id,由于配置集群时,只有一个调度器,必须保证每个服务器该值都相同,只要每个都一样就行
spring.quartz.properties.org.quartz.scheduler.instanceName=quartzScheduler
# 集群中每台服务器自己的id,AUTO表示自动生成,ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的)
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
# 实现集群时,任务的存储实现方式,org.quartz.impl.jdbcjobstore.JobStoreTX表示数据库存储
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
# Driver代表了解不同数据库系统的特定“方言”
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# quartz存储任务相关数据的表的前缀
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
# 是否启用集群,启用,改为true,注意:启用集群后,必须配置下面的数据源,否则quartz调度器会初始化失败
spring.quartz.properties.org.quartz.jobStore.isClustered=true
# 集群中服务器相互检测间隔,每台服务器都会按照下面配置的时间间隔往服务器中更新自己的状态,
# 如果某台服务器超过以下时间没有checkin,调度器就会认为该台服务器已经down掉,不会再分配任务给该台服务器
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=10000
# 以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,因此可以作为名称 - 值对存储
# 而不是在BLOB列中以其序列化形式存储更多复杂的对象。
# 从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题
spring.quartz.properties.org.quartz.jobStore.useProperties=false
# 线程池
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# quartz线程池中线程数,可根据任务数量和负责度来调整
spring.quartz.properties.org.quartz.threadPool.threadCount=10
# quartz线程优先级
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
# 线程池
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
# 设置quartz任务的数据持久化方式,默认是内存方式
spring.quartz.job-store-type=JDBC
# 序启动会自动创建quartz相关表 初始化表结构ALWAYS 不初始化表结构NEVER
spring.quartz.jdbc.initialize-schema=ALWAYS

附:
org.quartz.jobStore.driverDelegateClass
Driver代表了解不同数据库系统的特定“方言”。可能的选择包括:
org.quartz.impl.jdbcjobstore.StdJDBCDelegate(用于完全符合JDBC的驱动程序)
org.quartz.impl.jdbcjobstore.MSSQLDelegate(对于Microsoft SQL Server和Sybase)
org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
org.quartz.impl.jdbcjobstore.WebLogicDelegate(对于WebLogic驱动程序)
org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
org.quartz.impl.jdbcjobstore.oracle.WebLogicOracleDelegate(对于Weblogic中使用的Oracle驱动程序)
org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate(对于在Weblogic中使用的Oracle驱动程序)
org.quartz.impl.jdbcjobstore.CloudscapeDelegate
org.quartz.impl.jdbcjobstore.DB2v6Delegate
org.quartz.impl.jdbcjobstore.DB2v7Delegate
org.quartz.impl.jdbcjobstore.DB2v8Delegate
org.quartz.impl.jdbcjobstore.HSQLDBDelegate
org.quartz.impl.jdbcjobstore.PointbaseDelegate
org.quartz.impl.jdbcjobstore.SybaseDelegate

五、Quartz 的触发时间的配置

cron 方式:采用cronExpression表达式配置时间。
simple 方式:和JavaTimer差不多,可以指定一个开始时间和结束时间外加一个循环时间。
calendars 方式:可以和cron配合使用,用cron表达式指定一个触发时间规律,用calendar指定一个范围。
注意:cron方式需要用到的4张数据表:
qrtz_triggers,qrtz_cron_triggers,qrtz_fired_triggers,qrtz_job_details。

六、简单调度示例

  1. 计划任务类
@Component
@Slf4j
public class ScheduleTask  extends QuartzJobBean {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("当前时间是: " + dateFormat.format(new Date()));
    }
}
  1. 计划任务配置
@Configuration
public class ScheduleConfiguration {
    @Bean
    public JobDetail scheduleJob() {
        // 添加已定义的计划任务
        return JobBuilder.newJob(ScheduleTask.class)
                .storeDurably()     //
                // 可以给该JobDetail起一个id,便于之后的检索。也可以 .withIdentity("myjob", "group1")
                .withIdentity("sample_schedule") //即使没有Trigger关联时,也不需要删除该JobDetail
                .withDescription("Sample schedule task") // 描述信息
                .build();
    }

    @Bean
    public Trigger scheduleTrigger() {
        return newTrigger()
                .withIdentity("trigger")
                .forJob(scheduleJob())
                 // cron 表达式,每分钟执行一次
                .withSchedule(CronScheduleBuilder.cronSchedule("0 * * * * ?"))
                .build();
    }
}
  1. 测试
    启动程序:


    测试结果

六、动态计划任务

  1. 创建表
DROP TABLE IF EXISTS `task_schedule_job`;
CREATE TABLE `task_schedule_job`  (
  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `cron_expression` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'cron表达式',
  `method_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务调用的方法名',
  `method_params` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求参数',
  `misfire_policy` varchar(4) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '执行策略',
  `is_concurrent` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务是否有状态',
  `job_status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务状态',
  `job_group` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务分组',
  `execute_class` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Spring bean',
  `job_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务名',
  `load_way` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加载任务方式',
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务描述',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建者',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新者',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `task_schedule_job_log`;
CREATE TABLE `task_schedule_job_log`  (
  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务日志ID',
  `job_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务名称',
  `job_group` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务组名',
  `execute_class` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '执行类名',
  `method_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务方法',
  `method_params` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '方法参数',
  `job_message` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '日志信息',
  `status` char(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '执行状态(0普通 1成功 -1失败)',
  `exception_info` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '异常信息',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '定时任务调度日志表' ROW_FORMAT = Dynamic;

  1. 创建实体类
    任务类:ScheduleJob
@Data
@Accessors(chain = true)
@TableName("task_schedule_job")
public class ScheduleJob  {
    /** 任务主键 */
    @TableId(value = "id", type = IdType.UUID)
    private String id;
    /** 任务名 */
    @TableField(value = "job_name")
    private String jobName;
    /** cron表达式 */
    @TableField(value = "cron_expression")
    private String cronExpression;
    /** Spring bean */
    @TableField(value = "execute_class")
    private String executeClass;
    /** 任务调用的方法名 */
    @TableField(value = "method_name")
    private String methodName;
    /**
     * 任务调用的参数
     */
    @TableField(value = "method_params")
    private String methodParams;
    /**
     * 执行策略
     */
    @TableField(value = "misfire_policy")
    private String misfirePolicy;

    @TableField(value = "load_way")
    private String loadWay;
    /** 任务是否有状态 */
    @TableField(value = "is_concurrent")
    private String isConcurrent;
    /** 任务描述 */
    @TableField(value = "description")
    private String description;
    /** 更新者 */
    @TableField(value = "update_by")
    private String updateBy;
    /** 创建时间 */
    @TableField(value = "create_time")
    @JSONField(format="yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
    /** 任务状态 */
    @TableField(value = "job_status")
    private String jobStatus;
    /** 任务分组 */
    @TableField(value = "job_group")
    private String jobGroup;
    /** 更新时间 */
    @TableField(value = "update_time")
    @JSONField(format="yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
    /** 创建者 */
    @TableField(value = "create_by")
    private String createBy;
}

日志类:

@Data
@TableName("task_schedule_job_log")
public class ScheduleJobLog {
    /**
     * 成功
     */
    public static final String SCHEDULE_JOB_LOG_RUN_SUCCESS = "1";
    /**
     * 正常
     */
    public static final String SCHEDULE_JOB_LOG_RUN_NOMAL = "0";
    /**
     * 失败
     */
    public static final String SCHEDULE_JOB_LOG_RUN_FAIL = "-1";
    /**任务日志ID*/
    @TableId(value = "id", type = IdType.UUID)
    private String id;
    /**任务名称*/
    @TableField(value = "job_name")
    private String jobName;
    /**任务组名*/
    @TableField(value = "job_group")
    private String jobGroup;
    /** Spring bean */
    @TableField(value = "execute_class")
    private String executeClass;
    /**任务方法*/
    @TableField(value = "method_name")
    private String methodName;
    /**方法参数*/
    @TableField(value = "method_params")
    private String methodParams;
    /**日志信息*/
    @TableField(value = "job_message")
    private String jobMessage;
    /**执行状态(0正常 1失败)*/
    @TableField(value = "status")
    private String status;
    /**异常信息*/
    @TableField(value = "exception_info")
    private String exceptionInfo;
    /**创建时间*/
    @TableField(value = "create_time")
    @JSONField(format="yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
}

数据类:

@Data
public class ScheduleJob implements Serializable {
    private static final long serialVersionUID = 1L;
    private String jobId;
    /**
     * 任务名称
     */
    private String jobName;
    /**
     * 任务分组
     */
    private String jobGroup;
    /**
     * 任务状态 是否启动任务
     */
    private String jobStatus;

    /**
     * cron表达式
     */
    private String cronExpression;

    /**
     * 类加载方式
     */
    private String loadWay;
    /**
     * 描述
     */
    private String description;
    /**
     * 任务是否有状态
     */
    private String isConcurrent;

    /**
     * 任务调用的方法名
     */
    private String methodName;

    /**
     * 任务调用的参数
     */
    private String methodParams;
    /**
     * 执行策略
     */
    private String misfirePolicy;
    /**
     * 执行类
     */
    private String executeClass;
}

  1. 任务常量类
    作用:用于定义生成键
public interface ScheduleConstants {
    String TASK_JOB_KEY_PRE_ = "TASK_JOB_KEY_PRE_";
    /**
     * 触发器键
     */
    String TASK_TRIGGER_KEY_PRE_ = "TASK_TRIGGER_KEY_PRE_";
    /**
     * 任务Bean键
     */
    String TASK_JOB_BEAN_KEY = "TASK_JOB_BEAN_KEY";
    /**
     * 默认策略
     */
    String MISFIRE_DEFAULT = "0";

    /**
     * 立即触发执行
     */
    String MISFIRE_IGNORE_MISFIRES = "1";

    /**
     * 触发一次执行
     */
    String MISFIRE_FIRE_AND_PROCEED = "2";

    /**
     * 不触发立即执行
     */
    String MISFIRE_DO_NOTHING = "3";
    String STATUS_RUNNING = "1";
    String STATUS_NOT_RUNNING = "0";
    String CONCURRENT_IS = "1";
    String CONCURRENT_NOT = "0";
}
  1. 计划任务工厂类
    作用:执行任务
@Slf4j
public class QuartzJobFactory extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info(context.getMergedJobDataMap().get(ScheduleConstants.TASK_JOB_BEAN_KEY).toString());
        // 根据键名获取计划任务
        ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get(ScheduleConstants.TASK_JOB_BEAN_KEY);
        // 任务回调,主要用来记录日志
        QuartzExecuteCallback quartzExecuteCallback = SpringContextHolder.getBean(QuartzExecuteCallback.class);
        long startTime = System.currentTimeMillis();
        try {
            // TODO 1.开始执行日志
            quartzExecuteCallback.onStart(scheduleJob);
            // 执行任务
            log.info("任务开始执行 - 名称:{} 方法:{}", scheduleJob.getJobName(), scheduleJob.getMethodName());
            ScheduleJobinvoke.invokeMethod(scheduleJob);
            long times = System.currentTimeMillis() - startTime;
            String message = scheduleJob.getJobName() + " 总共耗时:" + times + "毫秒";
            //  TODO 2.执行成功日志
            quartzExecuteCallback.onSuccess(scheduleJob, message);
            log.info("任务执行结束 - 名称:{} 耗时:{} 毫秒", scheduleJob.getJobName(), times);
        } catch (Exception e) {
            log.info("任务执行失败 - 名称:{} 方法:{}", scheduleJob.getJobName(), scheduleJob.getMethodName());
            log.error("任务执行异常  - :", e);
            long times = System.currentTimeMillis() - startTime;
            String message = scheduleJob.getJobName() + " 总共耗时:" + times + "毫秒";
            // TODO 3.执行失败日志
            quartzExecuteCallback.onFailure(scheduleJob, e, message);
        }
    }
}
  1. 计划任务反射类
public class ScheduleJobinvoke {

    /**
     * 通过反射调用scheduleJob中定义的方法
     *
     * @param scheduleJob
     */
    public static void invokeMethod(ScheduleJob scheduleJob) {
        try {
            Object object = null;
            Class<?> clazz = null;
            Method method = null;
            // 类加载方式
            if (scheduleJob.getLoadWay().equals("1")) {
                object = SpringContextHolder.getBean(scheduleJob.getExecuteClass());
            } else if (StringUtils.isNotBlank(scheduleJob.getExecuteClass())) {
                clazz = Class.forName(scheduleJob.getExecuteClass());
                object = clazz.newInstance();
            }
            if (object == null) {
                throw new QuartzException("任务对象不存在");
            }
            clazz = object.getClass();
            method = clazz.getDeclaredMethod(scheduleJob.getMethodName());
            if (method != null) {
                String params = scheduleJob.getMethodParams();
                ReflectionUtils.makeAccessible(method);
                // 执行方法,有参与无参
                if (StringUtils.isNotEmpty(params))
                {
                    method.invoke(object, params);
                }else
                {
                    method.invoke(object);
                }
            }
        } catch (InstantiationException e) {
            throw new QuartzException(e.getMessage());
        }  catch (ClassNotFoundException e) {
            throw new QuartzException(e.getMessage());
        } catch (NoSuchMethodException e) {
            throw new QuartzException(e.getMessage());
        } catch (SecurityException e) {
            throw new QuartzException(e.getMessage());
        } catch (IllegalAccessException e) {
            throw new QuartzException(e.getMessage());
        } catch (IllegalArgumentException e) {
            throw new QuartzException(e.getMessage());
        } catch (InvocationTargetException e) {
            throw new QuartzException(e.getMessage());
        }
    }
}
  1. 禁用并发接口
@DisallowConcurrentExecution
public class QuartzJobFactoryDisallowConcurrentExecution extends QuartzJobFactory {
}

@DisallowConcurrentExecution 注解
Quartz定时任务默认都是并发执行的,不会等待上一次任务执行完毕,只要间隔时间到就会执行, 如果定时任执行太长,会长时间占用资源,导致其它任务堵塞。
@DisallowConcurrentExecution 禁止并发执行多个相同定义的JobDetail, 这个注解是加在Job类上的, 但意思并不是不能同时执行多个Job, 而是不能并发执行同一个Job Definition(由JobDetail定义), 但是可以同时执行多个不同的JobDetail。
@PersistJobDataAfterExecution 同样, 也是加在Job上,表示当正常执行完Job后, JobDataMap中的数据应该被改动, 以被下一次调用时用。当使用@PersistJobDataAfterExecution 注解时, 为了避免并发时, 存储数据造成混乱。

  1. 任务管理器
@Slf4j
@Component
public class QuartzManager {
    /**
     * 获取触发器key
     */
    public static TriggerKey getTriggerKey(ScheduleJob job) {
        return TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
    }

    /**
     * 获取jobKey
     */
    public static JobKey getJobKey(ScheduleJob job) {
        return JobKey.jobKey(job.getJobName(), job.getJobGroup());
    }

    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(ScheduleJob scheduleJob, CronScheduleBuilder cb)
            throws QuartzException {
        String misfirePolicy = scheduleJob.getMisfirePolicy();
        if (StringUtils.isEmpty(misfirePolicy)) {
            misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT;
        }
        switch (misfirePolicy) {
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb;
            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionIgnoreMisfires();
            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionFireAndProceed();
            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionDoNothing();
            default:
                throw new QuartzException("这个任务规则无效 '" + scheduleJob.getMisfirePolicy()
                        + "不能在cron计划任务中使用");
        }
    }

    /**
     * 添加任务
     *
     * @param job
     * @throws SchedulerException
     */
    public void addJob(ScheduleJob job) throws SchedulerException {
        if (job == null || !ScheduleConstants.STATUS_RUNNING.equals(job.getJobStatus())) {
            return;
        }
        // 获取调度工厂Bean
        Scheduler scheduler = SpringContextHolder.getBean(SchedulerFactoryBean.class).getScheduler();
        log.debug(scheduler + "...................................................add");
        TriggerKey triggerKey = this.getTriggerKey(job);
        // 调度器中获取触发器
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        // TODO 1. 不存在,创建一个
        if (null == trigger) {
            // 如果任务就同步获取QuartzJobFactory 对象,如果不是同步QuartzJobFactoryDisallowConcurrentExecution
            Class<? extends Job> clazz = ScheduleConstants.CONCURRENT_IS.equals(job.getIsConcurrent())
                    ? QuartzJobFactory.class : QuartzJobFactoryDisallowConcurrentExecution.class;
            // 创建对象
            JobDetail jobDetail = JobBuilder.newJob(clazz)
                    .storeDurably()
                    .requestRecovery()
                    .withIdentity(getJobKey(job))
                    .withDescription(job.getDescription())
                    .build();
            jobDetail.getJobDataMap()
                    .put(ScheduleConstants.TASK_JOB_BEAN_KEY, job);
            // 使用Cron表达式
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
            // 执行策略
            scheduleBuilder = handleCronScheduleMisfirePolicy(job, scheduleBuilder);
            // 使用触发器
            trigger = TriggerBuilder
                    .newTrigger()
                    .withIdentity(getTriggerKey(job))
                    .withSchedule(scheduleBuilder)
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
        } else {
            // TODO 2.Trigger已存在,那么更新相应的定时设置
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
            // TODO 3.执行策略
            scheduleBuilder = handleCronScheduleMisfirePolicy(job, scheduleBuilder);
            // TODO 4.按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            // TODO 5.按新的trigger重新设置job执行
            scheduler.rescheduleJob(triggerKey, trigger);
        }
        // TODO 6.暂停任务
        if (job.equals(ScheduleConstants.STATUS_NOT_RUNNING)) {
            pauseJob(job);
        }
    }

    /**
     * 获取所有计划中的任务列表
     *
     * @return
     * @throws SchedulerException
     */
    public List<ScheduleJob> getAllJob() throws SchedulerException {
        Scheduler scheduler = SpringContextHolder.getBean(SchedulerFactoryBean.class).getScheduler();
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        List<ScheduleJob> jobList = new ArrayList<ScheduleJob>();
        for (JobKey jobKey : jobKeys) {
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
            for (Trigger trigger : triggers) {
                JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                ScheduleJob job = (ScheduleJob) jobDetail.getJobDataMap().get(ScheduleConstants.TASK_JOB_BEAN_KEY);
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                job.setJobStatus(triggerState.name());
                if (trigger instanceof CronTrigger) {
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    String cronExpression = cronTrigger.getCronExpression();
                    job.setCronExpression(cronExpression);
                }
                jobList.add(job);
            }
        }
        return jobList;
    }

    /**
     * 所有正在运行的job
     *
     * @return
     * @throws SchedulerException
     */
    public List<ScheduleJob> getRunningJob() throws SchedulerException {
        Scheduler scheduler = SpringContextHolder.getBean(SchedulerFactoryBean.class).getScheduler();
        List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
        List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size());
        for (JobExecutionContext executingJob : executingJobs) {
            JobDetail jobDetail = executingJob.getJobDetail();
            JobKey jobKey = jobDetail.getKey();
            Trigger trigger = executingJob.getTrigger();
            ScheduleJob job = (ScheduleJob) jobDetail.getJobDataMap().get(ScheduleConstants.TASK_JOB_BEAN_KEY);
            Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
            job.setJobStatus(triggerState.name());
            if (trigger instanceof CronTrigger) {
                CronTrigger cronTrigger = (CronTrigger) trigger;
                String cronExpression = cronTrigger.getCronExpression();
                job.setCronExpression(cronExpression);
            }
            jobList.add(job);
        }
        return jobList;
    }

    /**
     * 暂停一个job
     *
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException {
        Scheduler scheduler = SpringContextHolder.getBean(SchedulerFactoryBean.class).getScheduler();
        JobKey jobKey = getJobKey(scheduleJob);
        scheduler.pauseJob(jobKey);
    }

    /**
     * 恢复一个job
     *
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException {
        Scheduler scheduler = SpringContextHolder.getBean(SchedulerFactoryBean.class).getScheduler();
        JobKey jobKey = getJobKey(scheduleJob);
        scheduler.resumeJob(jobKey);
    }

    /**
     * 删除一个job
     *
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void deleteJob(ScheduleJob scheduleJob) throws SchedulerException {
        Scheduler scheduler = SpringContextHolder.getBean(SchedulerFactoryBean.class).getScheduler();
        JobKey jobKey = getJobKey(scheduleJob);
        scheduler.deleteJob(jobKey);
    }

    /**
     * 立即执行job
     *
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void runAJobNow(ScheduleJob scheduleJob) throws SchedulerException {
        Scheduler scheduler = SpringContextHolder.getBean(SchedulerFactoryBean.class).getScheduler();
        JobKey jobKey = getJobKey(scheduleJob);
        scheduler.triggerJob(jobKey);
    }

    /**
     * 更新job时间表达式
     *
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void updateJobCron(ScheduleJob scheduleJob) throws SchedulerException {
        Scheduler scheduler = SpringContextHolder.getBean(SchedulerFactoryBean.class).getScheduler();
        TriggerKey triggerKey = getTriggerKey(scheduleJob);
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression());
        // 执行策略
        scheduleBuilder = handleCronScheduleMisfirePolicy(scheduleJob, scheduleBuilder);

        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
        // 参数
        trigger.getJobDataMap().put(ScheduleConstants.TASK_JOB_BEAN_KEY, scheduleJob);
        scheduler.rescheduleJob(triggerKey, trigger);

        // 暂停任务
        if (scheduleJob.getJobStatus().equals(ScheduleConstants.STATUS_NOT_RUNNING)) {
            pauseJob(scheduleJob);
        }
    }

    /**
     * 清空任务
     *
     * @throws SchedulerException
     */
    public void empty() throws SchedulerException {
        Scheduler scheduler = SpringContextHolder.getBean(SchedulerFactoryBean.class).getScheduler();
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        scheduler.deleteJobs(Lists.newArrayList(jobKeys));
    }
}
  1. 自定义异常
public class QuartzException extends RuntimeException {
    public QuartzException() {
        super();
    }
    public QuartzException(String message) {
        super(message);
    }
    public QuartzException(String message, Throwable cause) {
        super(message, cause);
    }
    public QuartzException(Throwable cause) {
        super(cause);
    }
    protected QuartzException(String message, Throwable cause,
                              boolean enableSuppression,
                              boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
  1. 初始化接口
public interface QuartzInitCallback {
    void initSchedule();
}

  1. 记录日志回调类
    作用:用于记录日志
public interface QuartzExecuteCallback {

    /**
     * 执行开始
     * @param scheduleJob
     */
    void onStart(ScheduleJob scheduleJob);
    /**
     * 当执行成功
     * @param scheduleJob
     * @param message
     */
    void onSuccess(ScheduleJob scheduleJob, String message);

    /**
     * 执行失败
     * @param scheduleJob
     * @param e
     * @param message
     */
    void onFailure(ScheduleJob scheduleJob, Exception e, String message);
}

  1. 业务接口

1)接口

public interface IScheduleJobService extends IService<ScheduleJob> {
    /**
     * 初始化任务
     */
    void initSchedule();

    /**
     * 更改任务
     *
     * @param jobId
     * @param cmd
     */
    void changeStatus(String jobId, String cmd);

    /**
     * 更改任务 cron表达式
     */
    void updateCron(String jobId);

    /**
     * 执行一次
     */
    void runAJobNow(String jobId);

    /**
     * 刷新任务
     */
    void refreshTask();

    /**
     * 删除任务
     * @param jobId
     * @return
     */
    boolean deleteById(String jobId);

    /**
     * 获取任务分页数据
     * @param jobRequest
     * @return
     */
    IPage<ScheduleJob> getListByJobName(JobRequest jobRequest);

    /**
     * 添加任务
     * @param entity
     */
    void add(ScheduleJob entity);
}

2)实现类

@Slf4j
@Service
public class IScheduleJobServiceImpl extends ServiceImpl<ScheduleJobMapper, ScheduleJob> implements IScheduleJobService, QuartzInitCallback {
    @Autowired
    private ScheduleJobMapper scheduleJobMapper;
    @Autowired
    private QuartzManager quartzManager;

    @Override
    public void initSchedule() {
        try {
            QueryWrapper<ScheduleJob> queryWrapper = new QueryWrapper<>();
            // 这里获取任务信息数据
            List<ScheduleJob> jobList = this.list(queryWrapper);
            for (ScheduleJob scheduleJob : jobList) {
                quartzManager.addJob(ScheduleJobUtils.entityToData(scheduleJob));
            }
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public void changeStatus(String jobId, String cmd) {
        try {
            ScheduleJob scheduleJob = this.getById(jobId);
            if (scheduleJob == null) {
                return;
            }
            if ("stop".equals(cmd)) {
                quartzManager.deleteJob(ScheduleJobUtils.entityToData(scheduleJob));
                scheduleJob.setJobStatus(ScheduleConstants.STATUS_NOT_RUNNING);
            } else if ("start".equals(cmd)) {
                scheduleJob.setJobStatus(ScheduleConstants.STATUS_RUNNING);
                // 添加对象
                quartzManager.addJob(ScheduleJobUtils.entityToData(scheduleJob));
            }
            // 更新数据库
            this.updateById(scheduleJob);
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public void updateCron(String jobId) {
        ScheduleJob scheduleJob = this.getById(jobId);
        if (scheduleJob == null) {
            return;
        }
        try {
            // 更新任务
            quartzManager.updateJobCron(ScheduleJobUtils.entityToData(scheduleJob));
            // 更新数据据
            this.updateById(scheduleJob);
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public void runAJobNow(String jobId) {
        try {
            ScheduleJob scheduleJob = this.getById(jobId);
            quartzManager.runAJobNow(ScheduleJobUtils.entityToData(scheduleJob));
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public void refreshTask() {
        try {
            quartzManager.empty();
            // 实始化任务
            initSchedule();
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public boolean deleteById(String jobId) {
        try {
            ScheduleJob scheduleJob = this.getById(jobId);
            quartzManager.deleteJob(ScheduleJobUtils.entityToData(scheduleJob));
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
        // 删除数据库
        return this.deleteById(jobId);
    }

    @Override
    public IPage<ScheduleJob> getListByJobName(JobRequest jobRequest) {
        QueryWrapper<ScheduleJob> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("create_time");
        if (StringUtils.isNotEmpty(jobRequest.getJobName())) {
            queryWrapper.like("jobName", jobRequest.getJobName());
        }
        Page page = new Page(jobRequest.getCurrentPage(), jobRequest.getPageSize());
        IPage<ScheduleJob> iPage = this.page(page, queryWrapper);
        return iPage;
    }

    @Override
    public void add(ScheduleJob entity) {
        this.save(entity);
    }
}
  1. 任务日志接口与实现类

1)接口

public interface IScheduleJobLogService extends IService<ScheduleJobLog> {
    /**
     * @return
     */
    IPage<ScheduleJobLog> getListByJobNameAndExecuteClassAndStatus(ScheduleJobLogRequest scheduleJobLogRequest);
}

2)实现类

@Slf4j
@Service
public class IScheduleJobLogServiceImpl  extends ServiceImpl<ScheduleJobLogMapper, ScheduleJobLog> implements IScheduleJobLogService, QuartzExecuteCallback {
    @Autowired
    private ScheduleJobLogMapper scheduleJobLogMapper;

    @Override
    public void onStart(ScheduleJob scheduleJob) {
        ScheduleJobLog scheduleJobLog = newJobLog(scheduleJob);
        scheduleJobLog.setJobMessage(scheduleJob.getJobName() + "运行开始!");
        scheduleJobLog.setStatus(ScheduleJobLog.SCHEDULE_JOB_LOG_RUN_NOMAL);
        this.save(scheduleJobLog);
    }

    @Override
    public void onSuccess(ScheduleJob scheduleJob, String message) {
        ScheduleJobLog scheduleJobLog = newJobLog(scheduleJob);
        scheduleJobLog.setJobMessage(message);
        scheduleJobLog.setStatus(ScheduleJobLog.SCHEDULE_JOB_LOG_RUN_SUCCESS);
        this.save(scheduleJobLog);
    }

    @Override
    public void onFailure(ScheduleJob scheduleJob, Exception e, String message) {
        ScheduleJobLog scheduleJobLog = newJobLog(scheduleJob);
        scheduleJobLog.setJobMessage(message);
        scheduleJobLog.setStatus(ScheduleJobLog.SCHEDULE_JOB_LOG_RUN_FAIL);
        scheduleJobLog.setExceptionInfo(e.getMessage());
        this.save(scheduleJobLog);
    }

    /**
     * 进行数据类型转换
     * @param scheduleJob
     * @return
     */
    private ScheduleJobLog newJobLog(ScheduleJob scheduleJob){
        ScheduleJobLog scheduleJobLog = new ScheduleJobLog();
        scheduleJobLog.setExecuteClass(scheduleJob.getExecuteClass());
        scheduleJobLog.setJobName(scheduleJob.getJobName());
        scheduleJobLog.setJobGroup(scheduleJob.getJobGroup());
        scheduleJobLog.setMethodName(scheduleJob.getMethodName());
        scheduleJobLog.setMethodParams(scheduleJob.getMethodParams());
        scheduleJobLog.setCreateTime(new Date());
        return scheduleJobLog;
    }

    @Override
    public IPage<ScheduleJobLog> getListByJobNameAndExecuteClassAndStatus(ScheduleJobLogRequest scheduleJobLogRequest) {
        QueryWrapper<ScheduleJobLog> queryWrapper=new QueryWrapper<>();
        queryWrapper.orderByDesc("create_time");
        if(StringUtils.isNotEmpty(scheduleJobLogRequest.getJobName())) {
            queryWrapper.like("jobName", scheduleJobLogRequest.getJobName());
        }
        if(StringUtils.isNotEmpty(scheduleJobLogRequest.getExecuteClass())) {
            queryWrapper.like("executeClass", scheduleJobLogRequest.getExecuteClass());
        }
        if(StringUtils.isNotEmpty(scheduleJobLogRequest.getStatus())) {
            queryWrapper.like("status", scheduleJobLogRequest.getStatus());
        }
        Page page=new Page(scheduleJobLogRequest.getCurrentPage(),scheduleJobLogRequest.getPageSize());
        return this.page(page,queryWrapper);
    }
}

七、测试

  1. 创建任务类
@Component("testTask")
@Slf4j
public class TestTask {
    public void run() {
        log.info("任务测试");
    }
}
  1. 控制器类
    作用:使用web服务器控制器类测试
@RestController
@RequestMapping(value = "/task/job", produces = {"application/json;charset=UTF-8"})
@Slf4j
@Validated
@Api(tags = {"计划任务"})
public class ScheduleJobController {
    @Autowired
    private IScheduleJobService scheduleJobService;

    @GetMapping(value = "list")
    @Log(logType = LogType.SELECT)
    @ApiOperation(value = "1、获取任务列表", notes = "获取任务列表", httpMethod = "GET")
    public Result list(JobRequest jobRequest) throws IOException {
        IPage<ScheduleJob> iPage = scheduleJobService.getListByJobName(jobRequest);
        return Result.success(iPage);
    }

    @PostMapping("add")
    @Log(logType = LogType.INSERT)
    @ApiOperation(value = "2、添加任务", notes = "添加任务", httpMethod = "POST")
    public Result add(@RequestBody ScheduleJob entity) {
        scheduleJobService.add(entity);
        return Result.success("添加成功");
    }

    @PutMapping("{id}")
    @Log(logType = LogType.UPDATE)
    @ApiOperation(value = "3、更新任务", notes = "更新任务", httpMethod = "PUT")
    public Result update(@PathVariable("id") String id,@RequestBody ScheduleJob entity) {
        entity.setId(id);
        scheduleJobService.updateById(entity);
        return Result.success("更新成功");
    }

    @DeleteMapping("{id}")
    @Log(logType = LogType.DELETE)
    @ApiOperation(value = "4、删除任务", notes = "删除任务", httpMethod = "DELETE")
    public Result delete(@PathVariable("id") String id) {
        scheduleJobService.deleteById(id);
        return Result.success("删除成功");
    }

    @DeleteMapping("batch")
    @Log(logType = LogType.DELETE)
    @ApiOperation(value = "5、批量删除任务", notes = "批量删除任务", httpMethod = "DELETE")
    public Result batchDelete(@RequestBody List<String> ids) {
        scheduleJobService.removeByIds(ids);
        return Result.success("删除成功");
    }

    @PostMapping(value = "jobStatus")
    @Log(logType = LogType.OTHER, title = "执行任务")
    @ApiOperation(value = "6、执行任务", notes = "执行任务", httpMethod = "POST")
    public Result changeJobStatus(@RequestBody JobStatusRequest jobStatusRequest) {
        String label = "停止";
        if (jobStatusRequest.getCmd().equals("start")) {
            label = "启动";
        } else {
            label = "停止";
        }
        try {
            // TODO 改变任务状态
            scheduleJobService.changeStatus(jobStatusRequest.getId(), jobStatusRequest.getCmd());
        } catch (Exception e) {
            e.printStackTrace();
            return Result.fail("任务" + label + "失败" + e.getMessage());
        }
        return Result.success("任务" + label + "成功");
    }

    @PostMapping(value = "{id}/jobCron")
    @Log(logType = LogType.OTHER, title = "任务更新")
    @ApiOperation(value = "7、任务更新", notes = "任务更新", httpMethod = "POST")
    public Result updateCron(@PathVariable("id") String id) {
        scheduleJobService.updateCron(id);
        return Result.success("任务更新成功");
    }

    @PostMapping(value = "/runAJobNow")
    @Log(logType = LogType.OTHER, title = "执行一次")
    @ApiOperation(value = "8、执行一次", notes = "执行一次", httpMethod = "POST")
    public Result runAJobNow(@RequestBody ScheduleJob scheduleJob) {
        scheduleJobService.runAJobNow(scheduleJob.getId());
        return Result.success("任务启动成功");
    }

    /**
     * 刷新任务
     *
     * @return
     */
    @PostMapping(value = "/refreshJob")
    @Log(logType = LogType.OTHER, title = "刷新任务")
    @ApiOperation(value = "9、刷新任务", notes = "刷新任务", httpMethod = "POST")
    public Result refreshJob() {
        scheduleJobService.refreshTask();
        return Result.success("刷新任务成功");
    }
}

添加任务

启动任务

测试结果

八、常见问题:

  1. referenced by the trigger does not exist.
    需要自己创建表,请执行数据库脚本。
  2. 定时任务 quartz中出现相同类型的对象无法转换问题(java.lang.ClassCastException)
    项目中应该是采用了热部署,devtools,因为类加载器的不同所以会导致类型转换失败;
    在依赖中去掉:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

依赖。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容