下面,我将以我们之前创建的“员工请假流程”为例,分步详解如何操作一个流程。
具体BPMN文件如下 :
<?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:flowable="http://flowable.org/bpmn" 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" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef" exporter="Flowable Open Source Modeler" exporterVersion="6.8.0">
<process id="leaveRequestProcess" name="员工请假流程" isExecutable="true">
<startEvent id="startEvent" name="开始" flowable:initiator="initiator"></startEvent>
<userTask id="submitLeaveRequestTask" name="提交请假申请" flowable:assignee="${initiator}" flowable:formKey="leave-form">
<documentation>员工填写并提交请假表单,包含请假天数、事由等信息。</documentation>
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<userTask id="managerApprovalTask" name="经理审批" flowable:candidateGroups="managers" flowable:formKey="approval-form">
<documentation>直属经理审核请假申请。完成后需要设置流程变量 'approved' (布尔值: true 或 false)。</documentation>
</userTask>
<exclusiveGateway id="approvalDecisionGateway" name="审批决定"></exclusiveGateway>
<serviceTask id="notifyRejectionTask" name="通知申请人结果" flowable:class="com.example.flowable.delegate.NotifyApplicantDelegate">
<documentation>当申请被驳回时,自动向申请人发送通知。</documentation>
</serviceTask>
<endEvent id="endEventApproved" name="流程结束 (批准)"></endEvent>
<endEvent id="endEventRejected" name="流程结束 (驳回)"></endEvent>
<sequenceFlow id="flow1" sourceRef="startEvent" targetRef="submitLeaveRequestTask"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="submitLeaveRequestTask" targetRef="managerApprovalTask"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="managerApprovalTask" targetRef="approvalDecisionGateway"></sequenceFlow>
<sequenceFlow id="flowToApproved" name="批准" sourceRef="approvalDecisionGateway" targetRef="endEventApproved">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${approved == true}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flowToRejected" name="驳回" sourceRef="approvalDecisionGateway" targetRef="notifyRejectionTask">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${approved == false}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flowAfterRejection" sourceRef="notifyRejectionTask" targetRef="endEventRejected"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leaveRequestProcess">
<bpmndi:BPMNPlane bpmnElement="leaveRequestProcess" id="BPMNPlane_leaveRequestProcess">
<bpmndi:BPMNShape bpmnElement="startEvent" id="BPMNShape_startEvent">
<omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="submitLeaveRequestTask" id="BPMNShape_submitLeaveRequestTask">
<omgdc:Bounds height="80.0" width="100.0" x="175.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="managerApprovalTask" id="BPMNShape_managerApprovalTask">
<omgdc:Bounds height="80.0" width="100.0" x="325.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="approvalDecisionGateway" id="BPMNShape_approvalDecisionGateway">
<omgdc:Bounds height="40.0" width="40.0" x="475.0" y="158.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="notifyRejectionTask" id="BPMNShape_notifyRejectionTask">
<omgdc:Bounds height="80.0" width="100.0" x="565.0" y="230.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endEventApproved" id="BPMNShape_endEventApproved">
<omgdc:Bounds height="28.0" width="28.0" x="600.0" y="164.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endEventRejected" id="BPMNShape_endEventRejected">
<omgdc:Bounds height="28.0" width="28.0" x="715.0" y="256.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flowToRejected" id="BPMNEdge_flowToRejected" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="495.0" y="197.93916938110752"></omgdi:waypoint>
<omgdi:waypoint x="495.0" y="270.0"></omgdi:waypoint>
<omgdi:waypoint x="565.0" y="270.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="129.9499984899576" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="174.9999999999917" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="274.9499999999581" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="324.9999999999364" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.0" flowable:targetDockerY="20.0">
<omgdi:waypoint x="424.95000000000005" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="475.0" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowAfterRejection" id="BPMNEdge_flowAfterRejection" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="664.949999999996" y="270.0"></omgdi:waypoint>
<omgdi:waypoint x="715.0" y="270.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flowToApproved" id="BPMNEdge_flowToApproved" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="514.9416246851385" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="600.0" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
现在,我们来一步步走完“员工请假流程”。
步骤 1:启动一个流程实例
当一个员工想要请假时,我们就需要为他启动一个新的流程实例。
使用服务:RuntimeService
关键方法:startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables)
processDefinitionKey: 流程定义的 id,在我们的 BPMN 文件中是 leaveRequestProcess。
variables: 一个 Map,用于传入流程变量 (Process Variables)。这些变量在整个流程实例的生命周期中都可用。
假设我们创建一个 REST 接口来启动流程。
@RestController
@RequestMapping("/leave")
public class LeaveProcessController {
@Autowired
private RuntimeService runtimeService;
@PostMapping("/start")
public String startLeaveProcess(@RequestParam String userId) {
// 设置流程变量
Map<String, Object> variables = new HashMap<>();
// BPMN中定义的 flowable:initiator="initiator" 会自动将发起人ID存入此变量
// 这里我们也可以手动设置,或者设置其他业务变量
variables.put("initiator", userId);
variables.put("reason", "家庭急事需要请假3天"); // 比如请假事由
// 使用流程定义的 Key 启动流程
// Key 就是 BPMN 文件中 <process> 标签的 id 属性
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leaveRequestProcess", variables);
return "请假流程已成功启动,流程实例ID: " + processInstance.getId();
}
}
这样我们的流程就已经启动。
步骤 2:查询并处理用户任务
现在,流程停在了某个用户的待办事项中。用户需要查询属于自己的任务,并完成它。
使用服务:TaskService
关键方法:createTaskQuery()
示例代码:
查询指定用户的待办任务列表。
@GetMapping("/tasks")
public List<Map<String, String>> getMyTasks(@RequestParam String userId) {
// 创建任务查询
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee(userId) // 查询分配给该用户的任务
.orderByTaskCreateTime().desc() // 按创建时间降序排序
.list(); // 获取列表
// 简化返回结果
return tasks.stream().map(task -> {
Map<String, String> map = new HashMap<>();
map.put("taskId", task.getId());
map.put("taskName", task.getName());
return map;
}).collect(Collectors.toList());
}
在“经理审批”节点,我们设置的是 candidateGroups=“managers”。这意味着任务属于 managers 组,组内任何人都看得到,但需要先认领 (Claim) 才能处理。(ps:这里先不需要理解组整个概念,下章节主要讲解整个地方)
// 假设一个经理 'managerA' 决定处理这个任务
taskService.claim(taskId, "managerA");
认领后,这个任务的 assignee 就变成了 managerA,其他经理就无法再处理了。
完成任务是推动流程前进的关键。
使用服务:TaskService
关键方法:complete(String taskId, Map<String, Object> variables)
示例代码:
经理审批并完成任务。这一步至关重要,因为他需要设置流程变量 approved 来驱动后续的排他网关。
@PostMapping("/tasks/{taskId}/complete")
public String completeTask(@PathVariable String taskId, @RequestParam boolean approved) {
// 检查任务是否存在
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == "null") {
return "任务不存在或已被处理";
}
// 设置流程变量,用于网关判断
Map<String, Object> variables = new HashMap<>();
variables.put("approved", approved);
// 完成任务,并传入流程变量
taskService.complete(taskId, variables);
return "任务 " + taskId + " 已成功处理!";
}
当经理调用这个接口并传入 approved=true 时,流程引擎在完成任务后,会根据条件 ${approved == true} 让流程走向“批准”路径;反之,则走向“驳回”路径。
步骤 3:查询流程历史
当流程结束后(无论是批准还是驳回),它就从“运行时”数据变成了“历史”数据。我们可以通过 HistoryService 来查询。
使用服务:HistoryService
关键方法:createHistoricProcessInstanceQuery()
示例代码:
查询某个已完成的流程实例信息。
@GetMapping("/history/{processInstanceId}")
public HistoricProcessInstance getHistory(@PathVariable String processInstanceId) {
return historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.finished() // 确保查询的是已完成的实例
.singleResult(); // 获取单个结果
}
操作一个流程的本质,就是通过调用 Flowable 提供的各种 Service API,来与流程引擎进行交互。整个过程可以概括为:
启动:使用 RuntimeService 根据流程定义的 Key 启动一个流程实例,并可以传入初始的流程变量。
推进:使用 TaskService 查询 分配给用户或用户组的任务,然后完成它,同时可以传入新的流程变量来影响流程走向。
查询:在流程运行期间使用 RuntimeService 查询状态,在流程结束后使用 HistoryService 查询执行记录。