package com.ruoyi.inspectiontask.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.device.mapper.DeviceAreaMapper; import com.ruoyi.device.mapper.DeviceLedgerMapper; import com.ruoyi.device.pojo.DeviceArea; import com.ruoyi.device.pojo.DeviceLedger; import com.ruoyi.inspectiontask.dto.TimingTaskDto; import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper; import com.ruoyi.inspectiontask.mapper.TimingTaskMapper; import com.ruoyi.inspectiontask.pojo.TimingTask; import com.ruoyi.inspectiontask.service.TimingTaskService; import com.ruoyi.project.system.domain.SysUser; import com.ruoyi.project.system.mapper.SysUserMapper; import lombok.extern.slf4j.Slf4j; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.DayOfWeek; import java.time.DateTimeException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Year; import java.time.YearMonth; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; /** * @author :yys * @date : 2025/9/19 10:55 */ @Service @Slf4j public class TimingTaskServiceImpl extends ServiceImpl implements TimingTaskService { @Autowired private TimingTaskMapper timingTaskMapper; @Autowired private InspectionTaskMapper inspectionTaskMapper; @Autowired private TimingTaskScheduler timingTaskScheduler; @Autowired private SysUserMapper sysUserMapper; @Autowired private DeviceLedgerMapper deviceLedgerMapper; @Autowired private DeviceAreaMapper deviceAreaMapper; @Override public IPage selectTimingTaskList(Page page, TimingTask timingTask) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(timingTask.getTaskName())) { queryWrapper.like(TimingTask::getTaskName, timingTask.getTaskName()); } if (timingTask.getAreaId() != null) { queryWrapper.eq(TimingTask::getAreaId, timingTask.getAreaId()); } IPage taskPage = timingTaskMapper.selectPage(page, queryWrapper); if (taskPage.getRecords().isEmpty()) { return new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal()); } Set userIds = new HashSet<>(); taskPage.getRecords().forEach(task -> { if (task.getRegistrantId() != null) { userIds.add(task.getRegistrantId()); } if (task.getCreateTime() != null) { task.setDateStr(task.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); } if (StringUtils.isNotBlank(task.getInspectorIds())) { Arrays.stream(task.getInspectorIds().split(",")) .filter(StringUtils::isNotBlank) .map(Long::valueOf) .forEach(userIds::add); } }); Map userNickNameMap = new HashMap<>(); if (!userIds.isEmpty()) { List users = sysUserMapper.selectUserByIds(new ArrayList<>(userIds)); users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName())); } Map areaNameMap = deviceAreaMapper.selectBatchIds(taskPage.getRecords().stream() .map(TimingTask::getAreaId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList())) .stream() .collect(Collectors.toMap(DeviceArea::getId, DeviceArea::getAreaName, (left, right) -> left, HashMap::new)); 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(), "未知用户")); } dto.setAreaName(areaNameMap.get(task.getAreaId())); if (StringUtils.isNotBlank(task.getInspectorIds())) { List inspectorNickNames = new ArrayList<>(); for (String idStr : task.getInspectorIds().split(",")) { if (StringUtils.isNotBlank(idStr)) { Long id = Long.valueOf(idStr); inspectorNickNames.add(userNickNameMap.getOrDefault(id, "未知用户")); } } dto.setInspector(inspectorNickNames); } return dto; }).collect(Collectors.toList()); 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); prepareTimingTask(timingTask); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); LocalDate localDate = LocalDate.now(); if (StringUtils.isNotEmpty(timingTaskDto.getDateStr())) { localDate = LocalDate.parse(timingTaskDto.getDateStr(), formatter); } LocalTime currentTime = LocalTime.now(); timingTask.setCreateTime(LocalDateTime.of(localDate, currentTime)); if (Objects.isNull(timingTaskDto.getId())) { timingTask.setRegistrationDate(LocalDate.now()); timingTask.setActive(true); timingTask.setNextExecutionTime(calculateFirstExecutionTime(timingTask)); int result = timingTaskMapper.insert(timingTask); if (result > 0) { timingTaskScheduler.scheduleTimingTask(timingTask); } return result; } int result = timingTaskMapper.updateById(timingTask); if (result > 0) { timingTaskScheduler.rescheduleTimingTask(timingTask); } return result; } private void prepareTimingTask(TimingTask timingTask) { Long[] selectedTaskIds = timingTask.getTaskIds(); if ((selectedTaskIds == null || selectedTaskIds.length == 0) && timingTask.getTaskId() != null) { selectedTaskIds = new Long[]{timingTask.getTaskId().longValue()}; } if (selectedTaskIds == null || selectedTaskIds.length == 0) { timingTask.setTaskIdsStr(null); return; } List deviceIds = Arrays.stream(selectedTaskIds) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); if (deviceIds.isEmpty()) { timingTask.setTaskIdsStr(null); return; } List devices = deviceLedgerMapper.selectBatchIds(deviceIds); Map deviceMap = devices.stream() .collect(Collectors.toMap(DeviceLedger::getId, device -> device, (left, right) -> left, LinkedHashMap::new)); List validDeviceIds = deviceIds.stream() .filter(deviceMap::containsKey) .collect(Collectors.toList()); if (validDeviceIds.isEmpty()) { throw new IllegalArgumentException("所选设备不存在"); } timingTask.setTaskIds(validDeviceIds.toArray(new Long[0])); timingTask.setTaskIdsStr(validDeviceIds.stream().map(String::valueOf).collect(Collectors.joining(","))); timingTask.setTaskId(validDeviceIds.get(0).intValue()); if (timingTask.getAreaId() == null) { DeviceLedger firstDevice = deviceMap.get(validDeviceIds.get(0)); if (firstDevice != null) { timingTask.setAreaId(firstDevice.getAreaId()); } } timingTask.setTaskName(validDeviceIds.stream() .map(deviceMap::get) .filter(Objects::nonNull) .map(DeviceLedger::getDeviceName) .filter(StringUtils::isNotBlank) .collect(Collectors.joining(","))); } public LocalDateTime calculateFirstExecutionTime(TimingTask task) { String frequencyType = task.getFrequencyType(); if ("DAILY".equals(frequencyType)) { return calculateDailyFirstExecution(task.getFrequencyDetail()); } else if ("WEEKLY".equals(frequencyType)) { 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 { throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType()); } } private LocalDateTime calculateDailyFirstExecution(String frequencyDetail) { 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 static final Map WEEK_DAY_MAP = new HashMap<>(); static { WEEK_DAY_MAP.put("MON", DayOfWeek.MONDAY); WEEK_DAY_MAP.put("TUE", DayOfWeek.TUESDAY); WEEK_DAY_MAP.put("WED", DayOfWeek.WEDNESDAY); WEEK_DAY_MAP.put("THU", DayOfWeek.THURSDAY); WEEK_DAY_MAP.put("FRI", DayOfWeek.FRIDAY); WEEK_DAY_MAP.put("SAT", DayOfWeek.SATURDAY); WEEK_DAY_MAP.put("SUN", DayOfWeek.SUNDAY); } private LocalDateTime calculateWeeklyFirstExecution(String frequencyDetail) { String[] parts = frequencyDetail.split(","); if (parts.length != 2) { throw new IllegalArgumentException("参数格式错误,应为 MON,13:43"); } String weekDayStr = parts[0].trim(); String timeStr = parts[1].trim(); DayOfWeek targetDay = WEEK_DAY_MAP.get(weekDayStr); if (targetDay == null) { throw new IllegalArgumentException("无效的星期: " + weekDayStr); } LocalTime targetTime = LocalTime.parse(timeStr, DateTimeFormatter.ofPattern("HH:mm")); LocalDateTime now = LocalDateTime.now(); LocalDateTime targetDateTime = now.with(targetDay).with(targetTime); if (targetDateTime.isBefore(now)) { targetDateTime = targetDateTime.plusWeeks(1); } return targetDateTime; } private LocalDateTime calculateMonthlyFirstExecution(String frequencyDetail) { String[] parts = frequencyDetail.split(","); if (parts.length != 2) { throw new IllegalArgumentException("参数格式错误,应为 03,17:00"); } String dayStr = parts[0].trim(); String timeStr = parts[1].trim(); int dayOfMonth; try { dayOfMonth = Integer.parseInt(dayStr); } catch (NumberFormatException e) { throw new IllegalArgumentException("无效的日期格式: " + dayStr, e); } if (dayOfMonth < 1 || dayOfMonth > 31) { throw new IllegalArgumentException("日期必须在 1-31 之间: " + dayOfMonth); } LocalTime targetTime; try { targetTime = LocalTime.parse(timeStr, DateTimeFormatter.ofPattern("HH:mm")); } catch (DateTimeException e) { throw new IllegalArgumentException("无效的时间格式: " + timeStr, e); } LocalDateTime now = LocalDateTime.now(); LocalDateTime targetDateTime = now.withDayOfMonth(dayOfMonth).with(targetTime); boolean isDateAdjusted = targetDateTime.getDayOfMonth() != dayOfMonth; if (targetDateTime.isBefore(now) || isDateAdjusted) { LocalDateTime nextMonth = now.plusMonths(1); LocalDateTime nextMonthTarget = nextMonth.withDayOfMonth(dayOfMonth).with(targetTime); if (nextMonthTarget.getDayOfMonth() != dayOfMonth) { int lastDayOfMonth = nextMonth.getMonth().length(Year.of(nextMonth.getYear()).isLeap()); nextMonthTarget = nextMonth.withDayOfMonth(lastDayOfMonth).with(targetTime); } targetDateTime = nextMonthTarget; } 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; } @Override @Transactional public void updateTaskExecutionTime(Long taskId) { TimingTask task = timingTaskMapper.selectById(taskId); if (task == null) { throw new RuntimeException("定时任务不存在,ID: " + taskId); } task.setLastExecutionTime(LocalDateTime.now()); LocalDateTime nextExecutionTime = calculateNextExecutionTime( task.getFrequencyType(), task.getFrequencyDetail(), LocalDateTime.now() ); task.setNextExecutionTime(nextExecutionTime); timingTaskMapper.updateById(task); } private LocalDateTime calculateNextExecutionTime(String frequencyType, String frequencyDetail, LocalDateTime currentTime) { try { switch (frequencyType) { case "DAILY": return calculateDailyNextTime(frequencyDetail, currentTime); case "WEEKLY": return calculateWeeklyNextTime(frequencyDetail, currentTime); case "MONTHLY": return calculateMonthlyNextTime(frequencyDetail, currentTime); case "QUARTERLY": return calculateQuarterlyNextTime(frequencyDetail, currentTime); case "YEARLY": return calculateYearlyNextTime(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); 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]; 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]); return current.plusMonths(1) .withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth())) .with(time); } private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) { String[] parts = detail.split(","); int quarterMonth = Integer.parseInt(parts[0]); int dayOfMonth = Integer.parseInt(parts[1]); LocalTime time = LocalTime.parse(parts[2]); 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 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 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; } @Override public int delByIds(Long[] ids) { int i = timingTaskMapper.deleteBatchIds(Arrays.asList(ids)); if (i > 0) { for (Long id : ids) { timingTaskScheduler.unscheduleTimingTask(id); } } return i; } }