业务流程管理系统之Activiti
工作流
工作流在企业管理系统中是高频使用的功能,一个最常见的例子是请假加班申请与审批的过程。事实上,工作流引擎能支持的业务场景远远不止单据审批,几乎所有涉及到业务流转、多人按流程完成工作的场景背后都可以通过工作流引擎作为支撑。基于工作流引擎,可以搭建客户关系管理系统(CRM)、运输管理系统(TMS)、仓储管理系统(WMS)、财务费用系统等多种复杂业务系统。对于达到一定规模的企业,良好的 BPM(业务流程管理,Business Process Management)体系可以支持创建公司内横跨不同部门的复杂业务流程,既提高工作效率、又可推动企业规范化发展。
主流实现
Java生态中有以下几种主流的开源实现:
- jBPM
- Acitiviti
- Flowable
jBPM 由 JBoss 公司开发,不过从 jBPM5 开始已经跟之前不是同一个产品了,jBPM5 的代码基础不是 jBPM4,而是从 Drools Flow 重新开始(Drools Flow 技术在国内用的比较少),后来 jBPM4 创建者 Tom Baeyens 离开 JBoss 后,加入 Alfresco 后很快推出了新的基于 jBPM4 的开源工作流系统 Activiti。
Activiti 由 Alfresco 公司开发,其中 activiti5 和 activiti6 的核心 leader 是 Tijs Rademakers,由于团队内部分歧,2017 年 Tijs Rademakers 离开团队,创建了后来的 Flowable。activiti5 以及 activiti6 代码则交接给 Salaboy 团队维护,activiti5 以及 activiti6 的代码官方已经暂停维护。在此之后 Salaboy 团队基于activiti6 开发了 activiti7 框架,这是一次常规升级,并没有为引擎加入更多的新特性。
Flowable 是基于 activiti6 衍生出来的版本,开发团队是从 Activiti 中分裂出来的,修复了一些 activiti6 的 bug,并在其基础上实现了 DMN 支持,BPEL 支持等。相对开源版,其商业版的功能会更强大。
Activiti 后来推出 Activiti Cloud 7.x 版本,Activiti cloud 将系统拆分为 Runtime Bundle、Audit Service、Query Service、Cloud Connectors、Application Service、Notification Service 等服务,这些工作的主要目的是为了上云,需要使用 Activiti 的系统只需要通过调用 http 接口的方式来实现工作流能力的整合,将工作流业务托管上云。
本文主要基于Acitiviti 7 进行学习和研究。
节点选项
- 参与者策略
- 动作(非必选,因为可以使用流程变量+表达式读取来控制流程走向)
参与者策略
配置某节点的参与者策略就是设置哪些用户可以参与该节点的操作。
常用的参与者策略有:
指定的用户或角色
当前单位的上级用户
当前单位的直接上级单位指定角色的用户
当前单位的直接上级用户
当前单位的用户
当前单位下指定角色的用户
当前单位的上级单位指定角色的用户
有送审权限的人
有审批权限的人
有上报权限的人
转移线
转移线也叫顺序流,一般在转移线上配置监听器和表达式。
- 业务执行器(监听器)
- 表达式
转移线上关联动作的做法,本质上也是表达式一种,判断点击什么标识的按钮,走指定的转移线。
驳回
驳回到上一节点、驳回到提交人、驳回到指定节点
驳回后控制是否允许再次提交(重复提交还是终止)
拓展配置
控制是否填写审批意见
允许自行取回
审批中控制是否允许修改数据(甚至控制只能修改指定的某几个字段)
会签节点
流程与业务对象关联
一个业务对象只能关联一个流程
一个流程可以被多个业务对象关联;
流程监控
查看已提交但未审批通过或已审批通过的业务流程。
待办事项
主要查询我的待办,已办、已驳回信息。
健壮性
刷新参与者
若使用工作流的过程中发生以下几种情况,则需要刷新流程参与者:
(1)用户所属角色或所属单位发生变化。举例说明,送审节点只有“填报角色”的用户能参与,当新增“填报角色”的用户后,需要点击【刷新参与者】按钮,新增用户才能在数据录入中参与送审节点的流程。若原来是“送审角色”的用户被修改所属单位或者移除“送审角色”,也同样需要点击【刷新参与者】按钮。
(2)主体中新增单位。当新增单位后,需要启动该单位的流程,并点击【刷新参与者】按钮,新增单位的用户才能在数据录入中看到相应的流程按钮。
发布对未执行完流程的影响
工作流定义修改,未执行完的流程该如何处理
导入-导出
流程定义支持导入-导出;
导出的文件其实是一个BPMN文件
BMPN
BMPN (Business Process Modeling Notation 业务流程建模与标注),主要目标就是要提供被所有业务用户所理解的一套建模和标记语言,包括业务分析者、软件开发者、业务管理者与监察者。
2011年 OMG组织推出 BPMN2.0
标准,对BPMN进行了重新定义。BPMN定义了一个业务流程图,该流程图被设计用于创建业务流程操作的图形化模型。
BMPN 基本对象
BMPN 四个基本对象:
- 事件 Event
- 活动 Task
- 网关 Gateway
- 流向 Flow

事件
开始事件 startEvent
结束事件 endEvent
活动任务
用户任务 userTask
服务任务 serviceTask
脚本任务 scriptTask
网关
排他网关
并行网关
包含网关
事件网关
流向

常见流程模式
常见的业务流程模式有以下几种:
- 顺序流程
- 分支流程
- 并行流程
- 会签流程
顺序流程
最简单的流程模式,活动之间的转移线一般没有额外的条件判断。
分支流程
某个节点后有多个分支,根据不同条件执行不同的分支,即不同的数据会走不同的流程路径。(有多个分支,只会走其中一个)
并行流程
某个节点后有多个分支,忽略条件判断全部都走,最后汇聚到一起,在全部分支没有到达汇聚节点之前,汇聚节点不会往后执行。(有多个分支,全部都会走)
会签流程
会签,一般指并行会签,指在一个节点上设置多个人,如ABC三人,三人会同时收到待办,需全部同意之后,审批才可到下一审批节点。(可以设置该节点参与者的人数:全部或者最少审批人数)
BPMN 文件
.bpmn 是xml格式的流程定义文件
几款BPMN 做图软件:
IDEA 中安装 actiBPM 插件
微软 Visio (BPMN)
ProcessOn (在线流程设计器)
Activiti
Activiti 是一个工作流引擎, Activiti 将业务系统中复杂的业务流程抽取出来,使用专门的建模语言(BPMN)进行定义,业务系统按照预先定义的流程进行执行,实现了业务系统的业务流程由 Activiti 进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
Alfresco 软件在 2010年5月17日宣布开源 Activiti 业务流程管理(BPM)框架, 其首席架构师由业务流程管理 BPM 的专家 Tom Baeyens 担任, Tom Baeyens 就是原来 jbpm 的架构师,而 jbpm 是另一个非常有名的工作流引擎。
官网:activiti.org
本文基于Activiti 7.x 版本进行学习和研究。
开发环境
Activiti 7.x 版本至少需要JDK11,Activiti 7提供了直接与SpringBoot的集成版本,与SpringBoot环境天然集成。
Activiti 7使用Spring Security 管理用户和权限。
注:Activiti 最新的程序包在Maven中央仓库可能获取不到,应该使用Activiti 官方提供的私服仓库地址下载。
配置文件
activiti.cfg.xml
配置文件:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 配置 ProcessEngineConfiguration -->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!-- 配置数据库连接 -->
<property name="jdbcDriver" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activitiDB? createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf8"></property>
<property name="jdbcUsername" value="root"></property>
<property name="jdbcPassword" value="root"></property>
<!-- 配置创建表策略 :没有表时,自动创建 -->
<property name="databaseSchemaUpdate" value="true"></property>
</bean>
</beans>
数据表
Activiti 在运行时需要数据库支持,Activiti 的表都以 ACT_
开头。 第二部分是表示表的用途的两个字母标识。 用途也和服务的 API 对应。主要以下几类数据表:
ACT_RE_*:
'RE'表示 repository。这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。
ACT_RU_*:
'RU'表示 runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。Activiti 只在流程实例执行过程中保 存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
ACT_HI_*:
'HI'表示 history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
ACT_GE_*:
'GE'表示 general。 通用数据, 用于不同场景下
介绍:
表分类 | 表名 | 描述 |
---|---|---|
流程定义表 | ||
act_re_deployment | 部署单元信息 | |
act_re_procdef | 已部署的流程定义 | |
act_re_model | 模型信息 | |
运行实例表 | ||
act_ru_execution | 运行时流程执行实例 | |
act_ru_identitylink | 运行时用户关系信息,存储任务节点与参与者的相关信息 | |
act_ru_task | 运行时任务 | |
act_ru_variable | 运行时变量表 | |
act_ru_job | 运行时作业 | |
act_ru_event_subscr | 运行时事件 | |
历史数据 | ||
act_hi_actinst | 历史的流程活动实例 | |
act_hi_attachment | 历史的流程附件 | |
act_hi_comment | 历史的说明性信息 | |
act_hi_detail | 历史的流程运行中的细节信息 | |
act_hi_identitylink | 历史的流程运行过程中用户关系 | |
act_hi_procinst | 历史的流程实例 | |
act_hi_taskinst | 历史的任务实例 | |
act_hi_varinst | 历史的流程运行中的变量信息 | |
通用数据 | ||
act_ge_bytearray | 通用的流程定义和流程资源 | |
act_ge_property | 系统相关属性 |
工作流引擎
通过工作流引擎配置获取工作流引擎
方式一:通过 activiti.cfg.xml
配置文件获取
下面两种方式选一种即可:
//1.这种方式支持配置文件名称自定义
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource( "activiti.cfg.xml");
//2. 如果配置文件的名字默认就是activiti.cfg.xml,并且也是放在类路径下,我们就可以使用默认的方式来进行加载
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
方式二:通过java config 方式定义配置
@Configuration
public class ProcessEngineConfig {
@Bean
public ProcessEngineConfiguration processEngineConfiguration (){
ProcessEngineConfiguration engineConfiguration=ProcessEngineConfiguration.
createStandaloneProcessEngineConfiguration();
//设置数据库连接属性
engineConfiguration.setJdbcDriver("com.mysql.jdbc.Driver");
engineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/activiti?createDatabaseIfNotExist=true"
+ "&useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true");
engineConfiguration.setJdbcUsername("root");
engineConfiguration.setJdbcPassword("123");
engineConfiguration.setDatabaseSchemaUpdate("true");
return engineConfiguration;
}
}
注:如果使用mysql8.0驱动的版本,连接信息要加上 &nullCatalogMeansCurrent=true
参数,否则mybatis 不会自动创建表结构;
ProcessEngine
ProcessEngine 流程引擎接口提供对工作流操作的所有服务的访问
public interface ProcessEngine {
String VERSION = "7.1.0-M6";
String getName();
void close();
RepositoryService getRepositoryService();
RuntimeService getRuntimeService();
TaskService getTaskService();
HistoryService getHistoryService();
ManagementService getManagementService();
DynamicBpmnService getDynamicBpmnService();
ProcessEngineConfiguration getProcessEngineConfiguration();
}
通过工作流引擎获取所有API接口:
service | 作用 |
---|---|
RepositoryService | 提供对流程定义和部署存储库的访问的服务(操作与 re_ 相关的数据表) |
RuntimeService | 流程运行管理服务(操作与 ru_ 相关的数据表) |
HistoryService | 提供正在进行的和历史的流程实例信息 (操作与 hi_ 相关的数据表) |
TaskService | 提供对任务和表单相关操作的访问的服务 |
DynamicBpmnService | 提供对流程定义和部署存储库的访问的服务。 |
ManagementService | 引擎管理服务 |

注:相比较之前的版本,Activiti7 去掉了 FormService、IdentityService 两个接口,与其他接口进行了整合。
基本操作
设计工作流
设计工作流就是编写BPMN文件的过程。
主流的设计方式是通过拖拽的方式绘制业务流程。

部署流程定义
public void deploy() {
//获取仓库服务 :管理流程定义
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deploy = repositoryService.createDeployment()//创建一个部署的构建器
.addClasspathResource("leaveBillProcess.bpmn")//从类路径中添加资源
.name("请求单流程")
.category("办公类别")
.deploy();
System.out.println("部署的id: "+deploy.getId()+"部署的名称: "+deploy.getName());
}
与流程定义相关的几张表:
表 | 描述 |
---|---|
act_re_deployment | 流程部署表,用于存放流程定义的部署信息 |
act_re_procdef | 流程定义表,用于存放流程定义的属性信息 |
act_ge_bytearray | 资源文件表,部署流程时,会将 BPMN 流程定义文件保存为一条记录,如果部署的流程还包括其他资源,也会增加相应的记录,例如 png 图片文件 |
启动流程实例
public void startProcess(){
String processDefiKey="leaveBillProcess";
//取运行时服务
RuntimeService runtimeService = processEngine.getRuntimeService();
//取得流程实例
ProcessInstance pi = runtimeService.startProcessInstanceByKey(processDefiKey);
System.out.println("流程实例id: "+pi.getId());//流程实例id
System.out.println("流程定义id: "+pi.getProcessDefinitionId());//输出流程定义的id
}
启动流程定义后,会生成一个流程实例
act_ru_execution # 流程执行对象信息
act_ru_task # 正在运行的任务表
act_hi_procinst # 历史流程实例表
act_hi_taskinst # 历史流程任务表
查询任务
public void queryTask(@RequestParam String assignee){
//取得任务服务
TaskService taskService = processEngine.getTaskService();
//创建一个任务查询对象
TaskQuery taskQuery = taskService.createTaskQuery();
//办理人的任务列表
List<Task> list = taskQuery.taskAssignee(assignee).list();
//遍历任务列表
for(Task task:list){
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("任务的id:"+task.getId());
System.out.println("任务的名称:"+task.getName());
}
}
处理任务
public void compileTask(@RequestParam String taskId){
processEngine.getTaskService().complete(taskId);
}
流程定义 ProcessDefinition
设计流程图

BPMN文件内容:
<?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:activiti="http://activiti.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.activiti.org/test">
<process id="leaveBillProcess" name="请假审批流程" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="填报人" activiti:candidateUsers="jack,pot"></userTask>
<userTask id="usertask2" name="审批人1" activiti:candidateUsers="jack1"></userTask>
<userTask id="usertask3" name="审批人2" activiti:candidateUsers="jack2"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leaveBillProcess">
<bpmndi:BPMNPlane bpmnElement="leaveBillProcess" id="BPMNPlane_leaveBillProcess">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="180.0" y="250.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="320.0" y="240.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="900.0" y="250.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="215.0" y="267.0"></omgdi:waypoint>
<omgdi:waypoint x="320.0" y="267.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="425.0" y="267.0"></omgdi:waypoint>
<omgdi:waypoint x="510.0" y="267.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
BPMN 文件内容主要分为两部分:
定义流程对象,包含在
<process>
标签内部,定义开始节点、结束节点、用户任务和转移线等对象。定义图形元素的位置和宽高,包含在
<bpmndi:BPMNDiagram>
标签内部。
注:推荐使用Activiti 提供的官方Eclipse 插件开发,IDEA中的插件有一些缺陷。
流程定义属性:
属性 | 描述 |
---|---|
key | bpmn文件中流程定义的 id 属性被当做是流程定义的 key属性 |
version | 带有特定 key 的流程定义在第一次部署的时候,将会自动分配版本号为1,对于之后部署相同 key 的流程定义时候,这次部署的版本号将会设置为比当前最大的版本号大1的值。该 key 属性被用来区别不同的流程定义。 |
id | 流程定义中的id属性被设置为 {processDefinitionKey}:{processDefinitionVersion}: {generated-id}, 这里的 generated-id 是一个唯一的数字被添加,用于确保在集群环境中缓存的流程定义的唯一性。 |
name | bpmn文件中的流程模型的 name 属性被当做是流程定义的 name 属性。如果bpmn文件中name 属性并没有指定,那么 id 属性被当做是 name。 |
部署流程定义
看下面一个例子:
<definitions id="myDefinitions" >
<process id="leaveBillProcess" name="请假申请单" isExecutable="true" processType="Public">
......
当部署了这个流程定义之后,在数据库中的流程定义看起来像这样:
id | key | name | version |
---|---|---|---|
leaveBillProcess:1:c4be60fc-1d30-11ed-a12f-005056c00001 | leaveBillProcess | 请假申请单 | 1 |
假设我们修改流程定义(例如改变用户任务),然后重新部署,数据库中就会多一条版本数据:
id | key | name | version |
---|---|---|---|
leaveBillProcess:1:c4be60fc-1d30-11ed-a12f-005056c00001 | leaveBillProcess | 请假申请单 | 1 |
leaveBillProcess:2:7efc4bf4-1d31-11ed-a12f-005056c00001 | leaveBillProcess | 请假申请单 | 2 |
此时启动流程实例时, runtimeService.startProcessInstanceByKey(“leaveBillProcess”) 方法被调用,它将会使用流程定义版本号为2的,因为这是最新版本的流程定义。可以说每次流程定义创建流程实例时,都会默认使用最新版本的流程定义。
停用和激活
流程定义可以停用和激活,停用后的流程定义无法启动新的流程实例。但是已启用的流程实例不受影响,依然可以继续使用;
public interface RepositoryService {
void suspendProcessDefinitionByKey(String processDefinitionKey);
void activateProcessDefinitionByKey(String processDefinitionKey);
}
流程实例 ProcessInstance
启动流程实例
启动流程实例的接口:
public interface RuntimeService {
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String businessKey);
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String businessKey,
Map<String,Object> variables);
}
启动流程实例时一般会指定业务主键,和业务数据绑定;还可以设置流程变量(可选),流程变量在整个流程实例中是共享的;
停用和激活
流程实例可以停用和激活
public interface RuntimeService {
void suspendProcessInstanceById(String processInstanceId);
void activateProcessInstanceById(String processInstanceId);
}
流程实例停用后,待处理的任务不能继续处理了,否则抛出异常。
执行路径 Execution
Execution 表示流程实例中的执行路径(执行环境)
- 当流程中没有分支时,Execution等同于ProcessInstance,Execution 和流程实例是一对一的关系;(Execution_ID 等于PROC_INST_ID)
- 当流程中存在分支(fork, parallel gateway),则在分支口会形成子Execution,此时Execution 和流程实例是多对一的关系;
Execution 和分支是一对一的关系,有多少个分支就有多少个execution。Execution 本身有父子关系,在分支口形成子Execution ;
任务 Task
本节讲解的是用户任务 UseTask
属性:
属性 | 描述 |
---|---|
id | id |
name | 任务名称 |
owner | 所有者 |
assignee | 受让人 |
ProcessDefinitionId | 流程定义ID |
ProcessInstanceId | 流程实例ID |
ExecutionId | 执行路径ID |
CreateTime | 创建时间 |
DueDate | 持续时间 |
Category | 任务类别(可选) |
ProcessVariables | 任务所属流程实例中的变量 |
TaskLocalVariables | 任务的私有变量 |
BusinessKey | 业务主键 |
分配候选人
activiti 支持多种给用户分配任务的方式,一般的,分配多个候选人的方案是拓展性最好的一种方式,也是实际业务中是最常用的。
任务中有候选人和候选组两个属性:(两个属性都是数组,元素之间使用英文逗号隔开)
- candidateUsers 候选人
- candidateGroups 候选组
示例:
<userTask id="usertask1" name="填报人" activiti:candidateUsers="jack,tom,nike"></userTask>
表示三个人都可以查看和处理该任务,默认,其中有一个人处理任务(审批通过),就会进入下一个节点任务。
认领任务与取消认领
认领任务
上面的分配一组候选人后,默认候选人中的其中一个处理任务,该节点就会完成。activiti 还提供一个认领任务的场景(非必须),就是该任务关联的所有候选人可以申领任务,其中一个人认领成功,其他候选人就看不到该代办任务了。
// 认领任务
public void claimTask(String userId,String taskId) {
taskService.claim(taskId,userId);
}
认领任务 claim() 和分配责任人 setAssignee() 的区别:
- claim() 前会校验当前任务有没有责任人,如果有抛异常;setAssignee() 只改变责任人属性,不做校验;
- claim() 会修改任务的 assignee 和 claim_time 属性,即责任人和认领时间,setAssignee() 只改变assignee 属性;
注:
认领任务和设置的候选人没有强关联关系,只是认领任务通常和候选人搭配使用。
取消认领
// 取消认领
public void unclaimTask(String taskId) {
taskService.unclaim(taskId);
}
注:
unclaim() 等同于 claim(taskId,null),即清空任务的 assignee 和 claim_time 属性;取消认领后,其他候选人可以继续查看和处理改任务。
直接分配单个责任人
用户任务可直接分配给一个用户。
固定分配
在设计流程定义时,可直接在用户任务上通过assignee 属性指定一个固定的责任人。
<userTask id="usertask2" name="审批人B" activiti:assignee="jack"></userTask>
动态分配
固定分配责任人的方式拓展性不是很好,如果想要修改某节点责任人就需要修改流程定义并且重新部署。我们可以使用流程变量+表达式的方式动态分配责任人:
<userTask id="usertask2" name="审批人B" activiti:assignee="${assignee}"></userTask>
assignee 变量在流程启动或者每个节点完成的时候再设置值,就可以实现审批人的动态分配。如审批人B在审批的时候,可随意指定下一个节点的审批人。
任务委派和解决
activiti 支持这样一个场景:
当流程节点流转到职员A时,因某种原因,职员A需要职员B临时处理一下,然后自己才能审批通过,此时职员A需要将任务暂时委派给职员B,等待B处理完成后,自己再继续处理。
public interface TaskService {
/** 委托人将任务委派给指定用户,这意味着受让人assignee已经设置并且委派状态设置为DelegationState.PENDING
,如果没有为任务设置所有者,则将所有者设置为任务的当前受让人。*/
void delegateTask(String taskId, String userId);
/** 受让人assignee完成此任务并且将其发回给所有者。仅当此任务为委托状态时才能调用。此方法返回后,delegationState 设置为 DelegationState.RESOLVED */
void resolveTask(String taskId);
}
任务的委派状态有两种:DelegationState
PENDING:
所有者委派了任务并希望在受让人解决任务后查看结果。 当受让人完成任务时,该任务被标记为已解决并将所有者设置为受让人,以便所有者在 ToDo 中获取此任务。
RESOLVED:
受让人已解决任务,受让人再次设置为所有者,并且所有者现在在待办事项列表中找到此任务以供审查。 所有者现在能够完成任务。
注:
当任务的委派状态为 PENDING 情况下,不能直接处理完成,否则报错,必须经过受让人处理为RESOLVED 状态后,才能处理完成。
受让人调用resolveTask() 解决委托后,该任务并没有被自动完成,还需要调用complete() 方法处理完成任务。
受让人没解决委托之前,也就是PENDING 状态下,该任务可以变更受让人,但同时只能有一个受让人解决委托。受让人A解决委托之后,也就是 RESOLVED 状态下,可以继续委托给受让人B,等待B解决委托。
任务私有变量
每个任务中可以存储本地私有变量,与同一个流程实例的其他任务不共享,下面会和流程实例变量一起详细讲解。
查看代办任务
TaskQuery 接口提供了丰富的任务查询方法:
public interface TaskQuery extends TaskInfoQuery<TaskQuery, Task> {
/** 仅查询没有设置受让人的任务 */
TaskQuery taskUnassigned();
/** 查询指定委派状态的任务 */
TaskQuery taskDelegationState(DelegationState delegationState);
/** 查询已申领或分配给用户或等待用户申领的任务(候选用户)*/
TaskQuery taskCandidateOrAssigned(String userIdForCandidateAndAssignee);
/** 查询已申领或分配给用户或等待用户申领的任务(候选用户或组) */
TaskQuery taskCandidateOrAssigned(String userIdForCandidateAndAssignee, List<String> usersGroups);
/** 仅查询没有子任务的任务 */
TaskQuery excludeSubtasks();
/** 只选择挂起的任务,因为它的流程实例被挂起。*/
TaskQuery suspended();
/** 仅选择活动的任务(即未挂起) */
TaskQuery active();
}
示例:
public void queryTask(String userId) {
TaskQuery taskQuery = taskService.createTaskQuery();
// 办理人的任务列表
List<Task> list = taskQuery.taskCandidateOrAssigned(userId).list();
// 遍历任务列表
for (Task task : list) {
System.out.println("任务的id:" + task.getId());
System.out.println("任务的名称:" + task.getName());
System.out.println("业务主键:" + task.getBusinessKey());
}
}
处理任务
IdentityLinkType
中定义了参与任务处理的用户类型:(共五种)
- starter 发起人
- candidate 候选人
- participant 参与者
- owner 所有者
- assignee 受让人
public void compileTask(String taskId) {
//完成任务
taskService.complete(taskId);
}
完成任务是设置流程变量和审批意见:
public void compileTask(String userId,String taskId) {
//设置流程变量(可选)
Long handleNum = taskService.getVariableInstance(taskId, "handleNum").getLongValue();
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("handleNum", handleNum+=1);
//添加处理审批意见(可选)
Authentication.setAuthenticatedUserId(userId);//需要添加此句否则审批意见表ACT_HI_COMMENT中的审批人为空
TaskQuery taskQuery = taskService.createTaskQuery();
Task singleResult = taskQuery.taskId(taskId).singleResult();
String processInstanceId = singleResult.getProcessInstanceId();
taskService.addComment(taskId, processInstanceId, "我的审批意见");
//处理任务前校验当前用户有无处理该任务的权限
Task authTask = taskQuery.taskId(taskId).taskCandidateOrAssigned(userId).singleResult();
if(authTask==null) {
throw new RuntimeException("当前用户没有处理该任务的权限!");
}
//完成任务
taskService.complete(taskId,variables);
}
注:
1. 如果流程实例被停用,处理任务就会抛出异常。
1. complete()方法本身不校验当前用户是否为该任务的责任人或候选人,权限校验需要自己完善。
查看历史处理
当用户处理完成任务后,activiti 提供了记录历史信息的功能,历史信息存储到 act_hi_
开头的数据表中。
activiti 提供了四种历史信息记录级别:(HistoryLevel 枚举中定义)
- none: 不记录历史流程,性能高,流程结束后不可读取。
- activity: 归档流程实例和活动实例,流程变量不同步。
- audit: 在activiti基础上同步变量值,保存表单属性。
- full: 记录所有实例和变量细节变化,最完整的历史记录(性能较差,一般不建议开启)。
注:
在activiti7之前的版本中,默认级别是audit,但在activiti7的某些版本中,默认级别为none,即不记录历史信息,注意不同版本的区别
在配置文件中设置历史信息记录级别:
# 保存历史数据级别设置为audit级别
spring.activiti.history-level=audit
任务监听
实现任务监听的接口:TaskListener
可以监听到的事件(时机)有:
/** 任务创建 */
String EVENTNAME_CREATE = "create";
/** 任务指派 */
String EVENTNAME_ASSIGNMENT = "assignment";
/** 任务完成 */
String EVENTNAME_COMPLETE = "complete";
/** 任务删除 */
String EVENTNAME_DELETE = "delete";
/** 以上所有事件 */
String EVENTNAME_ALL_EVENTS = "all";
示例:
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
String eventName = delegateTask.getEventName();
if ("create".equals(eventName)) {
//TODO 监听任务创建事件
}
if ("complete".equals(eventName)) {
//TODO 监听任务完成事件
}
}
}
配置监听器到任务:
<userTask activiti:candidateUsers="jack1" activiti:exclusive="true" id="_5" name="审批人1">
<extensionElements>
<activiti:taskListener class="com.example.activiti7.listener.MyTaskListener" event="create"/>
<activiti:executionListener class="com.example.listener.MyExecutionListener" event="start"/>
</extensionElements>
</userTask>
多实例循环 Multi-Instance
多实例节点是在业务流程中定义重复环节的一个方法。此方式可以实现业务流程中的会签场景。
属性 | 说明 |
---|---|
activiti:candidateUsers=”jack,nike,tom” | 该节点设置多个人审核 |
loopCardinality=2 | 设置循环的次数 |
isSequential=false | 设置是否串行处理 |
completionCondition=${handleNum >= 2} | 设置完成条件(如只要2个人审过就行) |
图形标记:
如果节点是多实例的,会在节点底部显示三条短线:三条横线表示顺序执行,三条竖线表示实例会并行执行。
流程变量 Variable
流程变量的主要作用是传递业务参数到流程实例中,以动态控制流程走向。
设置流程变量
我们可以在启动流程实例时,设置流程变量:
public interface RuntimeService {
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String businessKey,
Map<String,Object> variables);
}
也可以在每个任务完成时设置流程变量:
public interface TaskService {
void complete(String taskId, Map<String, Object> variables);
void complete(String taskId, Map<String, Object> variables, Map<String, Object> transientVariables);
}
相关联的数据表
数据表 | 描述 |
---|---|
act_ru_variable | 正在执行的流程变量表 |
act_hi_varinst | 流程变量历史表 |
实例变量和任务变量
setVariable 和 setVariableLocal 的区别:
setVariable 的作用域在一个流程实例中有效,一个流程实例的不同任务,都可以获取到;
setVariableLocal 的作用域只在一个任务中,该变量会关联task ID,在其他任务节点是获取不到的;
支持的变量类型
Long Double Text
Activiti 支持存储复杂的 Java 对象(序列化)作为流程变量,或者整个 XML 文档作为字符串。
顺序流 Sequence Flow
顺序流也叫转移线
条件表达式 UEL
事件监听
<process>
<startEvent id="startevent1" name="Start"></startEvent>
<endEvent id="endevent1" name="End"></endEvent>
<userTask id="usertask2" name="User Task"></userTask>
<sequenceFlow id="flow3" name="flow3" sourceRef="startevent1" targetRef="usertask2">
<extensionElements>
<activiti:executionListener event="take" class="com.example.activiti7.listener.MyExecutionListener"> </activiti:executionListener>
</extensionElements>
</sequenceFlow>
<sequenceFlow id="flow4" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
</process>
实现 ExecutionListener 接口,判断事件类型即可:
@Component
public class MyExecutionListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) {
String eventName = execution.getEventName();
if ("start".equals(eventName)) {
//TODO 监听流程开始事件
}
if ("end".equals(eventName)) {
//TODO 监听流程结束事件
}
if ("take".equals(eventName) {
//TODO 监听流程转移事件(转移线)
}
}
}
配置监听器到任务:
<userTask activiti:candidateUsers="jack1" activiti:exclusive="true" id="_5" name="审批人1">
<extensionElements>
<activiti:taskListener class="com.example.activiti7.listener.MyTaskListener" event="create"/>
<activiti:executionListener class="com.example.listener.MyExecutionListener" event="start"/>
</extensionElements>
</userTask>
监听器 Listener
activiti 提供两种监听器接口来拓展:
- 任务监听器 TaskListener
- 流程监听器 ExecutionListener
任务监听器 TaskListener
任务监听器监听任务相关的事件:
事件 | 描述 |
---|---|
create | 任务创建 |
assignment | 任务指派责任人 |
complete | 任务完成 |
delete | 任务删除 |
all | 以上所有事件 |
流程监听器 ExecutionListener
流程监听器监听流程实例相关的事件:
事件 | 描述 |
---|---|
start | 流程开始 |
end | 流程结束 |
take | 流程转移 |
配置监听器
任务配置监听器:
<userTask activiti:candidateUsers="jack1" activiti:exclusive="true" id="_5" name="审批人1">
<extensionElements>
<activiti:taskListener class="com.example.activiti7.listener.MyTaskListener" event="create"/>
<activiti:executionListener class="com.example.listener.MyExecutionListener" event="start"/>
</extensionElements>
</userTask>
顺序流配置监听器:
<sequenceFlow id="_7" sourceRef="_4" targetRef="_5">
<extensionElements>
<activiti:executionListener class="com.example.listener.MyExecutionListener" event="start"/>
</extensionElements>
</sequenceFlow>
注:
任务上可以配置任务监听器和流程监听器两种,顺序流上只能配置流程监听器。
网关
排他网关 Exclusive Gateway
排他网关会寻找第一条符合条件的流向。(如果多个条件都符合,只会选择其中一条)
定义排他网关:
<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" />
注:
并行网关 Parallel Gateway
并行网关会忽略条件判断,将流程分成多条分支,然后把多条分支汇聚成一条(注意:并行网关一定是成对出现的,有分支也有汇聚)。
分支:经过并行网关的所有流,都会并行执行。
汇聚:等所有流都到达并行网关之后,流程才会通过并行网关。

包含网关 InclusiveGateway
包含网关可以看做是排他网关和并行网关的结合体。包含网关既支持在顺序流上定义条件并解析,还支持选择多条顺序流并行执行。拥有排他网关和并行网关的两种特性。
事件网关 EventGateway
略
表达式
Activiti使用UEL进行表达式解析(统一表达语言),是Java EE6规范的一部分,为了在所有环境中支持最新UEL规范的所有功能,Activiti使用了JUEL的修改版。
表达式主要有两类:
- 值表达式
- 方法表达式
值表达式
值表达式:解析为一个值,默认情况下,所有流程变量都可以使用,此外,所有spring-bean 也可以在表达式中使用,示例:
${myVar}
${myBean.myProperty}
方法表达式
方法表达式:调用带有或不带有参数的方法,调用不带参数的方法时,请确保在方法名称后添加空括号(因为这会将表达式与值表达式区分开),传递的参数可以是文字值或自行解析的表达式,示例:
${printer.print()}
${myBean.addNewOrder('orderName')}
${myBean.doSomething(myVar, execution)}
异步执行器 AsyncExecutor
AsyncExecutor
配置发送邮件
高级进阶
更新流程
用activiti 做的流程,后期由于业务更改或者部署错误导致流程需要修改,此时,处于流程中的那些数据(已经开始流程,但是还没结束流程的)只能按照原有的流程定义去执行,而不能用新流程定义。目前官方没有提供直接的接口,只能自己去实现。
拓展自定义属性
<userTask id="tsk_upload" name="填报">
<extensionElements>
<activiti:taskListener event="create" delegateExpression="${ActivitiTaskCreateListenerBean}"/> <activiti:actorStrategy type="com.jiuqi.nr.bpm.impl.Actor.CanUploadActorStrategy"/>
<activiti:action code="act_upload" name="提交" needComment="false"/>
<activiti:retrivable value="false"/>
<activiti:formEditable value="true"/>
</extensionElements>
</userTask>
发送邮件
activiti 提供邮件任务发送邮件
多租户
设计模式
命令行模式
public class TaskServiceImpl extends ServiceImpl implements TaskService {
public Task newTask(String taskId) {
return commandExecutor.execute(new NewTaskCmd(taskId));
}
public Task saveTask(Task task) {
return commandExecutor.execute(new SaveTaskCmd(task));
}
public void deleteTask(String taskId) {
commandExecutor.execute(new DeleteTaskCmd(taskId, null, false));
}
}
Activiti cloud
Flowable
Flowable 是一个使用 Java 编写的轻量级业务流程引擎,使用 Apache V2 license 协议开源。2016 年 10 月,Activiti 工作流引擎的主要开发者离开 Alfresco 公司并在 Activiti 分支基础上开启了 Flowable 开源项目。基于 Activiti v6 beta4 发布的第一个 Flowable release 版本为6.0。以 JAR 形式发布使得 Flowable 可以轻易加入任何Java环境。 另外,也可以使用 Flowable REST API 进行 HTTP 调用。
Flowable 项目中包括 BPMN(Business Process Model and Notation)引擎、CMMN(Case Management Model and Notation)引擎、DMN(Decision Model and Notation)引擎、表单引擎(Form Engine)等模块。也有许多Flowable 应用(Flowable Modeler、Flowable Admin、Flowable IDM 与 Flowable Task),并提供了直接可用的 UI 示例。模块之间协作关系可以参考下图:
README
作者:银法王
修改记录:
2021-04-14 第一次修订
2022-08-08 完善
参考: