💖专栏简介
✔️本专栏将从Camunda(卡蒙达) 7中的关键概念到实现中国式工作流相关功能。
✔️文章中只包含演示核心代码及测试数据,完整代码可查看作者的开源项目snail-camunda
✔️请给snail-camunda 点颗星吧😘
💖说在前面
请无视标题,无论是或签还是比例签都是会签,只是该节点通过的规则不同。本文将演示会签通过的三种规则:【所有人审批通过】、【一人审批通过】、【按比例投票】。
注意:设置的是通过规则,对于驳回操作均为一人驳回则驳回。
💖设计流程定义
在《认识BPMN2.0》中提及用户任务可以直接分配给单个用户、用户列表或组列表,本文将演示分配给单个用户、用户列表两种方式,用户组在后续文章中也会演示。
以下变量名是在整个过程中比较重要的,结合示例理解并使用:
- nrOfInstances : 实例总数
- nrOfActiveInstances:当前活动的实例的数量。对于串行而言该值始终为1
- nrOfCompletedInstances:已经完成的实例数
- loopCounter :循环计数器
- Loop cardinality:循环基数
- Collection:会签人数的集合
- Element variable:变量元素。选择Collection时必选,为collection集合每次遍历的元素。
- Completion condition:完成条件
用户任务分配给单个用户可按如下图所示设置:
一人通过 通常被称为【或签】,设置完成条件:
${nrOfCompletedInstances == 1}
还需注意变量元素名和Assignee中设置的变量名保持一致。类似于在Java中的fori循环,变量名是i,使用该变量时也应该用i。
比例通过和全部通过就不截图了,完成条件分别改为:
//已完成的实例数占总实例数的三成以上就算通过
${nrOfCompletedInstances/nrOfInstances > 0.3}
//已完成的实例数 等于 总实例数才算通过
${nrOfCompletedInstances == nrOfInstances}
好吧,直接把流程定义放上来:
<?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_1o78fuh" 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_19w1rrm" isExecutable="true" camunda:historyTimeToLive="180"><bpmn:startEvent id="StartEvent_1"><bpmn:outgoing>Flow_0g1nmt1</bpmn:outgoing></bpmn:startEvent><bpmn:sequenceFlow id="Flow_0g1nmt1" sourceRef="StartEvent_1" targetRef="root" /><bpmn:userTask id="root" name="发起人" camunda:assignee="${initiator}"><bpmn:incoming>Flow_0g1nmt1</bpmn:incoming><bpmn:outgoing>Flow_178rknz</bpmn:outgoing></bpmn:userTask><bpmn:sequenceFlow id="Flow_178rknz" sourceRef="root" targetRef="Activity_0163wxf" /><bpmn:userTask id="Activity_0163wxf" name="一人通过" camunda:assignee="${assignee}"><bpmn:incoming>Flow_178rknz</bpmn:incoming><bpmn:outgoing>Flow_1t1uand</bpmn:outgoing><bpmn:multiInstanceLoopCharacteristics camunda:collection="${userOneList}" camunda:elementVariable="assignee"><bpmn:completionCondition xsi:type="bpmn:tFormalExpression">${nrOfCompletedInstances == 1}</bpmn:completionCondition></bpmn:multiInstanceLoopCharacteristics></bpmn:userTask><bpmn:sequenceFlow id="Flow_1t1uand" sourceRef="Activity_0163wxf" targetRef="Activity_1hgbacv" /><bpmn:userTask id="Activity_1hgbacv" name="比例通过" camunda:assignee="${assignee}"><bpmn:incoming>Flow_1t1uand</bpmn:incoming><bpmn:outgoing>Flow_1giv9ue</bpmn:outgoing><bpmn:multiInstanceLoopCharacteristics camunda:collection="${userTwoList}" camunda:elementVariable="assignee"><bpmn:completionCondition xsi:type="bpmn:tFormalExpression">${nrOfCompletedInstances/nrOfInstances > 0.3 }</bpmn:completionCondition></bpmn:multiInstanceLoopCharacteristics></bpmn:userTask><bpmn:userTask id="Activity_14cwgmh" name="全部通过" camunda:assignee="${assignee}"><bpmn:incoming>Flow_1giv9ue</bpmn:incoming><bpmn:outgoing>Flow_0xb5gog</bpmn:outgoing><bpmn:multiInstanceLoopCharacteristics camunda:collection="${userThreeList}" camunda:elementVariable="assignee"><bpmn:completionCondition xsi:type="bpmn:tFormalExpression">${nrOfCompletedInstances == nrOfInstances}</bpmn:completionCondition></bpmn:multiInstanceLoopCharacteristics></bpmn:userTask><bpmn:endEvent id="Event_0a7muzc"><bpmn:incoming>Flow_0xb5gog</bpmn:incoming></bpmn:endEvent><bpmn:sequenceFlow id="Flow_0xb5gog" sourceRef="Activity_14cwgmh" targetRef="Event_0a7muzc" /><bpmn:sequenceFlow id="Flow_1giv9ue" sourceRef="Activity_1hgbacv" targetRef="Activity_14cwgmh" /></bpmn:process><bpmndi:BPMNDiagram id="BPMNDiagram_1"><bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_19w1rrm"><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_0fh3xaa_di" bpmnElement="root"><dc:Bounds x="270" y="77" width="100" height="80" /><bpmndi:BPMNLabel /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Activity_06ae8hl_di" bpmnElement="Activity_0163wxf"><dc:Bounds x="430" y="77" width="100" height="80" /><bpmndi:BPMNLabel /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Activity_0edrq19_di" bpmnElement="Activity_1hgbacv"><dc:Bounds x="590" y="77" width="100" height="80" /><bpmndi:BPMNLabel /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Activity_1j25q3g_di" bpmnElement="Activity_14cwgmh"><dc:Bounds x="760" y="77" width="100" height="80" /><bpmndi:BPMNLabel /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Event_0a7muzc_di" bpmnElement="Event_0a7muzc"><dc:Bounds x="912" y="99" width="36" height="36" /></bpmndi:BPMNShape><bpmndi:BPMNEdge id="Flow_0g1nmt1_di" bpmnElement="Flow_0g1nmt1"><di:waypoint x="215" y="117" /><di:waypoint x="270" y="117" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_178rknz_di" bpmnElement="Flow_178rknz"><di:waypoint x="370" y="117" /><di:waypoint x="430" y="117" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_1t1uand_di" bpmnElement="Flow_1t1uand"><di:waypoint x="530" y="117" /><di:waypoint x="590" y="117" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_0xb5gog_di" bpmnElement="Flow_0xb5gog"><di:waypoint x="860" y="117" /><di:waypoint x="912" y="117" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_1giv9ue_di" bpmnElement="Flow_1giv9ue"><di:waypoint x="690" y="117" /><di:waypoint x="760" y="117" /></bpmndi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</bpmn:definitions>
💖部署流程定义
在resources下新建目录bpmn用于存放流程定义文件,启动项目后调用部署接口:
/*** 流程定义相关接口* @author lonewalker*/
@RequestMapping("/process/definition")
@AllArgsConstructor
@RestController
public class ProcessDefinitionController {private final RepositoryService repositoryService;/*** 部署流程定义** @return 提示信息*/@PostMapping("/deploy")public String deployProcessDefinition(){repositoryService.createDeployment().addClasspathResource("bpmn/2.bpmn").name("演示").deploy();return "部署成功";}
}
💖流程实例测试
/*** 流程实例相关接口** @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> userOneList = new ArrayList<>();List<String> userTwoList = new ArrayList<>();List<String> userThreeList = new ArrayList<>();//一人通过节点的审批人userOneList.add("10086");userOneList.add("10087");//比例通过节点的审批人userTwoList.add("10087");userTwoList.add("10088");userTwoList.add("10089");userTwoList.add("10090");userTwoList.add("10091");//全部通过节点的审批人userThreeList.add("10090");userThreeList.add("10091");paramMap.put("initiator", "10086");paramMap.put("userOneList", userOneList);paramMap.put("userTwoList", userTwoList);paramMap.put("userThreeList", userThreeList);ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(requestParam.getProcessDefinitionKey(), requestParam.getBusinessKey(), paramMap);return processInstance.getProcessInstanceId();}/*** 完成单个任务** @param requestParam 请求参数* @return 任务所在节点信息*/@PostMapping("/completeSingleTask")public Boolean completeSingleTask(@RequestBody @Validated CompleteTaskRequest requestParam) {taskService.complete(requestParam.getTaskId());return true;}
}
发起流程实例后让流程来到【一人通过】节点
在【比例通过】节点设置完成条件是通过人数占总人数的三成,所以只需两个人审批通过即通过:
两人审批通过后是符合预期来到最后一个节点
查看任务的历史表【act_hi_taskinst】,两个任务被完成,其他任务则被删除了。
💖扩展
多实例节点可以配置串行或并行。三条垂直线表示实例将并行执行,而三条水平线表示顺序执行。
该部分就不单独做演示了。