3 天以前 c477506a6d672f71c6353608f3cf3424d8026790
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduler.java
@@ -1,7 +1,17 @@
package com.ruoyi.inspectiontask.service.impl;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import org.quartz.*;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -18,128 +28,86 @@
    @Autowired
    private Scheduler scheduler;
    /**
     * 添加新任务到调度器
     */
    public void scheduleTimingTask(TimingTask task) throws SchedulerException {
        JobDetail jobDetail = buildJobDetail(task);
        Trigger trigger = buildJobTrigger(task, jobDetail);
        scheduler.scheduleJob(jobDetail, trigger);
    }
    /**
     * 更新已有任务
     */
    public void rescheduleTimingTask(TimingTask task) throws SchedulerException {
        TriggerKey triggerKey = new TriggerKey("trigger_" + task.getId());
        // 获取现有触发器并转换为 CronTrigger
        Trigger oldTrigger = scheduler.getTrigger(triggerKey);
        if (!(oldTrigger instanceof CronTrigger)) {
            throw new SchedulerException("Existing trigger is not a CronTrigger");
        }
        // 3. 构建CronTrigger,确保持久化配置
        CronTrigger newTrigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerKey)                // 唯一标识,用于持久化存储
                .withDescription(task.getTaskName() + "_TRIGGER") // 触发器描述
                .forJob(oldTrigger.getJobKey())                       // 关联对应的Job
                .withSchedule(CronScheduleBuilder
                        .cronSchedule(convertToCronExpression(task)) // 错过执行时的策略(根据业务调整)
                )
                // 4. 设置开始时间(若为null则立即生效)
                .withIdentity(triggerKey)
                .withDescription(task.getTaskName() + "_TRIGGER")
                .forJob(oldTrigger.getJobKey())
                .withSchedule(CronScheduleBuilder.cronSchedule(convertToCronExpression(task)))
                .startAt(task.getNextExecutionTime() != null
                        ? Date.from(task.getNextExecutionTime().atZone(ZoneId.systemDefault()).toInstant())
                        : new Date())
                .build();
        // 构建新触发器
//        Trigger newTrigger = TriggerBuilder.newTrigger()
//                .withIdentity(triggerKey)
//                .withDescription(task.getTaskName())
//                .withSchedule(CronScheduleBuilder.cronSchedule(convertToCronExpression(task)))
//                .startAt(Date.from(task.getNextExecutionTime().atZone(ZoneId.systemDefault()).toInstant()))
//                .forJob(oldTrigger.getJobKey())
//                .build();
        scheduler.rescheduleJob(triggerKey, newTrigger);
    }
    /**
     * 暂停任务
     */
    public void pauseTimingTask(Long taskId) throws SchedulerException {
        JobKey jobKey = new JobKey("timingTask_" + taskId);
        scheduler.pauseJob(jobKey);
        scheduler.pauseJob(new JobKey("timingTask_" + taskId));
    }
    /**
     * 恢复任务
     */
    public void resumeTimingTask(Long taskId) throws SchedulerException {
        JobKey jobKey = new JobKey("timingTask_" + taskId);
        scheduler.resumeJob(jobKey);
        scheduler.resumeJob(new JobKey("timingTask_" + taskId));
    }
    /**
     * 删除任务
     */
    public void unscheduleTimingTask(Long taskId) throws SchedulerException {
        JobKey jobKey = new JobKey("timingTask_" + taskId);
        scheduler.deleteJob(jobKey);
    public void unscheduleTimingTask(Long taskId) {
        try {
            scheduler.deleteJob(new JobKey("timingTask_" + taskId));
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
    private JobDetail buildJobDetail(TimingTask task) {
        // 1. 构建唯一JobKey(基于任务ID,确保重启后能识别)
        JobKey jobKey = new JobKey("timingTask_" + task.getId());
        // 2. 封装任务数据(仅使用基本类型,确保可序列化)
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("taskId", task.getId());           // 任务ID(Long,可序列化)
        jobDataMap.put("taskName", task.getTaskName());   // 任务名称(String,可序列化)
        jobDataMap.put("taskType", task.getFrequencyType()); // 任务类型(String)
        // 按需添加其他必要的基本类型参数
        jobDataMap.put("taskId", task.getId());
        jobDataMap.put("taskName", task.getTaskName());
        jobDataMap.put("taskType", task.getFrequencyType());
        // 3. 构建JobDetail,设置持久化相关属性
        return JobBuilder.newJob(TimingTaskJob.class)
                .withIdentity(jobKey)                    // 唯一标识,用于持久化存储
                .withDescription(task.getTaskName())     // 任务描述,存入数据库
                .usingJobData(jobDataMap)                // 绑定任务数据
                .storeDurably(true)                          // 即使没有触发器关联也持久化保存
                .requestRecovery(true)                   // 当调度器崩溃后恢复时,重新执行未完成的任务
                .withIdentity(jobKey)
                .withDescription(task.getTaskName())
                .usingJobData(jobDataMap)
                .storeDurably(true)
                .requestRecovery(true)
                .build();
    }
    private Trigger buildJobTrigger(TimingTask task, JobDetail jobDetail) {
        // 1. 构建唯一TriggerKey(基于任务ID)
        TriggerKey triggerKey = new TriggerKey("trigger_" + task.getId());
        // 2. 生成Cron表达式(原逻辑不变)
        String cronExpression = convertToCronExpression(task);
        // 3. 构建CronTrigger,确保持久化配置
        return TriggerBuilder.newTrigger()
                .withIdentity(triggerKey)                // 唯一标识,用于持久化存储
                .withDescription(task.getTaskName() + "_TRIGGER") // 触发器描述
                .forJob(jobDetail)                       // 关联对应的Job
                .withSchedule(CronScheduleBuilder
                        .cronSchedule(cronExpression)
                        .withMisfireHandlingInstructionDoNothing() // 错过执行时的策略(根据业务调整)
                )
                // 4. 设置开始时间(若为null则立即生效)
                .withIdentity(triggerKey)
                .withDescription(task.getTaskName() + "_TRIGGER")
                .forJob(jobDetail)
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)
                        .withMisfireHandlingInstructionDoNothing())
                .startAt(task.getNextExecutionTime() != null
                        ? Date.from(task.getNextExecutionTime().atZone(ZoneId.systemDefault()).toInstant())
                        : new Date())
                .build();
    }
    private String convertToCronExpression(TimingTask task) {
        // 参数校验
        if (task == null || task.getFrequencyType() == null || task.getFrequencyDetail() == null) {
            throw new IllegalArgumentException("任务参数不能为空");
        }
        // 使用switch确保条件互斥
        String frequencyType = task.getFrequencyType().toUpperCase(); // 统一转为大写比较
        String frequencyType = task.getFrequencyType().toUpperCase();
        switch (frequencyType) {
            case "DAILY":
                return convertDailyToCron(task.getFrequencyDetail());
@@ -149,18 +117,18 @@
                return convertMonthlyToCron(task.getFrequencyDetail());
            case "QUARTERLY":
                return convertQuarterlyToCron(task.getFrequencyDetail());
            case "YEARLY":
                return convertYearlyToCron(task.getFrequencyDetail());
            default:
                throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType());
        }
    }
    // 每日任务转换
    private String convertDailyToCron(String frequencyDetail) {
        LocalTime time = parseTime(frequencyDetail);
        return String.format("0 %d %d * * ?", time.getMinute(), time.getHour());
    }
    // 每周任务转换
    private String convertWeeklyToCron(String frequencyDetail) {
        String[] parts = validateAndSplit(frequencyDetail, ",", 2);
        String daysOfWeek = convertDayNamesToCron(parts[0]);
@@ -168,7 +136,6 @@
        return String.format("0 %d %d ? * %s", time.getMinute(), time.getHour(), daysOfWeek);
    }
    // 每月任务转换
    private String convertMonthlyToCron(String frequencyDetail) {
        String[] parts = validateAndSplit(frequencyDetail, ",", 2);
        int day = validateDayOfMonth(parts[0]);
@@ -176,29 +143,29 @@
        return String.format("0 %d %d %d * ?", time.getMinute(), time.getHour(), day);
    }
    // 每季度任务转换
    private String convertQuarterlyToCron(String frequencyDetail) {
        String[] parts = validateAndSplit(frequencyDetail, ",", 3);
        int month = validateMonth(parts[0]);  // 验证月份(1-12)
        int day = validateDayOfMonth(parts[1]);  // 验证日期
        LocalTime time = parseTime(parts[2]);  // 解析时间
        int month = validateMonth(parts[0]);
        int day = validateDayOfMonth(parts[1]);
        LocalTime time = parseTime(parts[2]);
        // 计算季度起始月份(1月=1, 4月=4, 7月=7, 10月=10)
        int quarterStartMonth = ((month - 1) / 3) * 3 + 1;
        return String.format("0 %d %d %d %d/3 ?",
                time.getMinute(),
                time.getHour(),
                day,
                quarterStartMonth);
        return String.format("0 %d %d %d %d/3 ?", time.getMinute(), time.getHour(), day, quarterStartMonth);
    }
    // 新增验证月份的方法(1-12)
    private String convertYearlyToCron(String frequencyDetail) {
        String[] parts = validateAndSplit(frequencyDetail, ",", 3);
        int month = validateMonth(parts[0]);
        int day = validateDayOfMonth(parts[1]);
        LocalTime time = parseTime(parts[2]);
        return String.format("0 %d %d %d %d ?", time.getMinute(), time.getHour(), day, month);
    }
    private int validateMonth(String monthStr) {
        try {
            int month = Integer.parseInt(monthStr);
            if (month < 1 || month > 12) {
                throw new IllegalArgumentException("月份必须在1-12之间");
                throw new IllegalArgumentException("月份必须在 1-12 之间");
            }
            return month;
        } catch (NumberFormatException e) {
@@ -206,61 +173,54 @@
        }
    }
    // 辅助方法:解析时间
    private LocalTime parseTime(String timeStr) {
        try {
            return LocalTime.parse(timeStr);
        } catch (DateTimeParseException e) {
            throw new IllegalArgumentException("时间格式必须为HH:mm", e);
            throw new IllegalArgumentException("时间格式必须为 HH:mm", e);
        }
    }
    // 辅助方法:验证并分割字符串
    private String[] validateAndSplit(String input, String delimiter, int expectedParts) {
        String[] parts = input.split(delimiter);
        if (parts.length != expectedParts) {
            throw new IllegalArgumentException(
                    String.format("格式错误,应为%d部分用'%s'分隔", expectedParts, delimiter));
            throw new IllegalArgumentException(String.format("格式错误,应为 %d 部分,以 '%s' 分隔", expectedParts, delimiter));
        }
        return parts;
    }
    // 辅助方法:验证月份中的日
    private int validateDayOfMonth(String dayStr) {
        int day = Integer.parseInt(dayStr);
        if (day < 1 || day > 31) {
            throw new IllegalArgumentException("日期必须在1-31之间");
            throw new IllegalArgumentException("日期必须在 1-31 之间");
        }
        return day;
    }
    // 辅助方法:验证季度中的月
    private int validateMonthInQuarter(String monthStr) {
        int month = Integer.parseInt(monthStr);
        if (month < 1 || month > 3) {
            throw new IllegalArgumentException("季度月份必须是1、2或3");
        }
        return month;
    }
    // 转换星期几名称
    private String convertDayNamesToCron(String dayNames) {
        return Arrays.stream(dayNames.split("\\|"))
                .map(this::convertSingleDayName)
                .collect(Collectors.joining(","));
    }
    // 转换单个星期几名称
    private String convertSingleDayName(String dayName) {
        switch (dayName.toUpperCase()) {
            case "MON": return "MON";
            case "TUE": return "TUE";
            case "WED": return "WED";
            case "THU": return "THU";
            case "FRI": return "FRI";
            case "SAT": return "SAT";
            case "SUN": return "SUN";
            default: throw new IllegalArgumentException("无效的星期几: " + dayName);
            case "MON":
                return "MON";
            case "TUE":
                return "TUE";
            case "WED":
                return "WED";
            case "THU":
                return "THU";
            case "FRI":
                return "FRI";
            case "SAT":
                return "SAT";
            case "SUN":
                return "SUN";
            default:
                throw new IllegalArgumentException("无效的星期: " + dayName);
        }
    }
}
}