一、概念
任务节点处理完成之后,后面的处理可能需要根据不同的调节走不同的流程(选择一条路径,串行),或者是将任务分发到多个审批人头上(分发到多条流程路径),
或者在某个任务节点要判断多个人审批之后才能继续执行(多条路径汇聚到一点)。
这种操作就要用到流程分支的概念,一个分支相当于就是一个子流程,有串行流,也有并行流。
二、连线流程
在连线上设置条件,根据条件判断后面的流程走那个分支,这样做的好处在于方便快捷,坏处在于如果所以连线都没有满足条件,则当前流程会异常结束,不会
有异常显示。
步骤:
在bpmn文件中的连线的属性上,设置UEL表达式,${msg==xxx},在审批代码里面通过map将这个uel的变量通过任务执行方法传入,
任务执行的时候,就会自行判断走那个流程分支。
varMap=new HashMap<String, Object>();
varMap.put("msg", "important");
taskServ.complete(task.getId(),varMap);
三、排他网关
可以辅助连线上设置条件的方式,来实现串行子流程(分支),在连线前面加上一个网关元素,如果所以的连线条件都不满足,会抛出异常信息。
步骤:
实现串行分支,也就是流程按照条件,选择一条分支继续执行,在bpmn上,任务节点之间,设置一个排他网管,不同分支上的条件还是同样的设置方式。
varMap=new HashMap<String, Object>();
varMap.put("money", 1650);
taskServ.complete(task.getId(),varMap);
四、并行网关
(1)、分支和汇聚
一个任务节点通过并向网关加连线,可以分发到不同的分支(子流程),由多个分支的负责人同时处理。
不同分分支子流程处理完成之后,可以汇聚到一个点(并向网关),
在这个点上设置通过条件,可以按分支到达的比例或者个数来设置,达到标准后再继续往下执行。
基于这种特性,可以实现基于不同角色审批的任务分发和会签功能。
步骤:
<1>、在bpmn设置并向网关的分支点和汇聚点
<2>、在并向网关汇聚点上依据以下几个变量,设置通过的UEL条件表达式:
nrOfInstances(numberOfInstances):会签中总共的实例数
nrOfCompletedInstances:已经完成的实例数量
nrOfActiviteInstances:当前活动的实例数量,即还没有完成的实例数量
条件${nrOfInstances == nrOfCompletedInstances}表示所有人员审批完成后会签结束。
条件${ nrOfCompletedInstances == 1}表示一个人完成审批,该会签就结束。
其他条件依次类推,同时这里也可以写自己添加的流程变量
<3>、并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。
<4>、每条分支,都会在数据库生成一条对应的执行实例数据。
五、基于同角色的会签
Activiti实现会签是基于多实例任务,将节点设置成多实例,主要通过在UserTask节点的属性上配置。
主要是在任务节点的multi instace上进行设置:
1. Sequential:执行顺序。必选项,可选值有true、false。用于设置多实例的执行顺序。True:多实例顺序执行,false:多实例并行
2. loop cardinality:循环基数。可选项。可以直接填整数,表示会签的人数。
3. Collection:集合。可选项。会签人数的集合,通常为list。和loop cardinality二选一
4. Element variable:元素变量。选择Collection时必选,为collection集合每次遍历的元素(和mian config里面设置的Assigner相对应)
5. Completion condition:完成条件。可选。Activiti会签有个特性,比如设置一个人完成后会签结束,那么其他人的代办任务都会消失。
这里可以用自定义的条件,也可以依据默认的条件和UEL表达式来表示:
nrOfInstances 该会签环节中总共有多少个实例
nrOfActiveInstances 当前活动的实例的数量,即还没有 完成的实例数量
nrOfCompletedInstances 已经完成的实例的数量
handleWaitWork(taskService, "liSi", true);
/**
* 处理代办任务
* @param taskService
* @param empCode
* @param result 审批结果;true:同意;false:拒绝
*/
public static void handleWaitWork(TaskService taskService, String empCode, boolean result){
List<Task> tasks = taskService.createTaskQuery().taskAssignee(empCode).orderByTaskId().desc().list();
if(tasks != null && tasks.size() != 0){
for(Task tempTask : tasks){
//处理代办
Map<String, Object> map = new HashMap<String, Object>();
map.put("pass", result);
taskService.complete(tempTask.getId(), map);
}
}else{
System.out.println("没有代办任务");
}
}
然后添加监听器,监听每个任务实例的执行情况,根据执行情况设置下一步任务连线的result属性,
resultN就是驳回到申请任务节点,resultY就是流转到下一个任务节点。
/**
* 会签监听
* @author xuyl 2019年5月7日 上午11:12:42
*
*/
public class SignListener implements TaskListener{
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public void notify(DelegateTask delegateTask) {
System.out.println("会签监听");
//获取流程id
String exId = delegateTask.getExecutionId();
//获取流程参数pass,会签人员完成自己的审批任务时会添加流程参数pass,false为拒绝,true为同意
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
boolean pass = (Boolean) runtimeService.getVariable(exId, "pass");
/*
* false:有一个人拒绝,整个流程就结束了,
* 因为Complete condition的值为pass == false,即,当流程参数为pass时会签就结束开始下一个任务
* 所以,当pass == false时,直接设置下一个流程跳转需要的参数
* true:审批人同意,同时要判断是不是所有的人都已经完成了,而不是由一个人同意该会签就结束
* 值得注意的是如果一个审批人完成了审批进入到该监听时nrOfCompletedInstances的值还没有更新,因此需要+1
*/
if(pass == false){
//会签结束,设置参数result为N,下个任务为申请
runtimeService.setVariable(exId, "result", "N");
//下个任务
String processInstanceId = delegateTask.getProcessInstanceId();
Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
System.out.println("下个任务编码:" + task.getId() + ",下个任务名称:" + task.getName());
}else{
Integer complete = (Integer) runtimeService.getVariable(exId, "nrOfCompletedInstances");
Integer all = (Integer) runtimeService.getVariable(exId, "nrOfInstances");
//说明都完成了并且没有人拒绝
if((complete + 1) / all == 1){
runtimeService.setVariable(exId, "result", "Y");
//下个任务
String processInstanceId = delegateTask.getProcessInstanceId();
Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
System.out.println("下个任务编码:" + task.getId() + ",下个任务名称:" + task.getName());
}
}
}
}
6、关于驳回
最简单的方式是通过设置连线上的条件来实现,其次就是通过在连线的基础上加上网关来防止有条件不满足导致的异常退出情况。
但是这两种都不适用于比较灵活的,复杂的流程跳转,所以最全面的方式还是通过api操作对应的数据库表来实现。
回退的思路就是动态更改节点的流向。先遇水搭桥,最后再过河拆桥。
具体操作如下:
取得当前节点的信息
取得当前节点的上一个节点的信息
保存当前节点的流向
新建流向,由当前节点指向上一个节点
将当前节点的流向设置为上面新建的流向
当前节点完成任务
将当前节点的流向还原
取得之前上个节点的执行人
设置上个节点的assignee为之前的执行人
/**
* 跳到最开始的任务节点(直接打回)
* @param task 当前任务
*/
public void jumpToStart(Task task) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
HistoryService historyService = processEngine.getHistoryService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
String processInstanceId = task.getProcessInstanceId();
// 获取所有历史任务(按创建时间升序)
List<HistoricTaskInstance> hisTaskList = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByTaskCreateTime()
.asc()
.list();
if (CollectionUtils.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
return;
}
// 第一个任务
HistoricTaskInstance startTask = hisTaskList.get(0);
// 当前任务
HistoricTaskInstance currentTask = hisTaskList.get(hisTaskList.size() - 1);
BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
// 获取第一个活动节点
FlowNode startFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(startTask.getTaskDefinitionKey());
// 获取当前活动节点
FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentTask.getTaskDefinitionKey());
// 临时保存当前活动的原始方向
List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();
originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
// 清理活动方向
currentFlowNode.getOutgoingFlows().clear();
// 建立新方向
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newSequenceFlowId");
newSequenceFlow.setSourceFlowElement(currentFlowNode);
newSequenceFlow.setTargetFlowElement(startFlowNode);
List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
newSequenceFlowList.add(newSequenceFlow);
// 当前节点指向新的方向
currentFlowNode.setOutgoingFlows(newSequenceFlowList);
// 完成当前任务
taskService.complete(task.getId());
// 重新查询当前任务
Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
if (null != nextTask) {
taskService.setAssignee(nextTask.getId(), startTask.getAssignee());
}
// 恢复原始方向
currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
}