基于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 触发器)
  • 设置当天或指定日期的时间范围内,指定时间间隔执行任务。
  • 其他定时功能可根据不同的任务触发器进行实现。

依赖的引入

        <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。

/**
 * @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高级使用

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

数据库表设计

  • 官方提供了一份数据库表设计,有兴趣的小伙伴可以去下载
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配置

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

实体类的创建

/**
 * @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层

/**
 * @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层

/**
 * @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层

/**
 * @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

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

/**
 * @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

/**
 * @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表达式

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表达式的正确性

/**
 * @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层

/**
 * @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启动类配置

/**
 * @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定时业务

/**
 * @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监听器

/**
 * @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() {

    }
}

监听器使用

  •             scheduler.scheduleJob(jobDetail, trigger);
                scheduler.getListenerManager().addSchedulerListener(new MyJobListener()); // 使用监听器
    

封装接收前端Param

/**
 * @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中放入了一些数据,但是修改之后数据不发生变化

解决思路:

最早写法是:

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

更改后写法:

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

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

存数据库最初写法:

// getAllJobs
entity.setJobDataMapJson(JSONUtil.toJsonStr(jobDetail.getJobDataMap()));

现在写法:

// oldTrigger需要自己构建
entity.setJobdatamapjson(JSONUtil.toJsonStr(oldTrigger.getJobDataMap()));

那么任务job的数据呢?

最早的写法:获取jobDataMap数据

String megContent = (String) context.getJobDetail().getJobDataMap().get("megContent");
Integer roleId = (Integer) context.getJobDetail().getJobDataMap().get("roleId");
Integer dayType = (Integer) context.getJobDetail().getJobDataMap().get("dayType");

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

String megContent1 = (String) jobDataMap.get("megContent");
Integer roleId1 = (Integer) jobDataMap.get("roleId");
Integer dayType1 = (Integer) jobDataMap.get("dayType");

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

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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/126531.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入探析隔离CAN收发器NSI1050-DDBR各项参数

NSI1050-DDBR是一个隔离的CAN收发器&#xff0c;可以完全与ISO11898-2标准兼容。 NSI1050-DDBR集成了两个通道的数字隔离器和一个高电平可靠性CAN收发器。 数字隔离器是基于Novosense电容隔离技术的氧化硅隔离。 高度集成的解决方案可以帮助简化系统设计并提高可靠性。 NSI1050…

Yolov8模型训练报错:torch.cuda.OutOfMemoryError

最近在使用自己的数据训练Yolov8模型的时候遇到了很多错误&#xff0c;下面将逐一解答。 问题报错 在训练过程中红字报错&#xff1a;torch.cuda.OutOfMemoryError: CUDA out of memory. 后面还会跟着一大段报错&#xff1a; Tried to allocate XXX MiB (GPU 0; XXX GiB to…

Vue23-props配置功能

Vue2&3-props配置功能 Vue2-props配置 功能&#xff1a;接收从其他组件传过来的数据&#xff0c;将数据从静态转为动态注意&#xff1a; 同一层组件不能使用props&#xff0c;必须是父组件传子组件的形式。父组件传数据&#xff0c;子组件接收数据。不能什么数据都接收&a…

EMNLP2023 | LLM作用下的成分句法分析基础研究

深度学习自然语言处理 原创作者&#xff1a;cola 自训练已被证明是一种有效的针对跨域任务的方法。传统的自训练方法依赖于有限且低质量的源语料库。为克服这一限制&#xff0c;本文提出用大型语言模型(LLM)增强自训练&#xff0c;以迭代地生成特定领域的语料库。并针对句法成分…

JavaEE初阶学习:Linux 基本使用和 web 程序部署

1.Linux的基本认识 Linux 是一个操作系统.(搞管理的系统) 和Windows都是同类产品~~ Linux 实际的场景: 1.服务器 2.嵌入式设备 3.移动端(手机)Android 其实就是Linux 1991年,还在读大学的 芬兰人 Linus Benedict Torvalds,搞了一个Linux 这样的系统0.01版,正式发布了~ 后…

Docker学习——⑥

文章目录 1、什么是存储卷?2、为什么需要存储卷?3、存储卷分类4、管理卷 Volume5、绑定卷 bind mount6、临时卷 tmpfs7、综合实战-MySQL 灾难恢复8、常见问题 1、什么是存储卷? 存储卷就是将宿主机的本地文件系统中存在的某个目录直接与容器内部的文件系统上的某一目录建立…

js各种简单事件处理(整理)

**## 获取当天昨天日期** // 当天日期 const today new Date();// 格式化当天日期为 YYYY-MM-DD 格式 const formattedToday today.toISOString().slice(0, 10);// 昨天日期 const yesterday new Date(); yesterday.setDate(yesterday.getDate() - 1);// 格式化昨天日期为 Y…

并查集模版以及两道例题

&#x1f4af; 博客内容&#xff1a;并查集 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准C后端工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&#xff01; &#x1f496; 欢迎大家&#xff1a;这里是C…

DVWA - 2

文章目录 SQL Injectionlowmediumhigh SQL Injection low 输入 1&#xff0c;可以展示 id 1 的人员信息&#xff1a;输入 1’&#xff0c;有报错信息。可以看出是mysql数据库&#xff0c;‘‘1’’’ 去除两边的引号&#xff0c;再去除1两端的引号&#xff0c;可以看出闭合符…

Thales hsm是什么意思,有什么作用?

Thales HSM是一种硬件安全模块(Hardware Security Module&#xff0c;HSM)&#xff0c;是Thales公司开发的一种安全设备&#xff0c;用于保护和管理密码和数字证书。HSM是一种物理设备&#xff0c;通常用于需要高度安全性的环境中&#xff0c;如政府机构、金融机构、大型企业等…

底座(基座)模型是如何训练的?

我们把LLM的基本训练步骤分为两步&#xff0c;预训练和对齐&#xff1b;预训练我们非常熟悉&#xff0c;是bert-finetuning时代的基本原理&#xff0c;只不过LLM一般遵循自回归的逻辑&#xff0c;因此使用GPT模型的预训练方式&#xff1a;CLM&#xff08;具备因果关系的MLM&…

【Java 进阶篇】Java Filter 过滤器拦截路径配置详解

过滤器&#xff08;Filter&#xff09;是 Java Web 应用中一种强大的组件&#xff0c;它可以用于在请求到达目标资源之前或响应返回客户端之前执行一些预处理或后处理操作。其中&#xff0c;过滤器的拦截路径配置是非常重要的&#xff0c;它决定了过滤器会拦截哪些请求。在本文…

Kotlin系列之注解详解

目录 注解&#xff1a;file:JvmName 注解&#xff1a;JvmField 注解&#xff1a;JvmOverloads 注解&#xff1a;JvmStatic 注解&#xff1a;JvmMultifileClass 注解&#xff1a;JvmSynthetic 注解&#xff1a;file:JvmName file:JvmName(“XXX”) 放在类的最顶层&#x…

浏览器添加油猴(tampermonkey)扩展

msedge浏览器为例 1.打开msedge浏览器 2.点击右上角省略号 3.点击扩展 4.点击管理扩展 5.点击获取 Microsoft Edge 扩展 6.搜索 tampermonkey 7.获取自己想要安装的油猴

kubernetes helm

目录 一、helm 二、部署helm 三、封装chart包 四、上传chart到OCI仓库 五、部署wordpress博客系统 六、helm部署storageclass 七、helm部署ingress-nginx 八、helm部署metrics-server 九、kubeapps 一、helm Helm是Kubernetes 应用的包管理工具&#xff0c;主要用来…

经销商管理怎么做?

有人说&#xff0c;谁占据了渠道&#xff0c;谁就拥有了销售的大半个江山。在渠道为王的时代&#xff0c;每个企业都想快速打开市场&#xff0c;以渠道铺设自己的销路&#xff0c;捞取一桶桶金。因此&#xff0c;占领渠道&#xff0c;将渠道管理好是企业&#xff0c;尤其是快消…

K8S概念与架构

K8S概念与架构 一、Kubernetes 概述1、K8S 是什么2、为什么要用 K8S3、k8s介绍二、Kubernetes 集群架构与组件2.1、Master核心组件 2.2、Node核心组件 三、Kubernetes 核心概念3.1、Pod 控制器 一、Kubernetes 概述 1、K8S 是什么 K8S 的全称为 Kubernetes (K12345678S)&…

如何用Excel软件制作最小二乘法①

一、用自带的选项&#xff08;不推荐&#xff09;&#xff0c;因为感觉只是近似&#xff0c;虽然结果一样 1.在Excel中输入或打开要进行在excel中输入或打开要进行最小二乘法拟合的数据&#xff0c;如图所示。 2.按住“shift”键的同时&#xff0c;用鼠标左键单击以选择数据&a…

linux 显卡驱动 cuda 离线安装

1、 安装显卡驱动&#xff1a; Download NVIDIA, GeForce, Quadro, and Tesla Drivers &#xff08;1&#xff09;注意选择对应的cuda版本&#xff0c;和系统版本&#xff0c;并下载 &#xff08;2&#xff09;

element-Cascader级联选择器用法?

html <el-form-item label"行业选择" :label-width"formLabelWidth"><div class"m-4"><el-cascader v-model"form.tradeid" :options"options" :props"props" /></div></el-form-ite…