Quartz+SpringBoot实现一个任务对应多个触发器的添加、暂停、恢复、删除、查询所有,每个方法都有解释

接文章,不知道Quartz简单使用的看上篇文章

CSDNhttps://mp.csdn.net/mp_blog/creation/editor/126828399看前端效果

提示: 任务和触发器唯一标识由组和名组成。

dto对象

package com.sifan.erp.dto;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;


@Data
public class ScheduleInfoDto implements Serializable {

    @ApiModelProperty(value = "服务名称")
    private String serveName;

//    @ApiModelProperty(value = "接口地址")
//    private String interfaceAddress;

    @ApiModelProperty(value = "任务描述")
    private String descName;

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

    @ApiModelProperty(value = "任务组")
    private String jobGroup;

    @ApiModelProperty(value = "任务类名")
    private String jobClassName;

    @ApiModelProperty(value = "触发器名称")
    private String triggerName;

    @ApiModelProperty(value = "触发器组")
    private String triggerGroup;

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

    @ApiModelProperty(value = "表达式中文")
    private String cronExpressionZH;

    @ApiModelProperty(value = "任务创建者名字")
    private String userName;

    @ApiModelProperty(value = "任务创建时间")
    private String createdTime;

    @ApiModelProperty(value = "任务更新时间")
    private String updatedTime;

    @ApiModelProperty(value = "触发器描述")
    private String triggerDescription;
    /**
     * 状态 :
     * NONE,
     * NORMAL,
     * PAUSED,
     * COMPLETE,
     * ERROR,
     * BLOCKED;
     */
    @ApiModelProperty(value = "触发器状态")
    private String triggerState;

    @ApiModelProperty(value = "新触发器名称")
    private String newTriggerName;

    @ApiModelProperty(value = "新触发器组")
    private String newTriggerGroup;
    @ApiModelProperty(value = "上一次执行时间")
    private String previousFireTime;

}

Job类,实际执行的类:通过jobDataMap拿到创建任务时存的参数

package com.sifan.erp.common;

import com.amazon.spapi.client.ApiException;
import com.sifan.erp.domain.Aws;
import com.sifan.erp.domain.SellerAccount;
import com.sifan.erp.service.AwsService;
import com.sifan.erp.service.OrderService;
import com.sifan.erp.service.SellerAccountService;
import com.sifan.erp.utils.RedisUtil;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@DisallowConcurrentExecution //不允许并发执行
public class AmazonDataCaptureJob implements Job {
    private static final Logger log = LoggerFactory.getLogger(AmazonDataCaptureJob.class);
    @Resource
    private OrderService orderService;
    @Resource
    private SellerAccountService sellerAccountService;
    @Resource
    private AwsService awsService;
    @Resource
    private RedisUtil redisUtil;


    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDataMap jobDataMap = jobExecutionContext.getTrigger().getJobDataMap();
        Object obj = jobDataMap.get("userId");
        if (obj == null) {
            log.error("因为userId为空,定时任务AmazonDataCaptureJob执行失败");
            return;
        }
        Integer userId = (Integer) obj;
        List<SellerAccount> accounts = sellerAccountService.getAccountsByUserId(userId);
        accounts.forEach(account -> {
            //拉取亚马逊订单数据到数据库
            Aws aws = awsService.getIsUsedAws(account.getAccountId());
            AmazonApi.authAws(account, aws, redisUtil);
            pullAmazonOrderData(orderService, account.getAccountId());
            //更新订单数据
            try {
                orderService.updateShipmentStatus(account.getAccountId());
            } catch (ApiException e) {
                log.error("api异常:{}", e);
            }
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        log.info("AmazonDataCaptureJob任务成功执行,userId = {}", userId);
    }

    private void pullAmazonOrderData(OrderService orderService, Integer sellerId) {
        //获取最新订单时间 比如2022-09-05T15:21:20Z
        String recentOrderTime = orderService.getRecentOrderTime();
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Constants.DATE_FORMAT);
            Date parse = simpleDateFormat.parse(recentOrderTime);
            //对最新时间加一秒处理 比如2022-09-05T15:21:21Z
            Date date = new Date(parse.getTime() + 1000L);
            String format = simpleDateFormat.format(date);
            //根据数据库订单最新时间的下一秒去亚马逊拉取数据
            orderService.saveOrdersList(format, sellerId);
        } catch (ApiException | ParseException e) {
            log.error("定时抓取订单数据异常,异常信息 {}", e);
        }
    }
}

1、添加功能:根据前端传来的信息自己决定是添加任务还是在任务下添加触发器、里面有些业务逻辑可以忽略。

/**
     * 添加定时任务Job
     */
    @SneakyThrows
    @Override
    public String addSchedule(Integer loginUserId, ScheduleInfoDto dto) {
        log.info("添加任务 任务信息为 {}", dto);
        //任务组名后缀,保证不同用户创建任务不冲突
        String userSuf = "_" + loginUserId;
        JobKey jobKey = new JobKey(dto.getJobName(), dto.getJobGroup() + userSuf);
        List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
        //获取新的触发器名和组
        String triggerGroup = dto.getNewTriggerGroup();
        String triggerName = dto.getNewTriggerName();
        Trigger taskTrigger = null;
        for (int i = 0; i < triggers.size(); i++) {
            Trigger trigger = triggers.get(i);
            TriggerKey key = trigger.getKey();
            String name = key.getName();
            String group = key.getGroup();
            if (StrUtil.equals(name, triggerName) && StrUtil.equals(group, triggerGroup)) {
                taskTrigger = trigger;
                break;
            }
        }

        //如果trigger存在,并且任务存在
        if (taskTrigger != null) {
            if (scheduler.checkExists(jobKey)) {
                return "该任务已存在!";
            }
        }

        if (StrUtil.isEmpty(dto.getJobClassName())) {
            return "任务类名为空,请输入正确的类名";
        }
        if (StrUtil.isEmpty(dto.getTriggerName())) {
            dto.setTriggerName("jobTrigger" + IdUtil.fastSimpleUUID());
        }
        if (StrUtil.isEmpty(dto.getTriggerGroup())) {
            dto.setTriggerGroup("jobTriggerGroup" + IdUtil.fastSimpleUUID());
        }
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("userId", loginUserId);
        //任务信息
        JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(dto.getJobClassName()))
                .withIdentity(dto.getJobName(), dto.getJobGroup() + userSuf)
                .withDescription(dto.getDescName())
                .storeDurably()
                .build();
        //触发器信息
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .withIdentity(dto.getNewTriggerName(), dto.getNewTriggerGroup() + userSuf)
                .forJob(dto.getJobName(), dto.getJobGroup() + userSuf)
                .startNow()
                .usingJobData(jobDataMap)
                .withSchedule(CronScheduleBuilder.cronSchedule(dto.getCronExpression()))
                .withDescription(dto.getTriggerDescription())
                .build();
        //如果任务名、组存在,则在任务上添加触发器
        Date date = null;
        if (scheduler.checkExists(jobKey)) {
            date = scheduler.scheduleJob(cronTrigger);
        } else {
            //如果任务不存在,添加任务和触发器
            date = scheduler.scheduleJob(jobDetail, cronTrigger);
        }
        if (date == null) {
            return "任务添加失败";
        }
        //记录是谁添加了定时任务
        QrtzUser qrtzUser = new QrtzUser();
        qrtzUser.setUserId(loginUserId);
        qrtzUser.setGroupName(dto.getJobGroup());
        qrtzUser.setJobName(dto.getJobName());
        qrtzUser.setUpdatedTime(new Date());
        qrtzUser.setTriggerGroup(dto.getNewTriggerGroup());
        qrtzUser.setTriggerName(dto.getNewTriggerName());
        //插入记录
        qrtzUserMapper.insert(qrtzUser);
        log.info("任务添加成功,任务信息:{}", dto);
        return "任务添加成功";
    }

解释:任务存在时,添加触发器,通过cronTrigger.forJob(任务名,任务组) 方法把触发器和任务联系起来,调用

scheduler.scheduleJob(cronTrigger);添加触发器

任务不存在时,添加任务,通过

scheduler.scheduleJob(jobDetail, cronTrigger);方法添加任务,jobDetail也可以写在cronTrigger里面,目的都是把信息传进去。

通过jobDataMap传递参数

注意:如果发生异常,异常会被lombok的

@SneakyThrows注解捕获,再通过SpringMVC的全局异常处理器能很好地处理

2、暂停功能:暂停触发器

/**
     * 暂停定时任务
     *
     * @param triggerName
     * @param triggerGroup
     * @param loginUserId
     */
    @Override
    public void pauseSchedule(String triggerName, String triggerGroup, Integer loginUserId) {
        try {
            scheduler.pauseTrigger(new TriggerKey(triggerName, triggerGroup + "_" + loginUserId));
            log.info("触发器名:{},触发器组:{} ,暂停成功", triggerName, triggerGroup);
        } catch (SchedulerException e) {
            log.error("触发器名:{},触发器组:{},暂停异常,{}", triggerName, triggerGroup, e);
        }
    }

解释:暂停触发器很简单就是调用下面的方法

scheduler.pauseTrigger(new TriggerKey(triggerName, triggerGroup + "_" + loginUserId));
如果是暂停任务的话调用scheduler.pauseJob(JobKey.jobKey());这个方法会暂停该任务下面的所有触发器。

3、恢复功能:跟暂停功能相反,可以通过

scheduler.resumeJob(JobKey.jobKey());方法恢复任务
/**
     * 恢复定时任务
     *
     * @param triggerName
     * @param triggerGroup
     * @param loginUserId
     */
    @Override
    public void resumeSchedule(String triggerName, String triggerGroup, Integer loginUserId) {
        try {
            scheduler.resumeTrigger(new TriggerKey(triggerName, triggerGroup + "_" + loginUserId));
            log.info("触发器名:{},触发器组:{} ,恢复成功", triggerName, triggerGroup);
        } catch (SchedulerException e) {
            log.error("触发器名:{},触发器组:{},恢复异常,{}", triggerName, triggerGroup, e);
        }
    }

4、更新触发器功能:这个功能和前面有点不一样,说是替换触发器更合适,它是把旧触发器替换我新触发器,达到更新的功能

/**
     * 更新定时任务
     */
    @SneakyThrows
    @Override
    public String updateSchedule(Integer loginUserId, ScheduleInfoDto dto) {
        JobKey jobKey = new JobKey(dto.getJobName(), dto.getJobGroup());
        if (ObjectUtil.isEmpty(loginUserId)) {
            return "请先登录再操作,任务更新失败";
        }
        //检查任务key是否存在
        if (scheduler.checkExists(jobKey)) {
            //老的触发器
            Trigger oldTrigger = null;
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
            //遍历任务的所有触发器,拿到老的触发器
            for (int i = 0; i < triggers.size(); i++) {
                Trigger trigger = triggers.get(i);
                TriggerKey key = trigger.getKey();
                String group = key.getGroup();
                String name = key.getName();
                if (StrUtil.equals(name, dto.getTriggerName()) && StrUtil.equals(group, dto.getTriggerGroup())) {
                    oldTrigger = trigger;
                    log.info("成功拿到老触发器");
                    break;
                }
            }
            if (null == oldTrigger) {
                return "老触发器不存在,更新失败";
            }
            //获取前端传来的触发器组、名 ,如果没有则自动生成一个
            String triggerName = dto.getNewTriggerName();
            String triggerGroup = dto.getNewTriggerGroup();
            if (StrUtil.isEmpty(triggerName)) {
                triggerName = UUID.randomUUID().toString().replace("-", "");
            }
            if (StrUtil.isEmpty(triggerGroup)) {
                triggerGroup = UUID.randomUUID().toString().replace("-", "");
            }
            //设置新的触发器
            CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                    .withIdentity(triggerName, triggerGroup)
                    .startNow()
                    .withSchedule(CronScheduleBuilder.cronSchedule(dto.getCronExpression()))
                    .withDescription(dto.getTriggerDescription())
                    .build();
            //把老触发器换成新触发器
            Date date = scheduler.rescheduleJob(oldTrigger.getKey(), cronTrigger);
            // 如果触发器更新成功会返回一个date
            if (null == date) {
                return "任务更新失败";
            }
            UpdateWrapper<QrtzUser> qrtzUserUpdateWrapper = new UpdateWrapper<>();
            qrtzUserUpdateWrapper.eq("user_id", loginUserId).eq("job_name", dto.getJobName()).eq("group_name", dto.getJobGroup());
            //把触发器替换成功的时间作为任务更新时间
            qrtzUserUpdateWrapper.set("updated_time", date);
            qrtzUserService.update(qrtzUserUpdateWrapper);
            return "任务更新成功";
        } else {
            return "任务不存在,不能执行更新操作";
        }
    }

解释:通过

scheduler.rescheduleJob(oldTrigger.getKey(), cronTrigger);方法更新触发器,第一个参数是老触发器、第二个参数是新触发器,老触发器通过遍历任务下面所有触发器,比对触发器名字和组,拿到老触发器;新触发器通过
TriggerBuilder.newTrigger().....build(),这个建造方法获取

5、删除触发器,如果任务下面已经没有了触发器则删除任务

/**
     * 删除定时任务
     *
     * @param triggerName
     * @param triggerGroup
     * @param loginUserId
     */
    @Override
    public void deleteSchedule(String triggerName, String triggerGroup, Integer loginUserId, String jobName, String jobGroup) {
        try {
            TriggerKey triggerKey = new TriggerKey(triggerName, triggerGroup + "_" + loginUserId);
            //删除触发器之前暂停触发器
            scheduler.pauseTrigger(triggerKey);
            //删除触发器
            boolean res = scheduler.unscheduleJob(triggerKey);
            //如果触发器删除失败
            log.info("定时任务删除结果 {}", res);
            if (!res) {
                return;
            }
            QueryWrapper<QrtzUser> qw = new QueryWrapper<>();
            qw.eq("trigger_name", triggerName);
            qw.eq("trigger_group", triggerGroup);
            qrtzUserMapper.delete(qw);
            log.info("触发器名:{},触发器组:{} ,删除成功", triggerName, triggerGroup);
            //如果任务下没有触发器,删除任务
            JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
            List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(jobKey);
            if (triggersOfJob.size() == 0) {
                scheduler.pauseJob(jobKey);
                boolean r = scheduler.deleteJob(jobKey);
                log.info("任务名:{},任务组:{} ,删除结果 {}", jobName, jobGroup, r);
            }
        } catch (SchedulerException e) {
            log.error("任务名:{},任务组:{},删除异常,{}", triggerName, triggerGroup, e);
        }
    }

解释:删除触发器

scheduler.pauseTrigger(triggerKey);
scheduler.unscheduleJob(triggerKey);

删除任务

scheduler.pauseJob(jobKey);
scheduler.deleteJob(jobKey);

需要注意的是:删除之前,需要先暂停,不然删除会失败

6、查询所有任务所有触发器

/**
     * 获取所有定时任务*
     * QrtzUser中的任务组+_userId = 真实的任务组
     * 触发器组_userId = 真实的触发器组*
     *
     * @param loginUserId
     * @return
     */
    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    @Override
    @SneakyThrows
    public List<ScheduleInfoDto> getAllSchedule(Integer loginUserId) {
        if (loginUserId == null) {
            log.info("用户id为空");
            return null;
        }
        String userSuf = "_" + loginUserId;
        log.info("获取用户id = {} 的所有定时任务", loginUserId);
        List<ScheduleInfoDto> list = new ArrayList<>();
        QueryWrapper<QrtzUser> qw = new QueryWrapper<>();
        qw.eq("user_id", loginUserId);
        List<QrtzUser> qrtzUsers = qrtzUserMapper.selectList(qw);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        qrtzUsers.forEach(qrtzUser -> {
            String triggerGroup = qrtzUser.getTriggerGroup();
            String triggerName = qrtzUser.getTriggerName();
            String jobName = qrtzUser.getJobName();
            String jobGroup = qrtzUser.getGroupName() + userSuf;
            JobKey jobKey = new JobKey(jobName, jobGroup);
            try {
                //获取任务详情
                JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                TriggerKey triggerKey = new TriggerKey(triggerName, triggerGroup + userSuf);
                //获取触发器
                Trigger trigger = scheduler.getTrigger(triggerKey);
                ScheduleInfoDto scheduleInfoDto = new ScheduleInfoDto();
                scheduleInfoDto.setJobClassName(jobDetail.getJobClass().toString());
                scheduleInfoDto.setDescName(jobDetail.getDescription());
                scheduleInfoDto.setJobGroup(qrtzUser.getGroupName());
                scheduleInfoDto.setJobName(jobName);
                User user = userService.getById(loginUserId);
                //设置创建者
                scheduleInfoDto.setUserName(user.getUsername());
                if (ObjectUtil.isNotEmpty(qrtzUser.getCreatedTime())) {
                    scheduleInfoDto.setCreatedTime(simpleDateFormat.format(qrtzUser.getCreatedTime()));
                }
                if (ObjectUtil.isNotEmpty(qrtzUser.getUpdatedTime())) {
                    scheduleInfoDto.setUpdatedTime(simpleDateFormat.format(qrtzUser.getUpdatedTime()));
                }
                //获取触发器名
                scheduleInfoDto.setTriggerName(trigger.getKey().getName());
                //获取触发器组
                scheduleInfoDto.setTriggerGroup(triggerGroup);
                scheduleInfoDto.setTriggerDescription(trigger.getDescription());
                try {
                    //获取任务状态
                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                    scheduleInfoDto.setTriggerState(triggerState.name());
                    //获取Cron表达式
                    if (trigger instanceof CronTrigger) {
                        CronTrigger cronTrigger = (CronTrigger) trigger;
                        String cronExpression = cronTrigger.getCronExpression();
                        scheduleInfoDto.setCronExpression(cronExpression);
                        scheduleInfoDto.setCronExpressionZH(CronExpParserUtil.translateToChinese(cronExpression));
                    }
                } catch (SchedulerException e) {
                    e.printStackTrace();
                }
                list.add(scheduleInfoDto);


            } catch (SchedulerException e) {
                e.printStackTrace();
                return;
            }
        });
        return list;
    }

解释:通过任务名和任务组,调用

scheduler.getJobDetail(jobKey);拿到任务信息,通过任务遍历任务下面所有触发器,就能拿到所有触发器信息,包括Cron表达式、状态、描述、名字、组名等。
如果你不知道任务名、任务组通过
GroupMatcher<JobKey> matchers =GroupMatcher.anyJobGroup();
Set<JobKey> jobKeys = scheduler.getJobKeys(matchers);

通过上面两行代码就能拿到所有的任务名、任务组,接下来就能愉快的玩耍了