package com.yunhai.flowcenter.engine;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunhai.flowcenter.entity.FlwInstance;
import com.yunhai.flowcenter.entity.FlwRouteLog;
import com.yunhai.flowcenter.entity.FlwRouter;
import com.yunhai.flowcenter.notify.UserMsgClient;
import com.yunhai.flowcenter.repository.FlwRouterRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 条件路由判断编排(2026-09-24 §4.4):查 flw_router 注册 → 组装判断契约体(variables 透传
* form_data_json)→ {@link RouteJudgeClient} 同步调用 → 结果落 flw_route_log(一次一行)。
*
*
失败策略(已定,勿改):挂起 + 管理端重跑,禁止配默认出口——超时/非 2xx/
* outlet 未命中/注册缺失一律返回 null(route_log 落 outlet=NULL 行 + 告警日志与站内信),
* 节点保持在途(currentNodes 不动、办件仍 10),管理端 reroute 重新调判断。
* "校长室免审"这类判断失败默认放行是安全事故,宁挂起。
* 退回重走 CONDITION 必然重新调判断(单据可能已改):attempt 递增,留痕只增不改。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ConditionRouteService {
private final FlwRouterRepository routerRepository;
private final RouteLogStore routeLogStore;
private final RouteJudgeClient judgeClient;
private final UserMsgClient userMsgClient;
private final ObjectMapper objectMapper;
/**
* 判断并留痕。
*
* @return 命中的出口名(必属 outlets);失败返回 null(已落 outlet=NULL 留痕行 + 告警)
*/
public String decide(FlwInstance instance, FlowDefModel model, FlowDefModel.Node node) {
List outlets = outletNames(model, node);
if (node.expressionJudge()) {
return decideByExpression(instance, model, node, outlets);
}
FlwRouter router = routerRepository.findByRouterKey(node.routerKey).orElse(null);
if (router == null || router.getStatus() == null || router.getStatus() != 1) {
log.error("[flow-center] 路由判断失败:routerKey={} 未注册或已停用(挂起):instance={} node={}",
node.routerKey, instance.getId(), node.id);
return persistFailure(instance, node, outlets, "routerKey 未注册或已停用:" + node.routerKey);
}
RouteJudgeClient.JudgeBody body = new RouteJudgeClient.JudgeBody(
instance.getSchId(), instance.getId(), instance.getVersion(),
instance.getBizType(), instance.getBizId(), instance.getSubmitterId(),
node.id, node.name,
node.routerKey, outlets, parseVariables(instance.getFormDataJson()));
try {
RouteJudgeClient.JudgeResult result = judgeClient.call(router, body);
if (!outlets.contains(result.outlet)) {
log.error("[flow-center] 路由判断返回出口未命中 outlets(挂起):instance={} node={} outlet={} outlets={}",
instance.getId(), node.id, result.outlet, outlets);
return persistFailure(instance, node, outlets,
"返回出口未命中 outlets:" + result.outlet + ";raw=" + result.rawResponse);
}
persist(instance, node, node.routerKey, result.outlet, result.rawResponse);
log.info("[flow-center] 路由判断命中:instance={} node={} router={} outlet={}",
instance.getId(), node.id, node.routerKey, result.outlet);
return result.outlet;
} catch (Exception e) {
log.error("[flow-center] 路由判断调用失败(挂起):instance={} node={} router={} {}",
instance.getId(), node.id, node.routerKey, e.toString());
return persistFailure(instance, node, outlets, "调用失败:" + e);
}
}
/** EXPRESSION 模式留痕的 router_key 快照(真实注册键不适用;列 NOT NULL) */
static final String EXPRESSION_ROUTER_KEY = "EXPRESSION";
/**
* EXPRESSION 模式(2026-10-10 四议题):按出边定义顺序对 condition 求值,首个 true 的出口名
* 即结果;逐边结果摘要落 raw_response。全 false / 求值异常 → 挂起,沿用 SERVICE 模式纪律
* (留痕 outlet=NULL + 告警 + 管理端重跑),不配默认出口。
*/
private String decideByExpression(FlwInstance instance, FlowDefModel model, FlowDefModel.Node node,
List outlets) {
Map env = builtinEnv(instance, node, parseVariables(instance.getFormDataJson()));
StringBuilder trace = new StringBuilder("EXPRESSION judge: ");
try {
for (FlowDefModel.Edge e : model.outEdges(node.id)) {
Object result = ExpressionEvaluator.evaluate(e.condition, env);
trace.append("[").append(e.name).append("] ").append(e.condition)
.append(" => ").append(result).append("; ");
if (Boolean.TRUE.equals(result)) {
int attempt = persist(instance, node, EXPRESSION_ROUTER_KEY, e.name, trace.toString());
log.info("[flow-center] 表达式判断命中:instance={} node={} outlet={} attempt={}",
instance.getId(), node.id, e.name, attempt);
return e.name;
}
}
log.error("[flow-center] 表达式全部未命中(挂起):instance={} node={} outlets={} trace={}",
instance.getId(), node.id, outlets, trace);
return persistFailure(instance, node, outlets, "表达式全部未命中:" + trace);
} catch (Exception e) {
log.error("[flow-center] 表达式求值异常(挂起):instance={} node={} {}",
instance.getId(), node.id, e.toString());
return persistFailure(instance, node, outlets, "表达式求值异常:" + e + ";" + trace);
}
}
/** 内置变量注入(EXPRESSION 求值 env;SERVICE 模式的 JudgeBody 同名字段一一对应) */
static Map builtinEnv(FlwInstance instance, FlowDefModel.Node node,
Map variables) {
variables.put("schId", instance.getSchId());
variables.put("instanceId", instance.getId());
variables.put("instanceVersion", instance.getVersion());
variables.put("bizType", instance.getBizType());
variables.put("bizId", instance.getBizId());
variables.put("nodeId", node.id);
variables.put("nodeName", node.name);
variables.put("submitterId", instance.getSubmitterId());
return variables;
}
/** CONDITION 出边名即判断契约的 outlets(发布期已保证非空唯一) */
private static List outletNames(FlowDefModel model, FlowDefModel.Node node) {
List names = new ArrayList<>();
for (FlowDefModel.Edge e : model.outEdges(node.id)) {
names.add(e.name);
}
return names;
}
/** form_data_json 原样透传为 variables(SDK 发起参数不加新字段;解析失败=空对象) */
private Map parseVariables(String formDataJson) {
if (formDataJson == null || formDataJson.trim().isEmpty()) {
return new java.util.HashMap<>();
}
try {
return objectMapper.readValue(formDataJson,
new com.fasterxml.jackson.core.type.TypeReference