Activiti工作流 - 学习笔记

202 阅读35分钟

1. Activiti介绍

Activiti是目前使用最为广泛的开源工作流引擎,2010年5月就正是启动了。在了 解Activiti之前,我们首先要了解下什么是工作流。

1.1 工作流WorkFlow

关于什么是工作流,有一个官方的定义: 工作流是指一类能够完全自动执行的经 营过程,根据一系列规程规则,将文档、信息或任务在不同的执行者之间进行传递 和执行。

其实说直白一点,就是业务上一个完整的审批流程。例如员工的入职、请 假、出差、采购等等、还有一些关键业务如订单申请、合同审核等等,这些过程, 都是一个工作流。 对于工作流,传统的处理方式往往需要有人拿着各类的文件,在多个执行部门之 间不断的审批。而当我们开始用软件来协助处理这一类审批流程时,就开始出现了 工作流系统。工作流系统可以减少大量的线下沟通成本,提高工作效率。

有了工作流系统之后,才开始出现工作流引擎。在没有专门的工作流引擎之前, 我们为了实现这样的流程控制,通常的做法都是采用状态字段的方式来跟踪流程的 变化情况。例如对一个员工请假请求,我们会定义已申请、组长已审核、部门经理 已审核等等这样一些状态,然后通过这些状态来控制不同的业务行为,比如部门经 理角色只能看到组长已审核通过的,并且请假天数超过3天的订单等等。 这种实现方式实现起来比较简单,也是软件系统中非常常用的一种方式。但是这 种通过状态字段来进行流程控制的方式还是有他的弊端。

一方面:整个流程定义不够清晰。业务流程是分散在各个业务阶段中的,从代码 的角度非常难以看到整个流程是如何定义的。

另一方面:当流程发生变更时,这种方式编写的代码就需要做非常大的变更。例 如从三级审批要增加为四级审批甚至是协同审批,那各个业务阶段的审批流程都需 要随之做大量的变更。

正是出于这些痛点,后面才有了工作流引擎。使用工作流引擎后,整个审批流程 可以在同一个地方进行整体设计,并且当审批流程发生变更时,业务程序也可以不 用改变。这样业务系统的适应能力就得到了极大提升。 其实引擎的思想无处不在。我们有Drools规则引擎,可以在程序不发生 变动的情况下,集中定义业务规则并进行修改。Aviator表达式引擎,可 以快速计算某一个表达式的结果。搜索引擎,可以快速进行统一搜索等 等。其核心思想都是将业务之间的共性抽取出来,减少业务变动对程序 的影响。

1.2 Activiti工作流引擎

Activiti正是目前使用最为广泛的开源工作流引擎。Activiti的官网地址是:www.activiti.org/

image-20220119143555109

他可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进 行定义。业务流程按照预先定义的流程执行,整个实现流程完全由activiti进行管 理,从而减少业务系统由于流程变更进行系统改造的工作量,从而减少系统开发维 护成本,提高系统的健壮性。所以使用Activiti,重点就是两个步骤,首先使用 BPMN定义流程,然后使用Activiti框架实现流程。

1.3 建模语言BPMN

谈到BPMN,首先就要谈BPM。 BPM即Business Process Managemenet,业 务流程管理。是一种规范化的构造端到端的业务流程,以持续的提高组织业务效 率。在常见的商业管理教育如EMBA、MBA中都包含了BPM的课程。

有了BPM的需求,就出现了BPM软件。他是根据企业中业务环境的变化,推进人 与人之间,人与系统之间以及系统与系统之间的整合及调整的经营方法域解决方案 的IT工具。通过对企业业务流程的整个生命周期进行建模、自动化、管理监控和优 化,使企业成本降低,利润得到提升。BPM软件在企业中应用非常广泛,凡是有业 务流程的地方都可以使用BPM进行管理。比如企业人事办公管理、采购流程管理、 公文审批流程管理、财务管理等。

而BPMN是Business Process Model And Notation 业务流程模型和符号,就是 用来描述业务流程的一种建模标准。BPMN最早由BPMI(BusinessProcess Management Initiative)方案提出。由一整套标准的业务流程建模符号组成。使用 BPMN可以快速定义业务流程。

BPMN最早在2004年5月发布。2005年9月开始并入OMG(The Object Managemenet Group)组织。OMG于2011年1月发布BPMN2.0的最终版本。 BPMN是目前被各大BPM厂商广泛接受的BPM标准。Activiti就是使用BPMN2.0进 行流程建模、流程执行管理。 整个BPMN是用一组符号来描述业务流程中发生的各种事件的。BPMN通过在这 些符号事件之间连线来描述一个完整的业务流程。

而对于一个完整的BPMN图形流程,其实最终是通过XML进行描述的。通常,会 将BPMN流程最终保存为一个.bpmn的文件,然后可以使用文本编辑器打开进行查 看。而图形与xml文件之间,会有专门的软件来进行转换。 关于如何配置一个工作流,在后面的实战过程中我们会接触到。

1.4 Activiti使用步骤

通常使用Activiti时包含以下几个步骤:

  • 部署activiti: Activiti包含一堆Jar包,因此需要把业务系统和Activiti的环境集成 在一起进行部署。
  • 定义流程: 使用Activiti的建模工具定义业务流程.bpmn文件。
  • 部署流程定义: 使用Activiti提供的API把流程定义内容存储起来,在Acitivti执行 过程汇总可以查询定义的内容。Activiti是通过数据库来存储业务流程的。
  • 启动流程实例: 流程实例也叫ProcessInstance。启动一个流程实例表示开始一 次业务流程的运作。例如员工提交请假申请后,就可以开启一个流程实例,从而 推动后续的审批等操作。
  • 用户查询待办任务(task):因为现在系统的业务流程都交给了activiti管理,通过 activiti就可以查询当前流程执行到哪个步骤了。当前用户需要办理哪些任务也就 同样可以由activiti帮我们管理,开发人员不需要自己编写sql语句进行查询了。
  • 用户办理任务:用户查询到自己的待办任务后,就可以办理某个业务,如果这个 业务办理完成还需要其他用户办理,就可以由activiti帮我们把工作流程往后面的 步骤推动。
  • 流程结束:当任务办理完成没有下一个任务节点后,这个流程实例就执行完成 了。

2. Activiti环境搭建

使用Activiti需要的基本环境包括: JDK 8或以上版本;然后需要一个数据库用来保存流程定义数据,建议mysql 5或以上版本。

2.1 安装插件

开发工具IDEA,在IDEA中需要安装Activiti的流程定义工具插件actiBPM。目前该插 件从2014年11月后就没有再更新,对于IDEA版本只支持到2019.1。新版本的IDEA 已经无法从插件市场搜索到该插件。安装时,可以到jetBrain的插件市场 plugins.jetbrains.com/ 搜索actiBPM插件,下载到本地后,从本地安装该插件。

image-20220119145257640

安装完成后,就可以使用这个插件在项目中编辑.bpmn的文件来定义业务流程了。 但是这个文件之前介绍过,他的本质是一个xml文本文件,所以还是需要更多的了解 xml的配置方式。

可能最新版本的IDEA无法安装该插件,可用Activiti BPMN visualizer 和 JBoss jBPM代替!

2.2 初始化数据库

我们这里选择mysql数据库。接下来按照以下步骤来初始化activiti所需要的数据 表。

  • 在mysql中创建一个数据库activiti,将会用来创建activiti相关的表。

    CREATE DATABASE activiti DEFAULT CHARACTER SET utf8;
    

    然后,activiti需要依赖的业务表有25张,而activiti中提供了工具帮我们生成所需要的表。

  • 创建一个maven工程BasicDemo,在pom.xml中引入以下依赖:

    <dependencies>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-engine</artifactId>
            <version>7.1.0.M6</version>
        </dependency>
    
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring</artifactId>
            <version>7.1.0.M6</version>
        </dependency>
    
        <!-- bpmn 模型处理 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-model</artifactId>
            <version>7.1.0.M6</version>
        </dependency>
    
        <!-- bpmn 转换 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-converter</artifactId>
            <version>7.1.0.M6</version>
        </dependency>
    
        <!-- bpmn json 数据转换 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>7.1.0.M6</version>
        </dependency>
    
        <!-- bpmn 布局 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-layout</artifactId>
            <version>7.1.0.M6</version>
            <scope>test</scope>
        </dependency>
    
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
    
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
    
        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
    
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>
    
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    
        <!-- log start -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.32</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.30</version>
        </dependency>
    </dependencies>
    
  • 添加log4j日志配置

    这里采用的是log4j来记录日志,所以需要在resources目录下创建log4j.properties 文件来对日志进行配置

  # Set root category priority to INFO and its only appender to CONSOLE.
  #log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
  log4j.rootCategory=debug, CONSOLE, LOGFILE
  # Set the enterprise logger category to FATAL and its only appender to
  CONSOLE.
  log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
  # CONSOLE is set to be a ConsoleAppender using a PatternLayout.
  log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
  log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
  log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n
  # LOGFILE is set to be a File appender using a PatternLayout.
  log4j.appender.LOGFILE=org.apache.log4j.FileAppender
  log4j.appender.LOGFILE.File=.\activiti.log
  log4j.appender.LOGFILE.Append=true
  log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
  log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n
  • 添加activiti的配置文件

    activiti默认就会使用mysql来创建表。创建时需要先创建一个配置文件 activiti.cfg.xml,来对数据源信息进行定义。

    在resources目录下创建activiti.cfg.xml文件。

    注意:

    # 这个目录其实就是classpath下的默认位置。这是activiti默认读取的目录和文件。
    # 创建在其他目录下也是可以的,但是就需要在生成时指定文件的目录和名字。
    

    配置文件的基础内容如下: 这里主要是定义几个namespace。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/springbeans.xsd
    http://www.springframework.org/schema/contex
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd">
    </beans>
    
  • 在activiti.cfg.xml中进行配置

    我们可以在activiti.cfg.xml中添加关于数据库的基础配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/activiti?serverTimezone=GMT%2B8&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
            <property name="username" value="root" />
            <property name="password" value="shw123zxc"/>
            <property name="maxActive" value="3"/>
        </bean>
    
        <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
            <property name="dataSource" ref="dataSource" />
            <!--activiti数据库表处理策略-->
    
            <property name="databaseSchemaUpdate" value="true" />
        </bean>
    
    </beans>
    

    注意:

    # processEngineConfiguration这个名字最好不要修改。这是activiti读取的默认Bean名字。
    # 在processEngineConfiguration中也可以直接配置jdbcDriver、jdbcUrl、jdbcUsername、jdbcPassword几个属性。
    # 关于databaseSchemaUpdate这个属性,稍微跟踪一下源码就能看到他的配置方式:默认是false;表示不创建数据库,只是检查数据库中的表结构,不满足就会抛出异常create-drop:表示在引擎启动时创建表结构,引擎处理结束时删除表结构。true:表示创建完整表机构,并在必要时更新表结构。
    
  • 编写java程序生成表

    创建一个测试类,调用activiti的工具类,直接生成activiti需要的数据库表。代码如下:

    public class TestCreatTable {
    
        @Test
        public void testCreateTable(){
            // 默认创建方式
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
            //通用的创建方式,指定配置文件名和Bean名称
            // ProcessEngineConfiguration processEngineConfiguration =
            // ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("ac tiviti.cfg.xml", "processEngineConfiguration");
            // ProcessEngine processEngine1 = processEngineConfiguration.buildProcessEngine();
            System.out.println(processEngine);
        }
    }
    

    注意:

    # 从这个代码就能看出我们之前那些默认配置的作用。
    # ProcessEngines.getDefaultProcessEngine()这行代码默认就会去读取classpath:下的activiti.cfg.xml和activiti-context.xml两个配置文件。并且从spring容器中加载名为processEngineConfiguration的Bean。
    

    执行这个脚本就会完成mysql的表结构创建。如果执行正常,可以看到执行了一大 堆的sql语句,最终打印出一行日志

    org.activiti.engine.impl.ProcessEngineImpl@6b7906b3
    

    这就表示引擎创建成功了。同时在mysql中可以看到activiti用到的25张表。

    image-20220119161205972

# 这些表机构通常也可以导出成sql文件,然后直接进行移植。但是考虑到
# 不同版本可能会有微调,所以通常不建议以sql文件的方式移植。

2.3 表结构解读

从这些刚才创建的表中可以看到,activiti的表都以act_开头。第二个部分表示表 的用途。用途也和服务的API对应。

ACT_RE:'RE'表示 repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。 ACT_RU:'RU'表示 runtime。 这些运行时的表,包含流程实例,任务,变量,异 步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。

ACT_HI:'HI'表示 history。这些表包含历史数据,比如历史流程实例,变量,任务等等。

ACT_GE:GE 表示general。通用数据,用于不同场景下

完整的数据库表作用如下:

image-20220119161440499|454

image-20220119161454569|496

2.4 Activiti核心类

当拿到ProcessEngine之后,我们可以简单的看一下他的方法,这是四个核心的service,围绕activiti的核心业务功能大都通过这几个service来组成。

image-20220119162829252

image-20220119162913165|566

简单介绍:

  • RepositoryService

    是activiti的资源管理类,提供了管理和控制流程发布包和流程定义的操作。使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。

    除了部署流程定义以外还可以:查询引擎中的发布包和流程定义。

    暂停或激活发布包,对应全部和特定流程定义。暂停意味着它们不能再执行任何操 作了,激活是对应的反向操作。获得多种资源,像是包含在发布包里的文件,或引擎自动生成的流程图。

    获得流程定义的pojo版本,可以用来通过java解析流程,而不必通过xml。

  • RuntimeService

    Activiti的流程运行管理类。可以从这个服务类中获取很多关于流程执行相关的信息。

  • TaskService

    Activiti的任务管理类。可以从这个类中获取任务的信息。

  • HistoryService

    Activiti的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根 据配置),比如流程实例启动时间,任务的参与者, 完成任务的时间,每个流程实 例的执行路径,等等。这个服务主要通过查询功能来获得这些数据。

  • ManagementService

    Activiti的引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护。


3. Activiti入门

创建Activiti工作流的主要步骤包含以下几步:

  • 定义流程。按照BPMN的规范,使用流程定义工具,将整个流程描述出来
  • 部署流程。把画好的BPMN流程定义文件加载到数据库中,生成相关的表数据
  • 启动流程。使用java代码来操作数据库表中的内容。

3.1 流程符号详解

  • 事件 Event

    image-20220119171107182

    事件是驱动工作流发展的核心对象,在工作流的流程定制过程中会经常看到。

  • 活动 Activity

    image-20220119171132072

    活动是工作或任务的一个通用术语。一个活动可以是一个任务,也可以是当前流程 的子处理流程。并且,活动会有不同的类型。例如Activiti中定义了 UserTask,ScriptTask,ServiceTask,MailTask等等多种类型。这些活动是构成整个业 务流程的主体。

  • 网关 GateWay

    image-20220119171202017

    网关是用来处理角色的,他决定了工作流的业务走向。有几种常用的网关需要了解 一下:

    • 排他网关

      只有一条路径会被选择。流程执行到该网关时,会按照输出流的顺序逐个计算,当 条件的结果为true时,继续执行当前网关的输出流。

      如果有多条线路计算结构都是true,则会执行第一个值为true的路线。如果所有 网关计算结果都没有true,引擎会抛出异常。

      排他网关需要和条件顺序流结合使用,default属性指定默认顺序流,当所有的条 件不满足时会执行默认顺序流。

    • 并行网关

      所有路径会被同时选择。

      并行执行所有输出顺序流,为每一条顺序流创建一个并行执行路线。最终,在所有 的执行路线都执行完后才继续向下执行。

    • 包容网关

      可以同时执行多条路线。相当于是排他网关和并行网关的结合。

      可以在网关上设置条件,计算每条路线上的表达式。当表达式计算结果为true时,创建一个并行线路并继续执行。最终,当所有需要执行的线路都执行完成后才继续 向下执行。

    • 事件网关

      专门为中间捕获事件而设置。允许设置多个输出流指向多个不同的中间捕获事件。 当流程执行到事件网关后,流程出于等待状态,需要等待抛出对应的事件才能将等 待状态转换为活动状态。

  • 流向 Flow

    image-20220119171419308

    流就是连接两个流程节点的连线,代表了流程之间的关联关系。

    注意:

    # Activiti的BPMN符号是在标准符号基础上做了一定的扩展。你可以新建一个BPMN文件,然后对照actBPM工具提供的工作流图形来理解。
    

3.2 定制一个简单的请假流程

首先在resources目录下创建bpmn目录,然后在目录下创建一个Bomn文件,起名 Leave,表示是一个请假流程。

然后我们在流程图中拖拽出下图的流程图(使用Activiti BPMN visualizer插件)

image-20220119171732428|269

这里注意每个模块都可以选中后在左侧设置他的属性。

这里,Assignee属性表示是这个任务的负责人。这里我们给 创建出差申请 设置负 责人 worker;部门经理审批 设置负责人 manager;财务审批 设置负责人 financer。

image-20220119171833494|416

设置完成后,记得点击一下流程的空白页,在左侧设置整个流程的属性。

image-20220119171904215|452

这样,我们整个员工出差申请的流程就定义完成了。之前介绍过,这个文件实际上是一个xml文件,所以,这个文件是可以用文本编辑器直接打开的。整个文件大致是 这样

image-20220119171938484

其中根节点是definitions节点。在这个节点中,可以定义多个工作流程process节 点。这也就意味着可以在一个图中定义多个工作流,但是通常在使用过程中建议一 个文件只包含一个流程定义,这样可以简化维护难度。definitions节点中,xmlns 和tagetNamespace两个属性是必须要包含的。

然后在文件中,包含了以标签描述的流程定义部分以及以 标签描述的流程布局定义部分。分别用来定义工作流 程以及流程图的布局信息。

# 文件关闭后,重新打开,可能会出现中文乱码的问题。这是因为IDEA的字符集问题。此时,需要修改IDEA的配置文件。 在IDEA中选Help ->Edit Custom VM Options 菜单,在打开的文件最后加上一Dfile.encoding=UTF-8配置,然后重启IDEA即可。

3.3 部署请假流程

接下来,需要将设计器中定义的流程部署到activiti的数据库中。activiti提供了api将 流程定义的bpmn和png文件部署到activiti中。

要获取png图片文件,可以直接截图,也可以使用IDEA导出。导出时,先将文件修 改为Leave.bpmn.xml,然后右键该文件,选择Diagrams -> Shwo BPMN 2.0 Diagrams,可以打开图片工具,然后选择上方的导出按钮,就可以导出一个png文件。

image-20220119173745576

选择导出

image-20220119173851285

部署流程时,可以分别上传bpmn文件和png文件,也可以将两个文件打成zip压缩 包一起上传。

/**
 * @author 史宏伟
 * @createTime 2022年01月19日 17:43:00
 */
public class ActivitDemo {
    @Test
    public void testDeployment(){
        // 1、创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 2、得到RepositoryService实例
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 3、使用RepositoryService进行部署
        Deployment deployment = repositoryService.createDeployment()
            .addClasspathResource("bpmn/Leave.bpmn20.xml") // 添加bpmn资源
            //png资源命名是有规范的。Leave.[key].[png|jpg|gif|svg] 或者Leave.[png|jpg|gif|svg]
            .addClasspathResource("bpmn/Leave.bpmn20.png") // 添加png资源
            .name("请假申请流程")
            .deploy();
        // 4、输出部署信息
        System.out.println("流程部署id:" + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }
    /**
     * zip压缩文件上传方式
     */
    @Test
    public void deployProcessByZip() {
        // 定义zip输入流
        InputStream inputStream = this
            .getClass()
            .getClassLoader()
            .getResourceAsStream("bpmn/Leave.zip");
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
        // 获取repositoryService
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 流程部署
        Deployment deployment = repositoryService.createDeployment()
            .addZipInputStream(zipInputStream)
            .deploy();
        System.out.println("流程部署id:" + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }

}

这个过程中,最重要的就是要去找repositoryService。执行完成后可以在日志中看 到部署的情况:

# 流程部署id:1
# 流程部署名称:请假申请流程

并且,从日志中可以分析出整个部署过程操作了三张数据表:

  • act_re_deployment 流程定义部署表,每部署一次增加一条记录
  • act_re_procdef 流程定义表,部署每个新的流程定义都会在这张表中增 加一条记录。记录中的key就是流程定义中最为重要的字段。
  • act_ge_bytearray 流程资源表 ,每个流程定义对应两个资源记录,bpmn 和png。
# 一次部署可以部署多个流程定义 即act_re_deployment和act_re_procdef中的数据其实是一对多的。但是在实际开发中,建议一次部署只不是一个流程。

3.4 启动流程实例

一个业务流程部署到activiti后,就可以使用了。例如这个出差申请的流程部署完成 后,就可以启动一个流程进行一次出差申请了。流程的执行过程主要通过 RuntimeService服务来管理。

/**
 * 启动流程实例
 */
@Test
public void testStartProcess(){
    // 1、创建ProcessEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2、获取RunTimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 3、根据流程定义Id启动流程
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myLeave");
    // 输出内容
    System.out.println("流程定义id:" +processInstance.getProcessDefinitionId());
    System.out.println("流程实例id:" + processInstance.getId());
    System.out.println("当前活动Id:" + processInstance.getActivityId());
}

执行结果可以看到流程实例的情况:

# 流程定义id:myLeave:1:4
# 流程实例id:2501
# 当前活动Id:null

继续查看日志,可以看到这个过程中涉及到的数据表:

  • act_hi_actinst 流程实例执行历史
  • act_hi_identitylink 流程的参与用户历史信息
  • act_hi_procinst 流程实例历史信息
  • act_hi_taskinst 流程任务历史信息
  • act_ru_execution 流程执行信息
  • act_ru_identitylink 流程的参与用户信息
  • act_ru_task 任务信息

3.5 任务查询

流程启动后,任务的负责人就可以查询自己当前需要处理的待办任务了。任务相关的服务都是由TaskService管理。

/**
     * 查询当前个人待执行的任务
     */
@Test
public void testFindPersonalTaskList() {
    // 任务负责人
    String assignee = "worker";
    ProcessEngine processEngine =
        ProcessEngines.getDefaultProcessEngine();
    // 创建TaskService
    TaskService taskService = processEngine.getTaskService();
    // 根据流程key 和 任务负责人 查询任务
    List<Task> list = taskService.createTaskQuery()
        .processDefinitionKey("myLeave") //流程Key
        .taskAssignee(assignee)//只查询该任务负责人的任务
        .list();
    for (Task task : list) {
        System.out.println("流程实例id:" + task.getProcessInstanceId());
        System.out.println("任务id:" + task.getId());
        System.out.println("任务负责人:" + task.getAssignee());
        System.out.println("任务名称:" + task.getName());
    }
}

执行完成后可以看到当前流程的任务列表

image-20220120095411462

# 当前请假流程启动后,就该等待worker用户提交请假申请了。实际中的请假申请流程应该是从worker提交请假申请开始,但是在activiti工作流中,都是从starter事件开始,这个关系要理清楚。

3.6 流程任务处理

任务负责人查询到代办任务后,可以选择任务进行处理,完成任务。

// 完成任务
@Test
public void completTask(){
    // 获取引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取taskService
    TaskService taskService = processEngine.getTaskService();
    // 根据流程key 和 任务的负责人 查询任务
    // 返回一个任务对象
    Task task = taskService.createTaskQuery()
        .processDefinitionKey("myLeave") //流程Key
        .taskAssignee("worker") //要查询的负责人
        .singleResult();
    // 完成任务,参数:任务id
    taskService.complete(task.getId());
}

这个任务完成后,这一个请假流程就推动到了下一个步骤,部门经理审批了。后续 可以用不同的用户来推动流程结束。

其实在完成审批任务的过程中,可以针对这个taskId,进行其他一些补充操作。例 如添加Comment,添加附件,添加子任务,添加候选负责人等等。具体可以看下 taskService的API。

3.7 流程信息查询

这一步可以查询流程相关信息,包含流程定义,流程部署,流程版本。

/**
     * 查询流程定义
     */
@Test
public void queryProcessDefinition(){
    // 获取引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // repositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 得到ProcessDefinitionQuery 对象
    ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
    // 查询出当前所有的流程定义
    // 条件:processDefinitionKey =evection
    // orderByProcessDefinitionVersion 按照版本排序
    // desc倒叙
    // list 返回集合
    List<ProcessDefinition> definitionList =
        processDefinitionQuery.processDefinitionKey("myLeave")
        .orderByProcessDefinitionVersion()
        .desc()
        .list();
    // 输出流程定义信息
    for (ProcessDefinition processDefinition : definitionList) {
        System.out.println("流程定义 id="+processDefinition.getId());
        System.out.println("流程定义name="+processDefinition.getName());
        System.out.println("流程定义 key="+processDefinition.getKey());
        System.out.println("流程定义Version="+processDefinition.getVersion());
        System.out.println("流程部署ID="+processDefinition.getDeploymentId());
    }
}

执行后可以看到查询结果

image-20220120102136330

3.8 查询正在处理的流程信息

@Test
public void queryProcessInstance(){
    String processDefinitionKey = "myLeave";
    ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = defaultProcessEngine.getRuntimeService();
    List<ProcessInstance> list = runtimeService.createProcessInstanceQuery()
        .processDefinitionKey(processDefinitionKey).list();

    for (ProcessInstance processInstance : list) {
        System.out.println(" ---------------------- ");
        System.out.println("流程实例ID:"+processInstance.getProcessInstanceId());
        System.out.println("所属流程定义ID:"+ processInstance.getProcessDefinitionId());
        System.out.println("是否执行完成:" + processInstance.isEnded());
        System.out.println("是否暂停:" + processInstance.isSuspended());
        System.out.println("当前活动标识:" + processInstance.getActivityId());
        System.out.println("业务关键字:"+processInstance.getBusinessKey());
    }
}

image-20220120102909628

3.9 删除流程

public void deleteDeployment() {
    // 流程部署id
    String deploymentId = "1";
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 通过流程引擎获取repositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    //删除流程定义,如果该流程定义已有流程实例启动则删除时出错
    repositoryService.deleteDeployment(deploymentId);
    //设置true 级联删除流程定义(强制删除),即使该流程有流程实例启动也可以删除,设置为false非级连删除方式
    //repositoryService.deleteDeployment(deploymentId, true);
}

注意:

1、这里只删除了流程定义,不会删除历史表信息
2、删除任务时,可以选择传入一个boolean型的变量cascade ,表示是
否级联删除。默认是false,表示普通删除。
如果该流程下存在已经运行的流程,使用普通删除会报错,而级联删除
可以将流程及相关记录全部删除。删除没有完成的流程节点后,就可以
完全删除流程定义信息了。
项目开发中,级联删除操作一般只开放给管理员使用。

3.10 流程资源下载

在流程执行过程中,可以上传流程资源文件。我们之前在部署流程时,已经将 bpmn和描述bpmn的png图片都上传了,并且在流程执行过程中,也可以上传资源 文件。如果其他用户想要查看这些资源文件,可以从数据库中把资源文件下载下来。

但是文件是以Blob的方式存在数据库中的,要获取Blob文件,可以使用JDBC来处 理。也可以使用activiti提供的api来辅助实现。我们这里采用activiti的方式来实现。

首先引入commons-io依赖

<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>

然后,就可以通过流程定义对象来获取流程资源。这里获取我们之前上传的bpmn 和png文件

@Test
public void queryBpmnFile() throws Exception {
    ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();

    RepositoryService repositoryService = defaultProcessEngine.getRepositoryService();
    // 得到ProcessDefinitionQuery 对象
    ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
    ProcessDefinition processDefinition = processDefinitionQuery.processDefinitionKey("myLeave")
        .orderByProcessDefinitionVersion()
        .desc()
        .singleResult();

    String deploymentId = processDefinition.getDeploymentId();

    InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
    InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());

    File file_png = new File("./myLeave.png");
    File file_bpmn = new File("./myLeave.bpmn");
    FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
    FileOutputStream pngOut = new FileOutputStream(file_png);

    pngOut.close();
    bpmnOut.close();
    pngInput.close();
    bpmnInput.close();
}

注意:

# 在获取资源文件名时,png图片资源的文件名是processDefinition.getDiagramResourceName(),他来自于ACT_RE_PROCDEF表中的DGRM_RESOURCE_NAME字段。这个字段的值是在部署流程时根据文件名后缀判断出来的。 支持的格式为[ResourceName].[key].[png|jpg|gif|svg]或者[ResourceName].[png|jpg|gif|svg]而bpmn文件的文件名是processDefinition.getResourceName(),他来自于ACT_RE_PROCDEF表中的RESOURCE_NAME字段。

3.11 流程历史信息查看

流程的历史信息都保存在activiti的act_hi_*相关的表中,我们可以查询流程执行的历 史信息。这里需要通过HistoryService来查看相关的历史记录。

@Test
public void findHistoryInfo(){
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取HistoryService
    HistoryService historyService = processEngine.getHistoryService();
    // 获取 actinst表的查询对象
    HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
    // 查询 actinst表,条件:根据 InstanceId 查询,查询一个流程的所有历史信息
    instanceQuery.processInstanceId("7501");
    // 查询 actinst表,条件:根据 DefinitionId 查询,查询一种流程的所有历史信息
    // instanceQuery.processDefinitionId("myLeave:1:22504");
    // 增加排序操作,orderByHistoricActivityInstanceStartTime 根据开始时间排序asc 升序
    instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
    // 查询所有内容
    List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
    // 输出
    for (HistoricActivityInstance hi : activityInstanceList) {
        System.out.println(hi.getActivityId());
        System.out.println(hi.getActivityName());
        System.out.println(hi.getProcessDefinitionId());
        System.out.println(hi.getProcessInstanceId());
        System.out.println("<==========================>");
    }
}

image-20220120112832951

注意:

# 1、关于流程历史信息,要注意,在删除流程时,如果是采取级联删除的方式,那这个历史信息也会随着一起删除。而普通删除方式不会删除历史信息。
# 2、历史信息有不同的种类,具体可以通过historyService构建不同类型的Query对象来获取结果。

4. Activiti进阶

4.1 流程定义与流程实例

流程定义ProcessDefinition 和流程实例ProcessInstance是Activiti中非常重要的 两个概念。他们的关系其实类似于JAVA中类和对象的概念。

流程定义ProcessDefinition是以BPMN文件定义的一个工作流程,是一组工作规 范。例如我们之前定义的请假流程。流程实例ProcessInstance则是指一个具体的业 务流程。例如某个员工发起一次请假,就会实例化一个请假的流程实例,并且每个 不同的流程实例之间是互不影响的。

在后台的表结构中,有很多张表都包含了流程定义ProcessDefinetion和流程实例ProcessInstance的字段。流程定义的字段通常是PROC_DEF_ID,而流程实例的字段通常是PROC_INST_ID。

4.1.1 启动流程实例时,添加Businesskey

在之前的简单案例中,我们启动一个流程实例的关键代码其实就是这一行。

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myLeave");

当我们去查看下startProcessInstanceByKey这个方法时,会看到这个方法有好几个重载的实现方法,可以传一些不同的参数。其中几个重要的参数包括

  • String processDefinitionKey:流程定义的唯一键 不能为空
  • String businessKey:每个线程实例上下文中关联的唯一键。这个也是我们这一 章节要介绍的重点。
  • Map variables:在线程实例中传递的流程变量。这个流程变量 可以在整个流程实例中使用,后面会介绍到。
  • String tenantId:租户ID,这是Activiti的多租户设计。相当于每个租户可以上来 获取一个相对独立的运行环境。

这一章节我们来介绍这个businessKey,业务关键字。这是Activiti提供的一个非常重要的便利,用来将activiti的工作流程与实际业务进行关联。

例如,当我们需要对一个业务订单进行审批时,订单的详细信息并不在activiti的数 据当中,但是在审批时确实需要查看这些订单的详细信息。这个时候,就可以用这 个businessKey来关联订单ID,这样在业务系统中,就可以通过这个订单ID去关联 订单详细信息,审批人员就可以快速拿来进行参考。

进行实际业务整合时,这个businessKey可以根据业务场景,设计成不同的数据格 式,比如关键信息逗号拼接,甚至是json都可以,唯一需要注意的是这个字段的数 据库长度设计是255,不要超出了数据库的长度限制。

设置businessKey:

@Test
public void addBusinessKey(){
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();

    ProcessInstance instance = runtimeService.startProcessInstanceByKey("myLeave","1001");
    System.out.println("businessKey==" + instance.getBusinessKey());
}

接下来,我们看看如何在流程实例执行过程中获取这个业务关键字:

@Test
public void queryProcessInstance() {
    // 流程定义key
    String processDefinitionKey = "myLeave";
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取RunTimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    List<ProcessInstance> list = runtimeService
        .createProcessInstanceQuery()
        .processDefinitionKey(processDefinitionKey)//
        .list();
    for (ProcessInstance processInstance : list) {
        System.out.println("----------------------------");
        System.out.println("流程实例id:"+ processInstance.getProcessInstanceId());
        System.out.println("所属流程定义id:"+ processInstance.getProcessDefinitionId());
        System.out.println("是否执行完成:" + processInstance.isEnded());
        System.out.println("是否暂停:" + processInstance.isSuspended());
        System.out.println("当前活动标识:" +processInstance.getActivityId());
        System.out.println("业务关键字:"+processInstance.getBusinessKey());
    }
}

通过最后面的一行processInstance.getBusinessKey()就能获取到当前流程实例中 的业务关键字。在数据库中,act_ru_execution表中的BUSINESS_KEY字段就是用 来保存这个业务关键字的。

4.1.2 挂起、激活流程实例

之前我们已经测试了如何删除一个流程,有很多时候,我们只是需要暂时停止一 个流程,过一段时间就要恢复。例如月底不接受报销审批流程,年底不接受借贷审 批流程,或者非工作日不接受售后报销流程等,这个时候,就可以将流程进行挂起 操作。挂起后的流程就不会再继续执行。

在挂起流程时,有两种操作方式。

一种是将整个流程定义Process Definition挂起,这样,这个流程定义下的所有流程 实例都将挂起,无法继续执行。

@Test
public void SuspendAllProcessInstance() {
    // 获取processEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取repositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 查询流程定义的对象
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().
        processDefinitionKey("myEvection").
        singleResult();
    // 得到当前流程定义的实例是否都为暂停状态
    boolean suspended = processDefinition.isSuspended();
    // 流程定义id
    String processDefinitionId = processDefinition.getId();
    // 判断是否为暂停
    if (suspended) {
        // 如果是暂停,可以执行激活操作 ,参数1 :流程定义id ,参数2:是否激活,参数3:激活时间
        repositoryService.activateProcessDefinitionById(processDefinitionId,true,null);
        System.out.println("流程定义:" + processDefinitionId + ",已激活");
    } else {
        // 如果是激活状态,可以暂停,参数1 :流程定义id ,参数2:是否暂停,参数3:暂停时间
        repositoryService.suspendProcessDefinitionById(processDefinitionId,true,null);
        System.out.println("流程定义:" + processDefinitionId + ",已挂起");
    }
}

另一种方式是将某一个具体的流程实例挂起。例如对某一个有问题的请假申请进 行挂起操作,数据调整完成后再进行激活。继续执行挂起状态的流程将会抛出异常

@Test
public void SuspendSingleProcessInstance(){
    // 获取processEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // RuntimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 查询流程定义的对象
    ProcessInstance processInstance = runtimeService.
        createProcessInstanceQuery().
        processInstanceId("15001").
        singleResult();

    // 得到当前流程定义的实例是否都为暂停状态
    boolean suspended = processInstance.isSuspended();
    // 流程定义id
    String processInstanceId = processInstance.getId();
    // 判断是否为暂停
    if(suspended){
        // 如果是暂停,可以执行激活操作 ,参数:流程定义id
        runtimeService.activateProcessInstanceById(processInstanceId);
        System.out.println("流程:"+processInstanceId+",已激活");
    }else{
        // 如果是激活状态,可以暂停,参数:流程定义id
        runtimeService.suspendProcessInstanceById( processInstanceId);
        System.out.println("流程:"+processInstanceId+",已挂起");
    }
}

4.2 流程变量

流程变量也是Activiti中非常重要的角色。我们之前定义的请假流程并没有用到流程 变量,每个步骤都是非常固定的,但是,当我们需要实现一些复杂的业务流程,比 如请假3天以内由部门经理审批,3天以上需要增加总经理审批这样的流程时,就需要用到流程变量了。

注意:

# 这个流程变量和之前介绍的业务关键字其实是有些相似的,都可以携带业务信息。并且也都可以通过activiti的api查询出来。但是通常在使用过程中,应该尽量减少流程变量中的业务信息,这样能够减少业务代码对activiti工作流的代码侵入。

在上一章节介绍到,流程变量的类型是Map。所以,流程变量比 业务关键字要强大很多。变量值不仅仅是字符串,也可以是POJO对象。但是当需要 将一个POJO对象放入流程变量时,要注意这个对象必须要实现序列化接口 serializable。

4.2.1 流程变量的作用域

变量的作用域可以设置为Global和Local两种。

  • Global变量

    这个是流程变量的默认作用域,表示是一个完整的流程实例。 Global变量中变量 名不能重复。如果设置了相同的变量名,后面设置的值会直接覆盖前面设置的变 量值。

  • Local 变量

    Local变量的作用域只针对一个任务或一个执行实例的范围,没有流程实例大。 Local变量由于作用在不同的任务或不同的执行实例中,所以不同变量的作用域是 互不影响的,变量名可以相同。Local变量名也可以和Global变量名相同,不会 有影响。

4.2.2 使用流程变量

定义好流程变量后,就可以在整个流程定义中使用这些流程变量了。例如可以在 某些任务属性如assignee上使用assignee,或者在某些连线上使用{assignee},或者在某些连线上使用{day<3}。

Activiti中可以使用UEL表达式来使用这些流程变量。UEL表达式可以直接获取一 个变量的值,可以计算一个Boolean结果的表达式,还可以直接使用某些对象的属 性。例如对于之前创建的请假流程,如果要实现3天以内部门经理审核,3天以上增 加总经理审核,可以做如下调整:

image-20220124093250149

出差天数小于3连线条件:

image-20220124093328686

出差天数大于等于3连线条件:

image-20220124093431231

4.2.3 设置Global流程变量

在流程定义中使用到了流程变量,就需要在后台JAVA代码中设置对应的流程变量。 实际上在流程执行的很多过程中都可以设计自流程变量。

启动流程时设置变量

在启动流程实例时设置流程变量,这时流程变量的作用域是整个流程实例。相当于 是Global作用域。核心代码:

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, map);

任务办理时设置变量

在完成任务时设置流程变量,该流程变量只有在该任务完成后其它结点才可使用该 变量,它的作用域是整个流程实例,如果设置的流程变量的key在流程实例中已存在 相同的名字则后设置的变量替换前边设置的变量。核心代码:

1 taskService.complete(task.getId(),map);

注意:

# 这种方式设置流程变量,如果当前执行的任务ID不存在,则会抛出异常,流程变量也会设置失败。

通过当前流程实例设置

通过流程实例id设置全局变量,该流程实例必须未执行完成。

@Test
public void setGlobalVariableByExecutionId(){
    // 当前流程实例执行 id,通常设置为当前执行的流程实例
    String executionId="2601";
    // 获取processEngine
    ProcessEngine processEngine =
        ProcessEngines.getDefaultProcessEngine();
    // 获取RuntimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 创建出差pojo对象
    Evection evection = new Evection();
    // 设置天数
    evection.setNum(3d);
    // 通过流程实例 id设置流程变量
    runtimeService.setVariable(executionId, "myLeave", evection);
    // 一次设置多个值
    // runtimeService.setVariables(executionId, variables)
}

注意:

# ececutionId必须是当前未完成的流程实例的执行ID。通常此ID设置流程实例的ID。流程变量设计完成后,也可以通过runtimeService.getVariable()获取流程变量

通过当前任务设置

@Test
public void setGlobalVariableByTaskId(){
    //当前待办任务id
    String taskId="1404";
    // 获取processEngine
    ProcessEngine processEngine =
        ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    Evection evection = new Evection();
    evection.setNum(3);
    //通过任务设置流程变量
    taskService.setVariable(taskId, "evection", evection);
    //一次设置多个值
    //taskService.setVariables(taskId, variables)
}
# 任务id必须是当前待办任务id,act_ru_task中存在。如果该任务已结束,会报错。也可以通过taskService.getVariable()获取流程变量。

注意事项

# 1、 如果UEL表达式中流程变量名不存在则报错。
# 2、 如果UEL表达式中流程变量值为空NULL,流程不按UEL表达式去执行,而流程结束。
# 3、 如果UEL表达式都不符合条件,流程结束
# 4、 如果连线不设置条件,会走flow序号小的那条线
# 5、设置流程变量会在当前执行流程变量表act_ru_variable中插入记录,同时也会在历史流量变量表act_hi_varinst中也插入记录。

4.2.4 设置Local流程变量

local流程变量同样可以有多个设置的地方。

任务办理时设置

任务办理时设置local流程变量,当前运行的流程实例只能在该任务结束前使用,任 务结束该变量无法在当前流程实例使用,可以通过查询历史任务查询。关键代码:

// 设置local变量,作用域为该任务
taskService.setVariablesLocal(taskId, variables);
// 完成任务
taskService.complete(taskId);

通过当前任务设置

@Test
public void setLocalVariableByTaskId(){
    // 当前待办任务id
    String taskId="1404";
    // 获取processEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    Evection evection = new Evection ();
    evection.setNum(3d);
    // 通过任务设置流程变量
    taskService.setVariableLocal(taskId, "evection", evection);
    // 一次设置多个值
    //taskService.setVariablesLocal(taskId, variables)
}

注意:

# 任务ID必须是当前待办任务id,要在act_ru_task中存在

4.3 网关

网关是用来控制流程流向的重要组件,通常都会要结合流程变量来使用。

4.3.1 排他网关ExclusiveGateway

排他网关,用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条 件是否为true,如果为true则执行该分支。

注意:

# 排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会选择id值较小的一条分支去执行。

为什么要用排他网关?

不用排他网关也可以实现分支,如:在连线的condition条件上设置分支条件。

在连线设置condition条件的缺点:如果条件都不满足,流程就结束了(是异常结束)。

如果 使用排他网关决定分支的走向,如下:

image-20220207153750652

如果从网关出去的线所有条件都不满足则系统抛出异常。

org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway 'exclusivegateway1' could be selected for continuing the process