可以把它理解为流程中的一个 “数据驱动的哨兵” 或 “智能监听器”。它会一直等待,直到某个特定的业务条件(通常基于流程变量)成立,然后才会被触发。
1. 核心概念与工作原理
条件事件的核心是一个返回布尔值(true/false)的表达式。
触发器:不是消息、不是信号、也不是时间,而是一个条件表达式。
工作机制:Flowable 引擎并不会持续地、疯狂地检查这个条件。相反,它非常高效:只有当流程实例中的一个或多个流程变量被创建、更新或删除时,引擎才会重新评估相关的条件事件的表达式。 如果表达式的结果从 false 变为 true,事件就会被触发。
一句话总结:它通过监听流程变量的变化来触发流程的下一步动作。
条件事件的类型
条件事件可以出现在流程的三个不同位置,每种都有其独特的用途:
a. 条件开始事件 (Conditional Start Event)
图标:标准起始事件圆圈内有一个“便签”或“文档”图标。
作用:用于启动一个新的流程实例。
触发时机:当一个与任何现有流程实例无关的变量被设置,并且该变量的设置使得某个流程定义的条件开始事件的条件成立时,Flowable 引擎就会自动启动一个新的流程实例。
使用场景:
系统集成:当另一个系统通过 API 设置了一个全局状态变量(例如,runtimeService.setVariable(processInstanceId, varName, value),但这里的 processInstanceId 是 null)时,自动触发一个新流程。
基于规则的启动:例如,定义一个“欺诈审查”流程,其条件开始事件是 ${transactionRiskScore > 90}。当外部系统通过 API 提交一笔交易数据,并设置了 transactionRiskScore 变量后,如果分数超过90,此流程就会自动启动。
b. 条件中间捕获事件 (Conditional Intermediate Catching Event)
图标:双层圆圈内有一个“便签”图标。
作用:让流程在某个节点暂停,等待特定条件满足后再继续。
触发时机:当流程执行到这个节点时,它会停下来。之后,如果该流程实例内部的某个变量发生变化,导致条件成立,流程就会从这个节点继续向下执行。
使用场景:
等待状态变更:在一个订单流程中,流程可以停在一个条件事件上,等待条件 ${orderStatus == ‘PAID’} 成立。当支付回调更新了 orderStatus 变量后,流程自动继续。
复杂的依赖关系:流程需要等待多个前置条件都满足才能进行下一步,可以将这些条件组合成一个复杂的表达式,例如 ${documentsUploaded == true && backgroundCheck == ‘PASS’}。
c. 条件边界事件 (Conditional Boundary Event)
图标:附加在某个活动(如用户任务、服务任务)边界上的双层圆圈(虚线或实线)内有一个“便签”图标。
作用:为某个活动提供一个备用路径或中断机制。
触发时机:当它所附加的活动正在进行时,如果流程实例的变量发生变化并满足了边界事件的条件,该事件就会被触发。
中断型(Interrupting):默认类型,实线边界。触发时,会立即终止其附加的活动,并沿着边界事件的路径继续。
非中断型(Non-interrupting):虚线边界。触发时,不会终止其附加的活动,而是会并行地创建一条新的执行路径。原活动继续执行。
使用场景:
任务升级(中断型):一个用户任务“普通审批”如果超过24小时未处理,一个定时器可以更新一个变量 isOverdue = true。一个附加在“普通审批”任务上的中断型条件边界事件可以监听 ${isOverdue == true},一旦满足,就中断当前审批,转到“上级加急审批”路径。
补充信息(非中断型):在一个“撰写报告”的用户任务期间,如果有人上传了补充材料(supplementaryMaterialAvailable = true),一个非中断型条件边界事件可以监听到此变化,并触发一个“通知撰写人查阅新材料”的并行任务,而撰写报告的任务本身不受影响。
业务场景介绍:企业费用报销流程(含补充材料)
这是一个非常典型的企业内部流程。员工提交报销,经理审批。但现实中,经常发生一种情况:员工提交的报销单缺少发票或信息不全。
传统处理方式的痛点:
直接驳回: 经理直接驳回申请,员工需要重新填写所有信息,发起一个全新的流程,体验很差。
线下沟通: 经理通过聊天工具或邮件让员工补充材料,流程在系统中一直处于“待审批”状态,不透明,容易遗忘。
使用条件事件的优化方案:
经理可以选择“退回补充材料”,流程将自动暂停,等待员工重新上传材料。一旦系统检测到材料已补齐(通过一个流程变量的变化来识别),流程将自动回到经理的审批节点。
流程核心角色
申请人 (Employee):发起报销,补充材料。
部门经理 (Manager):审批报销单。
财务 (Finance):支付报销款。
流程步骤详解
提交报销申请 (Submit Claim)
员工填写报销表单,提交后启动流程。
系统设置一个关键的流程变量,例如 materialsComplete = true。
经理审批 (Manager Approval)
这是一个用户任务(User Task)。经理的审批界面有三个按钮:
【批准】 (Approve)
【拒绝】 (Reject)
【退回补充材料】 (Request More Info)
审批结果? (Approval Gateway)
这是一个排他网关(Exclusive Gateway),根据经理的操作决定流程走向。
路径1:批准 -> 流程走向“财务支付”。
路径2:拒绝 -> 流程直接进入“报销被拒绝”的终止结束事件,整个流程强制结束。
路径3:退回补充材料 -> 这是我们关注的重点。
设置状态为“材料不全” (Set Status to Incomplete)
这是一个脚本任务(Script Task)。当经理点击“退回补充材料”后,系统自动执行脚本,将流程变量 materialsComplete 的值更新为 false。
execution.setVariable(“materialsComplete”, false);
同时,可以触发一个通知,告知申请人需要补充材料。
等待事件发生 (Event-Based Gateway)
这是一个基于事件的网关,它让流程进入一个“赛跑”状态,等待第一个到达的事件。它连接了两个并行的“捕获事件”:
事件A (核心): 等待材料补齐 (Wait for Materials)
这是一个条件中间捕获事件 (Conditional Intermediate Catching Event)。
它会持续监听流程变量 materialsComplete。一旦这个变量的值从 false 变为 true,此事件就会被触发。
如何触发? 申请人通过某个系统界面(或独立的任务)重新上传了发票,该操作的后端逻辑会调用 Flowable API 更新变量:runtimeService.setVariable(processInstanceId, “materialsComplete”, true);
事件B (备用): 等待超时 (Wait Timeout)
这是一个定时器中间捕获事件 (Timer Intermediate Catching Event)。
我们不能让流程无限期等待。这里设置一个3天(PT3D)的定时器。如果3天内员工还未补充材料,此事件会触发。
流程走向
如果条件事件先触发(员工在3天内补充了材料),流程会重新流转回“经理审批”节点,让经理再次审批。
如果定时器事件先触发(员工超时未处理),流程会走向“超时自动关闭”的结束事件,流程结束。
财务支付 / 流程结束
如果审批通过,流程流向财务进行支付,最后正常结束。
条件事件在此场景中的价值
流程自动化与智能化:流程不再是僵硬的线性执行,而是能够根据**数据状态(materialsComplete变量)**的变化做出响应,实现了“状态驱动”。
提升用户体验:避免了粗暴的“驳回重提”,申请人只需在原流程上补充信息即可,操作连贯。
解耦和灵活性:补充材料的操作可以是一个独立的任务,甚至可以是一个完全独立的外部应用。只要它最终能通过API更新流程变量,就能驱动主流程继续,这使得系统集成变得非常灵活。
清晰的流程状态:当流程停在条件事件上时,它的状态是明确的——“等待补充材料”。这比含糊不清的“审批中”要好得多,便于追踪和管理。
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
xmlns:flowable="http://flowable.org/bpmn"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.flowable.org/processdef">
<process id="expenseClaimProcess" name="企业费用报销流程(含补充材料)" isExecutable="true">
<!-- 流程开始 -->
<startEvent id="startEvent" name="开始"></startEvent>
<sequenceFlow id="flow1" sourceRef="startEvent" targetRef="submitClaimTask"></sequenceFlow>
<!-- 1. 员工提交报销 -->
<userTask id="submitClaimTask" name="提交报销申请" flowable:assignee="${initiator}">
<documentation>员工填写报销单并提交。系统在此步骤后应默认设置 materialsComplete = true。</documentation>
</userTask>
<sequenceFlow id="flow2" sourceRef="submitClaimTask" targetRef="managerApprovalTask"></sequenceFlow>
<!-- 2. 部门经理审批 -->
<userTask id="managerApprovalTask" name="部门经理审批" flowable:candidateGroups="managers">
<documentation>经理可选择“批准”、“拒绝”或“退回补充材料”。该选择结果存储在流程变量 'approvalResult' 中。</documentation>
</userTask>
<sequenceFlow id="flow3" sourceRef="managerApprovalTask" targetRef="approvalGateway"></sequenceFlow>
<!-- 3. 审批网关 -->
<exclusiveGateway id="approvalGateway" name="审批结果?"></exclusiveGateway>
<!-- 路径A: 批准 -->
<sequenceFlow id="flowApprove" name="批准" sourceRef="approvalGateway" targetRef="financePaymentTask">
<conditionExpression xsi:type="tFormalExpression">${approvalResult == 'approve'}</conditionExpression>
</sequenceFlow>
<!-- 路径B: 拒绝 -->
<sequenceFlow id="flowReject" name="拒绝" sourceRef="approvalGateway" targetRef="endEventRejected">
<conditionExpression xsi:type="tFormalExpression">${approvalResult == 'reject'}</conditionExpression>
</sequenceFlow>
<!-- 路径C: 退回补充材料 -->
<sequenceFlow id="flowRequestInfo" name="退回补充材料" sourceRef="approvalGateway" targetRef="setIncompleteStatus">
<conditionExpression xsi:type="tFormalExpression">${approvalResult == 'requestInfo'}</conditionExpression>
</sequenceFlow>
<!-- 4. 脚本任务:设置材料不全状态 -->
<scriptTask id="setIncompleteStatus" name="设置状态为“材料不全”" scriptFormat="groovy">
<script>
<![CDATA[
execution.setVariable("materialsComplete", false);
]]>
</script>
</scriptTask>
<sequenceFlow id="flowToEventGateway" sourceRef="setIncompleteStatus" targetRef="eventGateway"></sequenceFlow>
<!-- 5. 基于事件的网关:等待员工操作或超时 -->
<eventBasedGateway id="eventGateway" name="等待事件发生"></eventBasedGateway>
<sequenceFlow id="flowToConditionalEvent" sourceRef="eventGateway" targetRef="waitForMaterialsEvent"></sequenceFlow>
<sequenceFlow id="flowToTimerEvent" sourceRef="eventGateway" targetRef="timeoutEvent"></sequenceFlow>
<!-- 5a. 条件事件:等待材料补齐 -->
<intermediateCatchEvent id="waitForMaterialsEvent" name="等待材料补齐">
<documentation>监听流程变量 'materialsComplete'。当外部操作将其设置为 true 时,此事件触发。</documentation>
<conditionalEventDefinition>
<condition>${materialsComplete == true}</condition>
</conditionalEventDefinition>
</intermediateCatchEvent>
<!-- 补齐后,流程回到经理审批 -->
<sequenceFlow id="flowBackToApproval" sourceRef="waitForMaterialsEvent" targetRef="managerApprovalTask"></sequenceFlow>
<!-- 5b. 定时器事件:等待超时 -->
<intermediateCatchEvent id="timeoutEvent" name="等待超时(3天)">
<timerEventDefinition>
<timeDuration>PT3D</timeDuration> <!-- Period Time 3 Days -->
</timerEventDefinition>
</intermediateCatchEvent>
<sequenceFlow id="flowToEndOnTimeout" sourceRef="timeoutEvent" targetRef="endEventTimeout"></sequenceFlow>
<!-- 6. 财务支付 -->
<userTask id="financePaymentTask" name="财务支付" flowable:candidateGroups="finance"></userTask>
<sequenceFlow id="flowToApprovedEnd" sourceRef="financePaymentTask" targetRef="endEventApproved"></sequenceFlow>
<!-- 流程结束事件 -->
<endEvent id="endEventApproved" name="报销通过"></endEvent>
<endEvent id="endEventTimeout" name="超时自动关闭"></endEvent>
<!-- 终止结束事件:强制结束所有活动 -->
<endEvent id="endEventRejected" name="报销被拒绝">
<terminateEventDefinition></terminateEventDefinition>
</endEvent>
</process>
<!-- BPMN 图表定义 -->
<bpmndi:BPMNDiagram id="BPMNDiagram_expenseClaimProcess">
<bpmndi:BPMNPlane bpmnElement="expenseClaimProcess" id="BPMNPlane_expenseClaimProcess">
<bpmndi:BPMNShape bpmnElement="startEvent" id="BPMNShape_startEvent">
<omgdc:Bounds height="36.0" width="36.0" x="100.0" y="250.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="submitClaimTask" id="BPMNShape_submitClaimTask">
<omgdc:Bounds height="60.0" width="100.0" x="180.0" y="238.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="managerApprovalTask" id="BPMNShape_managerApprovalTask">
<omgdc:Bounds height="60.0" width="100.0" x="330.0" y="238.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="approvalGateway" id="BPMNShape_approvalGateway">
<omgdc:Bounds height="40.0" width="40.0" x="480.0" y="248.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="financePaymentTask" id="BPMNShape_financePaymentTask">
<omgdc:Bounds height="60.0" width="100.0" x="570.0" y="150.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endEventApproved" id="BPMNShape_endEventApproved">
<omgdc:Bounds height="36.0" width="36.0" x="720.0" y="162.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endEventRejected" id="BPMNShape_endEventRejected">
<omgdc:Bounds height="36.0" width="36.0" x="570.0" y="350.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="setIncompleteStatus" id="BPMNShape_setIncompleteStatus">
<omgdc:Bounds height="60.0" width="100.0" x="570.0" y="238.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="eventGateway" id="BPMNShape_eventGateway">
<omgdc:Bounds height="40.0" width="40.0" x="720.0" y="248.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="waitForMaterialsEvent" id="BPMNShape_waitForMaterialsEvent">
<omgdc:Bounds height="36.0" width="36.0" x="810.0" y="200.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="timeoutEvent" id="BPMNShape_timeoutEvent">
<omgdc:Bounds height="36.0" width="36.0" x="810.0" y="300.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endEventTimeout" id="BPMNShape_endEventTimeout">
<omgdc:Bounds height="36.0" width="36.0" x="900.0" y="300.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="136.0" y="268.0"></omgdi:waypoint>
<omgdi:waypoint x="180.0" y="268.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="280.0" y="268.0"></omgdi:waypoint>
<omgdi:waypoint x="330.0" y="268.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="430.0" y="268.0"></omgdi:waypoint>
<omgdi:waypoint x="480.0" y="268.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowApprove" id="BPMNEdge_flowApprove">
<omgdi:waypoint x="500.0" y="248.0"></omgdi:waypoint>
<omgdi:waypoint x="500.0" y="180.0"></omgdi:waypoint>
<omgdi:waypoint x="570.0" y="180.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowReject" id="BPMNEdge_flowReject">
<omgdi:waypoint x="500.0" y="288.0"></omgdi:waypoint>
<omgdi:waypoint x="500.0" y="368.0"></omgdi:waypoint>
<omgdi:waypoint x="570.0" y="368.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowRequestInfo" id="BPMNEdge_flowRequestInfo">
<omgdi:waypoint x="520.0" y="268.0"></omgdi:waypoint>
<omgdi:waypoint x="570.0" y="268.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowToEventGateway" id="BPMNEdge_flowToEventGateway">
<omgdi:waypoint x="670.0" y="268.0"></omgdi:waypoint>
<omgdi:waypoint x="720.0" y="268.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowToConditionalEvent" id="BPMNEdge_flowToConditionalEvent">
<omgdi:waypoint x="740.0" y="248.0"></omgdi:waypoint>
<omgdi:waypoint x="740.0" y="218.0"></omgdi:waypoint>
<omgdi:waypoint x="810.0" y="218.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowToTimerEvent" id="BPMNEdge_flowToTimerEvent">
<omgdi:waypoint x="740.0" y="288.0"></omgdi:waypoint>
<omgdi:waypoint x="740.0" y="318.0"></omgdi:waypoint>
<omgdi:waypoint x="810.0" y="318.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowBackToApproval" id="BPMNEdge_flowBackToApproval">
<omgdi:waypoint x="828.0" y="200.0"></omgdi:waypoint>
<omgdi:waypoint x="828.0" y="100.0"></omgdi:waypoint>
<omgdi:waypoint x="380.0" y="100.0"></omgdi:waypoint>
<omgdi:waypoint x="380.0" y="238.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowToEndOnTimeout" id="BPMNEdge_flowToEndOnTimeout">
<omgdi:waypoint x="846.0" y="318.0"></omgdi:waypoint>
<omgdi:waypoint x="900.0" y="318.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowToApprovedEnd" id="BPMNEdge_flowToApprovedEnd">
<omgdi:waypoint x="670.0" y="180.0"></omgdi:waypoint>
<omgdi:waypoint x="720.0" y="179.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>