Quartz的分布式功能化设计

Quartz的分布式功能化设计

文章目录

quartz分布式自带的管理表位置:建表语句所在位置: quartz-x.y.z.jarorg.quartz.imp.jdbcjobstore路径下,这边只介绍Mysql的,文件名为 table_mysql_innodb.sql 。在sql文件中一共有11张表。

shell 复制代码
1.qrtz_blob_triggers
2.qrtz_cron_triggers
3.qrtz_simple_triggers
4.qrtz_simprop_triggers
5.qrtz_fired_triggers
6.qrtz_triggers
7.qrtz_job_details
8.qrtz_calendars
9.qrtz_paused_trigger_grps
10.qrtz_scheduler_state
11.qrtz_locks

使用场景:所有的任务都是针对业务来的,并非公共调度平台,所以侵入式代码库是可以的。

主体功能

  1. 通过新增数据库管理表(SYS_QUARTZ_JOB),来明确任务基本信息
  2. 任务类型都是定时型;
  3. 支持新增任务,只能存在一个在用的同名任务;
  4. 支持暂停任务;
  5. 支持恢复暂停的任务;
  6. 支持更新任务,数据库管理表是更新操作,而Quartz框架是进行了先删除在重建进行更新;
  7. 支持删除任务;
  8. 支持即时执行一次任务。

实现依赖

xml 复制代码
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 打war包时加入此项, 告诉spring-boot tomcat相关jar包用外部的,不要打进去 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
		<!-- 校验帮助包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!-- 任务调度quartz-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
        <!-- 数据源相关 分页插件page helper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>${pagehelper-spring-boot-starter.version}</version>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>${mapper-spring-boot-starter.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-spring-boot-starter.verson}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-connector-java.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid-spring-boot-starter.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${gooogle-guava.version}</version>
        </dependency>

springboot工程的集成quartz配置
application.properties

shell 复制代码
# quartz配置
spring.quartz.job-store-type=jdbc
spring.quartz.jdbc.schema=never
spring.quartz.properties.org.quartz.scheduler.instanceName=quartzScheduler
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
spring.quartz.properties.org.quartz.jobStore.dataSource=mysql
spring.quartz.properties.org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
spring.quartz.properties.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.jobStore.misfireThreshold=12000
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=15000
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
spring.quartz.properties.org.quartz.threadPool.threadCount=1
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true

API

  1. 新建Quartz任务:addJob(@Validated @RequestBody QuartzJobDTO quartzJobDTO,HttpServletRequest req)
  2. 立即执行任务:runJobNow(@NotNull(message = "jobId不能为空") Long jobId, HttpServletRequest req)
  3. 删除Quartz任务:deleteJob(@NotNull(message = "jobId不能为空") Long jobId, HttpServletRequest req)
  4. 恢复Quartz任务(针对暂停的任务):resumeJob(@NotBlank(message = "任务完整类名不能为空") @Size(max = 250, message = "最大长度250") String jobClassName, HttpServletRequest req)
  5. 暂停Quartz任务:pauseJob(@NotBlank(message = "任务完整类名不能为空") @Size(max = 250, message = "最大长度250") String jobClassName, HttpServletRequest req)
  6. 查询未删除的Quartz任务 分页式:quartzJoblist(@Validated @RequestBody QuartzJobPageDTO quartzJobPageDto)
  7. 更新Quartz任务(采用先删除后增加的方式处理):updateJob(@Validated @RequestBody UpdateQuartzJobDTO quartzJob, HttpServletRequest req)

例子JOB

SampleJob

新增JOB_API请求体

json 复制代码
{
    "jobClassName": "com.donny.web.quartz.jobs.SampleJob",
    "cronExpression": "0/20 * * * * ? ",
    "description":"测试简单Quartz任务,20s执行一次",
    "status": 1
}

Job记录表设计

sql 复制代码
CREATE TABLE t_sys_quartz_job
(
    `id`              BIGINT unsigned PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
    `create_time`     DATETIME         NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time`     DATETIME         NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
    `create_user`     VARCHAR(12)      NOT NULL COMMENT '创建人',
    `update_user`     VARCHAR(12)      NOT NULL COMMENT '修改人',
    `job_class_name`  VARCHAR(250)     NOT NULL COMMENT '任务的类名',
    `cron_expression` VARCHAR(250)     NULL COMMENT '任务的cron表达式',
    `description`     VARCHAR(250)     NULL COMMENT '任务的简要描述',
    `status`          tinyint unsigned NOT NULL DEFAULT '0' COMMENT '任务的状态,0:正常,1:停止',
    `is_deleted`      tinyint unsigned NOT NULL DEFAULT '0' COMMENT '任务是否已删除,0:否,1:是'
) ENGINE = InnoDB COMMENT ='quartz任务记录表';

java具体代码

DateDO

java 复制代码
package com.donny.web.model.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import javax.persistence.Column;
import java.io.Serializable;
import java.util.Date;

/**
 * 数据库基础日期字段类,主要针对数据库记录的两个日期字段
 *
 * @author donny
 * @version 1.0
 * @since 2024年01月17日 14:37
 */
@Data
public class DateDO implements Serializable {
    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(name = "create_time")
    private Date createTime;

    /**
     * 更新时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Column(name = "update_time")
    private Date updateTime;
}

OperatorDO

java 复制代码
package com.donny.web.model.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.Column;
import java.io.Serializable;

/**
 * 数据库基础字段操作者类,主要针对更新记录操作人4个字段
 *
 * @author donny
 * @version 1.0
 * @since 2024年01月17日 14:38
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class OperatorDO extends DateDO implements Serializable {
    /**
     * 创建者工号
     */
    @Column(name = "create_user")
    private String createUser;

    /**
     * 更新者工号
     */
    @Column(name = "update_user")
    private String updateUser;
}

SysQuartzJobDO

java 复制代码
package com.donny.web.model.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;
import java.io.Serializable;

/**
 * sys_quartz_job的表映射Entity
 *
 * @author donny
 * @version 1.0
 * @since 2023/12/27
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Table(name = "t_sys_quartz_job")
public class SysQuartzJobDO extends OperatorDO implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    /**
     * 任务类名
     */
    @Column(name = "job_class_name")
    private String jobClassName;

    /**
     * cron表达式
     */
    @Column(name = "cron_expression")
    private String cronExpression;

    /**
     * 描述
     */
    @Column(name = "description")
    private String description;

    /**
     * 状态 0正常 1停止
     */
    @Column(name = "status")
    private Integer status;
    /**
     * 逻辑删除标记
     */
    @Column(name = "is_deleted")
    private Integer isDeleted;

}

PageDTO

java 复制代码
package com.donny.web.model.dto;

import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;

/**
 * 分页查询的关于"页"的条件,供具体分页查询的业务DTO继承
 *
 * @author Donny
 * @version 1.0
 * @since 2023年12月27日 16:19
 */
@Setter
@Getter
public class PageDTO {

    /**
     * 当前页码
     */
    @NotNull(message = "currentPage,当前页码不能为空")
    private Integer currentPage;
    /**
     * 每页记录数
     */
    @NotNull(message = "pageSize,每页记录数不能为空")
    @Max(value = 50, message = "最大长度为50")
    private Integer pageSize;

    @Override
    public String toString() {
        return "PageDTO{" +
                "currentPage=" + currentPage +
                ", pageSize=" + pageSize +
                '}';
    }
}

QuartzJobDTO

java 复制代码
package com.donny.web.model.dto;

import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.*;

/**
 * 供api使用
 * SysQuartzJob对象可供api设置的字段集合对象
 *
 * @author Donny
 * @version 1.0
 * @since 2023/12/27
 */
@Setter
@Getter
public class QuartzJobDTO {
    /**
     * 创建者工号
     */
    private String createUser;

    /**
     * 更新者工号
     */
    private String updateUser;

    /**
     * 任务完整类名
     */
    @NotBlank(message = "jobClassName,不能为空")
    @Size(max = 250, message = "jobClassName最大长度250")
    private String jobClassName;

    /**
     * 调度周期 cron表达式
     */
    @NotBlank(message = "调度周期cron表达式不能为空")
    @Size(max = 250, message = "cronExpression最大长度250")
    private String cronExpression;

    /**
     * 任务备注信息
     */
    @Size(max = 250, message = "jobClassName最大长度250")
    private String description;

    /**
     * 状态 0正常 1停止
     */
    @NotNull(message = "status,状态不能为空")
    @Max(value = 1, message = "status不超过1")
    @Min(value = 0, message = "status不低于0")
    private Integer status;

    /**
     * 逻辑删除标记
     */
    private Integer isDeleted;

    @Override
    public String toString() {
        return "QuartzJobDTO{" +
                ", createUser='" + createUser + '\'' +
                ", updateUser='" + updateUser + '\'' +
                ", jobClassName='" + jobClassName + '\'' +
                ", cronExpression='" + cronExpression + '\'' +
                ", description='" + description + '\'' +
                ", status=" + status +
                ", isDeleted=" + isDeleted +
                '}';
    }
}

QuartzJobPageDTO

java 复制代码
package com.donny.web.model.dto;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.util.Date;

/**
 * 供api使用
 * QuartzJob列表查询条件对象
 *
 * @author Donny
 * @version 1.0
 * @since 2023年12月28日 10:24
 */
public class QuartzJobPageDTO extends PageDTO {
    /**
     * 任务类名
     */
    private String jobClassName;
    /**
     * 状态 0正常 1停止
     */
    private Integer status;
    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date startTime;
    /**
     * 修改时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date endTime;

    public String getJobClassName() {
        return jobClassName;
    }

    public void setJobClassName(String jobClassName) {
        this.jobClassName = jobClassName;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public Date getStartTime() {
        return null == startTime ? null : (Date) startTime.clone();
    }

    public void setStartTime(Date startTime) {
        this.startTime = null == startTime ? null : (Date) startTime.clone();
    }

    public Date getEndTime() {
        return null == endTime ? null : (Date) endTime.clone();
    }

    public void setEndTime(Date endTime) {
        this.endTime = null == endTime ? null : (Date) endTime.clone();
    }

    @Override
    public String toString() {
        return "QuartzJobPageDTO{" +
                "jobClassName='" + jobClassName + '\'' +
                ", status=" + status +
                ", startTime=" + startTime +
                ", endTime=" + endTime +
                ", currentPage=" + super.getCurrentPage() +
                ", pageSize=" + super.getPageSize() +
                "}";
    }
}

QuartzJobStatusEnum

java 复制代码
package com.donny.web.quartz;

/**
 * QuartzJob的Status 枚举值
 *
 * @author donny
 * @version 1.0
 * @since 2023年12月27日 16:30
 */
public enum QuartzJobStatusEnum {
    NORMAL(0),
    STOPPED(1);

    private int status;

    QuartzJobStatusEnum(int status) {
        this.status = status;
    }

    public int getValue() {
        return status;
    }

    public static String getDescription(QuartzJobStatusEnum status) {
        String description = "待设状态";
        switch (status) {
            case NORMAL:
                description = "正常";
                break;
            case STOPPED:
                description = "已停止";
                break;
            default:
                break;
        }
        return description;
    }
}

QuartzJobController

java 复制代码
package com.donny.web.controller.manager;

import com.donny.web.model.dto.QuartzJobPageDTO;
import com.donny.web.model.dto.UpdateQuartzJobDTO;
import com.donny.web.platform.pagehelper.PageBean;
import com.donny.web.model.dto.QuartzJobDTO;
import com.donny.web.model.entity.SysQuartzJobDO;
import com.donny.web.platform.ResponseBuilder;
import com.donny.web.platform.ReturnCode;
import com.donny.web.platform.WebReturnCode;
import com.donny.web.quartz.IQuartzJobService;
import com.donny.web.utils.PortalSessionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
 * quartz的管理入口
 * 主体功能:<p>
 * 通过新增数据库管理表(SYS_QUARTZ_JOB),来明确任务基本信息<p>
 * 0.任务类型都是定时型;<p>
 * 1.支持新增任务,只能存在一个在用的同名任务;<p>
 * 2.支持暂停任务;<p>
 * 3.支持恢复暂停的任务;<p>
 * 4.支持更新任务,数据库管理表是更新操作,而Quartz框架是进行了先删除在重建进行更新;<p>
 * 5.支持删除任务;<p>
 * 6.支持即时执行一次任务。
 *
 * @author Donny
 * @version 1.0
 * @since 2023/12/26
 */
@RestController
@Validated
@RequestMapping(value = "/api/v2/manager/quartz")
@Slf4j
public class QuartzJobController {
    @Resource
    IQuartzJobService quartzJobService;

    /**
     * 新建Quartz任务
     *
     * @param quartzJobDTO {@link QuartzJobDTO}
     */
    @PostMapping(value = "/addJob")
    public ResponseEntity<ReturnCode> addJob(@Validated @RequestBody QuartzJobDTO quartzJobDTO,
                                             HttpServletRequest req) {
        String user = PortalSessionUtils.getLoginUserId(req);
        quartzJobDTO.setCreateUser(user);
        quartzJobDTO.setUpdateUser(user);
        if (quartzJobService.addJob(quartzJobDTO)) {
            log.info("QuartzJob:[{}]已新增。", quartzJobDTO.getJobClassName());
            return ResponseBuilder.build(WebReturnCode.SUCCEED, "新增成功", null);
        } else {
            return ResponseBuilder.build(WebReturnCode.FAILED, "新增失败", null);
        }
    }

    /**
     * 更新Quartz任务
     */
    @PostMapping(value = "/updateJob")
    public ResponseEntity<ReturnCode> updateJob(@Validated @RequestBody UpdateQuartzJobDTO quartzJob,
                                                HttpServletRequest req) {
        quartzJob.setUpdateUser(PortalSessionUtils.getLoginUserId(req));
        if (quartzJobService.updateQuartzJob(quartzJob)) {
            log.info("QuartzJob:[{}]已更新。", quartzJob.getJobClassName());
            return ResponseBuilder.build(WebReturnCode.SUCCEED, "更新成功", null);
        } else {
            return ResponseBuilder.build(WebReturnCode.FAILED, "更新失败", null);
        }
    }

    /**
     * 查询未删除的Quartz任务 分页式
     */
    @PostMapping(value = "/joblist")
    public ResponseEntity<ReturnCode> quartzJoblist(@Validated @RequestBody QuartzJobPageDTO quartzJobPageDto) {
        PageBean pageBean = new PageBean(quartzJobPageDto.getCurrentPage(), quartzJobPageDto.getPageSize());
        return ResponseBuilder.build(WebReturnCode.SUCCEED, quartzJobService.listAll(quartzJobPageDto, pageBean));
    }

    /**
     * 暂停Quartz任务
     *
     * @param jobClassName job的类名(完成路径)
     */
    @GetMapping(value = "/pauseJob")
    public ResponseEntity<ReturnCode> pauseJob(@NotBlank(message = "任务完整类名不能为空")
                                               @Size(max = 250, message = "最大长度250")
                                               String jobClassName,
                                               HttpServletRequest req) {

        SysQuartzJobDO job = quartzJobService.findByJobClassName(jobClassName);
        if (null == job) {
            return ResponseBuilder.build(WebReturnCode.FAILED, "暂停失败,不存在该任务", null);
        }
        job.setUpdateUser(PortalSessionUtils.getLoginUserId(req));
        if (quartzJobService.pauseJob(job)) {
            log.info("QuartzJob:[{}]已暂停。", jobClassName);
            return ResponseBuilder.build(WebReturnCode.SUCCEED, "暂停成功", null);
        } else {
            return ResponseBuilder.build(WebReturnCode.FAILED, "暂停失败", null);
        }
    }

    /**
     * 恢复Quartz任务
     *
     * @param jobClassName job的类名(完整路径)
     */
    @GetMapping(value = "/resumeJob")
    public ResponseEntity<ReturnCode> resumeJob(@NotBlank(message = "任务完整类名不能为空")
                                                @Size(max = 250, message = "最大长度250")
                                                String jobClassName,
                                                HttpServletRequest req) {

        SysQuartzJobDO job = quartzJobService.findByJobClassName(jobClassName);
        if (null == job) {
            return ResponseBuilder.build(WebReturnCode.FAILED, "恢复失败,不存在该任务", null);
        }
        job.setUpdateUser(PortalSessionUtils.getLoginUserId(req));
        if (quartzJobService.resumeJob(job)) {
            log.info("QuartzJob:[{}]已恢复。", jobClassName);
            return ResponseBuilder.build(WebReturnCode.SUCCEED, "恢复成功", null);
        } else {
            return ResponseBuilder.build(WebReturnCode.FAILED, "恢复失败", null);
        }
    }


    /**
     * 逻辑删除Quartz任务
     *
     * @param jobId Quartz任务的主键
     */
    @GetMapping(value = "/deleteJob")
    public ResponseEntity<ReturnCode> deleteJob(@NotNull(message = "jobId不能为空")
                                                Long jobId,
                                                HttpServletRequest req) {
        SysQuartzJobDO quartzJob = quartzJobService.getJobById(jobId);
        if (quartzJob == null) {
            return ResponseBuilder.build(WebReturnCode.SUCCEED, "不存在该任务", null);
        }
        quartzJob.setUpdateUser(PortalSessionUtils.getLoginUserId(req));
        if (quartzJobService.logicDeleteAndStopJob(quartzJob)) {
            log.info("QuartzJob:[{}]已删除。", quartzJob.getJobClassName());
            return ResponseBuilder.build(WebReturnCode.SUCCEED, "删除成功", null);
        } else {
            return ResponseBuilder.build(WebReturnCode.FAILED, "删除失败", null);
        }
    }

    /**
     * 立即尝试执行一个任务
     *
     * @param jobId Quartz任务的主键
     */
    @GetMapping(value = "/runJobNow")
    public ResponseEntity<ReturnCode> runJobNow(@NotNull(message = "jobId不能为空")
                                                Long jobId,
                                                HttpServletRequest req) {
        SysQuartzJobDO quartzJob = quartzJobService.getJobById(jobId);
        if (quartzJob == null) {
            return ResponseBuilder.build(WebReturnCode.FAILED, "不存在该任务", null);
        }
        if (quartzJobService.runJobNow(quartzJob)) {

            log.info("QuartzJob:[{}]已执行。操作人[{}]。", quartzJob.getJobClassName(), PortalSessionUtils.getLoginUserId(req));
            return ResponseBuilder.build(WebReturnCode.SUCCEED, "执行成功", null);
        } else {
            return ResponseBuilder.build(WebReturnCode.FAILED, "执行失败", null);
        }
    }

    /**
     * 根据主键查询任务信息
     *
     * @param id {@link UpdateQuartzJobDTO#getId()}
     */
    @GetMapping(value = "/get")
    public ResponseEntity<ReturnCode> getQuartzJob(@NotNull(message = "id不能为空")
                                                   Long id) {
        try {
            return ResponseBuilder.build(WebReturnCode.SUCCEED, this.quartzJobService.getJobById(id));
        } catch (Exception e) {
            log.error("查询失败:" + e.getMessage(), e);
            return ResponseBuilder.build(WebReturnCode.FAILED, "查询失败:" + e.getMessage(), null);
        }
    }
}

IQuartzJobService

java 复制代码
package com.donny.web.quartz;

import com.donny.web.model.dto.QuartzJobDTO;
import com.donny.web.model.dto.QuartzJobPageDTO;
import com.donny.web.model.entity.SysQuartzJobDO;
import com.donny.web.platform.pagehelper.PageBean;
import com.donny.web.platform.pagehelper.PageResult;

/**
 * QuartzJob 服务层接口
 *
 * @author donny
 * @version 1.0
 * @since 2023/12/26
 */
public interface IQuartzJobService {

    /**
     * 通过任务类名查询任务
     *
     * @param jobClassName 任务的
     */
    SysQuartzJobDO findByJobClassName(String jobClassName);

    /**
     * 通过任务id查询任务
     *
     * @param id 任务的数据库主键
     */
    SysQuartzJobDO getJobById(Long id);

    /**
     * 查询未删除的Quartz任务 分页式
     */
    PageResult<SysQuartzJobDO> listAll(QuartzJobPageDTO quartzJobPageDto, PageBean pageBean);

    /**
     * 新增任务
     *
     * @param quartzJob {@link QuartzJobDTO }
     */
    boolean addJob(QuartzJobDTO quartzJob);

    /**
     * 更新任务
     *
     * @param quartzJob {@link QuartzJobDTO }
     */
    boolean updateQuartzJob(QuartzJobDTO quartzJob);

    /**
     * 逻辑删除任务
     *
     * @param quartzJob {@link SysQuartzJobDO }
     */
    boolean logicDeleteAndStopJob(SysQuartzJobDO quartzJob);

    /**
     * 暂停任务
     *
     * @param quartzJob {@link SysQuartzJobDO }
     */
    boolean pauseJob(SysQuartzJobDO quartzJob);

    /**
     * 恢复任务
     *
     * @param quartzJob {@link SysQuartzJobDO }
     */
    boolean resumeJob(SysQuartzJobDO quartzJob);

    /**
     * 立即执行任务
     *
     * @param quartzJob {@link SysQuartzJobDO }
     */
    boolean runJobNow(SysQuartzJobDO quartzJob);
}

QuartzJobServiceImpl

java 复制代码
package com.donny.web.quartz;

import com.github.pagehelper.Page;
import com.github.pagehelper.page.PageMethod;
import com.donny.web.dao.mysql.QuartzJobMapper;
import com.donny.web.model.dto.QuartzJobDTO;
import com.donny.web.model.dto.QuartzJobPageDTO;
import com.donny.web.model.dto.UpdateQuartzJobDTO;
import com.donny.web.model.entity.SysQuartzJobDO;
import com.donny.web.platform.exception.BusinessException;
import com.donny.web.platform.pagehelper.PageBean;
import com.donny.web.platform.pagehelper.PageResult;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * QuartzJob 服务层实现
 *
 * @author donny
 * @version 1.0
 * @since 2023/12/26
 */
@Service
public class QuartzJobServiceImpl implements IQuartzJobService {

    private static final Logger LOGGER = LoggerFactory.getLogger(QuartzJobServiceImpl.class);

    @Resource
    private Scheduler scheduler;

    private final QuartzJobMapper quartzJobMapper;

    @Autowired
    public QuartzJobServiceImpl(QuartzJobMapper quartzJobMapper) {
        this.quartzJobMapper = quartzJobMapper;
    }

    @Override
    public SysQuartzJobDO findByJobClassName(String jobClassName) {
        return this.quartzJobMapper.findByJobClassName(jobClassName);
    }

    @Override
    public SysQuartzJobDO getJobById(Long id) {
        return this.quartzJobMapper.getJobById(id);
    }

    @Override
    public PageResult<SysQuartzJobDO> listAll(QuartzJobPageDTO quartzJobPageDto, PageBean pageBean) {
        try (Page<SysQuartzJobDO> page = PageMethod.startPage(pageBean.getCurrentPage(), pageBean.getPageSize())) {
            this.quartzJobMapper.listAll(quartzJobPageDto);
            return new PageResult<>(page.getResult(), page.getPageNum(), page.getPageSize(), page.getTotal());
        }
    }

    @Override
    public boolean addJob(QuartzJobDTO quartzJob) {
        SysQuartzJobDO result = this.quartzJobMapper.findByJobClassName(quartzJob.getJobClassName().trim());
        if (null != result) {
            return false;
        }
        schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim());
        int count = this.quartzJobMapper.insert(quartzJob);
        return count == 1;
    }

    /**
     * [Quartz框架] 添加定时任务
     */
    private void schedulerAdd(String jobClassName, String cronExpression) {
        try {
            // 启动调度器
            this.scheduler.start();
            // 构建job信息
            JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobClassName).build();
            // 表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            // 按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName).withSchedule(scheduleBuilder).build();
            this.scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            LOGGER.warn("[Quartz Scheduler] 创建定时任务失败" + e.getMessage(), e);
            throw new BusinessException("[Quartz Scheduler] 创建定时任务失败", e);
        }
    }

    private static Job getClass(String classname) {
        try {
            Class<?> class1 = Class.forName(classname);
            return (Job) class1.newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            LOGGER.warn("[Quartz Scheduler] getClass获取对应类实例失败" + e.getMessage(), e);
            throw new BusinessException("[Quartz Scheduler] getClass获取对应类实例失败");
        }
    }

    /**
     * [Quartz框架]删除定时任务
     */
    private void schedulerDelete(String jobClassName) {
        try {
            /*使用给定的键暂停Trigger 。*/
            this.scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName));
            /*从调度程序中删除指示的Trigger */
            this.scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName));
            /*从 Scheduler 中删除已识别的Job - 以及任何关联的Trigger */
            this.scheduler.deleteJob(JobKey.jobKey(jobClassName));
        } catch (SchedulerException e) {
            LOGGER.warn("[Quartz Scheduler] 删除定时任务失败" + e.getMessage(), e);
            throw new BusinessException("[Quartz Scheduler] 删除定时任务失败");
        }
    }

    @Override
    public boolean updateQuartzJob(QuartzJobDTO quartzJob) throws BusinessException {
        try {
            schedulerDelete(quartzJob.getJobClassName().trim());
            schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim());
            if (QuartzJobStatusEnum.NORMAL.getValue() != quartzJob.getStatus()) {
                this.scheduler.pauseJob(JobKey.jobKey(quartzJob.getJobClassName().trim()));
            }
        } catch (SchedulerException e) {
            LOGGER.warn("[Quartz Scheduler] 更新定时任务失败" + e.getMessage(), e);
            throw new BusinessException("[Quartz Scheduler] 更新定时任务失败");
        }

        int count = this.quartzJobMapper.update(quartzJob);
        return count == 1;
    }

    @Override
    public boolean logicDeleteAndStopJob(SysQuartzJobDO quartzJob) {
        schedulerDelete(quartzJob.getJobClassName().trim());
        int count = this.quartzJobMapper.logicDelete(quartzJob.getId(), quartzJob.getUpdateUser());
        return count == 1;
    }

    @Override
    public boolean pauseJob(SysQuartzJobDO quartzJob) throws BusinessException {
        try {
            this.scheduler.pauseJob(JobKey.jobKey(quartzJob.getJobClassName().trim()));
        } catch (SchedulerException e) {
            LOGGER.warn("[Quartz Scheduler] 暂停定时任务失败" + e.getMessage(), e);
            throw new BusinessException("[Quartz Scheduler] 暂停定时任务失败");
        }
        UpdateQuartzJobDTO quartzJobDto = new UpdateQuartzJobDTO();
        quartzJobDto.setId(quartzJob.getId());
        quartzJobDto.setStatus(QuartzJobStatusEnum.STOPPED.getValue());
        int count = this.quartzJobMapper.update(quartzJobDto);
        return count == 1;
    }

    @Override
    public boolean resumeJob(SysQuartzJobDO quartzJob) throws BusinessException {
        try {
            this.scheduler.resumeJob(JobKey.jobKey(quartzJob.getJobClassName().trim()));
        } catch (SchedulerException e) {
            LOGGER.warn("[Quartz Scheduler] 恢复定时任务失败" + e.getMessage(), e);
            throw new BusinessException("[Quartz Scheduler] 恢复定时任务失败");
        }
        UpdateQuartzJobDTO quartzJobDto = new UpdateQuartzJobDTO();
        quartzJobDto.setId(quartzJob.getId());
        quartzJobDto.setStatus(QuartzJobStatusEnum.NORMAL.getValue());
        quartzJobDto.setUpdateUser(quartzJob.getUpdateUser());
        int count = this.quartzJobMapper.update(quartzJobDto);
        return count == 1;
    }

    @Override
    public boolean runJobNow(SysQuartzJobDO quartzJob) {
        boolean flag = true;
        try {
            JobKey jobKey = JobKey.jobKey(quartzJob.getJobClassName().trim());
            this.scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            flag = false;
            LOGGER.warn("[Quartz Scheduler] 尝试执行任务失败" + e.getMessage(), e);
        }
        return flag;
    }
}

QuartzJobMapper

java 复制代码
package com.donny.web.dao.mysql;

import com.donny.web.model.dto.QuartzJobDTO;
import com.donny.web.model.dto.QuartzJobPageDTO;
import com.donny.web.model.entity.SysQuartzJobDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author donny
 * @version 1.0
 * @since 2023/12/26
 */
@Repository
public interface QuartzJobMapper {

    int insert(@Param("quartzJob") QuartzJobDTO quartzJob);

    int update(@Param("quartzJob") QuartzJobDTO quartzJob);

    int logicDelete(@Param("id") Long id, @Param("updateUser") String updateUser);

    int physicalDelete(@Param("id") Long id);

    /**
     * 根据job的ID主键查询未删除的任务
     *
     * @param id job的ID主键
     */
    SysQuartzJobDO getJobById(@Param("id") Long id);

    /**
     * 根据jobClassName查询存在的正常的未删除的job
     *
     * @param jobClassName job的类名(完成路径)
     */
    SysQuartzJobDO findByJobClassName(@Param("jobClassName") String jobClassName);

    /**
     * 查询未删除的
     *
     * @param quartzJobPageDto 查询条件对象
     */
    List<SysQuartzJobDO> listAll(@Param("quartzJobPageDto") QuartzJobPageDTO quartzJobPageDto);
}

QuartzJobMapper.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.donny.web.dao.mysql.QuartzJobMapper">
    <!--定义查询数据库时,表字段与java对象成员变量名的对应关系-->
    <resultMap type="com.donny.web.model.entity.SysQuartzJobDO" id="quartzJobMap">
        <id column="id" property="id"/>
        <result column="job_class_name" property="jobClassName"/>
        <result column="cron_expression" property="cronExpression"/>
        <result column="description" property="description"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="create_time" property="createTime"/>
        <result column="update_time" property="updateTime"/>
        <result column="create_user" property="createUser"/>
        <result column="update_user" property="updateUser"/>
    </resultMap>

    <insert id="insert" parameterType="com.donny.web.model.dto.QuartzJobDTO">
        insert into t_sys_quartz_job(create_user, update_user, job_class_name, cron_expression, `description`)
        values (#{quartzJob.createUser},
                #{quartzJob.updateUser},
                #{quartzJob.jobClassName},
                #{quartzJob.cronExpression},
                #{quartzJob.description})
    </insert>

    <update id="update" parameterType="com.donny.web.model.dto.QuartzJobDTO">
        update t_sys_quartz_job sqj
        <set>
            <if test="quartzJob.cronExpression != null and quartzJob.cronExpression != ''">
                sqj.cron_expression = #{quartzJob.cronExpression},
            </if>
            <if test="quartzJob.description != null and quartzJob.description != ''">
                sqj.`description` = #{quartzJob.description},
            </if>
            <if test="quartzJob.isDeleted != null and quartzJob.isDeleted != ''">
                sqj.is_deleted = #{quartzJob.isDeleted},
            </if>
            <if test="quartzJob.status != null">
                sqj.`status` = #{quartzJob.status},
            </if>
            <if test="quartzJob.updateUser != null and quartzJob.updateUser != ''">
                sqj.update_user = #{quartzJob.updateUser},
            </if>
            sqj.update_time = NOW()
        </set>
        where sqj.id = #{quartzJob.id}
    </update>

    <update id="logicDelete">
        update t_sys_quartz_job sqj
        <set>
            sqj.update_time = NOW(),
            sqj.update_user = #{updateUser},
            sqj.is_deleted = 1
        </set>
        where sqj.id = #{id}
    </update>

    <delete id="physicalDelete">
        delete
        from t_sys_quartz_job
        where id = #{id}
    </delete>

    <select id="findByJobClassName" resultMap="quartzJobMap">
        select sqj.id,
        create_time, update_time, create_user, update_user,
        job_class_name, cron_expression, `description`, `status`, is_deleted
        from t_sys_quartz_job sqj
        <where>
            sqj.is_deleted = 0
            <if test="jobClassName != null and jobClassName != ''">
                and sqj.job_class_name=#{jobClassName}
            </if>
        </where>
    </select>

    <select id="listAll" parameterType="com.donny.web.model.dto.QuartzJobPageDTO" resultMap="quartzJobMap">
        select sqj.id,
        create_time, update_time, create_user, update_user,
        job_class_name, cron_expression, `description`, `status`, is_deleted
        from t_sys_quartz_job sqj
        <where>
            sqj.is_deleted=0
            <if test="quartzJobPageDto.jobClassName != null and quartzJobPageDto.jobClassName != ''">
                and sqj.job_class_name LIKE CONCAT(#{quartzJobPageDto.jobClassName}, '%')
            </if>
            <if test="quartzJobPageDto.status != null">
                and sqj.`status` = #{quartzJobPageDto.status}
            </if>
            <if test="quartzJobPageDto.startTime != null">
                <![CDATA[ and sqj.create_time >= #{quartzJobPageDto.startTime} ]]>
            </if>
            <if test="quartzJobPageDto.endTime != null">
                <![CDATA[ and sqj.create_time <= #{quartzJobPageDto.endTime} ]]>
            </if>
        </where>
        order by sqj.id desc
    </select>

    <select id="getJobById" resultMap="quartzJobMap">
        select sqj.id,
               create_time,
               update_time,
               create_user,
               update_user,
               job_class_name,
               cron_expression,
               `description`,
               `status`,
               is_deleted
        from t_sys_quartz_job sqj
        where sqj.id = #{id}
          and sqj.is_deleted = 0
    </select>

</mapper>

SampleJob

java 复制代码
package com.donny.web.quartz.jobs;

import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;


/**
 * @author donny
 * @version 1.0
 * @since 2023年12月27日 15:58
 */
@Slf4j
public class SampleJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info("SampleJob is execute");
    }
}
相关推荐
七夜zippoe1 小时前
分布式系统实战经验
java·分布式
nomi-糯米2 小时前
Fisco Bcos 2.11.0配置console控制台2.10.0及部署调用智能合约
分布式·网络安全·区块链·智能合约·分布式账本
喜欢猪猪2 小时前
Kafka是如何保证数据的安全性、可靠性和分区的
分布式·kafka
芊言芊语2 小时前
分布式消息服务Kafka版的详细解析和配置方式
分布式·kafka
Alluxio2 小时前
选择Alluxio来解决AI模型训练场景数据访问的五大理由
大数据·人工智能·分布式·ai·语言模型
武子康3 小时前
大数据-133 - ClickHouse 基础概述 全面了解
java·大数据·分布式·clickhouse·flink·spark
.生产的驴3 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
人生百态,人生如梦4 小时前
大数据处理从零开始————3.Hadoop伪分布式和分布式搭建
hadoop·分布式
芊言芊语5 小时前
分布式缓存服务Redis版解析与配置方式
redis·分布式·缓存
月夜星辉雪9 小时前
【RabbitMQ 项目】服务端:路由交换模块
分布式·rabbitmq