写点什么

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

作者:Java你猿哥
  • 2023-04-28
    湖南
  • 本文字数:17794 字

    阅读完需:约 58 分钟

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

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


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>
复制代码

后端整合

配置类

单独数据源配置

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
复制代码

数据源配置类如下:

@Configurationpublic 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 = DefaultQuartzSchedulerorg.quartz.scheduler.rmi.export = falseorg.quartz.scheduler.rmi.proxy = falseorg.quartz.scheduler.wrapJobExecutionInUserTransaction = false
<!-- 每个集群节点要有独立的instanceId -->org.quartz.scheduler.instanceId = 'AUTO'# 实例化ThreadPool时,使用的线程类为SimpleThreadPoolorg.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool# threadCount和threadPriority将以setter的形式注入ThreadPool实例# 并发个数org.quartz.threadPool.threadCount = 15# 优先级org.quartz.threadPool.threadPriority = 5org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = trueorg.quartz.jobStore.misfireThreshold = 5000
# 默认存储在内存中#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore#持久化org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.jobStore.tablePrefix = QRTZ_org.quartz.jobStore.dataSource = qzDSorg.quartz.dataSource.qzDS.maxConnections = 10
复制代码

调度器配置类内容如下:

@Configurationpublic 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 模板类

@Slf4jpublic 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@DisallowConcurrentExecutionpublic 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")@Slf4jpublic 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


@Datapublic 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

@Datapublic class JobUpdateDTO  extends JobDTO{    private String oldJobName;    private String oldJobGroup;}
复制代码


Service

@Service@Slf4jpublic 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; }}
复制代码

至此,烤串完毕,火侯正好,外酥里嫩!


用户头像

Java你猿哥

关注

一只在编程路上渐行渐远的程序猿 2023-03-09 加入

关注我,了解更多Java、架构、Spring等知识

评论

发布
暂无评论
boot-admin整合Quartz实现动态管理定时任务_Java_Java你猿哥_InfoQ写作社区