Springboot-admin整合Quartz實(shí)現(xiàn)動(dòng)態(tài)管理定時(shí)任務(wù)的過(guò)程詳解
boot-admin整合Quartz實(shí)現(xiàn)動(dòng)態(tài)管理定時(shí)任務(wù)
淄博燒烤爆紅出了圈,當(dāng)你坐在八大局的燒烤攤,面前是火爐、烤串、小餅和蘸料,音樂(lè)響起,啤酒倒?jié)M,燒烤靈魂的party即將開(kāi)場(chǎng)的時(shí)候,你系統(tǒng)中的Scheduler(調(diào)試器),也自動(dòng)根據(jù)設(shè)定的Trigger(觸發(fā)器),從容優(yōu)雅的啟動(dòng)了一系列的Job(后臺(tái)定時(shí)任務(wù))。工作一切早有安排,又何須費(fèi)心勞神呢?因?yàn)閎oot-admin早已將Quartz這塊肉串在了烤簽上!
項(xiàng)目源碼倉(cāng)庫(kù)github
項(xiàng)目源碼倉(cāng)庫(kù)gitee

Quartz是一款Java編寫(xiě)的開(kāi)源任務(wù)調(diào)度框架,同時(shí)它也是Spring默認(rèn)的任務(wù)調(diào)度框架。它的作用其實(shí)類(lèi)似于Timer定時(shí)器以及ScheduledExecutorService調(diào)度線程池,當(dāng)然Quartz作為一個(gè)獨(dú)立的任務(wù)調(diào)度框架表現(xiàn)更為出色,功能更強(qiáng)大,能夠定義更為復(fù)雜的執(zhí)行規(guī)則。
boot-admin 是一款采用前后端分離模式、基于 SpringCloud 微服務(wù)架構(gòu) + vue-element-admin 的 SaaS 后臺(tái)管理框架。
那么boot-admin怎樣才能將Quartz串成串呢?一共分三步:
加入依賴(lài)
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> </dependency>
前端整合
vue頁(yè)面以el-table作為任務(wù)的展示控件,串起任務(wù)的創(chuàng)建、修改、刪除、掛起、恢復(fù)、狀態(tài)查看等功能。
vue頁(yè)面
<template>
<div class="app-container" style="background-color: #FFFFFF;">
<!--功能按鈕區(qū)-->
<div class="cl pd-5 bg-1 bk-gray">
<div align="left" style="float:left">
<el-button size="mini" type="primary" @click="search()">查詢(xún)</el-button>
<el-button size="mini" type="primary" @click="handleadd()">添加</el-button>
</div>
<div align="right">
<!--分頁(yè)控件-->
<div style="align:right">
<el-pagination
:current-page="BaseTableData.page.currentPage"
:page-sizes="[5,10,20,50,100,500]"
:page-size="BaseTableData.page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="BaseTableData.page.total"
@size-change="handlePageSizeChange"
@current-change="handlePageCurrentChange"
/>
</div>
<!--分頁(yè)控件-->
</div>
</div>
<!--功能按鈕區(qū)-->
<!--表格-->
<el-table max-height="100%" :data="BaseTableData.table" style="width: 100%" :border="true">
<el-table-column type="index" :index="indexMethod" />
<el-table-column prop="jobName" label="任務(wù)名稱(chēng)" width="100px" />
<el-table-column prop="jobGroup" label="任務(wù)所在組" width="100px" />
<el-table-column prop="jobClassName" label="任務(wù)類(lèi)名" />
<el-table-column prop="cronExpression" label="表達(dá)式" width="120" />
<el-table-column prop="timeZoneId" label="時(shí)區(qū)" width="120" />
<el-table-column prop="startTime" label="開(kāi)始" width="120" :formatter="(row,column,cellValue) => dateTimeColFormatter(row,column,cellValue)"/>
<el-table-column prop="nextFireTime" label="下次" width="120" :formatter="(row,column,cellValue) => dateTimeColFormatter(row,column,cellValue)"/>
<el-table-column prop="previousFireTime" label="上次" width="120" :formatter="(row,column,cellValue) => dateTimeColFormatter(row,column,cellValue)"/>
<el-table-column prop="triggerState" label="狀態(tài)" width="80">
<template slot-scope="scope">
<p v-if="scope.row.triggerState=='NORMAL'">等待</p>
<p v-if="scope.row.triggerState=='PAUSED'">暫停</p>
<p v-if="scope.row.triggerState=='NONE'">刪除</p>
<p v-if="scope.row.triggerState=='COMPLETE'">結(jié)束</p>
<p v-if="scope.row.triggerState=='ERROR'">錯(cuò)誤</p>
<p v-if="scope.row.triggerState=='BLOCKED'">阻塞</p>
</template>
</el-table-column>
<el-table-column label="操作" width="220px">
<template slot-scope="scope">
<el-button type="warning" size="least" title="掛起" @click="handlePause(scope.row)">掛起</el-button>
<el-button type="primary" size="least" title="恢復(fù)" @click="handleResume(scope.row)">恢復(fù)</el-button>
<el-button type="danger" size="least" title="刪除" @click="handleDelete(scope.row)">刪除</el-button>
<el-button type="success" size="least" title="修改" @click="handleUpdate(scope.row)">修改</el-button>
</template>
</el-table-column>
</el-table>
<!--表格-->
<!--主表單彈出窗口-->
<el-dialog
v-cloak
title="維護(hù)"
:visible.sync="InputBaseInfoDialogData.dialogVisible"
:close-on-click-modal="InputBaseInfoDialogData.showCloseButton"
top="5vh"
:show-close="InputBaseInfoDialogData.showCloseButton"
:fullscreen="InputBaseInfoDialogData.dialogFullScreen"
>
<!--彈窗頭部header-->
<div slot="title" style="margin-bottom: 10px">
<div align="left" style="float:left">
<h3>定時(shí)任務(wù)管理</h3>
</div>
<div align="right">
<el-button type="text" title="全屏顯示" @click="resizeInputBaseInfoDialogMax()"><i class="el-icon-arrow-up" /></el-button>
<el-button type="text" title="以彈出窗口形式顯示" @click="resizeInputBaseInfoDialogNormal()"><i class="el-icon-arrow-down" /></el-button>
<el-button type="text" title="關(guān)閉" @click="closeInputBaseInfoDialog()"><i class="el-icon-error" /></el-button>
</div>
</div>
<!--彈窗頭部header-->
<!--彈窗表單-->
<el-form
ref="InputBaseInfoForm"
:status-icon="InputBaseInfoDialogData.statusIcon"
:model="InputBaseInfoDialogData.data"
class="demo-ruleForm"
>
<el-form-item label="原任務(wù)名稱(chēng)" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="jobName">
{{ InputBaseInfoDialogData.data.oldJobName }}【修改任務(wù)時(shí)使用】
</el-form-item>
<el-form-item label="原任務(wù)分組" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="jobGroup">
{{ InputBaseInfoDialogData.data.oldJobGroup }}【修改任務(wù)時(shí)使用】
</el-form-item>
<el-form-item label="任務(wù)名稱(chēng)" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="jobName">
<el-input v-model="InputBaseInfoDialogData.data.jobName" auto-complete="off" />
</el-form-item>
<el-form-item label="任務(wù)分組" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="jobGroup">
<el-input v-model="InputBaseInfoDialogData.data.jobGroup" auto-complete="off" />
</el-form-item>
<el-form-item label="類(lèi)名" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="jobClassName">
<el-input v-model="InputBaseInfoDialogData.data.jobClassName" auto-complete="off" />
</el-form-item>
<el-form-item label="表達(dá)式" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="cronExpression">
<el-input v-model="InputBaseInfoDialogData.data.cronExpression" auto-complete="off" />
</el-form-item>
</el-form>
<!--彈窗表單-->
<!--彈窗尾部footer-->
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="saveInputBaseInfoForm()">保 存</el-button>
</div>
<!--彈窗尾部footer-->
</el-dialog>
<!--彈出窗口-->
<!--查看場(chǎng)所彈出窗口-->
<el-dialog
v-cloak
title="修改任務(wù)"
:visible.sync="ViewBaseInfoDialogData.dialogVisible"
:close-on-click-modal="ViewBaseInfoDialogData.showCloseButton"
top="5vh"
:show-close="ViewBaseInfoDialogData.showCloseButton"
:fullscreen="ViewBaseInfoDialogData.dialogFullScreen"
>
<!--彈窗頭部header-->
<div slot="title" style="margin-bottom: 10px">
<div align="left" style="float:left">
<h3>修改任務(wù)</h3>
</div>
<div align="right">
<el-button type="text" @click="dialogResize('ViewBaseInfoDialog',true)"><i class="el-icon-arrow-up" title="全屏顯示" /></el-button>
<el-button type="text" @click="dialogResize('ViewBaseInfoDialog',false)"><i
class="el-icon-arrow-down"
title="以彈出窗口形式顯示"
/></el-button>
<el-button type="text" @click="dialogClose('ViewBaseInfoDialog')"><i class="el-icon-error" title="關(guān)閉" /></el-button>
</div>
</div>
<!--彈窗頭部header-->
<!--彈窗表單-->
<el-form
ref="ViewBaseInfoForm"
:status-icon="ViewBaseInfoDialogData.statusIcon"
:model="ViewBaseInfoDialogData.data"
class="demo-ruleForm"
>
<el-form-item label="表達(dá)式" :label-width="ViewBaseInfoDialogData.formLabelWidth" prop="cronExpression">
{{ this.BaseTableData.currentRow.cronExpression }}
</el-form-item>
</el-form>
<!--彈窗表單-->
</el-dialog>
</div>
</template>
<script>
import {
getBlankJob,
fetchJobPage,
getUpdateObject,
saveJob,
pauseJob,
resumeJob,
deleteJob
} from '@/api/job'
export default {
name: 'Jobmanage',
data: function() {
return {
/**
* 后臺(tái)服務(wù)忙,防止重復(fù)提交的控制變量
* */
ServiceRunning: false,
/**
*表格和分頁(yè)組件
* */
BaseTableData: {
currentRow: {},
page: {
currentPage: 1,
pageSize: 20,
pageNum: 1,
pages: 1,
size: 5,
total: 1
},
/**
*主表格數(shù)據(jù)
* */
table: [],
/**
*勾選選中的數(shù)據(jù)
* */
selected: []
},
InputBaseInfoDialogData: {
data: {},
dialogVisible: false,
dialogFullScreen: false,
formLabelWidth: '180px',
showCloseButton: false,
statusIcon: true
},
ViewBaseInfoDialogData: {
cronExpression: '',
dialogVisible: false,
dialogFullScreen: true,
formLabelWidth: '180px'
}
}
},
/**
*初始化自動(dòng)執(zhí)行查詢(xún)表格數(shù)據(jù)--不用調(diào)整
**/
mounted: function() {
this.loadTableData()
},
methods: {
/**
* 查詢(xún)---------根據(jù)實(shí)際調(diào)整參數(shù)
*/
async loadTableData() {
if (this.ServiceRunning) {
this.$message({
message: '請(qǐng)不要重復(fù)點(diǎn)擊。',
type: 'warning'
})
return
}
this.ServiceRunning = true
const response = await fetchJobPage(this.BaseTableData.page)
if (response.code !== 100) {
this.ServiceRunning = false
this.$message({
message: response.message,
type: 'warning'
})
return
}
const {
data
} = response
this.BaseTableData.page.total = data.total
this.BaseTableData.table = data.records
this.ServiceRunning = false
},
/**
* 每頁(yè)大小調(diào)整事件
* @param val
*/
handlePageSizeChange(val) {
if (val != this.BaseTableData.page.pageSize) {
this.BaseTableData.page.pageSize = val
this.loadTableData()
}
},
/**
* 當(dāng)前面號(hào)調(diào)整事件
* @param val
*/
handlePageCurrentChange(val) {
if (val != this.BaseTableData.page.currentPage) {
this.BaseTableData.page.currentPage = val
this.loadTableData()
}
},
dialogResize(dialogName, toMax) {
VFC_dialogResize(dialogName, toMax)
},
resizeInputBaseInfoDialogMax() {
this.InputBaseInfoDialogData.dialogFullScreen = true
},
resizeInputBaseInfoDialogNormal() {
this.InputBaseInfoDialogData.dialogFullScreen = false
},
dialogClose(dialogName) {
},
closeInputBaseInfoDialog() {
this.InputBaseInfoDialogData.dialogVisible = false
this.loadTableData()
},
async getBlankForm() {
const response = await getBlankJob()
if (response.code !== 100) {
this.ServiceRunning = false
this.$message({
message: response.message,
type: 'warning'
})
return
}
const {
data
} = response
this.InputBaseInfoDialogData.data = data
},
async getUpdateForm(row) {
const response = await getUpdateObject(row)
if (response.code !== 100) {
this.ServiceRunning = false
this.$message({
message: response.message,
type: 'warning'
})
return
}
const {
data
} = response
this.InputBaseInfoDialogData.data = data
},
// 彈出對(duì)話框
handleadd() {
this.getBlankForm()
this.InputBaseInfoDialogData.dialogVisible = true
},
handleUpdate(row) {
if (row.triggerState !== 'PAUSED') {
this.$message({
message: '請(qǐng)先掛起任務(wù),再修改。',
type: 'warning'
})
return
}
this.getUpdateForm(row)
this.InputBaseInfoDialogData.dialogVisible = true
},
search() {
this.loadTableData()
},
/**
* 提交修改主表單
*/
async saveInputBaseInfoForm() {
if (this.ServiceRunning) {
this.$message({
message: '請(qǐng)不要重復(fù)點(diǎn)擊。',
type: 'warning'
})
return
}
this.ServiceRunning = true
const response = await saveJob(this.InputBaseInfoDialogData.data)
if (response.code !== 100) {
this.ServiceRunning = false
this.$message({
message: response.message,
type: 'warning'
})
return
}
this.ServiceRunning = false
this.$message({
message: '數(shù)據(jù)保存成功。',
type: 'success'
})
this.loadTableData()
},
async handlePause(row) {
if (this.ServiceRunning) {
this.$message({
message: '請(qǐng)不要重復(fù)點(diǎn)擊。',
type: 'warning'
})
return
}
this.ServiceRunning = true
const response = await pauseJob(row)
if (response.code !== 100) {
this.ServiceRunning = false
this.$message({
message: response.message,
type: 'warning'
})
return
}
this.ServiceRunning = false
this.$message({
message: '任務(wù)成功掛起。',
type: 'success'
})
this.loadTableData()
},
async handleResume(row) {
if (this.ServiceRunning) {
this.$message({
message: '請(qǐng)不要重復(fù)點(diǎn)擊。',
type: 'warning'
})
return
}
this.ServiceRunning = true
const response = await resumeJob(row)
if (response.code !== 100) {
this.ServiceRunning = false
this.$message({
message: response.message,
type: 'warning'
})
return
}
this.ServiceRunning = false
this.$message({
message: '任務(wù)成功恢復(fù)。',
type: 'success'
})
this.loadTableData()
},
async handleDelete(row) {
if (row.triggerState !== 'PAUSED') {
this.$message({
message: '請(qǐng)先掛起任務(wù),再刪除。',
type: 'warning'
})
return
}
if (this.ServiceRunning) {
this.$message({
message: '請(qǐng)不要重復(fù)點(diǎn)擊。',
type: 'warning'
})
return
}
this.ServiceRunning = true
const response = await deleteJob(row)
if (response.code !== 100) {
this.ServiceRunning = false
this.$message({
message: response.message,
type: 'warning'
})
return
}
this.ServiceRunning = false
this.$message({
message: '任務(wù)成功刪除。',
type: 'success'
})
this.loadTableData()
},
indexMethod(index) {
return this.BaseTableData.page.pageSize * (this.BaseTableData.page.currentPage - 1) + index + 1
},
dateTimeColFormatter(row, column, cellValue) {
return this.$commonUtils.dateTimeFormat(cellValue)
},
}
}
</script>
<style>
</style>api定義
job.js定義訪問(wèn)后臺(tái)接口的方式
import request from '@/utils/request'
//獲取空任務(wù)
export function getBlankJob() {
return request({
url: '/api/system/auth/job/blank',
method: 'get'
})
}
//獲取任務(wù)列表(分頁(yè))
export function fetchJobPage(data) {
return request({
url: '/api/system/auth/job/page',
method: 'post',
data
})
}
//獲取用于修改的任務(wù)信息
export function getUpdateObject(data) {
return request({
url: '/api/system/auth/job/dataforupdate',
method: 'post',
data
})
}
//保存任務(wù)
export function saveJob(data) {
return request({
url: '/api/system/auth/job/save',
method: 'post',
data
})
}
//暫停任務(wù)
export function pauseJob(data) {
return request({
url: '/api/system/auth/job/pause',
method: 'post',
data
})
}
//恢復(fù)任務(wù)
export function resumeJob(data) {
return request({
url: '/api/system/auth/job/resume',
method: 'post',
data
})
}
//刪除任務(wù)
export function deleteJob(data) {
return request({
url: '/api/system/auth/job/delete',
method: 'post',
data
})
}后端整合配置類(lèi)單獨(dú)數(shù)據(jù)源配置
Quartz會(huì)自動(dòng)創(chuàng)建11張數(shù)據(jù)表,數(shù)據(jù)源可以與系統(tǒng)主數(shù)據(jù)源相同,也可以獨(dú)立設(shè)置。

筆者建議單獨(dú)設(shè)置Quartz數(shù)據(jù)源。在配置文件 application.yml 添加以下內(nèi)容
base2048:
job:
enable: true
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/base2048job?useSSL=false&serverTimezone=UTC&autoReconnect=true&allowPublicKeyRetrieval=true&useOldAliasMetadataBehavior=true
username: root
password: mysql數(shù)據(jù)源配置類(lèi)如下:
@Configuration
public class QuartzDataSourceConfig {
@Primary
@Bean(name = "defaultDataSource")
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Bean(name = "quartzDataSource")
@QuartzDataSource
@ConfigurationProperties(prefix = "base2048.job.datasource")
public DruidDataSource quartzDataSource() {
return new DruidDataSource();
}
}調(diào)度器配置
在 resources 下添加 quartz.properties 文件,內(nèi)容如下:
# 固定前綴org.quartz # 主要分為scheduler、threadPool、jobStore、plugin等部分 # # org.quartz.scheduler.instanceName = DefaultQuartzScheduler org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false org.quartz.scheduler.wrapJobExecutionInUserTransaction = false <!-- 每個(gè)集群節(jié)點(diǎn)要有獨(dú)立的instanceId --> org.quartz.scheduler.instanceId = 'AUTO' # 實(shí)例化ThreadPool時(shí),使用的線程類(lèi)為SimpleThreadPool org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool # threadCount和threadPriority將以setter的形式注入ThreadPool實(shí)例 # 并發(fā)個(gè)數(shù) org.quartz.threadPool.threadCount = 15 # 優(yōu)先級(jí) org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true org.quartz.jobStore.misfireThreshold = 5000 # 默認(rèn)存儲(chǔ)在內(nèi)存中 #org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore #持久化 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.jobStore.dataSource = qzDS org.quartz.dataSource.qzDS.maxConnections = 10
調(diào)度器配置類(lèi)內(nèi)容如下:
@Configuration
public class SchedulerConfig {
@Autowired
private MyJobFactory myJobFactory;
@Value("${base2048.job.enable:false}")
private Boolean JOB_LOCAL_RUNING;
@Value("${base2048.job.datasource.driver-class-name}")
private String dsDriver;
@Value("${base2048.job.datasource.url}")
private String dsUrl;
@Value("${base2048.job.datasource.username}")
private String dsUser;
@Value("${base2048.job.datasource.password}")
private String dsPassword;
@Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setOverwriteExistingJobs(true);
// 延時(shí)啟動(dòng)
factory.setStartupDelay(20);
// 用于quartz集群,QuartzScheduler 啟動(dòng)時(shí)更新己存在的Job
// factory.setOverwriteExistingJobs(true);
// 加載quartz數(shù)據(jù)源配置
factory.setQuartzProperties(quartzProperties());
// 自定義Job Factory,用于Spring注入
factory.setJobFactory(myJobFactory);
// 在com.neusoft.jn.gpbase.quartz.job.BaseJobTemplate 同樣出現(xiàn)該配置
//原因 : qrtz 在集群模式下 存在 同一個(gè)任務(wù) 一個(gè)在A服務(wù)器任務(wù)被分配出去 另一個(gè)B服務(wù)器任務(wù)不再分配的情況.
//
if(!JOB_LOCAL_RUNING){
// 設(shè)置調(diào)度器自動(dòng)運(yùn)行
factory.setAutoStartup(false);
}
return factory;
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
Properties properties = propertiesFactoryBean.getObject();
properties.setProperty("org.quartz.dataSource.qzDS.driver",dsDriver);
properties.setProperty("org.quartz.dataSource.qzDS.URL",dsUrl);
properties.setProperty("org.quartz.dataSource.qzDS.user",dsUser);
properties.setProperty("org.quartz.dataSource.qzDS.password",dsPassword);
return properties;
}
/*
* 通過(guò)SchedulerFactoryBean獲取Scheduler的實(shí)例
*/
@Bean(name="scheduler")
public Scheduler scheduler() throws Exception {
return schedulerFactoryBean().getScheduler();
}
}任務(wù)模板
Job基類(lèi)
public abstract class BaseJob implements Job, Serializable {
private static final String JOB_MAP_KEY = "self";
public static final String STATUS_RUNNING = "1";
public static final String STATUS_NOT_RUNNING = "0";
public static final String CONCURRENT_IS = "1";
public static final String CONCURRENT_NOT = "0";
/**
* 任務(wù)名稱(chēng)
*/
private String jobName;
/**
* 任務(wù)分組
*/
private String jobGroup;
/**
* 任務(wù)狀態(tài) 是否啟動(dòng)任務(wù)
*/
private String jobStatus;
/**
* cron表達(dá)式
*/
private String cronExpression;
/**
* 描述
*/
private String description;
/**
* 任務(wù)執(zhí)行時(shí)調(diào)用哪個(gè)類(lèi)的方法 包名+類(lèi)名
*/
private Class beanClass = this.getClass();
/**
* 任務(wù)是否有狀態(tài)
*/
private String isConcurrent;
/**
* Spring bean
*/
private String springBean;
/**
* 任務(wù)調(diào)用的方法名
*/
private String methodName;
/**
* 為了將執(zhí)行后的任務(wù)持久化到數(shù)據(jù)庫(kù)中
*/
@JsonIgnore
private JobDataMap dataMap = new JobDataMap();
public JobKey getJobKey(){
return JobKey.jobKey(jobName, jobGroup);// 任務(wù)名稱(chēng)和組構(gòu)成任務(wù)key
}
public JobDataMap getDataMap(){
if(dataMap.size() == 0){
dataMap.put(JOB_MAP_KEY,this);
}
return dataMap;
}
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public String getJobGroup() {
return jobGroup;
}
public void setJobGroup(String jobGroup) {
this.jobGroup = jobGroup;
}
public String getJobStatus() {
return jobStatus;
}
public void setJobStatus(String jobStatus) {
this.jobStatus = jobStatus;
}
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Class getBeanClass() {
return beanClass;
}
public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
public String getIsConcurrent() {
return isConcurrent;
}
public void setIsConcurrent(String isConcurrent) {
this.isConcurrent = isConcurrent;
}
public String getSpringBean() {
return springBean;
}
public void setSpringBean(String springBean) {
this.springBean = springBean;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
}Job模板類(lèi)
@Slf4j
public abstract class BaseJobTemplate extends BaseJob {
@Value("${base2048.job.enable:false}")
private Boolean JOB_LOCAL_RUNING;
@Override
public final void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
if (JOB_LOCAL_RUNING) {
try {
this.runing(jobExecutionContext);
} catch (Exception ex) {
throw new JobExecutionException(ex);
}
} else {
log.info("配置參數(shù)不允許在本機(jī)執(zhí)行定時(shí)任務(wù)");
}
}
public abstract void runing(JobExecutionContext jobExecutionContext);
}Job示例類(lèi)
業(yè)務(wù)Job從模板類(lèi)繼承。
@Slf4j
@Component
@DisallowConcurrentExecution
public class TestJob extends BaseJobTemplate {
@Override
public void runing(JobExecutionContext jobExecutionContext) {
try {
log.info("測(cè)試任務(wù)開(kāi)始:【{}】", Instant.now().atOffset(ZoneOffset.ofHours(8)));
System.out.println("============= 測(cè)試任務(wù)正在運(yùn)行 =====================");
System.out.println("============= Test job is running ===============");
log.info("測(cè)試任務(wù)結(jié)束:【{}】", Instant.now().atOffset(ZoneOffset.ofHours(8)));
} catch (Exception ex) {
log.error("測(cè)試任務(wù)異常:【{}】", Instant.now().atOffset(ZoneOffset.ofHours(8)));
log.error(ex.getMessage(), ex);
}
}
}管理功能
Controller
@RestController
@RequestMapping("/api/system/auth/job")
@Slf4j
public class QuartzJobController {
@Resource
private QuartzService quartzService;
@PostMapping("/save")
@ApiOperation(value = "保存添加或修改任務(wù)",notes = "保存添加或修改任務(wù)")
public ResultDTO addOrUpdate(@RequestBody JobUpdateDTO jobUpdateDTO) throws Exception {
if (StringUtils.isBlank(jobUpdateDTO.getOldJobName())) {
ResultDTO resultDTO = this.addSave(jobUpdateDTO);
return resultDTO;
} else {
/**
* 先刪除后添加
*/
JobDTO jobDTO = new JobDTO();
jobDTO.setJobName(jobUpdateDTO.getOldJobName());
jobDTO.setJobGroup(jobUpdateDTO.getOldJobGroup());
this.delete(jobDTO);
ResultDTO resultDTO = this.addSave(jobUpdateDTO);
return resultDTO;
}
}
private ResultDTO addSave(@RequestBody JobUpdateDTO jobUpdateDTO) throws Exception {
BaseJob job = (BaseJob) Class.forName(jobUpdateDTO.getJobClassName()).newInstance();
job.setJobName(jobUpdateDTO.getJobName());
job.setJobGroup(jobUpdateDTO.getJobGroup());
job.setDescription(jobUpdateDTO.getDescription());
job.setCronExpression(jobUpdateDTO.getCronExpression());
try {
quartzService.addJob(job);
return ResultDTO.success();
}catch (Exception ex){
log.error(ex.getMessage(),ex);
return ResultDTO.failureCustom("保存添加任務(wù)時(shí)服務(wù)發(fā)生意外情況。");
}
}
@PostMapping("/page")
@ApiOperation(value = "查詢(xún)?nèi)蝿?wù)",notes = "查詢(xún)?nèi)蝿?wù)")
public ResultDTO getJobPage(@RequestBody BasePageQueryVO basePageQueryVO) {
try {
IPage<JobDTO> jobDtoPage = quartzService.queryJob(basePageQueryVO.getCurrentPage(),basePageQueryVO.getPageSize());
return ResultDTO.success(jobDtoPage);
}catch (Exception ex){
log.error(ex.getMessage(),ex);
return ResultDTO.failureCustom("查詢(xún)?nèi)蝿?wù)時(shí)服務(wù)發(fā)生意外情況。");
}
}
@PostMapping("/pause")
@ApiOperation(value = "暫停任務(wù)",notes = "暫停任務(wù)")
public ResultDTO pause(@RequestBody JobDTO jobDTO) {
try {
quartzService.pauseJob(jobDTO.getJobName(),jobDTO.getJobGroup());
return ResultDTO.success();
}catch (Exception ex){
log.error(ex.getMessage(),ex);
return ResultDTO.failureCustom("暫停任務(wù)時(shí)服務(wù)發(fā)生意外情況。");
}
}
@PostMapping("/resume")
@ApiOperation(value = "恢復(fù)任務(wù)",notes = "恢復(fù)任務(wù)")
public ResultDTO resume(@RequestBody JobDTO jobDTO) {
try {
quartzService.resumeJob(jobDTO.getJobName(),jobDTO.getJobGroup());
return ResultDTO.success();
}catch (Exception ex){
log.error(ex.getMessage(),ex);
return ResultDTO.failureCustom("恢復(fù)任務(wù)時(shí)服務(wù)發(fā)生意外情況。");
}
}
@PostMapping("/delete")
@ApiOperation(value = "刪除任務(wù)",notes = "刪除任務(wù)")
public ResultDTO delete(@RequestBody JobDTO jobDTO) {
try {
if(quartzService.deleteJob(jobDTO.getJobName(),jobDTO.getJobGroup())) {
return ResultDTO.failureCustom("刪除失敗。");
}else{
return ResultDTO.success();
}
}catch (Exception ex){
log.error(ex.getMessage(),ex);
return ResultDTO.failureCustom("刪除任務(wù)時(shí)服務(wù)發(fā)生意外情況。");
}
}
@GetMapping("/blank")
public ResultDTO getBlankJobDTO(){
JobUpdateDTO jobUpdateDTO = new JobUpdateDTO();
jobUpdateDTO.setJobClassName("com.qiyuan.base2048.quartz.job.jobs.");
jobUpdateDTO.setCronExpression("*/9 * * * * ?");
return ResultDTO.success(jobUpdateDTO);
}
@PostMapping("/dataforupdate")
public ResultDTO getUpdateJobDTO(@RequestBody JobDTO jobDTO){
JobUpdateDTO jobUpdateDTO = JobDtoTransMapper.INSTANCE.map(jobDTO);
jobUpdateDTO.setOldJobName(jobDTO.getJobName());
jobUpdateDTO.setOldJobGroup(jobDTO.getJobGroup());
return ResultDTO.success(jobUpdateDTO);
}
}JobDTO
@Data
public class JobDTO {
private String jobClassName;
private String jobName;
private String jobGroup;
private String description;
private String cronExpression;
private String triggerName;
private String triggerGroup;
private String timeZoneId;
private String triggerState;
private Date startTime;
private Date nextFireTime;
private Date previousFireTime;
}JobUpdateDTO
@Data
public class JobUpdateDTO extends JobDTO{
private String oldJobName;
private String oldJobGroup;
}Service
@Service
@Slf4j
public class QuartzServiceImpl implements QuartzService {
/**
* Scheduler代表一個(gè)調(diào)度容器,一個(gè)調(diào)度容器可以注冊(cè)多個(gè)JobDetail和Trigger.當(dāng)Trigger和JobDetail組合,就可以被Scheduler容器調(diào)度了
*/
@Autowired
private Scheduler scheduler;
@Resource
private QrtzJobDetailsMapper qrtzJobDetailsMapper;
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
@Autowired
public QuartzServiceImpl(Scheduler scheduler){
this.scheduler = scheduler;
}
@Override
public IPage<JobDTO> queryJob(int pageNum, int pageSize) throws Exception{
List<JobDTO> jobList = null;
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
jobList = new ArrayList<>();
for (JobKey jobKey : jobKeys) {
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for (Trigger trigger : triggers) {
JobDTO jobDetails = new JobDTO();
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
jobDetails.setCronExpression(cronTrigger.getCronExpression());
jobDetails.setTimeZoneId(cronTrigger.getTimeZone().getDisplayName());
}
jobDetails.setTriggerGroup(trigger.getKey().getName());
jobDetails.setTriggerName(trigger.getKey().getGroup());
jobDetails.setJobGroup(jobKey.getGroup());
jobDetails.setJobName(jobKey.getName());
jobDetails.setStartTime(trigger.getStartTime());
jobDetails.setJobClassName(scheduler.getJobDetail(jobKey).getJobClass().getName());
jobDetails.setNextFireTime(trigger.getNextFireTime());
jobDetails.setPreviousFireTime(trigger.getPreviousFireTime());
jobDetails.setTriggerState(scheduler.getTriggerState(trigger.getKey()).name());
jobList.add(jobDetails);
}
}
} catch (SchedulerException e) {
e.printStackTrace();
}
IPage<JobDTO> jobDTOPage = new Page<>(pageNum,pageSize);
jobDTOPage.setRecords(jobList);
jobDTOPage.setTotal(jobList.size());
jobDTOPage.setCurrent(1);
jobDTOPage.setPages(1);
jobDTOPage.setSize(jobList.size());
return jobDTOPage;
}
/**
* 添加一個(gè)任務(wù)
* @param job
* @throws SchedulerException
*/
@Override
public void addJob(BaseJob job) throws SchedulerException {
/** 創(chuàng)建JobDetail實(shí)例,綁定Job實(shí)現(xiàn)類(lèi)
* JobDetail 表示一個(gè)具體的可執(zhí)行的調(diào)度程序,job是這個(gè)可執(zhí)行調(diào)度程序所要執(zhí)行的內(nèi)容
* 另外JobDetail還包含了這個(gè)任務(wù)調(diào)度的方案和策略**/
// 指明job的名稱(chēng),所在組的名稱(chēng),以及綁定job類(lèi)
JobDetail jobDetail = JobBuilder.newJob(job.getBeanClass())
.withIdentity(job.getJobKey())
.withDescription(job.getDescription())
.usingJobData(job.getDataMap())
.build();
/**
* Trigger代表一個(gè)調(diào)度參數(shù)的配置,什么時(shí)候去調(diào)度
*/
//定義調(diào)度觸發(fā)規(guī)則, 使用cronTrigger規(guī)則
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(job.getJobName(),job.getJobGroup())
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.startNow()
.build();
//將任務(wù)和觸發(fā)器注冊(cè)到任務(wù)調(diào)度中去
scheduler.scheduleJob(jobDetail,trigger);
//判斷調(diào)度器是否啟動(dòng)
if(!scheduler.isStarted()){
scheduler.start();
}
log.info(String.format("定時(shí)任務(wù):%s.%s-已添加到調(diào)度器!", job.getJobGroup(),job.getJobName()));
}
/**
* 根據(jù)任務(wù)名和任務(wù)組名來(lái)暫停一個(gè)任務(wù)
* @param jobName
* @param jobGroupName
* @throws SchedulerException
*/
@Override
public void pauseJob(String jobName,String jobGroupName) throws SchedulerException {
scheduler.pauseJob(JobKey.jobKey(jobName,jobGroupName));
}
/**
* 根據(jù)任務(wù)名和任務(wù)組名來(lái)恢復(fù)一個(gè)任務(wù)
* @param jobName
* @param jobGroupName
* @throws SchedulerException
*/
@Override
public void resumeJob(String jobName,String jobGroupName) throws SchedulerException {
scheduler.resumeJob(JobKey.jobKey(jobName,jobGroupName));
}
public void rescheduleJob(String jobName,String jobGroupName,String cronExpression,String description) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroupName);
// 表達(dá)式調(diào)度構(gòu)建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 按新的cronExpression表達(dá)式重新構(gòu)建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withDescription(description).withSchedule(scheduleBuilder).build();
// 按新的trigger重新設(shè)置job執(zhí)行
scheduler.rescheduleJob(triggerKey, trigger);
}
/**
* 根據(jù)任務(wù)名和任務(wù)組名來(lái)刪除一個(gè)任務(wù)
* @param jobName
* @param jobGroupName
* @throws SchedulerException
*/
@Override
public boolean deleteJob(String jobName,String jobGroupName) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName,jobGroupName);
scheduler.pauseTrigger(triggerKey); //先暫停
scheduler.unscheduleJob(triggerKey); //取消調(diào)度
boolean flag = scheduler.deleteJob(JobKey.jobKey(jobName,jobGroupName));
return flag;
}
private JobDTO createJob(String jobName, String jobGroup, Scheduler scheduler, Trigger trigger)
throws SchedulerException {
JobDTO job = new JobDTO();
job.setJobName(jobName);
job.setJobGroup(jobGroup);
job.setDescription("觸發(fā)器:" + trigger.getKey());
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
job.setTriggerState(triggerState.name());
if(trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger)trigger;
String cronExpression = cronTrigger.getCronExpression();
job.setCronExpression(cronExpression);
}
return job;
}
}至此,烤串完畢,火侯正好,外酥里嫩!

項(xiàng)目源碼倉(cāng)庫(kù)github
項(xiàng)目源碼倉(cāng)庫(kù)gitee
到此這篇關(guān)于Springboot-admin整合Quartz實(shí)現(xiàn)動(dòng)態(tài)管理定時(shí)任務(wù)的文章就介紹到這了,更多相關(guān)Springboot-admin整合Quartz內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Data JPA中的動(dòng)態(tài)查詢(xún)實(shí)例
本篇文章主要介紹了詳解Spring Data JPA中的動(dòng)態(tài)查詢(xún)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
Java多線程之ReentrantReadWriteLock源碼解析
這篇文章主要介紹了Java多線程之ReentrantReadWriteLock源碼解析,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-05-05
詳解SpringBoot和Mybatis配置多數(shù)據(jù)源
本篇文章主要介紹了詳解SpringBoot和Mybatis配置多數(shù)據(jù)源,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
java計(jì)算工作時(shí)間除去節(jié)假日以及雙休日
這篇文章主要為大家詳細(xì)介紹了java計(jì)算工作時(shí)間除去節(jié)假日以及雙休日的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
Spring動(dòng)態(tài)數(shù)據(jù)源實(shí)現(xiàn)讀寫(xiě)分離詳解
這篇文章主要為大家詳細(xì)介紹了Spring動(dòng)態(tài)數(shù)據(jù)源實(shí)現(xiàn)讀寫(xiě)分離,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07

