💖专栏简介
✔️本专栏将从Camunda(卡蒙达) 7中的关键概念到实现中国式工作流相关功能。
✔️文章中只包含演示核心代码及测试数据,完整代码可查看作者的开源项目snail-camunda
✔️请给snail-camunda 点颗星吧😘
💖Execution Listener
执行监听器可以在流程执行期间发生某些事件时执行额外的Java代码或者计算表达式,这些事件包含:
- 流程的开始与结束
- 活动的开始与结束
- 网关的开始与结束
- 中间事件的开始与结束
执行监听器的触发事件有:
- start
- end
- take
其中节点有start、end两种事件,而连线则有take事件。连线也称之为过渡,take事件也就是在进行过渡
针对监听器类型首先演示一下Expression,在Start Event节点设置执行监听器。
与其他表达式一样,执行变量可以被解析使用。因为执行实现对象有一个公开事件名称的属性,所以可以使用execution. eventName将事件名称传递给方法。
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1z0li53" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.19.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.20.0">
<bpmn:process id="Process_0ic6df5" isExecutable="true" camunda:historyTimeToLive="180">
<bpmn:extensionElements />
<bpmn:startEvent id="StartEvent_1">
<bpmn:extensionElements>
<camunda:executionListener expression="${StartEvent.hello(execution.eventName)}" event="start" />
</bpmn:extensionElements>
<bpmn:outgoing>Flow_16zen96</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_16zen96" sourceRef="StartEvent_1" targetRef="Activity_0im4bve">
<bpmn:extensionElements />
</bpmn:sequenceFlow>
<bpmn:endEvent id="Event_1w6waod">
<bpmn:extensionElements />
<bpmn:incoming>Flow_1yfgw6n</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1yfgw6n" sourceRef="Activity_0im4bve" targetRef="Event_1w6waod" />
<bpmn:userTask id="Activity_0im4bve" name="审批人" camunda:assignee="${assignee}">
<bpmn:extensionElements />
<bpmn:incoming>Flow_16zen96</bpmn:incoming>
<bpmn:outgoing>Flow_1yfgw6n</bpmn:outgoing>
</bpmn:userTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0ic6df5">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1w6waod_di" bpmnElement="Event_1w6waod">
<dc:Bounds x="432" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_02axx51_di" bpmnElement="Activity_0im4bve">
<dc:Bounds x="270" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_16zen96_di" bpmnElement="Flow_16zen96">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1yfgw6n_di" bpmnElement="Flow_1yfgw6n">
<di:waypoint x="370" y="117" />
<di:waypoint x="432" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
在代码中如下处理
@Slf4j
@Component("StartEvent")
public class StartEvent {
public void hello(String eventName){
log.info("挑战Camunda,eventName--{}",eventName);
}
}
发起流程实力后:
💖Task Listener
任务监听器的触发事件类型如下:
- create :在create事件之前不会触发其他与任务相关的事件
- assignment:专门跟踪assignee 属性的更改
- complete:在任务从运行时数据中删除之前,发生complete事件,该事件的成功执行代表任务事件生命周期的结束。
- delete:删除事件发生在从运行时数据中删除任务之前,在delete事件之后不会触发其他事件,因为它也代表任务事件生命周期的结束。这意味着delete事件与complete事件是互斥的
- update:当修改任务属性时触发,任务属性包含分配人,所有者,注释,任务局部变量等
- timeout:当与此任务监听器关联的计时器到期时,将发生超时事件。注意,这需要定义一个Timer。
设计流程定义,在用户任务节点设置任务监听器的六种事件
这里比较特殊的是Timeout,此处的ID就是唯一标识符,仅在事件设置为timeout时才需要。
选择不同的Type就要设置不同格式的值:
- Date: 格式为ISO 8601格式的固定时间和日期 比如 2024-03-11T12:13:14Z
- Duration:两种格式分别为PnYnMnDTnHnMnS、PnW。比如P10D 【间隔10天】
- Cycle:两种格式分别为ISO 8601重复间隔标准规定的重复持续时间格式,比如R3/PT10H【3 次重复间隔,每次持续 10 小时】、第二种就是cron表达式
注意下面流程定义中的任务监听器 Java class路径 一定要根据自己的项目做调整
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1s60m5d" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.19.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.20.0">
<bpmn:process id="Process_144a42e" isExecutable="true" camunda:historyTimeToLive="180">
<bpmn:startEvent id="StartEvent_1">
<bpmn:extensionElements />
<bpmn:outgoing>Flow_1qgw3o5</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1qgw3o5" sourceRef="StartEvent_1" targetRef="Activity_0gpxgwy">
<bpmn:extensionElements />
</bpmn:sequenceFlow>
<bpmn:userTask id="Activity_0gpxgwy" name="测试任务监听器" camunda:assignee="${assignee}">
<bpmn:extensionElements>
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="create" />
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="assignment" />
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="complete" />
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="delete" />
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="update" />
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="timeout" id="listenerOne">
<bpmn:timerEventDefinition id="TimerEventDefinition_1kyckfx">
<bpmn:timeCycle xsi:type="bpmn:tFormalExpression">5 * * * * ?</bpmn:timeCycle>
</bpmn:timerEventDefinition>
</camunda:taskListener>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1qgw3o5</bpmn:incoming>
<bpmn:outgoing>Flow_0589xmv</bpmn:outgoing>
<bpmn:multiInstanceLoopCharacteristics camunda:collection="${assigneeList}" camunda:elementVariable="assignee">
<bpmn:completionCondition xsi:type="bpmn:tFormalExpression">${nrOfCompletedInstances == 1}</bpmn:completionCondition>
</bpmn:multiInstanceLoopCharacteristics>
</bpmn:userTask>
<bpmn:endEvent id="Event_0ybej7n">
<bpmn:incoming>Flow_0589xmv</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0589xmv" sourceRef="Activity_0gpxgwy" targetRef="Event_0ybej7n" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_144a42e">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_087xqfi_di" bpmnElement="Activity_0gpxgwy">
<dc:Bounds x="270" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0ybej7n_di" bpmnElement="Event_0ybej7n">
<dc:Bounds x="432" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1qgw3o5_di" bpmnElement="Flow_1qgw3o5">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0589xmv_di" bpmnElement="Flow_0589xmv">
<di:waypoint x="370" y="117" />
<di:waypoint x="432" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
相关代码:
/**
* 流程实例相关接口
*
* @author lonewalker
*/
@RequestMapping("/process/instance")
@RequiredArgsConstructor
@RestController
public class ProcessInstanceController {
private final RuntimeService runtimeService;
private final TaskService taskService;
/**
* 根据流程定义key发起流程实例
*
* @param requestParam 请求参数
* @return 流程实例id
*/
@PostMapping("/startProcessInstanceByKey")
public String startProcessInstanceByKey(@RequestBody StartProcessRequest requestParam) {
Map<String, Object> paramMap = new HashMap<>(8);
List<String> assigneeList = new ArrayList<>();
assigneeList.add("10086");
assigneeList.add("10087");
assigneeList.add("10088");
paramMap.put("assigneeList", assigneeList);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(requestParam.getProcessDefinitionKey(), requestParam.getBusinessKey(), paramMap);
return processInstance.getProcessInstanceId();
}
/**
* 完成单个任务
*
* @param requestParam 请求参数
* @return {@code true 成功}
*/
@PostMapping("/completeSingleTask")
public Boolean completeSingleTask(@RequestBody @Validated CompleteTaskRequest requestParam) {
taskService.complete(requestParam.getTaskId());
return true;
}
/**
* 转交任务
*
* @param requestParam 请求参数
* @return {@code true 成功}
*/
@PostMapping("/transferTask")
public Boolean transferTask(@RequestBody TransferTaskRequest requestParam){
taskService.setAssignee(requestParam.getTaskId(), requestParam.getUserId());
return true;
}
}
各位按顺序看过来的,所以部分前文已有的参数类就不贴代码了
@Data
public class TransferTaskRequest {
private String taskId;
private String userId;
}
自定义任务监听器:
@Slf4j
@Component
public class CustomTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
log.info("Event Type--{}",delegateTask.getEventName());
}
}
发起流程实例后,等待超过设置的时间,控制台打印如下:
调用新增的【转交任务】接口,可以看到又执行了update和assignment,符合预期。
由于我们设置的是三个人或签,当调用完成任务接口后:第一个任务触发complete,另外两个触发delete
💖动态获取审批人
实际项目中可能会设置节点审批人是角色,那这个角色对应的人是动态变化的,假如流程被驳回到中间的某个节点再回到这个节点,需要保证获取角色对应的人员是最新的。
前文的演示中都是在发起流程实例时直接传入审批人参数,这里演示一下如何通过执行监听器 take获取审批人,介绍Execution Listener时也提到take是在连线【过渡】上设置的。当然也有朋友疑惑为何不在Task Listener上设置审批人,首先我们知道assignment事件是在assignee属性变更时触发,此时我们要设置assignee肯定要在它之前就要拿到值,那还剩个create事件,我们测试一下:
在执行监听器完成后就报错了
也就是一定要在创建任务之前就拿到审批人数据,流程引擎是根据传入的数据以及配置来判断创建几个任务
修改流程定义,在用户任务节点前的连线【过渡】上设置执行监听器take事件
我的路径是com.lonewalker.demo.listener.CustomExecutionListener,各位根据自己的项目修改
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1s60m5d" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.19.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.20.0">
<bpmn:process id="Process_144a42e" isExecutable="true" camunda:historyTimeToLive="180">
<bpmn:startEvent id="StartEvent_1">
<bpmn:extensionElements />
<bpmn:outgoing>Flow_1qgw3o5</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1qgw3o5" sourceRef="StartEvent_1" targetRef="Activity_0gpxgwy">
<bpmn:extensionElements>
<camunda:executionListener class="com.lonewalker.demo.listener.CustomExecutionListener" event="take" />
</bpmn:extensionElements>
</bpmn:sequenceFlow>
<bpmn:userTask id="Activity_0gpxgwy" name="测试任务监听器" camunda:assignee="${assignee}">
<bpmn:extensionElements>
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="create" />
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="assignment" />
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="complete" />
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="delete" />
<camunda:taskListener class="com.lonewalker.demo.listener.CustomTaskListener" event="update" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_1qgw3o5</bpmn:incoming>
<bpmn:outgoing>Flow_0589xmv</bpmn:outgoing>
<bpmn:multiInstanceLoopCharacteristics camunda:collection="${assigneeList}" camunda:elementVariable="assignee">
<bpmn:completionCondition xsi:type="bpmn:tFormalExpression">${nrOfCompletedInstances == 1}</bpmn:completionCondition>
</bpmn:multiInstanceLoopCharacteristics>
</bpmn:userTask>
<bpmn:endEvent id="Event_0ybej7n">
<bpmn:incoming>Flow_0589xmv</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0589xmv" sourceRef="Activity_0gpxgwy" targetRef="Event_0ybej7n" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_144a42e">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_087xqfi_di" bpmnElement="Activity_0gpxgwy">
<dc:Bounds x="270" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0ybej7n_di" bpmnElement="Event_0ybej7n">
<dc:Bounds x="432" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1qgw3o5_di" bpmnElement="Flow_1qgw3o5">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0589xmv_di" bpmnElement="Flow_0589xmv">
<di:waypoint x="370" y="117" />
<di:waypoint x="432" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
代码中将发起流程实例时传入的审批人参数转到执行监听器中:
@Slf4j
@Component
public class CustomExecutionListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) {
log.info("【执行监听器】Event Type--{}", execution.getEventName());
List<String> assigneeList = new ArrayList<>();
assigneeList.add("10086");
assigneeList.add("10087");
assigneeList.add("10088");
execution.setVariable("assigneeList", assigneeList);
}
}
重新部署、发起流程实例
💖扩展
网上很多人说Task Linstener的assignment事件是在create之前,根据我们的案例演示很明显不是