BPMN2.0,flowable工作流,多实例【用户任务】的实现
本文介绍了基于Flowable工作流引擎实现多级领导审批功能的配置方案。系统采用BPMN2.0前端和Flowable 6.8.0后端,重点解决了两个核心需求:1)支持多审批人顺序或并行审批;2)动态设置审批人。技术实现上通过用户任务的多实例配置(isSequential控制顺序/并行)和执行监听器(FlowExecutionListener)动态修改审批人。
·
目录
1、环境
前端:BPMN2.0.js
后端:flowable:6.8.0
2、流程信息
流程图(7、流程文件在文章最后):

各节点信息:
| 节点名称 | 节点id |
|---|---|
| 开始 | ks |
| 登记 | dj |
| 员工 | yg |
| 领导审批 | ldsp |
| 结束 | js |
3、需求
- 【领导审批】节点需要多个审批人顺序审批或需要多个审批人都进行审批。
- 【领导审批】节点的多个审批人需要动态设置。
4、思路
- 针对第一个需求,使用的是【用户任务】的多实例。
- 针对第二个需求,在使用【用户任务】的多实例情况下使用【执行监听器】。
5、【领导审批】节点配置
【领导审批】节点xml配置
<bpmn2:userTask id="ldsp" name="领导审批" flowable:dataType="USERS" flowable:assignee="${assignee}" flowable:candidateUsers="分管领导001,部门领导001" flowable:text="分管领导001,部门领导001">
<bpmn2:extensionElements>
<flowable:executionListener class="com.cn.workflow.flowable.listener.FlowExecutionListener" event="start" />
</bpmn2:extensionElements>
<bpmn2:incoming>sq</bpmn2:incoming>
<bpmn2:outgoing>Flow_13elc6g</bpmn2:outgoing>
<bpmn2:outgoing>th</bpmn2:outgoing>
<bpmn2:multiInstanceLoopCharacteristics isSequential="false" flowable:collection="${multiInstanceHandler.getUserNames(execution)}" flowable:elementVariable="assignee">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">${nrOfCompletedInstances >= nrOfInstances}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
说明:
满足需求1的配置:
- flowable:assignee="${assignee}":${assignee}里面的名称要跟flowable:elementVariable="assignee"的值一样。
- isSequential="true":true表示顺序执行,false表示并行执行。
- ${nrOfCompletedInstances >= nrOfInstances}:表示完成条件为全部实例完成。
- flowable:candidateUsers="分管领导001,部门领导001":表示预设处理人为分管领导001,部门领导001。
- flowable:collection="${multiInstanceHandler.getUserNames(execution)}":表示处理人的集合,这里调用了java代码(multiInstanceHandler.getUserNames方法)和结合flowable:candidateUsers预设的值返回具体的处理人。下面会贴上代码。
满足需求2的配置:
在上面的配置基础上,再加上【执行监听器】,配置如下
<bpmn2:extensionElements>
<flowable:executionListener class="com.cn.workflow.flowable.listener.FlowExecutionListener" event="start" />
</bpmn2:extensionElements>
6、代码实现
代码由开源项目做二次修改:https://gitee.com/nbacheng/ruoyi-nbcio/tree/master/
multiInstanceHandler.getUserNames代码:
@Component("multiInstanceHandler")
public class MultiInstanceHandler {
@Autowired
private RemoteUserService remoteUserService;
@Autowired
private RemoteDeptService remoteDeptService;
public Set<String> getUserNames(DelegateExecution execution) {
Set<String> candidateUserNames = new LinkedHashSet<>();
FlowElement flowElement = execution.getCurrentFlowElement();
if (ObjectUtil.isNotEmpty(flowElement) && flowElement instanceof UserTask) {
UserTask userTask = (UserTask) flowElement;
String dataType = userTask.getAttributeValue( "http://flowable.org/bpmn", "dataType");
if ("USERS".equals(dataType) && CollUtil.isNotEmpty(userTask.getCandidateUsers())) {
// 添加候选用户
candidateUserNames.addAll(userTask.getCandidateUsers());
} else if (CollUtil.isNotEmpty(userTask.getCandidateGroups())) {
// 以下功能可以参考,具体逻辑按各自的来处理
// 获取组的ID,角色ID集合或部门ID集合
List<Long> groups = userTask.getCandidateGroups().stream()
.map(item -> Long.parseLong(item.substring(4)))
.collect(Collectors.toList());
List<Long> userIds = new ArrayList<>();
if ("ROLES".equals(dataType)) {
// 通过角色id,获取所有用户id集合
R<List<Long>> listR = remoteUserService.selectUserIdsByRoleIds(groups, SecurityConstants.INNER);
userIds = listR.getCode() == R.SUCCESS ? listR.getData() : new ArrayList<>();
} else if ("DEPTS".equals(dataType)) {
// 通过部门id,获取所有用户id集合
R<List<Long>> listR = remoteDeptService.selectUserIdsByDeptIds(groups, SecurityConstants.INNER);
userIds = listR.getCode() == R.SUCCESS ? listR.getData() : new ArrayList<>();
}
// 添加候选用户
R<List<String>> listR = remoteUserService.selectUserNames(userIds, SecurityConstants.INNER);
if(listR.getCode() == R.SUCCESS){
List<String> userNames = listR.getData();
userNames.forEach(userName -> candidateUserNames.add(userName));
}else{
throw new ServiceException("候选用户不能为空!");
}
}
}
return candidateUserNames;
}
}
FlowExecutionListener代码:
如果配置了【执行监听器】,那么程序会先执行FlowExecutionListener,再执行multiInstanceHandler.getUserNames
@Component(value = "flowExecutionListener")
public class FlowExecutionListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) {
FlowElement flowElement = execution.getCurrentFlowElement();
if(ObjectUtil.isNotEmpty(flowElement) && flowElement instanceof UserTask){
UserTask userTask = (UserTask) execution.getCurrentFlowElement();
//这里只做简单的设置办理人
//还可以设置办理组、改变变量dataType的值等
userTask.setCandidateUsers(Arrays.asList("技术部分管领导001","技术部分管领导002","技术部主管领导001"));
}
System.out.println("执行监听器:{}"+ execution);
}
}
7、流程文件
flowable.xml:
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="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:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" id="diagram_Process_1761616908502" targetNamespace="http://flowable.org/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="Process_1761616908502" name="审批流程" isExecutable="true">
<bpmn2:startEvent id="ks" name="开始">
<bpmn2:outgoing>Flow_0zttbj2</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:userTask id="yg" name="员工" flowable:dataType="INITIATOR" flowable:assignee="${initiator}" flowable:text="流程发起人">
<bpmn2:incoming>fq</bpmn2:incoming>
<bpmn2:incoming>th</bpmn2:incoming>
<bpmn2:outgoing>sq</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:userTask id="ldsp" name="领导审批" flowable:dataType="USERS" flowable:assignee="${assignee}" flowable:candidateUsers="分管领导001,部门领导001" flowable:text="分管领导001,部门领导001">
<bpmn2:extensionElements>
<flowable:executionListener class="com.cn.workflow.flowable.listener.FlowExecutionListener" event="start" />
</bpmn2:extensionElements>
<bpmn2:incoming>sq</bpmn2:incoming>
<bpmn2:outgoing>Flow_13elc6g</bpmn2:outgoing>
<bpmn2:outgoing>th</bpmn2:outgoing>
<bpmn2:multiInstanceLoopCharacteristics flowable:collection="${multiInstanceHandler.getUserNames(execution)}" flowable:elementVariable="assignee">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">${nrOfCompletedInstances >= nrOfInstances}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
<bpmn2:endEvent id="js" name="结束">
<bpmn2:incoming>Flow_13elc6g</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="Flow_13elc6g" name="提交" sourceRef="ldsp" targetRef="js">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${nextNodeId=="js"}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="fq" name="发起" sourceRef="dj" targetRef="yg" />
<bpmn2:sequenceFlow id="sq" name="申请" sourceRef="yg" targetRef="ldsp" />
<bpmn2:sequenceFlow id="th" name="退回" sourceRef="ldsp" targetRef="yg">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${nextNodeId=="yg"}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:userTask id="dj" name="登记" flowable:dataType="INITIATOR" flowable:assignee="${initiator}" flowable:text="流程发起人">
<bpmn2:incoming>Flow_0zttbj2</bpmn2:incoming>
<bpmn2:outgoing>fq</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="Flow_0zttbj2" sourceRef="ks" targetRef="dj" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1761616908502">
<bpmndi:BPMNEdge id="Flow_0zttbj2_di" bpmnElement="Flow_0zttbj2">
<di:waypoint x="38" y="239" />
<di:waypoint x="110" y="239" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="th_di" bpmnElement="th">
<di:waypoint x="550" y="199" />
<di:waypoint x="550" y="170" />
<di:waypoint x="336" y="170" />
<di:waypoint x="336" y="199" />
<bpmndi:BPMNLabel>
<dc:Bounds x="432" y="152" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="sq_di" bpmnElement="sq">
<di:waypoint x="386" y="239" />
<di:waypoint x="500" y="239" />
<bpmndi:BPMNLabel>
<dc:Bounds x="432" y="221" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="fq_di" bpmnElement="fq">
<di:waypoint x="210" y="239" />
<di:waypoint x="286" y="239" />
<bpmndi:BPMNLabel>
<dc:Bounds x="235" y="221" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_13elc6g_di" bpmnElement="Flow_13elc6g">
<di:waypoint x="600" y="239" />
<di:waypoint x="682" y="239" />
<bpmndi:BPMNLabel>
<dc:Bounds x="635" y="217" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="ks_di" bpmnElement="ks">
<dc:Bounds x="2" y="221" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="10" y="264" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="yg_di" bpmnElement="yg">
<dc:Bounds x="286" y="199" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ldsp_di" bpmnElement="ldsp">
<dc:Bounds x="500" y="199" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="js_di" bpmnElement="js">
<dc:Bounds x="682" y="221" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="691" y="264" width="22" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="dj_di" bpmnElement="dj">
<dc:Bounds x="110" y="199" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)