From 0a4d548b6bf9cfef252400fb6d6612e45cf5b602 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 27 十月 2026 13:21:23 +0800
Subject: [PATCH] fix(scheduler): 修复定时任务调度问题并增强系统稳定性

---
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java |  174 ++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 150 insertions(+), 24 deletions(-)

diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
index 33c815e..deb88ac 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -1,20 +1,21 @@
 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.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;
 
@@ -29,25 +30,59 @@
  */
 @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
     public IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask) {
         // 1. 鍏堝垎椤垫煡璇㈠畾鏃朵换鍔℃暟鎹�
-        IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, null);
+        // 鏋勫缓鏌ヨ鏉′欢
+        LambdaQueryWrapper<TimingTask> queryWrapper = new LambdaQueryWrapper<>();
+        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. 濡傛灉娌℃湁鏁版嵁锛岀洿鎺ヨ繑鍥炵┖鍒嗛〉
         if (taskPage.getRecords().isEmpty()) {
@@ -66,6 +101,7 @@
 
         // 鏀堕泦宸℃浜篒D锛堝涓狪D浠ラ�楀彿鍒嗛殧锛�
         taskPage.getRecords().forEach(task -> {
+            task.setDateStr(task.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
             if (StringUtils.isNotBlank(task.getInspectorIds())) {
                 Arrays.stream(task.getInspectorIds().split(","))
                         .filter(StringUtils::isNotBlank)
@@ -116,35 +152,67 @@
     @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. 瑙f瀽瀛楃涓蹭负 LocalDate锛堝彧鍖呭惈骞存湀鏃ワ級
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        LocalDate localDate = LocalDate.now();
+        if(StringUtils.isNotEmpty(timingTaskDto.getDateStr())){
+            localDate = LocalDate.parse(timingTaskDto.getDateStr(), formatter);
+        }
+        // 2. 鑾峰彇褰撳墠绯荤粺鐨� LocalTime锛堝寘鍚椂鍒嗙锛�
+        LocalTime currentTime = LocalTime.now();
 
+        // 3. 鍚堝苟 LocalDate 鍜屽綋鍓� LocalTime 涓� LocalDateTime
+        LocalDateTime localDateTime = LocalDateTime.of(localDate, currentTime);
+        timingTask.setCreateTime(localDateTime);
         // 璁剧疆鍒涘缓浜轰俊鎭拰榛樿鍊�
         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;
         }
     }
 
-    private LocalDateTime calculateFirstExecutionTime(TimingTask task) {
+    public LocalDateTime calculateFirstExecutionTime(TimingTask task) {
         // 鏍规嵁棰戠巼绫诲瀷鍜岃鎯呰绠楅娆℃墽琛屾椂闂�
         String frequencyType = task.getFrequencyType();
         if ("DAILY".equals(frequencyType)) {
@@ -157,8 +225,8 @@
             // 濡傛灉鏄瘡鏈堟墽琛岋紝璁$畻涓嬩釜鏈堢殑鍏蜂綋鏃ユ湡
             return calculateMonthlyFirstExecution(task.getFrequencyDetail());
         } else if ("QUARTERLY".equals(frequencyType)) {
-            // 鑷畾涔夐鐜囷紝濡傛瘡灏忔椂銆佹瘡30鍒嗛挓绛�
-            return calculateCustomFirstExecution(task.getFrequencyDetail());
+            // 瀛e害鎵ц锛屾牸寮忎负 鏈�,鏃�,鏃堕棿
+            return calculateQuarterlyFirstExecution(task.getFrequencyDetail());
         } else {
             throw new IllegalArgumentException("涓嶆敮鎸佺殑棰戠巼绫诲瀷: " + task.getFrequencyType());
         }
@@ -277,8 +345,40 @@
         return targetDateTime;
     }
 
-    private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) {
-        return null;
+    /**
+     * 璁$畻瀛e害浠诲姟鐨勯娆℃墽琛屾椂闂达紝鏍煎紡涓� 鏈�,鏃�,鏃堕棿锛堝 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("鏃犳硶璁$畻瀛e害浠诲姟鐨勯娆℃墽琛屾椂闂�: " + frequencyDetail);
     }
 
     @Override
@@ -440,11 +540,37 @@
         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
     public int delByIds(Long[] ids) {
-        return timingTaskMapper.deleteBatchIds(Arrays.asList(ids));
+        int i = timingTaskMapper.deleteBatchIds(Arrays.asList(ids));
+        if(i > 0){
+            for (Long id : ids) {
+                timingTaskScheduler.unscheduleTimingTask(id);
+            }
+        }
+        return i;
     }
 
 }

--
Gitblit v1.9.3