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<CronTrigger> 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);
|
}
|
}
|
}
|