liusheng
6 天以前 cc479e6a056785103996cc7ea67dbd5faa3e800e
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
package com.ruoyi.common.core.service;
 
import com.ruoyi.common.core.service.dto.VisitScheduleRollResultDto;
 
import java.util.List;
 
/**
 * 循环任务组「轮次滚动」能力的预留接口(第 9 步)。
 *
 * <h3>为什么这个接口是"粗粒度"的(重要设计说明)</h3>
 * 任务组滚动的核心动作要做一次 <b>{@code ServiceSubtask} 的全字段深拷贝</b>:
 * <pre>
 * ServiceSubtask copy = JSON.parseObject(JSON.toJSONString(src), ServiceSubtask.class);
 * copy.setId(null); copy.setSenduuid(null); copy.setResult(null); ...
 * </pre>
 * 而 {@code ServiceSubtask} 有 <b>116 个字段</b>。
 * <p>
 * 如果为了"把逻辑搬进新模块"而在模块间传一个 116 字段的 DTO,字段映射本身就是巨大风险面,
 * 且<b>换不来任何收益</b> —— 全字段拷贝的语义由 JSON 序列化保证,不需要逐字段搬运。
 * <p>
 * 所以本接口刻意切成两个动作,把"原子性"留在实现侧:
 * <ol>
 *   <li>{@link #selectActiveCycleSchedules(int)} —— 查一批待评估的循环计划;</li>
 *   <li>{@link #rollOneSchedule(Long)} —— <b>滚动单个计划(整个动作原子完成,含深拷贝与推进)</b>。</li>
 * </ol>
 * 调用方({@code ruoyi-inhospvisit} 的 {@code TaskGroupCycleRoller})
 * 负责"批次上限 + 逐条 try/catch + 计数与日志"这部分<b>编排</b>逻辑。
 * <p>
 * 这与其它端口({@code VisitTaskCreateApi} 等)的粒度不同,是<b>有意的</b>:
 * 那些端口的对象字段少、映射可控;这里的对象有 116 个字段、且语义是"整体克隆"。
 *
 * <h3>与 {@code ServiceTaskScheduleServiceImpl#rollCycleTasks} 的关系</h3>
 * {@code rollCycleTasks} 的实现<b>本次刻意保留在 smartor 不动</b>(它仍被
 * {@code RyTask.rollTaskGroupCycle} 调用,而改调用点在第 10 步)。
 * 本接口的实现(过渡 Bridge)与它<b>共享同一套底层能力</b>(同一个 Mapper、同一份深拷贝语义),
 * 不复制业务逻辑;等到第 10 步把调用点切过来后,{@code rollCycleTasks} 才退役。
 *
 * <h3>⚠️ 过渡期禁止双跑</h3>
 * 第 9 步做完后,<b>「老的 {@code rollCycleTasks}」与「新的 {@code TaskGroupCycleRoller}」
 * 不能同时被调度</b> —— 两边都会"推进轮次 + 新建 subtask",双跑会造成重复轮次。
 * 第 10 步会把 {@code RyTask} 的方法体切到新路径;在那之前,新路径只被自检程序调用。
 */
public interface ScheduleRollPort {
 
    /**
     * 查一批待评估的循环计划(原 {@code ServiceTaskScheduleMapper#selectActiveCycleSchedules})。
     * <p>
     * 该查询自身的条件({@code schedule_style = 1}、{@code max_loop_count} 上限等)在
     * smartor 的 XML 里,本次<b>照抄不动</b>,由实现侧保证。
     *
     * @param limit 批次上限(原实现固定 200)
     * @return 计划ID列表;无数据返回空列表
     */
    List<Long> selectActiveCycleSchedules(int limit);
 
    /**
     * 滚动单个计划:若"本轮已全部结束"则推进到下一轮并克隆 subtask。
     * <p>
     * 逐行对应原 {@code ServiceTaskScheduleServiceImpl#rollOneSchedule}(L196-280),包括:
     * <ul>
     *   <li>上限校验({@code maxLoopCount})与"再开一轮是否超限"的判定;</li>
     *   <li>本轮是否全部结束({@code countActiveByScheduleIdAndLoop});</li>
     *   <li>拉本轮 subtask 作克隆模板 + 计划明细按 seq 索引;</li>
     *   <li>推进步长({@code scheduleTimeNum/Unit})合法性校验;</li>
     *   <li>逐条克隆(<b>全字段深拷贝</b> + 清 6 个字段 + 按 detail 覆盖模板 + 推进时间)并落库;</li>
     *   <li>复制 {@code service_subtask_preachform} 行;</li>
     *   <li>推进 schedule 的 {@code current_loop_count / current_seq / next_seq / next_execute_time}。</li>
     * </ul>
     * <b>整个动作必须在实现侧一次性完成</b>(含事务),不能拆成"取模板→改→存"三步跨模块调用。
     *
     * @param scheduleId 计划ID
     * @return 滚动结果({@code rolled=false} 时 {@code skipReason} 说明原因)
     */
    VisitScheduleRollResultDto rollOneSchedule(Long scheduleId);
}