package com.ruoyi.inspectiontask.service.impl; 
 | 
  
 | 
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.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.*; 
 | 
import java.time.format.DateTimeFormatter; 
 | 
import java.util.*; 
 | 
import java.util.stream.Collectors; 
 | 
  
 | 
/** 
 | 
 * @author :yys 
 | 
 * @date : 2025/9/19 10:55 
 | 
 */ 
 | 
@Service 
 | 
@Slf4j 
 | 
public class TimingTaskServiceImpl extends ServiceImpl<TimingTaskMapper, TimingTask> implements TimingTaskService { 
 | 
  
 | 
    @Autowired 
 | 
    private TimingTaskMapper timingTaskMapper; 
 | 
  
 | 
    @Autowired 
 | 
    private InspectionTaskMapper inspectionTaskMapper; 
 | 
  
 | 
    @Autowired 
 | 
    private TimingTaskScheduler timingTaskScheduler; 
 | 
  
 | 
    @Autowired 
 | 
    private 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.selectUserByIds((new ArrayList<>(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 = 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()); 
 | 
  
 | 
        // 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.setNextExecutionTime(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) { 
 | 
        // 根据频率类型和详情计算首次执行时间 
 | 
        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 ("QUARTERLY".equals(frequencyType)) { 
 | 
            // 自定义频率,如每小时、每30分钟等 
 | 
            return calculateCustomFirstExecution(task.getFrequencyDetail()); 
 | 
        } else { 
 | 
            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); 
 | 
    } 
 | 
  
 | 
    // 映射星期简写与DayOfWeek 
 | 
    private static final Map<String, DayOfWeek> 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); 
 | 
        } 
 | 
  
 | 
        // 验证日期有效性(1-31之间) 
 | 
        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); 
 | 
  
 | 
        // 检查日期是否被自动调整(如31日在小月会被调整) 
 | 
        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) { 
 | 
                // 正确获取下个月的最后一天(修复isLeapYear调用问题) 
 | 
                int lastDayOfMonth = nextMonth.getMonth().length( 
 | 
                        Year.of(nextMonth.getYear()).isLeap() 
 | 
                ); 
 | 
                nextMonthTarget = nextMonth.withDayOfMonth(lastDayOfMonth).with(targetTime); 
 | 
            } 
 | 
  
 | 
            targetDateTime = nextMonthTarget; 
 | 
        } 
 | 
  
 | 
        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); 
 | 
                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; 
 | 
    } 
 | 
  
 | 
  
 | 
  
 | 
    @Override 
 | 
    public int delByIds(Long[] ids) { 
 | 
        return timingTaskMapper.deleteBatchIds(Arrays.asList(ids)); 
 | 
    } 
 | 
  
 | 
} 
 |