1. Quartz简介

  Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目。

  Quartz是一个完全由Java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制。

  Quartz可以与J2EE与J2SE应用程序相结合也可以单独使用。

  Quartz允许程序开发人员根据时间的间隔来调度作业。

  Quartz实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。

  Quartz官网:http://www.quartz-scheduler.org/

2. Quartz核心概念

  • Job

      Job表示一个工作,要执行的具体内容。
  • JobDetail

      JobDetail表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail还包含了这个任务调度的方案和策略。
  • Trigger

      Trigger代表一个调度参数的配置,什么时候去调。
  • Scheduler

      Scheduler代表一个调度容器,一个调度容器中可以注册多个JobDetail和Trigger。当Trigger与JobDetail组合,就可以被Scheduler容器调度了。

3. 初始化数据库

  Quartz采用持久化到数据库方式,需要创建官网提供的11张表。因此,可以在官网下载对应的版本,根据路径src\org\quartz\impl\jdbcjobstore找到对应数据库类型的脚本,例如Mysql为:tables_mysql.sql

  Mysql相关的表及系统需要的表脚本如下,请先创建数据库:quartzdemo,并初始化数据库表结构及数据。

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0; -- ----------------------------
-- Table structure for t_qrtz_blob_triggers
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_blob_triggers`;
CREATE TABLE `t_qrtz_blob_triggers` (
`sched_name` varchar(120) NOT NULL,
`trigger_name` varchar(190) NOT NULL,
`trigger_group` varchar(190) NOT NULL,
`blob_data` blob NULL,
PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
INDEX `sched_name`(`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
CONSTRAINT `t_qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `t_qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT
); -- ----------------------------
-- Records of t_qrtz_blob_triggers
-- ---------------------------- -- ----------------------------
-- Table structure for t_qrtz_calendars
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_calendars`;
CREATE TABLE `t_qrtz_calendars` (
`sched_name` varchar(120) NOT NULL,
`calendar_name` varchar(190) NOT NULL,
`calendar` blob NOT NULL,
PRIMARY KEY (`sched_name`, `calendar_name`) USING BTREE
); -- ----------------------------
-- Records of t_qrtz_calendars
-- ---------------------------- -- ----------------------------
-- Table structure for t_qrtz_cron_triggers
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_cron_triggers`;
CREATE TABLE `t_qrtz_cron_triggers` (
`sched_name` varchar(120) NOT NULL,
`trigger_name` varchar(190) NOT NULL,
`trigger_group` varchar(190) NOT NULL,
`cron_expression` varchar(120) NOT NULL,
`time_zone_id` varchar(80) NULL DEFAULT NULL,
PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
CONSTRAINT `t_qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `t_qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT
); -- ----------------------------
-- Records of t_qrtz_cron_triggers
-- ---------------------------- -- ----------------------------
-- Table structure for t_qrtz_fired_triggers
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_fired_triggers`;
CREATE TABLE `t_qrtz_fired_triggers` (
`sched_name` varchar(120) NOT NULL,
`entry_id` varchar(95) NOT NULL,
`trigger_name` varchar(190) NOT NULL,
`trigger_group` varchar(190) NOT NULL,
`instance_name` varchar(190) NOT NULL,
`fired_time` bigint(0) NOT NULL,
`sched_time` bigint(0) NOT NULL,
`priority` int(0) NOT NULL,
`state` varchar(16) NOT NULL,
`job_name` varchar(190) NULL DEFAULT NULL,
`job_group` varchar(190) NULL DEFAULT NULL,
`is_nonconcurrent` varchar(1) NULL DEFAULT NULL,
`requests_recovery` varchar(1) NULL DEFAULT NULL,
PRIMARY KEY (`sched_name`, `entry_id`) USING BTREE,
INDEX `idx_qrtz_ft_trig_inst_name`(`sched_name`, `instance_name`) USING BTREE,
INDEX `idx_qrtz_ft_inst_job_req_rcvry`(`sched_name`, `instance_name`, `requests_recovery`) USING BTREE,
INDEX `idx_qrtz_ft_j_g`(`sched_name`, `job_name`, `job_group`) USING BTREE,
INDEX `idx_qrtz_ft_jg`(`sched_name`, `job_group`) USING BTREE,
INDEX `idx_qrtz_ft_t_g`(`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
INDEX `idx_qrtz_ft_tg`(`sched_name`, `trigger_group`) USING BTREE
); -- ----------------------------
-- Records of t_qrtz_fired_triggers
-- ---------------------------- -- ----------------------------
-- Table structure for t_qrtz_job_details
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_job_details`;
CREATE TABLE `t_qrtz_job_details` (
`sched_name` varchar(120) NOT NULL,
`job_name` varchar(190) NOT NULL,
`job_group` varchar(190) NOT NULL,
`description` varchar(250) NULL DEFAULT NULL,
`job_class_name` varchar(250) NOT NULL,
`is_durable` varchar(1) NOT NULL,
`is_nonconcurrent` varchar(1) NOT NULL,
`is_update_data` varchar(1) NOT NULL,
`requests_recovery` varchar(1) NOT NULL,
`job_data` blob NULL,
PRIMARY KEY (`sched_name`, `job_name`, `job_group`) USING BTREE,
INDEX `idx_qrtz_j_req_recovery`(`sched_name`, `requests_recovery`) USING BTREE,
INDEX `idx_qrtz_j_grp`(`sched_name`, `job_group`) USING BTREE
); -- ----------------------------
-- Records of t_qrtz_job_details
-- ---------------------------- -- ----------------------------
-- Table structure for t_qrtz_locks
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_locks`;
CREATE TABLE `t_qrtz_locks` (
`sched_name` varchar(120) NOT NULL,
`lock_name` varchar(40) NOT NULL,
PRIMARY KEY (`sched_name`, `lock_name`) USING BTREE
); -- ----------------------------
-- Records of t_qrtz_locks
-- ----------------------------
INSERT INTO `t_qrtz_locks` VALUES ('clusteredScheduler', 'STATE_ACCESS');
INSERT INTO `t_qrtz_locks` VALUES ('clusteredScheduler', 'TRIGGER_ACCESS'); -- ----------------------------
-- Table structure for t_qrtz_paused_trigger_grps
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_paused_trigger_grps`;
CREATE TABLE `t_qrtz_paused_trigger_grps` (
`sched_name` varchar(120) NOT NULL,
`trigger_group` varchar(190) NOT NULL,
PRIMARY KEY (`sched_name`, `trigger_group`) USING BTREE
); -- ----------------------------
-- Records of t_qrtz_paused_trigger_grps
-- ---------------------------- -- ----------------------------
-- Table structure for t_qrtz_scheduler_state
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_scheduler_state`;
CREATE TABLE `t_qrtz_scheduler_state` (
`sched_name` varchar(120) NOT NULL,
`instance_name` varchar(190) NOT NULL,
`last_checkin_time` bigint(0) NOT NULL,
`checkin_interval` bigint(0) NOT NULL,
PRIMARY KEY (`sched_name`, `instance_name`) USING BTREE
); -- ----------------------------
-- Records of t_qrtz_scheduler_state
-- ----------------------------
INSERT INTO `t_qrtz_scheduler_state` VALUES ('clusteredScheduler', 'C3Stones-PC', 1600918524362, 10000); -- ----------------------------
-- Table structure for t_qrtz_simple_triggers
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_simple_triggers`;
CREATE TABLE `t_qrtz_simple_triggers` (
`sched_name` varchar(120) NOT NULL,
`trigger_name` varchar(190) NOT NULL,
`trigger_group` varchar(190) NOT NULL,
`repeat_count` bigint(0) NOT NULL,
`repeat_interval` bigint(0) NOT NULL,
`times_triggered` bigint(0) NOT NULL,
PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
CONSTRAINT `t_qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `t_qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT
); -- ----------------------------
-- Records of t_qrtz_simple_triggers
-- ---------------------------- -- ----------------------------
-- Table structure for t_qrtz_simprop_triggers
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_simprop_triggers`;
CREATE TABLE `t_qrtz_simprop_triggers` (
`sched_name` varchar(120) NOT NULL,
`trigger_name` varchar(190) NOT NULL,
`trigger_group` varchar(190) NOT NULL,
`str_prop_1` varchar(512) NULL DEFAULT NULL,
`str_prop_2` varchar(512) NULL DEFAULT NULL,
`str_prop_3` varchar(512) NULL DEFAULT NULL,
`int_prop_1` int(0) NULL DEFAULT NULL,
`int_prop_2` int(0) NULL DEFAULT NULL,
`long_prop_1` bigint(0) NULL DEFAULT NULL,
`long_prop_2` bigint(0) NULL DEFAULT NULL,
`dec_prop_1` decimal(13, 4) NULL DEFAULT NULL,
`dec_prop_2` decimal(13, 4) NULL DEFAULT NULL,
`bool_prop_1` varchar(1) NULL DEFAULT NULL,
`bool_prop_2` varchar(1) NULL DEFAULT NULL,
PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
CONSTRAINT `t_qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `t_qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT
); -- ----------------------------
-- Records of t_qrtz_simprop_triggers
-- ---------------------------- -- ----------------------------
-- Table structure for t_qrtz_triggers
-- ----------------------------
DROP TABLE IF EXISTS `t_qrtz_triggers`;
CREATE TABLE `t_qrtz_triggers` (
`sched_name` varchar(120) NOT NULL,
`trigger_name` varchar(190) NOT NULL,
`trigger_group` varchar(190) NOT NULL,
`job_name` varchar(190) NOT NULL,
`job_group` varchar(190) NOT NULL,
`description` varchar(250) NULL DEFAULT NULL,
`next_fire_time` bigint(0) NULL DEFAULT NULL,
`prev_fire_time` bigint(0) NULL DEFAULT NULL,
`priority` int(0) NULL DEFAULT NULL,
`trigger_state` varchar(16) NOT NULL,
`trigger_type` varchar(8) NOT NULL,
`start_time` bigint(0) NOT NULL,
`end_time` bigint(0) NULL DEFAULT NULL,
`calendar_name` varchar(190) NULL DEFAULT NULL,
`misfire_instr` smallint(0) NULL DEFAULT NULL,
`job_data` blob NULL,
PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
INDEX `idx_qrtz_t_j`(`sched_name`, `job_name`, `job_group`) USING BTREE,
INDEX `idx_qrtz_t_jg`(`sched_name`, `job_group`) USING BTREE,
INDEX `idx_qrtz_t_c`(`sched_name`, `calendar_name`) USING BTREE,
INDEX `idx_qrtz_t_g`(`sched_name`, `trigger_group`) USING BTREE,
INDEX `idx_qrtz_t_state`(`sched_name`, `trigger_state`) USING BTREE,
INDEX `idx_qrtz_t_n_state`(`sched_name`, `trigger_name`, `trigger_group`, `trigger_state`) USING BTREE,
INDEX `idx_qrtz_t_n_g_state`(`sched_name`, `trigger_group`, `trigger_state`) USING BTREE,
INDEX `idx_qrtz_t_next_fire_time`(`sched_name`, `next_fire_time`) USING BTREE,
INDEX `idx_qrtz_t_nft_st`(`sched_name`, `trigger_state`, `next_fire_time`) USING BTREE,
INDEX `idx_qrtz_t_nft_misfire`(`sched_name`, `misfire_instr`, `next_fire_time`) USING BTREE,
INDEX `idx_qrtz_t_nft_st_misfire`(`sched_name`, `misfire_instr`, `next_fire_time`, `trigger_state`) USING BTREE,
INDEX `idx_qrtz_t_nft_st_misfire_grp`(`sched_name`, `misfire_instr`, `next_fire_time`, `trigger_group`, `trigger_state`) USING BTREE,
CONSTRAINT `t_qrtz_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `job_name`, `job_group`) REFERENCES `t_qrtz_job_details` (`sched_name`, `job_name`, `job_group`) ON DELETE RESTRICT ON UPDATE RESTRICT
); -- ----------------------------
-- Records of t_qrtz_triggers
-- ---------------------------- -- ----------------------------
-- Table structure for t_sys_job
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_job`;
CREATE TABLE `t_sys_job` (
`id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`job_name` varchar(100) NULL DEFAULT NULL COMMENT '任务名称',
`cron_expression` varchar(255) NULL DEFAULT NULL COMMENT 'cron表达式',
`bean_class` varchar(255) NULL DEFAULT NULL COMMENT '任务执行类(包名+类名)',
`status` varchar(10) NULL DEFAULT NULL COMMENT '任务状态',
`job_group` varchar(50) NULL DEFAULT NULL COMMENT '任务分组',
`job_data_map` varchar(1000) NULL DEFAULT NULL COMMENT '参数',
`create_user_id` int(0) NULL DEFAULT NULL COMMENT '创建人ID',
`create_date` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_user_id` int(0) NULL DEFAULT NULL COMMENT '更新人ID',
`update_date` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`remarks` varchar(255) NULL DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`) USING BTREE
) AUTO_INCREMENT = 3 COMMENT = '定时任务'; -- ----------------------------
-- Records of t_sys_job
-- ----------------------------
INSERT INTO `t_sys_job` VALUES (1, 'TestJob', '0/5 * * * * ?', 'com.c3stones.job.biz.TestJob', 'NONE', 'default', '{\"username\":\"zhangsan\", \"age\":18}', 1, '2020-09-25 15:22:32', 1, '2020-09-25 15:22:32', '测试定时任务1');
INSERT INTO `t_sys_job` VALUES (2, 'Test2Job', '0 * * * * ?', 'com.c3stones.job.biz.Test2Job', 'NONE', 'default', '{\"username\":\"lisi\", \"age\":20}', 1, '2020-09-25 15:22:54', 1, '2020-09-25 15:22:54', '测试定时任务2'); -- ----------------------------
-- Table structure for t_sys_user
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_user`;
CREATE TABLE `t_sys_user` (
`id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` varchar(50) NULL DEFAULT NULL COMMENT '用户名称',
`nickname` varchar(100) NULL DEFAULT NULL COMMENT '用户昵称',
`password` varchar(255) NULL DEFAULT NULL COMMENT '用户密码',
PRIMARY KEY (`id`) USING BTREE
)AUTO_INCREMENT = 3 COMMENT = '系统用户'; -- ----------------------------
-- Records of t_sys_user
-- ----------------------------
INSERT INTO `t_sys_user` VALUES (1, 'user', 'C3Stones', '$2a$10$WXEPqxjMwY6d6A0hkeBtGu.acRRWUOJmX7oLUuYMHF1VWWUm4EqOC');
INSERT INTO `t_sys_user` VALUES (2, 'system', '管理员', '$2a$10$dmO7Uk9/lo1D5d1SvCGgWuB050a0E2uuBDNITEpWFiIfCg.3UbA8y'); SET FOREIGN_KEY_CHECKS = 1;

4. 示例代码

  本文在之前博客SpringBoot + Layui +Mybatis-plus实现简单后台管理系统(内置安全过滤器)的示例项目spring-boot-layui-demo基础上增加了任务调度菜单,因此请先下载相关工程。

  • 修改pom.xml

      引入依赖spring-boot-starter-quartz即可实现SpringBoot与Quartz集成。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.c3stones</groupId>
<artifactId>spring-boot-quartz-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-quartz-demo</name>
<description>Spring Boot Quartz Demo</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.8.RELEASE</version>
<relativePath />
</parent> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>
  • 配置文件application.yml添加quartz相关配置
server:
port: 8080
servlet:
session:
timeout: 1800s spring:
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/quartzdemo?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
username: root
password: 123456
thymeleaf:
prefix: classpath:/view/
suffix: .html
encoding: UTF-8
servlet:
content-type: text/html
# 生产环境设置true
cache: false
quartz:
properties:
org:
quartz:
scheduler:
instanceName: clusteredScheduler
instanceId: AUTO
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: t_qrtz_
isClustered: false
clusterCheckinInterval: 10000
useProperties: false
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
job-store-type: jdbc # Mybatis-plus配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
global-config:
db-config:
id-type: AUTO
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志配置
logging:
config: classpath:logback-spring.xml # 信息安全
security:
web:
excludes:
- /login
- /logout
- /images/**
- /jquery/**
- /layui/**
xss:
enable: true
excludes:
- /login
- /logout
- /images/*
- /jquery/*
- /layui/*
sql:
enable: true
excludes:
- /images/*
- /jquery/*
- /layui/*
csrf:
enable: true
excludes:
  • 创建调度器配置类
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean; /**
* 调度器配置类
*
* @author CL
*
*/
@Configuration
public class SchedulerConfig implements SchedulerFactoryBeanCustomizer { @Autowired
private DataSource dataSource; @Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
// 启动延时
schedulerFactoryBean.setStartupDelay(10);
// 自动启动任务调度
schedulerFactoryBean.setAutoStartup(true);
// 是否覆盖现有作业定义
schedulerFactoryBean.setOverwriteExistingJobs(true);
// 配置数据源
schedulerFactoryBean.setDataSource(dataSource);
} }
  • 创建全局用户工具类
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import com.c3stones.sys.entity.User; /**
* 用户工具类
*
* @author CL
*
*/
public class UserUtils { /**
* 获取当前用户
*
* @return
*/
public static User get() {
return (User) getSession().getAttribute("user");
} /**
* 获取session
*
* @return
*/
public static HttpSession getSession() {
return getRequest().getSession();
} /**
* 获取request
*
* @return
*/
public static HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
return requestAttributes.getRequest();
} }
  • 创建实体
import java.io.Serializable;
import java.time.LocalDateTime; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; /**
* 定时任务
*
* @author CL
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "t_sys_job")
@EqualsAndHashCode(callSuper = false)
public class Job extends Model<Job> implements Serializable { private static final long serialVersionUID = 1L; /**
* ID
*/
@TableId(type = IdType.AUTO)
private Integer id; /**
* 任务名称
*/
private String jobName; /**
* cron表达式
*/
private String cronExpression; /**
* 任务执行类(包名+类名)
*/
private String beanClass; /**
* 任务状态(0-停止,1-运行)
*/
private String status; /**
* 任务分组
*/
private String jobGroup; /**
* 参数
*/
private String jobDataMap; /**
* 下一次执行时间
*/
@TableField(exist = false)
private LocalDateTime nextfireDate; /**
* 创建人ID
*/
private Integer createUserId; /**
* 创建时间
*/
private LocalDateTime createDate; /**
* 更新人ID
*/
private Integer updateUserId; /**
* 更新时间
*/
private LocalDateTime updateDate; /**
* 描述
*/
private String remarks; }
  • 创建定时任务处理器
import java.text.ParseException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import com.c3stones.job.entity.Job; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j; /**
* 定时任务管理器
*
* @author CL
*
*/
@Slf4j
@Component
public class QuartzHandler { @Autowired
private Scheduler scheduler; /**
* 新增定义任务
*
* @param job 定义任务
* @param clazz 任务执行类
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public boolean start(Job job, Class clazz) {
boolean result = true;
try {
String jobName = job.getJobName();
String jobGroup = job.getJobGroup();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (null == cronTrigger) {
// 处理参数
Map<String, String> map = new HashMap<>(5);
String jobDataMap = job.getJobDataMap();
if (StrUtil.isNotBlank(jobDataMap)) {
if (JSONUtil.isJson(jobDataMap)) {
Map parseMap = JSONUtil.toBean(jobDataMap, Map.class);
parseMap.forEach((k, v) -> {
map.put(String.valueOf(k), String.valueOf(v));
});
}
}
// 启动定时任务
JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(jobName, jobGroup)
.setJobData(new JobDataMap(map)).build();
cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup)
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression())).build();
scheduler.scheduleJob(jobDetail, cronTrigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}
} else {
// 重启定时任务
cronTrigger = cronTrigger.getTriggerBuilder().withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression())).build();
scheduler.rescheduleJob(triggerKey, cronTrigger);
}
} catch (SchedulerException e) {
log.info("新增定时任务异常:{}", e.getMessage());
result = false;
}
return result;
} /**
* 暂停定时任务
*
* @param job 定时任务
* @return
*/
public boolean pasue(Job job) {
boolean result = true;
try {
String jobName = job.getJobName();
String jobGroup = job.getJobGroup();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
Trigger trigger = scheduler.getTrigger(triggerKey);
JobKey jobKey = trigger.getJobKey();
scheduler.pauseJob(jobKey);
} catch (SchedulerException e) {
log.info("暂停定时任务异常:{}", e.getMessage());
result = false;
}
return result;
} /**
* 重启定时任务
*
* @param job 定时任务
* @return
*/
public boolean restart(Job job) {
boolean result = true;
try {
String jobName = job.getJobName();
String jobGroup = job.getJobGroup();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
Trigger trigger = scheduler.getTrigger(triggerKey);
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
log.info("重启定时任务异常:{}", e.getMessage());
result = false;
}
return result;
} /**
* 立即执行一次
*
* @param job 定时任务
* @return
*/
public boolean trigger(Job job) {
boolean result = true;
try {
String jobName = job.getJobName();
String jobGroup = job.getJobGroup();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
Trigger trigger = scheduler.getTrigger(triggerKey);
JobKey jobKey = trigger.getJobKey();
scheduler.triggerJob(jobKey);
} catch (SchedulerException e) {
log.info("立即执行一次异常:{}", e.getMessage());
result = false;
}
return result;
} /**
* 修改触发时间表达式
*
* @param job 定时任务
* @param newCronExpression 新的cron表达式
* @return
*/
public boolean updateCronExpression(Job job, String newCronExpression) {
boolean result = true;
try {
String jobName = job.getJobName();
String jobGroup = job.getJobGroup();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
job.setCronExpression(newCronExpression);
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
cronTrigger = cronTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder)
.build();
scheduler.rescheduleJob(triggerKey, cronTrigger);
} catch (SchedulerException e) {
log.info("修改触发时间表达式异常:{}", e.getMessage());
result = false;
}
return result;
} /**
* 删除定时任务
*
* @param job 定时任务
* @return
*/
public boolean delete(Job job) {
boolean result = true;
try {
String jobName = job.getJobName();
String jobGroup = job.getJobGroup();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
Trigger trigger = scheduler.getTrigger(triggerKey);
JobKey jobKey = trigger.getJobKey();
// 停止触发器
scheduler.pauseTrigger(triggerKey);
// 移除触发器
scheduler.unscheduleJob(triggerKey);
// 删除任务
scheduler.deleteJob(jobKey);
} catch (SchedulerException e) {
log.info("删除定时任务异常:{}", e.getMessage());
result = false;
}
return result;
} /***
* 判断是否存在定时任务
*
* @param job 定时任务
* @return
*/
public boolean has(Job job) {
boolean result = true;
try {
if (!scheduler.isShutdown()) {
String jobName = job.getJobName();
String jobGroup = job.getJobGroup();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
Trigger trigger = scheduler.getTrigger(triggerKey);
result = (trigger != null) ? true : false;
} else {
result = false;
}
} catch (SchedulerException e) {
log.info("判断是否存在定时任务异常:{}", e.getMessage());
result = false;
}
return result;
} /**
* 获得定时任务状态
*
* @param job 定时任务
* @return
*/
public String getStatus(Job job) {
String status = StrUtil.EMPTY;
try {
String jobName = job.getJobName();
String jobGroup = job.getJobGroup();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
TriggerState triggerState = scheduler.getTriggerState(triggerKey);
status = triggerState.toString();
} catch (Exception e) {
log.info("获得定时任务状态异常:{}", e.getMessage());
}
return StrUtil.isNotEmpty(status) ? status : TriggerState.NONE.toString();
} /**
* 启动调度器
*
* @return
*/
public boolean startScheduler() {
boolean result = true;
try {
scheduler.start();
} catch (SchedulerException e) {
log.info("启动调度器异常:{}", e.getMessage());
result = false;
}
return result;
} /**
* 关闭调度器
*
* @return
*/
public boolean standbyScheduler() {
boolean result = true;
try {
if (!scheduler.isShutdown()) {
scheduler.standby();
}
} catch (SchedulerException e) {
log.info("关闭调度器异常:{}", e.getMessage());
result = false;
}
return result;
} /**
* 判断调度器是否为开启状态
*
* @return
*/
public boolean isStarted() {
boolean result = true;
try {
result = scheduler.isStarted();
} catch (SchedulerException e) {
log.info("判断调度器是否为开启状态异常:{}", e.getMessage());
}
return result;
} /**
* 判断调度器是否为关闭状态
*
* @return
*/
public boolean isShutdown() {
boolean result = true;
try {
result = scheduler.isShutdown();
} catch (SchedulerException e) {
log.info("判断调度器是否为关闭状态异常:{}", e.getMessage());
}
return result;
} /**
* 判断调度器是否为待机状态
*
* @return
*/
public boolean isInStandbyMode() {
boolean result = true;
try {
result = scheduler.isInStandbyMode();
} catch (SchedulerException e) {
log.info("判断调度器是否为待机状态异常:{}", e.getMessage());
}
return result;
} /**
* 获得下一次执行时间
*
* @param cronExpression cron表达式
* @return
*/
public LocalDateTime nextfireDate(String cronExpression) {
LocalDateTime localDateTime = null;
try {
if (StrUtil.isNotEmpty(cronExpression)) {
CronExpression ce = new CronExpression(cronExpression);
Date nextInvalidTimeAfter = ce.getNextInvalidTimeAfter(new Date());
localDateTime = Instant.ofEpochMilli(nextInvalidTimeAfter.getTime()).atZone(ZoneId.systemDefault())
.toLocalDateTime();
}
} catch (ParseException e) {
log.info("获得下一次执行时间异常:{}", e.getMessage());
}
return localDateTime;
} }
  • 创建Mapper
import org.apache.ibatis.annotations.Mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.c3stones.job.entity.Job; /**
* 定时任务Mapper
*
* @author CL
*
*/
@Mapper
public interface JobMapper extends BaseMapper<Job> { }
  • 创建Service
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.c3stones.job.entity.Job; /**
* 定时任务Service
*
* @author CL
*
*/
public interface JobService extends IService<Job> { /**
* 查询列表数据
*
* @param job 系统用户
* @param current 当前页
* @param size 每页显示条数
* @return
*/
public Page<Job> listData(Job job, long current, long size); }
  • 创建Service实现类
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.c3stones.job.config.QuartzHandler;
import com.c3stones.job.entity.Job;
import com.c3stones.job.mapper.JobMapper;
import com.c3stones.job.service.JobService; import cn.hutool.core.util.StrUtil; /**
* 定时任务Service实现
*
* @author CL
*
*/
@Service
public class JobServiceImpl extends ServiceImpl<JobMapper, Job> implements JobService { @Autowired
private QuartzHandler quartzHandler; /**
* 查询列表数据
*
* @param job 系统用户
* @param current 当前页
* @param size 每页显示条数
* @return
*/
@Override
public Page<Job> listData(Job job, long current, long size) {
QueryWrapper<Job> queryWrapper = new QueryWrapper<>();
if (StrUtil.isNotBlank(job.getJobName())) {
queryWrapper.like("job_name", job.getJobName());
}
Page<Job> page = baseMapper.selectPage(new Page<>(current, size), queryWrapper);
List<Job> records = page.getRecords(); // 处理定时任务数据
for (int i = 0; i < records.size(); i++) {
Job j = records.get(i);
// 获取下一次执行时间
j.setNextfireDate(quartzHandler.nextfireDate(j.getCronExpression())); // 更新状态
String status = quartzHandler.getStatus(j);
if (!(status).equals(j.getStatus())) {
j.setStatus(status);
super.updateById(j);
} records.set(i, j);
}
page.setRecords(records);
return page;
}
}
  • 创建Controller
import java.time.LocalDateTime;

import org.quartz.Trigger.TriggerState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.c3stones.common.vo.Response;
import com.c3stones.job.config.QuartzHandler;
import com.c3stones.job.entity.Job;
import com.c3stones.job.service.JobService;
import com.c3stones.sys.entity.User;
import com.c3stones.sys.utils.UserUtils; /**
* 定时任务Controller
*
* @author CL
*
*/
@Controller
@RequestMapping(value = "job")
public class JobController { @Autowired
private QuartzHandler quartzHandler; @Autowired
private JobService jobService; /**
* 查询列表
*
* @return
*/
@RequestMapping(value = "list")
public String list() {
return "pages/job/jobList";
} /**
* 查询列表数据
*
* @param user 系统用户
* @param current 当前页
* @param size 每页显示条数
* @return
*/
@RequestMapping(value = "listData")
@ResponseBody
public Response<Page<Job>> listData(Job job, @RequestParam(name = "page") long current,
@RequestParam(name = "limit") long size) {
Page<Job> page = jobService.listData(job, current, size);
return Response.success(page);
} /**
* 更新
*
* @param job 定时任务
* @return
*/
@RequestMapping(value = "update")
@ResponseBody
public Response<Boolean> update(Job job) {
Assert.notNull(job.getId(), "ID不能为空");
User user = UserUtils.get();
if (user != null) {
job.setUpdateUserId(user.getId());
}
LocalDateTime now = LocalDateTime.now();
job.setUpdateDate(now);
boolean result = jobService.updateById(job);
Job queryJob = jobService.getById(job.getId());
String status = quartzHandler.getStatus(queryJob);
if (!(TriggerState.NONE.toString()).equals(status)) {
result = quartzHandler.updateCronExpression(queryJob, queryJob.getCronExpression());
}
return Response.success("更新" + (result ? "成功" : "失败"), result);
} /**
* 删除
*
* @param job 定时任务
* @return
*/
@RequestMapping(value = "delete")
@ResponseBody
public Response<Boolean> delete(Job job) {
Assert.notNull(job.getId(), "ID不能为空");
Job queryJob = jobService.getById(job.getId());
boolean result = true;
if (!(TriggerState.NONE.toString()).equals(queryJob.getStatus())) {
result = quartzHandler.delete(queryJob);
}
if (result) {
result = jobService.removeById(job.getId());
}
return Response.success("删除" + (result ? "成功" : "失败"), result);
} /**
* 启动
*
* @param job 定时任务
* @return
* @throws ClassNotFoundException
*/
@RequestMapping(value = "start")
@ResponseBody
public Response<Boolean> start(Job job) throws ClassNotFoundException {
Assert.notNull(job.getId(), "ID不能为空");
Job queryJob = jobService.getById(job.getId());
Assert.notNull(queryJob, "定时任务不存在");
Class<?> clazz = Class.forName(queryJob.getBeanClass());
Assert.notNull(clazz, "未找到任务执行类");
boolean result = quartzHandler.start(queryJob, clazz);
return Response.success("启动" + (result ? "成功" : "失败"), result);
} /**
* 暂停
*
* @param job 定时任务
* @return
*/
@RequestMapping(value = "pasue")
@ResponseBody
public Response<Boolean> pasue(Job job) {
Assert.notNull(job.getId(), "ID不能为空");
Job queryJob = jobService.getById(job.getId());
Assert.notNull(queryJob, "定时任务不存在"); String status = quartzHandler.getStatus(queryJob);
if (!((TriggerState.NORMAL.toString()).equals(status) || (TriggerState.PAUSED.toString()).equals(status)
|| (TriggerState.BLOCKED.toString()).equals(status))) {
return Response.success("当前状态不可暂停", false);
}
if ((TriggerState.PAUSED.toString()).equals(status)) {
return Response.success("已暂停", false);
} boolean result = quartzHandler.pasue(queryJob);
return Response.success("暂停" + (result ? "成功" : "失败"), result);
} /**
* 立即执行
*
* @param job 定时任务
* @return
*/
@RequestMapping(value = "trigger")
@ResponseBody
public Response<Boolean> trigger(Job job) {
Assert.notNull(job.getId(), "ID不能为空");
Job queryJob = jobService.getById(job.getId());
Assert.notNull(queryJob, "定时任务不存在"); String status = quartzHandler.getStatus(queryJob);
if (!((TriggerState.NORMAL.toString()).equals(status) || (TriggerState.PAUSED.toString()).equals(status)
|| (TriggerState.COMPLETE.toString()).equals(status))) {
return Response.success("当前状态不可立即执行", false);
} boolean result = quartzHandler.trigger(queryJob);
return Response.success("立即执行" + (result ? "成功" : "失败"), result);
} /**
* 判断定时器是否为待机模式
*/
@RequestMapping(value = "isInStandbyMode")
@ResponseBody
public Response<Boolean> isInStandbyMode() {
boolean result = quartzHandler.isInStandbyMode();
return Response.success(result);
} /**
* 启动定时器
*
* @return
*/
@RequestMapping(value = "startScheduler")
@ResponseBody
public Response<Boolean> startScheduler() {
boolean result = quartzHandler.startScheduler();
return Response.success("启动定时器" + (result ? "成功" : "失败"), result);
} /**
* 待机定时器
*
* @return
*/
@RequestMapping(value = "standbyScheduler")
@ResponseBody
public Response<Boolean> standbyScheduler() {
boolean result = quartzHandler.standbyScheduler();
return Response.success("关闭定时器" + (result ? "成功" : "失败"), result);
} /**
* 新增
*
* @return
*/
@RequestMapping(value = "add")
public String add() {
return "pages/job/jobAdd";
} /**
* 保存
*
* @return
*/
@RequestMapping(value = "save")
@ResponseBody
public Response<Boolean> save(Job job) {
User user = UserUtils.get();
if (user != null) {
job.setCreateUserId(user.getId());
job.setUpdateUserId(user.getId());
}
LocalDateTime now = LocalDateTime.now();
job.setCreateDate(now);
job.setUpdateDate(now);
boolean result = jobService.save(job);
return Response.success(result);
} }
  • 主页index.html配置菜单
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>C3Stones</title>
<link th:href="@{/images/favicon.ico}" rel="icon">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/admin.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.js}"></script>
<script th:src="@{/layui/js/index.js}" data-main="home"></script>
</head>
<body class="layui-layout-body">
<div class="layui-layout layui-layout-admin">
<div class="layui-header custom-header">
<ul class="layui-nav layui-layout-left">
<li class="layui-nav-item slide-sidebar" lay-unselect>
<a href="javascript:;" class="icon-font"><i class="ai ai-menufold"></i></a>
</li>
</ul>
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item">
<a href="javascript:;">[[${user?.nickname}]]</a>
<dl class="layui-nav-child">
<dd><a th:href="@{/logout}">退出</a></dd>
</dl>
</li>
</ul>
</div> <div class="layui-side custom-admin">
<div class="layui-side-scroll">
<div class="custom-logo">
<img alt="" th:src="@{/images/logo.jpg}">
<h1>C3Stones</h1>
</div>
<ul id="Nav" class="layui-nav layui-nav-tree">
<li class="layui-nav-item">
<a href="javascript:;">
<i class="layui-icon"></i>
<em>主页</em>
</a>
<dl class="layui-nav-child">
<dd><a th:href="@{/view}">控制台</a></dd>
</dl>
</li>
<li class="layui-nav-item">
<a href="javascript:;">
<i class="layui-icon"></i>
<em>系统管理</em>
</a>
<dl class="layui-nav-child">
<dd><a th:href="@{/user/list}">用户管理</a></dd>
</dl>
<dl class="layui-nav-child">
<dd><a th:href="@{/job/list}">任务调度</a></dd>
</dl>
</li>
</ul> </div>
</div> <div class="layui-body">
<div class="layui-tab app-container" lay-allowClose="true" lay-filter="tabs">
<ul id="appTabs" class="layui-tab-title custom-tab"></ul>
<div id="appTabPage" class="layui-tab-content"></div>
</div>
</div> <div class="layui-footer">
<p> 2020 - C3Stones Blog : <a href="https://www.cnblogs.com/cao-lei/" target="_blank">https://www.cnblogs.com/cao-lei/</a></p>
</div>
<div class="mobile-mask"></div>
</div>
</body>
</html>
  • 新增定时任务列表页面jobList.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/view.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.all.js}"></script>
<script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
<script th:src="@{/layui/js/view.js}"></script>
<title></title>
</head>
<body class="layui-view-body">
<div class="layui-content">
<div class="layui-row">
<div class="layui-card">
<div class="layui-card-header">
<i class="layui-icon mr5"></i>任务调度(定时器状态:<label id="schedulerStatus"></label>)
<button class="layui-btn layui-btn-xs layui-hide" data-type="startScheduler">启动定时器</button>
<button class="layui-btn layui-btn-xs layui-btn-danger layui-hide" data-type="standbyScheduler">定时器待机</button>
<button class="layui-btn layui-btn-xs layui-btn-normal pull-right mt10" data-type="add"><i class="layui-icon mr5"></i>新增</button>
</div>
<div class="layui-card-body">
<div class="searchTable">
任务名称:
<div class="layui-inline mr5">
<input class="layui-input" name="jobName" autocomplete="off">
</div>
<button class="layui-btn" data-type="reload">查询</button>
<button class="layui-btn layui-btn-primary" data-type="reset">重置</button>
</div>
<table class="layui-hide" id="jobDataTable" lay-filter="config"></table>
<script type="text/html" id="operation">
<a class="layui-btn layui-btn-xs " lay-event="start">启动</a>
<a class="layui-btn layui-btn-xs layui-btn-warm" lay-event="pasue">暂停</a>
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="trigger">立即执行</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del">删除</a>
</script>
</div>
</div>
</div>
</div>
</body>
<script>
var element = layui.element;
var table = layui.table;
var layer = layui.layer;
table.render({
id: 'jobTable'
,elem: '#jobDataTable'
,url: '[[@{/job/listData}]]'
,cellMinWidth: 100
,page: {
layout: ['prev', 'page', 'next', 'count', 'skip', 'limit']
,groups: 5
,first: false
,last: false
}
,cols: [
[
{field:'id', title: 'ID', width: 50}
,{field:'jobName', title: '任务名称', width: 120}
,{field:'cronExpression', title: '周期表达式', edit: 'text', width: 100}
,{field:'beanClass', title: '任务执行类', width: 250}
,{field:'jobDataMap', title: '参数', width: 200}
,{field:'status', title: '状态', templet: '#statusTemp', width: 80, align: 'center'}
,{field:'jobGroup', title: '分组', templet: '#groupTemp', width: 60, align: 'center'}
,{field:'nextfireDate', title: '下一次执行时间', width: 160, align: 'center'}
,{field:'remarks', title: '描述', width: 200}
,{fixed: 'right', title:'操作', align: 'center', toolbar: '#operation', width:240}
]
]
,response: {
statusCode: 200
}
,parseData: function(res){
return {
"code": res.code
,"msg": res.msg
,"count": res.data.total
,"data": res.data.records
};
}
}); active = {
add: function() {
layer.open({
type: 2,
area: ['90%', '90%'],
title: '新增',
content: '[[@{/}]]job/add'
});
},
reload: function() {
table.reload('jobTable', {
page: {
curr: 1
}
,where: {
jobName : $("input[name='jobName']").val()
}
}, 'data');
},
reset: function() {
$(".searchTable .layui-input").val("");
},
startScheduler: function() {
$.ajax({
url : "[[@{/}]]job/startScheduler",
data : {},
type : "post",
dataType : "json",
error : function(data) {
errorHandle(data);
},
success : function(data) {
getSchedulerStatus();
msg(data);
refresh();
}
});
},
standbyScheduler: function() {
$.ajax({
url : "[[@{/}]]job/standbyScheduler",
data : {},
type : "post",
dataType : "json",
error : function(data) {
errorHandle(data);
},
success : function(data) {
getSchedulerStatus();
msg(data);
refresh();
}
});
}
}; // 按钮事件
$('.layui-btn').on('click', function(){
var type = $(this).data('type');
active[type] ? active[type].call(this) : '';
}); //监听行工具事件
table.on('tool(config)', function(obj){
var row = obj.data;
if (obj.event === 'start') {
$.ajax({
url : "[[@{/}]]job/start",
data : {'id': row.id},
type : "post",
dataType : "json",
error : function(data) {
errorHandle(data);
},
success : function(data) {
msg(data);
refresh();
}
});
} if (obj.event == 'pasue') {
$.ajax({
url : "[[@{/}]]job/pasue",
data : {'id': row.id},
type : "post",
dataType : "json",
error : function(data) {
errorHandle(data);
},
success : function(data) {
msg(data);
refresh();
}
});
} if (obj.event == 'trigger') {
$.ajax({
url : "[[@{/}]]job/trigger",
data : {'id': row.id},
type : "post",
dataType : "json",
error : function(data) {
errorHandle(data);
},
success : function(data) {
msg(data);
refresh();
}
});
} else if(obj.event === 'del') {
layer.confirm("确认删除吗?", {icon: 3, title:'提示'}, function(index) {
layer.close(index);
$.ajax({
url : "[[@{/}]]job/delete",
data : {'id': row.id},
type : "post",
dataType : "json",
error : function(data) {
errorHandle(data);
},
success : function(data) {
refresh();
}
});
});
}
}); table.on('edit(config)', function(obj){
var value = obj.value;
if (isEmpty(value)) {
layer.msg("不能为空", {icon: 2});
refresh();
return;
}
$.ajax({
url : "[[@{/}]]job/update",
data : {'id': obj.data.id, 'cronExpression' : value},
type : "post",
dataType : "json",
error : function(data) {
errorHandle(data);
},
success : function(data) {
msg(data);
refresh();
}
});
}); // 获取定时器状态
$(function(){getSchedulerStatus();});
function getSchedulerStatus() {
$.ajax({
url : "[[@{/}]]job/isInStandbyMode",
data : {},
type : "post",
dataType : "json",
error : function(data) {
errorHandle(data);
},
success : function(data) {
if (!data.data) { // 启动状态
$("button[data-type='startScheduler']").addClass("layui-hide");
$("button[data-type='standbyScheduler']").removeClass("layui-hide");
$("#schedulerStatus").html("<span class='text-green'>启动中</span>");
} else { // 待机状态
$("button[data-type='startScheduler']").removeClass("layui-hide");
$("button[data-type='standbyScheduler']").addClass("layui-hide");
$("#schedulerStatus").html("<span class='text-orange'>待机中</span>");
}
}
});
}
</script>
<script type="text/html" id="statusTemp">
{{# if(d.status === 'NONE'){ }}
<span class="text-purple">未启动</span>
{{# } else if(d.status === 'NORMAL') { }}
<span class="text-green">正常</span>
{{# } else if(d.status === 'PAUSED') { }}
<span class="text-orange">暂停</span>
{{# } else if(d.status === 'COMPLETE') { }}
<span class="text-aqua">完成</span>
{{# } else if(d.status === 'ERROR') { }}
<span class="text-red">异常</span>
{{# } else if(d.status === 'BLOCKED') { }}
<span class="text-maroon">锁定</span>
{{# } else { }}
<span class="text-gray">未知</span>
{{# } }}
</script>
<script type="text/html" id="groupTemp">
{{# if(d.jobGroup === 'default'){ }}
默认
{{# } else if(d.jobGroup === 'system') { }}
系统
{{# } else { }}
未知
{{# } }}
</script>
</html>
  • 新增定时任务新增页面jobAdd.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link th:href="@{/layui/css/layui.css}" rel="stylesheet" />
<link th:href="@{/layui/css/view.css}" rel="stylesheet" />
<script th:src="@{/layui/layui.all.js}"></script>
<script th:src="@{/jquery/jquery-2.1.4.min.js}"></script>
<script th:src="@{/jquery/jquery-form.js}"></script>
<script th:src="@{/layui/js/view.js}"></script>
<title></title>
</head>
<body class="layui-view-body">
<div class="layui-row">
<div class="layui-card">
<form class="layui-form layui-card-body layui-form-pane" method="post" th:action="@{/job/save}">
<input type="hidden" name="status" value="NONE">
<div class="layui-form-item">
<div class="layui-inline mr0" style="width: 49.7%">
<label class="layui-form-label"><i>*</i>任务名称</label>
<div class="layui-input-block">
<input type="text" name="jobName" id="jobName" maxlength="30" lay-verify="required" class="layui-input">
</div>
</div>
<div class="layui-inline mr0" style="width: 49.8%">
<label class="layui-form-label"><i>*</i>任务分组</label>
<div class="layui-input-block">
<select name="jobGroup">
<option value="default">默认</option>
<option value="system">系统</option>
</select>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">任务描述</label>
<div class="layui-input-block">
<input type="text" name="remarks" maxlength="50" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i>*</i>执行类</label>
<div class="layui-input-inline width-460">
<input type="text" name="beanClass" lay-verify="required" maxlength="200" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">包名 + 类名,示例:com.c3stones.job.biz.TestJob</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">参数</label>
<div class="layui-input-inline width-460">
<input type="text" name="jobDataMap" placeholder="JSON数据格式" maxlength="1000" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">示例:{"username":"zhangsan", "age":18}</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i>*</i>表达式</label>
<div class="layui-input-inline width-460">
<input type="text" name="cronExpression" placeholder="例如:0/5 * * * * ?" lay-verify="required" maxlength="200" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux"><a class="text-blue" href="https://cron.qqe2.com/" target="_blank">在线Cron表达式生成器</a></div>
</div>
<div class="layui-form-item">
<button type="submit" class="layui-btn" lay-submit lay-filter="*">提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</form>
</div>
</div>
</body>
<script>
var form = layui.form;
var layer = layui.layer; form.render(); // 提交表单
form.on('submit(*)', function(data){
$(".layui-form").ajaxForm({
error: function(data){
errorHandle(data);
},
success: function(data) {
parent.location.reload();
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
}
});
});
</script>
</html>

5. 测试

  • 创建两种类型Job

    • 实现Job接口
    import java.time.LocalDateTime;
    
    import org.quartz.Job;
    import org.quartz.JobDataMap;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.springframework.beans.factory.annotation.Autowired; import com.c3stones.job.service.JobService; import lombok.extern.slf4j.Slf4j; /**
    * 测试定时任务
    *
    * @author CL
    *
    */
    @Slf4j
    // @DisallowConcurrentExecution //不并发执行
    public class TestJob implements Job { @Autowired
    private JobService jobService; @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
    JobDataMap jobDataMap = context.getMergedJobDataMap();
    log.info("定时任务1 => 定时任务定时任务数量 => {},参数值 => {},当前时间 => {}", jobService.count(),
    "{ username=" + jobDataMap.get("username") + ", age=" + jobDataMap.get("age") + " }",
    LocalDateTime.now());
    } }
    • 继承QuartzJobBean类
    import java.time.LocalDateTime;
    
    import org.quartz.JobDataMap;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.quartz.QuartzJobBean; import com.c3stones.job.service.JobService; import lombok.extern.slf4j.Slf4j; /**
    * 测试定时任务
    *
    * @author CL
    *
    */
    @Slf4j
    // @DisallowConcurrentExecution //不并发执行
    public class Test2Job extends QuartzJobBean { @Autowired
    private JobService jobService; @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    JobDataMap jobDataMap = context.getMergedJobDataMap();
    log.info("定时任务2 => 定时任务数量 => {},参数值 => {},当前时间 => {}", jobService.count(),
    "{ username=" + jobDataMap.get("username") + ", age=" + jobDataMap.get("age") + " }",
    LocalDateTime.now());
    } }
  • 配置定时任务

      浏览器访问:http://127.0.0.1:8080/login,填写用户信息user/123456登录系统,点击菜单:系统管理>任务调度,通过新增页面,添加两个定时任务。配置完成页面如下:



      顶部按钮:定时器待机启动定时器为定时器操作按钮,即对所有定时任务有效。当定时器状态为启动中时,定时器待机显示,点击定时器状态变为待机中,所有定时任务待机;反之,所有定时任务可正常触发。

      右侧操作栏按钮:启动暂停立即执行删除,仅对当前定时任务有效。新增完的定时任务为未启动状态,点击启动按钮即可触发定时任务,点击暂停按钮即可暂停定时任务,点击立即执行按钮即可立即执行一次定时任务,点击删除按钮即可删除定时任务。
  • 点击操作按钮,观察控制台日志打印

6. 项目地址

  spring-boot-quartz-demo

SpringBoot2.x集成Quartz实现定时任务管理(持久化到数据库)的更多相关文章

  1. 用abp vNext快速开发Quartz.NET定时任务管理界面

    今天这篇文章我将通过实例代码带着大家一步一步通过abp vNext这个asp.net core的快速开发框架来进行Quartz.net定时任务调度的管理界面的开发.大伙最好跟着一起敲一下代码,当然源码 ...

  2. 【Quartz】将定时任务持久化到数据库

    之前的文章所做的demo是将定时任务的信息保存在内存中的,见以下配置 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore 如果,我们需要在 ...

  3. Springboot2.X集成Quartz集群

    为什么要使用Quzrtz集群 在项目进行集群部署时,如果业务在执行中存在互斥关系,没有对定时任务进行统一管理,就会引起业务的多次执行,不能满足业务要求.这时就需要对任务进行管理,要保证一笔业务在所有的 ...

  4. Quartz 定时任务管理

    前言 将项目中的所有定时任务都统一管理吧,使用 quartz 定时任务 设计思路 使用 quartz 的相关jar 包,懒得去升级了,我使用的是 quart 1.6 写一个定时任务管理类 用一张数据库 ...

  5. Quartz简单实现定时任务管理(SSM+Quartz)

    首先你得有一个用Maven搭好的SSM框架,数据库用的Mysql,这里只有关于Quartz的部分.其实有大神总结的很好了,但做完后总有些地方不一样,所以写这篇作为笔记.这里先把大神的写的分享给大家:h ...

  6. SpringBoot集成Quartz(解决@Autowired空指针Null问题即依赖注入的属性为null)

    使用spring-boot作为基础框架,其理念为零配置文件,所有的配置都是基于注解和暴露bean的方式. Quartz的4个核心概念: 1.Job表示一个工作,要执行的具体内容.此接口中只有一个方法v ...

  7. 完整的定时任务解决方案Spring集成+定时任务本身管理+DB持久化+集群

    完整的定时任务解决方案Spring集成+定时任务本身管理+DB持久化+集群 maven依赖 <dependency> <groupId>org.quartz-scheduler ...

  8. Spring Boot集成quartz实现定时任务并支持切换任务数据源

    org.quartz实现定时任务并自定义切换任务数据源 在工作中经常会需要使用到定时任务处理各种周期性的任务,org.quartz是处理此类定时任务的一个优秀框架.随着项目一点点推进,此时我们并不满足 ...

  9. SpringBoot集成Quartz实现定时任务

    1 需求 在我的前后端分离的实验室管理项目中,有一个功能是学生状态统计.我的设计是按天统计每种状态的比例.为了便于计算,在每天0点,系统需要将学生的状态重置,并插入一条数据作为一天的开始状态.另外,考 ...

随机推荐

  1. JUC锁种类总结

    在并发编程中有各种各样的锁,有的锁对象一个就身兼多种锁身份,所以初学者常常对这些锁造成混淆,所以这里来总结一下这些锁的特点和实现. 乐观锁.悲观锁 悲观锁 悲观锁是最常见的锁,我们常说的加锁指的也就是 ...

  2. JVM(一)-JVM入门

    JVM的定义: JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的 ...

  3. Mysql获取webshell方式总结

    select ... into outfile general_log cnblogs-MySQL获取webshell的几种方式 csdn-PhpMyadmin后台拿webshell方法总结

  4. Redis 未授权访问漏洞批量提权

    一.getshell前提 ①能有对 /root/.ssh/目录写入的权限 ②目标机开启22端口 二.安装依赖 sudo easy_install redis 三.使用 redis python hac ...

  5. RSA脚本环境配置-攻防世界-OldDriver

    [Crypto] 题目链接 [RSA算法解密] 审题分析 首先拿到一个压缩包,解压得到文件enc.txt. 先不用去管其他,第一眼enc马上联想到 RSA解密.接着往下看 [{"c" ...

  6. 凭借着这份面经,我拿下了字节,美团的offer!

    最近经常有粉丝私信问我问了一些诸如秋招该怎么复习的问题,我就想顺便把回答整理发一发.我也是把之前面试的一些经历经验和身边的人面试的经验总结了一下放在下面. 前期准备规划: 如果秋招的话一般过年回来就可 ...

  7. Mac下载器Folx的标签功能怎么使用

    当大家使用Folx下载软件的时候,会发现,下载好的文件或者视频,会被Folx自动打上标签,进行归类,这其实就是Folx自带的智能标签功能,它能智能识别图片.视频.应用程序并分类.但很多时候,智能标签并 ...

  8. Django rest framework 基础

    01: Django rest framework 基础 ​ ​ 1.1 什么是RESTful 1. REST与技术无关,代表的是一种软件架构风格(REST是Representational Stat ...

  9. Java集合【9】-- Vector源码解析

    目录 1.Vector介绍 2. 成员变量 3. 构造函数 4. 常用方法 4.1 增加 4.2 删除 4.3 修改 4.4 查询 4.5 其他常用函数 4.6 Lambda表达式相关的方法 4.7 ...

  10. MySQL replace into那些隐藏的风险

    目录 replace into时存在主键冲突 replace into时存在唯一索引冲突 replace into时存在主键冲突&唯一索引冲突 存在问题 结论 MySQL中 replace i ...