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;
|
}
|
}
|
}
|