【工作流Activiti7】3、Activiti7 回退与会签

news2024/11/20 20:35:37

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那里,流程重新又走了一遍才全部完成

回退和会签就先讲到这里 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/111993.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2022 年全球重大经济事件盘点( I )

2022 年&#xff0c;全球经济正面临百年未有之大变局&#xff0c;接踵而至的大事件造成今年行情剧烈波动&#xff0c;以往的投资逻辑不断遭遇修改。 正所谓阳光之下没有新鲜事。通过对于重大事件的复盘&#xff0c;分析了解过往历史脉络&#xff0c;投资者方能温故知新&#…

计算机视觉与图形学-神经渲染专题-

《Removing Objects From Neural Radiance Fields》链接&#xff1a;https://arxiv.org/pdf/2212.11966.pdf摘要神经辐射场 (NeRFs) 正逐步应用到场景表征的各个方向&#xff0c;来实现新颖视图的合成。NeRF 将越来越多内容与其他人共享。不过&#xff0c;在共享 NeRF 之前&…

深入理解 Linux 零拷贝以及 Linux 中 I/O 的底层原理,在kafka、nginx、golang等等各种文件传输场景中不同的优化手段和实际应用

深入理解 Linux 零拷贝以及 Linux 中 I/O 的底层原理,在kafka、nginx、golang等等各种文件传输场景中不同的优化手段和实际应用。从文件传输场景以及零拷贝技术深究 Linux I/O 的发展过程、优化手段以及实际应用。 前言 存储器是计算机的核心部件之一,在完全理想的状态下,存…

搞定 Redis 数据存储原理,别只会 set、get 了

我的核心模块如图 1-10。 图 1-10 Client 客户端&#xff0c;官方提供了 C 语言开发的客户端&#xff0c;可以发送命令&#xff0c;性能分析和测试等。 网络层事件驱动模型&#xff0c;基于 I/O 多路复用&#xff0c;封装了一个短小精悍的高性能 ae 库&#xff0c;全称是 a si…

TIP2022|领域迁移Adaboost,让模型“选择”学哪些数据

论文下载&#xff1a;https://zdzheng.xyz/files/TIP_Adaboost.pdf 备份&#xff1a;https://arxiv.org/pdf/2103.15685.pdf 作者&#xff1a;Zhedong Zheng&#xff0c;Yi Yang 代码链接&#xff1a; GitHub - layumi/AdaBoost_Seg: TIP2022 Adaptive Boosting (AdaBoost) …

rescue-prime:基于Goldilocks域的Rescue-Prime 哈希函数加速

1. 引言 前序博客&#xff1a; Goldilocks域 所谓计算友好的哈希函数&#xff0c;是指&#xff1a; 基于素数域元素&#xff0c;而不是 通常的如SHA3-256/SHA256/BLAKE3中的raw bits/bytes/N-bit words。原因是&#xff0c;在STARK证明系统中&#xff0c;基于素数域的计算电…

三极管 vs MOS管 | PMOS与NMOS

三极管 与 MOS管 MOS管等效模型&#xff1a;电压控制&#xff08;输入端G是电容&#xff09;&#xff1b;负载端D-S是小电阻&#xff0c;大电流时损耗小。 三级管等效模型&#xff1a;电流控制&#xff08;输入端G是电阻&#xff09;&#xff1b;负载端是二极管&#xff0c;大…

活动星投票“2023年度台历宝宝”网络评选投票图文投票怎么做

近些年来&#xff0c;第三方的微信投票制作平台如雨后春笋般络绎不绝。随着手机的互联网的发展及微信开放平台各项基于手机能力的开放&#xff0c;更多人选择微信投票小程序平台&#xff0c;因为它有非常大的优势。1.它比起微信公众号自带的投票系统、传统的H5投票系统有可以图…

我国户用光伏行业现状:装机规模创新高 国补退去对产业影响如何?

区别于大型光伏电站的大功率、占地广&#xff0c;户用光伏发电是指将光伏电池板置于家庭住宅顶层或者院落内&#xff0c;用小功率或者微逆变器进行换流过程&#xff0c;并直接利用该新能源&#xff0c;亦可将多余的电能并入电网&#xff0c;户用光伏属于分布式光伏范畴。目前&a…

【源码共读】将值转换为数组《arrify》

使用 根据库的作者提供的readme&#xff0c;使用方式很简单&#xff1a; 1.安装 npm install arrify 2.使用 import arrify from arrify;arrify(&#x1f984;); //> [&#x1f984;]arrify([&#x1f984;]); //> [&#x1f984;]arrify(new Set([&#x1f984;]));…

获取第三方数据四种方式

目录 调用api 远程表 数据源 jsoup 如何判断该使用哪一种获取数据方式&#xff1f; 调用api 优点&#xff1a; 接口文档规范&#xff0c;体现在请求方式和传递的参数及参数类型有严格说明减少开发人员逻辑处理。api将功能的逻辑在接口内部封装好&#xff0c;不需要开发人…

禅道api调用(爬虫方式)

目录 获取所有进行中的项目信息 url postman Java代码 实体类 逻辑处理 根据项目id获取指定项目下所有未关闭的任务id url postman Java代码 总结 获取所有进行中的项目信息 url http://禅道地址xxx/zentao/project-all-doing-项目ID-order_desc-0.html postman Jav…

Linux-系统随你玩之--用户及用户组管理

一、用户基本介绍 Linux 系统是一个多用户多任务的操作系统&#xff0c;任何一个要使用系统资源的用户&#xff0c;都必须首先向系统 管理员申请一个账号&#xff0c;然后才可以以这个用户登陆系统。 二、Linux中用户和组 2.1、用户和组介绍 用户&#xff1a; 每一个用户都…

独立开发变现周刊(第85期):一个会员服务的SaaS,月收入2万美金

分享独立开发、产品变现相关内容&#xff0c;每周五发布。目录1、Obsidian Canvas&#xff1a;一个无限的空间来构建你的想法2、message-pusher: 搭建专属于你的消息推送服务3、Careerflow LinkedIn: 40倍提升你的工作机会4、vue-pure-admin: 一款开源后台管理系统5、一个提供会…

CAD简单制作风向(风速)玫瑰图

背景: 风向玫瑰图(简称风玫瑰图)也叫风向频率玫瑰图,它是根据某一地区多年平均统计的各个风向的百分数值,并按一定比例绘制,一般多用8个或16个罗盘方位表示,由于形状酷似玫瑰花朵而得名。 玫瑰图上所表示风的吹向,是指从外部吹向地区中心的方向,各方向上按统计数值画…

雷军主导小米管理层变革:创业派隐退 职业经理人上位

雷递网 雷建平 12月23日岁末之际&#xff0c;在京东零售大幅调整后&#xff0c;小米也进行了一轮大调整。小米集团内部邮件所示&#xff0c;小米总裁王翔将在月底卸任集团总裁职务退休&#xff0c;同时&#xff0c;继续作为高级顾问为公司服务。小米集团总裁一职将由2019年加入…

基于K-means聚类算法进行客户人群分析

摘要&#xff1a;在本案例中&#xff0c;我们使用人工智能技术的聚类算法去分析超市购物中心客户的一些基本数据&#xff0c;把客户分成不同的群体&#xff0c;供营销团队参考并相应地制定营销策略。本文分享自华为云社区《基于K-means聚类算法进行客户人群分析》&#xff0c;作…

做跨境电商,如何从同类产品中脱颖而出?

随便打开一个跨境电商平台&#xff0c;你会发现自己售卖的产品有那么多类似的选择&#xff0c;如何确保你的产品能被客户选择&#xff1f;怎样在一系列产品中脱颖而出&#xff1f; 不少卖家提到了&#xff0c;搞差异化竞争&#xff0c;这是跨境电商卖家常挂在嘴边的一个词&…

绝对肝货,超全的 MyBatis 动态代理原理讲解。

1.MyBatis简介 MyBatis是一个ORM工具&#xff0c;封装了JDBC的操作&#xff0c;简化业务编程&#xff1b; Mybatis在web工程中&#xff0c;与Spring集成&#xff0c;提供业务读写数据库的能力。 2.使用步骤 1.引入依赖 采用Maven包依赖管理&#xff0c;mybatis-3.5.5版本&…

MyBatis学习 | 动态SQL

文章目录一、简介二、if标签2.1 if标签的简单使用2.2 where标签2.3 trim标签&#xff08;了解&#xff09;三、choose标签 & set标签3.1 choose标签3.2 set标签四、foreach标签4.1 foreach标签的简单使用4.2 批量插入五、内置参数六、bind标签七、sql标签 & include标签…