package com.ruoyi.business.service.impl; import com.ruoyi.business.entity.TimingTask; import com.ruoyi.business.task.TimingTaskJob; import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.DayOfWeek; import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeParseException; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.Set; 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"); } // 构建新触发器 Trigger newTrigger = TriggerBuilder.newTrigger() .withIdentity(triggerKey) .withDescription(task.getTaskName()) .withSchedule(CronScheduleBuilder.cronSchedule(convertToCronExpression(task))) .startAt(Date.from(task.getNexExecutionTime().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) { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("taskId", task.getId()); return JobBuilder.newJob(TimingTaskJob.class) .withIdentity("timingTask_" + task.getId()) .withDescription(task.getTaskName()) .usingJobData(jobDataMap) .storeDurably() .build(); } private Trigger buildJobTrigger(TimingTask task, JobDetail jobDetail) { String cronExpression = convertToCronExpression(task); TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger() .withIdentity("trigger_" + task.getId()) .withDescription(task.getTaskName()) .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)); if (jobDetail != null) { triggerBuilder.forJob(jobDetail); } if (task.getNexExecutionTime() != null) { triggerBuilder.startAt(Date.from(task.getNexExecutionTime().atZone(ZoneId.systemDefault()).toInstant())); } return triggerBuilder.build(); } private String convertToCronExpression(TimingTask task) { // 参数校验 if (task == null || task.getFrequencyType() == null || task.getFrequencyDetail() == null) { throw new IllegalArgumentException("任务参数不能为空"); } // 使用switch确保条件互斥 return switch (task.getFrequencyType().toUpperCase()) { // 统一转为大写比较 case "DAILY" -> convertDailyToCron(task.getFrequencyDetail()); case "WEEKLY" -> convertWeeklyToCron(task.getFrequencyDetail()); case "MONTHLY" -> convertMonthlyToCron(task.getFrequencyDetail()); case "QUARTERLY" -> 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); } } }