您好,登錄后才能下訂單哦!
這篇文章主要介紹“Springboot-admin怎么整合Quartz實現動態管理定時任務”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Springboot-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作為任務的展示控件,串起任務的創建、修改、刪除、掛起、恢復、狀態查看等功能。
<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()); // 自定義Job Factory,用于Spring注入 factory.setJobFactory(myJobFactory); // 在com.neusoft.jn.gpbase.quartz.job.BaseJobTemplate 同樣出現該配置 //原因 : qrtz 在集群模式下 存在 同一個任務 一個在A服務器任務被分配出去 另一個B服務器任務不再分配的情況. // if(!JOB_LOCAL_RUNING){ // 設置調度器自動運行 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; } /* * 通過SchedulerFactoryBean獲取Scheduler的實例 */ @Bean(name="scheduler") public Scheduler scheduler() throws Exception { return schedulerFactoryBean().getScheduler(); } }
Job基類
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"; /** * 任務名稱 */ private String jobName; /** * 任務分組 */ private String jobGroup; /** * 任務狀態 是否啟動任務 */ private String jobStatus; /** * cron表達式 */ private String cronExpression; /** * 描述 */ private String description; /** * 任務執行時調用哪個類的方法 包名+類名 */ private Class beanClass = this.getClass(); /** * 任務是否有狀態 */ private String isConcurrent; /** * Spring bean */ private String springBean; /** * 任務調用的方法名 */ private String methodName; /** * 為了將執行后的任務持久化到數據庫中 */ @JsonIgnore private JobDataMap dataMap = new JobDataMap(); public JobKey getJobKey(){ return JobKey.jobKey(jobName, jobGroup);// 任務名稱和組構成任務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模板類
@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("配置參數不允許在本機執行定時任務"); } } public abstract void runing(JobExecutionContext jobExecutionContext); }
業務Job從模板類繼承。
@Slf4j @Component @DisallowConcurrentExecution public class TestJob extends BaseJobTemplate { @Override public void runing(JobExecutionContext jobExecutionContext) { try { log.info("測試任務開始:【{}】", Instant.now().atOffset(ZoneOffset.ofHours(8))); System.out.println("============= 測試任務正在運行 ====================="); System.out.println("============= Test job is running ==============="); log.info("測試任務結束:【{}】", Instant.now().atOffset(ZoneOffset.ofHours(8))); } catch (Exception ex) { log.error("測試任務異常:【{}】", 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 = "保存添加或修改任務",notes = "保存添加或修改任務") 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("保存添加任務時服務發生意外情況。"); } } @PostMapping("/page") @ApiOperation(value = "查詢任務",notes = "查詢任務") 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("查詢任務時服務發生意外情況。"); } } @PostMapping("/pause") @ApiOperation(value = "暫停任務",notes = "暫停任務") 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("暫停任務時服務發生意外情況。"); } } @PostMapping("/resume") @ApiOperation(value = "恢復任務",notes = "恢復任務") 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("恢復任務時服務發生意外情況。"); } } @PostMapping("/delete") @ApiOperation(value = "刪除任務",notes = "刪除任務") 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("刪除任務時服務發生意外情況。"); } } @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代表一個調度容器,一個調度容器可以注冊多個JobDetail和Trigger.當Trigger和JobDetail組合,就可以被Scheduler容器調度了 */ @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; } /** * 添加一個任務 * @param job * @throws SchedulerException */ @Override public void addJob(BaseJob job) throws SchedulerException { /** 創建JobDetail實例,綁定Job實現類 * JobDetail 表示一個具體的可執行的調度程序,job是這個可執行調度程序所要執行的內容 * 另外JobDetail還包含了這個任務調度的方案和策略**/ // 指明job的名稱,所在組的名稱,以及綁定job類 JobDetail jobDetail = JobBuilder.newJob(job.getBeanClass()) .withIdentity(job.getJobKey()) .withDescription(job.getDescription()) .usingJobData(job.getDataMap()) .build(); /** * Trigger代表一個調度參數的配置,什么時候去調度 */ //定義調度觸發規則, 使用cronTrigger規則 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity(job.getJobName(),job.getJobGroup()) .withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression())) .startNow() .build(); //將任務和觸發器注冊到任務調度中去 scheduler.scheduleJob(jobDetail,trigger); //判斷調度器是否啟動 if(!scheduler.isStarted()){ scheduler.start(); } log.info(String.format("定時任務:%s.%s-已添加到調度器!", job.getJobGroup(),job.getJobName())); } /** * 根據任務名和任務組名來暫停一個任務 * @param jobName * @param jobGroupName * @throws SchedulerException */ @Override public void pauseJob(String jobName,String jobGroupName) throws SchedulerException { scheduler.pauseJob(JobKey.jobKey(jobName,jobGroupName)); } /** * 根據任務名和任務組名來恢復一個任務 * @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); // 表達式調度構建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 按新的cronExpression表達式重新構建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withDescription(description).withSchedule(scheduleBuilder).build(); // 按新的trigger重新設置job執行 scheduler.rescheduleJob(triggerKey, trigger); } /** * 根據任務名和任務組名來刪除一個任務 * @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); //取消調度 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("觸發器:" + 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; } }
關于“Springboot-admin怎么整合Quartz實現動態管理定時任務”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。