package com.ruoyi.safe.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.project.system.domain.SysUser; import com.ruoyi.project.system.mapper.SysUserMapper; import com.ruoyi.safe.mapper.SafeLineInspectionTaskMapper; import com.ruoyi.safe.pojo.SafeLineInspectionTask; import com.ruoyi.safe.service.SafeLineInspectionTaskService; import lombok.RequiredArgsConstructor; import org.quartz.SchedulerException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.DayOfWeek; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.YearMonth; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; /** *

* 安全生产--线路定时巡检任务 服务实现类 *

*/ @Service @RequiredArgsConstructor public class SafeLineInspectionTaskServiceImpl extends ServiceImpl implements SafeLineInspectionTaskService { private static final int ENABLED = 1; private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); 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 final SafeLineInspectionTaskMapper safeLineInspectionTaskMapper; private final SafeLineInspectionTaskScheduler safeLineInspectionTaskScheduler; private final SysUserMapper sysUserMapper; @Override public IPage pageSafeLineInspectionTask(Page page, SafeLineInspectionTask task) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(StringUtils.isNotBlank(task.getInspectionName()), SafeLineInspectionTask::getInspectionName, task.getInspectionName()) .like(StringUtils.isNotBlank(task.getLineName()), SafeLineInspectionTask::getLineName, task.getLineName()) .like(StringUtils.isNotBlank(task.getInspectionProject()), SafeLineInspectionTask::getInspectionProject, task.getInspectionProject()) .eq(task.getIsEnabled() != null, SafeLineInspectionTask::getIsEnabled, task.getIsEnabled()) .orderByDesc(SafeLineInspectionTask::getId); IPage result = safeLineInspectionTaskMapper.selectPage(page, queryWrapper); fillInspectorName(result.getRecords()); return result; } @Override @Transactional public boolean saveOrUpdateTask(SafeLineInspectionTask task) throws SchedulerException { SafeLineInspectionTask oldTask = null; if (task.getId() != null) { oldTask = safeLineInspectionTaskMapper.selectById(task.getId()); if (oldTask == null) { throw new IllegalArgumentException("定时巡检任务不存在"); } } normalizeTask(task, oldTask); task.setNextExecutionTime(calculateFirstExecutionTime(task.getFrequencyType(), task.getFrequencyDetail())); boolean result; if (task.getId() == null) { result = safeLineInspectionTaskMapper.insert(task) > 0; if (result && isEnabled(task.getIsEnabled())) { safeLineInspectionTaskScheduler.scheduleTask(task); } return result; } result = safeLineInspectionTaskMapper.updateById(task) > 0; if (result) { boolean oldEnabled = oldTask != null && isEnabled(oldTask.getIsEnabled()); boolean newEnabled = isEnabled(task.getIsEnabled()); if (!newEnabled) { safeLineInspectionTaskScheduler.unscheduleTask(task.getId()); } else if (oldEnabled) { safeLineInspectionTaskScheduler.rescheduleTask(task); } else { safeLineInspectionTaskScheduler.scheduleTask(task); } } return result; } @Override @Transactional public boolean removeTaskByIds(List ids) { boolean removed = removeBatchByIds(ids); if (removed) { for (Long id : ids) { safeLineInspectionTaskScheduler.unscheduleTask(id); } } return removed; } @Override public LocalDateTime calculateFirstExecutionTime(String frequencyType, String frequencyDetail) { LocalDateTime now = LocalDateTime.now(); return calculateNextExecutionTime(frequencyType, frequencyDetail, now); } @Override public LocalDateTime calculateNextExecutionTime(String frequencyType, String frequencyDetail, LocalDateTime currentTime) { validateFrequency(frequencyType, frequencyDetail); return switch (frequencyType) { case "DAILY" -> calculateDailyNextTime(frequencyDetail, currentTime); case "WEEKLY" -> calculateWeeklyNextTime(frequencyDetail, currentTime); case "MONTHLY" -> calculateMonthlyNextTime(frequencyDetail, currentTime); default -> throw new IllegalArgumentException("不支持的频率类型: " + frequencyType); }; } private void normalizeTask(SafeLineInspectionTask task, SafeLineInspectionTask oldTask) { if (StringUtils.isBlank(task.getInspectionName())) { throw new IllegalArgumentException("巡检任务名称不能为空"); } if (StringUtils.isBlank(task.getLineName())) { throw new IllegalArgumentException("线路名称不能为空"); } if (StringUtils.isBlank(task.getInspectionProject())) { throw new IllegalArgumentException("巡检项目不能为空"); } if (StringUtils.isBlank(task.getInspectorId())) { throw new IllegalArgumentException("巡检人不能为空"); } task.setIsEnabled(resolveEnabledValue(task.getIsEnabled(), oldTask)); task.setFrequencyType(task.getFrequencyType() == null ? null : task.getFrequencyType().trim().toUpperCase()); task.setFrequencyDetail(task.getFrequencyDetail() == null ? null : task.getFrequencyDetail().trim()); validateFrequency(task.getFrequencyType(), task.getFrequencyDetail()); } private void validateFrequency(String frequencyType, String frequencyDetail) { if (StringUtils.isBlank(frequencyType) || StringUtils.isBlank(frequencyDetail)) { throw new IllegalArgumentException("任务频率不能为空"); } switch (frequencyType) { case "DAILY" -> parseTime(frequencyDetail); case "WEEKLY" -> { String[] parts = splitFrequencyDetail(frequencyDetail); if (!WEEK_DAY_MAP.containsKey(parts[0])) { throw new IllegalArgumentException("无效的星期: " + parts[0]); } parseTime(parts[1]); } case "MONTHLY" -> { String[] parts = splitFrequencyDetail(frequencyDetail); validateDayOfMonth(parts[0]); parseTime(parts[1]); } default -> throw new IllegalArgumentException("不支持的频率类型: " + frequencyType); } } private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime currentTime) { LocalTime executionTime = parseTime(timeStr); LocalDateTime candidate = LocalDateTime.of(currentTime.toLocalDate(), executionTime); return currentTime.isBefore(candidate) ? candidate : candidate.plusDays(1); } private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime currentTime) { String[] parts = splitFrequencyDetail(detail); DayOfWeek targetDay = WEEK_DAY_MAP.get(parts[0]); LocalTime targetTime = parseTime(parts[1]); int daysUntil = targetDay.getValue() - currentTime.getDayOfWeek().getValue(); if (daysUntil < 0 || (daysUntil == 0 && !currentTime.toLocalTime().isBefore(targetTime))) { daysUntil += 7; } return LocalDateTime.of(currentTime.toLocalDate().plusDays(daysUntil), targetTime); } private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime currentTime) { String[] parts = splitFrequencyDetail(detail); int dayOfMonth = validateDayOfMonth(parts[0]); LocalTime targetTime = parseTime(parts[1]); YearMonth currentMonth = YearMonth.from(currentTime); for (int i = 0; i < 24; i++) { YearMonth targetMonth = currentMonth.plusMonths(i); if (dayOfMonth > targetMonth.lengthOfMonth()) { continue; } LocalDateTime candidate = LocalDateTime.of( targetMonth.getYear(), targetMonth.getMonthValue(), dayOfMonth, targetTime.getHour(), targetTime.getMinute() ); if (currentTime.isBefore(candidate)) { return candidate; } } throw new IllegalArgumentException("无法计算下次执行时间"); } private String[] splitFrequencyDetail(String detail) { String[] parts = detail.split(","); if (parts.length != 2) { throw new IllegalArgumentException("频率详情格式错误"); } return Arrays.stream(parts).map(String::trim).toArray(String[]::new); } private LocalTime parseTime(String timeStr) { try { return LocalTime.parse(timeStr, TIME_FORMATTER); } catch (DateTimeParseException e) { throw new IllegalArgumentException("时间格式必须为HH:mm", e); } } private int validateDayOfMonth(String dayStr) { try { int day = Integer.parseInt(dayStr); if (day < 1 || day > 31) { throw new IllegalArgumentException("日期必须在1-31之间"); } return day; } catch (NumberFormatException e) { throw new IllegalArgumentException("日期必须在1-31之间", e); } } private Integer resolveEnabledValue(Integer requestEnabled, SafeLineInspectionTask oldTask) { if (requestEnabled != null) { return requestEnabled; } if (oldTask != null && oldTask.getIsEnabled() != null) { return oldTask.getIsEnabled(); } return ENABLED; } private boolean isEnabled(Integer enabledValue) { return Objects.equals(enabledValue, ENABLED); } private void fillInspectorName(List records) { if (records == null || records.isEmpty()) { return; } Set userIds = records.stream() .map(SafeLineInspectionTask::getInspectorId) .filter(StringUtils::isNotBlank) .flatMap(ids -> Arrays.stream(ids.split(","))) .map(String::trim) .filter(StringUtils::isNotBlank) .map(this::parseUserId) .filter(Objects::nonNull) .collect(Collectors.toSet()); if (userIds.isEmpty()) { return; } Map userNameMap = new HashMap<>(); List users = sysUserMapper.selectUserByIds(new ArrayList<>(userIds)); users.forEach(user -> userNameMap.put(user.getUserId(), user.getNickName())); for (SafeLineInspectionTask record : records) { if (StringUtils.isBlank(record.getInspectorId())) { continue; } List names = Arrays.stream(record.getInspectorId().split(",")) .map(String::trim) .filter(StringUtils::isNotBlank) .map(this::parseUserId) .filter(Objects::nonNull) .map(id -> userNameMap.getOrDefault(id, "未知用户")) .collect(Collectors.toList()); record.setInspector(names); record.setInspectorName(String.join(",", names)); } } private Long parseUserId(String idStr) { try { return Long.parseLong(idStr); } catch (NumberFormatException e) { return null; } } }