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; /** *

* 定时巡检任务表 服务实现类 *

* * @author ld * @since 2025-06-30 */ @Service @Slf4j @RequiredArgsConstructor public class TimingTaskServiceImpl extends ServiceImpl implements TimingTaskService { private final TimingTaskMapper timingTaskMapper; private final InspectionTaskMapper inspectionTaskMapper; private final TimingTaskScheduler timingTaskScheduler; private final SysUserMapper sysUserMapper; @Override public IPage selectTimingTaskList(Page page, TimingTask timingTask) { // 1. 先分页查询定时任务数据 IPage taskPage = timingTaskMapper.selectPage(page, null); // 2. 如果没有数据,直接返回空分页 if (taskPage.getRecords().isEmpty()) { return new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal()); } // 3. 收集所有需要查询的用户ID Set 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 userNickNameMap = new HashMap<>(); if (!userIds.isEmpty()) { List users = sysUserMapper.selectBatchIds(userIds); users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName())); } // 5. 转换为DTO List 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 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 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 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 parseDayOfWeeks(String dayOfWeekStr) { Set 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 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)); } }