简单介绍 Activiti 7 并进行实操

708 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、工作流

概述

  • 借助计算机实现流程的自动化控制

    • 比如请假审批、报销审批等
  • 传统方式下,一旦流程发生改变,需要修改大量的代码

  • 工作流引擎,业务流程发生改变,代码基本上不需要有什么改变

  • 工作流能够将业务和流程分离,工作流引擎主要负责流程的流转,使用bpmn文件定义流程

Activiti7

官网:www.activiti.org

Activiti 是一个工作流引擎, activiti 可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言(BPMN2.0)进行定义,业务系统按照预先定义的流程进行执行,实现了业务系统的业务流程由 activiti 进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。

简单说,就是Activiti是一个工作流引擎,使用它能够给我们带来健壮性、减少开销等。用它就对啦!

image.png

二、搭建环境

  • 创建数据库,用于存放 activiti 启动后生成的表

  • 创建项目,添加依赖

    <?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">
        <modelVersion>4.0.0</modelVersion>
    ​
        <groupId>cn.regex</groupId>
        <artifactId>day1108_activiti-demo</artifactId>
        <version>1.0.0</version>
    ​
        <properties>
            <slf4j.version>1.6.6</slf4j.version>
            <log4j.version>1.2.12</log4j.version>
            <activiti.version>7.0.0.SR1</activiti.version>
        </properties>
    ​
        <dependencies>
            <!-- activiti引擎 -->
            <dependency>
                <groupId>org.activiti</groupId>
                <artifactId>activiti-engine</artifactId>
                <version>${activiti.version}</version>
            </dependency>
            <!-- 整合Spring -->
            <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>5.1.40</version>
            </dependency>
            <!-- mybatis -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.4.5</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>
            <!-- log end -->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>2.6</version>
            </dependency>
            <!--数据库连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.2.4</version>
            </dependency>
        </dependencies>
    </project>
    
  • 定义配置文件(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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql:///activiti"/>
            <property name="username" value="root"/>
            <property name="password" value="admin"/>
        </bean>
        <!-- 配置独立的流程引擎 -->
        <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
            <property name="dataSource" ref="dataSource"/>
            <property name="databaseSchemaUpdate" value="true"/>
        </bean>
    </beans>
    
  • 日志配置文件(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的工具类,生成acitivti需要的数据库表(总共25张)

    public class TestInit {
        @Test
        public void testInit(){
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
            System.out.println(processEngine);
        }
    }
    

三、业务建模

  • 安装插件(actiBPM)

    • 在Plugins中搜索actiBPM,然后点击Search in repositories,若搜索不到,则需要本地安装

      image-20210601110835928.png image-20210601111038435.png

    • 从IDEA官网下载actiBPM.jar包,下载地址:plugins.jetbrains.com/plugin/7429… image-20210601111706743.png

  • 绘制业务流程图

四、初阶

  • 部署流程定义:使用RepositoryService将文件部署到activiti

  • 启动流程实例:RuntimeService

  • 查询待办任务:TaskService

    • 对于查询xxxService.createXXXQuery()
    • list()返回集合,singleResult()返回单个对象
  • 完成待办任务:TaskService

    taksService.addComment(taskId,processInstanceId);
    
  • 添加批注:TaskService

    taksService.addComment(taskId,processInstanceId);
    
  • 查询历史任务:HistoryService

    List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
        .activityType("userTask")//过滤任务类型
        .finished()//只查询完成任务
        .list();
    
  • 查询批注信息TaskService

    List<Comment> list = taskService.getComment(taskId);
    

五、进阶

  • 查询流程定义:RepositoryService

  • 删除流程定义

    repositoryService.deleteDeployment(deploymentId);//如果存在正在运行流程实例,删除抛异常
    repositoryService.deleteDeployment(deploymentId,true);//删除流程定义的同时,把正在运行的流程实例也一并删除(级联删除)
    
  • 资源的下载(了解)

    • 查询流程定义

    • 根据流程定义获取资源名称

    • 获取输入流

      repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
      
    • 把文件输入到指定位置

  • 流程定义和流程实例的关系(类比类和对象的关系)

  • 业务Key(BusinessKey):在activities中,业务和流程是分离的,可以通过BusinessKey进行绑定

  • 流程定义挂起和激活(应用场景)

    • 流程定义挂起之后,不能发起新的流程实例
  • 流程实例挂起和激活

    • 流程实例挂起后,任务不能往后执行,但不会影响发起新的流程实例
  • 任务分配人

    • 方式一:固定分配方式(硬编码,不使用)
    • 方式二:UEL表达式:在bpmn图中使用${变量名}占位符,在启动流程的时候,将变量传入
    • 方式三:监听器(了解):可以监听节点创建的时机,给任务设置负责人
  • 任务候选人

    • 一个任务可以设置多个候选人,多个候选人都可以看到这个任务
    • 候选人需要将任务认领为个人任务,才能将流程继续
  • 流程变量:流程变量即存在于流程中的变量,是Activiti用于管理工作流所需要的变量,作为业务系统和Activiti之间的桥梁

    • 在节点中设置负责人,使用UEL表达式定义占位符

    • 在连线上设置UEL表达式,控制流程走向

    • 异常

      • 没给传入流程变量,报错
      • 所有连线的条件都不满足,报错
      • 所有条件都满足,所有连线都会走(导致每个分支都走一遍)
    • 网关

      • 排他网关

        • 只会走分支中的条件为true那条分支
        • 如果有多个条件都为true,只会走连线编号最小的那个路径
      • 并行网关

        • 同时有多个任务并行执行,需要这些任务都同时做完,才能走到下一个节点
      • 包含网关

        • 排他网关+并行网关

六、相关API

Service接口说明
RepositoryServiceActiviti的资源管理接口
RuntimeServiceActiviti的流程运行管理接口
TaskServiceActiviti的任务管理接口
HistoryServiceActiviti的历史管理接口
ManagementServiceActiviti的引擎管理接口

获取service对象:

// 获取流程引擎对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取资源管理对象
RepositoryService repositoryService = processEngine.getRepositoryService();
// 获取流程运行管理对象
RuntimeService runtimeService = processEngine.getRuntimeService();
// 获取任务管理对象
TaskService taskService = processEngine.getTaskService();
// 获取历史管理对象
HistoryService historyService = processEngine.getHistoryService();
// 获取引擎管理对象
ManagementService managementService = processEngine.getManagementService();

RepositoryService

API说明
createDeployment()创建部署对象
createDeploymentQuery()创建部署查询
createProcessDefinitionQuery()创建流程定义查询
deleteDeployment("部署ID")根据部署ID查询部署
addClasspathResource("资源路径")添加资源(resources目录下获取)
name("名称")部署名称
deploy()部署到Activiti引擎
getResourceAsStream("部署ID", "文件名称")获取部署的bpmn文件输入流
processDefinitionKey("流程定义Key")指定流程定义的Key
orderByProcessDefinitionVersion()根据流程定义对象版本进行排序,必须与desc()或asc()一起使用
Deployment
getId()获取流程部署ID
getName()获取流程部署名称
ProcessDefinition
getId()获取流程定义ID
getName()获取流程定义名称
getKey()获取流程定义的Key
getVersion()获取流程定义的版本
getDeploymentId()获取部署ID
getResourceName()获取部署的bpmn文件的名称
getDiagramResourceName()获取部署的png文件的名称
@Test
public void testDeploy() {
    // 创建流程引擎对象,类似于 Shiro 中的SecurityManagement
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取资源管理对象
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 进行部署
    Deployment deployment = repositoryService.createDeployment()    // 创建部署
            .addClasspathResource("leaveProcess.bpmn")  // 添加资源
            .addClasspathResource("leaveProcess.png")
            .name("报销流程")
            .deploy();
    System.err.println(deployment.getId());     // 流程部署ID:2501
    System.err.println(deployment.getName());   // 流程部署名称:报销流程
}
@Test
public void testDownloadResource() throws Exception {
    // 获取流程引擎对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取资源管理对象
    RepositoryService repositoryService = processEngine.getRepositoryService();
    List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery() // 创建流程定义查询对象
            .orderByProcessDefinitionVersion()  // 根据流程定义对象版本进行排序
            .desc()
            .list();
    ProcessDefinition processDefinition = list.get(0);  // 获取最新的一个
    String deploymentId = processDefinition.getDeploymentId();  // 获取部署ID
    // 获取leaveProcess.bpmn输入流
    InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
    // 获取图片(leaveProcess.png)输入流
    InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
    // 设置leaveProcess.bpmn输出流
    FileOutputStream bpmnOutput = new FileOutputStream("F:\regex\test\leaveProcess.bpmn");
    // 设置图片(leaveProcess.png)输出流
    FileOutputStream pngOutput = new FileOutputStream("F:\regex\test\leaveProcess.png");
    IOUtils.copy(bpmnInput, bpmnOutput);
    IOUtils.copy(pngInput, pngOutput);
}

RuntimeService

API说明
startProcessInstanceByKey("key值", "map")根据指定的key创建一个最新版本的流程实例(map可有可无)
ProcessInstance
getBusinessKey()获取业务标识
getDeploymentId()获取部署ID
getProcessDefinitionId()获取流程定义ID
getId()获取流程实例ID
getName()获取流程名称
@Test
public void testStartProcess() {
    // 获取流程引擎对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取运行管理对象
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 根据流程定义的key启动流程实例,该key为定义bpmn时设置的
    ProcessInstance leaveProcess = runtimeService.startProcessInstanceByKey("leaveProcess");
    System.err.println("获取BusinessKey:" + leaveProcess.getBusinessKey());
    System.err.println("获取部署ID:" + leaveProcess.getDeploymentId());
    System.err.println("获取流程定义ID:" + leaveProcess.getProcessDefinitionId());    // leaveProcess:1:2504
    System.err.println("获取流程实例ID:" + leaveProcess.getId());     // 5001
    System.err.println("获取流程名称:" + leaveProcess.getName());
}

TaskService

API说明
createTaskQuery()创建任务查询
processDefinitionKey("流程定义Key")根据指定的流程定义key查询任务集合
taskAssignee("任务负责人")根据指定的任务负责人名称查询任务集合
list()获取任务集合
singleResult()获取单个任务
complete("任务ID")通过任务ID将指定的任务标记为完成
addComment("任务ID", "流程实例ID", "批注信息")在任务完成之前给任务添加批注信息
getTaskComments("任务ID")通过任务ID获取任务的评论
claim("任务ID", "用户ID")认领任务(用户ID即assignee)
Task
getId()获取任务ID
getProcessInstanceId()获取流程实例ID
getName()获取任务名称
Comment
getFullMessage()获取评论的完整信息
@Test
public void testAddComment() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    String assignee = "李四";
    List<Task> taskList = taskService.createTaskQuery()
            .processDefinitionKey("leaveProcess")
            .taskAssignee(assignee)
            .list();
    for (Task task : taskList) {
        System.err.println(task);
        // 任务ID,流程实例ID
        taskService.addComment(task.getId(), task.getProcessInstanceId(), task.getName() + "再日回来");
        taskService.complete(task.getId());
    }
}

HistoryService

API说明
createHistoricActivityInstanceQuery()创建历史活动实例查询
activityType("活动类型")根据指定的活动类型查询历史活动实例(比如userTask,act_hi_actinst表)
processInstanceId("流程实例ID")根据指定的流程实例ID查询历史活动实例
taskAssignee("任务负责人")根据指定的任务负责人查询历史活动实例
finished()查询已完成的历史活动实例
list()获取历史活动实例集合
HistoricActivityInstance
getStartTime()获取活动实例开始时间
getEndTime()获取活动实例结束时间
getDurationInMillis()获取活动实例耗时
getTaskId()获取任务ID
@Test
public void testSelectHistoryTask() {
    // 流程实例ID
    String processInstanceId = "12501";
    // 任务负责人
    String assignee = "李四";
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    HistoryService historyService = processEngine.getHistoryService();
    TaskService taskService = processEngine.getTaskService();
    // 通过指定的流程实例ID和任务负责人获取已完成的所有用户任务
    List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
            .activityType("userTask")
            .processInstanceId(processInstanceId)
            .taskAssignee(assignee)
            .finished()         // 查询已完成
            .list();
    for (HistoricActivityInstance instance : list) {
        System.err.println("list:" + list.size());
        System.err.println(instance);
        System.err.println("开始时间:" + instance.getStartTime());
        System.err.println("结束时间:" + instance.getEndTime());
        System.err.println("任务耗时:" + instance.getDurationInMillis());
        System.err.println(instance.getTaskId());
        // 根据任务ID获取任务的所有评论信息
        List<Comment> taskComments = taskService.getTaskComments(instance.getTaskId());
        System.out.println("taskComment:" + taskComments.size());
        for (Comment taskComment : taskComments) {
            // 获取评论的完整信息
            String message = taskComment.getFullMessage();
            System.err.println(message);
        }
    }
}