liusheng
4 天以前 0bbbd746e8c39d91ad7b47a4e36e29ab972dc98c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package com.ruoyi.inhospvisit.service;
 
import com.ruoyi.common.core.service.ScheduleRollPort;
import com.ruoyi.common.core.service.dto.VisitScheduleRollResultDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import java.util.List;
 
/**
 * 任务组「轮次滚动」编排器 —— 逐行搬自
 * {@code ServiceTaskScheduleServiceImpl#rollCycleTasks}(L170-190,21 行)。
 *
 * <h3>为什么这里只有"编排"(第 9 步的设计)</h3>
 * 真正滚动单个计划要做一次 {@code ServiceSubtask} 的 <b>116 字段全字段深拷贝</b>,
 * 那部分<b>刻意留在实现侧</b>(smartor 的 {@code ScheduleRollPortBridge}),
 * 不跨越模块边界传 116 字段的 DTO —— 理由见 {@link ScheduleRollPort} 的类注释。
 * <p>
 * 本类负责的部分(与原文逐行对应):
 * <ol>
 *   <li>批次上限 {@code ROLL_BATCH_LIMIT = 200}(原 L50 / L171);</li>
 *   <li>扫描日志 {@code [任务组-滚动] 扫描到 {} 个待评估的循环计划}(原 L175);</li>
 *   <li>逐条 {@code try/catch},单条失败不影响其它计划(原 L178-186);</li>
 *   <li>计数与收尾日志 {@code 本次推进 {} 个计划}(原 L188)。</li>
 * </ol>
 *
 * <h3>⚠️ 与原实现的两处已知差异(都已登记)</h3>
 * <ol>
 *   <li><b>事务边界</b>:原实现在 {@code rollOneSchedule} 上标 {@code @Transactional}
 *       (方法在同一个类里,通过 {@code SpringUtils.getAopProxy(this)} 自调以让注解生效,见原 L180)。
 *       搬迁后 {@code @Transactional} 落在桥实现 {@code ScheduleRollPortBridge#rollOneSchedule} 上
 *       —— <b>跨 Bean 调用,事务照常生效</b>,所以不再需要 AOP 自调那一步。
 *       这是等价改写,不是行为变更。</li>
 *   <li><b>计划对象是重查的</b>:原实现把"查询结果里的计划对象"直接传给 {@code rollOneSchedule};
 *       端口只给 id,所以桥里按 id 重查一次。差异只在"两次读取之间计划是否被改过"这个毫秒级窗口,
 *       且 roll 内部仍会做"本轮是否结束"的校验。已在桥的注释里说明。</li>
 * </ol>
 *
 * <h3>⚠️ 过渡期禁止双跑</h3>
 * 本类在第 10 步(把 {@code RyTask.rollTaskGroupCycle} 的方法体切过来)之前,
 * <b>不能被挂到任何调度上</b>:老的 {@code rollCycleTasks} 还在跑,
 * 两边都会"推进轮次 + 新建 subtask",双跑会造成重复轮次。
 * 目前本类只被自检程序调用。
 */
@Slf4j
@Service
public class TaskGroupCycleRoller {
 
    /** 单次扫描的最大计划数,避免长事务(原 {@code ServiceTaskScheduleServiceImpl} L50)。 */
    private static final int ROLL_BATCH_LIMIT = 200;
 
    @Autowired
    private ObjectProvider<ScheduleRollPort> rollPortProvider;
 
    /**
     * 扫描循环任务组计划并推进已完成的轮次。
     * <p>
     * 逐行对应原 {@code rollCycleTasks()}(L170-190)。
     *
     * @return 本次推进的计划数
     */
    public int rollCycleTasks() {
        ScheduleRollPort port = rollPort();
        if (port == null) {
            log.warn("【出入院随访】ScheduleRollPort 不可用(任务组端口未装配),本次不推进任何轮次。");
            return 0;
        }
 
        // 原 L171-174
        List<Long> scheduleIds = port.selectActiveCycleSchedules(ROLL_BATCH_LIMIT);
        if (CollectionUtils.isEmpty(scheduleIds)) {
            return 0;
        }
        // 原 L175
        log.info("[任务组-滚动] 扫描到 {} 个待评估的循环计划", scheduleIds.size());
 
        int rolled = 0;
        for (Long scheduleId : scheduleIds) {
            try {
                // 原 L180:if (SpringUtils.getAopProxy(this).rollOneSchedule(schedule)) rolled++;
                // 搬迁后 @Transactional 在桥实现上,跨 Bean 调用即生效,无需 AOP 自调。
                VisitScheduleRollResultDto result = port.rollOneSchedule(scheduleId);
                if (result != null && result.isRolled()) {
                    rolled++;
                }
            } catch (Exception e) {
                // 原 L183-186:单条失败不影响其它计划,记日志继续
                log.error("[任务组-滚动] scheduleId={} 滚动失败:{}", scheduleId, e.getMessage(), e);
            }
        }
        // 原 L188-189
        log.info("[任务组-滚动] 本次推进 {} 个计划", rolled);
        return rolled;
    }
 
    private ScheduleRollPort rollPort() {
        if (rollPortProvider == null) {
            return null;
        }
        try {
            return rollPortProvider.stream().findFirst().orElse(null);
        } catch (Exception e) {
            log.warn("【出入院随访】获取 ScheduleRollPort 失败:{}", e.toString());
            return null;
        }
    }
}