Quartz的分布式功能化设计
文章目录
quartz分布式自带的管理表位置:建表语句所在位置: quartz-x.y.z.jar , org.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
使用场景:所有的任务都是针对业务来的,并非公共调度平台,所以侵入式代码库是可以的。
主体功能
- 通过新增数据库管理表(SYS_QUARTZ_JOB),来明确任务基本信息
- 任务类型都是定时型;
- 支持新增任务,只能存在一个在用的同名任务;
- 支持暂停任务;
- 支持恢复暂停的任务;
- 支持更新任务,数据库管理表是更新操作,而Quartz框架是进行了先删除在重建进行更新;
- 支持删除任务;
- 支持即时执行一次任务。
实现依赖
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
- 新建Quartz任务:
addJob(@Validated @RequestBody QuartzJobDTO quartzJobDTO,HttpServletRequest req)
- 立即执行任务:
runJobNow(@NotNull(message = "jobId不能为空") Long jobId, HttpServletRequest req)
- 删除Quartz任务:
deleteJob(@NotNull(message = "jobId不能为空") Long jobId, HttpServletRequest req)
- 恢复Quartz任务(针对暂停的任务):
resumeJob(@NotBlank(message = "任务完整类名不能为空") @Size(max = 250, message = "最大长度250") String jobClassName, HttpServletRequest req)
- 暂停Quartz任务:
pauseJob(@NotBlank(message = "任务完整类名不能为空") @Size(max = 250, message = "最大长度250") String jobClassName, HttpServletRequest req)
- 查询未删除的Quartz任务 分页式:
quartzJoblist(@Validated @RequestBody QuartzJobPageDTO quartzJobPageDto)
- 更新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");
}
}