zoukankan      html  css  js  c++  java
  • SpringBoot2.x集成Quartz实现定时任务管理(持久化到数据库)

    1. Quartz简介

      Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目。
      Quartz是一个完全由Java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制。
      Quartz可以与J2EE与J2SE应用程序相结合也可以单独使用。
      Quartz允许程序开发人员根据时间的间隔来调度作业。
      Quartz实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
      Quartz官网:http://www.quartz-scheduler.org/

    2. Quartz核心概念

    • Job
        Job表示一个工作,要执行的具体内容。
    • JobDetail
        JobDetail表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail还包含了这个任务调度的方案和策略。
    • Trigger
        Trigger代表一个调度参数的配置,什么时候去调。
    • Scheduler
        Scheduler代表一个调度容器,一个调度容器中可以注册多个JobDetail和Trigger。当Trigger与JobDetail组合,就可以被Scheduler容器调度了。

    3. 初始化数据库

      Quartz采用持久化到数据库方式,需要创建官网提供的11张表。因此,可以在官网下载对应的版本,根据路径srcorgquartzimpljdbcjobstore找到对应数据库类型的脚本,例如Mysql为:tables_mysql.sql
      Mysql相关的表及系统需要的表脚本如下,请先创建数据库:quartzdemo,并初始化数据库表结构及数据。

    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for t_qrtz_blob_triggers
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_blob_triggers`;
    CREATE TABLE `t_qrtz_blob_triggers`  (
      `sched_name` varchar(120) NOT NULL,
      `trigger_name` varchar(190) NOT NULL,
      `trigger_group` varchar(190) NOT NULL,
      `blob_data` blob NULL,
      PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
      INDEX `sched_name`(`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
      CONSTRAINT `t_qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `t_qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT
    );
    
    -- ----------------------------
    -- Records of t_qrtz_blob_triggers
    -- ----------------------------
    
    -- ----------------------------
    -- Table structure for t_qrtz_calendars
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_calendars`;
    CREATE TABLE `t_qrtz_calendars`  (
      `sched_name` varchar(120) NOT NULL,
      `calendar_name` varchar(190) NOT NULL,
      `calendar` blob NOT NULL,
      PRIMARY KEY (`sched_name`, `calendar_name`) USING BTREE
    );
    
    -- ----------------------------
    -- Records of t_qrtz_calendars
    -- ----------------------------
    
    -- ----------------------------
    -- Table structure for t_qrtz_cron_triggers
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_cron_triggers`;
    CREATE TABLE `t_qrtz_cron_triggers`  (
      `sched_name` varchar(120) NOT NULL,
      `trigger_name` varchar(190) NOT NULL,
      `trigger_group` varchar(190) NOT NULL,
      `cron_expression` varchar(120) NOT NULL,
      `time_zone_id` varchar(80) NULL DEFAULT NULL,
      PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
      CONSTRAINT `t_qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `t_qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT
    );
    
    -- ----------------------------
    -- Records of t_qrtz_cron_triggers
    -- ----------------------------
    
    -- ----------------------------
    -- Table structure for t_qrtz_fired_triggers
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_fired_triggers`;
    CREATE TABLE `t_qrtz_fired_triggers`  (
      `sched_name` varchar(120) NOT NULL,
      `entry_id` varchar(95) NOT NULL,
      `trigger_name` varchar(190) NOT NULL,
      `trigger_group` varchar(190) NOT NULL,
      `instance_name` varchar(190) NOT NULL,
      `fired_time` bigint(0) NOT NULL,
      `sched_time` bigint(0) NOT NULL,
      `priority` int(0) NOT NULL,
      `state` varchar(16) NOT NULL,
      `job_name` varchar(190) NULL DEFAULT NULL,
      `job_group` varchar(190) NULL DEFAULT NULL,
      `is_nonconcurrent` varchar(1) NULL DEFAULT NULL,
      `requests_recovery` varchar(1) NULL DEFAULT NULL,
      PRIMARY KEY (`sched_name`, `entry_id`) USING BTREE,
      INDEX `idx_qrtz_ft_trig_inst_name`(`sched_name`, `instance_name`) USING BTREE,
      INDEX `idx_qrtz_ft_inst_job_req_rcvry`(`sched_name`, `instance_name`, `requests_recovery`) USING BTREE,
      INDEX `idx_qrtz_ft_j_g`(`sched_name`, `job_name`, `job_group`) USING BTREE,
      INDEX `idx_qrtz_ft_jg`(`sched_name`, `job_group`) USING BTREE,
      INDEX `idx_qrtz_ft_t_g`(`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
      INDEX `idx_qrtz_ft_tg`(`sched_name`, `trigger_group`) USING BTREE
    );
    
    -- ----------------------------
    -- Records of t_qrtz_fired_triggers
    -- ----------------------------
    
    -- ----------------------------
    -- Table structure for t_qrtz_job_details
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_job_details`;
    CREATE TABLE `t_qrtz_job_details`  (
      `sched_name` varchar(120) NOT NULL,
      `job_name` varchar(190) NOT NULL,
      `job_group` varchar(190) NOT NULL,
      `description` varchar(250) NULL DEFAULT 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`) USING BTREE,
      INDEX `idx_qrtz_j_req_recovery`(`sched_name`, `requests_recovery`) USING BTREE,
      INDEX `idx_qrtz_j_grp`(`sched_name`, `job_group`) USING BTREE
    );
    
    -- ----------------------------
    -- Records of t_qrtz_job_details
    -- ----------------------------
    
    -- ----------------------------
    -- Table structure for t_qrtz_locks
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_locks`;
    CREATE TABLE `t_qrtz_locks`  (
      `sched_name` varchar(120) NOT NULL,
      `lock_name` varchar(40) NOT NULL,
      PRIMARY KEY (`sched_name`, `lock_name`) USING BTREE
    );
    
    -- ----------------------------
    -- Records of t_qrtz_locks
    -- ----------------------------
    INSERT INTO `t_qrtz_locks` VALUES ('clusteredScheduler', 'STATE_ACCESS');
    INSERT INTO `t_qrtz_locks` VALUES ('clusteredScheduler', 'TRIGGER_ACCESS');
    
    -- ----------------------------
    -- Table structure for t_qrtz_paused_trigger_grps
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_paused_trigger_grps`;
    CREATE TABLE `t_qrtz_paused_trigger_grps`  (
      `sched_name` varchar(120) NOT NULL,
      `trigger_group` varchar(190) NOT NULL,
      PRIMARY KEY (`sched_name`, `trigger_group`) USING BTREE
    );
    
    -- ----------------------------
    -- Records of t_qrtz_paused_trigger_grps
    -- ----------------------------
    
    -- ----------------------------
    -- Table structure for t_qrtz_scheduler_state
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_scheduler_state`;
    CREATE TABLE `t_qrtz_scheduler_state`  (
      `sched_name` varchar(120) NOT NULL,
      `instance_name` varchar(190) NOT NULL,
      `last_checkin_time` bigint(0) NOT NULL,
      `checkin_interval` bigint(0) NOT NULL,
      PRIMARY KEY (`sched_name`, `instance_name`) USING BTREE
    );
    
    -- ----------------------------
    -- Records of t_qrtz_scheduler_state
    -- ----------------------------
    INSERT INTO `t_qrtz_scheduler_state` VALUES ('clusteredScheduler', 'C3Stones-PC', 1600918524362, 10000);
    
    -- ----------------------------
    -- Table structure for t_qrtz_simple_triggers
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_simple_triggers`;
    CREATE TABLE `t_qrtz_simple_triggers`  (
      `sched_name` varchar(120) NOT NULL,
      `trigger_name` varchar(190) NOT NULL,
      `trigger_group` varchar(190) NOT NULL,
      `repeat_count` bigint(0) NOT NULL,
      `repeat_interval` bigint(0) NOT NULL,
      `times_triggered` bigint(0) NOT NULL,
      PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
      CONSTRAINT `t_qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `t_qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT
    );
    
    -- ----------------------------
    -- Records of t_qrtz_simple_triggers
    -- ----------------------------
    
    -- ----------------------------
    -- Table structure for t_qrtz_simprop_triggers
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_simprop_triggers`;
    CREATE TABLE `t_qrtz_simprop_triggers`  (
      `sched_name` varchar(120) NOT NULL,
      `trigger_name` varchar(190) NOT NULL,
      `trigger_group` varchar(190) NOT NULL,
      `str_prop_1` varchar(512) NULL DEFAULT NULL,
      `str_prop_2` varchar(512) NULL DEFAULT NULL,
      `str_prop_3` varchar(512) NULL DEFAULT NULL,
      `int_prop_1` int(0) NULL DEFAULT NULL,
      `int_prop_2` int(0) NULL DEFAULT NULL,
      `long_prop_1` bigint(0) NULL DEFAULT NULL,
      `long_prop_2` bigint(0) NULL DEFAULT NULL,
      `dec_prop_1` decimal(13, 4) NULL DEFAULT NULL,
      `dec_prop_2` decimal(13, 4) NULL DEFAULT NULL,
      `bool_prop_1` varchar(1) NULL DEFAULT NULL,
      `bool_prop_2` varchar(1) NULL DEFAULT NULL,
      PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
      CONSTRAINT `t_qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `t_qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT
    );
    
    -- ----------------------------
    -- Records of t_qrtz_simprop_triggers
    -- ----------------------------
    
    -- ----------------------------
    -- Table structure for t_qrtz_triggers
    -- ----------------------------
    DROP TABLE IF EXISTS `t_qrtz_triggers`;
    CREATE TABLE `t_qrtz_triggers`  (
      `sched_name` varchar(120) NOT NULL,
      `trigger_name` varchar(190) NOT NULL,
      `trigger_group` varchar(190) NOT NULL,
      `job_name` varchar(190) NOT NULL,
      `job_group` varchar(190) NOT NULL,
      `description` varchar(250) NULL DEFAULT NULL,
      `next_fire_time` bigint(0) NULL DEFAULT NULL,
      `prev_fire_time` bigint(0) NULL DEFAULT NULL,
      `priority` int(0) NULL DEFAULT NULL,
      `trigger_state` varchar(16) NOT NULL,
      `trigger_type` varchar(8) NOT NULL,
      `start_time` bigint(0) NOT NULL,
      `end_time` bigint(0) NULL DEFAULT NULL,
      `calendar_name` varchar(190) NULL DEFAULT NULL,
      `misfire_instr` smallint(0) NULL DEFAULT NULL,
      `job_data` blob NULL,
      PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
      INDEX `idx_qrtz_t_j`(`sched_name`, `job_name`, `job_group`) USING BTREE,
      INDEX `idx_qrtz_t_jg`(`sched_name`, `job_group`) USING BTREE,
      INDEX `idx_qrtz_t_c`(`sched_name`, `calendar_name`) USING BTREE,
      INDEX `idx_qrtz_t_g`(`sched_name`, `trigger_group`) USING BTREE,
      INDEX `idx_qrtz_t_state`(`sched_name`, `trigger_state`) USING BTREE,
      INDEX `idx_qrtz_t_n_state`(`sched_name`, `trigger_name`, `trigger_group`, `trigger_state`) USING BTREE,
      INDEX `idx_qrtz_t_n_g_state`(`sched_name`, `trigger_group`, `trigger_state`) USING BTREE,
      INDEX `idx_qrtz_t_next_fire_time`(`sched_name`, `next_fire_time`) USING BTREE,
      INDEX `idx_qrtz_t_nft_st`(`sched_name`, `trigger_state`, `next_fire_time`) USING BTREE,
      INDEX `idx_qrtz_t_nft_misfire`(`sched_name`, `misfire_instr`, `next_fire_time`) USING BTREE,
      INDEX `idx_qrtz_t_nft_st_misfire`(`sched_name`, `misfire_instr`, `next_fire_time`, `trigger_state`) USING BTREE,
      INDEX `idx_qrtz_t_nft_st_misfire_grp`(`sched_name`, `misfire_instr`, `next_fire_time`, `trigger_group`, `trigger_state`) USING BTREE,
      CONSTRAINT `t_qrtz_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `job_name`, `job_group`) REFERENCES `t_qrtz_job_details` (`sched_name`, `job_name`, `job_group`) ON DELETE RESTRICT ON UPDATE RESTRICT
    );
    
    -- ----------------------------
    -- Records of t_qrtz_triggers
    -- ----------------------------
    
    -- ----------------------------
    -- Table structure for t_sys_job
    -- ----------------------------
    DROP TABLE IF EXISTS `t_sys_job`;
    CREATE TABLE `t_sys_job`  (
      `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'ID',
      `job_name` varchar(100) NULL DEFAULT NULL COMMENT '任务名称',
      `cron_expression` varchar(255) NULL DEFAULT NULL COMMENT 'cron表达式',
      `bean_class` varchar(255) NULL DEFAULT NULL COMMENT '任务执行类(包名+类名)',
      `status` varchar(10) NULL DEFAULT NULL COMMENT '任务状态',
      `job_group` varchar(50) NULL DEFAULT NULL COMMENT '任务分组',
      `job_data_map` varchar(1000) NULL DEFAULT NULL COMMENT '参数',
      `create_user_id` int(0) NULL DEFAULT NULL COMMENT '创建人ID',
      `create_date` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
      `update_user_id` int(0) NULL DEFAULT NULL COMMENT '更新人ID',
      `update_date` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
      `remarks` varchar(255) NULL DEFAULT NULL COMMENT '描述',
      PRIMARY KEY (`id`) USING BTREE
    ) AUTO_INCREMENT = 3 COMMENT = '定时任务';
    
    -- ----------------------------
    -- Records of t_sys_job
    -- ----------------------------
    INSERT INTO `t_sys_job` VALUES (1, 'TestJob', '0/5 * * * * ?', 'com.c3stones.job.biz.TestJob', 'NONE', 'default', '{"username":"zhangsan", "age":18}', 1, '2020-09-25 15:22:32', 1, '2020-09-25 15:22:32', '测试定时任务1');
    INSERT INTO `t_sys_job` VALUES (2, 'Test2Job', '0 * * * * ?', 'com.c3stones.job.biz.Test2Job', 'NONE', 'default', '{"username":"lisi", "age":20}', 1, '2020-09-25 15:22:54', 1, '2020-09-25 15:22:54', '测试定时任务2');
    
    -- ----------------------------
    -- Table structure for t_sys_user
    -- ----------------------------
    DROP TABLE IF EXISTS `t_sys_user`;
    CREATE TABLE `t_sys_user`  (
      `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'ID',
      `username` varchar(50) NULL DEFAULT NULL COMMENT '用户名称',
      `nickname` varchar(100) NULL DEFAULT NULL COMMENT '用户昵称',
      `password` varchar(255) NULL DEFAULT NULL COMMENT '用户密码',
      PRIMARY KEY (`id`) USING BTREE
    )AUTO_INCREMENT = 3 COMMENT = '系统用户';
    
    -- ----------------------------
    -- Records of t_sys_user
    -- ----------------------------
    INSERT INTO `t_sys_user` VALUES (1, 'user', 'C3Stones', '$2a$10$WXEPqxjMwY6d6A0hkeBtGu.acRRWUOJmX7oLUuYMHF1VWWUm4EqOC');
    INSERT INTO `t_sys_user` VALUES (2, 'system', '管理员', '$2a$10$dmO7Uk9/lo1D5d1SvCGgWuB050a0E2uuBDNITEpWFiIfCg.3UbA8y');
    
    SET FOREIGN_KEY_CHECKS = 1;
    

    4. 示例代码

      本文在之前博客SpringBoot + Layui +Mybatis-plus实现简单后台管理系统(内置安全过滤器)的示例项目spring-boot-layui-demo基础上增加了任务调度菜单,因此请先下载相关工程。

    • 修改pom.xml
        引入依赖spring-boot-starter-quartz即可实现SpringBoot与Quartz集成。
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<groupId>com.c3stones</groupId>
    	<artifactId>spring-boot-quartz-demo</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>spring-boot-quartz-demo</name>
    	<description>Spring Boot Quartz Demo</description>
    
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.2.8.RELEASE</version>
    		<relativePath />
    	</parent>
    
    	<dependencies>
    		<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-quartz</artifactId>
            </dependency>
    		<dependency>
    			<groupId>com.baomidou</groupId>
    			<artifactId>mybatis-plus-boot-starter</artifactId>
    			<version>3.3.1</version>
    		</dependency>
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<scope>runtime</scope>
    		</dependency>
    		<dependency>
    			<groupId>cn.hutool</groupId>
    			<artifactId>hutool-all</artifactId>
    			<version>5.4.1</version>
    		</dependency>
    		<dependency>
    			<groupId>org.projectlombok</groupId>
    			<artifactId>lombok</artifactId>
    			<optional>true</optional>
    		</dependency>
    		<dependency>
    			<groupId>org.jsoup</groupId>
    			<artifactId>jsoup</artifactId>
    			<version>1.11.3</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-configuration-processor</artifactId>
    			<optional>true</optional>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-thymeleaf</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    	</dependencies>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    </project>
    
    • 配置文件application.yml添加quartz相关配置
    server:
      port: 8080
      servlet:
        session:
          timeout: 1800s
      
    spring:
      jackson:
        time-zone: GMT+8
        date-format: yyyy-MM-dd HH:mm:ss
      datasource:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/quartzdemo?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
          username: root
          password: 123456
      thymeleaf:
        prefix: classpath:/view/
        suffix: .html
        encoding: UTF-8
        servlet:
          content-type: text/html
        # 生产环境设置true
        cache: false
      quartz:
        properties:
          org:
            quartz:
              scheduler:
                instanceName: clusteredScheduler
                instanceId: AUTO
              jobStore:
                class: org.quartz.impl.jdbcjobstore.JobStoreTX
                driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
                tablePrefix: t_qrtz_
                isClustered: false
                clusterCheckinInterval: 10000
                useProperties: false
              threadPool:
                class: org.quartz.simpl.SimpleThreadPool
                threadCount: 10
                threadPriority: 5
                threadsInheritContextClassLoaderOfInitializingThread: true
        job-store-type: jdbc
    
    # Mybatis-plus配置
    mybatis-plus:
       mapper-locations: classpath:mapper/*.xml
       global-config:
          db-config:
             id-type: AUTO
    #   configuration:
    #      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    # 日志配置
    logging:
      config: classpath:logback-spring.xml
           
    # 信息安全
    security:
      web:
        excludes:
          - /login
          - /logout
          - /images/**
          - /jquery/**
          - /layui/**
      xss:
        enable: true
        excludes:
          - /login
          - /logout
          - /images/*
          - /jquery/*
          - /layui/*
      sql:
        enable: true
        excludes:
          - /images/*
          - /jquery/*
          - /layui/*
      csrf:
        enable: true
        excludes:
    
    • 创建调度器配置类
    import javax.sql.DataSource;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.quartz.SchedulerFactoryBean;
    
    /**
     * 调度器配置类
     * 
     * @author CL
     *
     */
    @Configuration
    public class SchedulerConfig implements SchedulerFactoryBeanCustomizer {
    
    	@Autowired
    	private DataSource dataSource;
    
    	@Override
    	public void customize(SchedulerFactoryBean schedulerFactoryBean) {
    		// 启动延时
    		schedulerFactoryBean.setStartupDelay(10);
    		// 自动启动任务调度
    		schedulerFactoryBean.setAutoStartup(true);
    		// 是否覆盖现有作业定义
    		schedulerFactoryBean.setOverwriteExistingJobs(true);
    		// 配置数据源
    		schedulerFactoryBean.setDataSource(dataSource);
    	}
    
    }
    
    • 创建全局用户工具类
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import com.c3stones.sys.entity.User;
    
    /**
     * 用户工具类
     * 
     * @author CL
     *
     */
    public class UserUtils {
    
    	/**
    	 * 获取当前用户
    	 * 
    	 * @return
    	 */
    	public static User get() {
    		return (User) getSession().getAttribute("user");
    	}
    
    	/**
    	 * 获取session
    	 * 
    	 * @return
    	 */
    	public static HttpSession getSession() {
    		return getRequest().getSession();
    	}
    
    	/**
    	 * 获取request
    	 * 
    	 * @return
    	 */
    	public static HttpServletRequest getRequest() {
    		ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
    				.getRequestAttributes();
    		return requestAttributes.getRequest();
    	}
    
    }
    
    • 创建实体
    import java.io.Serializable;
    import java.time.LocalDateTime;
    
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.baomidou.mybatisplus.extension.activerecord.Model;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.NoArgsConstructor;
    
    /**
     * 定时任务
     * 
     * @author CL
     *
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @TableName(value = "t_sys_job")
    @EqualsAndHashCode(callSuper = false)
    public class Job extends Model<Job> implements Serializable {
    
    	private static final long serialVersionUID = 1L;
    
    	/**
    	 * ID
    	 */
    	@TableId(type = IdType.AUTO)
    	private Integer id;
    
    	/**
    	 * 任务名称
    	 */
    	private String jobName;
    
    	/**
    	 * cron表达式
    	 */
    	private String cronExpression;
    
    	/**
    	 * 任务执行类(包名+类名)
    	 */
    	private String beanClass;
    
    	/**
    	 * 任务状态(0-停止,1-运行)
    	 */
    	private String status;
    
    	/**
    	 * 任务分组
    	 */
    	private String jobGroup;
    
    	/**
    	 * 参数
    	 */
    	private String jobDataMap;
    
    	/**
    	 * 下一次执行时间
    	 */
    	@TableField(exist = false)
    	private LocalDateTime nextfireDate;
    
    	/**
    	 * 创建人ID
    	 */
    	private Integer createUserId;
    
    	/**
    	 * 创建时间
    	 */
    	private LocalDateTime createDate;
    
    	/**
    	 * 更新人ID
    	 */
    	private Integer updateUserId;
    
    	/**
    	 * 更新时间
    	 */
    	private LocalDateTime updateDate;
    
    	/**
    	 * 描述
    	 */
    	private String remarks;
    
    }
    
    • 创建定时任务处理器
    import java.text.ParseException;
    import java.time.Instant;
    import java.time.LocalDateTime;
    import java.time.ZoneId;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    import org.quartz.CronExpression;
    import org.quartz.CronScheduleBuilder;
    import org.quartz.CronTrigger;
    import org.quartz.JobBuilder;
    import org.quartz.JobDataMap;
    import org.quartz.JobDetail;
    import org.quartz.JobKey;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerException;
    import org.quartz.Trigger;
    import org.quartz.Trigger.TriggerState;
    import org.quartz.TriggerBuilder;
    import org.quartz.TriggerKey;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import com.c3stones.job.entity.Job;
    
    import cn.hutool.core.util.StrUtil;
    import cn.hutool.json.JSONUtil;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * 定时任务管理器
     * 
     * @author CL
     *
     */
    @Slf4j
    @Component
    public class QuartzHandler {
    
    	@Autowired
    	private Scheduler scheduler;
    
    	/**
    	 * 新增定义任务
    	 * 
    	 * @param job   定义任务
    	 * @param clazz 任务执行类
    	 * @return
    	 */
    	@SuppressWarnings({ "rawtypes", "unchecked" })
    	public boolean start(Job job, Class clazz) {
    		boolean result = true;
    		try {
    			String jobName = job.getJobName();
    			String jobGroup = job.getJobGroup();
    			TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
    			CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
    			if (null == cronTrigger) {
    				// 处理参数
    				Map<String, String> map = new HashMap<>(5);
    				String jobDataMap = job.getJobDataMap();
    				if (StrUtil.isNotBlank(jobDataMap)) {
    					if (JSONUtil.isJson(jobDataMap)) {
    						Map parseMap = JSONUtil.toBean(jobDataMap, Map.class);
    						parseMap.forEach((k, v) -> {
    							map.put(String.valueOf(k), String.valueOf(v));
    						});
    					}
    				}
    				// 启动定时任务
    				JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(jobName, jobGroup)
    						.setJobData(new JobDataMap(map)).build();
    				cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup)
    						.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression())).build();
    				scheduler.scheduleJob(jobDetail, cronTrigger);
    				if (!scheduler.isShutdown()) {
    					scheduler.start();
    				}
    			} else {
    				// 重启定时任务
    				cronTrigger = cronTrigger.getTriggerBuilder().withIdentity(triggerKey)
    						.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression())).build();
    				scheduler.rescheduleJob(triggerKey, cronTrigger);
    			}
    		} catch (SchedulerException e) {
    			log.info("新增定时任务异常:{}", e.getMessage());
    			result = false;
    		}
    		return result;
    	}
    
    	/**
    	 * 暂停定时任务
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 */
    	public boolean pasue(Job job) {
    		boolean result = true;
    		try {
    			String jobName = job.getJobName();
    			String jobGroup = job.getJobGroup();
    			TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
    			Trigger trigger = scheduler.getTrigger(triggerKey);
    			JobKey jobKey = trigger.getJobKey();
    			scheduler.pauseJob(jobKey);
    		} catch (SchedulerException e) {
    			log.info("暂停定时任务异常:{}", e.getMessage());
    			result = false;
    		}
    		return result;
    	}
    
    	/**
    	 * 重启定时任务
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 */
    	public boolean restart(Job job) {
    		boolean result = true;
    		try {
    			String jobName = job.getJobName();
    			String jobGroup = job.getJobGroup();
    			TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
    			Trigger trigger = scheduler.getTrigger(triggerKey);
    			scheduler.rescheduleJob(triggerKey, trigger);
    		} catch (SchedulerException e) {
    			log.info("重启定时任务异常:{}", e.getMessage());
    			result = false;
    		}
    		return result;
    	}
    
    	/**
    	 * 立即执行一次
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 */
    	public boolean trigger(Job job) {
    		boolean result = true;
    		try {
    			String jobName = job.getJobName();
    			String jobGroup = job.getJobGroup();
    			TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
    			Trigger trigger = scheduler.getTrigger(triggerKey);
    			JobKey jobKey = trigger.getJobKey();
    			scheduler.triggerJob(jobKey);
    		} catch (SchedulerException e) {
    			log.info("立即执行一次异常:{}", e.getMessage());
    			result = false;
    		}
    		return result;
    	}
    
    	/**
    	 * 修改触发时间表达式
    	 * 
    	 * @param job               定时任务
    	 * @param newCronExpression 新的cron表达式
    	 * @return
    	 */
    	public boolean updateCronExpression(Job job, String newCronExpression) {
    		boolean result = true;
    		try {
    			String jobName = job.getJobName();
    			String jobGroup = job.getJobGroup();
    			TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
    			CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
    			job.setCronExpression(newCronExpression);
    			CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
    			cronTrigger = cronTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder)
    					.build();
    			scheduler.rescheduleJob(triggerKey, cronTrigger);
    		} catch (SchedulerException e) {
    			log.info("修改触发时间表达式异常:{}", e.getMessage());
    			result = false;
    		}
    		return result;
    	}
    
    	/**
    	 * 删除定时任务
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 */
    	public boolean delete(Job job) {
    		boolean result = true;
    		try {
    			String jobName = job.getJobName();
    			String jobGroup = job.getJobGroup();
    			TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
    			Trigger trigger = scheduler.getTrigger(triggerKey);
    			JobKey jobKey = trigger.getJobKey();
    			// 停止触发器
    			scheduler.pauseTrigger(triggerKey);
    			// 移除触发器
    			scheduler.unscheduleJob(triggerKey);
    			// 删除任务
    			scheduler.deleteJob(jobKey);
    		} catch (SchedulerException e) {
    			log.info("删除定时任务异常:{}", e.getMessage());
    			result = false;
    		}
    		return result;
    	}
    
    	/***
    	 * 判断是否存在定时任务
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 */
    	public boolean has(Job job) {
    		boolean result = true;
    		try {
    			if (!scheduler.isShutdown()) {
    				String jobName = job.getJobName();
    				String jobGroup = job.getJobGroup();
    				TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
    				Trigger trigger = scheduler.getTrigger(triggerKey);
    				result = (trigger != null) ? true : false;
    			} else {
    				result = false;
    			}
    		} catch (SchedulerException e) {
    			log.info("判断是否存在定时任务异常:{}", e.getMessage());
    			result = false;
    		}
    		return result;
    	}
    
    	/**
    	 * 获得定时任务状态
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 */
    	public String getStatus(Job job) {
    		String status = StrUtil.EMPTY;
    		try {
    			String jobName = job.getJobName();
    			String jobGroup = job.getJobGroup();
    			TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
    			TriggerState triggerState = scheduler.getTriggerState(triggerKey);
    			status = triggerState.toString();
    		} catch (Exception e) {
    			log.info("获得定时任务状态异常:{}", e.getMessage());
    		}
    		return StrUtil.isNotEmpty(status) ? status : TriggerState.NONE.toString();
    	}
    
    	/**
    	 * 启动调度器
    	 * 
    	 * @return
    	 */
    	public boolean startScheduler() {
    		boolean result = true;
    		try {
    			scheduler.start();
    		} catch (SchedulerException e) {
    			log.info("启动调度器异常:{}", e.getMessage());
    			result = false;
    		}
    		return result;
    	}
    
    	/**
    	 * 关闭调度器
    	 * 
    	 * @return
    	 */
    	public boolean standbyScheduler() {
    		boolean result = true;
    		try {
    			if (!scheduler.isShutdown()) {
    				scheduler.standby();
    			}
    		} catch (SchedulerException e) {
    			log.info("关闭调度器异常:{}", e.getMessage());
    			result = false;
    		}
    		return result;
    	}
    
    	/**
    	 * 判断调度器是否为开启状态
    	 * 
    	 * @return
    	 */
    	public boolean isStarted() {
    		boolean result = true;
    		try {
    			result = scheduler.isStarted();
    		} catch (SchedulerException e) {
    			log.info("判断调度器是否为开启状态异常:{}", e.getMessage());
    		}
    		return result;
    	}
    
    	/**
    	 * 判断调度器是否为关闭状态
    	 * 
    	 * @return
    	 */
    	public boolean isShutdown() {
    		boolean result = true;
    		try {
    			result = scheduler.isShutdown();
    		} catch (SchedulerException e) {
    			log.info("判断调度器是否为关闭状态异常:{}", e.getMessage());
    		}
    		return result;
    	}
    
    	/**
    	 * 判断调度器是否为待机状态
    	 * 
    	 * @return
    	 */
    	public boolean isInStandbyMode() {
    		boolean result = true;
    		try {
    			result = scheduler.isInStandbyMode();
    		} catch (SchedulerException e) {
    			log.info("判断调度器是否为待机状态异常:{}", e.getMessage());
    		}
    		return result;
    	}
    
    	/**
    	 * 获得下一次执行时间
    	 * 
    	 * @param cronExpression cron表达式
    	 * @return
    	 */
    	public LocalDateTime nextfireDate(String cronExpression) {
    		LocalDateTime localDateTime = null;
    		try {
    			if (StrUtil.isNotEmpty(cronExpression)) {
    				CronExpression ce = new CronExpression(cronExpression);
    				Date nextInvalidTimeAfter = ce.getNextInvalidTimeAfter(new Date());
    				localDateTime = Instant.ofEpochMilli(nextInvalidTimeAfter.getTime()).atZone(ZoneId.systemDefault())
    						.toLocalDateTime();
    			}
    		} catch (ParseException e) {
    			log.info("获得下一次执行时间异常:{}", e.getMessage());
    		}
    		return localDateTime;
    	}
    
    }
    
    • 创建Mapper
    import org.apache.ibatis.annotations.Mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.c3stones.job.entity.Job;
    
    /**
     * 定时任务Mapper
     * 
     * @author CL
     *
     */
    @Mapper
    public interface JobMapper extends BaseMapper<Job> {
    
    }
    
    • 创建Service
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.c3stones.job.entity.Job;
    
    /**
     * 定时任务Service
     * 
     * @author CL
     *
     */
    public interface JobService extends IService<Job> {
    
    	/**
    	 * 查询列表数据
    	 * 
    	 * @param job     系统用户
    	 * @param current 当前页
    	 * @param size    每页显示条数
    	 * @return
    	 */
    	public Page<Job> listData(Job job, long current, long size);
    
    }
    
    • 创建Service实现类
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.c3stones.job.config.QuartzHandler;
    import com.c3stones.job.entity.Job;
    import com.c3stones.job.mapper.JobMapper;
    import com.c3stones.job.service.JobService;
    
    import cn.hutool.core.util.StrUtil;
    
    /**
     * 定时任务Service实现
     * 
     * @author CL
     *
     */
    @Service
    public class JobServiceImpl extends ServiceImpl<JobMapper, Job> implements JobService {
    
    	@Autowired
    	private QuartzHandler quartzHandler;
    
    	/**
    	 * 查询列表数据
    	 * 
    	 * @param job     系统用户
    	 * @param current 当前页
    	 * @param size    每页显示条数
    	 * @return
    	 */
    	@Override
    	public Page<Job> listData(Job job, long current, long size) {
    		QueryWrapper<Job> queryWrapper = new QueryWrapper<>();
    		if (StrUtil.isNotBlank(job.getJobName())) {
    			queryWrapper.like("job_name", job.getJobName());
    		}
    		Page<Job> page = baseMapper.selectPage(new Page<>(current, size), queryWrapper);
    		List<Job> records = page.getRecords();
    
    		// 处理定时任务数据
    		for (int i = 0; i < records.size(); i++) {
    			Job j = records.get(i);
    			// 获取下一次执行时间
    			j.setNextfireDate(quartzHandler.nextfireDate(j.getCronExpression()));
    
    			// 更新状态
    			String status = quartzHandler.getStatus(j);
    			if (!(status).equals(j.getStatus())) {
    				j.setStatus(status);
    				super.updateById(j);
    			}
    
    			records.set(i, j);
    		}
    		page.setRecords(records);
    		return page;
    	}
    }
    
    • 创建Controller
    import java.time.LocalDateTime;
    
    import org.quartz.Trigger.TriggerState;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.util.Assert;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.c3stones.common.vo.Response;
    import com.c3stones.job.config.QuartzHandler;
    import com.c3stones.job.entity.Job;
    import com.c3stones.job.service.JobService;
    import com.c3stones.sys.entity.User;
    import com.c3stones.sys.utils.UserUtils;
    
    /**
     * 定时任务Controller
     * 
     * @author CL
     *
     */
    @Controller
    @RequestMapping(value = "job")
    public class JobController {
    
    	@Autowired
    	private QuartzHandler quartzHandler;
    
    	@Autowired
    	private JobService jobService;
    
    	/**
    	 * 查询列表
    	 * 
    	 * @return
    	 */
    	@RequestMapping(value = "list")
    	public String list() {
    		return "pages/job/jobList";
    	}
    
    	/**
    	 * 查询列表数据
    	 * 
    	 * @param user    系统用户
    	 * @param current 当前页
    	 * @param size    每页显示条数
    	 * @return
    	 */
    	@RequestMapping(value = "listData")
    	@ResponseBody
    	public Response<Page<Job>> listData(Job job, @RequestParam(name = "page") long current,
    			@RequestParam(name = "limit") long size) {
    		Page<Job> page = jobService.listData(job, current, size);
    		return Response.success(page);
    	}
    
    	/**
    	 * 更新
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 */
    	@RequestMapping(value = "update")
    	@ResponseBody
    	public Response<Boolean> update(Job job) {
    		Assert.notNull(job.getId(), "ID不能为空");
    		User user = UserUtils.get();
    		if (user != null) {
    			job.setUpdateUserId(user.getId());
    		}
    		LocalDateTime now = LocalDateTime.now();
    		job.setUpdateDate(now);
    		boolean result = jobService.updateById(job);
    		Job queryJob = jobService.getById(job.getId());
    		String status = quartzHandler.getStatus(queryJob);
    		if (!(TriggerState.NONE.toString()).equals(status)) {
    			result = quartzHandler.updateCronExpression(queryJob, queryJob.getCronExpression());
    		}
    		return Response.success("更新" + (result ? "成功" : "失败"), result);
    	}
    
    	/**
    	 * 删除
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 */
    	@RequestMapping(value = "delete")
    	@ResponseBody
    	public Response<Boolean> delete(Job job) {
    		Assert.notNull(job.getId(), "ID不能为空");
    		Job queryJob = jobService.getById(job.getId());
    		boolean result = true;
    		if (!(TriggerState.NONE.toString()).equals(queryJob.getStatus())) {
    			result = quartzHandler.delete(queryJob);
    		}
    		if (result) {
    			result = jobService.removeById(job.getId());
    		}
    		return Response.success("删除" + (result ? "成功" : "失败"), result);
    	}
    
    	/**
    	 * 启动
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 * @throws ClassNotFoundException
    	 */
    	@RequestMapping(value = "start")
    	@ResponseBody
    	public Response<Boolean> start(Job job) throws ClassNotFoundException {
    		Assert.notNull(job.getId(), "ID不能为空");
    		Job queryJob = jobService.getById(job.getId());
    		Assert.notNull(queryJob, "定时任务不存在");
    		Class<?> clazz = Class.forName(queryJob.getBeanClass());
    		Assert.notNull(clazz, "未找到任务执行类");
    		boolean result = quartzHandler.start(queryJob, clazz);
    		return Response.success("启动" + (result ? "成功" : "失败"), result);
    	}
    
    	/**
    	 * 暂停
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 */
    	@RequestMapping(value = "pasue")
    	@ResponseBody
    	public Response<Boolean> pasue(Job job) {
    		Assert.notNull(job.getId(), "ID不能为空");
    		Job queryJob = jobService.getById(job.getId());
    		Assert.notNull(queryJob, "定时任务不存在");
    
    		String status = quartzHandler.getStatus(queryJob);
    		if (!((TriggerState.NORMAL.toString()).equals(status) || (TriggerState.PAUSED.toString()).equals(status)
    				|| (TriggerState.BLOCKED.toString()).equals(status))) {
    			return Response.success("当前状态不可暂停", false);
    		}
    		if ((TriggerState.PAUSED.toString()).equals(status)) {
    			return Response.success("已暂停", false);
    		}
    
    		boolean result = quartzHandler.pasue(queryJob);
    		return Response.success("暂停" + (result ? "成功" : "失败"), result);
    	}
    
    	/**
    	 * 立即执行
    	 * 
    	 * @param job 定时任务
    	 * @return
    	 */
    	@RequestMapping(value = "trigger")
    	@ResponseBody
    	public Response<Boolean> trigger(Job job) {
    		Assert.notNull(job.getId(), "ID不能为空");
    		Job queryJob = jobService.getById(job.getId());
    		Assert.notNull(queryJob, "定时任务不存在");
    
    		String status = quartzHandler.getStatus(queryJob);
    		if (!((TriggerState.NORMAL.toString()).equals(status) || (TriggerState.PAUSED.toString()).equals(status)
    				|| (TriggerState.COMPLETE.toString()).equals(status))) {
    			return Response.success("当前状态不可立即执行", false);
    		}
    
    		boolean result = quartzHandler.trigger(queryJob);
    		return Response.success("立即执行" + (result ? "成功" : "失败"), result);
    	}
    
    	/**
    	 * 判断定时器是否为待机模式
    	 */
    	@RequestMapping(value = "isInStandbyMode")
    	@ResponseBody
    	public Response<Boolean> isInStandbyMode() {
    		boolean result = quartzHandler.isInStandbyMode();
    		return Response.success(result);
    	}
    
    	/**
    	 * 启动定时器
    	 * 
    	 * @return
    	 */
    	@RequestMapping(value = "startScheduler")
    	@ResponseBody
    	public Response<Boolean> startScheduler() {
    		boolean result = quartzHandler.startScheduler();
    		return Response.success("启动定时器" + (result ? "成功" : "失败"), result);
    	}
    
    	/**
    	 * 待机定时器
    	 * 
    	 * @return
    	 */
    	@RequestMapping(value = "standbyScheduler")
    	@ResponseBody
    	public Response<Boolean> standbyScheduler() {
    		boolean result = quartzHandler.standbyScheduler();
    		return Response.success("关闭定时器" + (result ? "成功" : "失败"), result);
    	}
    
    	/**
    	 * 新增
    	 * 
    	 * @return
    	 */
    	@RequestMapping(value = "add")
    	public String add() {
    		return "pages/job/jobAdd";
    	}
    
    	/**
    	 * 保存
    	 * 
    	 * @return
    	 */
    	@RequestMapping(value = "save")
    	@ResponseBody
    	public Response<Boolean> save(Job job) {
    		User user = UserUtils.get();
    		if (user != null) {
    			job.setCreateUserId(user.getId());
    			job.setUpdateUserId(user.getId());
    		}
    		LocalDateTime now = LocalDateTime.now();
    		job.setCreateDate(now);
    		job.setUpdateDate(now);
    		boolean result = jobService.save(job);
    		return Response.success(result);
    	}
    
    }
    
    • 主页index.html配置菜单
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
        <title>C3Stones</title>
        <link th:href="@{/images/favicon.ico}" rel="icon">
    	<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
    	<link th:href="@{/layui/css/admin.css}" rel="stylesheet" />
    	<script th:src="@{/layui/layui.js}"></script>
    	<script th:src="@{/layui/js/index.js}" data-main="home"></script>
    </head>
    <body class="layui-layout-body">
        <div class="layui-layout layui-layout-admin">
            <div class="layui-header custom-header">
                <ul class="layui-nav layui-layout-left">
                    <li class="layui-nav-item slide-sidebar" lay-unselect>
                        <a href="javascript:;" class="icon-font"><i class="ai ai-menufold"></i></a>
                    </li>
                </ul>
                <ul class="layui-nav layui-layout-right">
                    <li class="layui-nav-item">
                        <a href="javascript:;">[[${user?.nickname}]]</a>
                        <dl class="layui-nav-child">
                            <dd><a th:href="@{/logout}">退出</a></dd>
                        </dl>
                    </li>
                </ul>
            </div>
    
            <div class="layui-side custom-admin">
                <div class="layui-side-scroll">
                    <div class="custom-logo">
                        <img alt="" th:src="@{/images/logo.jpg}">
                        <h1>C3Stones</h1>
                    </div>
                    <ul id="Nav" class="layui-nav layui-nav-tree">
                        <li class="layui-nav-item">
                            <a href="javascript:;">
                                <i class="layui-icon">&#xe68e;</i>
                                <em>主页</em>
                            </a>
                            <dl class="layui-nav-child">
                                <dd><a th:href="@{/view}">控制台</a></dd>
                            </dl>
                        </li>
                        <li class="layui-nav-item">
                            <a href="javascript:;">
                                <i class="layui-icon">&#xe716;</i>
                                <em>系统管理</em>
                            </a>
                            <dl class="layui-nav-child">
                                <dd><a th:href="@{/user/list}">用户管理</a></dd>
                            </dl>
                            <dl class="layui-nav-child">
                                <dd><a th:href="@{/job/list}">任务调度</a></dd>
                            </dl>
                        </li>
                    </ul>
    
                </div>
            </div>
    
            <div class="layui-body">
                 <div class="layui-tab app-container" lay-allowClose="true" lay-filter="tabs">
                    <ul id="appTabs" class="layui-tab-title custom-tab"></ul>
                    <div id="appTabPage" class="layui-tab-content"></div>
                </div>
            </div>
    
            <div class="layui-footer">
                <p>© 2020 - C3Stones Blog : <a href="https://www.cnblogs.com/cao-lei/" target="_blank">https://www.cnblogs.com/cao-lei/</a></p>
            </div>
            <div class="mobile-mask"></div>
        </div>
    </body>
    </html>
    
    • 新增定时任务列表页面jobList.html
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
        <link th:href="@{/layui/css/view.css}" rel="stylesheet" />
        <script th:src="@{/layui/layui.all.js}"></script>
        <script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
        <script th:src="@{/layui/js/view.js}"></script>
        <title></title>
    </head>
    <body class="layui-view-body">
    	<div class="layui-content">
    	    <div class="layui-row">
    			<div class="layui-card">
                    <div class="layui-card-header">
                    	<i class="layui-icon mr5">&#xe66f;</i>任务调度(定时器状态:<label id="schedulerStatus"></label>)
                    	<button class="layui-btn layui-btn-xs layui-hide" data-type="startScheduler">启动定时器</button>
    					<button class="layui-btn layui-btn-xs layui-btn-danger layui-hide" data-type="standbyScheduler">定时器待机</button>
                    	<button class="layui-btn layui-btn-xs layui-btn-normal pull-right mt10" data-type="add"><i class="layui-icon mr5">&#xe654;</i>新增</button>	
                    </div>
                    <div class="layui-card-body">
                    	<div class="searchTable">
    					 任务名称:
    					 <div class="layui-inline mr5">
    					 	<input class="layui-input" name="jobName" autocomplete="off">
    					 </div>
    					 <button class="layui-btn" data-type="reload">查询</button>
    					 <button class="layui-btn layui-btn-primary" data-type="reset">重置</button>
    					</div>
                    	<table class="layui-hide" id="jobDataTable" lay-filter="config"></table>
    					<script type="text/html" id="operation">
    						<a class="layui-btn layui-btn-xs " lay-event="start">启动</a>
    						<a class="layui-btn layui-btn-xs layui-btn-warm" lay-event="pasue">暂停</a>
    						<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="trigger">立即执行</a>
    						<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del">删除</a>
    					</script>
                    </div>
                </div>
            </div>
        </div>
    </body>
    <script>
    var element = layui.element;
    var table = layui.table;
    var layer = layui.layer;
    table.render({
    	id: 'jobTable'
    	,elem: '#jobDataTable'
        ,url: '[[@{/job/listData}]]'
        ,cellMinWidth: 100
       	,page: {
      		layout: ['prev', 'page', 'next', 'count', 'skip', 'limit']
      	    ,groups: 5
      	    ,first: false
      	    ,last: false
    	}
        ,cols: [
        	[
    	      {field:'id', title: 'ID',  50}
    	      ,{field:'jobName', title: '任务名称',  120}
    	      ,{field:'cronExpression', title: '周期表达式', edit: 'text',  100}
    	      ,{field:'beanClass', title: '任务执行类',  250}
    	      ,{field:'jobDataMap', title: '参数',  200}
    	      ,{field:'status', title: '状态', templet: '#statusTemp',  80, align: 'center'}
    	      ,{field:'jobGroup', title: '分组', templet: '#groupTemp',  60, align: 'center'}
    	      ,{field:'nextfireDate', title: '下一次执行时间',  160, align: 'center'}
    	      ,{field:'remarks', title: '描述',  200}
    	      ,{fixed: 'right', title:'操作', align: 'center', toolbar: '#operation', 240}
        	]
       	]
        ,response: {
            statusCode: 200
        }
        ,parseData: function(res){
        	return {
        		"code": res.code
                ,"msg": res.msg
                ,"count": res.data.total
                ,"data": res.data.records
        	};
        }
    });
    
    active = {
    	add: function() {
    		layer.open({
        		type: 2,
        		area: ['90%', '90%'],
        		title: '新增',
        		content: '[[@{/}]]job/add'
        	});
    	},
    	reload: function() {
    		table.reload('jobTable', {
    			page: {
    				curr: 1
    			}
    			,where: {
    				jobName : $("input[name='jobName']").val()
    			}
    		}, 'data');
    	},
    	reset: function() {
    		$(".searchTable .layui-input").val("");
    	},
    	startScheduler: function() {
    		$.ajax({
    	        url : "[[@{/}]]job/startScheduler",
    	        data : {},
    	        type : "post",
    	        dataType : "json",
    	        error : function(data) {
    	        	errorHandle(data);
    	        },
    	        success : function(data) {
    	        	getSchedulerStatus();
    	        	msg(data);
    	        	refresh();
    	        }
    	    });
    	},
    	standbyScheduler: function() {
    		$.ajax({
    	        url : "[[@{/}]]job/standbyScheduler",
    	        data : {},
    	        type : "post",
    	        dataType : "json",
    	        error : function(data) {
    	        	errorHandle(data);
    	        },
    	        success : function(data) {
    	        	getSchedulerStatus();
    	        	msg(data);
    	        	refresh();
    	        }
    	    });
    	}
    };
    
    // 按钮事件
    $('.layui-btn').on('click', function(){
        var type = $(this).data('type');
        active[type] ? active[type].call(this) : '';
    });
    
    //监听行工具事件
    table.on('tool(config)', function(obj){
    	var row = obj.data;
    	if (obj.event === 'start') {
    		$.ajax({
    	        url : "[[@{/}]]job/start",
    	        data : {'id': row.id},
    	        type : "post",
    	        dataType : "json",
    	        error : function(data) {
    	        	errorHandle(data);
    	        },
    	        success : function(data) {
    	        	msg(data);
    	        	refresh();
    	        }
    	    });
    	} if (obj.event == 'pasue') {
    		$.ajax({
    	        url : "[[@{/}]]job/pasue",
    	        data : {'id': row.id},
    	        type : "post",
    	        dataType : "json",
    	        error : function(data) {
    	        	errorHandle(data);
    	        },
    	        success : function(data) {
    	        	msg(data);
    	        	refresh();
    	        }
    	    });
    	} if (obj.event == 'trigger') {
    		$.ajax({
    	        url : "[[@{/}]]job/trigger",
    	        data : {'id': row.id},
    	        type : "post",
    	        dataType : "json",
    	        error : function(data) {
    	        	errorHandle(data);
    	        },
    	        success : function(data) {
    	        	msg(data);
    	        	refresh();
    	        }
    	    });
    	} else if(obj.event === 'del') {
    		layer.confirm("确认删除吗?", {icon: 3, title:'提示'}, function(index) {
    			layer.close(index);
    			$.ajax({
    		        url : "[[@{/}]]job/delete",
    		        data : {'id': row.id},
    		        type : "post",
    		        dataType : "json",
    		        error : function(data) {
    		        	errorHandle(data);
    		        },
    		        success : function(data) {
    		        	refresh();
    		        }
    		    });
    		});
        }
    });
    
    table.on('edit(config)', function(obj){
        var value = obj.value;
        if (isEmpty(value)) {
        	layer.msg("不能为空", {icon: 2});
        	refresh();
        	return;
        }
        $.ajax({
            url : "[[@{/}]]job/update",
            data : {'id': obj.data.id, 'cronExpression' : value},
            type : "post",
            dataType : "json",
            error : function(data) {
            	errorHandle(data);
            },
            success : function(data) {
            	msg(data);
            	refresh();
            }
        });
    });
    
    // 获取定时器状态
    $(function(){getSchedulerStatus();});
    function getSchedulerStatus() {
    	$.ajax({
            url : "[[@{/}]]job/isInStandbyMode",
            data : {},
            type : "post",
            dataType : "json",
            error : function(data) {
            	errorHandle(data);
            },
            success : function(data) {
            	if (!data.data) { // 启动状态
            		$("button[data-type='startScheduler']").addClass("layui-hide");
            		$("button[data-type='standbyScheduler']").removeClass("layui-hide");
            		$("#schedulerStatus").html("<span class='text-green'>启动中</span>");
            	} else { // 待机状态
            		$("button[data-type='startScheduler']").removeClass("layui-hide");
            		$("button[data-type='standbyScheduler']").addClass("layui-hide");
            		$("#schedulerStatus").html("<span class='text-orange'>待机中</span>");
            	}
            }
        });
    }
    </script>
    <script type="text/html" id="statusTemp">
    	{{#  if(d.status === 'NONE'){ }}
        	<span class="text-purple">未启动</span>
      	{{#  } else if(d.status === 'NORMAL') { }}
    		<span class="text-green">正常</span>
      	{{#  } else if(d.status === 'PAUSED') { }}
    		<span class="text-orange">暂停</span>
      	{{#  } else if(d.status === 'COMPLETE') { }}
    		<span class="text-aqua">完成</span>
      	{{#  } else if(d.status === 'ERROR') { }}
    		<span class="text-red">异常</span>
      	{{#  } else if(d.status === 'BLOCKED') { }}
    		<span class="text-maroon">锁定</span>
      	{{#  } else { }}
    		<span class="text-gray">未知</span>
    	{{#  } }}
    </script>
    <script type="text/html" id="groupTemp">
    	{{#  if(d.jobGroup === 'default'){ }}
        	默认
      	{{#  } else if(d.jobGroup === 'system') { }}
    		系统
      	{{#  } else { }}
    		未知
    	{{#  } }}
    </script>
    </html>
    
    • 新增定时任务新增页面jobAdd.html
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
        <link th:href="@{/layui/css/view.css}" rel="stylesheet" />
        <script th:src="@{/layui/layui.all.js}"></script>
        <script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
        <script th:src="@{/jquery/jquery-form.js}"></script>
        <script th:src="@{/layui/js/view.js}"></script>
        <title></title>
    </head>
    <body class="layui-view-body">
    	<div class="layui-row">
        	<div class="layui-card">
            	<form class="layui-form layui-card-body layui-form-pane" method="post" th:action="@{/job/save}">
            		<input type="hidden" name="status" value="NONE">
    				<div class="layui-form-item">
    					<div class="layui-inline mr0" style=" 49.7%">
    						<label class="layui-form-label"><i>*</i>任务名称</label>
    						<div class="layui-input-block">
    							<input type="text" name="jobName" id="jobName" maxlength="30" lay-verify="required" class="layui-input">
    						</div>
    					</div>
    					<div class="layui-inline mr0" style=" 49.8%">
    						<label class="layui-form-label"><i>*</i>任务分组</label>
    						<div class="layui-input-block">
    							<select name="jobGroup">
    								<option value="default">默认</option>
    								<option value="system">系统</option>
    							</select>
    						</div>
    					</div>
    				</div>
    				<div class="layui-form-item">
    					<label class="layui-form-label">任务描述</label>
    					<div class="layui-input-block">
    						<input type="text" name="remarks" maxlength="50" class="layui-input">
    					</div>
    				</div>
    				<div class="layui-form-item">
        				<label class="layui-form-label"><i>*</i>执行类</label>
    					<div class="layui-input-inline width-460">
    						<input type="text" name="beanClass" lay-verify="required" maxlength="200" class="layui-input">
    	    			</div>
    					<div class="layui-form-mid layui-word-aux">包名 + 类名,示例:com.c3stones.job.biz.TestJob</div>
      				</div>
    				<div class="layui-form-item">
        				<label class="layui-form-label">参数</label>
    					<div class="layui-input-inline width-460">
    						<input type="text" name="jobDataMap" placeholder="JSON数据格式" maxlength="1000" autocomplete="off" class="layui-input">
    	    			</div>
    					<div class="layui-form-mid layui-word-aux">示例:{"username":"zhangsan", "age":18}</div>
      				</div>
    				<div class="layui-form-item">
        				<label class="layui-form-label"><i>*</i>表达式</label>
    					<div class="layui-input-inline width-460">
    						<input type="text" name="cronExpression" placeholder="例如:0/5 * * * * ?" lay-verify="required" maxlength="200" class="layui-input">
    	    			</div>
    					<div class="layui-form-mid layui-word-aux"><a class="text-blue" href="https://cron.qqe2.com/" target="_blank">在线Cron表达式生成器</a></div>
      				</div>
    				<div class="layui-form-item">
    	                <button type="submit" class="layui-btn" lay-submit lay-filter="*">提交</button>
    	                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
                  	</div>
    			</form>
    		</div>
    	</div>
    </body>
    <script>
    var form = layui.form;
    var layer = layui.layer;
    
    form.render();
    
    // 提交表单
    form.on('submit(*)', function(data){
    	$(".layui-form").ajaxForm({
    		error: function(data){
    			errorHandle(data);
    		},
    		success: function(data) {
    			parent.location.reload();
    			var index = parent.layer.getFrameIndex(window.name);
    			parent.layer.close(index);
    		}
    	});
    });
    </script>
    </html>
    

    5. 测试

    • 创建两种类型Job
      • 实现Job接口
      import java.time.LocalDateTime;
      
      import org.quartz.Job;
      import org.quartz.JobDataMap;
      import org.quartz.JobExecutionContext;
      import org.quartz.JobExecutionException;
      import org.springframework.beans.factory.annotation.Autowired;
      
      import com.c3stones.job.service.JobService;
      
      import lombok.extern.slf4j.Slf4j;
      
      /**
       * 测试定时任务
       * 
       * @author CL
       *
       */
      @Slf4j
      // @DisallowConcurrentExecution //不并发执行
      public class TestJob implements Job {
      
      	@Autowired
      	private JobService jobService;
      
      	@Override
      	public void execute(JobExecutionContext context) throws JobExecutionException {
      		JobDataMap jobDataMap = context.getMergedJobDataMap();
      		log.info("定时任务1 => 定时任务定时任务数量 => {},参数值 => {},当前时间 => {}", jobService.count(),
      				"{ username=" + jobDataMap.get("username") + ", age=" + jobDataMap.get("age") + " }",
      				LocalDateTime.now());
      	}
      
      }    
      
      • 继承QuartzJobBean类
      import java.time.LocalDateTime;
      
      import org.quartz.JobDataMap;
      import org.quartz.JobExecutionContext;
      import org.quartz.JobExecutionException;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.scheduling.quartz.QuartzJobBean;
      
      import com.c3stones.job.service.JobService;
      
      import lombok.extern.slf4j.Slf4j;
      
      /**
       * 测试定时任务
       * 
       * @author CL
       *
       */
      @Slf4j
      // @DisallowConcurrentExecution //不并发执行
      public class Test2Job extends QuartzJobBean {
      
      	@Autowired
      	private JobService jobService;
      
      	@Override
      	protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
      		JobDataMap jobDataMap = context.getMergedJobDataMap();
      		log.info("定时任务2 => 定时任务数量 => {},参数值 => {},当前时间 => {}", jobService.count(),
      				"{ username=" + jobDataMap.get("username") + ", age=" + jobDataMap.get("age") + " }",
      				LocalDateTime.now());
      	}
      
      }
      
    • 配置定时任务
        浏览器访问:http://127.0.0.1:8080/login,填写用户信息user/123456登录系统,点击菜单:系统管理>任务调度,通过新增页面,添加两个定时任务。配置完成页面如下:

        顶部按钮:定时器待机启动定时器为定时器操作按钮,即对所有定时任务有效。当定时器状态为启动中时,定时器待机显示,点击定时器状态变为待机中,所有定时任务待机;反之,所有定时任务可正常触发。
        右侧操作栏按钮:启动暂停立即执行删除,仅对当前定时任务有效。新增完的定时任务为未启动状态,点击启动按钮即可触发定时任务,点击暂停按钮即可暂停定时任务,点击立即执行按钮即可立即执行一次定时任务,点击删除按钮即可删除定时任务。
    • 点击操作按钮,观察控制台日志打印

    6. 项目地址

      spring-boot-quartz-demo

  • 相关阅读:
    常用模块
    装饰器、生成器、迭代器
    java-异常处理
    java-运算符,IO输入输出,文件操作
    java-修饰符
    容器docker操作
    requirements模块使用
    pycharm的安装和使用
    python-scrapy框架
    生产环境django项目部署要点
  • 原文地址:https://www.cnblogs.com/cao-lei/p/13730564.html
Copyright © 2011-2022 走看看