业务流程管理系统之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
image-20210414174643620

事件

开始事件 startEvent

结束事件 endEvent

活动任务

用户任务 userTask

服务任务 serviceTask

脚本任务 scriptTask

网关

排他网关

并行网关

包含网关

事件网关

流向

image-20210414175858659

常见流程模式

常见的业务流程模式有以下几种:

  • 顺序流程
  • 分支流程
  • 并行流程
  • 会签流程

顺序流程

​ 最简单的流程模式,活动之间的转移线一般没有额外的条件判断。

分支流程

​ 某个节点后有多个分支,根据不同条件执行不同的分支,即不同的数据会走不同的流程路径。(有多个分支,只会走其中一个)

并行流程

​ 某个节点后有多个分支,忽略条件判断全部都走,最后汇聚到一起,在全部分支没有到达汇聚节点之前,汇聚节点不会往后执行。(有多个分支,全部都会走)

会签流程

​ 会签,一般指并行会签,指在一个节点上设置多个人,如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&amp;useUnicode=true&amp;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 引擎管理服务
image-20210914161801343

注:相比较之前的版本,Activiti7 去掉了 FormService、IdentityService 两个接口,与其他接口进行了整合。

基本操作

设计工作流

设计工作流就是编写BPMN文件的过程。

主流的设计方式是通过拖拽的方式绘制业务流程。

image-20220817175019376

部署流程定义

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

设计流程图

image-20220817175019376

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() 的区别:

  1. claim() 前会校验当前任务有没有责任人,如果有抛异常;setAssignee() 只改变责任人属性,不做校验;
  2. 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

并行网关会忽略条件判断,将流程分成多条分支,然后把多条分支汇聚成一条(注意:并行网关一定是成对出现的,有分支也有汇聚)。

分支:经过并行网关的所有流,都会并行执行。

汇聚:等所有流都到达并行网关之后,流程才会通过并行网关。

image-20220811091515094

包含网关 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 完善

参考:

activiti 官方文档

Flowable 开源版官方指南

https://www.modb.pro/db/215121


业务流程管理系统之Activiti
http://jackpot-lang.online/2021/04/08/系统技术架构设计/业务流程管理系统之Activiti/
作者
Jackpot
发布于
2021年4月8日
许可协议