springboot整合quartz实现动态添加、修改、删除、停止job(一)

作者:青山常在人不老   阅读 (3869)  |  收藏 (0)  |  点赞 (0)

摘要

本文将带您讲解如何一步步实现通过quartz实现添加、修改、删除、停止job,以及优化quartz工具类,支持自动停止逻辑


原文链接:springboot整合quartz实现动态添加、修改、删除、停止job(一)

什么是Quartz?

一个定时任务调度框架,简单易用,功能强大可以使实现定时任务的。

优点:

  1. 支持集群下定时任务处理

  2. 支持任务并发阻塞(上一个任务完成后,才能继续下一个任务)

  3. 支持通过API对任务的操作,例如新增任务、修改、启动、暂停、停止(可以在代码中进行调用,而无需修改配置文件再次部署)

  4. 支持的数据库种类被较多

目标

  1. 在Spring Boot中集成Quartz

  2. 使用MySql数据库(程序自动导入,无需人工执行脚本)

  3. 使用Spring 自身配置的数据源(不再单独配置qz数据源)

  4. 通过代码实现动态化添加、修改、暂停、终止job

开发环境

  1. JDK版本1.8

  2. Spring Boot 版本:2.3.3.RELEASE

  3. 开发工具:eclipse

开发实践

本章节将从Pom依赖配置开始,直到成功运行起该程序为止,为各位朋友提供真实可行的代码实现

关于Pom.xml中的配置,有两种方式,第一种使用spring-boot封装的依赖,第二种使用org.quartz的依赖
第一种方式:

<!--引入quartz定时框架-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

这种依赖的好处是你不需要考虑qz的版本号,Spring boot会根据自身的版本来适应不同的quartz版本,但是缺点也很明显,你无法使用其他版本的quartz(不同版本的QZ结构稍有差异),而且在这个封装的依赖中,其实里面也仅仅是指定了org.quartz的依赖,并没有其他的配置或者逻辑
第二种方式

<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
  <version>2.3.2</version>
</dependency>
<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz-jobs</artifactId>
  <version>2.3.2</version>
</dependency>

这种方式是直接引用的quartz,就是第一种方式中的配置的依赖,但是在这里你就可以自己选择quartz的版本(注意:不同版本的qz结构有差异,Spring boot读取文件时有可能会出现问题。例如在Spring boot 2.3.3.RELEASE中,2.2.1的版本自动生成表结构时会报错--jar包中没有对应的sql脚本,而2.3.2就不存在这个问题)
您可以根据自己的需求来选择哪种依赖方式(以上两种只需要使用其中任何一种即可,无需都使用)

################### Quartz配置 start ##################################################
server:
  port: 8080
  servlet:
    context-path: /quartz
spring:
  application:
    name: demo
  #连接池配置
  datasource:
    #账号配置
    url: jdbc:mysql://localhost:3306/qz_table?allowMultiQueries=true&useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    #hikari数据库连接池
    hikari:
      pool-name: Retail_HikariCP
      minimum-idle: 5 #最小空闲连接数量
      idle-timeout: 180000 #空闲连接存活最大时间,默认600000(10分钟)
      maximum-pool-size: 10 #连接池最大连接数,默认是10
      auto-commit: true  #此属性控制从池返回的连接的默认自动提交行为,默认值:true
      max-lifetime: 1800000 #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
      connection-timeout: 30000 #数据库连接超时时间,默认30秒,即30000
      connection-test-query: SELECT 1
  quartz:
          #          dataSource:
          #            default:
          #              driver: com.mysql.jdbc.Driver
          #              URL: jdbc:mysql://localhost:3306/jobconfig?useUnicode=true&characterEncoding=utf8
          #              user: root
          #              password: 12345678
          #              maxConnections: 5
    #相关属性配置
    properties:
      org:
        quartz:
          scheduler:
            instanceName: quartzScheduler
            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
#mybatis配置
mybatis:
  type-aliases-package: com.example.demo.entity
  mapper-locations: classpath:mapper/*.xml
#分页配置, pageHelper是物理分页插件
pagehelper:
  #4.0.0以后版本可以不设置该参数,该示例中是5.1.4
  helper-dialect: mysql
  #启用合理化,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页
  reasonable: true
################### Quartz配置 End ###################################################

注意:

  1. 本文配置使用的是yml文件方式,如果您想使用.properties方式,请将上面的冒号改成等号

  2. 本文中使用的quartz没有单独配置数据源,而是使用的您在Spring boot中已经配置的数据源(自动识别,可以看下源码,默认支持C3p0,hikari等几种)

  3. 本文中默认表结构初始化方式为initialize-schema: never(共有三种方式:always-每次启动都初始化数据库,never-不初始化表结构,embedded,您可以在首次运行时将此方式设置为always,然后再改成never)

  4. 在这个配置中dataSource注释掉了,如果您需要quartz单独配置一套数据源,请放开此部分注释

  5. 注意,quartz.yml需要被Spring boot发现(或者直接配置到application.yml中),否则即使配置了也不会起作用,而是使用默认的RAM(内存)去保存定时任务的数据(速度快但是应用重启后会丢失,我在此处就栽跟头了)

到这一步,配置已经完成,接下来就该具体的代码了

逻辑代码实现

该部分java类的结构如下:

common包中的Result.java为返回结果类(controller中使用,如您不想使用,请替换为自己的逻辑)

package com.example.demo.common;
import java.util.HashMap;
import java.util.Map;

/**
 * @project: 郭鹏飞的博客(wwww.pengfeiguo.com)
 * @description: 响应结果类
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-09-04 825338623@qq.com Create 1.0
 * @copyright ©2019-2020
 */
public class Result extends HashMap<String, Object> {


    public Result() {
        put("code", 200);
    }

    public static Result error() {
        return error(500, "未知异常,请联系管理员");
    }

    public static Result error(String msg) {
        return error(500, msg);
    }

    public static Result error(int code, String msg) {
        Result r = new Result();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static Result ok(Object msg) {
        Result r = new Result();
        r.put("msg", msg);
        return r;
    }


    public static Result ok(Map<String, Object> map) {
        Result r = new Result();
        r.putAll(map);
        return r;
    }

    public static Result ok() {
        return new Result();
    }

    @Override
    public Result put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}

这个包中的类存的是quartz中的job和trigger的名字常量

package com.example.demo.constant;

/**
 * @project: 郭鹏飞的博客(wwww.pengfeiguo.com)
 * @description: 常量类
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-09-04 825338623@qq.com Create 1.0
 * @copyright ©2019-2020
 */
public class GloabalConstant {
    public static final String QZ_JOB_GROUP_NAME = "JOB_GROUP_NAME";

    public static final String QZ_TRIGGER_GROUP_NAME = "TRIGGER_GROUP_NAME";

}

该包中的实体类为封装添加的job信息的实体类,大家可以根据自己的需求改成自己想要的字段

package com.example.demo.entity;


import java.util.Date;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import lombok.Data;

/**
 * @project: 郭鹏飞的博客(wwww.pengfeiguo.com)
 * @description: qzmodel。
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-09-04 825338623@qq.com Create 1.0
 * @copyright ©2019-2020
 */
@Data
public class QuartzJobModule {

    /**
     * 触发器开始时间
     */
    private Date startTime;
    /**
     * 触发器结束时间
     */
    private Date endTime;
    /**
     * job名称
     */
    private String jobName;
    /**
     * job组名
     */
    private String jobGroupName;
    /**
     * 定时器名称
     */
    private String triggerName;
    /**
     * 定时器组名
     */
    private String triggerGroupName;
    /**
     * 执行定时任务的具体操作
     */
    private Class jobClass;
    /**
     * cron表达式
     */
    private String cron;
    /**
     * job的附加信息
     */
    private JobDataMap jobDataMap = new JobDataMap();

    /**
     * 校验
     * @return
     */
    public boolean verify(){
        return !(StringUtils.isEmpty(jobName)
        || StringUtils.isEmpty(jobGroupName)
        || StringUtils.isEmpty(triggerName)
        || StringUtils.isEmpty(triggerGroupName)
        || StringUtils.isEmpty(cron)
//        || CollectionUtils.isEmpty(jobDataMap)
        || ObjectUtils.isEmpty(startTime)
        || ObjectUtils.isEmpty(endTime)
        || !ClassUtils.hasMethod(Job.class, "execute", JobExecutionContext.class)
        );
    }
}

这个里面有三个类,CronUtil.java为cron工具类,提供将时间转为cron表达式的工具;DateUtils.java为日期工具类,提供日期格式化的工具类;QuartzJobComponent.java是最重要的,提供job的CRUD操作,前两个类为封装QuartzJobModule中的属性提供的,可以自己实现,但是第三个类最好不要修改,可以直接拿来用。
CronUtil.java

package com.example.demo.utils;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;

/**
 * @project: 郭鹏飞的博客(wwww.pengfeiguo.com)
 * @description: cron生成工具类
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-09-04 825338623@qq.com Create 1.0
 * @copyright ©2019-2020
 */
public class CronUtil {

    /**
     * @param batchScheduleModel
     * @return
     * @Desc 转化cron表达式
     */
    public static String convertCronExpression(Date startDate, Date endDate, String[] weeks) {
        StringBuffer sb = new StringBuffer();
        sb.append(convertSeconds()).append(" ").append(convertMinutes(startDate)).append(" ")
            .append(convertHours(startDate, endDate)).append(" ").append(convertDay(startDate, endDate)).append(" ")
            .append(convertMonth(startDate, endDate)).append(" ").append(convertWeek(weeks));
        return sb.toString();
    }

    /**
     * 获取定时任务开始时间
     * 
     * @param batchScheduleModel
     * @return
     * @throws ParseException
     */
    public static Date getStartDate(Date startDate) throws ParseException {
        String yyyyMMddS = DateUtils.date2String(startDate, "yyyyMMdd");
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(startDate);
        int startHour = startCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                           // 12小时制,calendar.HOUR_OF_DAY 24小时)
        int startMin = startCal.get(Calendar.MINUTE);// 分
        String startTime = "2359";
        if (startHour < Integer.parseInt(startTime.substring(0, 2))) {
            startTime = startHour + "" + startMin;
        }
        return DateUtils.string2Date(yyyyMMddS + startTime + "00", "yyyyMMddHHmmss");
    }

    /**
     * 获取定时任务结束时间
     * 
     * @param batchScheduleModel
     * @return
     * @throws ParseException
     */
    public static Date getEndDate(Date endDate) throws ParseException {
        String yyyyMMddE = DateUtils.date2String(endDate, "yyyyMMdd");
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(endDate);
        int startHour = startCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                           // 12小时制,calendar.HOUR_OF_DAY 24小时)
        int startMin = startCal.get(Calendar.MINUTE);// 分
        String endTime = "0000";
        if (startHour < Integer.parseInt(endTime.substring(0, 2))) {
            endTime = startHour + "" + startMin;
        }
        return DateUtils.string2Date(yyyyMMddE + endTime + "00", "yyyyMMddHHmmss");
    }

    /**
     * 判断当前时间是否在规则时间范围内
     * 
     * @param timesEntityList
     * @return
     */
    public static boolean isInRuleTimes(Date startDate, Date endDate) {
        Date date = new Date();
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        int hour = cal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                 // 12小时制,calendar.HOUR_OF_DAY 24小时)
        int minute = cal.get(Calendar.MINUTE);// 分

        Calendar startCal = Calendar.getInstance();
        startCal.setTime(date);
        int startHour = startCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
        // 12小时制,calendar.HOUR_OF_DAY 24小时)
        int startMinute = startCal.get(Calendar.MINUTE);// 分

        Calendar endCal = Calendar.getInstance();
        endCal.setTime(date);
        int endHour = endCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                       // 12小时制,calendar.HOUR_OF_DAY 24小时)
        int endMinute = cal.get(Calendar.MINUTE);// 分
        if (startHour < hour && hour < endHour) {
            return true;
        }
        if (startHour == hour && hour == endHour && startMinute < minute && minute < endMinute) {
            return true;
        }
        if (startHour == hour && startMinute < minute) {
            return true;
        }
        if (endHour == hour && minute < endMinute) {
            return true;
        }
        return false;
    }

    /**
     * 抽取cron中的hour
     * 
     * @param batchRuleTimeEntityList
     * @return
     */
    public static String convertHours(Date startDay, Date endDay) {
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(startDay);
        int startHour = startCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                           // 12小时制,calendar.HOUR_OF_DAY 24小时)
        Calendar endCal = Calendar.getInstance();
        endCal.setTime(endDay);
        int endHour = endCal.get(Calendar.HOUR_OF_DAY);// 小时(calendar.HOUR
                                                       // 12小时制,calendar.HOUR_OF_DAY 24小时)
        StringBuffer sb = new StringBuffer();
        sb.append(startHour).append("-").append(endHour);
        return sb.toString();
    }

    /**
     * 抽取cron中的minute
     * 
     * @param batchRuleTimeEntityList
     * @return
     */
    public static String convertMinutes(Date startDay) {
        return "* ";
        /*
         * StringBuffer sb = new StringBuffer();
         * batchRuleTimeEntityList.forEach((b)->{
         * String start = b.getStartTime();
         * String end = b.getEndTime();
         * String minS = start.substring(3,5);
         * String minE = end.substring(3,5);
         * sb.append(minS).append("-").append(minE).append(",");
         * });
         * return sb.deleteCharAt(sb.length()-1).toString();
         */
    }

    /**
     * 抽取cron中的seconds
     * 
     * @return
     */
    public static String convertSeconds() {
        return "1";
    }

    /**
     * 抽取cron中的day, 在这个项目里直接返回?
     * 
     * @param startDate
     * @param endDate
     * @return
     */
    public static String convertDay(Date startDate, Date endDate) {
        return "?";
        /*
         * String start = DateUtil.formatDate(DateUtil.YYYYMMDD, startDate);
         * String end = DateUtil.formatDate(DateUtil.YYYYMMDD, endDate);
         * String dayS = start.substring(6,8);
         * String dayE = end.substring(6,8);
         * return dayS + "-" + dayE;
         */
    }

    /**
     * 抽取cron中的month
     * 
     * @param startDate
     * @param endDate
     * @return
     */
    public static String convertMonth(Date startDate, Date endDate) {
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(startDate);
        // 获取月份(因为在格里高利历和罗马儒略历一年中第一个月为JANUARY,它为0,最后一个月取决于一年中的月份数,所以这个值初始为0,所以需要加1)
        int startMonth = startCal.get(Calendar.MONTH) + 1;
        Calendar endCal = Calendar.getInstance();
        endCal.setTime(endDate);
        // 获取月份(因为在格里高利历和罗马儒略历一年中第一个月为JANUARY,它为0,最后一个月取决于一年中的月份数,所以这个值初始为0,所以需要加1)
        int endMonth = endCal.get(Calendar.MONTH) + 1;
        return startMonth + "-" + endMonth;
    }

    /**
     * 抽取cron中的week
     * 
     * @param dayOfWeeks
     * @return
     */
    public static String convertWeek(String[] dayOfWeeks) {
        StringBuffer sb = new StringBuffer();
        for (String dayOfWeek : dayOfWeeks) {
            sb.append(dayOfWeek).append(",");
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

    /**
     * 抽取cron中的week
     * 
     * @param dayOfWeeks
     * @return
     */
    public static String convertWeek(String dayOfWeeks) {
        StringBuffer sb = new StringBuffer();
        String[] split = dayOfWeeks.split(",");
        for (String str : split) {
            sb.append(str).append(",");
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

}

DateUtils.java

package com.example.demo.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.util.StringUtils;

/**
 * @project: 郭鹏飞的博客(wwww.pengfeiguo.com)
 * @description: 日期工具类
 * @version 1.0.0
 * @errorcode
 *            错误码: 错误描述
 * @author
 *         <li>2020-09-04 825338623@qq.com Create 1.0
 * @copyright ©2019-2020
 */
public class DateUtils {

    /**
     * 模式 :yyyyMMddHHmmss
     */
    private static final String YYYYMMDD_HHMMSS = "yyyyMMddHHmmss";
    /**
     * 模式 :yyyyMMdd
     */
    private static final String YYYYMMDD        = "yyyyMMdd";

    /**
     * 方法说明:日期类型按照指定格式转成字符串.
     *
     * @param date
     *            日期
     * @param pattern
     *            日期格式
     * @return
     */
    public static String date2String(Date date, String pattern) {
        if (null == date) {
            date = new Date();
        }
        if (StringUtils.isEmpty(pattern)) {
            pattern = "yyyy-MM-dd HH:mm:ss";
        }
        try {
            return getDateFormat(pattern).format(date);
        }
        catch (Exception e) {
            throw e;
        }
    }

    /**
     * 方法说明:获取指定模式pattern的SimpleDateFormat对象.
     *
     * @param pattern
     *            日期格式
     * @return
     */
    private static SimpleDateFormat getDateFormat(String pattern) {
        return new SimpleDateFormat(pattern);
    }

    /**
     * 方法说明:获取默认模式"yyyyMMdd"的SimpleDateFormat对象.
     *
     * @return
     */
    private static SimpleDateFormat getDateFormat() {
        return new SimpleDateFormat(YYYYMMDD);
    }

    /**
     * 日期转换
     *
     * @param srcStr
     *            日期字符串
     * @param pattern
     *            日期格式
     * @return
     * @throws ParseException
     */
    public static String stringFormat(String srcStr, String pattern) throws ParseException {
        Date date = string2Date(srcStr);
        return date2String(date, pattern);
    }

    /**
     * 方法说明:日期类型转成yyyyMMdd格式字符串.
     *
     * @param date
     *            日期
     * @return
     */
    public static String date2String(Date date) {
        return date2String(date, YYYYMMDD);
    }

    /**
     * 方法说明:字符串转日期类型.
     *
     * @param date
     *            日期字符串
     * @return
     * @throws ParseException
     * @throws Exception
     */
    public static Date string2Date(String date) throws ParseException {
        if (date.length() != 16) {
            return getDateFormat().parse(date);
        }
        else {
            return getDateFormat(YYYYMMDD_HHMMSS).parse(date);
        }
    }

    /**
     * 按照转换规则将日期字符串转换为Date类型的时间
     *
     * @param dateString
     *            要转换的日期字符串
     * @param format
     *            转换的格式,例如:YYYYMMDD
     * @return 转换后的Date类型的日期
     * @throws ParseException
     * @throws BusinessException
     *             异常
     */
    public static Date string2Date(String dateString, String format) throws ParseException {
        SimpleDateFormat sd1 = new SimpleDateFormat(format);
        Date date = sd1.parse(dateString);
        return date;
    }

}

由于篇幅限制,剩余内容请点击链接: https://www.blog-china.cn/blog/liuzaiqingshan/home/39/1607941061037 

分类   项目开发逻辑
字数   17891

博客标签    quartz动态任务  

评论