feat(condition): CONDITION 判断方式二选一——EXPRESSION 内置 Aviator 表达式(沙箱)+ SERVICE 原口径;JudgeBody 补 submitterId

- ExpressionEvaluator:Aviator 5.4.3 沙箱(FEATURE_SET 只留 If、ALLOWED_CLASS_SET 空=禁类访问/反射)、编译缓存、
  顶层变量缺失抛错挂起(Aviator 缺失变量按 nil 且 nil 视作最小值,<= 兜底出口会静默放行——宁挂起不误放行)
- ConditionRouteService.decide 分流:EXPRESSION 按出边顺序取首个 true(逐边结果摘要落 raw_response),
  全 false/异常挂起沿用 SERVICE 纪律(route_log outlet=NULL + EXPRESSION 常量 + 告警 + 管理端重跑)
- DefValidator 发布期分层校验:EXPRESSION 出口缺条件/语法错/违禁表达式硬拦;表单变量放行(运行时核对);
  内置变量疑似拼错 warn;judgeMode 非法值拦
- /admin/router/eval 调试端点(同一求值核心,不落库不走流程)
- 单测 22 新增 + 全量 170 绿;E2E 18/18(发布/分支选路/挂起留痕/重跑/调试)见 local-debug/four-topics-a-e2e.py
This commit is contained in:
chenxing
2026-10-10 18:52:40 +08:00
parent a1f99fd7e0
commit 92619508f0
12 changed files with 619 additions and 15 deletions
@@ -42,6 +42,9 @@ public class ConditionRouteService {
*/
public String decide(FlwInstance instance, FlowDefModel model, FlowDefModel.Node node) {
List<String> 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={}",
@@ -50,7 +53,8 @@ public class ConditionRouteService {
}
RouteJudgeClient.JudgeBody body = new RouteJudgeClient.JudgeBody(
instance.getSchId(), instance.getId(), instance.getVersion(),
instance.getBizType(), instance.getBizId(), node.id, node.name,
instance.getBizType(), instance.getBizId(), instance.getSubmitterId(),
node.id, node.name,
node.routerKey, outlets, parseVariables(instance.getFormDataJson()));
try {
RouteJudgeClient.JudgeResult result = judgeClient.call(router, body);
@@ -60,7 +64,7 @@ public class ConditionRouteService {
return persistFailure(instance, node, outlets,
"返回出口未命中 outlets:" + result.outlet + ";raw=" + result.rawResponse);
}
persist(instance, node, result.outlet, 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;
@@ -71,6 +75,54 @@ public class ConditionRouteService {
}
}
/** 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<String> outlets) {
Map<String, Object> 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<String, Object> builtinEnv(FlwInstance instance, FlowDefModel.Node node,
Map<String, Object> 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<String> outletNames(FlowDefModel model, FlowDefModel.Node node) {
List<String> names = new ArrayList<>();
@@ -97,19 +149,21 @@ public class ConditionRouteService {
private String persistFailure(FlwInstance instance, FlowDefModel.Node node, List<String> outlets,
String error) {
int attempt = persist(instance, node, null, truncate(error));
int attempt = persist(instance, node, node.expressionJudge() ? EXPRESSION_ROUTER_KEY : node.routerKey,
null, truncate(error));
alertSuspended(instance, node, outlets, error, attempt);
return null;
}
/** 落一行留痕(REQUIRES_NEW:外部调用已真实发生,不随外层回滚),返回本轮 attempt(告警 msgNo 用) */
private int persist(FlwInstance instance, FlowDefModel.Node node, String outlet, String raw) {
private int persist(FlwInstance instance, FlowDefModel.Node node, String routerKeyLabel,
String outlet, String raw) {
FlwRouteLog logRow = new FlwRouteLog();
logRow.setSchId(instance.getSchId());
logRow.setInstanceId(instance.getId());
logRow.setNodeId(node.id);
logRow.setNodeName(node.name);
logRow.setRouterKey(node.routerKey);
logRow.setRouterKey(routerKeyLabel);
logRow.setOutlet(outlet);
logRow.setRawResponse(raw);
return routeLogStore.append(logRow).getAttempt();