Flowable23手动、接收任务----------持续更新中
1. 什么是手动任务?
手动任务(manualTask)是 BPMN 2.0 规范中定义的一种特殊任务类型。它的核心思想是:这个任务是由流程引擎之外的人或系统来完成的,流程引擎本身对这个任务的执行过程一无所知,也不需要进行任何干预。
你可以把它想象成一个“占位符”或者“提醒项”。当流程执行到手动任务时,它会:
停下来,进入等待状态。
不创建任何像用户任务(User Task)那样的待办事项。
等待外部通过 API 调用来手动**“告知”**流程引擎:“嘿,这个手动任务已经完成了,你可以继续往下走了。”
与用户任务 (User Task) 的核心区别:
用户任务 (User Task):
引擎管理: 由 Flowable 引擎创建和管理。
有待办项: 会在 ACT_RU_TASK 表中生成一条记录,用户可以通过查询待办列表看到它。
需要分配: 通常需要分配给某个用户或用户组 (assignee, candidateUsers 等)。
主动完成: 用户或系统通过调用 taskService.complete(taskId) 来完成任务,需要提供 taskId。
手动任务 (Manual Task):
引擎不管理: 引擎只是知道流程停在这里了,但没有具体的“任务”实体需要管理。
无待办项: 不会生成待办任务,用户在待办列表里看不到它。
无需分配: 没有办理人的概念。
被动触发: 流程的推进不是通过完成一个具体的“task”,而是通过发送一个“信号”给当前流程执行实例(execution)来触发。
2. 手动任务的典型应用场景
手动任务非常适合那些不需要或不方便在 Flowable 内部进行管理的任务,例如:
线下操作:
场景: “邮寄纸质合同”。这个动作是线下的,流程只需要知道合同已经寄出即可继续。
流程: … -> 【手动任务:邮寄合同】 -> …。当操作员在线下寄出合同后,他在一个外部系统(如ERP)中点击一个按钮,该按钮调用 Flowable API,通知流程继续。
依赖外部系统回调:
场景: “等待第三方支付平台回调”。你的系统向支付平台发起了支付请求,需要等待支付平台异步通知支付结果。
流程: … -> 【手动任务:等待支付回调】 -> …。流程停在这里。当你的系统接收到来自支付平台的回调请求后,在处理回调的逻辑中,调用 Flowable API,让流程根据支付结果继续(比如走向“支付成功”或“支付失败”分支)。
非常规的人工操作:
场景: “进行复杂的设备调试”。这个任务可能需要工程师花费数小时甚至数天,并且没有固定的操作界面,不适合做成一个标准的待办任务。
流程: … -> 【手动任务:设备调试】 -> …。调试完成后,工程师在某个地方记录完成,触发流程继续。
3. 如何在 BPMN 中定义手动任务
在流程图中,它通常表示为一个手形图标。在 XML 中,定义非常简单:
<process id="manualTaskProcess" name="Manual Task Process"><startEvent id="start"/><sequenceFlow sourceRef="start" targetRef="myManualTask"/><manualTask id="myManualTask" name="请在线下完成某项操作"/><sequenceFlow sourceRef="myManualTask" targetRef="end"/><endEvent id="end"/>
</process>
- 如何“完成”手动任务 (核心操作)
这是最关键的部分。由于没有 taskId 可以 complete,我们该如何让流程继续呢?
答案是:通过触发执行实例 (Execution) 来继续。
当流程停在手动任务 myManualTask 时,实际上是有一个“执行指针”(Execution)停留在了这个活动上。我们需要找到这个执行实例,并告诉它继续。
代码实现步骤:
启动流程:
// 启动流程,返回流程实例对象
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("manualTaskProcess");
System.out.println("流程已启动,实例ID: " + processInstance.getId());
找到停在手动任务上的执行实例:
流程停下来后,我们可以通过查询 Execution 来找到它。一个流程实例中可能会有多个执行实例(比如在并行网关后),所以我们需要精确地找到停在目标活动(myManualTask)上的那个。
// 查询活动ID为'myManualTask'的执行实例
Execution execution = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).activityId("myManualTask") // 精准定位到手动任务的ID.singleResult(); // 假设只有一个执行实例停在这里if (execution != null) {System.out.println("找到停在手动任务上的执行实例,ID: " + execution.getId());
} else {System.out.println("未找到停在手动任务上的执行实例,可能流程已不在该节点。");return;
}
触发执行实例继续:
使用 RuntimeService 的 trigger 方法
// 触发该执行实例,让流程继续往下走
runtimeService.trigger(execution.getId());System.out.println("手动任务已完成,流程继续...");
trigger 方法会激活这个等待的 execution,使其沿着出向的顺序流继续执行。
验证流程是否结束:
// 检查流程是否已经结束
ProcessInstance finishedInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstance.getId()).singleResult();if (finishedInstance == null) {System.out.println("流程已成功结束。");
}
1. 什么是接收任务?
接收任务(receiveTask)是 BPMN 2.0 中定义的一种等待任务。它的核心作用是:让流程暂停执行,直到接收到一个特定的外部“消息”或“信号”。
你可以把它理解为一个“信号接收站”。当流程执行到接收任务时,它会停下来,像一个雷达一样,专门等待某个预定义的信号。只有当这个信号被外部程序正确地发送给它时,流程才会继续向下执行。
与手动任务 (Manual Task) 的区别:
关注点不同:
手动任务: 关注点是“一件事情做完了”。它不关心这个“完成”的信号是怎么来的,只要有人调用 trigger 告诉它“事情OK了”就行。
接收任务: 关注点是“收到了一个特定的消息”。它更强调消息本身,并且通常与消息事件(Message Event)配合使用,形成一种更结构化的通信模式。
触发机制不同:
手动任务: 通过 runtimeService.trigger(executionId) 触发,需要知道具体的 executionId。
接收任务: 通常通过 runtimeService.messageEventReceived(messageName, executionId) 或 runtimeService.trigger(executionId)(是的,trigger 也可以,但 messageEventReceived 更语义化)来触发。使用消息名(messageName)的方式更常见也更灵活。
语义和用途:
手动任务: 更偏向于表示一个“黑盒”的人工或系统活动。
接收任务: 更偏向于流程间的通信或与外部系统的异步消息交互,语义上更清晰,表明“我正在等待一个消息”。
2. 接收任务的典型应用场景
接收任务非常适合用于实现流程的异步等待和外部事件驱动。
流程间的通信:
场景: 一个“订单流程”需要等待“仓库流程”发货完成。
流程: “订单流程”在某个节点执行到一个【接收任务:等待发货确认】。当“仓库流程”完成发货后,它会发送一个名为 goodsShipped 的消息。“订单流程”的接收任务收到这个消息后,继续执行后续步骤(如通知客户)。
与外部系统集成(回调):
场景: 与手动任务的例子类似,向一个需要异步回调的第三方服务(如文件处理服务)发起请求。
流程: … -> 调用外部服务 -> 【接收任务:等待文件处理结果】 -> …。流程停在这里。当第三方服务处理完文件后,它通过一个约定的接口回调你的系统,你的系统在回调处理逻辑中,向流程发送一个包含处理结果的消息(如 fileProcessed)。
同步状态:
场景: 一个主流程启动了多个并行的子流程,主流程需要等待所有子流程都达到某个特定状态后才能继续。
流程: 每个子流程在达到该状态时,都向主流程发送一个特定的消息。主流程中的接收任务(可能配合并行网关)在收到所有必要的消息后才继续。
3. 如何在 BPMN 中定义接收任务
在流程图中,它通常表示为一个信封图标。在 XML 中,定义如下:
<process id="receiveTaskProcess" name="Receive Task Process"><startEvent id="start"/><sequenceFlow sourceRef="start" targetRef="myReceiveTask"/><receiveTask id="myReceiveTask" name="等待订单更新信号"/><sequenceFlow sourceRef="myReceiveTask" targetRef="end"/><endEvent id="end"/>
</process>
和手动任务一样,定义非常简单。但它通常和“消息事件”结合使用,这样可以利用消息名称来触发,更具解耦性。
4. 如何“完成”接收任务 (核心操作)
完成接收任务,本质上就是向等待的流程实例发送一个信号。有两种主要方式:
方式一:使用 trigger (与手动任务类似)
这种方式最直接,但耦合度稍高,因为你需要知道 executionId。
启动流程并找到执行实例:
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("receiveTaskProcess");// 找到停在接收任务上的执行实例
Execution execution = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).activityId("myReceiveTask").singleResult();
触发执行:
if (execution != null) {// 传递一些变量(可选)Map<String, Object> variables = new HashMap<>();variables.put("orderStatus", "PAID");runtimeService.trigger(execution.getId(), variables);System.out.println("接收任务已触发,流程继续...");
}
方式二:使用消息事件 messageEventReceived (推荐,更灵活)
这种方式是接收任务最经典和最语义化的用法。它允许你通过一个消息名称来触发,而不需要关心具体的 executionId。这使得外部系统可以非常方便地与流程解耦。
BPMN 定义需要稍作修改,关联一个消息定义:
<definitions ...><!-- 1. 定义一个全局的消息 --><message id="orderUpdateMsg" name="orderUpdateMessage" /><process id="receiveTaskProcess" name="Receive Task Process"><startEvent id="start"/><sequenceFlow sourceRef="start" targetRef="myReceiveTask"/><!-- 2. 让接收任务引用这个消息 --><receiveTask id="myReceiveTask" name="等待订单更新信号" messageRef="orderUpdateMsg"/><sequenceFlow sourceRef="myReceiveTask" targetRef="end"/><endEvent id="end"/></process>
</definitions>
代码实现:
启动流程: (同上)
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("receiveTaskProcess");
找到等待该消息的执行实例:
这一步是为了演示,在实际的外部回调场景中,你可能只有一个 processInstanceId 或业务Key,而不是直接去查 execution。
Execution execution = runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).messageEventSubscriptionName("orderUpdateMessage") // 通过消息名称查询订阅.singleResult();// 外部系统通常不知道 executionId,但它可以通过业务Key找到流程实例,再找到execution
// 或者,如果消息名称在整个引擎中是唯一的,甚至可以不提供executionId
发送消息来触发:
使用 runtimeService.messageEventReceived(messageName, executionId, processVariables)。
if (execution != null) {String messageName = "orderUpdateMessage";Map<String, Object> variables = new HashMap<>();variables.put("orderStatus", "SHIPPED");// 向特定的执行实例发送消息runtimeService.messageEventReceived(messageName, execution.getId(), variables);System.out.println("通过消息 '" + messageName + "' 触发了接收任务,流程继续...");
}
更解耦的做法:在某些情况下,如果你能保证某个消息名称只被一个流程实例订阅,你甚至可以不传 executionId,Flowable 会自己去找。但传递 executionId 是最保险的做法。