[audit] P6.3 F5-4 reroute 增 useLatestDef 僵尸逃生门:按当前已发布最高版本重跑+快照刷新+trace 留痕(默认 false 现行为逐字不变)
build / build-push (push) Successful in 11m48s

This commit is contained in:
chenxing
2026-10-18 08:18:26 +08:00
parent ad87005169
commit 8ceb2a4247
5 changed files with 240 additions and 4 deletions
@@ -414,10 +414,13 @@ public class FlowAdminController {
/**
* 重跑路由(判断失败挂起的恢复入口,2026-09-24 §4.4):重新调判断服务,成功即按出口续走;
* 仍失败回业务错(办件继续挂起,route_log 已落新行)。
* useLatestDef=true(F5-4 僵尸逃生门):按该 flowGuid 当前已发布最高版本重跑并刷新办件定义快照。
*/
@PostMapping("/instance/{id}/reroute")
public void instanceReroute(BoaUser user, @PathVariable("id") Long id) {
routerAdminService.reroute(adminGuard.requireAdmin(user, null), id);
public void instanceReroute(BoaUser user, @PathVariable("id") Long id,
@RequestParam(value = "useLatestDef", required = false, defaultValue = "false")
boolean useLatestDef) {
routerAdminService.reroute(adminGuard.requireAdmin(user, null), id, useLatestDef);
}
/**
@@ -48,6 +48,8 @@ public final class FlwEnums {
public static final String NODE_RESUBMIT = "resubmit";
/** 留痕事件行保留节点 id:撤回 */
public static final String NODE_WITHDRAW = "withdraw";
/** 留痕事件行保留节点 id:管理端按新版定义重跑路由(F5-4,nodeName 载明 v旧→v新) */
public static final String NODE_REROUTE_DEF = "reroute-def";
private final int code;
@@ -33,6 +33,7 @@ public class FlowRouterAdminService {
private final FlwRouterRepository routerRepository;
private final FlwInstanceRepository instanceRepository;
private final FlwDefRepository defRepository;
private final com.yunhai.flowcenter.repository.FlwTaskRepository taskRepository;
private final FlowEngine flowEngine;
private final ObjectMapper objectMapper;
@@ -153,9 +154,23 @@ public class FlowRouterAdminService {
/**
* 重跑路由(挂起恢复唯一入口,§4.4):办件须在途且挂在 CONDITION 节点(currentNodes 含之、
* 该节点无待办任务)。重新调判断,成功即续走推进;仍失败抛业务错(挂起不变,留痕已落新行)。
* 默认按办件落库的 def 快照重跑(现行为逐字不变)。
*/
@Transactional
public void reroute(Long schId, Long instanceId) {
reroute(schId, instanceId, false);
}
/**
* 重跑路由(F5-4 僵尸逃生门):useLatestDef=true 时 def 改取该 flowGuid 当前已发布最高版本,
* 办件 defId/defCode/defVersion 快照同步刷新(与 R4 重启同语义,只刷定义三列,不动业务快照/
* 状态/时间),trace 落一行留痕(保留节点 id=reroute-def,nodeName 载明 v旧→v新)。
* 锚定 128/129 僵尸案例:办件挂在 v1 的 CONDITION 节点、修好的表达式已发布为 v2,按 def 快照
* 重跑永远打转——此开关让管理员一键切到 v2 续走。在途 CONDITION 节点在新定义不存在时
* 按人类可读业务错拒绝(不猜测节点映射)。useLatestDef=false 现行为逐字不变。
*/
@Transactional
public void reroute(Long schId, Long instanceId, boolean useLatestDef) {
FlwInstance instance = instanceRepository.findById(instanceId)
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在:" + instanceId));
if (!instance.getSchId().equals(schId)) {
@@ -168,15 +183,58 @@ public class FlowRouterAdminService {
if (def == null) {
throw new BizException(ErrorCode.PARAM_ERROR, "办件定义缺失");
}
if (useLatestDef) {
def = refreshToLatestPublishedDef(instance, def);
}
String conditionNode = findParkedCondition(instance, def);
if (conditionNode == null) {
throw new BizException(ErrorCode.PARAM_ERROR,
"办件未挂在条件分支节点(无挂起的路由判断,无需重跑)");
throw new BizException(ErrorCode.PARAM_ERROR, useLatestDef
? "在途条件节点在最新已发布定义中不存在(节点已被改版移除/改型),无法按新定义重跑"
: "办件未挂在条件分支节点(无挂起的路由判断,无需重跑)");
}
flowEngine.rerouteCondition(instance, def, conditionNode);
instanceRepository.save(instance);
}
/** 取 flowGuid 当前已发布最高版本并刷新办件定义快照(仅三列;已是最新则原样返回不落留痕) */
private FlwDef refreshToLatestPublishedDef(FlwInstance instance, FlwDef current) {
FlwDef latest = defRepository.findFirstBySchIdAndFlowGuidAndStatusOrderByVersionDesc(
instance.getSchId(), current.getFlowGuid(), FlwEnums.DefStatus.PUBLISHED.getCode())
.orElse(null);
if (latest == null) {
throw new BizException(ErrorCode.PARAM_ERROR, "该流程当前无已发布版本,无法按最新定义重跑");
}
if (latest.getId().equals(instance.getDefId())) {
return latest;
}
instance.setDefId(latest.getId());
instance.setDefCode(latest.getCode());
instance.setDefVersion(latest.getVersion());
appendRerouteDefTrace(instance, current.getVersion(), latest.getVersion());
return latest;
}
/**
* 定义切换留痕行(同 FlowInstanceService.appendTraceEvent 形态;彼方法私有且该类冻结,不共用):
* status=6 + 保留节点 id,nodeName 载明版本迁移;不参与引擎任何判断(同 submit/withdraw 行)。
*/
private void appendRerouteDefTrace(FlwInstance instance, Integer fromVersion, Integer toVersion) {
java.time.LocalDateTime now = java.time.LocalDateTime.now();
com.yunhai.flowcenter.entity.FlwTask row = new com.yunhai.flowcenter.entity.FlwTask();
row.setInstanceId(instance.getId());
row.setSchId(instance.getSchId());
row.setNodeId(FlwEnums.TaskStatus.NODE_REROUTE_DEF);
row.setNodeName("按新版定义重跑(v" + fromVersion + "→v" + toVersion + ")");
// assignee=0:管理端动作无自然办理人,落 0 不产生待办(同发起行脏数据兜底口径)
row.setAssigneeId(0);
row.setAssigneeName("管理端");
row.setStatus(FlwEnums.TaskStatus.SUBMITTED.getCode());
row.setArriveTime(now);
row.setHandleTime(now);
row.setUrgeCount(0);
taskRepository.save(row);
}
/** 在途节点里找 CONDITION(多个时取第一个;正常挂起只会有一个) */
private String findParkedCondition(FlwInstance instance, FlwDef def) {
String cur = instance.getCurrentNodes();