Springboot集成Quartz定时任务(附接口DEMO)

目录结构

image.png

SQL脚本

-- 1.1. qrtz_blob_triggers : 以Blob 类型存储的触发器。
-- 1.2. qrtz_calendars:存放日历信息, quartz可配置一个日历来指定一个时间范围。
-- 1.3. qrtz_cron_triggers:存放cron类型的触发器。
-- 1.4. qrtz_fired_triggers:存放已触发的触发器。
-- 1.5. qrtz_job_details:存放一个jobDetail信息。
-- 1.6. qrtz_job_listeners:job监听器。
-- 1.7. qrtz_locks: 存储程序的悲观锁的信息(假如使用了悲观锁)。
-- 1.8. qrtz_paused_trigger_graps:存放暂停掉的触发器。
-- 1.9. qrtz_scheduler_state:调度器状态。
-- 1.10. qrtz_simple_triggers:简单触发器的信息。
-- 1.11. qrtz_trigger_listeners:触发器监听器。
-- 1.12. qrtz_triggers:触发器的基本信息。
-- 1.13. sys_scheduler_job:自定义定时任务配置表。

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;

CREATE TABLE QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(190) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(190) NOT NULL,
    TRIGGER_GROUP VARCHAR(190) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(190) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
INSTANCE_NAME VARCHAR(190) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(190) NULL,
JOB_GROUP VARCHAR(190) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(190) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;

CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);

CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);

commit;

CREATE TABLE `sys_scheduler_job` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(100) NOT NULL COMMENT 'JOB名称',
  `group_name` varchar(60) DEFAULT NULL COMMENT '组名称',
  `job_class` varchar(200) DEFAULT NULL COMMENT 'JOB类名',
  `cron_expression` varchar(60) DEFAULT NULL COMMENT 'CRON表达式',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `status` int(8) DEFAULT '12781001' COMMENT '状态,启用:12781001,停用:12781002',
  `create_by` varchar(40) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(40) DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `delete_flag` int(8) DEFAULT '12781002' COMMENT '是否逻辑删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='定时任务配置表';


pom.xml

    <!-- Quartz任务调度 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>

quartz.properties

# 表前缀
org.quartz.jobStore.tablePrefix = QRTZ_

# 调度实例失效间隔时间 ms
org.quartz.jobStore.clusterCheckinInterval = 8000

# 是否加入集群
org.quartz.jobStore.isClustered = true

# 实际执行时间与下一次应该执行时间之间的差值,超过这个差值就不会执行,低于这个差值就会执行
# 超狗10000(10秒)会导致MISFIRE_INSTRUCTION_DO_NOTHING不起作用
org.quartz.jobStore.misfireThreshold = 6000

# 数据库持久化
org.quartz.jobStore.txIsolationLevelReadCommitted = true
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

# 调度标识名,集群中每个实例都使用相同名称
org.quartz.scheduler.instanceName = ClusterQuartz
org.quartz.scheduler.instanceId = AUTO

# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

# 并发个数
org.quartz.threadPool.threadCount = 10

# 优先级
org.quartz.threadPool.threadPriority = 5

SchedulerConfig

package com.yun.quartz.config;

import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;

@Configuration
public class SchedulerConfig {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public Scheduler scheduler() throws IOException {
        return schedulerFactoryBean().getScheduler();
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setSchedulerName("cluster_scheduler");
        factory.setOverwriteExistingJobs(true);
        factory.setDataSource(dataSource);
        factory.setQuartzProperties(quartzProperties());

        SchedulerJobFactory jobFactory = new SchedulerJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        factory.setJobFactory(jobFactory);
        factory.setStartupDelay(30);
        // 设置自动启动,默认为true
//      factory.setAutoStartup(true);
        return factory;
    }

    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean bean = new PropertiesFactoryBean();
        bean.setLocation(new ClassPathResource("/quartz.properties"));
        bean.afterPropertiesSet();
        return bean.getObject();
    }
}

SchedulerJobFactory

package com.yun.quartz.config;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

public class SchedulerJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }

}

QuartzController

package com.yun.quartz.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yun.quartz.entity.SchedulerJobDTO;
import com.yun.quartz.entity.SchedulerJobVO;
import com.yun.quartz.entity.SchedulerModeDTO;
import com.yun.quartz.service.QuartzService;
import com.yun.utils.common.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/quartz")
@Api(value = "QuartzController", tags = {"Quartz接口"})
public class QuartzController {

    @Autowired
    private QuartzService baseService;

    @ApiOperation(notes="添加JOB", value = "create")
    @PostMapping(value = "/create")
    public ResponseResult<?> create(@Validated @RequestBody SchedulerJobDTO scheduleJobDTO) {
        baseService.create(scheduleJobDTO);
        return ResponseResult.success();
    }

    @ApiOperation(notes="修改JOB", value = "update")
    @PutMapping(value = "/update")
    public ResponseResult<?> update(@Validated @RequestBody SchedulerJobDTO scheduleJobDTO) {
        baseService.update(scheduleJobDTO);
        return ResponseResult.success();
    }

    @ApiOperation(notes="删除JOB", value = "delete")
    @DeleteMapping(value = "/delete")
    public ResponseResult<?> delete(@RequestBody SchedulerModeDTO dto) {
        baseService.delete(dto.getId());
        return ResponseResult.success();
    }

    @ApiOperation(notes="批量删除JOB", value = "deleteBatch")
    @PutMapping(value = "/deleteBatch")
    public ResponseResult<?> deleteBatch(@RequestBody List<Integer> ids) {
        baseService.deleteBatch(ids);
        return ResponseResult.success();
    }

    @ApiOperation(notes="暂停JOB", value = "pause")
    @PutMapping(value = "/pause")
    public ResponseResult<?> pause(@RequestBody SchedulerModeDTO dto) {
        baseService.pause(dto.getId());
        return ResponseResult.success();
    }

    @ApiOperation(notes="批量暂停JOB", value = "pauseBatch")
    @PutMapping(value = "/pauseBatch")
    public ResponseResult<?> pauseBatch(@RequestBody List<Integer> ids) {
        baseService.pauseBatch(ids);
        return ResponseResult.success();
    }

    @ApiOperation(notes="恢复JOB", value = "resume")
    @PutMapping(value = "/resume")
    public ResponseResult<?> resume(@RequestBody SchedulerModeDTO dto) {
        baseService.resume(dto.getId());
        return ResponseResult.success();
    }

    @ApiOperation(notes="批量恢复JOB", value = "resumeBatch")
    @PutMapping(value = "/resumeBatch")
    public ResponseResult<?> resumeBatch(@RequestBody List<Integer> ids) {
        baseService.resumeBatch(ids);
        return ResponseResult.success();
    }

    @ApiOperation(notes="立即执行JOB", value = "runNow")
    @PutMapping(value = "/runNow")
    public ResponseResult<?> runNow(@RequestBody SchedulerModeDTO dto) {
        baseService.runNow(dto.getId());
        return ResponseResult.success();
    }

    @ApiOperation(notes="分页查询", value="searchDefault")
    @PostMapping("/searchDefault")
    public ResponseResult<IPage<SchedulerJobVO>> searchDefault(@RequestBody SchedulerJobDTO dto) {
        return ResponseResult.success(baseService.searchDefault(dto));
    }

    @ApiOperation(notes="查询明细", value = "getById")
    @GetMapping(value = "/getById")
    public ResponseResult<?> getById(@RequestParam(value = "id") Integer id) {
        return ResponseResult.success(baseService.getById(id));
    }

    @ApiOperation(value = "查询所有JOB")
    @GetMapping(value = "/queryAllJob")
    public List<Map<String, Object>> queryAllQuartzJob() {
        return baseService.queryAllJob();
    }
}

QuartzService

package com.yun.quartz.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yun.quartz.entity.SchedulerJobDTO;
import com.yun.quartz.entity.SchedulerJobVO;

import java.util.List;
import java.util.Map;

public interface QuartzService {

    void create(SchedulerJobDTO scheduleJobDTO);

    void update(SchedulerJobDTO scheduleJobDTO);

    void delete(Integer id);

    void deleteBatch(List<Integer> ids);

    void pause(Integer id);

    void pauseBatch(List<Integer> ids);

    void resume(Integer id);

    void resumeBatch(List<Integer> ids);

    void runNow(Integer id);

    SchedulerJobVO getById(Integer id);

    IPage<SchedulerJobVO> searchDefault(SchedulerJobDTO dto);

    List<Map<String, Object>> queryAllJob();

    List<Map<String, Object>> queryRunJob();
}

QuartzServiceImpl

package com.yun.quartz.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yun.constant.CommonConstant;
import com.yun.constant.DictConstant;
import com.yun.constant.ErrorConstant;
import com.yun.mapper.SchedulerJobMapper;
import com.yun.quartz.entity.SchedulerJobDTO;
import com.yun.quartz.entity.SchedulerJobPO;
import com.yun.quartz.entity.SchedulerJobVO;
import com.yun.utils.common.BizException;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.*;

/**
 * <p>
 * 定时任务表 服务实现类
 * </p>
 *
 * @author CNSTT
 * @since 2023-03-20
 */
@Slf4j
@Service
public class QuartzServiceImpl implements QuartzService {

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private SchedulerJobMapper schedulerJobMapper;

    @PostConstruct
    public void startScheduler() {
        try {
            scheduler.start();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * 新增JOB
     *
     * @param dto 传参
     */
    @Override
    @Transactional
    public void create(SchedulerJobDTO dto) {
        LambdaQueryWrapper<SchedulerJobPO> wrapper = new QueryWrapper<SchedulerJobPO>().lambda();
        wrapper.eq(SchedulerJobPO::getName, dto.getName());
        wrapper.eq(SchedulerJobPO::getGroupName, dto.getGroupName());
        List<SchedulerJobPO> list = schedulerJobMapper.selectList(wrapper);
        if (!CollectionUtils.isEmpty(list)) {
            throw new BizException("任务组和名称已存在,请检查");
        }

        try {
            // 任务名称和组构成任务key
            JobDetail jobDetail = JobBuilder.newJob((Class<? extends QuartzJobBean>) Class.forName(dto.getJobClass()))
                    .withIdentity(dto.getName(), dto.getGroupName())
                    .build();

            // 设置job参数
//            if (jobData!= null && jobData.size() > 0) {
//                jobDetail.getJobDataMap().putAll(jobData);
//            }

            // 触发器key
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(dto.getName(), dto.getGroupName())
                    .startAt(DateBuilder.futureDate(1, DateBuilder.IntervalUnit.SECOND))
                    .withSchedule(CronScheduleBuilder.cronSchedule(dto.getCronExpression())).startNow().build();

            // 把作业和触发器注册到任务调度中
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            log.error("【ERROR】定时任务类【{}】未找到==========", dto.getJobClass());
            throw new BizException("定时任务类【" + dto.getJobClass() + "】未找到");
        } catch (Exception e) {
            e.printStackTrace();
            throw new BizException("新增定时任务异常");
        }

        // 插入记录
        SchedulerJobPO po = new SchedulerJobPO();
        BeanUtils.copyProperties(dto, po);
        po.setCreateBy(CommonConstant.ADMIN_USER_ID);
        po.setCreateTime(LocalDateTime.now());
        po.setUpdateBy(CommonConstant.ADMIN_USER_ID);
        po.setUpdateTime(LocalDateTime.now());
        po.setDeleteFlag(DictConstant.DICT_NO);
        schedulerJobMapper.insert(po);
    }

    /**
     * 修改JOB
     *
     * @param dto 传参
     */
    @Override
    @Transactional
    public void update(SchedulerJobDTO dto) {
        SchedulerJobPO po = querySchedulerById(dto.getId());

        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(dto.getName(), dto.getGroupName());
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
                    .withSchedule(CronScheduleBuilder.cronSchedule(dto.getCronExpression())).build();

            // 重启触发器
            scheduler.rescheduleJob(triggerKey, trigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new BizException("修改定时任务异常");
        }

        // 修改记录
        BeanUtils.copyProperties(dto, po);
        po.setUpdateBy(CommonConstant.ADMIN_USER_ID);
        po.setUpdateTime(LocalDateTime.now());
        schedulerJobMapper.updateById(po);
    }

    /**
     * 删除JOB
     *
     *  @param id 主键
     */
    @Override
    @Transactional
    public void delete(Integer id) {
        SchedulerJobPO po = querySchedulerById(id);

        try {
            scheduler.deleteJob(new JobKey(po.getName(), po.getGroupName()));
        } catch (Exception e) {
            e.printStackTrace();
            throw new BizException("删除定时任务异常");
        }

        // 删除记录
        schedulerJobMapper.deleteById(id);
    }

    /**
     * 批量删除JOB
     *
     * @param ids 主键集合
     */
    @Override
    @Transactional
    public void deleteBatch(List<Integer> ids) {
        ids.forEach(this::delete);
    }

    /**
     * 暂停JOB
     *
     * @param id 主键
     */
    @Override
    @Transactional
    public void pause(Integer id) {
        SchedulerJobPO po = querySchedulerById(id);

        try {
            JobKey jobKey = JobKey.jobKey(po.getName(), po.getGroupName());
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new BizException("暂停定时任务异常");
        }

        // 修改记录
        po.setStatus(DictConstant.DICT_NO);
        po.setUpdateBy(CommonConstant.ADMIN_USER_ID);
        po.setUpdateTime(LocalDateTime.now());
        schedulerJobMapper.updateById(po);
    }

    /**
     * 批量暂停JOB
     *
     * @param ids 主键集合
     */
    @Override
    @Transactional
    public void pauseBatch(List<Integer> ids) {
        ids.forEach(this::pause);
    }

    /**
     * 恢复JOB
     *
     * @param id 主键
     */
    @Override
    @Transactional
    public void resume(Integer id) {
        SchedulerJobPO po = querySchedulerById(id);

        try {
            JobKey jobKey = JobKey.jobKey(po.getName(), po.getGroupName());
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new BizException("恢复定时任务异常");
        }

        // 修改记录
        po.setStatus(DictConstant.DICT_YES);
        po.setUpdateBy(CommonConstant.ADMIN_USER_ID);
        po.setUpdateTime(LocalDateTime.now());
        schedulerJobMapper.updateById(po);
    }

    /**
     * 批量恢复JOB
     *
     * @param ids 主键集合
     */
    @Override
    @Transactional
    public void resumeBatch(List<Integer> ids) {
        ids.forEach(this::resume);
    }

    /**
     * 立即执行JOB
     *
     * @param id 主键
     */
    @Override
    @Transactional
    public void runNow(Integer id) {
        SchedulerJobPO po = querySchedulerById(id);

        try {
            JobKey jobKey = JobKey.jobKey(po.getName(), po.getGroupName());
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new BizException("立即执行定时任务异常");
        }
    }

    /**
     * 根据ID查询定时任务
     *
     * @param id 主键
     * @return IPage<ScheduleJobDTO>
     */
    private SchedulerJobPO querySchedulerById(Integer id) {
        if (id == null) throw new BizException(ErrorConstant.ERROR_MSG_KEY_MISSING);

        SchedulerJobPO po = schedulerJobMapper.selectById(id);
        if (po == null) throw new BizException("定时任务" + ErrorConstant.ERROR_MSG_NOT_EXISTS);
        return po;
    }

    /**
     * 根据ID查询定时任务
     *
     * @param id 主键
     */
    @Override
    public SchedulerJobVO getById(Integer id) {
        SchedulerJobVO vo = new SchedulerJobVO();
        SchedulerJobPO po = querySchedulerById(id);
        BeanUtils.copyProperties(po, vo);
        return vo;
    }

    /**
     * 分页查询
     *
     * @return IPage<ScheduleJobDTO>
     */
    @Override
    public IPage<SchedulerJobVO> searchDefault(SchedulerJobDTO dto) {
        return schedulerJobMapper.searchDefault(new Page<>(dto.getCurrentPage(), dto.getPageSize()), dto);
    }

    /**
     * 获取所有计划中的任务列表
     *
     * @return List<Map<String, Object>>
     */
    @Override
    public List<Map<String, Object>> queryAllJob() {
        List<Map<String, Object>> jobList;
        try {
            GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
            Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
            jobList = new ArrayList<>();
            for (JobKey jobKey : jobKeys) {
                log.info("maps: {}", scheduler.getJobDetail(jobKey).getJobDataMap().getWrappedMap());
                List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                for (Trigger trigger : triggers) {
                    Map<String, Object> map = new HashMap<>();
                    map.put("jobName", jobKey.getName());
                    map.put("jobGroupName", jobKey.getGroup());
                    map.put("description", "触发器:" + trigger.getKey());
                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                    map.put("jobStatus", triggerState.name());
                    if (trigger instanceof CronTrigger) {
                        CronTrigger cronTrigger = (CronTrigger) trigger;
                        String cronExpression = cronTrigger.getCronExpression();
                        map.put("jobTime", cronExpression);
                    }
                    jobList.add(map);
                }
            }
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new BizException("query all jobs error!");
        }
        return jobList;
    }

    /**
     * 获取所有正在运行的job
     *
     * @return List<Map<String, Object>>
     */
    @Override
    public List<Map<String, Object>> queryRunJob() {
        List<Map<String, Object>> jobList;
        try {
            List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
            jobList = new ArrayList<>(executingJobs.size());
            for (JobExecutionContext executingJob : executingJobs) {
                Map<String, Object> map = new HashMap<>();
                JobDetail jobDetail = executingJob.getJobDetail();
                JobKey jobKey = jobDetail.getKey();
                Trigger trigger = executingJob.getTrigger();
                map.put("jobName", jobKey.getName());
                map.put("jobGroupName", jobKey.getGroup());
                map.put("description", "触发器:" + trigger.getKey());
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                map.put("jobStatus", triggerState.name());
                if (trigger instanceof CronTrigger) {
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    String cronExpression = cronTrigger.getCronExpression();
                    map.put("jobTime", cronExpression);
                }
                jobList.add(map);
            }
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new BizException("query run jobs error!");
        }
        return jobList;
    }
}

TimerPollingCronJob

package com.yun.quartz.job;

import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

@Slf4j
@DisallowConcurrentExecution
public class TimerPollingCronJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info("TimerPollingCronJob Start................");
        log.info("TimerPollingCronJob End................");
    }
}

SchedulerJobPO

package com.yun.quartz.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * <p>
 * 定时任务配置表
 * </p>
 *
 * @author CNSTT
 * @since 2023-03-17
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_scheduler_job")
@ApiModel(value="定时任务", description="定时任务配置表")
public class SchedulerJobPO implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "主键")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @ApiModelProperty(value = "任务名称")
    @TableField(value = "name")
    private String name;

    @ApiModelProperty(value = "组名称")
    @TableField(value = "group_name")
    private String groupName;

    @ApiModelProperty(value = "类名称")
    @TableField(value = "job_class")
    private String jobClass;

    @ApiModelProperty(value = "cron表达式")
    @TableField(value = "cron_expression")
    private String cronExpression;

    @ApiModelProperty(value = "备注")
    @TableField(value = "remark")
    private String remark;

    @ApiModelProperty(value = "任务状态")
    @TableField(value = "status")
    private Integer status;

    @ApiModelProperty(value = "创建人")
    @TableField(value = "create_by")
    private String createBy;

    @ApiModelProperty(value = "创建时间")
    @TableField(value = "create_time")
    private LocalDateTime createTime;

    @ApiModelProperty(value = "修改人")
    @TableField(value = "update_by")
    private String updateBy;

    @ApiModelProperty(value = "修改时间")
    @TableField(value = "update_time")
    private LocalDateTime updateTime;

    @ApiModelProperty(value = "是否逻辑删除")
    @TableField(value = "delete_flag")
    private Integer deleteFlag;
}

SchedulerJobDTO

package com.yun.quartz.entity;

import com.yun.entity.dto.PageDTO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.NotNull;
import java.io.Serializable;

@Data
public class SchedulerJobDTO extends PageDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "主键")
    private Integer id;

    @ApiModelProperty(value = "任务名称")
    @NotNull(message = "任务名称不可为空")
    private String name;

    @ApiModelProperty(value = "组名称")
    @NotNull(message = "组名称不可为空")
    private String groupName;

    @ApiModelProperty(value = "类名称")
    @NotNull(message = "类名称不可为空")
    private String jobClass;

    @ApiModelProperty(value = "cron表达式")
    @NotNull(message = "cron表达式不可为空")
    private String cronExpression;

    @ApiModelProperty(value = "任务状态")
    private Integer status;

    @ApiModelProperty(value = "备注")
    private String remark;
}

分页查询 /searchDefault

image.png

新建 /create & 编辑 /update

image.png

暂停 /pauseBatch & 恢复 /resumeBatch

image.png

删除 /deleteBatch & 立即执行 /runNow

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容