
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登录系统,点击菜单:系统管理>任务调度,通过新增页面,添加两个定时任务。配置完成页面如下:
顶部按钮:定时器待机
和启动定时器
为定时器操作按钮,即对所有定时任务有效。当定时器状态为启动中时,定时器待机
显示,点击定时器状态变为待机中,所有定时任务待机;反之,所有定时任务可正常触发。
右侧操作栏按钮:启动
、暂停
、立即执行
和删除
,仅对当前定时任务有效。新增完的定时任务为未启动状态,点击启动
按钮即可触发定时任务,点击暂停
按钮即可暂停定时任务,点击立即执行
按钮即可立即执行一次定时任务,点击删除
按钮即可删除定时任务。 - 点击操作按钮,观察控制台日志打印