Activiti 7 配置及相关流程

1. Activiti 7 相关概念介绍:

1.1Activiti工作流引擎:

Activiti官方文档地址:https://www.activiti.org/

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

1.2建模语言BPMN:

BPMN是Business Process Model And Notation 业务流程模型和符号,就是用来描述业务流程的一种建模标准。BPMN最早由BPMI(BusinessProcessManagement 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进行描述的。

在这里插入图片描述
在这里插入图片描述

1.3Activiti使用步骤:

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

2. Activiti 7 项目的搭建:

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

2.1安装插件:

开发工具IDEA,在IDEA中需要安装Activiti的流程定义工具插件actiBPM。目前该插件从2014年11月后就没有再更新,对于IDEA版本只支持到2019.1。新版本的IDEA已经无法从插件市场搜索到该插件。安装时,可以到jetBrain的插件市场 https://plugins.jetbrains.com/ 搜索actiBPM插件,下载到本地后,从本地安装该插件。安装完成后,就可以使用这个插件在项目中编辑.bpmn的文件来定义业务流程了。但是这个文件之前介绍过,他的本质是一个xml文本文件,所以还是需要更多的了解xml的配置方式。
在这里插入图片描述

2.1.1更多的绘图方式:

bpmn-process-designer
一个基于 bpmn.js,Vue 2.x 和 ElementUI 开发的 BPMN 2.0 流程设计器(网页版),您可以使用它在浏览器上查看和编辑符合 BPMN 2.0 规范的流程文件。如图:
在这里插入图片描述
由本文偏后端着重介绍,此绘图方式可以在github参考学习

2.2初始化数据库表:

CREATE DATABASE activiti DEFAULT CHARACTER SET utf8;

编写对应的配置文件(默认为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/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">

    <!-- 这里可以使用 链接池 dbcp-->
    <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/activiti?serverTimezone=GMT%2B8" />
        <property name="username" value="root" />
        <property name="password" value="root" />
        <property name="maxActive" value="3" />
        <property name="maxIdle" value="1" />
    </bean>

    <bean id="processEngineConfiguration"
          class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <!-- 引用数据源 上面已经设置好了-->
        <property name="dataSource" ref="dataSource" />
        <!-- activiti数据库表处理策略 -->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>
</beans>

注:此处<property name="databaseSchemaUpdate" value="true"/>即为配置在数据库未查询到对应库表时自动创建

问题处理:

Cause: java.sql.SQLSyntaxErrorException: Table myactiviti.act_ge_property' doesn't exist 问题网址
因为mysql使用schema标识库名而不是catalog,因此mysql会扫描所有的库来找表,如果其他库中有相同名称的表,activiti就以为找到了,本质上这个表在当前数据库中并不存在。
设置nullCatalogMeansCurrent=true,表示mysql默认当前数据库操作,在mysql-connector-java 5.xxx该参数默认为true,在6.xxx以上默认为false,因此需要设置nullCatalogMeansCurrent=true

创建maven项目时需要的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>ActivitiDemo</artifactId>
        <groupId>com.roy</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>BasicDemo</artifactId>

    <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>
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</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>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</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>

</project>

添加log4j日志配置:

# 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=D:\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

编写java程序生成表:

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

package com.***;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.ProcessEngines;
import org.junit.Test;

/**
 * @author :lijiaheng
 * @date :Created in 2021/10/13
 * @description:
 */

public class TestCreateTable {
    /**
     * 生成 activiti的数据库表
     */
    @Test
    public void testCreateDbTable() {
        //默认创建方式
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //通用的创建方式,指定配置文件名和Bean名称
//        ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml", "processEngineConfiguration");
//        ProcessEngine processEngine1 = processEngineConfiguration.buildProcessEngine();
        System.out.println(processEngine);

    }
}

按照指定配置文件进行创建:
package com.***;

import org.activiti.engine.*;
import org.junit.Test;

public class TestCreate {

    /**
     * 使用activiti提供的默认方式来创建mysql的表
     *
     */
    @Test
    public void testCreateDbTable(){
//        需要使用avtiviti提供的工具类 ProcessEngines ,使用方法getDefaultProcessEngine
//        getDefaultProcessEngine会默认从resources下读取名字为actviti.cfg.xml的文件
//        创建processEngine时,就会创建mysql的表

//        默认方式
//        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        RepositoryService repositoryService = processEngine.getRepositoryService();
//        repositoryService.createDeployment();

//        使用自定义方式
//        配置文件的名字可以自定义,bean的名字也可以自定义
        ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.
                createProcessEngineConfigurationFromResource("activiti.cfg.xml",
                        "processEngineConfiguration");

//        获取流程引擎对象
        ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();

        RuntimeService runtimeService = processEngine.getRuntimeService();
        System.out.println(processEngine);
    }
}

创建后数据库表如下图所示:
在mysql中可以看到activiti用到的25张表
在这里插入图片描述

2.3表结构:

表分类 表名 解释

一般数据:
[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] 运行时流程执行实例

2.3.1 表结构解读:

从这些刚才创建的表中可以看到,activiti的表都以act_开头。第二个部分表示表的用途。用途也和服务的API对应。
ACT_RE :'RE’表示 repository。 这个前缀的表包含了流程定义和流程静态资源(图片,规则,等等)。
ACT_RU:'RU’表示 runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti 只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
ACT_HI:'HI’表示 history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
ACT_GE : GE 表示 general。 通用数据, 用于不同场景下完整的数据库表作用如下:
[ACT_RU_IDENTITYLINK]:运行时用户关系信息,存储任务节点与参与者的相关信息
[ACT_RU_JOB] 运行时作业
[ACT_RU_TASK] 运行时任务
[ACT_RU_VARIABLE] 运行时变量表

2.4 Activiti核心类:

service名称 service作用
RepositoryService: activiti的资源管理类
RuntimeService: activiti的流程运行管理类
TaskService: activiti的任务管理类
HistoryService: activiti的历史管理类
ManagerService: activiti的引擎管理类

2.4.1简单介绍:

RepositoryService
是activiti的资源管理类,提供了管理和控制流程发布包和流程定义的操作。使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。除了部署流程定义以外还可以:查询引擎中的发布包和流程定义。暂停或激活发布包,对应全部和特定流程定义。 暂停意味着它们不能再执行任何操作了,激活是对应的反向操作。获得多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。获得流程定义的pojo版本, 可以用来通过java解析流程,而不必通过xml。
RuntimeService
Activiti的流程运行管理类。可以从这个服务类中获取很多关于流程执行相关的信息
TaskService
Activiti的任务管理类。可以从这个类中获取任务的信息。
HistoryService
Activiti的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根据配置),比如流程实例启动时间,任务的参与者, 完成任务的时间,每个流程实例的执行路径,等等。 这个服务主要通过查询功能来获得这些数据。
ManagementService
Activiti的引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护。

3. Activiti 7 实际流程应用:

3.1流程符号:

事件 Event
在这里插入图片描述

活动 Activity
在这里插入图片描述

网关 GateWay

在这里插入图片描述

流向 Flow

在这里插入图片描述

实例demo:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.2查看bpmn文件的流程:

在这里插入图片描述

在这里插入图片描述

3.3 Activiti代码部署文件:

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

  /**
     * 部署流程定义  文件上传方式
     */
    @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.bpmn") // 添加bpmn资源
                //png资源命名是有规范的。Leave.myLeave.png|jpg|gif|svg  或者Leave.png|jpg|gif|svg
                .addClasspathResource("bpmn/Leave.myLeave.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());
    }

注: 传png文件主要目的是方便业务人员进行理解。bpmn文件也需要业务人员进行绘制。

ProcessEngine类是Activiti的核心类,我们需要的流程对象都可以从中取到。上图为获取repositoryService类处理资源相关的对象,对资源进行获取或处理。deploy() 此方法调用了部署,进行了流程部署。

3.2 启动Demo:

  /**
     * 启动流程实例
     */
    @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());

    }

注:与部署一致,我们通过ProcessEngine 获取了流程运行时操作对象,对流程运行时属性进行获取。

startProcessInstanceByKey(“myLeave”); 此处进行设置流程的定义,同名但是保存
在数据库的id不同。version会根据同样的key进行自增。

eg:
流程定义id 自动生成
流程实例id 自动生成
当前活动id

在这里插入图片描述

在这里插入图片描述

3.3 任务查询:

 /**
     * 查询当前个人待执行的任务
     */
    @Test
    public void testFindPersonalTaskList() {
//        任务负责人
        String assignee = "manager";
        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("----------------------------");
            System.out.println("流程实例id:" + task.getProcessInstanceId());
            System.out.println("任务id:" + task.getId());
            System.out.println("任务负责人:" + task.getAssignee());
            System.out.println("任务名称:" + task.getName());

        }
    }

在这里插入图片描述

3.4 流程定义查询

/**
    * 查询流程定义
    */
   @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("----------------------------");
           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());
       }
   }

在这里插入图片描述

3.5 删除流程

   @Test
    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);
    }

注意: 在执行的流程不能直接进行删除,需要修改对应的参数,此删除是将hi历史表中的数据清空,非管理原不应该有此权限。

在这里插入图片描述

 //设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
       //repositoryService.deleteDeployment(deploymentId, false);

在这里插入图片描述

3.6 查看流程实例:

/**
 * 查询流程实例
 */
@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());
    }
}

在这里插入图片描述

3.7 流程资源下载:

在流程执行过程中,可以上传流程资源文件。我们之前在部署流程时,已经将bpmn和描述bpmn的png图片都上传了,并且在流程执行过程中,也可以上传资源文件。如果其他用户想要查看这些资源文件,可以从数据库中把资源文件下载下来。
但是文件是以Blob的方式存在数据库中的,要获取Blob文件,可以使用JDBC来处理。也可以使用activiti提供的api来辅助实现。

api实现:
引入commons-io依赖

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

获取之前上传的文件:

@Test
    public void  queryBpmnFile() throws IOException {
//        1、得到引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        3、得到查询器:ProcessDefinitionQuery,设置查询条件,得到想要的流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey("myLeave")
                .singleResult();
//        4、通过流程定义信息,得到部署ID
        String deploymentId = processDefinition.getDeploymentId();
//        5、通过repositoryService的方法,实现读取图片信息和bpmn信息
//        png图片的流
        InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
//        bpmn文件的流
        InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
//        6、构造OutputStream流
        File file_png = new File("d:/myLeave.png");
        File file_bpmn = new File("d:/myLeave.bpmn");
        FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
        FileOutputStream pngOut = new FileOutputStream(file_png);
//        7、输入流,输出流的转换
        IOUtils.copy(pngInput,pngOut);
        IOUtils.copy(bpmnInput,bpmnOut);
//        8、关闭流
        pngOut.close();
        bpmnOut.close();
        pngInput.close();
        bpmnInput.close();
    }

3.8 流程历史查看:

/**
     * 查看历史信息
     */
    @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("<==========================>");
        }
    }

在这里插入图片描述

4. Activiti 7 处理不同业务问题方式:

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");

在这里插入图片描述

String processDefinitionKey:流程定义的唯一键 不能为空
String businessKey:每个线程实例上下文中关联的唯一键。
Map<String,Object> variables:在线程实例中传递的流程变量。这个流程变量可以在整个流程实例中使用。
String tenantId:租户ID,这是Activiti的多租户设计。相当于每个租户可以上来获取一个相对独立的运行环境。

注:这个字段的数据库长度设计是255。

在这里插入图片描述

/**
 * 查询流程实例
 */
@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());
    }
}

控制台打印:
在这里插入图片描述

4.1.2 流程挂起、激活

业务场景:

工作日不进行审批,或者流程需要调整不能够进行流程流转。

 /**
     * 添加业务key 到Activiti的表
     */
    @Test
    public void addBusinessKey(){
//        1、获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、获取RuntimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        3、启动流程的过程中,添加businesskey
//           第一个参数:流程定义的key
//           第二个参数:businessKey,存出差申请单的id,就是1001
        ProcessInstance instance = runtimeService.
                startProcessInstanceByKey("myLeave", "1001");
//        4、输出
        System.out.println("businessKey=="+instance.getBusinessKey());

    }

    /**
     * 全部流程实例的 挂起和 激活
     * suspend 暂停
     */
    @Test
    public void suspendAllProcessInstance(){
//        1、获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、获取Repositoryservice
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        3、查询流程定义,获取流程定义的查询对象
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey("myLeave")
                .singleResult();
//        4、获取当前流程定义的实例是否都是挂起状态
        boolean suspended = processDefinition.isSuspended();
//        5、获取流程定义的id
        String definitionId = processDefinition.getId();
//        6、如果是挂起状态,改为激活状态
        if(suspended){
//            如果是挂起,可以执行激活的操作,参数1:流程定义id 参数2:是否激活,参数3:激活时间
            repositoryService.activateProcessDefinitionById(definitionId,
                    true,
                    null);
            System.out.println("流程定义id:"+definitionId+",已激活");
        }else {
//        7、如果是激活状态,改为挂起状态,参数1:流程定义id 参数2:是否暂停 参数3 :暂停的时间
            repositoryService.suspendProcessDefinitionById(definitionId,
                    true,
                    null);
            System.out.println("流程定义id:"+definitionId+",已挂起");
        }
    }

    /**
     * 挂起、激活单个流程实例
     */
    @Test
    public void suspendSingleProcessInstance(){
//        1、获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、RuntimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        3、通过RuntimeService获取流程实例对象
        ProcessInstance instance = runtimeService.createProcessInstanceQuery()
                .processInstanceId("7501")
                .singleResult();
//        4、得到当前流程实例的暂停状态,true-已暂停  false -激活
        boolean suspended = instance.isSuspended();
//        5、获取流程实例id
        String instanceId = instance.getId();
//        6、判断是否已经暂停,如果已经暂停,就执行激活操作
        if(suspended){
//            如果已经暂停,就执行激活
            runtimeService.activateProcessInstanceById(instanceId);
            System.out.println("流程实例id:"+instanceId+"已经激活");
        }else {
//        7、如果是激活状态,就执行暂停操作
            runtimeService.suspendProcessInstanceById(instanceId);
            System.out.println("流程实例id:"+instanceId+"已经暂停");
        }
    }

    /**
     * 完成个人任务
     */
    @Test
    public void completTask(){
//        1、获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、获取TaskService
        TaskService taskService = processEngine.getTaskService();
//        3、使用taskservice获取任务,参数 流程实例的id,负责人
        Task task = taskService.createTaskQuery()
                .processInstanceId("2501")
                .taskAssignee("financer")
                .singleResult();
        System.out.println("流程实例id=="+task.getProcessInstanceId());
        System.out.println("流程任务id=="+task.getId());
        System.out.println("负责人=="+task.getAssignee());
        System.out.println("任务名称=="+task.getName());
//        4、根据任务的id完成任务
        taskService.complete(task.getId());
    }

4.2 流程变量:

业务场景:
如请假3天以内由部门经理审批,3天以上需要增加总经理审批这样的流程时可以利用流程变量 Map<String,Object> 进行处理。

4.3 流程变量的作用域

变量的作用域可以设置为GlobalLocal两种。
Global变量:

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

Local 变量:

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

使用demo:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

package com.***;

import com.***.demo.pojo.Evection;
import org.activiti.engine.*;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 测试流程变量
 */
public class TestVariables {
    /**
     * 流程部署
     */
    @Test
    public void testDeployment(){
//        1、创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、获取RepositoryServcie
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        3、使用service进行流程的部署,定义一个流程的名字,把bpmn和png部署到数据中
        Deployment deploy = repositoryService.createDeployment()
                .name("出差申请流程-variables")
                .addClasspathResource("bpmn/evection-global.bpmn")
                .deploy();
//        4、输出部署信息
        System.out.println("流程部署id="+deploy.getId());
        System.out.println("流程部署名字="+deploy.getName());
    }

    /**
     * 启动流程 的时候设置流程变量
     * 设置流程变量num
     * 设置任务负责人
     */
    @Test
    public void testStartProcess(){
//        获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取RunTimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        流程定义的Key
        String key = "myEvection2";
//        流程变量的map
        Map<String,Object> variables = new HashMap<>();
//        设置流程变量
        Evection evection = new Evection();
//        设置出差日期
        evection.setNum(2d);
//        把流程变量的pojo放入map
        variables.put("evection",evection);
//        设定任务的负责人
        variables.put("assignee0","李四");
        variables.put("assignee1","王经理");
        variables.put("assignee2","杨总经理");
        variables.put("assignee3","张财务");
//        启动流程
        runtimeService.startProcessInstanceByKey(key,variables);
    }

    @Test
    public void queryTask(){
//        流程定义的Key
        String key = "myEvection2";
//        任务负责人
//        String assingee = "李四";

        String assingee = "李四";
        //        获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取taskservice
        TaskService taskService = processEngine.getTaskService();
//        查询任务
        final List<Task> tasks = taskService.createTaskQuery()
                .processDefinitionKey(key)
//                .taskAssignee(assingee)
                .list();
        for(Task task:tasks){
            //     根据任务id来   完成任务
            System.out.println(task.getId());
            System.out.println(task.getName());
            System.out.println(task.getAssignee());
        }

    }
    /**
     * 完成个人任务
     */
    @Test
    public void completTask(){
//        流程定义的Key
        String key = "myEvection2";
//        任务负责人
        String assingee = "王经理1";

//        String assingee = "张财务";
        //        获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取taskservice
        TaskService taskService = processEngine.getTaskService();
//        查询任务
        Task task = taskService.createTaskQuery()
                .processDefinitionKey(key)
                .taskAssignee(assingee)
                .singleResult();
        if(task != null){
            //     根据任务id来   完成任务
            taskService.complete(task.getId());
        }

    }
}

在这里插入图片描述

在这里插入图片描述

注:Global变量可以全局的传递,例如startProcessInstanceByKey() 中的Map<String,Object> 类型流程变量,将保存的pojo对象向后传递。传递过程中可以进行覆盖。

4.3.2 设置Local流程变量:

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

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

// 完成任务
taskService.complete(taskId);

2.通过当前任务设置

@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)
}

注:其中流程数据在act_ru_task表中查询获得

ID_	REV_	EXECUTION_ID_	PROC_INST_ID_	PROC_DEF_ID_	NAME_	BUSINESS_KEY_	PARENT_TASK_ID_	DESCRIPTION_	TASK_DEF_KEY_	OWNER_	ASSIGNEE_	DELEGATION_	PRIORITY_	CREATE_TIME_	DUE_DATE_	CATEGORY_	SUSPENSION_STATE_	TENANT_ID_	FORM_KEY_	CLAIM_TIME_	APP_VERSION_
10002	1	7502	7501	myLeave:3:5004	部门经理审批				_4		manager		50	2021-10-15 17:19:30.811			1				
15002	1	12502	12501	myLeave:3:5004	部门经理审批				_4		manager		50	2021-10-15 17:22:37.774			1				
17505	1	17502	17501	myLeave:3:5004	提交请假申请	testBusinessKey			_3		worker		50	2021-10-18 17:18:18.921			1				
22512	1	22509	22501	myEvection2:1:20003	创建出差申请				_3		李四		50	2021-10-18 17:36:33.106			1				
25012	1	25009	25001	myEvection2:1:20003	创建出差申请				_3		李四		50	2021-10-18 17:45:53.715			1				
30012	1	30009	30001	myEvection2:2:27503	创建出差申请				_3		李四		50	2021-10-18 17:46:32.441			1				

5. Activiti 7 网关:

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

5.1 排他网关ExclusiveGateway

排他网关,用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支,注意:排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会选择id值较小的一条分支去执行。

为什么要用排他网关?
不用排他网关也可以实现分支,如:在连线的condition条件上设置分支条件。在连线设置condition条件的缺点:如果条件都不满足,流程就结束了(是异常结束)。

如果 使用排他网关决定分支的走向,如下:
在这里插入图片描述

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

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

5.2 并行网关ParallelGateway

并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:
fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
join汇聚: 所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。

注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切 分成多个并行分支。 与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。

说明:此时会要求技术经理和项目经理都进行审批。而连线上的条件会
被忽略。

在这里插入图片描述

技术经理和项目经理是两个execution分支,在act_ru_execution表有两条记录分别是技术经理和项目经理,act_ru_execution还有一条记录表示该流程实例。待技术经理和项目经理任务全部完成,在汇聚点汇聚,通过parallelGateway并行网关。并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。

5.3 包含网关InclusiveGateway

包含网关可以看做是排他网关和并行网关的结合体。
和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。
包含网关的功能是基于进入和外出顺序流的:
分支: 所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
汇聚: 所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
在这里插入图片描述

说明:这里当请假天数超过3天,需要项目经理和人事经理一起审批。而请假天数不超过3填,需要技术经理和人事经理一起审批。所有符合条件的分支也会在后面进行汇聚。

5.4 事件网关EventGateway

事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。与此同时,会为每个外出顺序流创建相对的事件订阅。
事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的"执行", 相反它们让流程引擎去决定执行到事件网关的流程需要订阅哪些事件。

要考虑以下条件:

  1. 事件网关必须有两条或以上外出顺序流;
  2. 事件网关后,只能使用intermediateCatchEvent类型(activiti不支持基于事件
    网关后连接ReceiveTask)
  3. 连接到事件网关的中间捕获事件必须只有一个入口顺序流。
    与事件网关配合使用的intermediateCatchEvent:

在这里插入图片描述

这个事件支持多种事件类型:
Message Event:消息事件
Singal Event: 信号事件
Timer Event: 定时事件

在这里插入图片描述
使用事件网关定义流程:

在这里插入图片描述

6.总结:

本文比较简单的介绍了Activiti7的创建和使用方式,而实际业务场景中我们会遇到更多和更复杂的场景。但是,复杂工作流的功能实现也是基于基础的功能进行叠加,掌握好基础的工作流功能后能够更好的进行复杂自定义化开发。
例如:工作流基础表在实际业务中,我们可以字形封装sql查询,以简化我们业务场景的实现。注意:需要进行事务控制,不然不通过工作流引擎容易导致数据错误。其次,可以通过学习高级用法例如监听器,进行更多业务功能实现。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐