springboot整合quartz实现动态定时任务配置实例

参考了网上的一些文档,发现写都都很细碎,且没有提供一个完成实例的源码,特此写了一个实例并上传到了github上供大家参考

项目地址: https://github.com/frankliu121/springboot-curd

效果:
springboot整合quartz实现动态定时任务配置实例_第1张图片
springboot整合quartz实现动态定时任务配置实例_第2张图片

1.添加quartz依赖

springboot 版本为2.x之后的则在spring启动器中已经包含了quart的依赖知己依赖启动器即可;

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

要是1.5.9则要使用以下添加依赖:

<dependency>
	<groupId>org.quartz-scheduler</groupId> 
    <artifactId>quartz</artifactId>
     <version>2.3.0</version> 
 </dependency> 
<dependency> 
    <groupId>org.springframework</groupId> 
    <artifactId>spring-context-support</artifactId> 
</dependency>

2.建立持久化所需要的表:

需注意quartz 1.x与quartz 2.x建表语句有所区别,可以去quartz官网去下载,我这里使用的是2.x版本的建表脚本:

#  
# In your Quartz properties file, you'll need to set   
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate  
#  
#  
# By: Ron Cordell - roncordell  
#  I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM.  
  
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;  
  
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;  
  
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;  
  
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;  
  
CREATE TABLE QRTZ_CRON_TRIGGERS (  
SCHED_NAME VARCHAR(120) NOT NULL,  
TRIGGER_NAME VARCHAR(200) NOT NULL,  
TRIGGER_GROUP VARCHAR(200) NOT NULL,  
CRON_EXPRESSION VARCHAR(120) NOT NULL,  
TIME_ZONE_ID VARCHAR(80),  
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),  
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)  
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))  
ENGINE=InnoDB;  
  
CREATE TABLE QRTZ_SIMPROP_TRIGGERS  
  (            
    SCHED_NAME VARCHAR(120) NOT NULL,  
    TRIGGER_NAME VARCHAR(200) NOT NULL,  
    TRIGGER_GROUP VARCHAR(200) NOT NULL,  
    STR_PROP_1 VARCHAR(512) NULL,  
    STR_PROP_2 VARCHAR(512) NULL,  
    STR_PROP_3 VARCHAR(512) NULL,  
    INT_PROP_1 INT NULL,  
    INT_PROP_2 INT NULL,  
    LONG_PROP_1 BIGINT NULL,  
    LONG_PROP_2 BIGINT NULL,  
    DEC_PROP_1 NUMERIC(13,4) NULL,  
    DEC_PROP_2 NUMERIC(13,4) NULL,  
    BOOL_PROP_1 VARCHAR(1) NULL,  
    BOOL_PROP_2 VARCHAR(1) NULL,  
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),  
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)   
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))  
ENGINE=InnoDB;  
  
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;  
  
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;  
  
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;  
  
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;  
  
CREATE TABLE QRTZ_SCHEDULER_STATE (  
SCHED_NAME VARCHAR(120) NOT NULL,  
INSTANCE_NAME VARCHAR(200) NOT NULL,  
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,  
CHECKIN_INTERVAL BIGINT(13) NOT NULL,  
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))  
ENGINE=InnoDB;  
  
CREATE TABLE QRTZ_LOCKS (  
SCHED_NAME VARCHAR(120) NOT NULL,  
LOCK_NAME VARCHAR(40) NOT NULL,  
PRIMARY KEY (SCHED_NAME,LOCK_NAME))  
ENGINE=InnoDB;  
  
CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);  
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);  
  
CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);  
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);  
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);  
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);  
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);  
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);  
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);  
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);  
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);  
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);  
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);  
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);  
  
CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);  
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);  
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);  
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);  
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);  
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);  
  
commit;  

springboot整合quartz实现动态定时任务配置实例_第3张图片
表的含义:

QRTZ_CALENDARS 以 Blob 类型存储 Quartz 的 Calendar 信息 QRTZ_CRON_TRIGGERS 存储CronTrigger触发器信息,包括Cron表达式和时区等信息
QRTZ_FIRED_TRIGGERS 存储已触发的Trigger状态信息和关联的Job执行信息
QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的Trigger组信息
QRTZ_SCHEDULER_STATE 存储有关Scheduler的状态信息
QRTZ_LOCKS 存储程序锁信息
QRTZ_JOB_DETAILS 存储Job的详细信息
QRTZ_JOB_LISTENERS 存储Job配置的JobListener信息
QRTZ_SIMPLE_TRIGGERS 存储SimpleTrigger触发器信息,包括重复次数,间隔等信息
QRTZ_BLOG_TRIGGERS 存储Blob类型的Trigger,一般用于自定义触发器
QRTZ_TRIGGER_LISTENERS 存储已配置的TriggerListener信息
QRTZ_TRIGGERS 存储已配置的Trigger的信息

3.修改application.yml添加quartz相关配置

spring:
  quartz:
    properties:
      org:
        quartz:
          scheduler:
            instanceName: clusteredScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_
            isClustered: false #是否使用集群
            clusterCheckinInterval: 10000
            useProperties: false
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
    #数据库方式
    job-store-type: jdbc
      #初始化表结构
    #jdbc:
    #initialize-schema: never

4.新建配置类对quartz进行相关配置:

package lf.ssm.config;

import org.quartz.*;
import org.quartz.spi.JobFactory;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

import java.io.IOException;

/**
 * @Classname QuartzConfig
 * @Date 2020/4/10 17:23
 * @Created by liufeng
 */

@Configuration
public class QuartzConfig {

    //配置JobFactory,为quartz作业添加自动连接支持
    public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
            ApplicationContextAware {
        private transient AutowireCapableBeanFactory beanFactory;

        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }

        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
            final Object job = super.createJobInstance(bundle);
            beanFactory.autowireBean(job);
            return job;
        }
    }
}

5.自定义任务实例JobEntity

储存任务信息可以根据自己的业务需求再扩展字段

package lf.ssm.entity.job;

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 io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lf.ssm.core.base.BaseModel;
import lf.ssm.util.ValidType;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
 * @Classname JobEntity
 * @Date 2020/4/10 17:01
 * @Created by liufeng
 */

@Data
@Accessors(chain = true)
@ApiModel("任务实体")
@TableName("JOB_ENTITY")
public class JobEntity extends BaseModel {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.ID_WORKER)
    @ApiModelProperty(value="id",name="id")
    @NotNull( message = "id不能为空",groups = {ValidType.Update.class,ValidType.Delete.class})
    private Long id;

    @ApiModelProperty(value="job名称",name="name")
    @NotNull( message = "任务名不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
    @NotBlank( message = "任务名不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
    private String name;

    @ApiModelProperty(value="job分组",name="job_group")
    private String jobGroup;

    @ApiModelProperty(value="执行的表达式",name="cronExpression")
    @TableField("cron_expression")
    @NotNull( message = "表达式不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
    @NotBlank( message = "表达式不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
    private String cronExpression;

    @ApiModelProperty(value="job的参数",name="parameter")
    private String parameter;

    @ApiModelProperty(value="job描述信息",name="description")
    private String description;

    @ApiModelProperty(value="标识job的执行状态 0:已停用 1已启用",name="status")
    @NotNull( message = "状态不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
    @NotBlank( message = "状态不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
    private String status;

    @ApiModelProperty(value="触发器名字",name="triggerName")
    @TableField("trigger_name")
    private String triggerName;

    @ApiModelProperty(value="触发器分组",name="triggerGroup")
    @TableField("trigger_group")
    private String triggerGroup;

    @ApiModelProperty(value="执行类名(包名+类名)",name="jobClassName")
    @NotNull( message = "类名不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
    @NotBlank( message = "类名不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
    @TableField("job_class_name")
    private String jobClassName;

    @ApiModelProperty(value="执行的方法名",name="jobClassName")
    @NotNull( message = "方法名不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
    @NotBlank( message = "方法名不能为空",groups = {ValidType.Add.class,ValidType.Update.class})
    @TableField("method_name")
    private  String methodName;

    @ApiModelProperty(value="是否立即运行,0:否,1:是",name="isNowRun")
    @TableField("is_now_run")
    private String isNowRun;

    @ApiModelProperty(value="备注",name="remark")
    private String remark;

    @ApiModelProperty(value="表达式解析后的值显示",name="cronExpressionVal")
    @TableField(exist = false)
    private String cronExpressionVal;
}

5.JobUtil:自定义的一个处理job的工具类

package lf.ssm.util;

import lf.ssm.entity.job.JobEntity;
import org.quartz.CronExpression;
import org.quartz.JobKey;
import org.quartz.TriggerKey;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * @Classname CronExpParserUtil
 * @Date 2020/4/11 16:33
 * @Created by liufeng
 */
public class JobUtil {

    public final static String DEFAULT_GROUP= "DEFAULT_GROUP";

    public final static String TRIGGER_PREFIX= "TRIGGER_";

    /**
     * 生成JobKey
     * @param job
     * @return
     */
    public static JobKey genJobKey(JobEntity job) {
        return new JobKey(job.getName().trim(), DEFAULT_GROUP);
    }


    /**
     * 产生TriggerKey
     *
     * @param job
     * @return
     */
    public static TriggerKey genTriggerKey(JobEntity job) {
        return new TriggerKey(TRIGGER_PREFIX + job.getName().trim(), DEFAULT_GROUP);
    }


    /**
     * 解析corn表达式,生成指定日期的时间序列
     *
     * @param cronExpression cron表达式
     * @param cronDate       cron解析日期
     * @param result         crom解析时间序列
     * @return 解析成功失败
     */
    public static boolean parser(String cronExpression, String cronDate, List<String> result) {
        if (cronExpression == null || cronExpression.length() < 1 || cronDate == null || cronDate.length() < 1) {
            return false;
        } else {
            CronExpression exp = null;
            // 初始化cron表达式解析器
            try {
                exp = new CronExpression(cronExpression);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            // 定义生成时间范围
            // 定义开始时间,前一天的23点59分59秒
            Calendar c = Calendar.getInstance();
            String sStart = cronDate + " 00:00:00";
            SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date dStart = null;
            try {
                dStart = sdf.parse(sStart);
            } catch (ParseException e) {
                e.printStackTrace();
            }

            c.setTime(dStart);
            c.add(Calendar.SECOND, -1);
            dStart = c.getTime();

            // 定义结束时间,当天的23点59分59秒
            c.add(Calendar.DATE, 1);
            Date dEnd = c.getTime();

            // 生成时间序列
            java.util.Date dd = dStart;
            dd = exp.getNextValidTimeAfter(dd);
            while ((dd.getTime() >= dStart.getTime()) && (dd.getTime() <= dEnd.getTime())) {
                result.add(sdf.format(dd));
                dd = exp.getNextValidTimeAfter(dd);
            }
            exp = null;
        }
        return true;
    }

    public static String translateToChinese(String cronExp) {
        if (cronExp == null || cronExp.length() < 1) {
            return "cron表达式为空";
        }
        CronExpression exp = null;
        // 初始化cron表达式解析器
        try {
            exp = new CronExpression(cronExp);
        } catch (ParseException e) {
            return "corn表达式不正确";
        }
        String[] tmpCorns = cronExp.split(" ");
        StringBuffer sBuffer = new StringBuffer();
        if (tmpCorns.length == 6) {
            //解析月
            if (!tmpCorns[4].equals("*")) {
                sBuffer.append(tmpCorns[4]).append("月");
            } else {
                sBuffer.append("每月");
            }
            //解析周
            if (!tmpCorns[5].equals("*") && !tmpCorns[5].equals("?")) {
                char[] tmpArray = tmpCorns[5].toCharArray();
                for (char tmp : tmpArray) {
                    switch (tmp) {
                        case '1':
                            sBuffer.append("星期天");
                            break;
                        case '2':
                            sBuffer.append("星期一");
                            break;
                        case '3':
                            sBuffer.append("星期二");
                            break;
                        case '4':
                            sBuffer.append("星期三");
                            break;
                        case '5':
                            sBuffer.append("星期四");
                            break;
                        case '6':
                            sBuffer.append("星期五");
                            break;
                        case '7':
                            sBuffer.append("星期六");
                            break;
                        case '-':
                            sBuffer.append("至");
                            break;
                        default:
                            sBuffer.append(tmp);
                            break;
                    }
                }
            }

            //解析日
            if (!tmpCorns[3].equals("?")) {
                if (!tmpCorns[3].equals("*")) {
                    sBuffer.append(tmpCorns[3]).append("日");
                } else {
                    sBuffer.append("每日");
                }
            }

            //解析时
            if (!tmpCorns[2].equals("*")) {
                sBuffer.append(tmpCorns[2]).append("时");
            } else {
                sBuffer.append("每时");
            }

            //解析分
            if (!tmpCorns[1].equals("*")) {
                sBuffer.append(tmpCorns[1]).append("分");
            } else {
                sBuffer.append("每分");
            }

            //解析秒
            if (!tmpCorns[0].equals("*")) {
                sBuffer.append(tmpCorns[0]).append("秒");
            } else {
                sBuffer.append("每秒");
            }
        }

        return sBuffer.toString();

    }

    //测试方法
    public static void main(String[] args) {
        String CRON_EXPRESSION = "0 0 3 * * ?";
        // 生成指定日期的CRON时间序列
        String CRON_DATE = "2016-04-26";
        System.out.println(CRON_EXPRESSION);
        System.out.println(JobUtil.translateToChinese(CRON_EXPRESSION));

        List<String> lTime = new ArrayList<String>();
        if (!JobUtil.parser(CRON_EXPRESSION, CRON_DATE, lTime)) {
            System.out.println("无法生成Cron表达式:日期," + CRON_DATE + ";不符合规则cron表达式:" + CRON_EXPRESSION);
        }
        for (int i = 0; i < lTime.size(); i++) {
            System.out.println(lTime.get(i));
        }

    }
}

6.IJobEntityService

package lf.ssm.service.job;

import lf.ssm.core.base.IBaseService;
import lf.ssm.entity.job.JobEntity;
import lf.ssm.exception.JobException;

/**
 * 

* 服务类 *

* * @author liufeng * @since 2020-04-10 */
public interface IJobEntityService extends IBaseService<JobEntity> { /** * 保存任务 * @param record */ void saveTask(JobEntity record); /** * 立即执行(触发一次任务) * @param record * @throws JobException */ void trigger(JobEntity record); /** * 暂停任务 * @param record */ void pause(JobEntity record); /** * 恢复任务 * @param record * @throws JobException */ void resume(JobEntity record); /** * 移除任务 * @param jobEntity * @throws JobException */ void remove(JobEntity jobEntity); /** * 更新任务 * @param record * @throws JobException */ void updateTask(JobEntity record); }

7.JobEntityServiceImpl

6.package lf.ssm.service.impl.job;

import lf.ssm.core.impl.BaseServiceImpl;
import lf.ssm.entity.job.JobEntity;
import lf.ssm.exception.JobException;
import lf.ssm.mapper.job.JobEntityMapper;
import lf.ssm.service.job.IJobEntityService;
import lf.ssm.util.BeanUtil;
import lf.ssm.util.Const;
import lf.ssm.util.JobUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.Optional;

/**
 * 

* 服务实现类 *

* * @author liufeng * @since 2020-04-10 */
@Service @Slf4j public class JobEntityServiceImpl extends BaseServiceImpl<JobEntity,JobEntityMapper> implements IJobEntityService { @Autowired private Scheduler scheduler; @Override @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = Exception.class) public void saveTask(@RequestBody JobEntity record) { try { buildJob(record); insert(record); } catch (Exception e) { throw new JobException(201,"新增任务失败"); } } /** * 立即执行 * quartz是通过临时生成一个trigger的方式来实现的,这个trigger将在本次任务运行完成之后自动删除。 * @param record * @throws JobException */ @Override public void trigger(JobEntity record) { try { scheduler.triggerJob(JobUtil.genJobKey(record)); } catch (Exception e) { e.printStackTrace(); } } @Override public void pause(JobEntity record) { try { record.setStatus(Const.DEFAULT_NO_STR); updateById(record); scheduler.pauseJob(JobUtil.genJobKey(record)); } catch (SchedulerException e) { e.printStackTrace(); throw new JobException(666,"暂停任务失败"); } } @Override public void resume(JobEntity record) { try { record.setStatus(Const.DEFAULT_YES_STR); updateById(record); scheduler.resumeJob(JobUtil.genJobKey(record)); } catch (Exception e) { e.printStackTrace(); throw new JobException(999,"恢复任务失败"); } } @Override @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = Exception.class) public void remove(JobEntity jobEntity){ try { deleteById(jobEntity.getId()); deleteJob(jobEntity); } catch (Exception e) { e.printStackTrace(); throw new JobException(520,"移除任务失败"); } } @Override @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = JobException.class) public void updateTask(JobEntity record) throws JobException { // 1.更新自定义任务实体数据 2.删除原任务相关数据 3.重新构建任务 try { deleteJob(selectById(record.getId())); buildJob(record); updateById(record); } catch (Exception e) { e.printStackTrace(); throw new JobException(886,"更新任务失败"); } } /** * 构建定时任务 * @param record * @throws ClassNotFoundException * @throws SchedulerException * @throws IllegalAccessException * @throws InstantiationException */ private void buildJob(JobEntity record) throws ClassNotFoundException, SchedulerException, IllegalAccessException, InstantiationException { Class cls = Class.forName(record.getJobClassName()); cls.newInstance(); // 构建jobdetail丶 触发器丶调度器 JobDetail job = JobBuilder.newJob(cls).withIdentity(JobUtil.genJobKey(record)) .withDescription(record.getRemark()).build(); CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(record.getCronExpression()); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity(JobUtil.genTriggerKey(record)) .startNow() .withSchedule(cronScheduleBuilder).build(); // 传递的参数 job.getJobDataMap().put("jobEntity", BeanUtil.beanToMap(record)); // 调度任务 if(!scheduler.isShutdown()) { scheduler.scheduleJob(job, trigger); } // 启动调度器 if (!scheduler.isStarted()) { scheduler.start(); } //未启用 if (Const.DEFAULT_NO_STR.equals(record.getStatus())){ scheduler.pauseJob(JobUtil.genJobKey(record)); } if (Const.DEFAULT_YES_STR.equals(record.getIsNowRun())) { //是否立即执行一次 trigger(record); } } /** * 移除任务 * @param record * @throws SchedulerException */ private void deleteJob(JobEntity record) throws SchedulerException { //移除任务 TriggerKey triggerKey = JobUtil.genTriggerKey(record); // 停止触发器 scheduler.pauseTrigger(triggerKey); // 移除触发器 scheduler.unscheduleJob(triggerKey); // 删除任务 scheduler.deleteJob(JobUtil.genJobKey(record)); } }

8.BaseJob

实现Job接口,通过JobExecutionContext对象可以获取到我们存的自定义任务实体JobEntity,来获取储存的className以及methodName,通过反射执行其子类实例的方法

package lf.ssm.core.base;

import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
import lf.ssm.entity.job.JobEntity;
import lf.ssm.exception.JobException;
import lf.ssm.util.*;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import javax.annotation.PostConstruct;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;

/**
 * @Classname BaseJob
 * @Date 2020/4/11 21:14
 * @Created by liufeng
 */
@Slf4j
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class BaseJob implements Job,Serializable {

    /**
     * 利用反射执行指定的方法
     * @param context
     * @throws JobExecutionException
     */
    @Override
    public void execute(JobExecutionContext context) {
        JobDataMap JobDataMap = context.getMergedJobDataMap();
        Map jobMap = (Map<String,Object>)JobDataMap.get("jobEntity");
        if (CollectionUtils.isEmpty(jobMap)){
            log.error("获取jobEntity失败");
            return;
        }
        JobEntity jobEntity = null;
        Class<?> clazz =null;
        try {
            jobEntity = BeanUtil.mapToBean(jobMap, JobEntity.class);
            clazz = Class.forName(jobEntity.getJobClassName());
            String className = StringUtils.toLowerCaseFirstOne(clazz.getSimpleName());
            Object bean = (Object) SpringUtil.getBean(className);
            Method method = ReflectionUtils.findMethod(bean.getClass(), jobEntity.getMethodName());
            log.info("=======开始执行定时任务:"+jobEntity.getJobClassName()+"_"+jobEntity.getMethodName()+"=======");
            long start = System.currentTimeMillis();
            ReflectionUtils.invokeMethod(method, bean);
            long end = System.currentTimeMillis();
            log.info("=======定时任务执行完毕,耗时:"+(end-start)+"毫秒,任务执行时间:"+DateUtil.formatDate(context.getFireTime())+",下次执行时间:"+ DateUtil.formatDate(context.getNextFireTime()) +"=======");
        }  catch (Exception e) {
            log.error("定时任务执行失败");
            e.printStackTrace();
        }
    }
}

9.JobEntityController

package lf.ssm.controller.job;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lf.ssm.core.base.BaseResult;
import lf.ssm.entity.job.JobEntity;
import lf.ssm.service.job.IJobEntityService;
import lf.ssm.util.JobUtil;
import lf.ssm.util.ValidType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import lf.ssm.core.base.BaseController;
import org.springframework.web.servlet.ModelAndView;

import javax.validation.Valid;
import java.util.Optional;

/**
 * 

* 任务实体控制器 *

* * @author liufeng * @since 2020-04-10 */
@Api(description = "定时任务控制器",tags = "JobEntityController") @RestController @RequestMapping("/jobEntity") public class JobEntityController extends BaseController { @Autowired private IJobEntityService service; /** * 跳转页面 * @param page * @return */ @GetMapping("/page/{page}") @ApiOperation("跳转页面") public ModelAndView toPage(@PathVariable("page") String page){ ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("vue/"+page); return modelAndView; } /** * 查询所有任务 * @author liufeng * @date 2020/4/11 13:24 * @return java.util.List */ @GetMapping("/list") @ApiOperation("查询所有任务") public IPage<JobEntity> list( long pageNum, long pageSize){ IPage<JobEntity> page = service.selectPage(pageNum, pageSize, new QueryWrapper<JobEntity>().orderByDesc("status","create_time")); //处理corn表达式显示 if (!CollectionUtils.isEmpty(page.getRecords())){ page.getRecords().forEach(r->{ r.setCronExpressionVal(JobUtil.translateToChinese(r.getCronExpression())); }); } return page; } /** * 添加定时任务 * @author liufeng * @date 2020/4/11 13:24 * @return lf.ssm.entity.base.BaseResult */ @PostMapping("/addJob") @ApiOperation("添加定时任务") public BaseResult addJob(@RequestBody @Validated({ValidType.Add.class}) JobEntity job, BindingResult bindingResult){ BaseResult result = BaseResult.successResult(); job.setTriggerName(JobUtil.TRIGGER_PREFIX+ job.getName()); job.setJobGroup(JobUtil.DEFAULT_GROUP); job.setTriggerGroup(JobUtil.DEFAULT_GROUP); service.saveTask(job); return result; } /** * 更新定时任务 * @author liufeng * @date 2020/4/11 13:24 * @return lf.ssm.entity.base.BaseResult */ @PostMapping("/updateJob") @ApiOperation("更新定时任务") public BaseResult updateJob(@RequestBody @Validated({ValidType.Update.class}) JobEntity job, BindingResult bindingResult){ BaseResult result = BaseResult.successResult(); service.updateTask((job)); return result; } /** * 移除定时任务 * @author liufeng * @date 2020/4/11 13:24 * @return lf.ssm.entity.base.BaseResult */ @PostMapping("/removeJob/{id}") @ApiOperation("移除定时任务") public BaseResult removeJob(@PathVariable("id") Long id){ BaseResult result = BaseResult.successResult(); if (id == null || id == 0){ return result.error(2,"id不能为空"); } JobEntity jobEntity = service.selectById(id); if (jobEntity == null){ return result.error(3,"未查询到实体"); } service.remove(jobEntity); return result; } /** * 暂停定时任务 * @author liufeng * @date 2020/4/11 13:24 * @return lf.ssm.entity.base.BaseResult */ @PostMapping("/pauseJob/{id}") @ApiOperation("暂停定时任务") public BaseResult pauseJob(@PathVariable("id") Long id){ BaseResult result = BaseResult.successResult(); if (id == null || id == 0){ return result.error(2,"id不能为空"); } JobEntity jobEntity = service.selectById(id); if (jobEntity == null){ return result.error(3,"未查询到实体"); } service.pause(jobEntity); return result; } /** * 恢复定时任务 * @author liufeng * @date 2020/4/11 13:24 * @return lf.ssm.entity.base.BaseResult */ @PostMapping("/resumeJob/{id}") @ApiOperation("恢复定时任务") public BaseResult resumeJob(@PathVariable("id") Long id){ BaseResult result = BaseResult.successResult(); if (id == null || id == 0){ return result.error(2,"id不能为空"); } JobEntity jobEntity = service.selectById(id); if (jobEntity == null){ return result.error(3,"未查询到实体"); } service.resume(jobEntity); return result; } /** * 触发一次任务 * @author liufeng * @date 2020/4/11 13:24 * @return lf.ssm.entity.base.BaseResult */ @PostMapping("/triggerJob/{id}") @ApiOperation("触发一次任务") public BaseResult triggerJob(@PathVariable("id") Long id){ BaseResult result = BaseResult.successResult(); if (id == null || id == 0){ return result.error(2,"id不能为空"); } JobEntity jobEntity = service.selectById(id); if (jobEntity == null){ return result.error(3,"未查询到实体"); } service.trigger(jobEntity); return result; } }

10.使用:

测试场景:
实现每隔20s打印某某某我爱你
实现每隔20s打印你最棒

1.新建类继承BaseJob

package lf.ssm.job;

import lf.ssm.core.base.BaseJob;
import lf.ssm.entity.test.User;
import lf.ssm.service.test.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * Test类定时任务
 * @Classname TestJob
 * @Date 2020/4/11 21:34
 * @Created by liufeng
 */
@DisallowConcurrentExecution
@Component
@Slf4j
public class TestJob extends BaseJob {

    @Autowired
    private IUserService userService;

    /**
     * 打印我爱你
     */
    public void printLoveYou(){
        log.info("======================printLoveYou定时任务开始执行===================");
        User user = userService.selectList().get(0);
        System.out.println("我爱你"+user.getUsername());
        iLoveYou();
        log.info("======================printLoveYou定时任务执行结束===================");
    }

    /**
     * 打印你最棒
     */
    public void printYouAreBest(){
        log.info("======================printLoveYou定时任务开始执行===================");
        List<User> users = userService.selectList();
        users.forEach(u->{
            System.out.println("亲爱的"+u.getUsername()+"你最棒");
        });
        log.info("======================printLoveYou定时任务执行结束===================");

    }

    /**
     * 打印心形
     */
    private void iLoveYou(){
        for(float y = (float) 1.5;y>-1.5;y -=0.1)  {
            for(float x= (float) -1.5;x<1.5;x+= 0.05){
                float a = x*x+y*y-1;
                if((a*a*a-x*x*y*y*y)<=0.0)  {
                    System.out.print("^");
                }
                else
                    System.out.print(" ");
            }
            System.out.println();
        }
    }
}

2.配置任务:
访问:http://localhost:8080/jobEntity/page/JobManager
springboot整合quartz实现动态定时任务配置实例_第4张图片
springboot整合quartz实现动态定时任务配置实例_第5张图片


你可能感兴趣的:(springboot整合quartz实现动态定时任务配置实例)