package com.ruoyi.inspectiontask.service.impl; import com.ruoyi.inspectiontask.pojo.TimingTask; import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeParseException; import java.util.Arrays; import java.util.Date; import java.util.stream.Collectors; @Service public class TimingTaskScheduler { @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则立即生效) .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); } /** * 恢复任务 */ public void resumeTimingTask(Long taskId) throws SchedulerException { JobKey jobKey = new JobKey("timingTask_" + taskId); scheduler.resumeJob(jobKey); } /** * 删除任务 */ public void unscheduleTimingTask(Long taskId) throws SchedulerException { JobKey jobKey = new JobKey("timingTask_" + taskId); scheduler.deleteJob(jobKey); } 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) // 按需添加其他必要的基本类型参数 // 3. 构建JobDetail,设置持久化相关属性 return JobBuilder.newJob(TimingTaskJob.class) .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则立即生效) .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(); // 统一转为大写比较 switch (frequencyType) { case "DAILY": return convertDailyToCron(task.getFrequencyDetail()); case "WEEKLY": return convertWeeklyToCron(task.getFrequencyDetail()); case "MONTHLY": return convertMonthlyToCron(task.getFrequencyDetail()); case "QUARTERLY": return convertQuarterlyToCron(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]); LocalTime time = parseTime(parts[1]); 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]); LocalTime time = parseTime(parts[1]); 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]); // 解析时间 // 计算季度起始月份(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); } // 新增验证月份的方法(1-12) private int validateMonth(String monthStr) { try { int month = Integer.parseInt(monthStr); if (month < 1 || month > 12) { throw new IllegalArgumentException("月份必须在1-12之间"); } return month; } catch (NumberFormatException e) { throw new IllegalArgumentException("无效的月份格式"); } } // 辅助方法:解析时间 private LocalTime parseTime(String timeStr) { try { return LocalTime.parse(timeStr); } catch (DateTimeParseException 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)); } return parts; } // 辅助方法:验证月份中的日 private int validateDayOfMonth(String dayStr) { int day = Integer.parseInt(dayStr); if (day < 1 || day > 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); } } }