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登錄系統,點擊菜單:系統管理>任務調度,通過新增頁面,添加兩個定時任務。配置完成頁面如下:

頂部按鈕:定時器待機和啟動定時器為定時器操作按鈕,即對所有定時任務有效。當定時器狀態為啟動中時,定時器待機顯示,點擊定時器狀態變為待機中,所有定時任務待機;反之,所有定時任務可正常觸發。
右側操作欄按鈕:啟動、暫停、立即執行和刪除,僅對當前定時任務有效。新增完的定時任務為未啟動狀態,點擊啟動按鈕即可觸發定時任務,點擊暫停按鈕即可暫停定時任務,點擊立即執行按鈕即可立即執行一次定時任務,點擊刪除按鈕即可刪除定時任務。 - 點擊操作按鈕,觀察控制台日志打印
