这篇文章主要介绍“Springboot-admin怎么整合Quartz实现动态管理定时任务”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Springboot-admin怎么整合Quartz实现动态管理定时任务”文章能帮助大家解决问题。
boot-admin整合Quartz实现动态管理定时任务
淄博烧烤爆红出了圈,当你坐在八大局的烧烤摊,面前是火炉、烤串、小饼和蘸料,音乐响起,啤酒倒满,烧烤灵魂的party即将开场的时候,你系统中的Scheduler(调试器),也自动根据设定的Trigger(触发器),从容优雅的启动了一系列的Job(后台定时任务)。工作一切早有安排,又何须费心劳神呢?因为boot-admin早已将Quartz这块肉串在了烤签上!
Quartz是一款Java编写的开源任务调度框架,同时它也是Spring默认的任务调度框架。它的作用其实类似于Timer定时器以及ScheduledExecutorService调度线程池,当然Quartz作为一个独立的任务调度框架表现更为出色,功能更强大,能够定义更为复杂的执行规则。
boot-admin 是一款采用前后端分离模式、基于 SpringCloud 微服务架构 + vue-element-admin 的 SaaS 后台管理框架。
那么boot-admin怎样才能将Quartz串成串呢?一共分三步:
加入依赖
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> </dependency>
前端整合
vue页面以el-table作为任务的展示控件,串起任务的创建、修改、删除、挂起、恢复、状态查看等功能。
vue页面
<template>
<div class="app-container" >
<!--功能按钮区-->
<div class="cl pd-5 bg-1 bk-gray">
<div align="left" >
<el-button size="mini" type="primary" @click="search()">查询</el-button>
<el-button size="mini" type="primary" @click="handleadd()">添加</el-button>
</div>
<div align="right">
<!--分页控件-->
<div >
<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>
<!--分页控件-->
</div>
</div>
<!--功能按钮区-->
<!--表格-->
<el-table max-height="100%" :data="BaseTableData.table" :border="true">
<el-table-column type="index" :index="indexMethod" />
<el-table-column prop="jobName" label="任务名称" width="100px" />
<el-table-column prop="jobGroup" label="任务所在组" width="100px" />
<el-table-column prop="jobClassName" label="任务类名" />
<el-table-column prop="cronExpression" label="表达式" width="120" />
<el-table-column prop="timeZoneId" label="时区" width="120" />
<el-table-column prop="startTime" label="开始" 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="状态" 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'">结束</p>
<p v-if="scope.row.triggerState=='ERROR'">错误</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="恢复" @click="handleResume(scope.row)">恢复</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="维护"
:visible.sync="InputBaseInfoDialogData.dialogVisible"
:close-on-click-modal="InputBaseInfoDialogData.showCloseButton"
top="5vh"
:show-close="InputBaseInfoDialogData.showCloseButton"
:fullscreen="InputBaseInfoDialogData.dialogFullScreen"
>
<!--弹窗头部header-->
<div slot="title" >
<div align="left" >
<h4>定时任务管理</h4>
</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="关闭" @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="原任务名称" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="jobName">
{{ InputBaseInfoDialogData.data.oldJobName }}【修改任务时使用】
</el-form-item>
<el-form-item label="原任务分组" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="jobGroup">
{{ InputBaseInfoDialogData.data.oldJobGroup }}【修改任务时使用】
</el-form-item>
<el-form-item label="任务名称" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="jobName">
<el-input v-model="InputBaseInfoDialogData.data.jobName" auto-complete="off" />
</el-form-item>
<el-form-item label="任务分组" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="jobGroup">
<el-input v-model="InputBaseInfoDialogData.data.jobGroup" auto-complete="off" />
</el-form-item>
<el-form-item label="类名" :label-width="InputBaseInfoDialogData.formLabelWidth" prop="jobClassName">
<el-input v-model="InputBaseInfoDialogData.data.jobClassName" auto-complete="off" />
</el-form-item>
<el-form-item label="表达式" :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>
<!--弹出窗口-->
<!--查看场所弹出窗口-->
<el-dialog
v-cloak
title="修改任务"
:visible.sync="ViewBaseInfoDialogData.dialogVisible"
:close-on-click-modal="ViewBaseInfoDialogData.showCloseButton"
top="5vh"
:show-close="ViewBaseInfoDialogData.showCloseButton"
:fullscreen="ViewBaseInfoDialogData.dialogFullScreen"
>
<!--弹窗头部header-->
<div slot="title" >
<div align="left" >
<h4>修改任务</h4>
</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="关闭" /></el-button>
</div>
</div>
<!--弹窗头部header-->
<!--弹窗表单-->
<el-form
ref="ViewBaseInfoForm"
:status-icon="ViewBaseInfoDialogData.statusIcon"
:model="ViewBaseInfoDialogData.data"
class="demo-ruleForm"
>
<el-form-item label="表达式" :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 {
/**
* 后台服务忙,防止重复提交的控制变量
* */
ServiceRunning: false,
/**
*表格和分页组件
* */
BaseTableData: {
currentRow: {},
page: {
currentPage: 1,
pageSize: 20,
pageNum: 1,
pages: 1,
size: 5,
total: 1
},
/**
*主表格数据
* */
table: [],
/**
*勾选选中的数据
* */
selected: []
},
InputBaseInfoDialogData: {
data: {},
dialogVisible: false,
dialogFullScreen: false,
formLabelWidth: '180px',
showCloseButton: false,
statusIcon: true
},
ViewBaseInfoDialogData: {
cronExpression: '',
dialogVisible: false,
dialogFullScreen: true,
formLabelWidth: '180px'
}
}
},
/**
*初始化自动执行查询表格数据--不用调整
**/
mounted: function() {
this.loadTableData()
},
methods: {
/**
* 查询---------根据实际调整参数
*/
async loadTableData() {
if (this.ServiceRunning) {
this.$message({
message: '请不要重复点击。',
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
},
/**
* 每页大小调整事件
* @param val
*/
handlePageSizeChange(val) {
if (val != this.BaseTableData.page.pageSize) {
this.BaseTableData.page.pageSize = val
this.loadTableData()
}
},
/**
* 当前面号调整事件
* @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
},
// 弹出对话框
handleadd() {
this.getBlankForm()
this.InputBaseInfoDialogData.dialogVisible = true
},
handleUpdate(row) {
if (row.triggerState !== 'PAUSED') {
this.$message({
message: '请先挂起任务,再修改。',
type: 'warning'
})
return
}
this.getUpdateForm(row)
this.InputBaseInfoDialogData.dialogVisible = true
},
search() {
this.loadTableData()
},
/**
* 提交修改主表单
*/
async saveInputBaseInfoForm() {
if (this.ServiceRunning) {
this.$message({
message: '请不要重复点击。',
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: '数据保存成功。',
type: 'success'
})
this.loadTableData()
},
async handlePause(row) {
if (this.ServiceRunning) {
this.$message({
message: '请不要重复点击。',
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: '任务成功挂起。',
type: 'success'
})
this.loadTableData()
},
async handleResume(row) {
if (this.ServiceRunning) {
this.$message({
message: '请不要重复点击。',
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: '任务成功恢复。',
type: 'success'
})
this.loadTableData()
},
async handleDelete(row) {
if (row.triggerState !== 'PAUSED') {
this.$message({
message: '请先挂起任务,再删除。',
type: 'warning'
})
return
}
if (this.ServiceRunning) {
this.$message({
message: '请不要重复点击。',
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: '任务成功删除。',
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定义访问后台接口的方式
import request from '@/utils/request'
//获取空任务
export function getBlankJob() {
return request({
url: '/api/system/auth/job/blank',
method: 'get'
})
}
//获取任务列表(分页)
export function fetchJobPage(data) {
return request({
url: '/api/system/auth/job/page',
method: 'post',
data
})
}
//获取用于修改的任务信息
export function getUpdateObject(data) {
return request({
url: '/api/system/auth/job/dataforupdate',
method: 'post',
data
})
}
//保存任务
export function saveJob(data) {
return request({
url: '/api/system/auth/job/save',
method: 'post',
data
})
}
//暂停任务
export function pauseJob(data) {
return request({
url: '/api/system/auth/job/pause',
method: 'post',
data
})
}
//恢复任务
export function resumeJob(data) {
return request({
url: '/api/system/auth/job/resume',
method: 'post',
data
})
}
//删除任务
export function deleteJob(data) {
return request({
url: '/api/system/auth/job/delete',
method: 'post',
data
})
}后端整合配置类单独数据源配置
Quartz会自动创建11张数据表,数据源可以与系统主数据源相同,也可以独立设置。
笔者建议单独设置Quartz数据源。在配置文件 application.yml 添加以下内容
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
数据源配置类如下:
@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();
}
}调度器配置
在 resources 下添加 quartz.properties 文件,内容如下:
# 固定前缀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 <!-- 每个集群节点要有独立的instanceId --> org.quartz.scheduler.instanceId = 'AUTO' # 实例化ThreadPool时,使用的线程类为SimpleThreadPool org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool # threadCount和threadPriority将以setter的形式注入ThreadPool实例 # 并发个数 org.quartz.threadPool.threadCount = 15 # 优先级 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true org.quartz.jobStore.misfireThreshold = 5000 # 默认存储在内存中 #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
调度器配置类内容如下:
@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);
// 延时启动
factory.setStartupDelay(20);
// 用于quartz集群,QuartzScheduler 启动时更新己存在的Job
// factory.setOverwriteExistingJobs(true);
// 加载quartz数据源配置
factory.setQuartzProperties(quartzProperties());
&