前言
什么是循环审批:
- 例如: 我是一个客服,然后接到了一个订单退款的处理,按道理我看完没问题了,然后就是通过审核,平台返钱就行了, 部分流程图就是 : 订单退款>>>客服审核>>>平台退款;
- 但是! 我就是一个打工人,所以我一个客服确实觉得没问题呀,但是我担心订单会造成绩效损失,我想让客服经理帮忙看看,然后客服经理看过后,觉得这老板会不会不开心呀,所以又想让老板看看;
- 但是! 大家知道,这个流程图绘制完了,就不会随时更改的,像这种需求,表面上其实一直在客服审核这个节点,但是却一直有不同的人在审批,在不更改原有流程的情况下,如何实现这个需求呢?
一、现有方案
对于这个需求,现有的flowable方案有如下两种:
- 在该节点增加任务监听,通过任务监听动态分配给用户,创建子任务实现
- 并行任务会签,这个比较牵强,但是也能实现,就是通过会签达到多个人都审批的需求,
相对比而言,第一个更符合需求
那么这两个方案的缺点是什么呢?
第一个方案,这里里面一般来说动态监听分配用户,只能在create时候分配一次,或者有其他方式,但一定是有限制的,无法做到无限扩展,例如突然弄个10个人审核,这根本无法实现
第二个方案,那就是不灵活,它无法临时起意,添加审核人,扩展复杂,只能在绘制流程图的时候就将这里考虑好才可以
二、单节点循环审批
此方案确实是工作中需要的,然后这个单节点循环审批的方案也是自行摸索了两天,搞定的,简单说下方案思路
在要循环审批的任务节点,连线自循环
将要循环审批的人存储到业务数据库,或者流程变量中
当前任务节点用${user}的方式分配
每次审批完毕,获取循环审批的人,设置下一次审批的人
利用任务监听,每次循环审批结束,判断是否可以结束,结束跳转到下一个节点,未结束继续循环
仍然是利用子任务实现,不过是动态的,废话不多说,直接上代码:
- 循环审批流程图 xml
<?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">
<process id="cycle-approve" name="循环审批" isExecutable="true">
<documentation>循环审批</documentation>
<startEvent id="start" name="开始"/>
<userTask id="shenpi" name="审批" flowable:assignee="${taskUser}">
<extensionElements>
<flowable:taskListener event="complete" class="com.example.demo.listen.CycleTaskHandler"/>
</extensionElements>
</userTask>
<endEvent id="end" name="结束"/>
<sequenceFlow id="sid-A95C71D2-732D-4CDD-8D79-66CD360471DC" sourceRef="start" targetRef="shenpi"/>
<sequenceFlow id="sid-37809D0B-FC42-4D97-B1AF-CE9C93AACE71" name="循环审批" sourceRef="shenpi" targetRef="shenpi">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${res==true && approvalCount<3}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="pass" name="通过" sourceRef="shenpi" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${res==false || approvalCount>=3}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_cycle-approve">
<bpmndi:BPMNPlane bpmnElement="cycle-approve" id="BPMNPlane_cycle-approve">
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
<omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="shenpi" id="BPMNShape_shenpi">
<omgdc:Bounds height="80.0" width="100.0" x="255.0" y="138.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
<omgdc:Bounds height="28.0" width="28.0" x="645.0" y="164.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-37809D0B-FC42-4D97-B1AF-CE9C93AACE71" id="BPMNEdge_sid-37809D0B-FC42-4D97-B1AF-CE9C93AACE71">
<omgdi:waypoint x="330.0" y="137.99998"/>
<omgdi:waypoint x="330.0" y="87.0"/>
<omgdi:waypoint x="305.00003" y="87.0"/>
<omgdi:waypoint x="305.0" y="138.00002"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-A95C71D2-732D-4CDD-8D79-66CD360471DC" id="BPMNEdge_sid-A95C71D2-732D-4CDD-8D79-66CD360471DC">
<omgdi:waypoint x="129.94999949366624" y="178.0"/>
<omgdi:waypoint x="254.99999999993574" y="178.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="pass" id="BPMNEdge_pass">
<omgdi:waypoint x="354.9499999999221" y="178.0"/>
<omgdi:waypoint x="645.0" y="178.0"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
- 循环审批图示
sid-37809D0B-FC42-4D97-B1AF-CE9C93AACE71 这个循环审批分支 ,只要是res为true,那么就会一直循环
- 具体实现
package com.example.demo.controller;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.flowable.variable.api.persistence.entity.VariableInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
* 多人多次循环审批
* https://doc.apipost.net/docs/37e932d3447c000?locale=zh-cn
*/
@Controller
@RequestMapping("/cycle")
public class CycleController {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
@Autowired
private ProcessEngine processEngine;
/**
* 添加报销
*
* @param userId 用户Id
* @param money 报销金额
* @param descption 描述
*/
@RequestMapping(value = "add")
@ResponseBody
public String addExpense(String userId) {
//启动流程
HashMap<String, Object> map = new HashMap<>();
map.put("taskUser", userId);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("cycle-approve", map);
return "提交成功.流程Id为:" + processInstance.getId();
}
/**
* 获取审批管理列表
*/
@RequestMapping(value = "/list")
@ResponseBody
public ResponseEntity list(String userId) {
List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();
Task task = tasks.get(0);
return ResponseEntity.ok(task.getId());
}
/**
* 批准
*
* @param taskId 任务ID
*/
@RequestMapping(value = "apply")
@ResponseBody
public String apply(String taskId, boolean res,String userId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
throw new RuntimeException("流程不存在");
}
//通过审核
HashMap<String, Object> map = new HashMap<>();
map.put("res", res);
// 每次设置下一循环审批的办理人
map.put("taskUser", userId);
taskService.complete(taskId, map);
return "processed ok!";
}
/**
* 拒绝
*/
@ResponseBody
@RequestMapping(value = "reject")
public String reject(String taskId) {
HashMap<String, Object> map = new HashMap<>();
map.put("outcome", "驳回");
taskService.complete(taskId, map);
return "reject";
}
/**
* 生成流程图
*
* @param processId 任务ID
*/
@RequestMapping(value = "processDiagram")
public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception {
List<ProcessInstance> t = runtimeService.createProcessInstanceQuery().list();
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
//流程走完的不显示图
if (pi == null) {
return;
}
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
//使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
String InstanceId = task.getProcessInstanceId();
List<Execution> executions = runtimeService
.createExecutionQuery()
.processInstanceId(InstanceId)
.list();
//得到正在执行的Activity的Id
List<String> activityIds = new ArrayList<>();
List<String> flows = new ArrayList<>();
for (Execution exe : executions) {
List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
activityIds.addAll(ids);
}
//获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
// InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0);
InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, Collections.emptyList(), engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), null, 1.0, false);
OutputStream out = null;
byte[] buf = new byte[1024];
int legth = 0;
try {
out = httpServletResponse.getOutputStream();
while ((legth = in.read(buf)) != -1) {
out.write(buf, 0, legth);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
可以参考apipost 的请求: 循环审批请求示例
apply方法中 userId 每次变更为下一个要审批的人
- 任务监听
@Slf4j
public class CycleTaskHandler implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
// 获取当前审批结果
boolean res = (boolean) delegateTask.getVariable("res");
// 获取当前审批次数
if (delegateTask.getVariable("approvalCount") == null) {
delegateTask.setVariable("approvalCount", 0);
}
int approvalCount = (int) delegateTask.getVariable("approvalCount");
// 更新审批次数
delegateTask.setVariable("approvalCount", ++approvalCount);
// 如果审批次数达到3次且每次res都为true,则设置res为false
if (approvalCount >= 3 && res) {
delegateTask.setVariable("res", false);
}
}
}
这里除了用么个审批人创建任务外,还利用了任务监听来计算执行次数,可以使判断更加灵活
利用流程变量 approvalCount 来控制是否结束,像结束当前节点,直接设置res未false,否则一直为true,一直自循环
这样处理后,理论上可以实现无限自循环审批
总结
基于flowable的单节点循环审批至此已经实现,感兴趣的小伙伴可以自行尝试,对了,此技术点已经申请专利,商用需付费