软件编程
位置:首页>> 软件编程>> java编程>> Springboot-admin整合Quartz实现动态管理定时任务的过程详解

Springboot-admin整合Quartz实现动态管理定时任务的过程详解

作者:超然楼  发布时间:2023-08-26 03:01:07 

标签:Springboot,admin,Quartz,定时任务

boot-admin整合Quartz实现动态管理定时任务

淄博烧烤爆红出了圈,当你坐在八大局的烧烤摊,面前是火炉、烤串、小饼和蘸料,音乐响起,啤酒倒满,烧烤灵魂的party即将开场的时候,你系统中的Scheduler(调试器),也自动根据设定的Trigger(触发器),从容优雅的启动了一系列的Job(后台定时任务)。工作一切早有安排,又何须费心劳神呢?因为boot-admin早已将Quartz这块肉串在了烤签上!
项目源码仓库github
项目源码仓库gitee

Springboot-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" style="background-color: #FFFFFF;">
   <!--功能按钮区-->
   <div class="cl pd-5 bg-1 bk-gray">
     <div align="left" style="float: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 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>
       <!--分页控件-->
     </div>
   </div>
   <!--功能按钮区-->
   <!--表格-->
   <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="任务名称" 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" style="margin-bottom: 10px">
       <div align="left" style="float:left">
         <h3>定时任务管理</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="关闭" @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" style="margin-bottom: 10px">
       <div align="left" style="float:left">
         <h3>修改任务</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="关闭" /></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张数据表,数据源可以与系统主数据源相同,也可以独立设置。

Springboot-admin整合Quartz实现动态管理定时任务的过程详解

笔者建议单独设置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示例类

业务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实现动态管理定时任务的过程详解

项目源码仓库github
项目源码仓库gitee

来源:https://www.cnblogs.com/soft1314/p/17357443.html

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com