Quartz的分布式功能化设计
文章目录
- Quartz的分布式功能化设计
- 主体功能
- 实现依赖
- API
- 例子JOB
- Job记录表设计
- java具体代码
- `DateDO`
- `OperatorDO`
- `SysQuartzJobDO`
- `PageDTO`
- `QuartzJobDTO`
- `QuartzJobPageDTO`
- `QuartzJobStatusEnum`
- `QuartzJobController`
- `IQuartzJobService`
- `QuartzJobServiceImpl`
- `QuartzJobMapper`
- `QuartzJobMapper.xml`
- `SampleJob`
quartz分布式自带的管理表位置:建表语句所在位置: quartz-x.y.z.jar,
org.quartz.imp.jdbcjobstore
路径下,这边只介绍Mysql的,文件名为
table_mysql_innodb.sql。在sql文件中一共有11张表。
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框架是进行了先删除在重建进行更新;
- 支持删除任务;
- 支持即时执行一次任务。
实现依赖
<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
# 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请求体
{"jobClassName": "com.donny.web.quartz.jobs.SampleJob","cronExpression": "0/20 * * * * ? ","description":"测试简单Quartz任务,20s执行一次","status": 1
}
Job记录表设计
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
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
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
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
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;@Overridepublic String toString() {return "PageDTO{" +"currentPage=" + currentPage +", pageSize=" + pageSize +'}';}
}
QuartzJobDTO
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;@Overridepublic String toString() {return "QuartzJobDTO{" +", createUser='" + createUser + '\'' +", updateUser='" + updateUser + '\'' +", jobClassName='" + jobClassName + '\'' +", cronExpression='" + cronExpression + '\'' +", description='" + description + '\'' +", status=" + status +", isDeleted=" + isDeleted +'}';}
}
QuartzJobPageDTO
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();}@Overridepublic String toString() {return "QuartzJobPageDTO{" +"jobClassName='" + jobClassName + '\'' +", status=" + status +", startTime=" + startTime +", endTime=" + endTime +", currentPage=" + super.getCurrentPage() +", pageSize=" + super.getPageSize() +"}";}
}
QuartzJobStatusEnum
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
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 {@ResourceIQuartzJobService 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
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
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);@Resourceprivate Scheduler scheduler;private final QuartzJobMapper quartzJobMapper;@Autowiredpublic QuartzJobServiceImpl(QuartzJobMapper quartzJobMapper) {this.quartzJobMapper = quartzJobMapper;}@Overridepublic SysQuartzJobDO findByJobClassName(String jobClassName) {return this.quartzJobMapper.findByJobClassName(jobClassName);}@Overridepublic SysQuartzJobDO getJobById(Long id) {return this.quartzJobMapper.getJobById(id);}@Overridepublic 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());}}@Overridepublic 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表达式构建一个新的triggerCronTrigger 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] 删除定时任务失败");}}@Overridepublic 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;}@Overridepublic boolean logicDeleteAndStopJob(SysQuartzJobDO quartzJob) {schedulerDelete(quartzJob.getJobClassName().trim());int count = this.quartzJobMapper.logicDelete(quartzJob.getId(), quartzJob.getUpdateUser());return count == 1;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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
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 version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//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">deletefrom t_sys_quartz_jobwhere 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_deletedfrom 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_deletedfrom 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_deletedfrom t_sys_quartz_job sqjwhere sqj.id = #{id}and sqj.is_deleted = 0</select></mapper>
SampleJob
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 {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {log.info("SampleJob is execute");}
}