当前位置: 首页 > news >正文

对账流程的 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 + 写业务表)

http://www.cnnetsun.cn/news/3776748.html

相关文章:

  • 格雷码与二进制转换:原理、C语言实现与工程应用
  • Epoch、Batch 与 DataLoader
  • 点击化学:从CuAAC到SPAAC,掌握模块化分子连接的底层逻辑与实战指南
  • C++ vector多维数组初始化:一行代码实现高效内存管理
  • 同样的 Agent,换了一套提示词,效果翻了 5 倍:Skill 工程实战指南
  • GPT文本生成原理与采样策略优化实践
  • 工业级PID控制器C语言实现:从离散化到抗饱和与参数整定
  • MATLAB图像处理实战:空域与频域方法消除条纹干扰
  • Unity与Cocos2d-x双引擎实现Flappy Bird:源码对比与实战解析
  • 商用AI主机如何解决Token成本与稳定性难题,赋能本地大模型应用开发
  • LDO与DC-DC选型指南:从压差、功耗到锂电池供电的实战解析
  • ComfyUI UltimateSDUpscale安装问题深度解析:从模块缺失到完美修复
  • K8s StatefulSet 持久化存储:PV 绑定、扩容与快照备份
  • 推挽与开漏输出电路原理详解:从MOSFET结构到I2C总线应用
  • AI Agent如何自动化生成PPT:从技术原理到实践应用
  • STM32开发中“Not a genuine ST Device!”错误排查与解决指南
  • YimMenu终极指南:3步打造GTA5最强防崩溃游戏菜单
  • LangChain技能全景:从基础连接到生产级智能体部署全解析
  • Arduino入门指南:从环境搭建到项目实战,快速上手物联网开发
  • 电子工程师必备:电容选型实战指南与高频特性深度解析
  • 基于 Free Pascal 从零编写裸机操作系统(一)
  • CST同轴线仿真全流程:从建模优化到高频连接器设计实践
  • 大模型思考过程加密:技术原理、行业影响与工程应对策略
  • PCB板HDI1/HDI2/HDI3/HDI…、ELIC指的是什么?
  • 微信聊天记录AI分析:原理、应用与隐私安全实践指南
  • 主流 Agent 架构分析
  • 步进电机步距角与细分驱动详解:从原理到实战,告别抖动与丢步
  • 实用的工艺品设计服务受青睐,优质选择不容错过
  • AI助手APP竞争格局解析:从通用到垂直,如何选择与高效使用?
  • 首届OPC-AI赋能实战班在甬举办,分享AI超级个体实践思考