Activiti 学习笔记

工作流概述

每一项业务的开始和结束,都可以理解为一个工作流,例如,公司的费用报销的基本流程如下:

员工先提出费用报销申请,提交给部门领导,部门领导审批后,提交给财务部门审批,审批完成后,通知提出申请的员工可以报销,报销流程结束。整个流程按照步骤完成,这就是一个简单的工作流。工作流可以理解为从开始节点发起流程,然后经过其中多个节点,完成动作,最后到结束节点的整个过程

系统的工作流的功能是对系统业务流程进行自动化管理,具体应用有:

  1. 关键业务类:订单、报价处理、合同审核、供应链管理等等
  2. 行政管理类:出差申请、请假申请、日报周报等等
  3. 人事管理类:员工培训安排、变动处理等等
  4. 财务相关类:收付款处理、报销处理、预算申请等等
  5. 客户服务类:客户信息管理、客户投诉、请求处理、售后服务等等

工作流引擎

在没有工作流引擎之前,为了实现流程控制,通常的做法是采用状态字段的值来跟踪流程的变化。例如设立一个字段,初始值为 0,经过某些流程后变成 1,变成 2,最后根据这个值来判断状态,给出相应的处理

很明显,这样一来工作的流程会和业务高度耦合,当流程发生变更,代码也必须调整。如果有一样工具能帮助我们管理工作流,当业务流程变化,不需要改变代码,那么业务系统的适应能力将大幅提升

Activit7 是一个工作流引擎,可以将系统中复杂的业务流程抽取出来,使用专门的建模语言 BPMN2.0 进行定义,业务流程将按照预定义的流程执行。系统的流程由 activit 管理,从而减少业务系统由于流程变化而导致的工作量,提高系统健壮性

官方网址:https://www.activiti.org/


BPMN

BPMN(Business Process Model AndNotation)即业务流程模型和符号,是一套标准的业务流程建模符号,使用 BPMN 提供的符号可以创建业务流程

目前 BPMN2.0 是最新的版本,基本符号主要包含:

1. 事件 Event

2. 活动 Activity

活动是工作或任务的一个通用术语,它可以是一个任务,还可以是一个当前流程的子处理流程,常见活动如下:

3. 网关 GateWay

网关用来处理决策,常用网关如下:

  • 排它网关:只有一条路径会被选择。流程执行到该网关时,按照路径顺序逐个计算,计算结果为 true 则继续执行该路径。如果有多条路径计算结果为 true,则执行第一个值为 true 的路径。如果所有路径计算结果都不为 true,则抛出异常。排他网关需要结合条件顺序流使用,default 属性指定默认顺序流,当所有条件不满足时会执行默认顺序流
  • 并行网关:所有路径并行执行,需等到所有路径执行完成,才能继续向下执行
  • 包容网关:可以设置条件,计算每个路径,当结果为 true 则继续执行,当有多个路径满足条件可并行执行,直到所有满足条件的路径都执行完成,才能继续向下执行
  • 事件网关:专门为中间捕获事件而设置,允许设置多个路径指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态

4. 流向 Flow

流是连接两个流程节点的连线,常见的流向包含以下几种:


Activit 环境搭建

1. 部署流程

Activiti 是一个工作流引擎,业务系统通过访问 Activiti 所提供的接口,可以很方便的操作流程。首先使用 Activiti 流程建模工具(Activity-Designer)通过 BPMN2.0 符号定义业务流程,生成一个 .bpmn 文件,即业务流程定义文件,交由Activiti 解析并管理。通过 Activiti 可以创建一个流程实例,表示一个次务流程的开始。由于业务流程已经交给 Activiti 管理,通过 Activiti 提供的 api 可以查看当前流程执行到哪一步,当前用户需要办理什么任务,用户查完成任务后,如果需要其它用户继续办理,比如采购单创建后要交由部门经理审核,Activiti 可以自动修改流程状态,推进流程执行,当没有下一个任务结点,那么流程也就完成了

2. Activit 配置

Activiti 运行要有数据库支持,比如:h2、mysql、oracle、postgres、mssql、db2

IDEA 搜索 actiBPM 插件,它是 Activit Designer 的 IDEA 版本,安装即可,这里选择的是另一款一款名为 Activiti BPMN Visualizer 的插件

创建 maven 工程并添加依赖

<properties>
    <slf4j.version>1.6.6</slf4j.version>
    <log4j.version>1.2.12</log4j.version>
    <activiti.version>7.0.0.Beta1</activiti.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-engine</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-spring</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn 模型处理 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-model</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn 转换 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-converter</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn json数据转换 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-json-converter</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn 布局 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-layout</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- activiti 云支持 -->
    <dependency>
        <groupId>org.activiti.cloud</groupId>
        <artifactId>activiti-cloud-services-api</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.17</version>
    </dependency>
    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.5</version>
    </dependency>
    <!-- 链接池 -->
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!-- log start -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
</dependencies>

配置日志,在 resource 目录下创建一个 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=f:\act\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

编写 activit 配置文件,activiti 会根据配置文件创建 mysql 表,默认在 resources 目录下创建 activit.cfg.xml 文件,路径和文件名都不能修改。默认方式中 activiti.cfg.xml 里面 bean 的名字要叫 processEngineConfiguration,不可以修改

<?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/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">

    <!-- 默认 id 对应的值为 processEngineConfiguration -->
    <!-- processEngine Activiti 的流程引擎 -->
    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <!-- 配置数据库相关信息 -->
        <property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activit?useSSL=false&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true&amp;useAffectedRows=true"/>
        <property name="jdbcUsername" value="root"/>
        <property name="jdbcPassword" value="123"/>
        <!-- activiti 数据库表处理策略,true 为如果数据库中已存在相应的表,则直接使用,否则创建 -->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>
</beans>

也可以使用连接池来提高性能

<?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/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">

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/activit?useSSL=false&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true&amp;useAffectedRows=true"/>
        <property name="username" value="root"/>
        <property name="password" value="123"/>
        <property name="maxActive" value="3"/>
        <property name="maxIdle" value="1"/>
    </bean>

    <!-- 默认 id 对应的值为 processEngineConfiguration -->
    <!-- processEngine Activiti 的流程引擎 -->
    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <!-- 引入上面配置好的链接池 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- activiti 数据库表处理策略,true 为如果数据库中已存在相应的表,则直接使用,否则创建 -->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>
</beans>

创建一个测试类,调用 activiti 的工具类,生成 acitivti 需要的表。直接使用 activiti 提供的工具类 ProcessEngines,会默认读取 classpath 下的 activiti.cfg.xml 文件,读取其中的数据库配置,创建 ProcessEngine,在创建 ProcessEngine 时会自动创建表

public class testCreate {

    @Test
    public void testCreateTable() {
        // 读取 activiti.cfg.xml 配置文件,创建 ProcessEngine 的同时会创建表
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        System.out.println(processEngine);
    }
}

这种方式默认读取 resource 目录下的 activiti.cfg.xml 配置文件,我们也可以自定义配置文件,并指定路径进行读取

// 先构建ProcessEngineConfiguration
ProcessEngineConfiguration configuration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
// 通过 ProcessEngineConfiguration 创建 ProcessEngine,此时会创建数据库
ProcessEngine processEngine = configuration.buildProcessEngine();

执行完成后我们查看数据库, 创建了 25 张表,结果如下:


Activiti 表结构

Activiti 运行必须要有这 25 张表的支持,其作用主要是在业务流程运行过程中,记录参与流程的用户主体,用户组信息,以及流程的定义,流程执行时的信息,和流程的历史信息等等

Activiti 的表都以 act_ 开头,紧接着是表示表的用途的两个字母标识,也和 Activiti 所提供的服务的 API 对应:

  • ACT_RE:RE 表示 repository,这个前缀的表包含了流程定义和流程静态资源 (图片、规则等等)
  • ACT_RU:RU 表示 runtime,这些表运行时,会包含流程实例、任务、变量、异步任务等流程业务进行中的数据。Activiti 只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录
  • ACT_HI:HI 表示 history,这些表包含一些历史数据,比如历史流程实例、变量、任务等等
  • ACT_GE:GE 表示 general,通用数据

Activiti 数据表具体作用如下:

表分类 表名 解释
一般数据
[ACT_GE_BYTEARRAY] 通用的流程定义和流程资源
[ACT_GE_PROPERTY] 系统相关属性
流程历史记录
[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_RE_DEPLOYMENT] 部署单元信息
[ACT_RE_MODEL] 模型信息
[ACT_RE_PROCDEF] 已部署的流程定义
运行实例表
[ACT_RU_EVENT_SUBSCR] 运行时事件
[ACT_RU_EXECUTION] 运行时流程执行实例
[ACT_RU_IDENTITYLINK] 运行时用户关系信息,存储任务节点与参与者的相关信息
[ACT_RU_JOB] 运行时作业
[ACT_RU_TASK] 运行时任务
[ACT_RU_VARIABLE] 运行时变量

Activiti 体系架构

完成 Activiti 数据库表生成,就可以在 Java 代码中调用 Activiti 的工具类,下面来了解 Activiti 的类关系

1. 类关系图

在新版本中,IdentityService、FormService 这两个 Serivce 都已经删除

2. Service 服务接口

Service 是工作流引擎提供用于进行工作流部署、执行、管理的服务接口

RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();

简单介绍一下各个 Service 的实现类:

  • RepositoryService:Activiti 的资源管理类,负责部署流程定义,管理流程资源。在使用 Activiti 时,一开始需要先完成流程部署,即将使用建模工具设计的业务流程图通过 RepositoryService 进行部署
  • RuntimeService:Activiti 的流程运行管理类,用于开始一个新的流程实例,获取关于流程执行的相关信息。流程定义用于确定一个流程中的结构和各个节点间行为,而流程实例则是对应的流程定义的一个执行,可以理解为 Java 中类和对象的关系
  • TaskService:Activiti 的任务管理类,用于处理业务运行中的各种任务,例如查询分给用户或组的任务、创建新的任务、分配任务、确定和完成一个任务
  • HistoryService:Activiti 的历史管理类,可以查询历史信息。执行流程时,引擎会保存很多数据,比如流程实例启动时间、任务的参与者、完成任务的时间、每个流程实例的执行路径等等。这个服务主要通过查询功能来获得这些数据
  • ManagementService:Activiti 的引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护

Activiti 实战

创建一个 Activit 工作流,并启动这个流程,主要包含以下几个步骤:

  1. 定义流程,按照 BPMN 的规范,使用流程定义工具,用流程符号把整个流程描述出来
  2. 部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据

1. 流程设计器的使用

在 resources 目录下创建一个 bpmn 目录,用来存放流程定义文件。选中 bpmn 目录,点击菜单 New -> New Activity 6.x BPMN 2.x file,创建一个 *.bpmn20.xml。选中该文件,右键 view BPMN(Activity ) Diagram,可以看到 BPMN-Activiti-Diagram 流程设计页面。在该页面右键,可以在菜单选择图形,拖拽图形,连线,即可完成流程设计图

连线的时候,要选中图形,这时在图形的右上角会出现一个箭头符号。双击选中箭头符号,右键按住不放即可拉长完成连线

选中一个图形,下方会出现一个属性栏,以创建出差申请为例,这里只填任务名称和负责人

2. 流程部署

流程部署是将在设计器中定义的流程部署到 activiti 数据库中,Activiti 提供了多种部署流程的方式,包括自动部署、classpath 部署、输入流部署、zip/bar 部署和上传部署等方式

自动部署前提是在 resources 目录下,创建一个新的目录 processes,用来放置 bpmn 文件

classpath 部署方式为采用代码进行部署,使用 Java 代码实现如下:

public class ActivitDemo {

    @Test
    public void testDeployment() {
        // 1. 创建 ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 2. 获取 RepositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 3. 使用 service 进行流程部署,即把 bpmn 部署到数据库,并定义流程的名称
        Deployment deploy = repositoryService.createDeployment()
            	// 定义流程资源名称
                .name("出差申请流程")
            	// 加载待部署的资源,可以多次引用
                .addClasspathResource("bpmn/evection.bpmn20.xml")
            	// 完成部署
                .deploy();
    }
}

输入流方式部署,输入流的来源可以有多种,例如本地计算机、classpath 读取、网络读取方式等,下面是读取本地计算机方式进行部署的示例代码

public class ActivitDemo {

    @Test
    public void testDeployment() {
        // 1. 创建 ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 2. 获取 RepositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 3. 获取输入流
        String filePath = "D:/workflow/bpmn/evection.bpmn20.xml";
        FileInputStream fileIns = new FileInputStream(filePath);
        // 4. 使用 service 进行流程部署
        Deployment deploy = repositoryService.createDeployment()
                .name("出差申请流程")
                .addInputStream("bpmn/evection.bpmn20.xml", fileIns)
                .deploy();
    }
}

zip/bar 部署是将流程资源文件压缩成 zip 包进行部署,同样采用流的方式

public void deployProcessByZip() {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 RepositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3. 流程部署
    InputStream inputStream = this.getClass()
        .getClassLoader()
        .getResourceAsStream("bpmn/evection.zip");
    ZipInputStream zipInputStream = new ZipInputStream(inputStream);
    repositoryService.createDeployment()
        .addZipInputStream(zipInputStream)
        .deploy();
}

按字符串方式部署,实际是把一个字符串转换为字节流后进行部署,优点是可以通过用户界面定义一个流程,然后进行部署,或者在测试时,直接将流程代码写入程序,进行流程部署

public void deployProcessByStr() {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 RepositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3. 构建字符串
    String str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><definitions>...</definitions>"
    // 4. 流程部署
    repositoryService.createDeployment()
        .name("出差申请流程")
        .addStr("bpmn/evection.bpmn20.xml", str)
        .deploy();
}

动态 BPMN 模型部署,通过在程序中动态生成流程模型方式进行部署,动态创建 BPMN,需要在程序中指定每一个元素,并给出元素间的关系,最后将这些元素整合为一个完整的 BPMN

public void testDeploymentByBPMN() {
    // 1. 创建 BPMN 模型实例
    BpmnModel bpmnModel = new BpmnModel();
    // 2. 创建开始事件
    StartEvent startEvent = new StartEvent();
    startEvent.setId("startEvent");
    startEvent.setName("动态创建开始节点");
    // 3. 创建用户任务
    UserTask userTask = new UserTask();
    userTask.setId("userTask1");
    userTask.setName("用户任务节点1");
    // 4. 创建结束事件
    EndEvent endEvent = new EndEvent();
    endEvent.setId("endEvent");
    endEvent.setName("动态创建结束节点");
    // 5. 定义连接
    ArrayList<SequenceFlow> sequenceFlows = new ArrayList<>();
    ArrayList<SequenceFlow> toEnd = new ArrayList<>();

    SequenceFlow s1 = new SequenceFlow();
    s1.setId("sequenceFlow1");
    s1.setName("开始节点指向用户任务节点");
    s1.setSourceRef("startEvent");
    s1.setTargetRef("userTask1");
    sequenceFlows.add(s1);
    SequenceFlow s2 = new SequenceFlow();
    s2.setId("sequenceFlow2");
    s2.setName("用户任务节点指向结束节点");
    s2.setSourceRef("userTask1");
    s2.setTargetRef("endEvent");
    toEnd.add(s2);

    startEvent.setOutgoingFlows(sequenceFlows);
    userTask.setOutgoingFlows(toEnd);
    userTask.setIncomingFlows(sequenceFlows);
    endEvent.setIncomingFlows(toEnd);

    Process process = new Process();
    process.setId("process1");
    process.setName("test");
    process.addFlowElement(startEvent);
    process.addFlowElement(s1);
    process.addFlowElement(userTask);
    process.addFlowElement(s2);
    process.addFlowElement(endEvent);
    bpmnModel.addProcess(process);
    new BpmnAutoLayout(bpmnModel).execute();
    // 6. 部署
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RepositoryService repositoryService = processEngine.getRepositoryService();
    Deployment deploy = repositoryService.createDeployment()
        .name("出差申请流程")
        .addBpmnModel("evection.bpmn20.xml", bpmnModel)
        .deploy();
}

3. 相关数据表分析

流程部署后,流程相关的资源全部以数据流的形式存储到数据表中,实际运行中,流程的处理都是通过调用数据表中的相关资源进行处理,下面介绍流程相关的数据表:

  • act_re_deployment:流程部署表,用于存放流程定义的部署信息,部署一个流程就会增加一条记录
  • act_re_procdef:流程定义表,用于存放流程定义的属性信息,和 act_re_deployment 是一对多的关系,act_re_procdef 表可以有多条记录,每条记录对应 act_re_deployment 中的一条记录
  • act_ge_bytearray:资源文件表,部署流程时,会将 BPMN 流程定义文件保存为一条记录,如果部署的流程还包括其他资源,也会增加相应的记录,例如 png 图片文件

4. Activiti 流程启动

流程定义部署后,就可以通过工作流管理业务流程。比如前文部署的出差申请流程可以使用了,启动一个流程表示发起一个新的出差申请单,这就相当于 java 类与 java 对象的关系,类定义好后需要创建一个对象使用,也可以创建多个对象。对于出差申请流程,张三发起一个出差申请单需要启动一个流程实例,李四发起一个出差申请单也需要启动一个流程实例

流程定义和流程实例的图解:

Activiti 流程启动主要有两种方式,分别是根据 processDefinitionKey 启动和根据 processDefinitionId 启动

根据 processDefinitionKey 启动,processDefinitionKey 就是 act_re_procdef 表的 KEY_ 字段的值,是对应的流程定义的 key

@Test
public void testStartProcess() {
    // 1. 创建 ProcessEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 RuntimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 3. 根据 processDefinitionKey 启动流程
    ProcessInstance instance = runtimeService.startProcessInstanceByKey("evection");
}

根据 processDefinitionId 启动,processDefinitionId 就是 act_re_procdef 的主键 ID 例如 evection:1:22503

@Test
public void testStartProcess() {
    // 1. 创建 ProcessEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 RuntimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 3. 根据 processDefinitionKey 启动流程
    ProcessInstance instance = runtimeService.startProcessInstanceById("evection:1:22503");
}

5. Activiti 个人任务查询

流程启动后,任务的负责人可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务

public void testFindPersonTaskList() {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 taskService
    TaskService taskService = processEngine.getTaskService();
    // 3. 获取流程 key 和任务的负责人,查询任务
    List<Task> taskList = taskService.createTaskQuery()
        .processDefinitionKey("evection")   // 流程key
        .taskAssignee("zhangsan")   // 要查询的负责人
        .list();
    // 4. 输出
    for (Task task : taskList) {
        System.out.println("流程实例 id = " + task.getProcessInstanceId());
        System.out.println("任务 id = " + task.getId());
        System.out.println("任务负责人 = " + task.getAssignee());
        System.out.println("任务名称 = " + task.getName());
    }
}

6. Activiti 完成个人任务

根据任务 id 查找任务并处理

public void completeTask() {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 TaskService
    TaskService taskService = processEngine.getTaskService();
    // 3. 根据任务id 完成任务
    taskService.complete("25005");
}

每次都要查找任务 id 很麻烦,一般来说,是任务负责人查询待办任务,选择任务进行处理

public void completeTask() {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 TaskService
    TaskService taskService = processEngine.getTaskService();
    // 3. 获取 jerry - evection 对应的任务
    Task task = taskService.createTaskQuery()
        .processDefinitionKey("evection")
        .taskAssignee("jerry")
        .singleResult();
    // 4. 根据任务 id 完成任务
    taskService.complete(task.getId());
}

7. Activiti 结合实际业务

流程定义部署在 activiti 后,就可以在系统中通过 activiti 去管理该流程的执行。启动流程实例时,指定 businesskey,就会在 act_ru_execution 表存储 businesskey

Businesskey,业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据

比如:出差流程启动一个流程实例,就可以将出差单的 id 作为 Businesskey 存储到 activiti,将来就可以根据出差单的 id 查询对应的 activiti 流程实例信息

/**
 * 添加业务 key 到 Activiti 的表
 */
@Test
public void addBusinessKey() {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 RuntimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 3. 启动流程,添加 BusinessKey
    ProcessInstance instance = runtimeService.startProcessInstanceByKey("evection", "1001");
    // 4. 输出
    System.out.println("businessKey: " + instance.getBusinessKey());
}

8. 流程定义的其他操作

流程定义查询

public void queryProcessDefinition() {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 RepositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3. 查询当前所有的流程定义,返回流程定义信息的集合
    ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
    List<ProcessDefinition> definitions = definitionQuery.processDefinitionKey("evection")
        .orderByProcessDefinitionVersion()  // 进行排序
        .desc() // 倒序
        .list();
    // 4. 输出信息
    for (ProcessDefinition definition : definitions) {
        System.out.println("流程定义 id = " + definition.getId());
        System.out.println("流程定义名称 = " + definition.getName());
        System.out.println("流程定义 key = " + definition.getKey());
        System.out.println("流程定义版本 = " + definition.getVersion());
    }
}

流程定义删除

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

流程资源下载,我们把流程资源文件上传到数据库,如果其他用户想要查看这些资源文件,可以从数据库下载资源文件

/**
     * 下载资源文件,使用 Activiti 提供的 api 下载资源文件,保存到文件目录
     */
public void getDeployment() throws IOException {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 RepositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3. 获取查询对象 ProcessDefinitionQuery,查询流程定义信息
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
        .processDefinitionKey("evection")
        .singleResult();
    // 4. 通过流程定义信息,获取部署 id
    String deploymentId = processDefinition.getDeploymentId();
    // 5. 通过 RepositoryService,传递部署 id 参数,读取资源信息
    InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
    // 6. 保存资源文件
    File file = new File("g:/evectionFlow.bpmn");
    FileOutputStream outputStream = new FileOutputStream(file);
    IOUtils.copy(bpmnInput, outputStream);
    bpmnInput.close();
    outputStream.close();
}

历史记录查询,即使流程定义已经删除了,流程执行的历史信息依然保存在 activiti 的 act_hi_* 相关的表中,我们还是可以查询流程执行的历史信息

public void findHistoryInfo() {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 HistoryService
    HistoryService historyService = processEngine.getHistoryService();
    // 3. 获取 actinst 表的查询对象
    HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
    // 4. 查询 actinst 表
    instanceQuery.processInstanceId("5001");
    instanceQuery.orderByHistoricActivityInstanceStartTime();
    // 5. 查询所有内容
    List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
    // 6. 输出
    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("----------------------------");
    }
}

流程的挂起与激活,某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行。

/**
 * 全部流程实例的挂起和激活
 */
@Test
public void suspendAllProcessInstance() {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 RepositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3. 查询流程定义
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("evection").singleResult();
    // 4. 获取当前流程定义的实例是否都是挂起状态
    boolean suspended = processDefinition.isSuspended();
    // 5. 获取流程定义的 id
    String definitionId = processDefinition.getId();
    // 6. 如果是挂起,改为激活状态,如果是激活状态,改为挂起状态
    if (suspended) {
        repositoryService.activateProcessDefinitionById(definitionId, true, null);
    } else {
        repositoryService.suspendProcessDefinitionById(definitionId, true, null);
    }
}
/**
 * 挂起或激活单个流程实例
 */
@Test
public void suspendSingleProcessInstance() {
    // 1. 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2. 获取 RuntimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 3. 得到当前流程实例的暂停状态
    ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId("27501").singleResult();
    boolean suspended = instance.isSuspended();
    // 4. 获取流程实例 id
    String instanceId = instance.getId();
    // 5. 如果是挂起,改为激活状态,如果是激活状态,改为挂起状态
    if (suspended) {
        runtimeService.activateProcessInstanceById(instanceId);
    } else {
        runtimeService.suspendProcessInstanceById(instanceId);
    }
}
posted @ 2024-12-01 16:32  低吟不作语  阅读(10)  评论(0编辑  收藏  举报