package com.ruoyi.safe.service.impl; import com.ruoyi.safe.pojo.SafeLineInspectionTask; import lombok.RequiredArgsConstructor; import org.quartz.CronScheduleBuilder; 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.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 @RequiredArgsConstructor public class SafeLineInspectionTaskScheduler { private static final String JOB_GROUP = "safeLineInspectionTask"; private static final String TRIGGER_GROUP = "safeLineInspectionTaskTrigger"; private final Scheduler scheduler; public void scheduleTask(SafeLineInspectionTask task) throws SchedulerException { JobDetail jobDetail = buildJobDetail(task); if (scheduler.checkExists(jobDetail.getKey())) { scheduler.deleteJob(jobDetail.getKey()); } scheduler.scheduleJob(jobDetail, buildJobTrigger(task, jobDetail)); } public void rescheduleTask(SafeLineInspectionTask task) throws SchedulerException { TriggerKey triggerKey = getTriggerKey(task.getId()); Trigger oldTrigger = scheduler.getTrigger(triggerKey); if (oldTrigger == null) { scheduleTask(task); return; } JobDetail jobDetail = scheduler.getJobDetail(oldTrigger.getJobKey()); if (jobDetail == null) { scheduleTask(task); return; } scheduler.rescheduleJob(triggerKey, buildJobTrigger(task, jobDetail)); } public void unscheduleTask(Long taskId) { try { scheduler.deleteJob(getJobKey(taskId)); } catch (SchedulerException e) { throw new RuntimeException(e); } } private JobDetail buildJobDetail(SafeLineInspectionTask task) { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("taskId", task.getId()); jobDataMap.put("taskName", task.getInspectionName()); jobDataMap.put("frequencyType", task.getFrequencyType()); return JobBuilder.newJob(SafeLineInspectionTaskJob.class) .withIdentity(getJobKey(task.getId())) .withDescription(task.getInspectionName()) .usingJobData(jobDataMap) .storeDurably(true) .requestRecovery(true) .build(); } private Trigger buildJobTrigger(SafeLineInspectionTask task, JobDetail jobDetail) { return TriggerBuilder.newTrigger() .withIdentity(getTriggerKey(task.getId())) .withDescription(task.getInspectionName() + "_TRIGGER") .forJob(jobDetail) .withSchedule(CronScheduleBuilder .cronSchedule(convertToCronExpression(task)) .withMisfireHandlingInstructionDoNothing()) .startAt(task.getNextExecutionTime() != null ? Date.from(task.getNextExecutionTime().atZone(ZoneId.systemDefault()).toInstant()) : new Date()) .build(); } private JobKey getJobKey(Long taskId) { return new JobKey("safeLineInspectionTask_" + taskId, JOB_GROUP); } private TriggerKey getTriggerKey(Long taskId) { return new TriggerKey("safeLineInspectionTaskTrigger_" + taskId, TRIGGER_GROUP); } private String convertToCronExpression(SafeLineInspectionTask task) { if (task == null || task.getFrequencyType() == null || task.getFrequencyDetail() == null) { throw new IllegalArgumentException("任务频率不能为空"); } return switch (task.getFrequencyType().toUpperCase()) { case "DAILY" -> convertDailyToCron(task.getFrequencyDetail()); case "WEEKLY" -> convertWeeklyToCron(task.getFrequencyDetail()); case "MONTHLY" -> convertMonthlyToCron(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 = Arrays.stream(parts[0].split("\\|")) .map(this::convertSingleDayName) .collect(Collectors.joining(",")); 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[] validateAndSplit(String input, int expectedParts) { String[] parts = input.split(","); if (parts.length != expectedParts) { throw new IllegalArgumentException("任务频率详情格式错误"); } return Arrays.stream(parts).map(String::trim).toArray(String[]::new); } private LocalTime parseTime(String timeStr) { try { return LocalTime.parse(timeStr); } catch (DateTimeParseException e) { throw new IllegalArgumentException("时间格式必须为HH:mm", e); } } private int validateDayOfMonth(String dayStr) { int day = Integer.parseInt(dayStr); if (day < 1 || day > 31) { throw new IllegalArgumentException("日期必须在1-31之间"); } return day; } private String convertSingleDayName(String dayName) { return switch (dayName.toUpperCase()) { case "MON" -> "MON"; case "TUE" -> "TUE"; case "WED" -> "WED"; case "THU" -> "THU"; case "FRI" -> "FRI"; case "SAT" -> "SAT"; case "SUN" -> "SUN"; default -> throw new IllegalArgumentException("无效的星期几: " + dayName); }; } }