3 天以前 c477506a6d672f71c6353608f3cf3424d8026790
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package com.ruoyi.device.service.impl;
 
import com.ruoyi.device.pojo.MaintenanceTask;
import lombok.extern.slf4j.Slf4j;
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;
 
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
@Slf4j
public class MaintenanceTaskScheduler {
 
    @Autowired
    private Scheduler scheduler;
 
    public void scheduleMaintenanceTask(MaintenanceTask task) {
        try {
            JobDetail jobDetail = buildJobDetail(task);
            Trigger trigger = buildJobTrigger(task, jobDetail);
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            log.error("SchedulerException scheduleMaintenanceTask ERROR", e);
            throw new RuntimeException(e);
        }
    }
 
    public void rescheduleMaintenanceTask(MaintenanceTask task) {
        try {
            TriggerKey triggerKey = new TriggerKey("triggerMaintenanceTask_" + task.getId());
            Trigger oldTrigger = scheduler.getTrigger(triggerKey);
            if (!(oldTrigger instanceof CronTrigger)) {
                throw new SchedulerException("Existing trigger is not a CronTrigger");
            }
 
            CronTrigger newTrigger = TriggerBuilder.newTrigger()
                    .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();
            scheduler.rescheduleJob(triggerKey, newTrigger);
        } catch (SchedulerException e) {
            log.error("SchedulerException rescheduleMaintenanceTask ERROR", e);
            throw new RuntimeException(e);
        }
    }
 
    public void pauseMaintenanceTask(Long taskId) throws SchedulerException {
        scheduler.pauseJob(new JobKey("MaintenanceTask_" + taskId));
    }
 
    public void resumeMaintenanceTask(Long taskId) throws SchedulerException {
        scheduler.resumeJob(new JobKey("MaintenanceTask_" + taskId));
    }
 
    public void unscheduleMaintenanceTask(Long taskId) {
        try {
            scheduler.deleteJob(new JobKey("MaintenanceTask_" + taskId));
        } catch (SchedulerException e) {
            log.error("SchedulerException unscheduleMaintenanceTask ERROR", e);
            throw new RuntimeException(e);
        }
    }
 
    private JobDetail buildJobDetail(MaintenanceTask task) {
        JobKey jobKey = new JobKey("MaintenanceTask_" + task.getId());
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("maintenanceTaskId", task.getId());
        jobDataMap.put("taskName", task.getTaskName());
        jobDataMap.put("taskType", task.getFrequencyType());
 
        return JobBuilder.newJob(MaintenanceTaskJob.class)
                .withIdentity(jobKey)
                .withDescription(task.getTaskName())
                .usingJobData(jobDataMap)
                .storeDurably(true)
                .requestRecovery(true)
                .build();
    }
 
    private Trigger buildJobTrigger(MaintenanceTask task, JobDetail jobDetail) {
        TriggerKey triggerKey = new TriggerKey("triggerMaintenanceTask_" + task.getId());
        String cronExpression = convertToCronExpression(task);
        return TriggerBuilder.newTrigger()
                .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("任务参数不能为空");
        }
 
        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());
            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]);
        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]);
        int day = validateDayOfMonth(parts[1]);
        LocalTime time = parseTime(parts[2]);
 
        int quarterStartMonth = ((month - 1) / 3) * 3 + 1;
        return String.format("0 %d %d %d %d/3 ?", time.getMinute(), time.getHour(), day, quarterStartMonth);
    }
 
    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);
            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 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);
        }
    }
}