news 2026/8/1 4:22:41

对账流程的 OGNL 变量完整数据流

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
对账流程的 OGNL 变量完整数据流

OGNL 变量 6 个来回传的关键点

数据流总览
启动流程 (Controller) │ businessKey + 初始 variables ▼ ┌─────────────────────────────────────────┐ │ serviceTaskFetchBoth (FetchBothDelegate)│ │ setVariable("custodyRecords", 1000) │ ← (1) ServiceTask 设变量 │ setVariable("clientRecords", 1000) │ └────────┬────────────────────────────────┘ ▼ ┌─────────────────────────────────────────┐ │ serviceTaskAutoMatch (AutoMatchDelegate)│ │ setVariable("diffCount", 0) │ ← (2) 算差异关键 │ setVariable("diffAmount", ZERO) │ └────────┬────────────────────────────────┘ ▼ exclusiveGatewayDiff (BPMN) ${diffCount > 0} → userTaskAdjust ← (3) Gateway 读 OGNL ${diffCount == 0} → serviceTaskWriteReconciliation ▼ (假设走 Adjust) ┌─────────────────────────────────────────┐ │ userTaskAdjust (BPMN) │ │ assignee="${operator}" │ ← (4) assignee 也是 OGNL │ documentation="差异: ${diffCount} 笔" │ ← (5) 文档可读 OGNL │ 调账 → 完成时 setVariable("operator") │ └────────┬────────────────────────────────┘ ▼ ┌─────────────────────────────────────────┐ │ serviceTaskWriteReconciliation │ ← (6) ServiceTask 读回来 │ getVariable("diffCount") │ │ getVariable("diffAmount") │ │ reconciliationService.writeReconciliation(...) │ └─────────────────────────────────────────┘
关键点 1:启动时怎么传 variables

Controller侧(WorkflowController.startReconciliation):

@PostMapping("/reconciliation/start")public Map<String, Object> startReconciliation(@RequestParam String businessKey) { Map<String, Object> variables = new HashMap<>(); variables.put("operator", "李四"); // 调账操作员 variables.put("reconDate", "2026-07-31"); // 流程定义里 userTask 的 ${operator} 会被解析成 "李四" ProcessInstance pi = runtimeService.startProcessInstanceByKey( "reconciliation", businessKey, variables); return Map.of("processInstanceId", pi.getId());}

注意:这里传的 variables 是全局变量,整个流程实例的所有节点都能读。

关键点 2:ServiceTask 设变量(设了 Gateway 就能读)

AutoMatchDelegate.java(demo 简化版,真实场景会去 DB 查两方数据):

@Component @RequiredArgsConstructor @Slf4j public class AutoMatchDelegate implements JavaDelegate { @Override public void execute(DelegateExecution execution) { log.info("🔍 [对账] 自动比对两方数据"); // 真实场景(伪代码): // List<CustodyRecord> custody = custodyMapper.findAll(...); // List<ClientRecord> client = clientMapper.findAll(...); // int diffCount = custody.stream() // .filter(c -> !client.contains(c)) // .collect(toList()).size(); // Demo 简化:直接设 0 走"通过"分支 execution.setVariable("diffCount", 0); execution.setVariable("diffAmount", BigDecimal.ZERO); } }

关键 APIexecution.setVariable("key", value)跟 HashMap 一模一样。一旦 set,BPMN 的${key}立即能读到

关键点 3:Gateway 用 OGNL 表达式读变量(最核心)

reconciliation.bpmn20.xml第 33-39 行:

<exclusiveGateway id="exclusiveGatewayDiff" name="差异>0?"/><sequenceFlow id="flow4_yes" name="是" sourceRef="exclusiveGatewayDiff" targetRef="userTaskAdjust"> <conditionExpression xsi:type="tFormalExpression">${diffCount > 0}</conditionExpression></sequenceFlow><sequenceFlow id="flow4_no" name="否" sourceRef="exclusiveGatewayDiff" targetRef="serviceTaskWriteReconciliation"> <conditionExpression xsi:type="tFormalExpression">${diffCount == 0 || diffCount == null}</conditionExpression></sequenceFlow>

OGNL 表达式能做啥

表达式

含义

${diffCount > 0}

简单数值比较

${diffAmount.compareTo(new BigDecimal("10000")) > 0}

BigDecimal 比较

${riskLevel == 'HIGH' && amount > 1000000}

复合条件

${approved == true}

布尔值

${businessType == 'CUSTODY' ? 'CUSTODY_REVIEW' : 'GENERAL'}

三元

${vars.containsKey('fastTrack')}

检查变量是否存在

踩坑:OGNL 是JVM 内部语言new BigDecimal(...)写起来很丑。实战建议:复杂计算放到 ServiceTask 里,Gateway 只做简单比较

踩坑 2:XML 里>字符要写成&gt;

<!-- 错误 --> <conditionExpression>${diffCount > 0}</conditionExpression> <!-- 正确 --> <conditionExpression>${diffCount &gt; 0}</conditionExpression>

(本 demo 用了 > 实际能跑通是因为 Flowable 容错,但严格场景要转义。)

关键点 4:userTask 的assignee也是 OGNL 表达式
<userTask id="userTaskAdjust" name="调账处理" flowable:assignee="${operator}"> <documentation>处理差异笔数: ${diffCount} 笔, 差异金额: ${diffAmount}</documentation></userTask>

flowable:assignee="${operator}":Flowable 引擎在 userTask 创建时,会从 execution variables 里找operator这个 key,把值赋给 assignee

好处同一个 BPMN 文件可以服务不同的 assignee,不用为每个人复制流程。

实战场景

  • 业务发起流程 → 传入operator: "李四"

  • 李四登录 → 看到自己的待办

  • 完成 → 流程往下走

documentation也是 OGNL,前端调账页面会显示:

处理差异笔数: 3 笔, 差异金额: 1500.00

关键点 5:ServiceTask 读变量(Get 回来写业务表)

WriteReconciliationDelegate.java

@Component @RequiredArgsConstructor @Slf4j public class WriteReconciliationDelegate implements JavaDelegate { private final ReconciliationService reconciliationService; // 走 Spring 容器 @Override public void execute(DelegateExecution execution) { // 3 个变量全部 get 回来 String businessKey = execution.getProcessInstanceBusinessKey(); Integer diffCount = (Integer) execution.getVariable("diffCount"); BigDecimal diffAmount = (BigDecimal) execution.getVariable("diffAmount"); String operator = (String) execution.getVariable("operator"); log.info("💾 [对账] 写入结果: businessKey={}, diffCount={}, diffAmount={}", businessKey, diffCount, diffAmount); reconciliationService.writeReconciliation( businessKey, diffCount != null ? diffCount : 0, diffAmount != null ? diffAmount : BigDecimal.ZERO, operator); } }

注意

  • getProcessInstanceBusinessKey()流程实例级别的,从启动时一路贯穿

  • getVariable("key")可能返回null永远要做 null check(上面diffCount != null ? diffCount : 0就是兜底)

  • 类型转换:execution.getVariable返回Object,需要强转。转换前用instanceof校验会更安全:

    java

    if (diffCount instanceof Integer) { // 安全使用}
关键点 6:local vs global 变量(进阶,常见踩坑)

java

// 全局变量:整个流程实例都能读(最常用)execution.setVariable("diffCount", 0);// Local 变量:只在当前 ServiceTask / 当前 execution token 可见execution.setVariableLocal("tempCalc", 0);

实战取舍

场景

用法

原因

业务数据(diffCount, operator)

setVariable(全局)

后续所有节点都需要

中间计算结果(临时累加)

setVariableLocal

避免污染其他节点的命名空间

userTask 完成时传变量

默认是 local

完成时仅供 outgoing flow 用

踩坑案例:你在 userTask 完成事件里 set 了一个变量,下一个 ServiceTask 读不到99% 是因为用了setVariableLocal

真实场景下 AutoMatchDelegate 长啥样

  • demo 简化版(默认无差异),真实场景应该是这样:

@Component @RequiredArgsConstructor @Slf4j public class AutoMatchDelegate implements JavaDelegate { private final CustodyRecordMapper custodyMapper; private final ClientRecordMapper clientMapper; @Override public void execute(DelegateExecution execution) { String businessKey = execution.getProcessInstanceBusinessKey(); log.info("🔍 [对账] 自动比对两方数据, businessKey={}", businessKey); // 1. 查两方数据 List<CustodyRecord> custodyList = custodyMapper.findByBusinessKey(businessKey); List<ClientRecord> clientList = clientMapper.findByBusinessKey(businessKey); // 2. 业务比对(用业务主键做差集) Set<String> custodyIds = custodyList.stream() .map(CustodyRecord::getRecordId) .collect(Collectors.toSet()); Set<String> clientIds = clientList.stream() .map(ClientRecord::getRecordId) .collect(Collectors.toSet()); // 3. 计算差异(差集) Set<String> diffIds = new HashSet<>(custodyIds); diffIds.removeAll(clientIds); // 托管方有,委托方没 // 4. 计算差异金额 BigDecimal diffAmount = custodyList.stream() .filter(c -> diffIds.contains(c.getRecordId())) .map(CustodyRecord::getAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); // 5. 设变量(Gateway 立即能读) execution.setVariable("diffCount", diffIds.size()); execution.setVariable("diffAmount", diffAmount); log.info("🔍 [对账] 差异笔数={}, 差异金额={}", diffIds.size(), diffAmount); } }

关键设计

  • @RequiredArgsConstructor+final字段 = Lombok 自动生成构造器,走 Spring 容器注入(Mapper 是 MyBatis 的,Spring Boot 启动时已自动配置)

  • 业务主键关联businessKey把 Flowable 流程实例跟业务单据关联起来,永远不要在业务表里存act_ru_execution.id这种内部 ID

  • setVariable 后立刻 log:方便排查"Gateway 走错分支"这种问题

4 个实战 tips

踩坑

现象

修法

1. OGNL 用了>字符没转义

部分 IDE 报错,运行时 OK

严格场景写&gt;

2. setVariableLocal 传业务变量

后续节点读不到

业务变量用setVariable(global)

3. getVariable 不做 null check

强转 NPE

value != null ? value : default

4. 业务主键没贯穿到底

业务表关联不上 act_ru_execution

永远用businessKey关联

一句话总结

ServiceTask 负责"算"(setVariable),BPMN 负责"判"(OGNL Gateway 读),userTask 负责"人"(assignee + documentation),下一个 ServiceTask 负责"落"(getVariable + 写业务表)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/1 4:20:53

格雷码与二进制转换:原理、C语言实现与工程应用

1. 项目概述&#xff1a;从二进制到格雷码的优雅跨越在数字电路和通信系统的世界里&#xff0c;我们每天都在和0与1打交道。二进制&#xff08;Binary&#xff09;是我们最熟悉的数字表示法&#xff0c;每一位的权重是2的幂次方&#xff0c;清晰明了。但当你需要处理高速旋转编…

作者头像 李华
网站建设 2026/8/1 4:20:03

Epoch、Batch 与 DataLoader

很多刚开始阅读 PyTorch 推荐系统训练代码的同学&#xff0c;都会卡在一组名词&#xff1a; epoch、batch、DataLoader、shuffle、loss.backward()、optimizer.step()、test、HR、NDCG。 单独看每个概念不难&#xff0c;但放进训练循环里&#xff0c;很容易分不清整条流水线&a…

作者头像 李华
网站建设 2026/8/1 4:19:08

C++ vector多维数组初始化:一行代码实现高效内存管理

1. 从“一行代码”说起&#xff1a;为什么我们需要关注vector的初始化&#xff1f;在C的日常开发里&#xff0c;尤其是处理算法题、数值计算或者游戏逻辑时&#xff0c;二维、三维数组&#xff08;或者说矩阵、张量&#xff09;是绕不开的数据结构。很多新手&#xff0c;甚至一…

作者头像 李华
网站建设 2026/8/1 4:16:34

同样的 Agent,换了一套提示词,效果翻了 5 倍:Skill 工程实战指南

上个月&#xff0c;我帮一个团队优化他们的 AI Agent。 这个 Agent 做的事情很简单——处理客户的退款申请。但上线一个月&#xff0c;用户满意度只有 58%&#xff0c;大量投诉说"机器人听不懂人话"、“答非所问”、“流程卡住了”。 我看了他们的 Prompt&#xff0c…

作者头像 李华
网站建设 2026/8/1 4:15:59

GPT文本生成原理与采样策略优化实践

1. GPT文本生成的核心原理与工作流程GPT&#xff08;Generative Pre-trained Transformer&#xff09;作为当前最先进的文本生成模型&#xff0c;其核心在于Transformer架构的自注意力机制。这个机制让模型能够动态评估输入序列中每个词对其他词的重要性权重&#xff0c;从而建…

作者头像 李华