你在使用哪种编程语言?快来投票,亲手选出你心目中的编程语言之王 了解详情
写点什么

Activiti 使用小技巧

2019 年 9 月 20 日

Activiti使用小技巧

三方合同系统自从签前审核项目开始,一直有跟工作流打交道,最近合同变更项目也开始对接工作流。在这个过程中,面对复杂多变的业务场景,为了解决各种出其不意的业务需求,我对 activiti 进行了更多的探索和发现,本文的初衷是将我发现的一些小技巧分享给大家,希望能给大家以后使用 activiti、对接工作流平台一些参考。


技巧一:排他网可以省略

排他网关,也叫异或(XOR)网关,用来在流程中实现决策。当流程执行到这个网关,所有外出顺序流都会被处理一遍,其中条件为 true 的顺序流会被选中,让流程经由这个顺序流继续执行。


我们来看一下面这个流程:商圈经理审核,排他网关决策,通过的话就走顺序流 4(flow4),否则走顺序流 3(flow3);法务后面也有一个排他网关,通过走 flow6,驳回走 flow7。



这个流程定义没问题,但是当我们遇到比较多判断的时候,流程中如果加入很多排他网关,那么流程图会比较长,例如会出现在页面上展示不全的问题。有没有办法简化一下?


我们回顾一下排他网关的决策原理:一个排他网关会对应着一个以上的顺序流,每个顺序流都有个 conditionExpression 条件,返回 boolean 类型的决策结果(直白地说,就是会走条件为 true 的那条线),排他网关会从上而下选择第一个条件为 true 的顺序流。所以,本质上排它网关的决策是顺序流的决策。


如果没有配 conditionExpression,顺序流默认是 true。这就是为什么,上图中,流程开始不需要条件就到达商圈经理任务,商圈经理任务不需要任何条件就能走到第一个排他网关。既然顺序流可以起决策作用,我们是不是可以考虑,给用户任务后面跟着的那些顺序流加上 conditionExpression,以达到控制流程走向的目的?答案是肯定的!


这就是我们探索的结轮:排他网关其实可以省略掉,直接在用户任务后面接决策条件,同样可以达到决策流程走向的效果。


但是要说明的一点,排他网关的作用是可以使流程更清晰,能够具体、直观地看出流程走到这个节点,有一个路径的判断、选择过程,它的直观是无可替代的。所以,我们并不是推荐大家一定要把排他网关省略掉,而是仅供一个参考,到底要不要省掉,请具体问题具体分析。我们一般是在流程中排他网关比较多导致流程图很长、影响了页面展示并且决策条件单一的情况下会考虑省略它。


上面的流程图可以简化如下,此时 flow3、flow4 都是带条件的(conditionExpression):




该流程定义 xml 如下:


<process id="myProcess" name="My  process" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="商圈经理" activiti:skipExpression="${execution.getVariable('hasManager')==false}"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<endEvent id="endevent2" name="End"></endEvent>
<userTask id="usertask2" name="法务"></userTask>
<sequenceFlow id="flow2" name="flow4" sourceRef="usertask1" targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${FLAG==true}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" name="flow3" sourceRef="usertask1" targetRef="endevent1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${FLAG==false}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" name="flow6" sourceRef="usertask2" targetRef="endevent2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${FLAG==true}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" name="flow7" sourceRef="usertask2" targetRef="endevent1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${FLAG==false}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow6" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
</process>
复制代码


技巧二:跳过用户任务的简单实现

跳过用户任务,意思是比如上面的流程,如果流程发起人没有商圈经理,那要跳过商圈经理这个审核节点,直接到法务审核。针对这个业务需求,我们可以再流程图上加一个”排他网关”(自从知道排他网关可以省略之后,我再也不画排他网关了),如下图:



这个流程看起来也没什么问题,是能满足需求的,不足之处就是丑了的。要是法务那个任务也“没有法务可以跳过”,那就又要多两条线了。实际业务里面,我们就遇到了一个流程三个用户任务,其中两个可以跳过的,幸好掌握了技巧一省略了排他网关,否则这个流程也会相当长。问题来了,这个流程还能不能再简化,有没有办法把这些线也去掉呢?


笔者发现,用户任务的配置里面有一个叫“skip expression”的配置项,“跳过表达式”,听起来正是我们想要的。


跳过表达式,当表达式条件为 true,就跳过当前任务,进入该任务之后的流程。在 activiti 实现里面,就不会创建该用户任务了。



有了这个跳过表达式,上面的流程图就可以简化成这样:



流程图跟上面第二个图一样,唯一不同的是,商圈经理这个任务加了跳过表达式。流程定义上的区别是,商圈经理任务多了配置:


activiti:skipExpression="${execution.getVariable('hasManager')==false}"
复制代码


流程定义如下:


<process id="myProcess" name="My  process" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="商圈经理" activiti:skipExpression="${execution.getVariable('hasManager')==false}"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<endEvent id="endevent2" name="End"></endEvent>
<userTask id="usertask2" name="法务"></userTask>
<sequenceFlow id="flow2" name="flow4" sourceRef="usertask1" targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${FLAG==true}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" name="flow3" sourceRef="usertask1" targetRef="endevent1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${FLAG==false}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" name="flow6" sourceRef="usertask2" targetRef="endevent2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${FLAG==true}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" name="flow7" sourceRef="usertask2" targetRef="endevent1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${FLAG==false}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow6" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
</process>
复制代码


技巧三:A 或 B 岗位的用户都可以处理的用户任务

首先,说明一下三方合同对接工作流平台,关于用户身份的处理机制。目前对接工作流平台,我们没有使用 activiti 用户组(group)的概念,原因是,第一目前工作流平台暂不支持用户组,没有存 UC 的用户数据;


其次,是业务中用户身份是多样的,很多时候判断一个用户到底有没有审核某任务的权限,我们有可能是根据用户权限点来控制、也有可能是根据用户角色来控制、还有可能是二者的结合,不确定 activiti 的 group 能不能满足多样化的业务需求。


所以,权衡了工作流平台的现状以及业务的复杂性,我们没有使用 activiti 的 group 机制来控制用户任务,而是采用的是折衷的、较为简单的方式,将用户任务的审核人信息作为该任务的一个属性,如 IDENTITY_FLAG,用户的待办、已办任务就通过该值进行查询。


基于以上的背景,我们在业务中遇到了一个问题。业务需求是这样的,一个任务,商圈经理或者总监都可以审,只要有一个人审核通过,流程就可以继续往下走了,大概是下面这样:



这个需求可以用“一个任务,多种身份”的方式来处理,但是这不是我们想要的。我们始终希望一个用户任务,只对应一类身份的审核人,这样对我们的待办、已办数据的处理更方便。


经过一番探究,后来我们采用了并行网关加监听的方案解决了这个问题。并行网关的作用是,将单条线路任务拆分成多个路径,等到多条路径都平行完成之后,再合并起来。那么我们就可能利用并行网关,将任务拆分成并行的路径,只要流程经过第一个并行网关之后,就会同时出现两组用户待办任务,满足了需求:商圈经理和总监都可能审核。


流程定义如下:


但是,有一个问题,当一个任务通过,该路径会到达后面那个并行网关,然而另一个任务还没有处理,流程会卡住。为了满足需求,我们要想办法把另一个任务处理掉,让其中一个任务通过,流程就能继续往下走。在此,我们采用了监听机制,两个任务互相监听对方是否完成,有的话,就把当前任务 complete 掉。



如上流程,如果商圈经理通过了该任务,监听方法就把大区总监的任务也通过,然后流程就可以顺利到达下一个节点。下面是商圈经理任务的 listener 的配置:监听 directorTask(大区总监任务),在监听方法里面判断,审核结果(AUDIT_FLAG)为通过时,完成本任务;驳回就不用了,因为整个流程已结束。



该流程定义 xml 如下:


<process id="managerOrDirector" name="商圈经理或总监审核" isExecutable="true">
<startEvent id="startevent1" name="开始"></startEvent>
<userTask id="managerTask" name="商圈经理">
<extensionElements>
<activiti:taskListener event="create" expression="#{listenerService.setVariableLocalFromInstance(task, 'BUSINESS_MANAGER_FLAG','IDENTITY_FLAG')}"></activiti:taskListener>
<activiti:taskListener event="complete" expression="#{listenerService.setVariableLocalFromInstance(task, 'AUDIT_FLAG', 'AUDIT_FLAG')}"></activiti:taskListener>
<activiti:taskListener event="complete" expression="#{listenerService.autoCompleteByDefinitionKey(task, 'directorTask', 'AUDIT_FLAG')}"></activiti:taskListener>
</extensionElements>
</userTask>
<userTask id="directorTask" name="大区总监">
<extensionElements>
<activiti:taskListener event="create" expression="#{listenerService.setVariableLocalFromInstance(task, 'DIRECTOR_FLAG','IDENTITY_FLAG')}"></activiti:taskListener>
<activiti:taskListener event="complete" expression="#{listenerService.setVariableLocalFromInstance(task, 'AUDIT_FLAG', 'AUDIT_FLAG')}"></activiti:taskListener>
<activiti:taskListener event="complete" expression="#{listenerService.autoCompleteByDefinitionKey(task, 'managerTask', 'AUDIT_FLAG')}"></activiti:taskListener>
</extensionElements>
</userTask>
<endEvent id="endevent1" name="驳回"></endEvent>
<endEvent id="endevent2" name="通过"></endEvent>
<parallelGateway id="parallelgateway1" name="Parallel Gateway"></parallelGateway>
<parallelGateway id="parallelgateway2" name="Parallel Gateway"></parallelGateway>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="parallelgateway1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="parallelgateway1" targetRef="managerTask"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="parallelgateway1" targetRef="directorTask"></sequenceFlow>
<sequenceFlow id="flow4" sourceRef="managerTask" targetRef="parallelgateway2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${AUDIT_FLAG==true}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" sourceRef="directorTask" targetRef="parallelgateway2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${AUDIT_FLAG==true}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow6" name="通过" sourceRef="parallelgateway2" targetRef="endevent2"></sequenceFlow>
<sequenceFlow id="flow7" sourceRef="managerTask" targetRef="endevent1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${AUDIT_FLAG==false}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow8" name="驳回" sourceRef="directorTask" targetRef="endevent1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${AUDIT_FLAG==false}]]></conditionExpression>
</sequenceFlow>
</process>
复制代码


本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。


原文链接:


https://mp.weixin.qq.com/s/YqOcpmVkXgXJSpZFHoBABw


2019 年 9 月 20 日 16:27770

评论 1 条评论

发布
用户头像
您好!想与作者进一步沟通工作流,怎么能联系到作者?可以付费!谢谢!
2020 年 11 月 02 日 11:46
回复
没有更多了
发现更多内容

filecoin矿机系统开发|filecoin矿机软件APP开发

开發I852946OIIO

系统开发

github 这样用,事半功倍

hepingfly

Java GitHub 搜索 使用技巧

DCache 分布式存储系统|Set, ZSet 缓存模块的创建与使用

TARS基金会

nosql 缓存 分布式 MySQL 高可用 TARS

Wireshark 数据包分析学习笔记Day15

穿过生命散发芬芳

Wireshark 数据包分析 3月日更

经典排序算法分析

roseduan

算法 排序算法

filecoin云算力软件开发|filecoin云算力APP系统开发

开發I852946OIIO

系统开发

收藏!这些IDE使用技巧,你都知道吗

xcbeyond

IDEA 技巧 3月日更

翻译:《实用的Python编程》06_04_More_generators

codists

Python

epoll源码分析以及在Redis中的实现

Linux服务器开发

redis 后端开发 epoll web服务器 Linux服务器开发

uni-app跨端开发H5、小程序、IOS、Android(二):开发工具HBuilderX使用技巧

黑马腾云

微信小程序 uni-app App 3月日更 Hbuilderx

Github封神!2021最新阿里巴巴Java面试权威指南(华山版)震撼开源!

Java王路飞

Java 程序员 架构 面试 分布式

繁忙的三月

IT蜗壳-Tango

3月日更 IT蜗壳教学

产品经理训练营 Week9 作业

Mai

炸裂,IBM系统架构师居然把自己15年Java经验整合成一本小说?

Java架构师迁哥

如何评估需求优先级?

石云升

项目管理 28天写作 职场经验 管理经验 3月日更

低代码/无代码,作为IT开发界的“新英雄”它究竟有何神技?

优秀

低代码 无代码开发

B+树索引优点

一个大红包

3月日更

手机

ES_her0

28天写作 3月日更

产品经理训练营 Week9 学习心得

Mai

filecoin挖矿软件开发|filecoin挖矿APP系统开发

开發I852946OIIO

系统开发

"无密码时代"已经来临!

龙归科技

身份认证

前端开发: 路由跳转页面详解

三掌柜

vue.js 前端 3月日更

算法攻关-最长公共子序列_1143

小诚信驿站

刘晓成 小诚信驿站 28天写作 算法攻关 最长公共子序列

后台产品导航栏原型设计小教程

lenka

3月日更

打卡学习VBA和PYTHON week01

小怪兽

IT蜗壳教学

科技进化的终点,与荣耀全场景的起点

脑极体

JDBC—往MySQL中写入Blob数据时,出现错误:com.mysql.jdbc.PacketTooBigException: Packet for query is too large (5724349 > 1048576)

打工人!

Java MySQL JDBC Blob

聊聊 Python 自动化脚本部署服务器全流程(详细)

星安果

Python 自动化 服务器 部署

php 再上热搜!swoole 创始人投出反对票,质疑 php 协程最新提案

薇薇

php 编程 新特性 php扩展

容器 & 服务:Kubernetes扩容

程序员架构进阶

Docker 容器 kubernete 28天写作 3月日更

mybatis实现分页的几种方法

xiezhr

mybatis 分页 mybatis分页

围绕“三个问题”开展的网易云音乐数据基础建设

围绕“三个问题”开展的网易云音乐数据基础建设

Activiti使用小技巧-InfoQ