基于Quartz实现动态定时任务

生命无罪,健康万岁,我是laity。

我曾七次鄙视自己的灵魂:

第一次,当它本可进取时,却故作谦卑;

第二次,当它在空虚时,用爱欲来填充;

第三次,在困难和容易之间,它选择了容易;

第四次,它犯了错,却借由别人也会犯错来宽慰自己;

第五次,它自由软弱,却把它认为是生命的坚韧;

第六次,当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副;

第七次,它侧身于生活的污泥中,虽不甘心,却又畏首畏尾。

本文带各位学习下Quartz的基本使用及业务中的整合,包括基本概念以及如何动态地对定时任务进行CRUD,并且如何实现定时任务的持久化以及任务恢复;其中分享下本人在使用时遇到的问题,和解决方案。

Quartz的基本使用

Quartz 是一个开源的作业调度框架,支持分布式定时任务,Quartz定时任务据我了解可分为Trigger(触发器)Job(任务)和Scheduler(调度器),定时任务的逻辑大体为:创建触发器和任务,并将其加入到调度器中。

Quartz 的核心类有以下三部分:

  • 任务 Job : 需要实现的任务类,实现 execute() 方法,执行后完成任务;
  • 触发器 Trigger : 包括 SimpleTriggerCronTrigger;
  • 调度器 Scheduler : 任务调度器,负责基于 Trigger触发器,来执行 Job任务.

Trigger 有五种触发器:

  • SimpleTrigger 触发器:需要在特定的日期/时间启动,且以指定的间隔时间(单位毫秒)重复执行 n 次任务,如 :在 9:00 开始,每隔1小时,每隔几分钟,每隔几秒钟执行一次 。没办法指定每隔一个月执行一次(每月的时间间隔不是固定值)。
  • CalendarIntervalTrigger 触发器:指定从某一个时间开始,以一定的时间间隔(单位有秒,分钟,小时,天,月,年,星期)执行的任务。
  • DailyTimeIntervalTrigger 触发器:指定每天的某个时间段内,以一定的时间间隔执行任务。并且支持指定星期。如:指定每天 9:00 至 18:00 ,每隔 70 秒执行一次,并且只要周一至周五执行。
  • CronTrigger 触发器:基于日历的任务调度器,即指定星期、日期的某时间执行任务。
  • NthIncludedDayTrigger 触发器:不同时间间隔的第 n 天执行任务。比如,在每个月的第 15 日处理财务发票记帐,同样设定双休日或者假期。

使用场景

  • 发布消息、问卷等信息时,发布者可以指定星期、月份的具体时间进行定时发布(cron 触发器)
  • 设置当天或指定日期的时间范围内,指定时间间隔执行任务。
  • 其他定时功能可根据不同的任务触发器进行实现。

依赖的引入

java 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

简单的测试

将job封装给jobDetail,由调度器scheudler根据触发器trggier条件触发相应的jobDetail,每次触发都会让jobDetail重新创建job对象,并且jobDetail会将数据传给job

有两种方式:

  • 1.jobDetail会根据自己usingJobData中的参数主动调用job对应的set方法,设置给job使用。

  • 2.*job可以从重写方法传过来的参数jobExecutionContext中获取jobDetail,*然后从jobDetail中获取到jobDataMap。

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 * @Description: 测试定时任务并获取自定义参数
 */

public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext content) throws JobExecutionException {
        long count = (long) content.getJobDetail().getJobDataMap().get("count");
        System.out.println("当前执行,第" + count + "次");
        content.getJobDetail().getJobDataMap().put("count", ++count);
        System.out.println("任务执行.....");
    }

    public static void main(String[] args) throws Exception {
        // 1.创建调度器 Scheduler
        SchedulerFactory factory = new StdSchedulerFactory();
        Scheduler scheduler = factory.getScheduler();

        // 2.创建JobDetail实例,并与MyJob类绑定(Job执行内容)
        JobDetail job = JobBuilder.newJob(MyJob.class)
                .withIdentity("job1", "group1")
                .usingJobData("count", 1L)
                .build();

        // 3.构建Trigger实例,每隔3s执行一次
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(simpleSchedule()
                        .withIntervalInSeconds(3)
                        .repeatForever())
                .build();

        // 4.执行,开启调度器
        scheduler.scheduleJob(job, trigger);
        System.out.println(System.currentTimeMillis());
        scheduler.start();

        //主线程睡眠1分钟,然后关闭调度器
        TimeUnit.MINUTES.sleep(1);
        scheduler.shutdown();
        System.out.println(System.currentTimeMillis());
    }
}

Quartz高级使用

当遇到更新版本等情况时,肯定要将程序给停了,但是程序停止后那些还未开始或者没执行完的定时任务就没了。所以我们需要将任务持久化到数据库中,然后在程序启动时将这些任务进行恢复。

数据库表设计

  • 官方提供了一份数据库表设计,有兴趣的小伙伴可以去下载
sql 复制代码
DROP TABLE IF EXISTS `quartz_entity`;
CREATE TABLE `quartz_entity` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `job_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务名',
  `group_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '任务分组',
  `start_time` timestamp DEFAULT NULL COMMENT '任务开始时间',
  `end_time` timestamp DEFAULT NULL COMMENT '任务结束时间',
  `job_class` varchar(255) DEFAULT NULL COMMENT '定时任务所在的类',
  `cron` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'cron表达式',
  `job_data_map_json` varchar(255) DEFAULT NULL COMMENT 'json格式的jobDataMap',
  `status` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0' COMMENT '任务状态。0-进行中;1-已完成;2-取消', 
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='定时任务信息';

SET FOREIGN_KEY_CHECKS = 1;

application-local.yml配置

yaml 复制代码
spring:
  info:
    build:
      encoding: UTF-8
  datasource:
    dynamic:
      druid:
        initial-size: 10
        # 初始化大小,最小,最大
        min-idle: 20
        maxActive: 500
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        testWhileIdle: true
        testOnBorrow: true
        validation-query: SELECT 1
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        filter:
          wall:
            config:
              multi-statement-allow: true
              none-base-statement-allow: true
            enabled: true
        # 配置DruidStatFilter
        web-stat-filter:
          enabled: true
          url-pattern: "/*"
          exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
        # 配置DruidStatViewServlet
        stat-view-servlet:
          enabled: true
          url-pattern: "/druid/*"
          allow:
          deny:
          reset-enable: false
          login-username: admin
          login-password: 111111
        query-timeout: 36000
      primary: slave
      strict: false
      datasource:
        master:
          url: jdbc:mysql://127.0.0.1:3306/jxgl?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
          username: root
          password: wang9264
          driver-class-name: com.mysql.jdbc.Driver
        slave:
          url: jdbc:mysql://127.0.0.1:3306/java?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
          username: root
          password: wang9264
          driver-class-name: com.mysql.jdbc.Driver

实体类的创建

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 */

@Data
public class QuartzEntity {

    @TableId(value = "id",type = IdType.AUTO)
    private Long id;

    private String jobName;

    private String groupName;

    private Date startTime;

    private Date endTime;

    private String jobClass;

    private String cron;

    private String jobDataMapJson;

    private String status;

}

service层

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 */

public interface QuartzService {
    void save(QuartzEntity entity);

    boolean modifyJob(QuartzEntity entity);

    boolean modifyTaskStatus(String jobName,String status);

    List<QuartzEntity> notStartOrNotEndJobs();
}

serviceImpl层

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 */
@Service("quartzService")
public class QuartzServiceImpl implements QuartzService {
    @Resource
    private QuartzDao quartzMapper;

    @Override
    public void save(QuartzEntity entity) {
        quartzMapper.insert(entity);
    }

    @Override
    public boolean modifyJob(QuartzEntity entity) {
        LambdaQueryWrapper<QuartzEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(QuartzEntity::getJobName, entity.getJobName());
        QuartzEntity one = quartzMapper.selectOne(wrapper);
        if (one != null) {
            entity.setId(one.getId());
            return quartzMapper.updateById(entity) > 0;
        }
        return false;
    }

    @Override
    public boolean modifyTaskStatus(String jobName, String status) {
        LambdaQueryWrapper<QuartzEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(QuartzEntity::getJobName, jobName);
        QuartzEntity one = quartzMapper.selectOne(wrapper);
        if (one != null) {
            one.setStatus(status);
            return quartzMapper.updateById(one) > 0;
        }
        return false;
    }

    @Override
    public List<QuartzEntity> notStartOrNotEndJobs() {
        return quartzMapper.notStartOrNotEndJobs();
    }
}

Dao层

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 */
@Mapper
public interface QuartzDao extends BaseMapper<QuartzEntity> {
    @Select("SELECT " +
            " *  " +
            "FROM " +
            " quartz_entity  " +
            "WHERE " +
            " ( end_time IS NULL  " +                                  // 没有结束时间的
            "  OR ( start_time < NOW() AND end_time > NOW())  " +      // 已经开始但未结束的
            "  OR start_time > NOW()  " +                              // 还未开始的
            " )  " +
            " AND `status` = '0'")
    List<QuartzEntity> notStartOrNotEndJobs();
}

封装组件

QuartzUtil.java

封装了 定时任务的创建、定时任务的修改、定时任务的结束、定时任务的查询、定时任务的恢复(重启服务的时候使用)

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 */
@Component
public class QuartzUtil {

    private static final SchedulerFactory SCHEDULER_FACTORY = new StdSchedulerFactory();

    @Autowired
    private QuartzService quartzService;

    /**
     * 添加一个定时任务
     *
     * @param name      任务名。每个任务唯一,不能重复。方便起见,触发器名也设为这个
     * @param group     任务分组。方便起见,触发器分组也设为这个
     * @param jobClass  任务的类类型  eg:MyJob.class
     * @param startTime 任务开始时间。传null就是立即开始
     * @param endTime   任务结束时间。如果是一次性任务或永久执行的任务就传null
     * @param cron      时间设置表达式。传null就是一次性任务
     */
    public boolean addJob(String name, String group, Class<? extends Job> jobClass,
                          Date startTime, Date endTime, String cron, JobDataMap jobDataMap) {
        try {
            // 第一步: 定义一个JobDetail
            JobDetail jobDetail = JobBuilder.newJob(jobClass).
                    withIdentity(name, group).setJobData(jobDataMap).build();
            // 第二步: 设置触发器
            TriggerBuilder<Trigger> triggerBuilder = newTrigger();
            triggerBuilder.withIdentity(name, group);
            triggerBuilder.startAt(toStartDate(startTime));
            triggerBuilder.endAt(toEndDate(endTime)); //设为null则表示不会停止
            if (StrUtil.isNotEmpty(cron)) {
                triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
            }
            Trigger trigger = triggerBuilder.build();
            //第三步:调度器设置
            Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
            scheduler.scheduleJob(jobDetail, trigger);
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        //存储到数据库中
        QuartzEntity entity = new QuartzEntity();
        entity.setJobName(name);
        entity.setGroupName(group);
        entity.setStartTime(startTime != null ? startTime : new Date());
        entity.setEndTime(endTime);
        entity.setJobClass(jobClass.getName());
        entity.setCron(cron);
        entity.setJobDataMapJson(JSONUtil.toJsonStr(jobDataMap));
        entity.setStatus("0");
        quartzService.save(entity);
        return true;
    }

    /**
     * 修改一个任务的开始时间、结束时间、cron。不改的就传null
     *
     * @param name         任务名。每个任务唯一,不能重复。方便起见,触发器名也设为这个
     * @param group        任务分组。方便起见,触发器分组也设为这个
     * @param newStartTime 新的开始时间
     * @param newEndTime   新的结束时间
     * @param cron         新的时间表达式
     */
    public boolean modifyJobTime(String name, String group, Date newStartTime,
                                 Date newEndTime, String cron) {
        try {
            Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
            TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
            Trigger oldTrigger = scheduler.getTrigger(triggerKey);
            if (oldTrigger == null) {
                return false;
            }
            TriggerBuilder<Trigger> triggerBuilder = newTrigger();
            triggerBuilder.withIdentity(name, group);
            if (newStartTime != null) {
                triggerBuilder.startAt(toStartDate(newStartTime));   // 任务开始时间设定
            } else if (oldTrigger.getStartTime() != null) {
                triggerBuilder.startAt(oldTrigger.getStartTime()); //没有传入新的开始时间就不变
            }
            if (newEndTime != null) {
                triggerBuilder.endAt(toEndDate(newEndTime));   // 任务结束时间设定
            } else if (oldTrigger.getEndTime() != null) {
                triggerBuilder.endAt(oldTrigger.getEndTime()); //没有传入新的结束时间就不变
            }
            if (StrUtil.isNotEmpty(cron)) {
                triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
            } else if (oldTrigger instanceof CronTrigger) {
                String oldCron = ((CronTrigger) oldTrigger).getCronExpression();
                triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(oldCron));
            }
            Trigger newTrigger = triggerBuilder.build();
            scheduler.rescheduleJob(triggerKey, newTrigger);    // 修改触发时间
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        // 修改数据库中的记录
        QuartzEntity entity = new QuartzEntity();
        entity.setJobName(name);
        entity.setGroupName(group);
        if (newStartTime != null) {
            entity.setStartTime(newStartTime);
        }
        if (newEndTime != null) {
            entity.setEndTime(newEndTime);
        }
        if (StrUtil.isNotEmpty(cron)) {
            entity.setCron(cron);
        }
        return quartzService.modifyJob(entity);
    }

    /**
     * 结束任务
     * @param jobName 任务名称
     * @param groupName 分组名称
     * @return boolean
     */
    public boolean cancelJob(String jobName, String groupName) {
        try {
            Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName, groupName);
            scheduler.pauseTrigger(triggerKey); // 停止触发器
            scheduler.unscheduleJob(triggerKey);    // 移除触发器
            scheduler.deleteJob(JobKey.jobKey(jobName, groupName)); // 删除任务
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        //将数据库中的任务状态设为 取消
        return quartzService.modifyTaskStatus(jobName, "2");
    }

    /**
     * 获取所有job任务信息
     * @return list
     * @throws SchedulerException error
     */
    public List<QuartzEntity> getAllJobs() throws SchedulerException {
        Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();

        List<QuartzEntity> quartzJobs = new ArrayList<>();
        try {
            List<String> triggerGroupNames = scheduler.getTriggerGroupNames();
            for (String groupName : triggerGroupNames) {
                GroupMatcher<TriggerKey> groupMatcher = GroupMatcher.groupEquals(groupName);
                Set<TriggerKey> triggerKeySet = scheduler.getTriggerKeys(groupMatcher);
                for (TriggerKey triggerKey : triggerKeySet) {
                    Trigger trigger = scheduler.getTrigger(triggerKey);
                    JobKey jobKey = trigger.getJobKey();
                    JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                    //组装数据
                    QuartzEntity entity = new QuartzEntity();
                    entity.setJobName(jobDetail.getKey().getName());
                    entity.setGroupName(jobDetail.getKey().getGroup());
                    entity.setStartTime(trigger.getStartTime());
                    entity.setEndTime(trigger.getStartTime());
                    entity.setJobClass(jobDetail.getJobClass().getName());
                    if (trigger instanceof CronTrigger) {
                        entity.setCron(((CronTrigger) trigger).getCronExpression());
                    }
                    entity.setJobDataMapJson(JSONUtil.toJsonStr(jobDetail.getJobDataMap()));
                    quartzJobs.add(entity);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return quartzJobs;
    }

    public void recoveryAllJob() {
        List<QuartzEntity> tasks = quartzService.notStartOrNotEndJobs();
        if (tasks != null && tasks.size() > 0) {
            for (QuartzEntity task : tasks) {
                try {
                    JobDataMap jobDataMap = JSONUtil.toBean(task.getJobDataMapJson(), JobDataMap.class);
                    JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(task.getJobClass()))
                            .withIdentity(task.getJobName(), task.getGroupName())
                            .setJobData(jobDataMap).build();
                    TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
                    triggerBuilder.withIdentity(task.getJobName(), task.getGroupName());
                    triggerBuilder.startAt(toStartDate(task.getStartTime()));
                    triggerBuilder.endAt(toEndDate(task.getEndTime()));
                    if (StrUtil.isNotEmpty(task.getCron())) {
                        triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(task.getCron()));
                    }
                    Trigger trigger = triggerBuilder.build();
                    Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
                    scheduler.scheduleJob(jobDetail, trigger);
                    if (!scheduler.isShutdown()) {
                        scheduler.start();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static Date toEndDate(Date endDateTime) {
        // endDateTime为null时转换会报空指针异常,所以需要进行null判断。
        // 结束时间可以为null,所以endDateTime为null,直接返回null即可
        return endDateTime != null ?
                DateUtil.date(endDateTime) : null;
    }

    private static Date toStartDate(Date startDateTime) {
        // startDateTime为空时返回当前时间,表示立即开始
        return startDateTime != null ?
                DateUtil.date(startDateTime) : new Date();
    }
}

SpringContextJobUtil.java

用于获取Bean

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 */

@Component
public class SpringContextJobUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    @SuppressWarnings("static-access")
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        this.context = context;
    }

    public static Object getBean(String beanName) {
        return context.getBean(beanName);
    }
}

CronUtil.java

你不可能让用户来输入cron表达式,所以根据用户的选择来解析成cron表达式

java 复制代码
package com.ys.control_core.util.job;

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author: Laity
 * @Project: JavaLaity
 * @Description: 用于生成Cron表达式
 */

public class CronUtil {

    /**
     * 每天
     */
    private static final int DAY_JOB_TYPE = 1;
    /**
     * 每周
     */
    private static final int WEEK_JOB_TYPE = 2;
    /**
     * 每月
     */
    private static final int MONTH_JOB_TYPE = 3;

    /**
     * 构建Cron表达式
     *
     * @param jobType        作业类型: 1/每天; 2/每周; 3/每月
     * @param minute         指定分钟
     * @param hour           指定小时
     * @param lastDayOfMonth 指定一个月的最后一天:0/不指定;1/指定
     * @param weekDays       指定一周哪几天:1/星期天; 2/...3/..   ; 7/星期六
     * @param monthDays      指定一个月的哪几天
     * @return String
     */
    public static String createCronExpression(Integer jobType, Integer minute, Integer hour, Integer lastDayOfMonth, List<Integer> weekDays, List<Integer> monthDays) {
        StringBuilder cronExp = new StringBuilder();
        // 秒
        cronExp.append("0 ");
        // 指定分钟,为空则默认0分
        cronExp.append(minute == null ? "0" : minute).append(" ");
        // 指定小时,为空则默认0时
        cronExp.append(hour == null ? "0" : hour).append(" ");
        // 每天
        if (jobType == DAY_JOB_TYPE) {
            // 日
            cronExp.append("* ");
            // 月
            cronExp.append("* ");
            // 周
            cronExp.append("?");
        } else if (lastDayOfMonth != null && lastDayOfMonth == 1) {
            // 日
            cronExp.append("L ");
            // 月
            cronExp.append("* ");
            // 周
            cronExp.append("?");
        }
        // 按每周
        else if (weekDays != null && jobType == WEEK_JOB_TYPE) {
            // 日
            cronExp.append("? ");
            // 月
            cronExp.append("* ");
            // 一个周的哪几天
            cronExp.append(StringUtils.join(weekDays, ","));
        }
        // 按每月
        else if (monthDays != null && jobType == MONTH_JOB_TYPE) {
            // 日
            cronExp.append(StringUtils.join(monthDays, ",")).append(" ");
            // 月
            cronExp.append("* ");
            // 周
            cronExp.append("?");
        } else {
            cronExp.append("* ").append("* ").append("?");
        }
        return cronExp.toString();
    }

    public static void main(String[] args) {
        String cronExpression = createCronExpression(1, 26, null, null, null, null);
        createCronExpression(2, 26, 9, 0, null, null);
        // 0/2 * * * * ?
        System.out.println(cronExpression);
    }
    /*
    {
    "jobType":2,
    "times":[
        {
            "minute":"30",
            "hour":"8"
        },
        {
            "minute":"00",
            "hour":"20"
        }
    ],
    "weekDays":[1,2]
    }
     */
}

ValidCron.java

用于检验Cron表达式的正确性

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 */
public class ValidCronUtil {

    public static boolean isValidCronExpression(String exp) {
        if (exp == null || exp.length() ==0) return false;
        boolean validExpression = CronExpression.isValidExpression(exp);
        if (validExpression) System.out.println("cron expression is valid.");
        return validExpression;
    }

    public static void main(String[] args) {
        String cron = "0 26 9 ? * 1,2,3,4,5";
        boolean validCronExpression = isValidCronExpression(cron);
        System.out.println(validCronExpression);
    }
}

Controller层

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 */
@RestController
@RequestMapping("/quartz/web")
@Api(tags = "定时任务相关接口API")
public class QuartzWebController {
    @Autowired
    private QuartzUtil quartzUtil;

    @Autowired
    private QuartzWebService quartzWebService;

    @PostMapping("/add-job")
    @ApiOperation(value = "添加任务", notes = "添加任务", httpMethod = "POST")
    public Rs AddQuartz(@Valid @RequestBody CreateJobParam entity) {
        JobDataMap jobDataMap = getJobDataMap(entity);
        String exp = CronUtil.createCronExpression(2, (Integer) jobDataMap.get("minute"), (Integer) jobDataMap.get("hour"), null, (List<Integer>) jobDataMap.get("weekDays"), null);
        boolean res = ValidCronUtil.isValidCronExpression(exp);
        if (!res) GlobalException.cast("参数有误!");
        entity.setCron(exp);
        boolean result = quartzUtil.addJob(entity.getJobname(), QuartzGroupEnum.T1.getValue(), MyJob.class,
                entity.getStarttime(), entity.getEndtime(), entity.getCron(), jobDataMap, entity.getRoleid());
        return result ? Rs.success("添加成功") : Rs.error("添加失败");
    }


    @PostMapping("/modify-job")
    @ApiOperation(value = "修改任务", notes = "修改任务", httpMethod = "POST")
    public Rs modifyQuartz(@Valid @RequestBody UpdateJobParam entity) {
        JobDataMap jobDataMap = new JobDataMap();
        // false || false || true
        if (entity.getMinute() != null || entity.getHour() != null || entity.getWeekDays() != null) {
            String exp = CronUtil.createCronExpression(2, entity.getMinute(), entity.getHour(), null, entity.getWeekDays(), null);
            boolean res = ValidCronUtil.isValidCronExpression(exp);
            if (!res) GlobalException.cast("参数有误!");
            entity.setCron(exp);
            jobDataMap.put("minute", entity.getMinute());
            jobDataMap.put("hour", entity.getHour());
            jobDataMap.put("weekDays", entity.getWeekDays());
        }
        if (entity.getRoleid() != null) {
            jobDataMap.put("roleId", entity.getRoleid());
        }
        if (entity.getSendMessage() != null) {
            jobDataMap.put("megContent", entity.getSendMessage());
        }
        if (entity.getDayType() != null) {
            jobDataMap.put("dayType", entity.getDayType() == null ? null : 1);
        }
        boolean result = quartzUtil.modifyJobTime(entity.getJobname(), QuartzGroupEnum.T1.getValue(),
                entity.getStarttime(), entity.getEndtime(), entity.getCron(), entity.getId(), jobDataMap, entity.getRoleid());
        return result ? Rs.success("修改成功") : Rs.success("修改失败");
    }

    @PostMapping("/cancel-job")
    @ApiOperation(value = "停止任务", notes = "停止任务", httpMethod = "POST")
    public Rs cancelTimeQuartz(@RequestBody QuartzEntity entity) {
        boolean result = quartzUtil.cancelJob(entity.getJobname(), QuartzGroupEnum.T1.getValue());
        return result ? Rs.success("操作成功") : Rs.success("操作失败");
    }

    @GetMapping("/get-all-jobs")
    @ApiOperation(value = "查询正在执行的任务", notes = "查询正在执行的任务", httpMethod = "GET")
    public Rs getAllJobs() throws SchedulerException {
        return Rs.success(quartzUtil.getAllJobs());
    }

    @GetMapping("/query-all-job")
    @ApiOperation(value = "查询所有创建的任务", notes = "查询所有创建的任务", httpMethod = "GET")
    public Rs getAllJob() {
        return Rs.success(quartzWebService.queryJobAll());
    }

    private JobDataMap getJobDataMap(CreateJobParam entity) {
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("megContent", entity.getSendMessage());
        jobDataMap.put("roleId", entity.getRoleid());
        jobDataMap.put("dayType", entity.getDayType() == null ? null : 1);
        jobDataMap.put("minute", entity.getMinute());
        jobDataMap.put("hour", entity.getHour());
        jobDataMap.put("weekDays", entity.getWeekDays());
        return jobDataMap;
    }
}

Application启动类配置

java 复制代码
/**
 * @author Laity
 */
@MapperScan("com.laity.control_core.dao")
@ComponentScan({"com.laity.*"})
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@SpringBootApplication() // exclude = {SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class}
public class ControlApplication implements ApplicationRunner {

    @Resource
    private QuartzUtil quartzUtil;

    public static void main(String[] args) {
        SpringApplication.run(ControlApplication.class, args);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        quartzUtil.recoveryAllJob();
    }
}

MyJob定时业务

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 */
@Component("MysqlJob")
public class MysqlJob implements Job {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey key = context.getJobDetail().getKey();
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        System.out.println(key.getName());
        String megContent = (String) context.getTrigger().getJobDataMap().get("megContent");
        Integer roleId = (Integer) context.getTrigger().getJobDataMap().get("roleId");
        Integer dayType = (Integer) context.getTrigger().getJobDataMap().get("dayType");
        // 需要使用ServiceBean => ARR arr = (ARR) SpringContextJobUtil.getBean("arrWebService");
        ......
    }

SchedulerListener监听器

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 * @Description: 全局监听器 - 接收所有的Trigger/Job的事件通知
 */
public class MyJobListener implements SchedulerListener {

    @Override
    public void jobScheduled(Trigger trigger) {
        // 用于部署JobDetail时调用
        String jobName = trigger.getJobKey().getName();
        System.out.println("用于部署JobDetail时调用==>" + jobName);
    }

    @Override
    public void jobUnscheduled(TriggerKey triggerKey) {
        // 用于卸载JobDetail时调用
        System.out.println(triggerKey + "完成卸载");
    }

    @Override
    public void triggerFinalized(Trigger trigger) {
        // 当endTime到了就会执行
        System.out.println("触发器被移除:" + trigger.getJobKey().getName());
        QuartzWebService quartzService = (QuartzWebService) SpringContextJobUtil.getBean("quartzService");
        quartzService.modifyTaskStatus(trigger.getJobKey().getName(), "2");
    }

    @Override
    public void triggerPaused(TriggerKey triggerKey) {
        System.out.println(triggerKey + "正在被暂停");
    }

    @Override
    public void triggersPaused(String s) {
        // s = triggerGroup
        System.out.println("触发器组:" + s + ",正在被暂停");
    }

    @Override
    public void triggerResumed(TriggerKey triggerKey) {
        System.out.println(triggerKey + "正在从暂停中恢复");
    }

    @Override
    public void triggersResumed(String s) {
        System.out.println("触发器组:" + s + ",正在从暂停中恢复");
    }

    @Override
    public void jobAdded(JobDetail jobDetail) {
        System.out.println(jobDetail.getKey() + "=>已添加工作任务");
    }

    @Override
    public void jobDeleted(JobKey jobKey) {
        System.out.println(jobKey + "=> 已删除该工作任务");
    }

    @Override
    public void jobPaused(JobKey jobKey) {
        System.out.println(jobKey + "=> 工作任务正在被暂停");
    }

    @Override
    public void jobsPaused(String s) {
        System.out.println("工作任务组:" + s + ",正在被暂停");
    }

    @Override
    public void jobResumed(JobKey jobKey) {
        System.out.println(jobKey + "jobKey正在从暂停中恢复");
    }

    @Override
    public void jobsResumed(String s) {
        System.out.println("工作任务组:" + s + ",正在从暂停中恢复");
    }

    @Override
    public void schedulerError(String s, SchedulerException e) {

    }

    @Override
    public void schedulerInStandbyMode() {

    }

    @Override
    public void schedulerStarted() {

    }

    @Override
    public void schedulerStarting() {
        System.out.println("=============================开启监听===========================");
    }

    @Override
    public void schedulerShutdown() {

    }

    @Override
    public void schedulerShuttingdown() {

    }

    @Override
    public void schedulingDataCleared() {

    }
}

监听器使用

java 复制代码
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.getListenerManager().addSchedulerListener(new MyJobListener()); // 使用监听器

封装接收前端Param

java 复制代码
/**
 * @author: Laity
 * @Project: JavaLaity
 */
@Data
@ApiModel(value = "创建定时任务")
@Accessors(chain = true)
public class CreateJobParam {

    @ApiModelProperty(value = "任务名称")
    @NotBlank(message = "任务名称不能为空")
    private String jobname;

    @ApiModelProperty(value = "开始时间")
    private Date starttime;

    @ApiModelProperty(value = "结束时间")
    private Date endtime;

    // @Ignore
    @ApiModelProperty(value = "cron表达式")
    private String cron;

    @ApiModelProperty(value = "角色id")
    @NotNull(message = "角色ID不能为空")
    private Integer roleid;

    @ApiModelProperty(value = "消息内容")
    @NotBlank(message = "消息内容不能为空")
    private String sendMessage;

    @ApiModelProperty(value = "因为有的消息是发给昨日的某人,所以设立此标识符,正常的不用传值,非正常:1")
    private Integer dayType;

    @ApiModelProperty(value = "指定分钟 0-60")
    @Max(60)
    @Min(0)
    private Integer minute;
    @Max(24)
    @Min(0)
    @ApiModelProperty(value = "指定小时 0-24")
    private Integer hour;
    @ApiModelProperty(value = "星期列表: 1/星期天、2/星期一、3/...、7/星期六")
    private List<Integer> weekDays;
}

测试


说明

可根据自己的需求自行配置其余配置:多线程、Reids缓存、MySQL、Quartz其余配置等

解决问题

修改Quartz中的JobDetailMap数据

因为我在JobDetailMap中放入了一些数据,但是修改之后数据不发生变化

解决思路:

最早写法是:

java 复制代码
			// addjob中存jobDetail
            JobDetail jobDetail = JobBuilder.newJob(jobClass).
                    withIdentity(name, group).setJobData(jobDataMap).build();
            TriggerBuilder<Trigger> triggerBuilder = newTrigger();

更改后写法:

java 复制代码
// 在构建Trigger实例时使用.usingJobData()方法实现
            TriggerBuilder<Trigger> triggerBuilder = newTrigger();
            triggerBuilder.withIdentity(name, group);
            triggerBuilder.startAt(toStartDate(startTime));
            triggerBuilder.endAt(toEndDate(endTime));
            triggerBuilder.usingJobData(jobDataMap);  // usingJobData传入jobDataMap

其中出现的问题:停止服务,查询配置不一致

存数据库最初写法:

java 复制代码
// getAllJobs
entity.setJobDataMapJson(JSONUtil.toJsonStr(jobDetail.getJobDataMap()));

现在写法:

java 复制代码
// oldTrigger需要自己构建
entity.setJobdatamapjson(JSONUtil.toJsonStr(oldTrigger.getJobDataMap()));

那么任务job的数据呢?

最早的写法:获取jobDataMap数据

java 复制代码
String megContent = (String) context.getJobDetail().getJobDataMap().get("megContent");
Integer roleId = (Integer) context.getJobDetail().getJobDataMap().get("roleId");
Integer dayType = (Integer) context.getJobDetail().getJobDataMap().get("dayType");

期间也断点调试使用过其它数据获取方式

java 复制代码
String megContent1 = (String) jobDataMap.get("megContent");
Integer roleId1 = (Integer) jobDataMap.get("roleId");
Integer dayType1 = (Integer) jobDataMap.get("dayType");

最终实现写法:数据不论是临时修改还是怎么都可以实时更新

java 复制代码
String megContent = (String) context.getTrigger().getJobDataMap().get("megContent");
Integer roleId = (Integer) context.getTrigger().getJobDataMap().get("roleId");
Integer dayType = (Integer) context.getTrigger().getJobDataMap().get("dayType");

昨日之深渊,今日之浅谈;我是Laity,正在前行的Laity。

相关推荐
李白的粉2 小时前
基于springboot的在线教育系统
java·spring boot·毕业设计·课程设计·在线教育系统·源代码
小马爱打代码3 小时前
SpringBoot原生实现分布式MapReduce计算
spring boot·分布式·mapreduce
iuyou️3 小时前
Spring Boot知识点详解
java·spring boot·后端
一弓虽3 小时前
SpringBoot 学习
java·spring boot·后端·学习
来自星星的猫教授5 小时前
spring,spring boot, spring cloud三者区别
spring boot·spring·spring cloud
乌夷7 小时前
使用spring boot vue 上传mp4转码为dash并播放
vue.js·spring boot·dash
A阳俊yi8 小时前
Spring Boot日志配置
java·spring boot·后端
苹果酱05678 小时前
2020-06-23 暑期学习日更计划(机器学习入门之路(资源汇总)+概率论)
java·vue.js·spring boot·mysql·课程设计
斜月8 小时前
一个服务预约系统该如何设计?
spring boot·后端
Java水解9 小时前
线程池详解:在SpringBoot中的最佳实践
spring boot·后端