定时任务--quartz实践

定时任务--quartz实践

实际开发中简单定时任务可以使用quartz,本次是在springboot中使用。

依赖

        <!--spring quartz依赖-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

数据库

quartz需要存储定时任务关键信息,例如cron表达式、触发器等

## 保存job详细信息的表
CREATE TABLE IF NOT EXISTS QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,##job的名字
JOB_GROUP VARCHAR(200) NOT NULL,##job的所属组的名字
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,##job实现类的完全包名,quartz就是根据这个路径到classpath找到该job类
IS_DURABLE VARCHAR(1) NOT NULL,#是否持久化,把该属性设置为1,quartz会把job持久化到数据库中
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,#一个blob字段,存放持久化job对象
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

#保存trigger信息 触发器信息表
CREATE TABLE IF NOT EXISTS QRTZ_TRIGGERS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  TRIGGER_NAME VARCHAR(200) NOT NULL,#trigger的名字,
  TRIGGER_GROUP VARCHAR(200) NOT NULL,#trigger所属组的名字
  JOB_NAME VARCHAR(200) NOT NULL,#qrtz_job_details表job_name的外键
  JOB_GROUP VARCHAR(200) NOT NULL,#qrtz_job_details表job_group的外键
  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状态,设置为ACQUIRED,如果设置为WAITING,则job不会触发
  TRIGGER_TYPE VARCHAR(8) NOT NULL,#CRON
  START_TIME BIGINT(13) NOT NULL,
  END_TIME BIGINT(13) NULL,
  CALENDAR_NAME VARCHAR(200) 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;

#存储cron表达式表
CREATE TABLE IF NOT EXISTS QRTZ_CRON_TRIGGERS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  TRIGGER_NAME VARCHAR(200) NOT NULL,#triggers表trigger_name的外键
  TRIGGER_GROUP VARCHAR(200) NOT NULL,# qrtz_triggers表trigger_group的外键
  CRON_EXPRESSION VARCHAR(120) NOT NULL,#cron表达式
  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 IF NOT EXISTS QRTZ_SIMPLE_TRIGGERS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  TRIGGER_NAME VARCHAR(200) NOT NULL,
  TRIGGER_GROUP VARCHAR(200) 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 IF NOT EXISTS QRTZ_SIMPROP_TRIGGERS
(
  SCHED_NAME VARCHAR(120) NOT NULL,
  TRIGGER_NAME VARCHAR(200) NOT NULL,
  TRIGGER_GROUP VARCHAR(200) 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 IF NOT EXISTS QRTZ_BLOB_TRIGGERS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  TRIGGER_NAME VARCHAR(200) NOT NULL,
  TRIGGER_GROUP VARCHAR(200) 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 IF NOT EXISTS QRTZ_CALENDARS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  CALENDAR_NAME VARCHAR(200) NOT NULL,
  CALENDAR BLOB NOT NULL,
  PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
  ENGINE=InnoDB;

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

CREATE TABLE IF NOT EXISTS 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(13) NOT NULL,
  SCHED_TIME BIGINT(13) NOT NULL,
  PRIORITY INTEGER NOT NULL,
  STATE VARCHAR(16) NOT NULL,
  JOB_NAME VARCHAR(200) NULL,
  JOB_GROUP VARCHAR(200) NULL,
  IS_NONCONCURRENT VARCHAR(1) NULL,
  REQUESTS_RECOVERY VARCHAR(1) NULL,
  PRIMARY KEY (SCHED_NAME,ENTRY_ID))
  ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS QRTZ_SCHEDULER_STATE (
  SCHED_NAME VARCHAR(120) NOT NULL,
  INSTANCE_NAME VARCHAR(200) 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 IF NOT EXISTS QRTZ_LOCKS (
  SCHED_NAME VARCHAR(120) NOT NULL,
  LOCK_NAME VARCHAR(40) NOT NULL,
  PRIMARY KEY (SCHED_NAME,LOCK_NAME))
  ENGINE=InnoDB;

application配置

关于配置详细解释:https://blog.csdn.net/zixiao217/article/details/53091812

#主要分为scheduler、threadPool、jobStore、dataSource等部分

org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true
#在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略
org.quartz.scheduler.rmi.export=false
#如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false


#实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#threadCount和threadPriority将以setter的形式注入ThreadPool实例
#并发个数  如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间.
#只有1到100之间的数字是非常实用的
org.quartz.threadPool.threadCount=5
#优先级 默认值为5
org.quartz.threadPool.threadPriority=5
#可以是“true”或“false”,默认为false
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true

#在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)
org.quartz.jobStore.misfireThreshold=5000
# 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失
#org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

#持久化方式,默认存储在内存中,此处使用数据库方式
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作
# StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,
#因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题
org.quartz.jobStore.useProperties=true
#表前缀
org.quartz.jobStore.tablePrefix=QRTZ_
#数据源别名,自定义
org.quartz.jobStore.dataSource=qzDS

#使用阿里的druid作为数据库连接池
org.quartz.dataSource.qzDS.connectionProvider.class=org.example.config.DruidPoolingconnectionProvider
org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC
org.quartz.dataSource.qzDS.user=root
org.quartz.dataSource.qzDS.password=123456
org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.maxConnections=10
#设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏
#org.quartz.jobStore.isClustered=false

配置类

配置是否开始定时任务

quartz:
  status: false
@Component
//有些时候不需要时刻开启定时任务
@ConditionalOnProperty(value = "quartz.status")
public class PpeJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private transient 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;
    }

}

项目也要监控定时任务配置

数据库,可以根据自己情况自定义。

DROP TABLE IF EXISTS `cron_metadata`;
CREATE TABLE `cron_metadata`  (
  `cron_metadata_id` bigint NOT NULL,-- 主键
  `cron` varchar(255) CHARACTER  NULL DEFAULT NULL,-- cron表达式
  `cron_group` varchar(255) CHARACTER  NULL DEFAULT NULL, -- 分组
  `cron_code` varchar(255) CHARACTER  NULL DEFAULT NULL,-- code
  `cron_name` varchar(255) CHARACTER  NULL DEFAULT NULL,-- 名称
  `start_time` datetime NULL DEFAULT NULL,-- 开始时间
  `end_time` datetime NULL DEFAULT NULL,-- 结束时间
  `actived` tinyint NULL DEFAULT 0,-- 是否开启
  `status` tinyint NULL DEFAULT 0,-- 状态
  `params` varchar(255) CHARACTER  NULL DEFAULT NULL,-- 描述
  `url` varchar(255) CHARACTER  NULL DEFAULT NULL,
  `sys_default_flag` tinyint NULL DEFAULT 0,
  `seq_num` int NULL DEFAULT 0,
  `reg_hum_guid` varchar(36) CHARACTER  NULL DEFAULT NULL,
  `reg_hum_name` varchar(100) CHARACTER  NULL DEFAULT NULL,
  `reg_date` datetime NULL DEFAULT NULL,
  `upd_hum_guid` varchar(36) CHARACTER  NULL DEFAULT NULL,
  `upd_hum_name` varchar(100) CHARACTER  NULL DEFAULT NULL,
  `upd_date` datetime NULL DEFAULT NULL,
  `memo` varchar(4000) CHARACTER  NULL DEFAULT NULL,
  `tenant_id` varchar(36) CHARACTER  NULL DEFAULT NULL,
  PRIMARY KEY (`cron_metadata_id`) USING BTREE
);

工具类

/**
 * 定时任务的工具类
 */
public class TaskUtils
{

    /**
     * 产生JobKey
     *
     * @param job
     * @return
     */
    public static JobKey genCronJobKey(CronMetadata job)
    {
        return new  JobKey(job.getCronCode().trim(),job.getCronGroup().trim());
    }

    /**
     * 产生TriggerKey
     *
     * @param job
     * @return
     */
    public static TriggerKey  genCronTriggerKey(CronMetadata job)
    {
        return new  TriggerKey(job.getCronCode().trim(),job.getCronGroup().trim());
    }


    /**
     * 判断是否两个trigger key是否相等
     *
     * @param tk1
     * @param tk2
     * @return
     */
    public static boolean isTriggerKeyEqual(TriggerKey tk1, TriggerKey tk2)
    {
        return tk1.getName().equals(tk2.getName()) && ((tk1.getGroup() == null && tk2.getGroup() == null)
                || (tk1.getGroup() != null && tk1.getGroup().equals(tk2.getGroup())));
    }
}

启动和初始化配置

InitService类

@Component
@Slf4j
@ConditionalOnProperty(value = "quartz.status")
public class InitService {

    @Autowired
    private ScheduleService scheduleService;
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
    /**
     * 初始化
     */
    //@PostConstruct
    public void init() {

        // 初始化基于cron时间配置的任务列表
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            scheduler.shutdown();
            scheduleService.initCronJobs();
        } catch (Exception e) {
            log.error("init cron tasks error," + e.getMessage(), e);
//            throw new RuntimeException("初始化定时任务失败!");
        }

    }
}

定时任务初始化服务类

@Component
@Slf4j
public class ScheduleService {

    @Autowired
    private QrtzTriggersDao qrtzTriggersDao;

    @Resource
    private CronMetadataDao cronMetadataDao;

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    /**
     * 初始化任务(基于cron触发器)
     */
    public void initCronJobs() {

        syncTriggers();
        //查询启动状态值为true(1)的启动
        Iterable<CronMetadata> jobList = cronMetadataDao.selectList(
                new LambdaQueryWrapper<CronMetadata>().eq(CronMetadata::getActived, true));

        for (CronMetadata job : jobList) {
            scheduleCronJob(job);
        }

    }

    /***
     * 同步定时任务 保证任务列表和业务表一致
     * @throws SchedulerException
     */
    public void syncTriggers() {

        //未与业务表同步的触发器
        List<QrtzTriggers> triggers = qrtzTriggersDao.selectTriggers();
        TriggerKey triggerKey;
        for (QrtzTriggers qrtzTriggers : triggers) {
            //删除所有业务表中不存在的任务及触发器
            triggerKey = new TriggerKey(qrtzTriggers.getTriggerName(), qrtzTriggers.getTriggerGroup());
            try {
                schedulerFactoryBean.getScheduler().pauseTrigger(triggerKey);
                schedulerFactoryBean.getScheduler().unscheduleJob(triggerKey);
                schedulerFactoryBean.getScheduler().deleteJob(new JobKey(qrtzTriggers.getTriggerName(), qrtzTriggers.getTriggerGroup()));
            } catch (SchedulerException e) {
                log.error("同步定时任务异常:" + e.getMessage(), e);
                throw new RuntimeException("同步定时任务异常!");
            }

        }
    }

    /**
     * 安排任务(基于cron触发器)
     *
     * @param job job
     */
    public void scheduleCronJob(CronMetadata job) {

        if (job != null && StringUtils.isNotBlank(job.getCronCode()) && StringUtils.isNotBlank(job.getUrl())
                && StringUtils.isNotBlank(job.getCron())) {
            //判断状态无需启动则返回
            if (!job.getStatus()) {
                return;
            }
            JobKey jobKey = TaskUtils.genCronJobKey(job);
            try {
                if (!schedulerFactoryBean.getScheduler().isStarted()) {
                    schedulerFactoryBean.start();
                }
                if (!schedulerFactoryBean.getScheduler().checkExists(jobKey)) {
                    log.info("Add new cron job to scheduler, jobName = " + job.getCronCode());
                    this.newJobAndNewCronTrigger(job);
                } else {
                    log.info("Update cron job to scheduler, jobName = " + job.getCronCode());
                    this.updateCronTriggerOfJob(job);
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("当前传入url不合法或类不存在!");
            } catch (SchedulerException e) {
                throw new RuntimeException(e.getMessage());
            }

        } else {
            log.error("Method scheduleCronJob arguments are invalid.");
            throw new RuntimeException("当前传入参数为空或不合法!");
        }
    }

    /**
     * 新建job和trigger到scheduler(基于cron触发器)
     *
     * @param job
     * @throws SchedulerException
     * @throws ClassNotFoundException
     */
    public void newJobAndNewCronTrigger(CronMetadata job)
            throws SchedulerException, ClassNotFoundException {

        JobKey jobKey = TaskUtils.genCronJobKey(job);
        TriggerKey triggerKey = TaskUtils.genCronTriggerKey(job);

        String cronExpr = job.getCron();
        if (!isValidExpression(cronExpr)) {
            log.error("传入定时任务cron表达式不合法" + cronExpr);
            throw new ServiceException("传入定时任务cron表达式不合法!");
        }

        // get a Class object by string class name of job;
        String url = Constants.DEFAULT_PACKAGE + job.getUrl().trim();
        Class jobClass = Class.forName(url);
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobKey).withDescription(job.getCronName())
                .build();
        TriggerBuilder trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).forJob(jobKey)
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpr).withMisfireHandlingInstructionDoNothing());

        if (job.getStartTime() != null) {
            trigger.startAt(job.getStartTime());
        }
        if (job.getEndTime() != null) {
            trigger.endAt(job.getEndTime());
        }
        Trigger t = trigger.build();
        t.getJobDataMap().put("cronMetadata", job);
        schedulerFactoryBean.getScheduler().scheduleJob(jobDetail, t);
    }

    /**
     * 更新job的trigger(基于cron触发器)
     *
     * @param job
     * @throws SchedulerException
     */
    public void updateCronTriggerOfJob(CronMetadata job) throws SchedulerException {

        JobKey jobKey = TaskUtils.genCronJobKey(job);
        TriggerKey triggerKey = TaskUtils.genCronTriggerKey(job);
        String cronExpr = job.getCron().trim();

        List<? extends Trigger> triggers = schedulerFactoryBean.getScheduler().getTriggersOfJob(jobKey);

        for (int i = 0; triggers != null && i < triggers.size(); i++) {
            Trigger trigger = triggers.get(i);
            TriggerKey curTriggerKey = trigger.getKey();

            if (isValidExpression(job.getCron())) {
                TriggerBuilder newTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).forJob(jobKey)
                        .withSchedule(CronScheduleBuilder.cronSchedule(cronExpr)
                                .withMisfireHandlingInstructionDoNothing());
                if (job.getStartTime() != null) {
                    newTrigger.startAt(job.getStartTime());
                }
                if (job.getEndTime() != null) {
                    newTrigger.endAt(job.getEndTime());
                }
                Trigger t = newTrigger.build();
                t.getJobDataMap().put("cronMetadata", job);
                schedulerFactoryBean.getScheduler().rescheduleJob(curTriggerKey, t);
            } else {
                throw new ServiceException("当前定时任务cron[" + job.getCron() + "]表达式不合法!");
            }
            //如果是修改需求启动任务
            if (job.getStatus()) {
                this.resumeOne(job);
            }
//                }
//            } else {
//                scheduler.unscheduleJob(curTriggerKey);
//            }

        }

    }

    /***
     *  删除指定job
     * @param cronMetadata job
     * @throws SchedulerException
     */
    public void removeJob(CronMetadata cronMetadata) {

        JobKey jobKey = TaskUtils.genCronJobKey(cronMetadata);
        TriggerKey triggerKey = TaskUtils.genCronTriggerKey(cronMetadata);
        try {
            schedulerFactoryBean.getScheduler().pauseTrigger(triggerKey);
            schedulerFactoryBean.getScheduler().unscheduleJob(triggerKey);
            schedulerFactoryBean.getScheduler().deleteJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new ServiceException(500, "关闭定时任务失败!");
        }

    }

    /**
     * 暂停指定 job
     *
     * @param cronMetadata
     * @throws SchedulerException
     */
    public void pauseJob(CronMetadata cronMetadata) {


        JobKey jobKey = TaskUtils.genCronJobKey(cronMetadata);
        TriggerKey triggerKey = TaskUtils.genCronTriggerKey(cronMetadata);
        try {
            schedulerFactoryBean.getScheduler().pauseTrigger(triggerKey);
            schedulerFactoryBean.getScheduler().pauseJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new ServiceException(500, "暂停定时任务失败!");
        }

    }

    /**
     * 关闭所有job
     *
     * @throws SchedulerException
     */
    public void pauseAll() throws SchedulerException {

        schedulerFactoryBean.getScheduler().pauseAll();
    }

    /**
     * 暂停后启动指定Job
     */
    public void resumeOne(CronMetadata cronMetadata) throws SchedulerException {

        JobKey jobKey = TaskUtils.genCronJobKey(cronMetadata);
        TriggerKey triggerKey = TaskUtils.genCronTriggerKey(cronMetadata);
        schedulerFactoryBean.getScheduler().resumeJob(jobKey);
        schedulerFactoryBean.getScheduler().resumeTrigger(triggerKey);
    }

    /**
     * 启动所有Job
     */
    public void resumeAll(List<CronMetadata> cronMetadatas) throws SchedulerException {
        for (CronMetadata job : cronMetadatas) {
            scheduleCronJob(job);
        }
    }

    /**
     * 暂停后启动所以Job
     */
    public void test() throws SchedulerException {


        schedulerFactoryBean.getScheduler().getTriggerGroupNames();
    }
}

创建任务类

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
@Component
public class HelloJob extends QuartzJobBean {

    static long a = 0;

    @Autowired
    private CronInstanceService cronInstanceService;


    /***
     * job执行方法
     * @param jobExecutionContext
     */
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) {

        a++;
        CronMetadata cronMetadata = (CronMetadata)jobExecutionContext.getMergedJobDataMap().get("cronMetadata");

        CronInstance cronInstance = new CronInstance();
        cronInstance.setCronMetadataId(cronMetadata.getCronMetadataId()).setExecTime(new Date());
        try {
            if(a%5!=0){
                System.out.println(jobExecutionContext.getTrigger().getKey()+"我执行了!"+a);
                cronInstance.setSuccessFlag(true);
            }else {
                throw new RuntimeException("我报错了!");
            }
        }catch (Exception e){
            cronInstance.setSuccessFlag(false)
                    .setDetail(e.getMessage());
        }
        finally {
            cronInstanceService.save(cronInstance);
        }


    }
}

对于项目观察和配置的接口

Dao和mapper

@Mapper
public interface CronMetadataDao extends BaseMapper<CronMetadata> {

    CronMetadata selectOneByCronGroupAndCronCode(@Param("cronGroup") String cronGroup, @Param("cronCode") String cronCode);

    List<CronMetadata> selectAll();

    int addAll(CronMetadata cronMetadata);

    int delByCronGroupAndCronCode(@Param("cronGroup") String cronGroup, @Param("cronCode") String cronCode);

    int countByCronGroupAndCronCode(@Param("cronGroup") String cronGroup, @Param("cronCode") String cronCode);

}
<?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.puhua.server.plan.boot.mapper.CronMetadataDao">

    <resultMap id="BaseResultMap" type="com.puhua.server.plan.boot.entity.CronMetadata">
        <id property="cronMetadataId" column="cron_metadata_id" jdbcType="BIGINT"/>
        <result property="cron" column="cron" jdbcType="VARCHAR"/>
        <result property="cronGroup" column="cron_group" jdbcType="VARCHAR"/>
        <result property="cronName" column="cron_name" jdbcType="VARCHAR"/>
        <result property="cronCode" column="cron_code" jdbcType="VARCHAR"/>
        <result property="startTime" column="start_time" jdbcType="TIMESTAMP"/>
        <result property="endTime" column="end_time" jdbcType="TIMESTAMP"/>
        <result property="status" column="status" jdbcType="TINYINT"/>
        <result property="actived" column="actived" jdbcType="TINYINT"/>
        <result property="params" column="params" jdbcType="VARCHAR"/>
        <result property="url" column="url" jdbcType="VARCHAR"/>
        <result property="seqNum" column="seq_num" jdbcType="INTEGER"/>
        <result property="regHumGuid" column="reg_hum_guid" jdbcType="VARCHAR"/>
        <result property="regHumName" column="reg_hum_name" jdbcType="VARCHAR"/>
        <result property="regDate" column="reg_date" jdbcType="TIMESTAMP"/>
        <result property="updHumGuid" column="upd_hum_guid" jdbcType="VARCHAR"/>
        <result property="updHumName" column="upd_hum_name" jdbcType="VARCHAR"/>
        <result property="updDate" column="upd_date" jdbcType="TIMESTAMP"/>
        <result property="memo" column="memo" jdbcType="VARCHAR"/>
        <result property="tenantId" column="tenant_id" jdbcType="VARCHAR"/>
    </resultMap>

    <sql id="Base_Column_List">
        cron_metadata_id
        ,cron,cron_group,
        cron_code,cron_name,start_time,end_time,
        status,actived,params,url,
        seq_num,reg_hum_guid,
        reg_hum_name,reg_date,upd_hum_guid,
        upd_hum_name,upd_date,memo
    </sql>
    <select id="selectOneByCronGroupAndCronCode" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from cron_metadata
        where
        cron_group = #{cronGroup,jdbcType=VARCHAR}
        AND cron_code = #{cronCode,jdbcType=VARCHAR}
    </select>
    <select id="selectAll" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from cron_metadata
    </select>
    <insert id="addAll">
        insert into cron_metadata
        (cron_metadata_id, cron, cron_group,
         cron_code, cron_name, start_time, end_time,
         status, actived, params, url,
         seq_num, reg_hum_guid,
         reg_hum_name, reg_date, upd_hum_guid,
         upd_hum_name, upd_date, memo,tenant_id)
        values (#{cronMetadataId,jdbcType=NUMERIC}, #{cron,jdbcType=VARCHAR}, #{cronGroup,jdbcType=VARCHAR},
                #{cronCode,jdbcType=VARCHAR}, #{cronName,jdbcType=VARCHAR}, #{startTime,jdbcType=TIMESTAMP},
                #{endTime,jdbcType=TIMESTAMP},
                #{status,jdbcType=NUMERIC}, #{params,jdbcType=NUMERIC}, #{params,jdbcType=VARCHAR},
                #{url,jdbcType=VARCHAR},
                #{seqNum,jdbcType=NUMERIC}, #{regHumGuid,jdbcType=VARCHAR},
                #{regHumName,jdbcType=VARCHAR}, #{regDate,jdbcType=TIMESTAMP}, #{updHumGuid,jdbcType=VARCHAR},
                #{updHumName,jdbcType=VARCHAR}, #{updDate,jdbcType=TIMESTAMP}, #{memo,jdbcType=VARCHAR},
                #{tenantId,jdbcType=VARCHAR})

    </insert>
    <delete id="delByCronGroupAndCronCode">
        delete
        from cron_metadata
        where cron_group = #{cronGroup,jdbcType=VARCHAR}
          AND cron_code = #{cronCode,jdbcType=VARCHAR}
    </delete>
    <select id="countByCronGroupAndCronCode" resultType="int">
        select count(*)
        from cron_metadata
        where cron_group = #{cronGroup,jdbcType=VARCHAR}
          AND cron_code = #{cronCode,jdbcType=VARCHAR}
    </select>
    <update id="updateAll">
        update cron_metadata
        set
    </update>

</mapper>

service和serviceImpl

/**
 *公共Job处理业务
 */
public interface CronMetadataService extends IService<CronMetadata> {
     /**
      *
      * @param cronMetadata 新增或者修改任务
      * @return 状态
      */
     void addOrUpdateJob(CronMetadata cronMetadata);



     void removeJob(Long id);

     /***
      * 暂停任务
      * @param id
      */
     void pauseJob(Long id);

     /***
      * 启动任务
      * @param id
      */
     void resumeOne(Long id);


     /***
      * 暂停所有任务
      */
     void pauseAll();

     /***
      * 启动所有任务
      */
     void resumeAll();


     /***
      * 查询所有
      * @param
      * @return
      */
      IPage<CronMetadata> queryPage(Page page, QueryWrapper queryWrapper);
     /***
      * 查询所有
      * @param
      * @return
      */
      List<CronMetadata> queryList(QueryWrapper queryWrapper);

}
@Service
public class CronMetadataServiceImpl extends ServiceImpl<CronMetadataDao, CronMetadata> implements CronMetadataService {

    @Autowired
    private CronMetadataDao cronMetadataDao;

    @Autowired
    private ScheduleService scheduleService;


    @Autowired
    private List<QuartzJobBean> quartzJobBeans;

    @Override
    @Transactional
    public void addOrUpdateJob(CronMetadata cronMetadata) {

        if (!isValidExpression(cronMetadata.getCron())) {
            throw new ServiceException("当前定时任务cron[" + cronMetadata.getCron() + "]表达式不合法!");
        }
        List<String> jobClassList = quartzJobBeans.stream().map(quartzJobBean -> quartzJobBean.getClass().getSimpleName()).collect(Collectors.toList());

        if (!jobClassList.contains(cronMetadata.getUrl())) {
            throw new ServiceException("当前传入任务类不存在或不合法!");
        }
        checkDate(cronMetadata.getStartTime(), cronMetadata.getEndTime());

        if (cronMetadata.getCronMetadataId() != null) {
            CronMetadata cronMetadataDb = cronMetadataDao.selectOne(
                    new LambdaQueryWrapper<CronMetadata>().eq(CronMetadata::getCronMetadataId, cronMetadata.getCronMetadataId()));
            if (cronMetadataDb == null) {
                throw new ServiceException(500, "当前修改任务数据库不存在,请检查后重试!");
            }
            if (!cronMetadata.getActived()) {
                cronMetadata.setStatus(false);
            }
            cronMetadata.setUpdDate(new Date());
            if (cronMetadata.getStatus() == null) {
                cronMetadata.setStatus(cronMetadataDb.getStatus());
            }
            if (cronMetadata.getActived() == null) {
                cronMetadata.setActived(cronMetadataDb.getActived());
            }
            cronMetadataDao.updateById(cronMetadata);
        } else {
            int count = cronMetadataDao.countByCronGroupAndCronCode(cronMetadata.getCronGroup(), cronMetadata.getCronCode());
            if (count > 0) {
                throw new ServiceException(500, "已有相同Job无需新增");
            }
            if (cronMetadata.getStatus() == null) {
                cronMetadata.setStatus(false);
            }
            if (cronMetadata.getActived() == null) {
                cronMetadata.setActived(false);
            }
            cronMetadata.setRegDate(new Date());
            cronMetadata.setTenantId(0+"");
            cronMetadataDao.insert(cronMetadata);
        }

        //判断状态是否启动该job
        if (cronMetadata.getStatus() && cronMetadata.getActived()) {
            scheduleService.scheduleCronJob(cronMetadata);
        } else if (!cronMetadata.getStatus() && cronMetadata.getActived()) {
            //暂停该job
            scheduleService.pauseJob(cronMetadata);
        } else {
            scheduleService.removeJob(cronMetadata);
        }
    }


    private void checkDate(Date startDate, Date endDate) {
        if (startDate != null && endDate != null && startDate.after(endDate)) {
            throw new ServiceException("开始日期不能大于结束日期!");
        } else if (startDate == null && endDate != null ) {
            throw new ServiceException("填写结束日期时必须填写开始日期!");
        }
    }

    @Override
    @Transactional
    public void removeJob(Long id) {
        CronMetadata cronMetadata = cronMetadataDao.selectOne(
                new LambdaQueryWrapper<CronMetadata>().eq(CronMetadata::getCronMetadataId, id));
        if (cronMetadata == null) {
            scheduleService.syncTriggers();
            throw new ServiceException("未查询到该任务,无法暂停!");
        }
        //先关闭scheduler中的当前Job
        scheduleService.removeJob(cronMetadata);
        //删除表中数据
        cronMetadataDao.delByCronGroupAndCronCode(cronMetadata.getCronGroup(), cronMetadata.getCronCode());
    }

    @Override
    public void pauseJob(Long id) {

        CronMetadata cronMetadata = cronMetadataDao.selectOne(
                new LambdaQueryWrapper<CronMetadata>().eq(CronMetadata::getCronMetadataId, id));
        if (cronMetadata == null) {
            scheduleService.syncTriggers();
            throw new RuntimeException("未查询到该任务,无法暂停!");
        } else if (!cronMetadata.getStatus()) {
            throw new RuntimeException("请不要重复暂停!");
        }
        scheduleService.pauseJob(cronMetadata);
        cronMetadata.setStatus(false);
        cronMetadataDao.updateById(cronMetadata);
    }

    @Override
    public void resumeOne(Long id) {
        CronMetadata cronMetadata = cronMetadataDao.selectOne(
                new LambdaQueryWrapper<CronMetadata>().eq(CronMetadata::getCronMetadataId, id));
        if (cronMetadata == null) {
            throw new ServiceException("未查询到该任务,无法启动!");
        }
        if (!cronMetadata.getActived()) {
            throw new ServiceException("当前任务未生效无法启动!");
        }
        try {
            scheduleService.resumeOne(cronMetadata);
        } catch (Exception e) {
            throw new ServiceException("启动job失败:" + e.getMessage());
        }
        cronMetadata.setStatus(true);
        cronMetadataDao.updateById(cronMetadata);
    }

    @Override
    public void pauseAll() {
        List<CronMetadata> cronMetadatas = cronMetadataDao.selectList(
                new LambdaQueryWrapper<CronMetadata>().eq(CronMetadata::getActived, true));
        if (cronMetadatas.size() == 0) {
            scheduleService.syncTriggers();
            throw new ServiceException("当前没有需要暂停的任务!");
        }
        try {
            scheduleService.pauseAll();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        cronMetadatas = cronMetadatas.stream().map(c -> c.setStatus(false)).collect(Collectors.toList());
        this.updateBatchById(cronMetadatas);
    }

    @Override
    public void resumeAll() {

        List<CronMetadata> cronMetadatas = cronMetadataDao.selectList(
                new LambdaQueryWrapper<CronMetadata>().eq(CronMetadata::getActived, true));
        if (cronMetadatas.size() == 0) {
            throw new ServiceException("当前没有需要启动的任务!");
        }
        cronMetadatas = cronMetadatas.stream().map(c -> c.setStatus(true)).collect(Collectors.toList());
        try {
            scheduleService.resumeAll(cronMetadatas);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        this.updateBatchById(cronMetadatas);
    }

    @Override
    public IPage<CronMetadata> queryPage(Page page, QueryWrapper queryWrapper) {
        return this.page(page, queryWrapper);
    }

    @Override
    public List<CronMetadata> queryList(QueryWrapper queryWrapper) {
        return cronMetadataDao.selectList(queryWrapper);
    }


}

接口controller

@RestController
@RequestMapping("/quartz")
@Api(value="定时任务",tags = {"定时任务管理"})
public class CronMetadataController {

@Autowired
private CronMetadataService cronMetadataService;

@Autowired
private List<QuartzJobBean> quartzJobBeans;


/**
 * 分页查询
 */
@ApiOperation(value = "分页查询")
@GetMapping
public Result page(Page page, @PlanQueryWrapper(CronMetadata.class) QueryWrapper query){
    IPage<CronMetadata> pageList = cronMetadataService.queryPage(page,query);
    return Result.success(new PageUtils(pageList));
}

/**
 * 查询所有
 */
@ApiOperation(value = "查询所有")
@GetMapping("/list")
public Result list(@PlanQueryWrapper(CronMetadata.class) QueryWrapper query){
    List list = cronMetadataService.queryList(query);
    return Result.success(list);
}

@ApiOperation(value = "新增或者修改任务")
@PostMapping
public Result addOrUpdateJob(@RequestBody CronMetadata cronMetadata){
    cronMetadataService.addOrUpdateJob(cronMetadata);
    return Result.success();
}

@ApiOperation(value = "查询所有定时任务类")
@GetMapping("/findQuartzClass")
public Result findQuartzClass(){
    return Result.success(quartzJobBeans.stream().map(quartzJobBean -> quartzJobBean.getClass().getSimpleName()).collect(Collectors.toList()));
}



/***
 * 根据ID删除任务
 * @param id
 * @return
 */
@ApiOperation(value = "根据ID删除任务")
@DeleteMapping("{id}")
public Result delete(@PathVariable Long id) {
    cronMetadataService.removeJob(id);
    return  Result.success();
}

/***
 * 根据ID暂停任务
 * @param id
 * @return
 */
@ApiOperation(value = "根据ID暂停任务")
@GetMapping("/pause/{id}")
public  Result pause(@PathVariable Long id) {
    cronMetadataService.pauseJob(id);
    return  Result.success();
}

/***
 * 根据ID启动任务
 * @param id
 * @return
 */
@ApiOperation(value = "根据ID启动任务")
@GetMapping("/resume/{id}")
public  Result resumeOne(@PathVariable Long id) {
    cronMetadataService.resumeOne(id);
    return  Result.success();
}
/***
 * 暂停所有任务
 * @return
 */
@ApiOperation(value = "暂停所有任务")
@GetMapping("/pauseAll")
public  Result pauseAll() {
    cronMetadataService.pauseAll();
    return  Result.success();
}

/***
 * 启动所有任务
 * @return
 */
@ApiOperation(value = "启动所有任务")
@GetMapping("/resumeAll")
public  Result resumeAll() {
    cronMetadataService.resumeAll();
    return  Result.success();
}
posted @ 2022-05-07 09:41  萌新来报道  阅读(83)  评论(0)    收藏  举报
-- 养成问题记录好习惯,网上资料多而杂,自己整理记录下,努力整理正确结论,大部分为实际开发遇到的问题