| | |
| | | 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 jakarta.annotation.PostConstruct; |
| | | import lombok.RequiredArgsConstructor; |
| | | 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; |
| | | |
| | |
| | | */ |
| | | @Service |
| | | @Slf4j |
| | | @RequiredArgsConstructor |
| | | public class TimingTaskServiceImpl extends ServiceImpl<TimingTaskMapper, TimingTask> implements TimingTaskService { |
| | | |
| | | @Autowired |
| | | private TimingTaskMapper timingTaskMapper; |
| | | private final TimingTaskMapper timingTaskMapper; |
| | | private final TimingTaskScheduler timingTaskScheduler; |
| | | private final SysUserMapper sysUserMapper; |
| | | private static final int ENABLED = 1; |
| | | private static final int DISABLED = 0; |
| | | |
| | | @Autowired |
| | | private InspectionTaskMapper inspectionTaskMapper; |
| | | |
| | | @Autowired |
| | | private TimingTaskScheduler timingTaskScheduler; |
| | | |
| | | @Autowired |
| | | private SysUserMapper sysUserMapper; |
| | | /** |
| | | * 启动时把已启用但未注册到 Quartz 的巡检任务补注册, |
| | | * 避免调度记录丢失后定时任务静默不执行 |
| | | */ |
| | | @PostConstruct |
| | | public void restoreScheduledTasks() { |
| | | try { |
| | | List<TimingTask> tasks = timingTaskMapper.selectList(new LambdaQueryWrapper<TimingTask>() |
| | | .eq(TimingTask::getIsEnabled, ENABLED) |
| | | .eq(TimingTask::getDeleted, 0)); |
| | | for (TimingTask task : tasks) { |
| | | try { |
| | | LocalDateTime nextTime = calculateFirstExecutionTime(task); |
| | | if (!Objects.equals(task.getNextExecutionTime(), nextTime)) { |
| | | task.setNextExecutionTime(nextTime); |
| | | timingTaskMapper.updateById(task); |
| | | } |
| | | timingTaskScheduler.ensureScheduled(task); |
| | | } catch (Exception e) { |
| | | log.error("恢复巡检定时任务失败: id={}, name={}", task.getId(), task.getTaskName(), e); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("恢复巡检定时任务异常", e); |
| | | } |
| | | } |
| | | |
| | | |
| | | @Override |
| | |
| | | if (StringUtils.isNotBlank(timingTask.getTaskName())) { |
| | | queryWrapper.like(TimingTask::getTaskName, timingTask.getTaskName()); |
| | | } |
| | | if (StringUtils.isNotBlank(timingTask.getInspectionProject())) { |
| | | queryWrapper.like(TimingTask::getInspectionProject, timingTask.getInspectionProject()); |
| | | } |
| | | if (timingTask.getIsEnabled() != null) { |
| | | queryWrapper.eq(TimingTask::getIsEnabled, timingTask.getIsEnabled()); |
| | | } |
| | | queryWrapper.orderByDesc(TimingTask::getCreateTime); |
| | | IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, queryWrapper); |
| | | |
| | | // 2. 如果没有数据,直接返回空分页 |
| | |
| | | @Override |
| | | @Transactional |
| | | public int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException { |
| | | TimingTask oldTimingTask = null; |
| | | if (Objects.nonNull(timingTaskDto.getId())) { |
| | | oldTimingTask = timingTaskMapper.selectById(timingTaskDto.getId()); |
| | | if (oldTimingTask == null) { |
| | | throw new IllegalArgumentException("定时任务不存在"); |
| | | } |
| | | } |
| | | TimingTask timingTask = new TimingTask(); |
| | | BeanUtils.copyProperties(timingTaskDto, timingTask); |
| | | timingTask.setIsEnabled(resolveEnabledValue(timingTask.getIsEnabled(), oldTimingTask)); |
| | | timingTask.setActive(ENABLED == timingTask.getIsEnabled()); |
| | | // 1. 解析字符串为 LocalDate(只包含年月日) |
| | | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | LocalDate localDate = LocalDate.now(); |
| | |
| | | // 设置创建人信息和默认值 |
| | | 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) { |
| | | if (result > 0 && isEnabled(timingTask.getIsEnabled(), timingTask.isActive())) { |
| | | // 新增成功后添加到调度器 |
| | | timingTaskScheduler.scheduleTimingTask(timingTask); |
| | | } |
| | | return result; |
| | | } else { |
| | | |
| | | |
| | | // 频率变化时重算下次执行时间,避免页面显示与实际调度不一致 |
| | | if (!Objects.equals(oldTimingTask.getFrequencyType(), timingTask.getFrequencyType()) |
| | | || !Objects.equals(oldTimingTask.getFrequencyDetail(), timingTask.getFrequencyDetail())) { |
| | | timingTask.setNextExecutionTime(calculateFirstExecutionTime(timingTask)); |
| | | } |
| | | int result = timingTaskMapper.updateById(timingTask); |
| | | if (result > 0) { |
| | | // 更新成功后重新调度任务 |
| | | timingTaskScheduler.rescheduleTimingTask(timingTask); |
| | | boolean oldEnabled = isEnabled(oldTimingTask == null ? null : oldTimingTask.getIsEnabled(), oldTimingTask != null && oldTimingTask.isActive()); |
| | | boolean newEnabled = isEnabled(timingTask.getIsEnabled(), timingTask.isActive()); |
| | | if (!newEnabled) { |
| | | timingTaskScheduler.unscheduleTimingTask(timingTask.getId()); |
| | | } else if (oldEnabled) { |
| | | // 更新成功后重新调度任务 |
| | | timingTaskScheduler.rescheduleTimingTask(timingTask); |
| | | } else { |
| | | // 从禁用改为启用时重新创建调度任务 |
| | | timingTaskScheduler.scheduleTimingTask(timingTask); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | |
| | | // 如果是每月执行,计算下个月的具体日期 |
| | | return calculateMonthlyFirstExecution(task.getFrequencyDetail()); |
| | | } else if ("QUARTERLY".equals(frequencyType)) { |
| | | // 自定义频率,如每小时、每30分钟等 |
| | | return calculateCustomFirstExecution(task.getFrequencyDetail()); |
| | | // 季度执行,格式为 月,日,时间 |
| | | return calculateQuarterlyFirstExecution(task.getFrequencyDetail()); |
| | | } else { |
| | | throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType()); |
| | | } |
| | |
| | | return targetDateTime; |
| | | } |
| | | |
| | | private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) { |
| | | return null; |
| | | /** |
| | | * 计算季度任务的首次执行时间,格式为 月,日,时间(如 01,15,14:00) |
| | | * 执行月份按季度起点对齐,与 convertQuarterlyToCron 生成的 cron 保持一致 |
| | | */ |
| | | private LocalDateTime calculateQuarterlyFirstExecution(String frequencyDetail) { |
| | | String[] parts = frequencyDetail.split(","); |
| | | if (parts.length != 3) { |
| | | throw new IllegalArgumentException("参数格式错误,应为'01,15,14:00'格式"); |
| | | } |
| | | int month = Integer.parseInt(parts[0].trim()); |
| | | int dayOfMonth = Integer.parseInt(parts[1].trim()); |
| | | LocalTime time = LocalTime.parse(parts[2].trim(), DateTimeFormatter.ofPattern("HH:mm")); |
| | | if (month < 1 || month > 12) { |
| | | throw new IllegalArgumentException("月份必须在1-12之间: " + month); |
| | | } |
| | | if (dayOfMonth < 1 || dayOfMonth > 31) { |
| | | throw new IllegalArgumentException("日期必须在1-31之间: " + dayOfMonth); |
| | | } |
| | | |
| | | // 按季度起点对齐执行月份(1、4、7、10 月) |
| | | int quarterStartMonth = ((month - 1) / 3) * 3 + 1; |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | int currentYear = now.getYear(); |
| | | for (int i = 0; i < 8; i++) { |
| | | int totalMonth = quarterStartMonth + i * 3; |
| | | int year = currentYear + (totalMonth - 1) / 12; |
| | | int targetMonth = ((totalMonth - 1) % 12) + 1; |
| | | int adjustedDay = Math.min(dayOfMonth, YearMonth.of(year, targetMonth).lengthOfMonth()); |
| | | LocalDateTime candidate = LocalDateTime.of(year, targetMonth, adjustedDay, time.getHour(), time.getMinute()); |
| | | if (candidate.isAfter(now)) { |
| | | return candidate; |
| | | } |
| | | } |
| | | throw new IllegalStateException("无法计算季度任务的首次执行时间: " + frequencyDetail); |
| | | } |
| | | |
| | | @Override |
| | |
| | | return days; |
| | | } |
| | | |
| | | private Integer resolveEnabledValue(Integer requestEnabled, TimingTask oldTimingTask) { |
| | | if (requestEnabled != null) { |
| | | return requestEnabled; |
| | | } |
| | | if (oldTimingTask != null) { |
| | | if (oldTimingTask.getIsEnabled() != null) { |
| | | return oldTimingTask.getIsEnabled(); |
| | | } |
| | | return oldTimingTask.isActive() ? ENABLED : DISABLED; |
| | | } |
| | | return ENABLED; |
| | | } |
| | | |
| | | private boolean isEnabled(Integer enabledValue, boolean activeFallback) { |
| | | if (enabledValue != null) { |
| | | return ENABLED == enabledValue; |
| | | } |
| | | return activeFallback; |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |