SpingBoot整合Quartz框架实现动态定时任务(支持实时增删改查任务)

1.前言

2. 关于Quartz入门讲解推荐

3. 关于参考代码地址请进

  • 经朋友分享clone了下面地址的代码,发现里面刚好有介绍Quartz,本来一直都想弄个关于Quartz的demo,一直没弄,看完之后觉得有必要弄个demo演示一下,推荐大家clone大佬上传到gitHub上的代码,如果你不clone直接看我的demo也行,我下面的就是做了改动,初始化表也少了些
    https://github.com/xkcoding/spring-boot-demo.

4. 先看效果

  • 新增任务
    在这里插入图片描述
  • 查询、修改状态、删除等
    在这里插入图片描述

5. 准备工作

5.1 数据库

  • ① 初始化下面几张表:qrtz,创表即可,没有数据,创表语句后面贴,本来11张表,我这里用到这几个,所以建全部表的话,可以参考上面github上的
    在这里插入图片描述
  • 在页面新增任务之后会自动填充这几张表的数据,当然你也可以数据库里直接insert,就是麻烦一点,先截图简单看一下这几个表的数据结构
    在这里插入图片描述
    在这里插入图片描述
  • dog_play_plan 这个表是根据业务逻辑自己创建,后面介绍

5.2 Java后端配置等

5.2.1 pom 文件

  • 如下:
    在这里插入图片描述
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
     </dependency>
    

5.2.2 yml 配置文件

  • 如下:
    在这里插入图片描述
    spring:
      quartz:
        # 参见 org.springframework.boot.autoconfigure.quartz.QuartzProperties
        job-store-type: jdbc
        wait-for-jobs-to-complete-on-shutdown: true
        scheduler-name: SpringBootDemoScheduler
        properties:
          org.quartz.threadPool.threadCount: 5
          org.quartz.threadPool.threadPriority: 5
          org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
          org.quartz.jobStore.misfireThreshold: 5000
          org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
          org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
          # 在调度流程的第一步,也就是拉取待即将触发的triggers时,是上锁的状态,即不会同时存在多个线程拉取到相同的trigger的情况,也就避免的重复调度的危险。参考:https://segmentfault.com/a/1190000015492260
          org.quartz.jobStore.acquireTriggersWithinLock: true
    #      org.quartz.jobStore.tablePrefix: sys_
    

6. 核心代码(直接粘代码的往下)

6.1 vo、mapper 与 mapper.xml

  • 如图:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

6.2 controller、service

  • 先截一个新增,其他的下面贴代码
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

6.3 JobUtil.java

  • 如图:
    在这里插入图片描述

6.4 BaseJob、定时 job(HelloJob为例)

  • 如图:
    在这里插入图片描述
    在这里插入图片描述

6.5 job.html

  • 如图:
    在这里插入图片描述

6.6 测试看效果

  • ① 启动服务,新增任务,查看新增的任务
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • ② 停用任务
    在这里插入图片描述
    在这里插入图片描述
    如果再启用,点恢复即可,傻瓜操作不介绍了,主要还是代码实现

7. 实现一个业务场景

7.1 dog_play_plan——计划表

  • 只说一下表就行了,实体和mapper和xml略了
    在这里插入图片描述
    在这里插入图片描述

7.2 DogPlayPlanJob.java

  • 如下:
    在这里插入图片描述
    当然你也可以直接处理业务,不弄计划表,看自己情况,为啥弄个计划表,打个比方要发短信,SendMessageJob有一个,但是有的2分钟发一次有的5分钟发一次,所以你可以弄成同一个组sendMessage但是不同的任务名2分钟一次5分钟一次,但是你需要处理一下哪些业务数据是2分钟一次,哪些要5分钟一次,当然我这计划表可能不是更合理的,所以需要根据自己需要优化

7.3 测试

  • 启动服务,添加业务,测试如下:
    在这里插入图片描述
    在这里插入图片描述

7.3 后期可优化点

1. 控制添加任务的数据

  • 新增任务不能随意,所以可以将业务名称、全类名维护字典或枚举或者直接将表单数据维护到一张表里,勾选数据进行反显,确保数据的正确性,尤其是全类名或cron表达式

2. dog_play_plan 表

  • 这个表根据自己需求情况进行优化,我这里只是简单打个比方

3. 定时任务管理应做成权限控制

  • 这个功能相对来说还是比较重要的,可以页面控制,但是要弄成权限控制,不能随意增删改查
  • 好了,简单说这么多,自己看着优化即可

7.4 对上面7.3提的进行一个简单的优化

1. 用枚举维护jobGroupName 和 jobName

  • 为了避免新增任务时数据乱写,所以维护起来
    在这里插入图片描述

2. 新增 sys_job_manager 表——用来管理定时任务新增用

  • 表结构如下:
    在这里插入图片描述
    在这里插入图片描述
  • 用途
    此表后台可以开发者维护,页面新增任务的时候,取此表维护的数据,如果没有维护不允许乱新增任务

3. 控制添加任务的查询sql

  • 查询所有可添加的任务:
    在这里插入图片描述
  • 检查添加的任务是否维护
    在这里插入图片描述

4. controller修改如下

  • 如下
    在这里插入图片描述

5. 新增时,注意数据

  • 如下:
    在这里插入图片描述

6. 别忘了 dog_play_plan

  • 这个表看情况,没有对应的计划也可不维护,但是要先维护上面的任务,再新增这个计划
    在这里插入图片描述
    这个表,看自己需求情况进行优化,大概意思是这个意思

8.附代码

8.1 数据库建表

  • 按顺序如下:
    CREATE TABLE `qrtz_cron_triggers` (
      `SCHED_NAME` varchar(120) NOT NULL,
      `TRIGGER_NAME` varchar(200) NOT NULL,
      `TRIGGER_GROUP` varchar(200) NOT NULL,
      `CRON_EXPRESSION` varchar(200) NOT NULL,
      `TIME_ZONE_ID` varchar(80) DEFAULT NULL,
      PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `qrtz_fired_triggers` (
      `SCHED_NAME` varchar(120) NOT NULL,
      `ENTRY_ID` varchar(95) NOT NULL,
      `TRIGGER_NAME` varchar(200) NOT NULL,
      `TRIGGER_GROUP` varchar(200) NOT NULL,
      `INSTANCE_NAME` varchar(200) NOT NULL,
      `FIRED_TIME` bigint(20) NOT NULL,
      `SCHED_TIME` bigint(20) NOT NULL,
      `PRIORITY` int(11) NOT NULL,
      `STATE` varchar(16) NOT NULL,
      `JOB_NAME` varchar(200) DEFAULT NULL,
      `JOB_GROUP` varchar(200) DEFAULT NULL,
      `IS_NONCONCURRENT` bit(1) DEFAULT NULL,
      `REQUESTS_RECOVERY` bit(1) DEFAULT NULL,
      PRIMARY KEY (`SCHED_NAME`,`ENTRY_ID`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `qrtz_job_details` (
      `SCHED_NAME` varchar(120) NOT NULL,
      `JOB_NAME` varchar(200) NOT NULL,
      `JOB_GROUP` varchar(200) NOT NULL,
      `DESCRIPTION` varchar(250) DEFAULT NULL,
      `JOB_CLASS_NAME` varchar(250) NOT NULL,
      `IS_DURABLE` bit(1) NOT NULL,
      `IS_NONCONCURRENT` bit(1) NOT NULL,
      `IS_UPDATE_DATA` bit(1) NOT NULL,
      `REQUESTS_RECOVERY` bit(1) NOT NULL,
      `JOB_DATA` blob,
      PRIMARY KEY (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `qrtz_locks` (
      `SCHED_NAME` varchar(120) NOT NULL,
      `LOCK_NAME` varchar(40) NOT NULL,
      PRIMARY KEY (`SCHED_NAME`,`LOCK_NAME`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    
    CREATE TABLE `qrtz_paused_trigger_grps` (
      `SCHED_NAME` varchar(120) NOT NULL,
      `TRIGGER_GROUP` varchar(200) NOT NULL,
      PRIMARY KEY (`SCHED_NAME`,`TRIGGER_GROUP`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='不加启动报错';
    
    CREATE TABLE `qrtz_triggers` (
      `SCHED_NAME` varchar(120) NOT NULL,
      `TRIGGER_NAME` varchar(200) NOT NULL,
      `TRIGGER_GROUP` varchar(200) NOT NULL,
      `JOB_NAME` varchar(200) NOT NULL,
      `JOB_GROUP` varchar(200) NOT NULL,
      `DESCRIPTION` varchar(250) DEFAULT NULL,
      `NEXT_FIRE_TIME` bigint(20) DEFAULT NULL,
      `PREV_FIRE_TIME` bigint(20) DEFAULT NULL,
      `PRIORITY` int(11) DEFAULT NULL,
      `TRIGGER_STATE` varchar(16) NOT NULL,
      `TRIGGER_TYPE` varchar(8) NOT NULL,
      `START_TIME` bigint(20) NOT NULL,
      `END_TIME` bigint(20) DEFAULT NULL,
      `CALENDAR_NAME` varchar(200) DEFAULT NULL,
      `MISFIRE_INSTR` smallint(6) DEFAULT NULL,
      `JOB_DATA` blob,
      PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `qrtz_simple_triggers` (
      `SCHED_NAME` varchar(120) NOT NULL,
      `TRIGGER_NAME` varchar(200) NOT NULL,
      `TRIGGER_GROUP` varchar(200) NOT NULL,
      `REPEAT_COUNT` bigint(20) NOT NULL,
      `REPEAT_INTERVAL` bigint(20) NOT NULL,
      `TIMES_TRIGGERED` bigint(20) NOT NULL,
      PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
      CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='修改定时任务时用这个';
    

8.2 Java核心代码

1. vo、mapper 与 mapper.xml 等

  • ① JobForm.java
    package com.liu.susu.task.quartz.vobo.form;
    
    import lombok.Data;
    import lombok.experimental.Accessors;
    
    import javax.validation.constraints.NotBlank;
    
    @Data
    @Accessors(chain = true)
    public class JobForm {
        /**
         * 定时任务名
         */
        @NotBlank(message = "任务名称")
        private String jobName;
    
        /**
         * 任务组名
         */
        @NotBlank(message = "任务组名不能为空")
        private String jobGroupName;
    
        /**
         * 定时任务全类名
         */
        @NotBlank(message = "定时任务全类名")
        private String jobClassName;
    
        /**
         * 定时任务cron表达式
         */
        @NotBlank(message = "cron表达式不能为空")
        private String cronExpression;
    }
    
    
  • ② JobAndTriggerVo.java
    package com.liu.susu.task.quartz.vobo.vo;
    
    import lombok.Data;
    
    import java.math.BigInteger;
    
    @Data
    public class JobAndTriggerVo {
        /**
         * 定时任务名称
         */
        private String jobName;
        /**
         * 定时任务组
         */
        private String jobGroup;
        /**
         * 定时任务全类名
         */
        private String jobClassName;
        /**
         * 触发器名称
         */
        private String triggerName;
        /**
         * 触发器组
         */
        private String triggerGroup;
        /**
         * 重复间隔
         */
        private BigInteger repeatInterval;
        /**
         * 触发次数
         */
        private BigInteger timesTriggered;
        /**
         * cron 表达式
         */
        private String cronExpression;
        /**
         * 时区
         */
        private String timeZoneId;
        /**
         * 定时任务状态
         */
        private String triggerState;
    }
    
    
  • ③ JobMapper.java
    package com.liu.susu.mapper.task;
    
    import com.liu.susu.task.quartz.vobo.vo.JobAndTriggerVo;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    @Mapper
    @Repository
    public interface JobMapper {
    
        /**
         * 查询定时作业和触发器列表
         */
        List<JobAndTriggerVo> list();
    
    }
    
    
  • ④ JobMapper.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.liu.susu.mapper.task.JobMapper">
    
      <select id="list" resultType="com.liu.susu.task.quartz.vobo.vo.JobAndTriggerVo">
    	SELECT
    		job_details.`JOB_NAME`,
    		job_details.`JOB_GROUP`,
    		job_details.`JOB_CLASS_NAME`,
    		cron_triggers.`CRON_EXPRESSION`,
    		cron_triggers.`TIME_ZONE_ID`,
    		qrtz_triggers.`TRIGGER_NAME`,
    		qrtz_triggers.`TRIGGER_GROUP`,
    		qrtz_triggers.`TRIGGER_STATE`
    	FROM
    		`QRTZ_JOB_DETAILS` job_details
    		LEFT JOIN `QRTZ_CRON_TRIGGERS` cron_triggers ON job_details.`JOB_NAME` = cron_triggers.`TRIGGER_NAME`
    		AND job_details.`JOB_GROUP` = cron_triggers.`TRIGGER_GROUP`
    		LEFT JOIN `QRTZ_TRIGGERS` qrtz_triggers ON qrtz_triggers.`TRIGGER_NAME` = job_details.`JOB_NAME`
    		AND qrtz_triggers.`TRIGGER_GROUP` = job_details.`JOB_GROUP`
        </select>
    </mapper>
    

2. controller、service

  • ① JobController.java
    package com.liu.susu.controller.task;
    
    import cn.hutool.core.util.ObjectUtil;
    import cn.hutool.core.util.StrUtil;
    import com.github.pagehelper.PageInfo;
    import com.liu.susu.common.ResultData;
    import com.liu.susu.common.ReturnCode;
    import com.liu.susu.service.task.JobService;
    import com.liu.susu.task.quartz.vobo.vo.JobAndTriggerVo;
    import com.liu.susu.task.quartz.vobo.form.JobForm;
    import lombok.extern.slf4j.Slf4j;
    import org.quartz.SchedulerException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import javax.validation.Valid;
    
    
    @RestController
    @RequestMapping("/job")
    @Slf4j
    public class JobController {
        private final JobService jobService;
    
        @Autowired
        public JobController(JobService jobService) {
            this.jobService = jobService;
        }
    
        /**
         * 保存定时任务
         */
        @PostMapping
        public ResultData addJob(@Valid JobForm form) {
            try {
                jobService.addJob(form);
                return new ResultData(ReturnCode.INSERT_SUCCESS);
            } catch (Exception e) {
                return new ResultData(ReturnCode.FAIL_999999);
            }
        }
    
        /**
         * 暂停定时任务
         */
        @PutMapping(params = "pause")
        public ResultData pauseJob(JobForm form) throws SchedulerException {
            if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) {
                return new ResultData(ReturnCode.FAIL_NO_PARAM, "参数不能为空!");
            }
            jobService.pauseJob(form);
            return new ResultData(ReturnCode.SUCCESS_000000, "暂停成功!");
        }
    
        /**
         * 恢复定时任务
         */
        @PutMapping(params = "resume")
        public ResultData resumeJob(JobForm form) throws SchedulerException {
            if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) {
                return new ResultData(ReturnCode.FAIL_NO_PARAM, "参数不能为空!");
            }
            jobService.resumeJob(form);
            return new ResultData(ReturnCode.SUCCESS_000000, "恢复成功!");
        }
    
        /**
         * 修改定时任务,定时时间
         */
        @PutMapping(params = "cron")
        public ResultData cronJob(@Valid JobForm form) {
            try {
                jobService.cronJob(form);
                return new ResultData(ReturnCode.UPDATE_SUCCESS, "修改成功!");
            } catch (Exception e) {
                return new ResultData(ReturnCode.FAIL_999999);
            }
        }
    
        /**
         * 删除定时任务
         */
        @DeleteMapping
        public ResultData deleteJob(JobForm form) throws SchedulerException {
            if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) {
                return new ResultData(ReturnCode.FAIL_NO_PARAM, "参数不能为空!");
            }
            jobService.deleteJob(form);
            return new ResultData(ReturnCode.DELETE_SUCCESS, "删除成功!");
        }
    
        /**
         * 查询定时任务列表
         * @param currentPage
         * @param pageSize
         * @return
         */
        @RequestMapping
        public ResultData jobList(Integer currentPage, Integer pageSize) {
            if (ObjectUtil.isNull(currentPage)) {
                currentPage = 1;
            }
            if (ObjectUtil.isNull(pageSize)) {
                pageSize = 10;
            }
            PageInfo<JobAndTriggerVo> all = jobService.list(currentPage, pageSize);
            return new ResultData(ReturnCode.SELECT_SUCCESS, all);
        }
    
    }
    
  • ② JobService.java
    package com.liu.susu.service.task;
    
    import com.github.pagehelper.PageInfo;
    import com.liu.susu.task.quartz.vobo.vo.JobAndTriggerVo;
    import com.liu.susu.task.quartz.vobo.form.JobForm;
    import org.quartz.SchedulerException;
    
    public interface JobService {
        /**
         * 添加并启动定时任务
         */
        void addJob(JobForm form) throws Exception;
    
        /**
         * 删除定时任务
         */
        void deleteJob(JobForm form) throws SchedulerException;
    
        /**
         * 暂停定时任务
         */
        void pauseJob(JobForm form) throws SchedulerException;
    
        /**
         * 恢复定时任务
         */
        void resumeJob(JobForm form) throws SchedulerException;
    
        /**
         * 重新配置定时任务
         */
        void cronJob(JobForm form) throws Exception;
    
        /**
         * 查询定时任务列表
         */
        PageInfo<JobAndTriggerVo> list(Integer currentPage, Integer pageSize);
    }
    
    
  • ③ JobServiceImpl.java
    package com.liu.susu.service.task.impl;
    
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.liu.susu.mapper.task.JobMapper;
    import com.liu.susu.service.task.JobService;
    import com.liu.susu.task.quartz.vobo.vo.JobAndTriggerVo;
    import com.liu.susu.task.quartz.vobo.form.JobForm;
    import com.liu.susu.utils.JobUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.quartz.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    
    @Service
    @Slf4j
    public class JobServiceImpl implements JobService {
        private final Scheduler scheduler;
        private final JobMapper jobMapper;
    
        @Autowired
        public JobServiceImpl(Scheduler scheduler, JobMapper jobMapper) {
            this.scheduler = scheduler;
            this.jobMapper = jobMapper;
        }
    
        /**
         * 添加并启动定时任务
         */
        @Override
        public void addJob(JobForm form) throws Exception {
            // 启动调度器
            scheduler.start();
    
            // 构建Job信息
    //        JobDetail jobDetail = JobBuilder.newJob(JobUtil.getClass(form.getJobClassName()).getClass()).withIdentity(form.getJobClassName(), form.getJobGroupName()).build();
            JobDetail jobDetail = JobBuilder.newJob(JobUtil.getClass(form.getJobClassName()).getClass()).withIdentity(form.getJobName(), form.getJobGroupName()).build();
    
            // Cron表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule(form.getCronExpression());
    
            //根据Cron表达式构建一个Trigger
    //        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(form.getJobClassName(), form.getJobGroupName()).withSchedule(cron).build();
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(form.getJobName(), form.getJobGroupName()).withSchedule(cron).build();
    
            try {
                scheduler.scheduleJob(jobDetail, trigger);
            } catch (SchedulerException e) {
                log.error("【定时任务】创建失败!", e);
                throw new Exception("【定时任务】创建失败!");
            }
    
        }
    
        /**
         * 删除定时任务
         */
        @Override
        public void deleteJob(JobForm form) throws SchedulerException {
            scheduler.pauseTrigger(TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName()));
            scheduler.unscheduleJob(TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName()));
            scheduler.deleteJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName()));
        }
    
        /**
         * 暂停定时任务
         */
        @Override
        public void pauseJob(JobForm form) throws SchedulerException {
            scheduler.pauseJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName()));
        }
    
        /**
         * 恢复定时任务
         */
        @Override
        public void resumeJob(JobForm form) throws SchedulerException {
            scheduler.resumeJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName()));
        }
    
        /**
         * 重新配置定时任务
         */
        @Override
        public void cronJob(JobForm form) throws Exception {
            try {
                TriggerKey triggerKey = TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName());
                // 表达式调度构建器
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(form.getCronExpression());
    
                CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
    
                // 根据Cron表达式构建一个Trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
    
                // 按新的trigger重新设置job执行
                scheduler.rescheduleJob(triggerKey, trigger);
            } catch (SchedulerException e) {
                log.error("【定时任务】更新失败!", e);
                throw new Exception("【定时任务】创建失败!");
            }
        }
    
        /**
         * 查询定时任务列表
         */
        @Override
        public PageInfo<JobAndTriggerVo> list(Integer currentPage, Integer pageSize) {
            PageHelper.startPage(currentPage, pageSize);
            List<JobAndTriggerVo> list = jobMapper.list();
            return new PageInfo<>(list);
        }
    }
    
    

3. JobUtil.java

  • 如下:
    package com.liu.susu.utils;
    
    
    import com.liu.susu.task.quartz.job.base.BaseJob;
    
    /**
     * 定时任务反射工具类
     */
    public class JobUtil {
        /**
         * 根据全类名获取Job实例
         */
        public static BaseJob getClass(String classname) throws Exception {
            Class<?> clazz = Class.forName(classname);
            return (BaseJob) clazz.newInstance();
        }
    }
    
    

4. BaseJob、HelloJob

  • ① BaseJob.java
    package com.liu.susu.task.quartz.job.base;
    
    import org.quartz.*;
    
    
    public interface BaseJob extends Job {
    
        @Override
        void execute(JobExecutionContext context) throws JobExecutionException;
    
    }
    
  • ② HelloJob.java
    package com.liu.susu.task.quartz.job;
    
    import com.liu.susu.task.quartz.job.base.BaseJob;
    import lombok.extern.slf4j.Slf4j;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobKey;
    
    import java.time.LocalDateTime;
    
    @Slf4j
    public class HelloJob implements BaseJob {
    
        @Override
        public void execute(JobExecutionContext context) {
    
            JobKey key = context.getJobDetail().getKey();
            if ("bb".equals(key.getName())){
                log.info("处理bb定时任务的业务…… 执行时间-->: {}", LocalDateTime.now());
            }
            if ("aa".equals(key.getName())){
                log.info("处理aa定时任务的业务…… 执行时间-->: {}",LocalDateTime.now());
            }
            log.info("处理aa、bb外的定时任务,执行时间-->: {}",LocalDateTime.now());
    
        }
    
    }
    
    

5. job.html

  • 如下:
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>spring-boot-demo-task-quartz</title>
        <link href="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.4.9/theme-chalk/index.css" rel="stylesheet">
        <script src="https://cdn.bootcss.com/vue/2.5.17/vue.min.js"></script>
        <script src="https://cdn.bootcss.com/vue-resource/1.5.1/vue-resource.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.4.9/index.js"></script>
    
        <style>
            #top {
                /*background: #20A0FF;*/
                padding: 5px;
                /*overflow: hidden*/
            }
        </style>
    
    </head>
    <body>
    <div id="job">
        <div id="top">
            <el-button size="small" type="primary" plain @click="search" :loading="loading" icon="el-icon-search">查询
            </el-button>
            <el-button size="small" type="primary" plain @click="handleadd" icon="el-icon-plus">添加</el-button>
        </div>
        <br/>
        <div>
            <el-table ref="jobTable" :data="tableData" style="width:100%" border center>
                <el-table-column prop="jobName" label="任务名称" show-overflow-tooltip align="center"></el-table-column>
                <el-table-column prop="jobGroup" label="任务所在组" sortable align="center"></el-table-column>
                <el-table-column prop="jobClassName" label="任务类名" align="center"></el-table-column>
                <el-table-column prop="triggerName" label="触发器名称" align="center"></el-table-column>
                <el-table-column prop="triggerGroup" label="触发器所在组" sortable align="center"></el-table-column>
                <el-table-column prop="cronExpression" label="表达式" align="center"></el-table-column>
                <el-table-column prop="timeZoneId" label="时区" align="center"></el-table-column>
                <el-table-column prop="triggerState" label="状态" align="center" :formatter="formatState"></el-table-column>
                <el-table-column label="操作" width="300" align="center">
                    <template scope="scope">
                        <el-button size="small" type="warning" @click="handlePause(scope.$index, scope.row)">
                            暂停
                        </el-button>
                        <el-button size="small" type="info" @click="handleResume(scope.$index, scope.row)">
                            恢复
                        </el-button>
                        <el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)">
                            删除
                        </el-button>
                        <el-button size="small" type="success" @click="handleUpdate(scope.$index, scope.row)">
                            修改
                        </el-button>
                    </template>
                </el-table-column>
            </el-table>
    
            <div align="center">
                <el-pagination
                        @size-change="handleSizeChange"
                        @current-change="handleCurrentChange"
                        :current-page="currentPage"
                        :page-sizes="[10, 20, 30, 40]"
                        :page-size="pagesize"
                        layout="total, sizes, prev, pager, next, jumper"
                        :total="totalCount">
                </el-pagination>
            </div>
        </div>
    
        <el-dialog title="添加任务" :visible.sync="dialogFormVisible">
            <el-form :model="form">
                <el-form-item label="任务名称" label-width="100px" style="width:90%">
                    <el-input v-model="form.jobName" auto-complete="off"></el-input>
                </el-form-item>
              <el-form-item label="任务类名称" label-width="100px" style="width:90%">
                <el-input v-model="form.jobClassName" auto-complete="off"></el-input>
              </el-form-item>
                <el-form-item label="任务分组" label-width="100px" style="width:90%">
                    <el-input v-model="form.jobGroup" auto-complete="off"></el-input>
                </el-form-item>
                <el-form-item label="表达式" label-width="100px" style="width:90%">
                    <el-input v-model="form.cronExpression" auto-complete="off"></el-input>
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click="dialogFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="add">确 定</el-button>
            </div>
        </el-dialog>
    
        <el-dialog title="修改任务" :visible.sync="updateFormVisible">
            <el-form :model="updateform">
                <el-form-item label="表达式" label-width="100px" style="width:90%">
                    <el-input v-model="updateform.cronExpression" auto-complete="off"></el-input>
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click="updateFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="update">确 定</el-button>
            </div>
        </el-dialog>
    </div>
    
    <footer align="center">
        <p>&copy; Quartz 定时任务管理</p>
    </footer>
    
    <script>
        var vue = new Vue({
            el: "#job",
            data: {
                //表格当前页数据
                tableData: [],
                //请求的URL
                url: 'job',
                //默认每页数据量
                pagesize: 10,
                //当前页码
                currentPage: 1,
                //查询的页码
                start: 1,
                //默认数据总数
                totalCount: 1000,
                //添加对话框默认可见性
                dialogFormVisible: false,
                //修改对话框默认可见性
                updateFormVisible: false,
                //提交的表单
                form: {
                    jobName: '',
                    jobGroup: '',
                    cronExpression: ''
                },
                updateform: {
                    jobName: '',
                    jobGroup: '',
                    cronExpression: ''
                },
                loading: false
            },
            methods: {
                // 格式化状态
                formatState: function (row, column, cellValue, index) {
                    if (row.triggerState === 'WAITING' || row.triggerState === 'ACQUIRED') {
                        return "运行中";
                    } else if (row.triggerState === 'PAUSED') {
                        return "暂停";
                    } else {
                        return "未知状态";
                    }
                },
                // 从服务器读取数据
                loadData: function (currentPage, pageSize) {
                    this.loading = true;
                    this.$http.get('job?' + 'currentPage=' + currentPage + '&pageSize=' + pageSize).then(function (res) {
                        console.log(res);
                        this.tableData = res.body.data.list;
                        this.totalCount = res.body.data.total;
                        this.loading = false;
                    }, function () {
                        console.log('failed');
                    });
                },
                // 删除任务
                handleDelete: function (index, row) {
                    this.$http.delete('job', {
                        params: {
                            "jobClassName": row.jobName,
                            "jobGroupName": row.jobGroup
                        }
                    }, {emulateJSON: true}).then(function (res) {
                        this.loadData(this.currentPage, this.pagesize);
                    }, function () {
                        console.log('failed');
                    });
                },
                // 暂停任务
                handlePause: function (index, row) {
                    this.$http.put('job?pause', {
                        "jobClassName": row.jobName,
                        "jobGroupName": row.jobGroup
                    }, {emulateJSON: true}).then(function (res) {
                        this.loadData(this.currentPage, this.pagesize);
                    }, function () {
                        console.log('failed');
                    });
                },
                // 恢复任务
                handleResume: function (index, row) {
                    this.$http.put('job?resume', {
                        "jobClassName": row.jobName,
                        "jobGroupName": row.jobGroup
                    }, {emulateJSON: true}).then(function (res) {
                        this.loadData(this.currentPage, this.pagesize);
                    }, function () {
                        console.log('failed');
                    });
                },
                // 搜索
                search: function () {
                    this.loadData(this.currentPage, this.pagesize);
                },
                // 弹出对话框
                handleadd: function () {
                    this.dialogFormVisible = true;
                },
                // 添加
                add: function () {
                    this.$http.post('job', {
                        "jobName": this.form.jobName,
                        "jobClassName": this.form.jobClassName,
                        "jobGroupName": this.form.jobGroup,
                        "cronExpression": this.form.cronExpression
                    }, {emulateJSON: true}).then(function (res) {
                        this.loadData(this.currentPage, this.pagesize);
                        this.dialogFormVisible = false;
                    }, function () {
                        console.log('failed');
                    });
                },
                // 更新
                handleUpdate: function (index, row) {
                    console.log(row);
                    this.updateFormVisible = true;
                    this.updateform.jobName = row.jobName;
                    this.updateform.jobGroup = row.jobGroup;
                },
                // 更新任务
                update: function () {
                    this.$http.put('job?cron',
                        {
                            "jobClassName": this.updateform.jobName,
                            "jobGroupName": this.updateform.jobGroup,
                            "cronExpression": this.updateform.cronExpression
                        }, {emulateJSON: true}
                    ).then(function (res) {
                        this.loadData(this.currentPage, this.pagesize);
                        this.updateFormVisible = false;
                    }, function () {
                        console.log('failed');
                    });
    
                },
                // 每页显示数据量变更
                handleSizeChange: function (val) {
                    this.pagesize = val;
                    this.loadData(this.currentPage, this.pagesize);
                },
                // 页码变更
                handleCurrentChange: function (val) {
                    this.currentPage = val;
                    this.loadData(this.currentPage, this.pagesize);
                }
            }
        });
    
        //载入数据
        vue.loadData(vue.currentPage, vue.pagesize);
    </script>
    
    </body>
    </html>
    

8.3 对于 7.4 优化后的代码 和 下载地址

8.3.1 优化后的代码

1. JobService.java
  • 如下:
    package com.liu.susu.service.task;
    
    import com.liu.susu.common.ResultData;
    import com.liu.susu.pojo.entity.sys.SysJobManagerEntity;
    import com.liu.susu.task.quartz.vobo.vo.JobAndTriggerVo;
    
    import java.util.List;
    
    public interface JobService {
    
        /**
         * 查询定时任务列表
         */
        List<JobAndTriggerVo> selectAllJobList();
    
        /**
         * 添加并启动定时任务
         */
        void addJob(SysJobManagerEntity form) throws Exception;
    
        /**
         * 暂停定时任务
         */
        ResultData pauseJob(String jobNameKey, String jobGroupNameCode);
    
        /**
         * 恢复定时任务
         */
        ResultData resumeJob(String jobNameKey, String jobGroupNameCode) ;
    
        /**
         * 重新配置定时任务
         */
        ResultData updateJobCron(String jobNameKey, String jobGroupNameCode,String newCron) ;
    
        /**
         * 删除定时任务
         */
        ResultData deleteJob(String jobNameKey, String jobGroupNameCode);
    
    
    }
    
    
2. JobServiceImpl.java
  • 如下:
    package com.liu.susu.service.task.impl;
    
    import com.liu.susu.common.ResultData;
    import com.liu.susu.common.ReturnCode;
    import com.liu.susu.mapper.task.JobMapper;
    import com.liu.susu.pojo.entity.sys.SysJobManagerEntity;
    import com.liu.susu.service.task.JobService;
    import com.liu.susu.task.quartz.vobo.vo.JobAndTriggerVo;
    import com.liu.susu.utils.JobUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.quartz.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    
    @Service
    @Slf4j
    public class JobServiceImpl implements JobService {
    
        @Autowired
        private Scheduler scheduler;
    
        @Autowired
        private JobMapper jobMapper;
    
        /**
         * 查询定时任务列表
         */
        @Override
        public List<JobAndTriggerVo> selectAllJobList() {
            return jobMapper.list();
        }
    
    
        /**
         * 添加并启动定时任务
         */
        @Override
        public void addJob(SysJobManagerEntity form) throws Exception {
            // 启动调度器
            scheduler.start();
    
            // 构建Job信息
            JobDetail jobDetail = JobBuilder.newJob(JobUtil.getClass(form.getJobClassName()).getClass())
                    .withIdentity(form.getJobNameKey(), form.getJobGroupNameCode()).build();
    
            // Cron表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule(form.getJobCron());
    
            //根据Cron表达式构建一个Trigger
            CronTrigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(form.getJobNameKey(), form.getJobGroupNameCode())
                    .withSchedule(cron).build();
    
            try {
                scheduler.scheduleJob(jobDetail, trigger);
            } catch (SchedulerException e) {
                log.error("【定时任务】创建失败!", e);
                throw new Exception("【定时任务】创建失败!");
            }
    
        }
    
        /**
         * 暂停定时任务
         */
        @Override
        public ResultData pauseJob(String jobNameKey, String jobGroupNameCode) {
            try {
                scheduler.pauseJob(JobKey.jobKey(jobNameKey, jobGroupNameCode));
                return new ResultData(ReturnCode.SUCCESS_000000,"暂停成功!");
            } catch (SchedulerException e) {
                e.printStackTrace();
                return new ResultData(ReturnCode.FAIL_999999);
            }
        }
    
        /**
         * 恢复定时任务
         */
        @Override
        public ResultData resumeJob(String jobNameKey, String jobGroupNameCode) {
            try {
                scheduler.resumeJob(JobKey.jobKey(jobNameKey, jobGroupNameCode));
                return new ResultData(ReturnCode.SUCCESS_000000,"定时任务恢复成功!");
            } catch (SchedulerException e) {
                e.printStackTrace();
                return new ResultData(ReturnCode.FAIL_999999);
            }
        }
    
        /**
         * 修改定时任务
         */
        @Override
        public ResultData updateJobCron(String jobNameKey, String jobGroupNameCode,String newCron) {
            try {
                TriggerKey triggerKey = TriggerKey.triggerKey(jobNameKey, jobGroupNameCode);
                // 表达式调度构建器
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(newCron);
    
                CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
    
                // 根据Cron表达式构建一个Trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
    
                // 按新的trigger重新设置job执行
                scheduler.rescheduleJob(triggerKey, trigger);
                return new ResultData(ReturnCode.UPDATE_SUCCESS,"定时任务修改成功!");
            } catch (SchedulerException e) {
                log.error("【定时任务】更新失败!", e);
                e.printStackTrace();
                return new ResultData(ReturnCode.FAIL_999999);
            }
        }
    
        /**
         * 删除定时任务
         */
        @Override
        public ResultData deleteJob(String jobNameKey, String jobGroupNameCode)  {
            try {
                scheduler.pauseTrigger(TriggerKey.triggerKey(jobNameKey, jobGroupNameCode));
                scheduler.unscheduleJob(TriggerKey.triggerKey(jobNameKey, jobGroupNameCode));
                scheduler.deleteJob(JobKey.jobKey(jobNameKey, jobGroupNameCode));
                return new ResultData(ReturnCode.DELETE_SUCCESS,"定时任务删除成功!");
            } catch (SchedulerException e) {
                e.printStackTrace();
                return new ResultData(ReturnCode.FAIL_999999);
            }
        }
    
    }
    
    
3.JobController2.java
  • 如下:
package com.liu.susu.controller.task;

import com.github.pagehelper.PageHelper;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.liu.susu.common.PageParam;
import com.liu.susu.common.ResultData;
import com.liu.susu.common.ReturnCode;
import com.liu.susu.mapper.task.SysJobManagerMapper;
import com.liu.susu.pojo.entity.sys.SysJobManagerEntity;
import com.liu.susu.service.task.JobService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Controller
@RequestMapping("/sys/quartz/task")
@Api(tags = "系统管理-->定时任务管理")
@ApiSupport(author = "乘风破浪",order = 3)
@Slf4j
public class JobController2 {

    @Autowired
    private JobService jobService;

    @Autowired
    private SysJobManagerMapper sysJobManagerMapper;


    @ApiOperationSupport(author = "乘风破浪",order = 1)
    @ApiOperation(value = "查询定时任务列表")
    @PostMapping("/selectAllJob")
    @ResponseBody
    public ResultData selectAllJob(@Valid PageParam pageParam) {
        if (pageParam!=null){
            PageHelper.startPage(pageParam.getPageNum(),pageParam.getPageSize());
        }
        return ResultData.getPageBaseResultData(pageParam,jobService.selectAllJobList());
    }

    @ApiOperationSupport(author = "乘风破浪",order = 2)
    @ApiOperation(value = "新增定时任务")
    @PostMapping("/addNewJob")
    @ResponseBody
    public ResultData addNewJob(@RequestBody SysJobManagerEntity form) {
        if (form!=null && StringUtils.isNotEmpty(form.getJobGroupNameCode())
        && StringUtils.isNotEmpty(form.getJobNameKey())){
            //添加任务之前,先校验是否在可添加的列表中有维护
            int count = sysJobManagerMapper.findSysJobManager(form.getJobGroupNameCode(), form.getJobNameKey());
            if (count>0){
                try {
                    jobService.addJob(form);
                    return new ResultData(ReturnCode.INSERT_SUCCESS);
                } catch (Exception e) {
                    return new ResultData(ReturnCode.FAIL_999999);
                }
            }else {
                return new ResultData(ReturnCode.FAIL_999998,"没有维护要新增的定时任务,请先找管理员维护!");
            }
        }
        return new ResultData(ReturnCode.FAIL_NO_PARAM, "表单参数为空,请检查参数");
    }

    @ApiOperationSupport(author = "乘风破浪",order = 3)
    @ApiOperation(value = "暂停任务")
    @GetMapping("/pauseJob")
    @ResponseBody
    public ResultData pauseJob(@RequestParam(value = "jobNameKey",required = true) String jobNameKey,
                               @RequestParam(value = "jobGroupNameCode",required = true) String jobGroupNameCode){

        if (StringUtils.isEmpty(jobNameKey) || StringUtils.isEmpty(jobGroupNameCode)){
            return new ResultData(ReturnCode.FAIL_NO_PARAM, "参数不能为空!");
        }
        return jobService.pauseJob(jobNameKey,jobGroupNameCode);
    }

    @ApiOperationSupport(author = "乘风破浪",order = 4)
    @ApiOperation(value = "恢复定时任务")
    @GetMapping("/resumeJob")
    @ResponseBody
    public ResultData resumeJob(@RequestParam(value = "jobNameKey") String jobNameKey,
                                @RequestParam(value = "jobGroupNameCode") String jobGroupNameCode) {
        if (StringUtils.isEmpty(jobNameKey) || StringUtils.isEmpty(jobGroupNameCode)){
            return new ResultData(ReturnCode.FAIL_NO_PARAM, "参数不能为空!");
        }
        return jobService.resumeJob(jobNameKey,jobGroupNameCode);
    }

    @ApiOperationSupport(author = "乘风破浪",order = 5)
    @ApiOperation(value = "修改定时任务,定时时间")
    @GetMapping("/updateJobCron")
    @ResponseBody
    public ResultData updateJobCron(@Param("jobNameKey") String jobNameKey,
                                    @Param("jobGroupNameCode") String jobGroupNameCode,
                                    @Param("cron") String newCron) {
        return jobService.updateJobCron(jobNameKey, jobGroupNameCode, newCron);
    }

    @ApiOperationSupport(author = "乘风破浪",order = 6)
    @ApiOperation(value = "删除定时任务")
    @GetMapping("/deleteJob")
    @ResponseBody
    public ResultData deleteJob(@Param("jobNameKey") String jobNameKey,
                                @Param("jobGroupNameCode") String jobGroupNameCode) {

        jobService.deleteJob(jobNameKey,jobGroupNameCode);
        return new ResultData(ReturnCode.DELETE_SUCCESS, "删除成功!");
    }


}

8.3.2 下载地址

  • 我把这个集成到自己项目里了,优化后的代码也在,代码太多,所以就贴上面部分代码了,其他的自己根据需要可自行下载:

    springboot_mybatis小项目.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@素素~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值