src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java
@@ -140,6 +140,8 @@ return calculateMonthlyNextTime(frequencyDetail, currentTime); case "QUARTERLY": return calculateQuarterlyNextTime(frequencyDetail, currentTime); case "YEARLY": return calculateYearlyNextTime(frequencyDetail, currentTime); default: throw new IllegalArgumentException("不支持的频率类型: " + frequencyType); } @@ -205,6 +207,23 @@ ); } private LocalDateTime calculateYearlyNextTime(String detail, LocalDateTime current) { String[] parts = detail.split(","); int month = Integer.parseInt(parts[0]); int dayOfMonth = Integer.parseInt(parts[1]); LocalTime time = LocalTime.parse(parts[2]); YearMonth targetYearMonth = YearMonth.of(current.getYear(), month); int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth()); LocalDateTime target = LocalDateTime.of(current.getYear(), month, adjustedDay, time.getHour(), time.getMinute()); if (!target.isAfter(current)) { targetYearMonth = YearMonth.of(current.getYear() + 1, month); adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth()); target = LocalDateTime.of(current.getYear() + 1, month, adjustedDay, time.getHour(), time.getMinute()); } return target; } private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) { Set<DayOfWeek> days = new HashSet<>(); String[] dayStrs = dayOfWeekStr.split("\\|"); src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskScheduler.java
@@ -2,7 +2,17 @@ import com.ruoyi.device.pojo.MaintenanceTask; import lombok.extern.slf4j.Slf4j; 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; @@ -13,10 +23,6 @@ import java.util.Date; import java.util.stream.Collectors; /** * @author :yys * @date : 2025/12/22 15:16 */ @Service @Slf4j public class MaintenanceTaskScheduler { @@ -24,9 +30,6 @@ @Autowired private Scheduler scheduler; /** * 添加新任务到调度器 */ public void scheduleMaintenanceTask(MaintenanceTask task){ try { JobDetail jobDetail = buildJobDetail(task); @@ -38,28 +41,19 @@ } } /** * 更新已有任务 */ public void rescheduleMaintenanceTask(MaintenanceTask task){ try{ TriggerKey triggerKey = new TriggerKey("triggerMaintenanceTask_" + 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()) @@ -71,29 +65,17 @@ } } /** * 暂停任务 */ public void pauseMaintenanceTask(Long taskId) throws SchedulerException { JobKey jobKey = new JobKey("MaintenanceTask_" + taskId); scheduler.pauseJob(jobKey); scheduler.pauseJob(new JobKey("MaintenanceTask_" + taskId)); } /** * 恢复任务 */ public void resumeMaintenanceTask(Long taskId) throws SchedulerException { JobKey jobKey = new JobKey("MaintenanceTask_" + taskId); scheduler.resumeJob(jobKey); scheduler.resumeJob(new JobKey("MaintenanceTask_" + taskId)); } /** * 删除任务 */ public void unscheduleMaintenanceTask(Long taskId){ try { JobKey jobKey = new JobKey("MaintenanceTask_" + taskId); scheduler.deleteJob(jobKey); scheduler.deleteJob(new JobKey("MaintenanceTask_" + taskId)); }catch (SchedulerException e){ log.error("SchedulerException unscheduleMaintenanceTask ERROR",e); throw new RuntimeException(e); @@ -101,56 +83,42 @@ } private JobDetail buildJobDetail(MaintenanceTask task) { // 1. 构建唯一JobKey(基于任务ID,确保重启后能识别) JobKey jobKey = new JobKey("MaintenanceTask_" + task.getId()); // 2. 封装任务数据(仅使用基本类型,确保可序列化) JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("maintenanceTaskId", task.getId()); // 任务ID(Long,可序列化) jobDataMap.put("taskName", task.getTaskName()); // 任务名称(String,可序列化) jobDataMap.put("taskType", task.getFrequencyType()); // 任务类型(String) // 按需添加其他必要的基本类型参数 jobDataMap.put("maintenanceTaskId", task.getId()); jobDataMap.put("taskName", task.getTaskName()); jobDataMap.put("taskType", task.getFrequencyType()); // 3. 构建JobDetail,设置持久化相关属性 return JobBuilder.newJob(MaintenanceTaskJob.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(MaintenanceTask task, JobDetail jobDetail) { // 1. 构建唯一TriggerKey(基于任务ID) TriggerKey triggerKey = new TriggerKey("triggerMaintenanceTask_" + 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(MaintenanceTask 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()); @@ -160,18 +128,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]); @@ -179,7 +147,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]); @@ -187,24 +154,24 @@ 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); @@ -217,7 +184,6 @@ } } // 辅助方法:解析时间 private LocalTime parseTime(String timeStr) { try { return LocalTime.parse(timeStr); @@ -226,17 +192,14 @@ } } // 辅助方法:验证并分割字符串 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) { @@ -245,34 +208,30 @@ 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); } } } src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
@@ -66,12 +66,12 @@ ); TimingTask timingTask = tasks.isEmpty() ? null : tasks.get(0); if (timingTask == null) { throw new JobExecutionException("找不到定时任务: " + taskId); throw new JobExecutionException("鎵句笉鍒板畾鏃朵换鍔? " + taskId); } List<Long> deviceIds = resolveTaskIds(timingTask); if (deviceIds.isEmpty()) { throw new JobExecutionException("定时任务未配置设备: " + taskId); throw new JobExecutionException("瀹氭椂浠诲姟鏈厤缃澶? " + taskId); } for (Long deviceId : deviceIds) { @@ -130,7 +130,7 @@ inspectionTask.setAreaId(deviceLedger.getAreaId()); inspectionTask.setInspectorId(timingTask.getInspectorIds()); inspectionTask.setInspectionLocation(timingTask.getInspectionLocation()); inspectionTask.setRemarks("自动生成自定时任务ID: " + timingTask.getId()); inspectionTask.setRemarks("鑷姩鐢熸垚鑷畾鏃朵换鍔D: " + timingTask.getId()); inspectionTask.setRegistrantId(timingTask.getRegistrantId()); inspectionTask.setRegistrant(timingTask.getRegistrant()); inspectionTask.setFrequencyType(timingTask.getFrequencyType()); @@ -152,11 +152,13 @@ return calculateMonthlyNextTime(frequencyDetail, currentTime); case "QUARTERLY": return calculateQuarterlyNextTime(frequencyDetail, currentTime); case "YEARLY": return calculateYearlyNextTime(frequencyDetail, currentTime); default: throw new IllegalArgumentException("不支持的频率类型: " + frequencyType); throw new IllegalArgumentException("涓嶆敮鎸佺殑棰戠巼绫诲瀷: " + frequencyType); } } catch (Exception e) { throw new RuntimeException("计算下次执行时间失败: " + e.getMessage(), e); throw new RuntimeException("璁$畻涓嬫鎵ц鏃堕棿澶辫触: " + e.getMessage(), e); } } @@ -179,7 +181,7 @@ return LocalDateTime.of(nextTime.toLocalDate(), time); } if (nextTime.isAfter(current.plusYears(1))) { throw new RuntimeException("无法找到下次执行时间"); throw new RuntimeException("鏃犳硶鎵惧埌涓嬫鎵ц鏃堕棿"); } } } @@ -217,6 +219,23 @@ ); } private LocalDateTime calculateYearlyNextTime(String detail, LocalDateTime current) { String[] parts = detail.split(","); int month = Integer.parseInt(parts[0]); int dayOfMonth = Integer.parseInt(parts[1]); LocalTime time = LocalTime.parse(parts[2]); YearMonth targetYearMonth = YearMonth.of(current.getYear(), month); int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth()); LocalDateTime target = LocalDateTime.of(current.getYear(), month, adjustedDay, time.getHour(), time.getMinute()); if (!target.isAfter(current)) { targetYearMonth = YearMonth.of(current.getYear() + 1, month); adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth()); target = LocalDateTime.of(current.getYear() + 1, month, adjustedDay, time.getHour(), time.getMinute()); } return target; } private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) { Set<DayOfWeek> days = new HashSet<>(); String[] dayStrs = dayOfWeekStr.split("\\|"); @@ -245,7 +264,7 @@ days.add(DayOfWeek.SUNDAY); break; default: throw new IllegalArgumentException("无效的星期: " + dayStr); throw new IllegalArgumentException("鏃犳晥鐨勬槦鏈? " + dayStr); } } 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,132 +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){ try { JobKey jobKey = new JobKey("timingTask_" + taskId); scheduler.deleteJob(jobKey); 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()); @@ -153,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]); @@ -172,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]); @@ -180,24 +143,24 @@ 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); @@ -210,7 +173,6 @@ } } // 辅助方法:解析时间 private LocalTime parseTime(String timeStr) { try { return LocalTime.parse(timeStr); @@ -219,17 +181,14 @@ } } // 辅助方法:验证并分割字符串 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) { @@ -238,33 +197,30 @@ 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); } } } src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -225,6 +225,8 @@ return calculateWeeklyFirstExecution(task.getFrequencyDetail()); } else if ("MONTHLY".equals(frequencyType)) { return calculateMonthlyFirstExecution(task.getFrequencyDetail()); } else if ("YEARLY".equals(frequencyType)) { return calculateYearlyFirstExecution(task.getFrequencyDetail()); } else if ("QUARTERLY".equals(frequencyType)) { return calculateCustomFirstExecution(task.getFrequencyDetail()); } else { @@ -315,6 +317,28 @@ return targetDateTime; } private LocalDateTime calculateYearlyFirstExecution(String frequencyDetail) { String[] parts = frequencyDetail.split(","); if (parts.length != 3) { throw new IllegalArgumentException("参数格式错误,应为 03,15,17:00"); } int month = Integer.parseInt(parts[0].trim()); int dayOfMonth = Integer.parseInt(parts[1].trim()); LocalTime targetTime = LocalTime.parse(parts[2].trim(), DateTimeFormatter.ofPattern("HH:mm")); LocalDateTime now = LocalDateTime.now(); YearMonth currentYearMonth = YearMonth.of(now.getYear(), month); int adjustedDay = Math.min(dayOfMonth, currentYearMonth.lengthOfMonth()); LocalDateTime targetDateTime = LocalDateTime.of(now.getYear(), month, adjustedDay, targetTime.getHour(), targetTime.getMinute()); if (!targetDateTime.isAfter(now)) { YearMonth nextYearMonth = YearMonth.of(now.getYear() + 1, month); adjustedDay = Math.min(dayOfMonth, nextYearMonth.lengthOfMonth()); targetDateTime = LocalDateTime.of(now.getYear() + 1, month, adjustedDay, targetTime.getHour(), targetTime.getMinute()); } return targetDateTime; } private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) { return null; } @@ -350,6 +374,8 @@ return calculateMonthlyNextTime(frequencyDetail, currentTime); case "QUARTERLY": return calculateQuarterlyNextTime(frequencyDetail, currentTime); case "YEARLY": return calculateYearlyNextTime(frequencyDetail, currentTime); default: throw new IllegalArgumentException("不支持的频率类型: " + frequencyType); } @@ -415,6 +441,23 @@ ); } private LocalDateTime calculateYearlyNextTime(String detail, LocalDateTime current) { String[] parts = detail.split(","); int month = Integer.parseInt(parts[0]); int dayOfMonth = Integer.parseInt(parts[1]); LocalTime time = LocalTime.parse(parts[2]); YearMonth targetYearMonth = YearMonth.of(current.getYear(), month); int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth()); LocalDateTime target = LocalDateTime.of(current.getYear(), month, adjustedDay, time.getHour(), time.getMinute()); if (!target.isAfter(current)) { targetYearMonth = YearMonth.of(current.getYear() + 1, month); adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth()); target = LocalDateTime.of(current.getYear() + 1, month, adjustedDay, time.getHour(), time.getMinute()); } return target; } private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) { Set<DayOfWeek> days = new HashSet<>(); String[] dayStrs = dayOfWeekStr.split("\\|");