当前位置: 首页 > news >正文

Quartz的分布式功能化设计

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.jarorg.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

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

主体功能

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

实现依赖

		<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

  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请求体

{"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");}
}

相关文章:

Quartz的分布式功能化设计

Quartz的分布式功能化设计 文章目录 Quartz的分布式功能化设计主体功能实现依赖API例子JOBJob记录表设计java具体代码DateDOOperatorDOSysQuartzJobDOPageDTOQuartzJobDTOQuartzJobPageDTOQuartzJobStatusEnumQuartzJobControllerIQuartzJobServiceQuartzJobServiceImplQuartzJ…...

Caffeine缓存

本地缓存基于本地环境的内存&#xff0c;访问速度非常快&#xff0c;对于一些变更频率低、实时性要求低的数据&#xff0c;可以放在本地缓存中&#xff0c;提升访问速度 使用本地缓存能够减少和Redis类的远程缓存间的数据交互&#xff0c;减少网络 I/O 开销&#xff0c;降低这…...

AI辅助研发正在成为造福人类的新生科技力量

目录 1.AI用于药物研发 &#xff08;1&#xff09;药物靶点预测&#xff1a; &#xff08;2&#xff09;药物分子设计&#xff1a; &#xff08;3&#xff09;药物筛选&#xff1a; &#xff08;4&#xff09;药效和安全性预测&#xff1a; &#xff08;5&#xff09…...

程序分享--排序算法--归并排序

关注我&#xff0c;持续分享逻辑思维&管理思维&#xff1b; 可提供大厂面试辅导、及定制化求职/在职/管理/架构辅导&#xff1b; 有意找工作的同学&#xff0c;请参考博主的原创&#xff1a;《面试官心得--面试前应该如何准备》&#xff0c;《面试官心得--面试时如何进行自…...

pg数据库和mysql区别

区别一 PostgreSQL (通常称为 PG) 和 MySQL 都是广泛使用的关系型数据库管理系统 (RDBMS)。虽然它们都是用于存储和管理数据的关系数据库&#xff0c;但它们在一些方面有很大的区别&#xff0c;如下所述&#xff1a; 数据类型&#xff1a;PostgreSQL 支持更多的数据类型&#…...

Jetpack Compose 动画正式开始学习

1. 简单值动画 //将一个Color简单值 从一个值 变化到另一个 另一个简单值 就用 animateColorAsStateval backgroundColor by animateColorAsState(if (tabPage TabPage.Home) Purple100 else Green300) 动画其实就是 一个状态不停发生改变导致 组件不断重组产生的过程 2.…...

iOS 17.4报错: libopencore-amrnb.a[arm64]

iOS 17.4报错&#xff1a; libopencore-amrnb.a[arm64] iOS 17.4 模拟器运行报错解决方案 iOS 17.4 模拟器运行报错 Building for ‘iOS-simulator’, but linking in object file (/XXX/lib/libopencore-amrnb.a[arm64]2) built for ‘iOS’ 解决方案 在Podfile里添加如下设…...

鼓楼夜市管理wpf+sqlserver

鼓楼夜市管理系统wpfsqlserver 下载地址:鼓楼夜市管理系统wpfsqlserver 说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于C#wpf架构和sql server数据库 功能模块&#xff1a; 登录注册 鼓楼夜市管理系统主界面所有店铺信…...

【五、接口自动化测试】5分钟掌握python + requests接口测试

你好啊&#xff01;我是山茶&#xff0c;一个持续探索AI 测试的程序员&#xff01; 在做接口测试时&#xff0c;在python中内置了HTTP库 urllib&#xff0c;可以用于发送http请求。基于urllib二次封装的三方库Requests&#xff0c;相较于urllib更佳简介易用。所以&#xff0c;…...

双边市场的基本理论

双边市场由两个不同类型的用户组成&#xff0c;通过一个中介机构或平台进行交易&#xff0c;其中一边用户的决策会影响另一边用户的结果。这种影响被称为间接网络外部性&#xff0c;它导致了平台在吸引和平衡两边用户时面临的挑战。 平台定价在双边市场中成为核心问题&#xf…...

R统计学2 - 数据分析入门问题21-40

往期R统计学文章&#xff1a; R统计学1 - 基础操作入门问题1-20 21. 如何对矩阵按行 (列) 作计算&#xff1f; 使用函数 apply() vec 1:20 # 转换为矩阵 mat matrix (vec , ncol4) # [,1] [,2] [,3] [,4] # [1,] 1 6 11 16 # [2,] 2 7 12 17 # [3,] …...

蓝桥杯2023年-买瓜(dfs,类型转换同样耗时)

题目描述 小蓝正在一个瓜摊上买瓜。瓜摊上共有 n 个瓜&#xff0c;每个瓜的重量为 Ai 。 小蓝刀功了得&#xff0c;他可以把任何瓜劈成完全等重的两份&#xff0c;不过每个瓜只能劈一刀。 小蓝希望买到的瓜的重量的和恰好为 m 。 请问小蓝至少要劈多少个瓜才能买到重量恰好…...

生成式人工智能服务安全基本要求实务解析

本文尝试明晰《基本要求》的出台背景与实践定位&#xff0c;梳理《基本要求》所涉的各类安全要求&#xff0c;以便为相关企业遵循执行《基本要求》提供抓手。 引言 自2022年初以来&#xff0c;我国陆续发布算法推荐、深度合成与生成式人工智能服务相关的规范文件&#xff0c;…...

nginx详解,配置http,https,负载均衡,反向代理,SMTP 代理步骤说明

Nginx 是一款高性能的开源 Web 服务器,同时也可以用作反向代理服务器、负载均衡器、HTTP 缓存、HTTPS 中继、以及作为邮件代理服务器等。以下是 Nginx 可以实现的一些常见用途: 静态内容服务: Nginx 可以用来提供静态内容,比如 HTML、CSS、JavaScript 文件等。 动态内容服务…...

ARTS Week 20

Algorithm 本周的算法题为 1222. 可以攻击国王的皇后 在一个 下标从 0 开始 的 8 x 8 棋盘上&#xff0c;可能有多个黑皇后和一个白国王。 给你一个二维整数数组 queens&#xff0c;其中 queens[i] [xQueeni, yQueeni] 表示第 i 个黑皇后在棋盘上的位置。还给你一个长度为 2 的…...

python如何读取文件

这里的文件是txt文件&#xff0c;office文件不支持。 假如有一个pi_digits的txt文件&#xff0c;里面的内容是“3.1415926” 如果要读取这个文件的内容&#xff0c;需要调取pathlib模块&#xff0c;并把路径告知python。同时python文件必须要和目标读取文件在一个文件夹里。 …...

InnoDB和MyISAM存储引擎

InnoDB mysql默认存储引擎 支持事务&#xff0c;行级锁&#xff08;并发量大&#xff09;&#xff0c;外键约束&#xff0c;容量大&#xff0c;支持缓存&#xff0c;支撑主键自增&#xff0c; 全文检索&#xff0c;不存储表的总行数&#xff0c;需要sql逐行统计 MyISAM 不…...

DataGrip 2023:让数据库开发变得更简单、更高效 mac/win

JetBrains DataGrip 2023是一款功能强大的数据库IDE&#xff0c;专为数据库开发和管理而设计。通过DataGrip&#xff0c;您可以连接到各种关系型数据库管理系统(RDBMS)&#xff0c;并使用其提供的一组工具来查询、管理、编辑和开发数据库。 DataGrip 2023软件获取 DataGrip 2…...

突破编程_C++_设计模式(命令模式)

1 命令模式的基本概念 C 命令模式是一种设计模式&#xff0c;它允许将请求封装为一个对象&#xff0c;从而可以用不同的请求对客户进行参数化&#xff1b;对请求排队或记录请求日志&#xff0c;以及支持可撤销的操作。命令模式的主要目的是将请求封装为对象&#xff0c;从而可…...

LeetCode102题:二叉树的层序遍历(python3)

代码思路&#xff1a;使用队列先进先出的特性&#xff0c;queue[]不为空进入for循环&#xff0c;tmp存储每层的节点&#xff0c;将结果添加至res[]中。 python中使用collections中的双端队列deque()&#xff0c;其popleft()方法可达到O(1)时间复杂度。 class Solution:def lev…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

Redis:现代应用开发的高效内存数据存储利器

一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发&#xff0c;其初衷是为了满足他自己的一个项目需求&#xff0c;即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源&#xff0c;Redis凭借其简单易用、…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)

UniApp 集成腾讯云 IM 富媒体消息全攻略&#xff08;地理位置/文件&#xff09; 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型&#xff0c;核心实现方式&#xff1a; 标准消息类型&#xff1a;直接使用 SDK 内置类型&#xff08;文件、图片等&#xff09;自…...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用

前言&#xff1a;我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM&#xff08;Java Virtual Machine&#xff09;让"一次编写&#xff0c;到处运行"成为可能。这个软件层面的虚拟化让我着迷&#xff0c;但直到后来接触VMware和Doc…...

《Offer来了:Java面试核心知识点精讲》大纲

文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...

小智AI+MCP

什么是小智AI和MCP 如果还不清楚的先看往期文章 手搓小智AI聊天机器人 MCP 深度解析&#xff1a;AI 的USB接口 如何使用小智MCP 1.刷支持mcp的小智固件 2.下载官方MCP的示例代码 Github&#xff1a;https://github.com/78/mcp-calculator 安这个步骤执行 其中MCP_ENDPOI…...