1.  回退(驳回)

回退的思路就是动态更改节点的流向。先遇水搭桥,最后再过河拆桥。

具体操作如下:

  1. 取得当前节点的信息
  2. 取得当前节点的上一个节点的信息
  3. 保存当前节点的流向
  4. 新建流向,由当前节点指向上一个节点
  5. 将当前节点的流向设置为上面新建的流向
  6. 当前节点完成任务
  7. 将当前节点的流向还原
  8. 取得之前上个节点的执行人
  9. 设置上个节点的assignee为之前的执行人

代码实现起来可能是这样的:

@Test
public void huitui() throws Exception {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processInstanceId("55001").singleResult();
backProcess(task);
} /**
* 驳回 / 回退
* 按照这种方法,可以回退至任意节点
* @param task
* @throws Exception
*/
public void backProcess(Task task) throws Exception {
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()
.desc()
.list(); List<HistoricActivityInstance> hisActivityList = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId).list(); if (CollectionUtils.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
return;
} // 当前任务
HistoricTaskInstance currentTask = hisTaskList.get(0);
// 前一个任务
HistoricTaskInstance lastTask = hisTaskList.get(1);
// 当前活动
HistoricActivityInstance currentActivity = hisActivityList.stream().filter(e -> currentTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);
// 前一个活动
HistoricActivityInstance lastActivity = hisActivityList.stream().filter(e -> lastTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0); BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId()); // 获取前一个活动节点
FlowNode lastFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(lastActivity.getActivityId());
// 获取当前活动节点
FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivity.getActivityId()); // 临时保存当前活动的原始方向
List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();
originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
// 清理活动方向
currentFlowNode.getOutgoingFlows().clear(); // 建立新方向
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newSequenceFlowId");
newSequenceFlow.setSourceFlowElement(currentFlowNode);
newSequenceFlow.setTargetFlowElement(lastFlowNode);
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(), lastTask.getAssignee());
} // 恢复原始方向
currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
}

以请假为例

<process id="holiday" name="holiday" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="填写请假单" activiti:assignee="${assignee1}"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="部门经理审批" activiti:assignee="${assignee2}"></userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<userTask id="usertask3" name="人事审批" activiti:candidateUsers="tom,jerry"></userTask>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>

假设现在已经到“人事审批”这个节点了,当前活动是usertask3

接下来,我们运行上面的代码,回退到上一个节点“部门经理审批”,于是

流程重新从“部门经理审批”节点开始往下走,当流程走完以后

证明,思路正确,写法没啥问题。但是,上面的代码可以简化一下,如下:

/**
* 跳到最开始的任务节点(直接打回)
* @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);
}

2.  会签

多个人同时处理一个任务,这种任务我们称之为会签任务 。Activiti实现会签是基于多实例任务,将节点设置成多实例,主要通过在UserTask节点的属性上配置。

会签的种类:

  • 按数量通过: 达到一定数量的通过表决后,会签通过。
  • 按比例通过: 达到一定比例的通过表决后,会签通过。
  • 一票否决: 只要有一个表决时否定的,会签通过。
  • 一票通过: 只要有一个表决通过的,会签通过。

每个实例有以下变量:

  • nrOfInstances: 实例总数
  • nrOfActiveInstances: 当前激活的(未完成的)实例总数。 如果串行执行,则改值永远是1

  • nrOfCompletedInstances: 已完成的实例总数

条件${nrOfInstances == nrOfCompletedInstances}表示所有人员审批完成后会签结束。

条件${ nrOfCompletedInstances == 1}表示一个人完成审批,该会签就结束。

其他条件依次类推,同时这里也可以写自己添加的流程变量。

相关文档如下:

下面举个例子:

<process id="countersign" name="countersign" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="申请" activiti:assignee="zhangsan"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="会签审批" activiti:assignee="${approver}">
<multiInstanceLoopCharacteristics isSequential="false"
activiti:collection="${approverList}" activiti:elementVariable="approver">
<completionCondition>${nrOfCompletedInstances == nrOfInstances}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<userTask id="usertask3" name="备案" activiti:assignee="tianqi"></userTask>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>

编写代码:

//  部署流程定义
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("diagram/countersign.bpmn")
.name("会签示例")
.key("countersign")
.deploy(); // 启动流程实例
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String, Object> variables = new HashMap<>();
variables.put("approverList", Arrays.asList("lisi","wangwu","zhaoliu"));
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("countersign", variables); // 完成任务
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processInstanceId("107501").taskAssignee("zhaoliu").singleResult();
if (null != task) {
taskService.complete(task.getId());
}

流程启动后,首先是zhangsan审批

当zhangsan完成自己的任务后,进入会签环节,于是我们看到当前有3个激活的任务

当lisi完成任务以后,当前任务剩下2个

当wangwu和zhaoliu都完成任务了以后,会签任务完成,进入下一个环节

刚才的例子中没有考虑到审批不通过的情况,接下来我们完善一下,考虑下面的流程

<process id="countersign" name="countersign" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="申请" activiti:assignee="zhangsan"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="会签审批" activiti:assignee="${approver}">
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${approverList}" activiti:elementVariable="approver">
<completionCondition>${nrOfCompletedInstances / nrOfInstances == 1 || pass == false}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<userTask id="usertask3" name="备案" activiti:assignee="tianqi"></userTask>
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow5" sourceRef="usertask2" targetRef="exclusivegateway1"></sequenceFlow>
<sequenceFlow id="flow6" name="通过" sourceRef="exclusivegateway1" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass == true}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow7" name="拒绝" sourceRef="exclusivegateway1" targetRef="usertask1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass == false}]]></conditionExpression>
</sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow8" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>

在会签审批完成任务时就要加上流程变量pass了

RuntimeService runtimeService = processEngine.getRuntimeService();
TaskService taskService = processEngine.getTaskService(); Task task = taskService.createTaskQuery().processInstanceId("152501").taskAssignee("lisi").singleResult();
if (null != task) {
Map<String, Object> variables = new HashMap<>();
variables.put("pass", true);
// variables.put("pass", false);
taskService.complete(task.getId(), variables); runtimeService.getVariable(task.getExecutionId(), "nrOfCompletedInstances");
}

zhaoliu审批的时候pass传的false,于是流程又走到zhangsan那里,流程重新又走了一遍才全部完成

关于回退和会签就先讲到这里

3.  参考

https://www.activiti.org/userguide/index.html#bpmnMultiInstance

http://zpycloud.com/archives/1755

https://blog.csdn.net/zjsdrs/article/details/89917206

Activiti7 回退与会签的更多相关文章

  1. jbpm4 回退、会签、撤销、自由流

    http://blog.csdn.net/xiaozhang0731/article/details/8699558 1. jBPM4的特点 jBPM是JBoss众多开源项目中的一个工作流开源项目,也 ...

  2. 关于activiti流程通过、驳回、会签、转办、中止、挂起等核心操作功能的封装

    http://blog.csdn.net/aochuanguying/article/details/7594197 package com.famousPro.process.service.imp ...

  3. JSAAS的Activiti会签开发扩展处理

    1.什么是会签? 在流程业务管理中,任务是通常都是由一个人去处理的,而多个人同时处理一个任务,这种任务我们称之为会签任务.这种业务需求很常见,如一个请款单,领导审批环节中,就需要多个部门领导签字.在流 ...

  4. Activiti之流程通过、驳回、会签、转办、中止、挂起等核心操作封装(Activiti5.9)

    http://blog.csdn.net/rosten/article/details/38300267 package com.famousPro.process.service.impl; imp ...

  5. activiti 动态自定义流程(包含会签流程)

    后台加入工作流步骤(这个不重要,自己实现) package com.blk.integrated.pojo; import java.io.Serializable; import java.util ...

  6. Activiti7 入门篇

    1.  工作流 简单地来讲,工作流就是在计算机的协助下实现流程的自动化控制.目前,笔者熟知的主流的框架有:Camunda .Flowable .Activiti .jBPM.还有我们国产的盘古BPM. ...

  7. Activiti7 与 Spring Boot 及 Spring Security 整合 踩坑记录

    1.  前言 实话实说,网上关于Activiti的教程千篇一律,有参考价值的不多.很多都是老早以前写的,基本都是直接照搬官方提供的示例,要么就是用单元测试跑一下,要么排除Spring Security ...

  8. 一步步学习javascript基础篇(9):ajax请求的回退

    需求1: ajax异步请求 url标识请求参数(也就是说复制url在新页面打开也会是ajax后的效果) ajax异步请求没问题,问题一般出在刷新url后请求的数据没了,这就是因为url没有记录参数.如 ...

  9. git 版本回退

    由于操作失误,需要将代码进行版本回退,首先在本地仓库执行了“git reset --hard HEAD^”命令,这样只会回退本地仓库的代码,但是我的代码之前已经push到了远程库中,查看远程仓库,发现 ...

随机推荐

  1. 浅析IOC 和 DI

    学习过spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...

  2. GO文件读写03---使用缓冲读写实现视频文件的拷贝

    package main import ( "bufio" "fmt" "io" "os" ) /* ·使用缓冲读写实现 ...

  3. GO文件读写01---读文件

    打开文件 package main import ( "fmt" "os" ) /* buffer 缓冲区 utility 便利的工具 util 便捷工具(傻瓜 ...

  4. TensorFlow之keras.layers.Conv2D( )

    keras.layers.Conv2D( ) 函数参数 def __init__(self, filters, kernel_size, strides=(1, 1), padding='valid' ...

  5. VTA硬件

    VTA硬件 提供了VTA硬件设计的自上而下的概述.本硬件设计涵盖两个级别的VTA硬件: VTA设计及其ISA硬件-软件接口的体系结构概述. VTA硬件模块的微体系结构概述以及计算核心的微代码规范. V ...

  6. 机器学习PAI

    机器学习PAI 机器学习PAI(Platform of Artificial Intelligence)是阿里云人工智能平台,提供一站式的机器学习解决方案.本文介绍什么是机器学习PAI. 什么是机器学 ...

  7. CUDA 7 流并发性优化

    异构计算是指高效地使用系统中的所有处理器,包括 CPU 和 GPU .为此,应用程序必须在多个处理器上并发执行函数. CUDA 应用程序通过在 streams 中执行异步命令来管理并发性,这些命令是按 ...

  8. 开放神经网络交换(ONNX)工具

    开放神经网络交换(ONNX)工具 开放神经网络交换(ONNX)是一个开放的生态系统,它使人工智能开发人员能够在项目发展过程中选择正确的工具.ONNX为人工智能模型提供了一种开源格式,包括深度学习和传统 ...

  9. Navicat Premium 15 安装包&激活工具及安装教程(亲测可用)

    Navicat Premium 15 安装包及激活工具 网盘地址: 链接:https://pan.baidu.com/s/1GU9qgdG1dRCw9Un8H9Ba9A提取码:F1r9 开始安装 下载 ...

  10. 实战SpringBoot Admin

    长话短说哦,直接查看下文吧 目录 声明 先锋 前提 SpringBoot Admin 介绍 服务端的搭建 客户端的搭建 参数的指南 尾声 声明 见名知意,实战SpringBoot Admin,实战!实 ...