liding
3 天以前 49e1bc66ebaf696ebd3fc3ed33d65c8795fd3cde
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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
package com.ruoyi.business.service.impl;
 
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.business.dto.TimingTaskDto;
import com.ruoyi.business.entity.TimingTask;
import com.ruoyi.business.mapper.InspectionTaskMapper;
import com.ruoyi.business.mapper.TimingTaskMapper;
import com.ruoyi.business.service.TimingTaskService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.system.mapper.SysUserMapper;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
 
import java.time.*;
import java.util.*;
import java.util.stream.Collectors;
 
/**
 * <p>
 * 定时巡检任务表 服务实现类
 * </p>
 *
 * @author ld
 * @since 2025-06-30
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class TimingTaskServiceImpl extends ServiceImpl<TimingTaskMapper, TimingTask> implements TimingTaskService {
 
    private final TimingTaskMapper timingTaskMapper;
 
    private final InspectionTaskMapper inspectionTaskMapper;
 
    private  final TimingTaskScheduler timingTaskScheduler;
 
    private  final SysUserMapper sysUserMapper;
 
 
    @Override
    public IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask) {
        // 1. 先分页查询定时任务数据
        IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, null);
 
        // 2. 如果没有数据,直接返回空分页
        if (taskPage.getRecords().isEmpty()) {
            return new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
        }
 
        // 3. 收集所有需要查询的用户ID
        Set<Long> userIds = new HashSet<>();
 
        // 收集登记人ID
        taskPage.getRecords().forEach(task -> {
            if (task.getRegistrantId() != null) {
                userIds.add(task.getRegistrantId());
            }
        });
 
        // 收集巡检人ID(多个ID以逗号分隔)
        taskPage.getRecords().forEach(task -> {
            if (StringUtils.isNotBlank(task.getInspectorIds())) {
                Arrays.stream(task.getInspectorIds().split(","))
                        .filter(StringUtils::isNotBlank)
                        .map(Long::valueOf)
                        .forEach(userIds::add);
            }
        });
 
        // 4. 批量查询用户信息
        Map<Long, String> userNickNameMap = new HashMap<>();
        if (!userIds.isEmpty()) {
            List<SysUser> users = sysUserMapper.selectBatchIds(userIds);
            users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName()));
        }
 
        // 5. 转换为DTO
        List<TimingTaskDto> dtoList = taskPage.getRecords().stream().map(task -> {
            TimingTaskDto dto = new TimingTaskDto();
            // 复制基本属性
            BeanUtils.copyProperties(task, dto);
 
            // 设置登记人昵称
            if (task.getRegistrantId() != null) {
                dto.setRegistrant(userNickNameMap.getOrDefault(task.getRegistrantId(), "未知用户"));
            }
 
            // 设置巡检人昵称列表
            if (StringUtils.isNotBlank(task.getInspectorIds())) {
                List<String> inspectorNickNames = Arrays.stream(task.getInspectorIds().split(","))
                        .filter(StringUtils::isNotBlank)
                        .map(idStr -> {
                            Long id = Long.valueOf(idStr);
                            return userNickNameMap.getOrDefault(id, "未知用户");
                        })
                        .toList();
                dto.setInspector(inspectorNickNames.toString());
            }
 
            return dto;
        }).collect(Collectors.toList());
 
        // 6. 构建返回的分页对象
        Page<TimingTaskDto> resultPage = new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
        resultPage.setRecords(dtoList);
        return resultPage;
    }
 
    @Override
    @Transactional
    public int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException {
        TimingTask timingTask = new TimingTask();
        BeanUtils.copyProperties(timingTaskDto, timingTask);
 
        // 设置创建人信息和默认值
        if (Objects.isNull(timingTaskDto.getId())) {
            timingTask.setRegistrationDate(LocalDate.now());
            timingTask.setActive(true);
 
            // 计算首次执行时间
            LocalDateTime firstExecutionTime = calculateFirstExecutionTime(timingTask);
            timingTask.setNexExecutionTime(firstExecutionTime);
 
            int result = timingTaskMapper.insert(timingTask);
            if (result > 0) {
                // 新增成功后添加到调度器
                timingTaskScheduler.scheduleTimingTask(timingTask);
            }
            return result;
        } else {
            int result = timingTaskMapper.updateById(timingTask);
            if (result > 0) {
                // 更新成功后重新调度任务
                timingTaskScheduler.rescheduleTimingTask(timingTask);
            }
            return result;
        }
    }
 
    private LocalDateTime calculateFirstExecutionTime(TimingTask task) {
        // 根据频率类型和详情计算首次执行时间
        return switch (task.getFrequencyType()) {
            case "DAILY" ->
                // 如果是每天执行,计算今天或明天的具体时间
                    calculateDailyFirstExecution(task.getFrequencyDetail());
            case "WEEKLY" ->
                // 如果是每周执行,计算下周的具体星期几
                    calculateWeeklyFirstExecution(task.getFrequencyDetail());
            case "MONTHLY" ->
                // 如果是每月执行,计算下个月的具体日期
                    calculateMonthlyFirstExecution(task.getFrequencyDetail());
            case "QUARTERLY" ->
                // 自定义频率,如每小时、每30分钟等
                    calculateCustomFirstExecution(task.getFrequencyDetail());
            default -> throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType());
        };
    }
 
    private LocalDateTime calculateDailyFirstExecution(String frequencyDetail) {
        // frequencyDetail可能是具体时间,如 "14:30"
        LocalTime executionTime = LocalTime.parse(frequencyDetail);
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime todayExecution = LocalDateTime.of(now.toLocalDate(), executionTime);
 
        // 如果今天的时间已过,则安排明天执行
        return now.isBefore(todayExecution) ? todayExecution : todayExecution.plusDays(1);
    }
 
    private LocalDateTime calculateWeeklyFirstExecution(String frequencyDetail) {
        return null;
    }
 
    private LocalDateTime calculateMonthlyFirstExecution(String frequencyDetail) {
        return null;
    }
 
    private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) {
        return null;
    }
 
    @Override
    @Transactional
    public void updateTaskExecutionTime(Long taskId) {
        TimingTask task = timingTaskMapper.selectById(taskId);
        if (task == null) {
            throw new RuntimeException("定时任务不存在,ID: " + taskId);
        }
 
        // 更新最后执行时间为当前时间
        task.setLastExecuteTime(LocalDateTime.now());
 
        // 计算下次执行时间
        LocalDateTime nextExecutionTime = calculateNextExecutionTime(
                task.getFrequencyType(),
                task.getFrequencyDetail(),
                LocalDateTime.now()
        );
        task.setNexExecutionTime(nextExecutionTime);
 
        // 更新数据库
        timingTaskMapper.updateById(task);
    }
 
    /**
     * 计算下次执行时间
     */
    private LocalDateTime calculateNextExecutionTime(String frequencyType,
                                                     String frequencyDetail,
                                                     LocalDateTime currentTime) {
        try {
            return switch (frequencyType) {
                case "DAILY" -> calculateDailyNextTime(frequencyDetail, currentTime);
                case "WEEKLY" -> calculateWeeklyNextTime(frequencyDetail, currentTime);
                case "MONTHLY" -> calculateMonthlyNextTime(frequencyDetail, currentTime);
                case "QUARTERLY" -> calculateQuarterlyNextTime(frequencyDetail, currentTime);
                default -> throw new IllegalArgumentException("不支持的频率类型: " + frequencyType);
            };
        } catch (Exception e) {
            throw new RuntimeException("计算下次执行时间失败: " + e.getMessage(), e);
        }
    }
 
    /**
     * 计算每日任务的下次执行时间
     */
    private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {
        LocalTime executionTime = LocalTime.parse(timeStr); // 解析格式 "HH:mm"
        LocalDateTime nextTime = LocalDateTime.of(current.toLocalDate(), executionTime);
 
        // 如果今天的时间已过,则安排明天
        return current.isBefore(nextTime) ? nextTime : nextTime.plusDays(1);
    }
 
    /**
     * 计算每周任务的下次执行时间
     */
    private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        String dayOfWeekStr = parts[0];  // 如 "MON" 或 "MON|WED|FRI"
        LocalTime time = LocalTime.parse(parts[1]); // 时间部分
 
        // 解析星期几(支持多个星期)
        Set<DayOfWeek> targetDays = parseDayOfWeeks(dayOfWeekStr);
 
        // 从当前时间开始找下一个符合条件的星期几
        LocalDateTime nextTime = current;
        while (true) {
            nextTime = nextTime.plusDays(1);
            if (targetDays.contains(nextTime.getDayOfWeek())) {
                return LocalDateTime.of(nextTime.toLocalDate(), time);
            }
 
            // 防止无限循环(理论上不会发生)
            if (nextTime.isAfter(current.plusYears(1))) {
                throw new RuntimeException("无法找到下次执行时间");
            }
        }
    }
 
    /**
     * 计算每月任务的下次执行时间
     */
    private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int dayOfMonth = Integer.parseInt(parts[0]);
        LocalTime time = LocalTime.parse(parts[1]);
 
        // 从下个月开始计算
        LocalDateTime nextTime = current.plusMonths(1)
                .withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth()))
                .with(time);
 
        return nextTime;
    }
 
    /**
     * 计算每季度任务的下次执行时间
     */
    private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int quarterMonth = Integer.parseInt(parts[0]); // 1=第1个月,2=第2个月,3=第3个月
        int dayOfMonth = Integer.parseInt(parts[1]);
        LocalTime time = LocalTime.parse(parts[2]);
 
        // 计算当前季度
        int currentQuarter = (current.getMonthValue() - 1) / 3 + 1;
        int currentMonthInQuarter = (current.getMonthValue() - 1) % 3 + 1;
 
        YearMonth targetYearMonth;
        if (currentMonthInQuarter < quarterMonth) {
            // 本季度内还有执行机会
            targetYearMonth = YearMonth.from(current)
                    .plusMonths(quarterMonth - currentMonthInQuarter);
        } else {
            // 需要到下个季度
            targetYearMonth = YearMonth.from(current)
                    .plusMonths(3 - currentMonthInQuarter + quarterMonth);
        }
 
        // 处理月末日期
        int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
 
        return LocalDateTime.of(
                targetYearMonth.getYear(),
                targetYearMonth.getMonthValue(),
                adjustedDay,
                time.getHour(),
                time.getMinute()
        );
    }
 
    /**
     * 解析星期几字符串
     */
    private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) {
        Set<DayOfWeek> days = new HashSet<>();
        String[] dayStrs = dayOfWeekStr.split("\\|");
 
        for (String dayStr : dayStrs) {
            switch (dayStr) {
                case "MON": days.add(DayOfWeek.MONDAY); break;
                case "TUE": days.add(DayOfWeek.TUESDAY); break;
                case "WED": days.add(DayOfWeek.WEDNESDAY); break;
                case "THU": days.add(DayOfWeek.THURSDAY); break;
                case "FRI": days.add(DayOfWeek.FRIDAY); break;
                case "SAT": days.add(DayOfWeek.SATURDAY); break;
                case "SUN": days.add(DayOfWeek.SUNDAY); break;
                default: throw new IllegalArgumentException("无效的星期几: " + dayStr);
            }
        }
 
        return days;
    }
 
//    /**
//     * 每天17:40准时触发,执行到期任务
//     */
//    @Scheduled(cron = "0 55 17 * * ?", zone = "Asia/Shanghai")
//    @Transactional
//    public void executeDueTasks() {
//        LocalDateTime now = LocalDateTime.now();
//        log.info("定时任务触发,当前时间: {}", now);
//
//        // 1. 查询所有需要立即执行的任务
//        List<TimingTask> dueTasks = timingTaskMapper.selectActiveTasks(now);
//
//        // 2. 处理每个任务
//        dueTasks.forEach(task -> {
//            // 2.1 转换为巡检任务并保存
//            InspectionTask inspectionTask = convertToInspectionTask(task);
//            inspectionTaskMapper.insert(inspectionTask);
//
//            // 2.2 计算并更新下次执行时间
//            updateNextExecutionTime(task, now);
//
//            log.info("任务[{}]已执行,下次执行时间: {}",
//                    task.getTaskName(),
//                    task.getNexExecutionTime());
//        });
//    }
//
//    private InspectionTask  convertToInspectionTask(TimingTask timingTask) {
//        InspectionTask inspectionTask = new InspectionTask();
//
//        // 复制基本属性
//        inspectionTask.setTaskName(timingTask.getTaskName());
//        inspectionTask.setInspectorId(timingTask.getInspectorIds());
//        inspectionTask.setPort(timingTask.getInspectionLocation());
//        inspectionTask.setRemarks("自动生成自定时任务ID: " + timingTask.getId());
//        inspectionTask.setRegistrantId(timingTask.getRegistrantId());
//        inspectionTask.setFrequency(timingTask.getFrequencyType());
//        return inspectionTask;
//    }
//
//    private void updateNextExecutionTime(TimingTask task, LocalDateTime currentExecutionTime) {
//        switch (task.getFrequencyType()) {
//            case "DAILY":
//                task.setNexExecutionTime(currentExecutionTime.plusDays(1));
//                break;
//            case "WEEKLY":
//                task.setNexExecutionTime(currentExecutionTime.plusWeeks(1));
//                break;
//            case "MONTHLY":
//                task.setNexExecutionTime(currentExecutionTime.plusMonths(1));
//                break;
//            default:
//                task.setNexExecutionTime(null); // 单次任务
//        }
//        timingTaskMapper.updateById(task);
//    }
 
 
 
    @Override
    public int delByIds(Long[] ids) {
        return timingTaskMapper.deleteByIds(Arrays.asList(ids));
    }
}