目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

Activiti7

ll# 工作流引擎Activiti7

   多数互联网和IT公司里面用的技术,钉钉、飞书等效能工具、企业OA、ERP、CRM

需求背景
   公司规定连续加班3天,去按摩可以报销一定比例的费用。

image.png

什么是工作流(WorkFlow)

  • 就是通过计算机对业务流程自动化执行管理
  • 主要解决的是使在多个参与者之间按照某种预定义的规则自动进行传递文档或任务的过程,促使此目标的实现
  • 企业日常中很多工作流程,比如:请假流程、报销流程、报价处理、合同审核
  • 使用行业
    • 消费品行业,制造业,电信服务业,银证险等金融服务业,物流服务业,物业服务业
    • 物业管理,大中型进出口贸易公司,政府事业机构,研究院所及教育服务业等,特别是大的跨国企业和集团公司。
  • 具体应用
    • 关键业务流程:合同审核、客户电话处理、供应链管理等
    • 行政管理类:出差申请、加班申请、请假申请、用车申请、各种办公用品申请 等凡是原来手工流转处理的行政表单。
    • 财务相关类:付款请求、应收款处理、日常报销处理、出差报销、预算和计划申请等
    • 特殊服务类:贸易公司报关处理、物流公司货物跟踪处理等各种通过表单逐步手工流转均可应用工作流软件自动规范地实施
  • 基于上面的需求,就出现了工作流系统,就是系统中包括了工作流的功能

什么是工作流引擎

  • 就是实现了工作流功能的框架,比如Activiti, Flowable 等
  • 不用工作流引擎是否可以实现工作流审批?肯定可以的啊,只不过用了框架更方便。 比如:操作数据库,可以JDBC直接操作,也可以用封装好的MybaitsPlus去操作,就看效率了

自己实现工作流功能的优缺点

id姓名报销费用审批人状态
1老王按摩费用1688元张三0
2大钊沐足费用998元张三0
3老帆加班打的费用100元冰冰0
  • 审批流程:发起人->主管->HR->财务->结束
  • 操作流程
    • 发起流程后往表里面插入记录,状态为0
    • 轮到主管审批人的时候就查找数据库状态,通过就是1,拒绝就是-1
    • 轮到HR审批人的时候就查找数据库状态,通过就是2,拒绝就是-1
    • 轮到财务审批人的时候就查找数据库状态,通过就是3,拒绝就是-1

问题

  • 上面代码可以很好控制,但是如果调整了流程,不用HR审批,怎么办?代码是不是要调整 就不通用
  • 而且考虑也不全面,一个流程是这样,假如大集团,几千几万个流程模版,那代码就没法看了
  • 所以就可以通过工作流引擎来解决上面的问题!!

BPM 和 BPMN

什么是BPM

   Business Process Management业务流程管理,它是一种涵盖流程建模、执行、监控、优化和自动化的综合【​方法论​】。

   关键部分包括如下:

  • 流程设计​:创建流程的蓝图。
  • 流程实施​:执行设计好的流程。
  • 流程监控​:实时跟踪流程的性能。
  • 流程分析​:评估流程的效果并找出改进点。
  • 流程优化​:基于分析结果改进流程。

什么是BPMN
   Business Process Model and Notation是一种业务流程建模符号,【一种标准的方式】来表示业务流程。

   BPMN的目的是要让业务分析师、开发人员和业务流程管理者能够轻松地理解、设计、执行和改进业务流程

   统一标准,类似UML一样,有多类型的符号来表示工作流中的各个节点含义

   bpmn图形其实是通过​xml表示业务流程​,.bpmn文件使用文本编辑器打开

关键元素

  • 事件​(Events):开始事件、结束事件、中间事件。
  • 活动Activities)​:任务、子流程
  • 网关Gateways)​:排他网关、并行网关。
  • 流程路径Flows)​:序列流、条件流。

image.png

主流工作流引擎对比

   在选择工作流引擎时,需要考虑项目的具体需求团队的技术栈和经验、以及对性能和稳定性的要求

   ActivitiFlowable因其与Spring的良好集成而在Java生态比较多

工作流引擎介绍优点缺点适用场景
ActivitiActiviti是一个开源的工作流引擎,基于BPMN 2.0,与Spring框架集成良好。轻量级,易于集成 Spring支持,活跃的社区和文档版本更新可能导致兼容性问题适用于中大企业和需要快速集成工作流的场景
FlowableFlowable是从Activiti分叉出来的工作流引擎,修复了Activiti的一些已知问题。修复了Activiti的一些bug,支持最新的BPMN规范,良好的文档和社区支持相对于Activiti,社区规模较小适用于需要遵循最新工作流标准和规范的项目
jbpmjbpm是一个历史悠久的工作流引擎,集成了规则引擎Drools。强大的规则引擎集成- 支持复杂的决策逻辑学习曲线陡峭,社区支持比较低适用于需要复杂规则和决策逻辑的项目

工作流引擎Activiti7

  • 是一个工作流引擎,遵循BPMN 2.0标准,提供了模块化设计、强大的任务管理、高度定制化等功能。
  • Activiti可以将业务系统中复杂的业务流程抽取出来
  • 使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,
  • 减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
  • Activiti7与之前的Activiti5和Activiti6在架构和定位上有显著不同,目前主要使用7.X版本

优点

  • 开源与社区支持 Activiti7是开源的,拥有广泛的社区支持和丰富的文档资源
  • 强大的任务管理 提供丰富的任务管理功能,支持复杂业务流程的自动化处理,提高工作效率
  • 模块化与灵活性 模块化设计使得系统易于维护和扩展,可以根据实际需求加载和使用不同的组件

缺点

  • 技术门槛较高 由于Activiti7采用模块化设计,对开发者的技术要求较高,需要掌握相关的云原生技术和Java开发技能
  • 对BPMN 2.0元素的支持有限 Activiti7对BPMN 2.0中某些元素的支持较少,需要开发者通过其他方式实现特定功能。
  • 学习曲线陡峭 对于初学者来说,Activiti7的学习曲线可能较为陡峭,需要投入较多的时间和精力进行学习和实践

如何使用工作流引擎

  • 流程定义(画图)
    image.png
  • 部署流程模版(往数据库里插入流程模版)
  • 启动流程实例(根据模版创建流程实例,比如通过java 类 创建对象)
    image.png
  • 领取任务节点处理 (不同的节点处理流程任务,比如 你老板审批你的请假 )
  • 结束流程实例(审批流程走完了)

使用工作流引擎的步骤

顺序流程说明
添加依赖(创建工程)创建Java工程 工作流引擎其实就是一堆jar包API,和普通框架依赖包一样添加进来
流程定义(画图)使用activiti流程建模工具定义业务流程(.bpmn文件) ,就是通过xml定义业务流程。
流程定义部署部署业务流程定义,就是把 .bpmn文件,使用activiti提供的api把流程定义内容存储到数据库
启动流程实例(流程模板)化流程实例ProcessInstance启动流程实例表示开始一次业务流程的运行,比如:员工报销流程定义部署完成后,如果张三要报销就可以启动一个流程实例,如果李四要报销也启动一个流程实例,两个流程的执行互相不影响
查询待办任务(Task节点)业务流程已经交给activiti管理,通过activiti的API就可以查询当前流程执行到哪,当前用户需要办理什么任务。工作流引擎帮我们管理了这些事情,不需要开发人员自己编写在sql语句查询。
处理任务Task节点用户查询待办任务后,就可以办理对应任务,比如通过审批,拒绝,或者增加备注等
流程结束当工作流程的任务节点办理完成,没有下一个任务结点了,这个流程实例就结束了

工作流引擎Activiti7环境搭建(JDK21)

准备工作说明
Mysql数据库创建Activiti7工作流相关定义和流程实例是存储到数据库,有25张表,可以配置自动创建
Java工程创建采用JDK21+Maven3.9版本+Mysql8.X

安装Mysql8.x
   则创建数据库CREATE DATABASE activiti DEFAULT CHARACTER SET utf8

#创建目录
mkdir -p /home/data/mysql/
#创建配置文件
touch /home/data/mysql/my.cnf

docker run \
    -p 3306:3306 \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -v /home/data/mysql/conf:/etc/mysql/conf.d \
    -v /home/data/mysql/data:/var/lib/mysql:rw \
    -v /home/data/mysql/my.cnf:/etc/mysql/my.cnf \
    --name mysql \
    --restart=always \
    -d mysql:8.0

Maven项目工程创建和依赖添加

   在线创建spring项目 https://start.spring.io/

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.activiti/activiti-spring-boot-starter -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.1.0.M6</version>
        </dependency>
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</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.11</version>
            <scope>test</scope>
        </dependency>
        <!-- log start -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

日志文件配置

log4j.rootLogger=DEBUG,console,file

log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/soulboy.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

数据库连接配置
   测试链接没问题,则创建数据库 CREATE DATABASE xd_activiti_demo DEFAULT CHARACTER SET utf8坑:避免数据库有多个名称含有 activiti, 不然会报错

activiti.cfg.xml文件
   要求在 resources 下创建 activiti.cfg.xml 文件,默认方式目录和文件名不能修改,activiti的源码中已经设置固定了

<?xml version="1.0" encoding="UTF8"?>
<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://192.168.1.11:3306/xd_activiti_demo"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <property name="maxActive" value="10"/>
        <property name="maxIdle" value="4"/>
    </bean>
    <!--配置流程引擎对象,默认方式下 bean的id,固定processEngineConfiguration-->
    <bean id="processEngineConfiguration"
          class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <!--引入上面配置的数据库链接池-->
        <property name="dataSource" ref="dataSource"/>
        <!--actviti数据库表在生成时的策略,支持多种方式 true,false,create-drop,drop-create
         默认是false 不会自动创建表;推荐是true常用的是如果数据库中已经存在相应的表,那直接使用,如果不存在,则会创建-->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>

</beans>

单元测试验证
   src/test/java/ActivitiTest.java

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

public class ActivitiTest {
    @Test
    public void testV1(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        var processEngine = ProcessEngines.getDefaultProcessEngine();
        System.out.println(processEngine);
    }
}

image.png

工作流引擎Activiti7可视化插件

   安装BPMN工作流定义插件,https://plugins.jetbrains.com/plugin/15222-activiti-bpmn-visualizer/versions

image.png

image.png

image.png

image.png

image.png

image.png

src/main/resources/bpmn/testV1.bpmn20.xml

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef">
  <process id="testV1" name="报销流程定义" isExecutable="true">
    <startEvent id="sid-96c7cbd5-959f-43be-9ada-c8c5ec2634e5"/>
    <userTask id="sid-0bcc0d21-c654-4d6f-9a9b-86d1665405e1" name="主管审批" activiti:assignee="张三"/>
    <userTask id="sid-2879de4c-1035-43ab-bf00-4cf53fc52a0e" name="财务审批" activiti:assignee="李四"/>
    <endEvent id="sid-e27667fc-3883-4c8f-a2eb-1d6943658f21"/>
    <sequenceFlow id="sid-1f6894c4-cbbd-4a0f-9a3b-c532257b70f9" sourceRef="sid-96c7cbd5-959f-43be-9ada-c8c5ec2634e5" targetRef="sid-0bcc0d21-c654-4d6f-9a9b-86d1665405e1"/>
    <sequenceFlow id="sid-dc1bad4f-563d-4e3a-82aa-5967975e7f30" sourceRef="sid-0bcc0d21-c654-4d6f-9a9b-86d1665405e1" targetRef="sid-2879de4c-1035-43ab-bf00-4cf53fc52a0e"/>
    <sequenceFlow id="sid-ac464ee7-39a4-48ef-a4e0-23a4bdb762a6" sourceRef="sid-2879de4c-1035-43ab-bf00-4cf53fc52a0e" targetRef="sid-e27667fc-3883-4c8f-a2eb-1d6943658f21"/>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_testV1">
    <bpmndi:BPMNPlane bpmnElement="testV1" id="BPMNPlane_testV1">
      <bpmndi:BPMNShape id="shape-1873d429-85f6-40c2-a418-d424d257165c" bpmnElement="sid-96c7cbd5-959f-43be-9ada-c8c5ec2634e5">
        <omgdc:Bounds x="-115.0" y="-95.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-be9e33f2-08ba-4703-98d6-e1097f42db5d" bpmnElement="sid-0bcc0d21-c654-4d6f-9a9b-86d1665405e1">
        <omgdc:Bounds x="-35.0" y="-120.0" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-48ae18cb-747a-497f-b77b-2b1e979deb61" bpmnElement="sid-2879de4c-1035-43ab-bf00-4cf53fc52a0e">
        <omgdc:Bounds x="115.0" y="-120.0" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-3dfd601e-1a1a-4771-8316-fcb6922198b8" bpmnElement="sid-e27667fc-3883-4c8f-a2eb-1d6943658f21">
        <omgdc:Bounds x="275.0" y="-95.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-64d40d0e-e08b-460e-9d02-238d13404908" bpmnElement="sid-1f6894c4-cbbd-4a0f-9a3b-c532257b70f9">
        <omgdi:waypoint x="-85.0" y="-80.0"/>
        <omgdi:waypoint x="-35.0" y="-80.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-60f150fd-a1ee-4108-a755-2531626e3099" bpmnElement="sid-dc1bad4f-563d-4e3a-82aa-5967975e7f30">
        <omgdi:waypoint x="65.0" y="-80.0"/>
        <omgdi:waypoint x="115.0" y="-80.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-e1cf31c1-d1b7-4c70-b784-d3289131296d" bpmnElement="sid-ac464ee7-39a4-48ef-a4e0-23a4bdb762a6">
        <omgdi:waypoint x="215.0" y="-80.0"/>
        <omgdi:waypoint x="275.0" y="-80.0"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

流程定义部署

   定义了业务工作流程,就需要部署,即解析XML存储到数据库里面

/**
     * 流程定义部署到数据库
     */
    @Test
    public void testDeploy(){
        // 创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取资源服务
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //部署流程定义到数据库
        Deployment deployed = repositoryService.createDeployment().addClasspathResource("bpmn/testV1.bpmn20.xml").deploy();
        System.out.println("id=" + deployed.getId());
        System.out.println("name=" + deployed.getName());
        System.out.println("key=" + deployed.getKey());
        System.out.println("deploymentTime=" + deployed.getDeploymentTime());
    }

image.png

Activiti7核心Service类

   Activiti将不同生命周期的服务封装在不同的Service中,这些Service通过ProcessEngine获取,主要的服务类包括

服务名称功能描述
RepositoryService部署流程定义,提供了对repository的存取服务,查询部署包和流程定义。通过服务将设计好的流程图(BPMN文件)部署到Activiti引擎。操作流程定义的状态,比如挂起和暂停等
RuntimeService提供了启动流程、查询流程实例、设置获取流程实例变量等功能。
TaskService提供了对用户任务相关的操作,如运行时任务查询、领取、完成、删除以及变量设置等。
HistoryService获取流程的历史数据的服务,比如历史流程实例和任务的查询
ManagementService提供了对Activiti流程引擎的管理和维护功能,主要用于Activiti系统的日常维护

image.png

image.png

Activiti7库表资源介绍

   Activiti7的表结构是其数据库架构的核心组成部分,用于存储和管理与工作流引擎相关的各种数据。

   Activiti7的表结构通常包含多个表,这些表根据功能和用途进行分类,并遵循一定的命名规则,Activiti7的表名都以ACT_为前缀,后跟两个字母的缩写标识,用于表示表的用途,如下

  • GE:表示general(通用),用于存放一些通用的数据。
  • RE:表示repository(存储库),包含流程定义和流程静态资源(如图片、规则等)。
  • RU:表示runtime(运行时),包含流程实例、任务、变量等运行中的数据。
  • HI:表示history(历史),包含历史流程实例、变量、任务等数据。
  • EVT:表示event(事件),用于记录事件日志。
  • PROCDEF:表示process define(流程定义),用于记录流程定义信息。
表结构分类表名字说明
通用数据表(ACT_GE_*)ACT_GE_BYTEARRAY用于保存与流程引擎相关的资源,如流程文件、流程图片等。资源被转换为byte数组存储在表中。
ACT_GE_PROPERTY存储整个流程引擎级别的数据,如属性名称和属性值等。
流程定义和部署信息表(ACT_RE_*)ACT_RE_DEPLOYMENT记录部署信息,包括部署的名称、时间等。
ACT_RE_MODEL流程设计模型部署表
ACT_RE_PROCDEF记录流程定义信息,如流程定义的名称、key、分类等。
运行时数据表(ACT_RU_*)ACT_RU_EXECUTION记录流程实例和执行流的信息,包括流程实例ID、执行流ID、父执行流ID等。
ACT_RU_TASK记录任务数据,包括任务名称、描述、优先级、指派人等。
ACT_RU_VARIABLE存放流程中的参数,包括流程实例参数、执行流参数和任务参数等
历史数据表(ACT_HI_*)ACT_HI_ACTINST记录历史活动信息,即流程流转过的所有节点。
ACT_HI_ATTACHMENT记录历史的流程附件。
ACT_HI_COMMENT记录历史的说明性信息。
ACT_HI_DETAIL记录流程中产生的变量详细信息。
ACT_HI_IDENTITYLINK记录任务参与者数据,主要存储历史节点参与者的信息。
ACT_HI_PROCINST记录历史流程实例信息。
ACT_HI_TASKINST记录历史任务实例信息。
ACT_HI_VARINST记录历史变量信息。

   除了上述主要分类的表外,Activiti7还可能包含其他表,如事件日志表(ACT_EVT_*)等,用于记录系统事件。

流程图绘制-部署和库表数据分析

流程图绘制

  • 指定流程定义的Key,即ID

  • 指定任务负责人,即Assignee分配负责人

    image.png

流程定义部署

@Test
    public void testDeploy2(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取资源service
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 部署流程定义
        Deployment deployed = repositoryService.createDeployment()
                .addClasspathResource("bpmn/test2.bpmn20.xml")
                .name("汽车报废补贴申请流程")
                .key("testDayKey")
                .deploy();
        System.out.println("id=" + deployed.getId());
        System.out.println("name=" + deployed.getName());
        System.out.println("key=" + deployed.getKey());
        System.out.println("deploymentTime=" + deployed.getDeploymentTime());
    }

数据库表变化

  • act_re_deployment:流程定义部署表,每部署一次增加一条记录
    image.png
  • act_re_procdef:流程定义表,部署每个新的流程定义都会在这张表中增加一条记录
    image.png
  • act_ge_bytearray:流程资源表,里面存储xml相关信息
    image.png

工作流核心操作

启动流程实例

   基于部署的流程定义,发起一个流程实例

image.png

数据库表变化

数据表功能描述
act_hi_procinst流程实例的历史信息
act_hi_taskinst流程任务的历史信息,记录所有任务节点的历史信息
act_ru_task流程实例的任务节点信息,启动流程实例,流程当前执行到第一个任务结点,此表会插入一条记录表示当前任务的执行情况。=,如果对应的节点任务完成,则记录会删除。
  • act_hi_procinst 流程实例的历史信息

示例代码

@Test
    public void testStart(){
        //创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //得到 RuntimeService 实例
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //根据流程定义的key启动流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testV1");
        //流程定义Key=testV1
        System.out.println("流程定义Key="+processInstance.getProcessDefinitionKey());
        //流程定义id=testV1:1:3
        System.out.println("流程定义id="+processInstance.getProcessDefinitionId());
        //流程实例id=2501
        System.out.println("流程实例id="+processInstance.getId());
    }

查询任务

   流程发起后,任务的负责人就可以查询自己需要处理的任务

示例代码

@Test
    public void testQuery(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 得到 TaskService 实例
        TaskService taskService = processEngine.getTaskService();
        //  查询代办任务 select * from ACT_RU_TASK where ASSIGNEE_ = '张三'
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("testV1")
                .taskAssignee("张三")
                //.singleResult() //返回单个结果
                .list();
        //打印任务
        for (Task task : list) {
            System.out.println("————————————————");
            //流程定义id=testV1:1:3
            System.out.println("流程定义id="+task.getProcessDefinitionId());
            //流程实例id=2501
            System.out.println("流程实例id="+task.getProcessInstanceId());
            //任务id=2505
            System.out.println("任务id="+task.getId());
            //任务负责人=张三
            System.out.println("任务负责人="+task.getAssignee());
            //任务名称=主管审批
            System.out.println("任务名称="+task.getName());
        }
    }

完成任务

   查询出来的任务就是当前负责人的代办任务, 完成对应的任务

示例代码

   张三审批

@Test
    public void testQuery(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 得到 TaskService 实例
        TaskService taskService = processEngine.getTaskService();
        //  查询代办任务 select * from ACT_RU_TASK where ASSIGNEE_ = '张三'
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("testV1")
                .taskAssignee("张三")
                //.singleResult() //返回单个结果
                .list();
        //打印任务
        for (Task task : list) {
            System.out.println("————————————————");
            //流程定义id=testV1:1:3
            System.out.println("流程定义id="+task.getProcessDefinitionId());
            //流程实例id=2501
            System.out.println("流程实例id="+task.getProcessInstanceId());
            //任务id=2505
            System.out.println("任务id="+task.getId());
            //任务负责人=张三
            System.out.println("任务负责人="+task.getAssignee());
            //任务名称=主管审批
            System.out.println("任务名称="+task.getName());
            
            //完成任务!!!
            taskService.complete(task.getId());
        }
    }

image.png

   李四审批

@Test
    public void testQuery(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 得到 TaskService 实例
        TaskService taskService = processEngine.getTaskService();
        //  查询代办任务 select * from ACT_RU_TASK where ASSIGNEE_ = '张三'
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("testV1")
                .taskAssignee("李四")
                //.singleResult() //返回单个结果
                .list();
        //打印任务
        for (Task task : list) {
            System.out.println("————————————————");
            //流程定义id=testV1:1:3
            System.out.println("流程定义id="+task.getProcessDefinitionId());
            //流程实例id=2501
            System.out.println("流程实例id="+task.getProcessInstanceId());
            //任务id=2505
            System.out.println("任务id="+task.getId());
            //任务负责人=张三
            System.out.println("任务负责人="+task.getAssignee());
            //任务名称=主管审批
            System.out.println("任务名称="+task.getName());

            //完成任务
            taskService.complete(task.getId());
        }
    }

image.png

添加审批意见处理

    增加审批意见

@Test
    public void testQuery(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 得到 TaskService 实例
        TaskService taskService = processEngine.getTaskService();
        //  查询代办任务 select * from ACT_RU_TASK where ASSIGNEE_ = '张三'
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("testV1")
                .taskAssignee("张三")
                //.singleResult() //返回单个结果
                .list();
        //打印任务
        for (Task task : list) {
            System.out.println("————————————————");
            //流程定义id=testV1:1:3
            System.out.println("流程定义id="+task.getProcessDefinitionId());
            //流程实例id=2501
            System.out.println("流程实例id="+task.getProcessInstanceId());
            //任务id=2505
            System.out.println("任务id="+task.getId());
            //任务负责人=张三
            System.out.println("任务负责人="+task.getAssignee());
            //任务名称=主管审批
            System.out.println("任务名称="+task.getName());

            //添加审批意见:第一个参数是任务id,第二个参数是流程实例id,第三个参数是批注内容
            taskService.addComment(task.getId(),task.getProcessInstanceId(),task.getName()+" 通过,但是不用去太多次");

            //完成任务
            taskService.complete(task.getId());
        }
    }

image.png

历史信息查询实战

   Activiti会在act_hi_*相关表中保留执行过的流程的历史数据,需要查询相关的历史信息

数据库表说明描述
ACT_HI_TASKINST(历史任务信息表)TASKINST 只记录 usertask 内容,专注于存储历史任务实例的信息。它主要记录用户任务(User Task)类型的任务节点信息,即那些需要用户参与并执行的任务。开始一个任务,不仅在act_ru_task表插入记录,也在历史任务表插入一条记录,任务完成此表记录不删除
ACT_HI_ACTINST(历史流程实例表)主要用于存储流程实例执行过程中的历史活动信息。它记录了流程流转过的所有节点,包括启动节点、结束节点、网关、调用子流程、服务类任务等……活动包括任务,此表中不仅记录了任务,还记录了流程执行过程的其它活动,比如开始事件、结束事件等

历史任务查询
   ACT_HI_TASKINST

image.png

/**
     * 历史任务查询  ACT_HI_TASKINST
     * 如何使用Activiti流程引擎的历史服务来查询历史任务实例
     * 主要步骤包括:获取默认流程引擎、创建历史任务实例查询、打印查询结果
     */
    @Test
    public void testQueryHistoryTask(){
        // 创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 从流程引擎中获取历史服务,用于操作历史数据
        HistoryService historyService = processEngine.getHistoryService();
        // 创建历史任务实例查询,查询库表是 ACT_HI_TASKINST
        List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
                //.processDefinitionKey("test1")
                //.taskAssignee("张三")
                .list();
        System.out.println("数量="+list.size());
        for (HistoricTaskInstance task : list) {
            System.out.println("------------");
            System.out.println("任务id="+task.getId());
            System.out.println("任务负责人="+task.getAssignee());
            System.out.println("任务名称="+task.getName());
            System.out.println("任务开始时间="+task.getStartTime());
            System.out.println("任务结束时间="+task.getEndTime());
            System.out.println("任务耗时="+task.getDurationInMillis());
        }
    }

image.png

控制台输出

数量=4
------------
任务id=10005
任务负责人=张三
任务名称=主管审批
任务开始时间=Sat Oct 19 08:22:47 CST 2024
任务结束时间=Sat Oct 19 08:30:31 CST 2024
任务耗时=464039
------------
任务id=12503
任务负责人=李四
任务名称=财务审批
任务开始时间=Sat Oct 19 08:30:31 CST 2024
任务结束时间=null
任务耗时=null
------------
任务id=2505
任务负责人=张三
任务名称=主管审批
任务开始时间=Sat Oct 19 05:42:15 CST 2024
任务结束时间=Sat Oct 19 07:05:19 CST 2024
任务耗时=4983614
------------
任务id=5002
任务负责人=李四
任务名称=财务审批
任务开始时间=Sat Oct 19 07:05:19 CST 2024
任务结束时间=Sat Oct 19 08:19:10 CST 2024
任务耗时=4431020

历史流程查询
   ACT_HI_ACTINST

image.png

/**
     * 历史流程实例的方法 ACT_HI_ACTINST
     * 本方法用于演示如何查询历史流程实例,并打印相关任务信息
     */
    @Test
    public void testQueryHistoryInstance(){
        // 创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取历史服务历史服务
        HistoryService historyService = processEngine.getHistoryService();
        // 创建历史活动实例查询对象,针对库表 ACT_HI_ACTINST
        HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
        // 此处注释掉的代码是通过指定流程实例ID查询,还可以增加更多条件
        // List<HistoricActivityInstance> list = instanceQuery.processInstanceId("10001").list();
        // 使用instanceQuery直接查询所有历史活动实例
        List<HistoricActivityInstance> list = instanceQuery.list();

        // 打印查询到的历史活动实例的数量
        System.out.println("数量="+list.size());
        // 遍历查询结果,打印每个任务的详细信息
        for (HistoricActivityInstance activityInstance : list) {
            System.out.println("------------");
            System.out.println("任务id="+activityInstance.getId());
            System.out.println("任务负责人="+activityInstance.getAssignee());
            System.out.println("任务名称="+activityInstance.getActivityName());
            System.out.println("任务开始时间="+activityInstance.getStartTime());
            System.out.println("任务结束时间="+activityInstance.getEndTime());
            System.out.println("任务持续时间="+activityInstance.getDurationInMillis());
            System.out.println("任务类型="+activityInstance.getActivityType());
        }
    }

控制台输出

数量=7
------------
任务id=10003
任务负责人=null
任务名称=null
任务开始时间=Sat Oct 19 08:22:47 CST 2024
任务结束时间=Sat Oct 19 08:22:47 CST 2024
任务持续时间=1
任务类型=startEvent
------------
任务id=10004
任务负责人=张三
任务名称=主管审批
任务开始时间=Sat Oct 19 08:22:47 CST 2024
任务结束时间=Sat Oct 19 08:30:31 CST 2024
任务持续时间=464061
任务类型=userTask
------------
任务id=12502
任务负责人=李四
任务名称=财务审批
任务开始时间=Sat Oct 19 08:30:31 CST 2024
任务结束时间=null
任务持续时间=null
任务类型=userTask
------------
任务id=2503
任务负责人=null
任务名称=null
任务开始时间=Sat Oct 19 05:42:15 CST 2024
任务结束时间=Sat Oct 19 05:42:15 CST 2024
任务持续时间=1
任务类型=startEvent
------------
任务id=2504
任务负责人=张三
任务名称=主管审批
任务开始时间=Sat Oct 19 05:42:15 CST 2024
任务结束时间=Sat Oct 19 07:05:19 CST 2024
任务持续时间=4983639
任务类型=userTask
------------
任务id=5001
任务负责人=李四
任务名称=财务审批
任务开始时间=Sat Oct 19 07:05:19 CST 2024
任务结束时间=Sat Oct 19 08:19:10 CST 2024
任务持续时间=4431047
任务类型=userTask
------------
任务id=7501
任务负责人=null
任务名称=null
任务开始时间=Sat Oct 19 08:19:10 CST 2024
任务结束时间=Sat Oct 19 08:19:10 CST 2024
任务持续时间=0
任务类型=endEvent

流程定义查询

   查看对应的表 ACT_RE_PROCDEF

   如果同个流程定义KEY重复部署,每部署一次则Version版本号会变化+1

示例代码

@Test
    public void testQueryDefinition(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取资源service
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //创建流程定义查询
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey("testV1")
                //orderByProcessDefinitionVersion 按照版本排序
                .orderByProcessDefinitionVersion()
                //desc倒叙
                .desc()
                // list 返回集合
                .list();
        for (ProcessDefinition processDefinition : list) {
            System.out.println("-----------");
            //流程定义id=testV1:1:3
            System.out.println("流程定义id="+processDefinition.getId());
            //流程定义key=testV1
            System.out.println("流程定义key="+processDefinition.getKey());
            //流程定义name=报销流程定义
            System.out.println("流程定义name="+processDefinition.getName());
            //流程定义版本=1
            System.out.println("流程定义版本="+processDefinition.getVersion());
        }
    }

控制台输出

流程定义id=testV1:1:3
流程定义key=testV1
流程定义name=报销流程定义
流程定义版本=1

image.png

image.png

流程定义删除

   删除流程定义通过repositoryService不会移除历史数据,如果无活动实例的流程定义可直接删除。

   如果存在活动实例时,普通删除将失败,则需要级联删除来清除流程及其所有相关数据。

   应优先清除未完成的流程节点后才能彻底移除流程定义

示例代码

@Test
    public void testDelDefinition() {
        // 创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取资源service
        RepositoryService repositoryService = processEngine.getRepositoryService();
        
        //删除流程定义 deploymentId,如果该流程定义已有流程实例启动并且在运行(ACT_RU_TASK表中有该流程定义模板的实例)
        //则会抛出异常 【物理外键】
        repositoryService.deleteDeployment("1");
        
        //级联删除:设置true,,如果流程实例启动并且在运行,也会删除
        //repositoryService.deleteDeployment("1",true);
    }

image.png

多版本流程定义进行

需求

  • 同个流程定义,不同版本之前发起流程实例的时候是采用哪个?
  • 比如:如果旧流程还在进行中,又有新的流程定义,则如何进行

image.png

image.png

答案

  • 老王的发起的时候是旧的流程,走到了主管大钊的节点,则按照旧的流程定义继续进行
  • 冰冰是新发起的,则采用最新的流程定义进行

示例代码

  • 使用旧流程定义发起一个流程实例
  • 修改旧的流程定义,修改里面的节点,需要使用同个Key
  • 使用修改后的流程定义,发起一个流程实例,验证流转

image.png

部署新的流程定义模板

@Test
    public void testDeployNew(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取资源service
        RepositoryService repositoryService = processEngine.getRepositoryService();
        // 部署流程定义
        Deployment deploy = repositoryService.createDeployment().addClasspathResource("bpmn/testV1.bpmn20.xml")
                .name("报销申请-新版").deploy();
        System.out.println(deploy.getId());
    }

ACT_RE_DEPLOYMENT

image.png

ACT_RE_PROCDEF

image.png

再次发起新的流程实例

@Test
    public void test2Start(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 得到 RuntimeService 实例
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 根据流程定义的key启动流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testV1");
        System.out.println("流程定义Key="+processInstance.getProcessDefinitionKey());
        System.out.println("流程定义id="+processInstance.getProcessDefinitionId());
        System.out.println("流程实例id="+processInstance.getId());
    }

image.png

流程审批
   主管张三审批后是老板(超哥)

/**
     * 审批
     */
    @Test
    public void testQuery(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 得到 TaskService 实例
        TaskService taskService = processEngine.getTaskService();
        //  查询代办任务 select * from ACT_RU_TASK where ASSIGNEE_ = '张三'
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("testV1")
                .taskAssignee("张三")
                //.deploymentId("20001")
                //.singleResult() //返回单个结果
                .list();
        //打印任务
        for (Task task : list) {
            System.out.println("————————————————");
            //流程定义id=testV1:1:3
            System.out.println("流程定义id="+task.getProcessDefinitionId());
            //流程实例id=2501
            System.out.println("流程实例id="+task.getProcessInstanceId());
            //任务id=2505
            System.out.println("任务id="+task.getId());
            //任务负责人=张三
            System.out.println("任务负责人="+task.getAssignee());
            //任务名称=主管审批
            System.out.println("任务名称="+task.getName());

            //添加审批意见:第一个参数是任务id,第二个参数是流程实例id,第三个参数是批注内容
            taskService.addComment(task.getId(),task.getProcessInstanceId(),task.getName()+" 通过,但是不用去太多次");

            //完成任务
            taskService.complete(task.getId());
        }
    }

ACT_RU_TASK

image.png

Businesskey业务标识

   业务标识,是工作流(如Activiti)中用于关联流程实例业务系统数据关键字

   它通常是业务表的主键,用于唯一标识一个业务实体(如请假单、出差单等)。

   BusinessKey与流程实例绑定使得通过【流程实例】可以查询到相关的业务数据

作用

作用描述
数据关联启动流程实例时,将BusinessKey存储在act_ru_execution表中,通过流程实例查询到对应的业务数据。
流程管理在流程执行过程中,通过BusinessKey查询流程实例的状态、进度等信息,进行流程管理。
数据追溯通过BusinessKey,可以追溯流程实例的执行过程,查看历史记录等信息

image.png

如何使用BusinessKey
   启动流程实例时添加BusinessKey,在启动流程实例时,指定BusinessKey

   这个BusinessKey通常是业务系统的某个主键值,用于标识具体的业务实体

   查询流程实例时,获取任务节点,即可获取BusinessKey

示例代码

/**
     * 启动流程实例 BusinessKey
     */
    @Test
    public void test2Start(){
        //  创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 得到 RuntimeService 实例
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 根据流程定义的key启动流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testV1","88888888");
        System.out.println("流程定义Key="+processInstance.getProcessDefinitionKey());
        System.out.println("流程定义id="+processInstance.getProcessDefinitionId());
        System.out.println("流程实例id="+processInstance.getId());
    }

   启动流程实例时添加BusinessKey,在启动流程实例时,指定BusinessKey

   查看 act_ru_execution 表,发现BusinessKey

image.png

通过流程实例(BusinessKey)查询业务数据库中的其他相关信息

/**
     * 查询BusinessKey案例
     * 本测试方法演示如何通过流程实例的BusinessKey来查询相关的业务数据
     */
    @Test
    public void testQueryBusinessKey(){
        // 初始化流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取任务服务和运行时服务
        TaskService taskService = processEngine.getTaskService();
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 查询当前所有的任务
        List<Task> list = taskService.createTaskQuery().list();
        // 遍历任务列表,查询每个任务所属流程实例的BusinessKey
        for (Task task : list) {
            // 获取流程实例ID
            String processInstanceId = task.getProcessInstanceId();
            // 根据流程实例ID查询流程实例
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            // 通过流程实例打印BusinessKey
            // 这里可以扩展为通过BusinessKey 查询业务数据库的操作
            System.out.println("业务id=" + processInstance.getBusinessKey());

            // 通过BusinessKey可以进一步查询业务数据库中的其他相关信息 TODO ...
            // BusinessKey可以查询到申请人的部门、薪资、电话、姓名、工龄等…… 
            // 进而让审批者判断是否审批通过
        }
    }

流程定义挂起和激活

   流程定义的挂起(Suspend)与激活(Activate)是管理工作流执行状态的重要功能。通常用于在需要时暂停或恢复流程实例的执行,满足业务变更、系统维护或特定业务规则的需求。

   在某些业务场景下,可能需要临时中断流程的执行,比如:公司里面的财务主管贪污被撤岗了,需要暂停所有相关的业务流程

   有100个人的流程发起过了,80个是已经完成了,20个是还在进行中

   由于流程变更需要将当前运行的流程暂停不是直接删除流程暂停后将不会继续执行

   业务流程正常后,就需要激活流程实例意味着恢复其执行,允许其继续按照定义中的流程逻辑执行

   激活可以是针对单个已挂起的流程实例,也可以是针对整个流程定义下所有已挂起的实例。

流程定义的状态

流程定义的状态描述
挂起流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停,挂起操作不会影响已完成的任务或历史数据。
挂起可以是针对单个流程实例,也可以是针对整个流程定义下的所有实例。
流程定义挂起后,如果发起流程实例则会报错,直到被重新激活。
激活激活流程实例恢复其执行,允许其继续按照定义中的流程逻辑执行。
激活后,流程实例将从其挂起前的状态继续执行。如果有待办任务,这些任务将重新变为可处理状态。

SUSPENSION_STATE 字段含义

数字含义
1已激活
2已挂起

环境准备

ACT_RE_PROCDEF(流程定义)

image.png

ACT_RU_TASK(流程定义下的多个实例)

image.png

流程定义的挂起和激活
    挂起某流程定义,ACT_RE_PROCDEF 表下(ID_ = testV1:1:45003 的流程定义)

/**
     * 流程定义挂起和激活
     */
    @Test
    public void testSuspendAndActivateInstance2Def(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //挂起某个流程定义
        //repositoryService.suspendProcessDefinitionById("testV1:1:45003");

        //激活某个流程定义
        repositoryService.activateProcessDefinitionById("testV1:1:45003");
    }

ACT_RE_PROCDEF(流程定义)
image.png

流程定义(实例) 挂起和激活
   挂起某流程实例, ACT_RU_TASK表下(PROC_INST_ID = 47501 的流程定义实例)

/**
     * 流程定义(实例) 挂起和激活
     */
    @Test
    public void testSuspendAndActivateInstance2DefInstance(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //单个流程定义下的某流程实例挂起
        RuntimeService runtimeService = processEngine.getRuntimeService();

        //挂起
        runtimeService.suspendProcessInstanceById("47501");

        //激活
        //runtimeService.activateProcessInstanceById("47505");
        
        //查询具体的流程实例对象
        //ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId("47505").singleResult();

        //检查流程实例是否已暂停
        //boolean isSuspended = processInstance.isSuspended();

        //获取流程实例ID
        //String instanceId = processInstance.getProcessInstanceId();

        // 是否已暂停执行激活或挂起操作
//        if (isSuspended) {
//            // 如果已暂停,执行激活操作
//            runtimeService.activateProcessInstanceById(instanceId);
//            System.out.println("流程实例:" + instanceId + " 已激活");
//        } else {
//            // 如果处于激活状态,执行挂起操作
//            runtimeService.suspendProcessInstanceById(instanceId);
//            System.out.println("流程实例:" + instanceId + " 已挂起");
//        }
    }

image.png

UEL表达式应用(动态任务负责人)

   流程定义里面,任务节点负责人是固定的,但是很多场景下需要有切换不同的负责人。比如:流程任务中,集团里面,不同的分公司里面报销流程审批人不一样,广州分公司,深圳分公司等

image.png

UEL表达式

   ​Unified Expression Language(UEL)​一种用于在Java EE应用中对表达式进行求值的语言。提供了简洁、灵活、强大的语法来表示和处理表达式。

   在Activiti工作流引擎中,UEL表达式被广泛用于流程定义中,以实现动态的任务分配、条件判断、变量设置等功能

   在流程运行时,UEL表达式可以根据流程变量的变化,动态地计算表达式的值

示例

  • 流程定义画图,任务节点不配置负责人,采用表达式${XXX} 进行占位

image.png

  • 部署流程定义(deployment)
/**
     * 流程定义部署到数据库
     */
    @Test
    public void testDeploy(){
        // 创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取资源服务
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //部署流程定义到数据库
        Deployment deployed = repositoryService.createDeployment().addClasspathResource("bpmn/uel_demo_v1.bpmn20.xml").deploy();
        System.out.println("id=" + deployed.getId());
        System.out.println("name=" + deployed.getName());
        System.out.println("key=" + deployed.getKey());
        System.out.println("deploymentTime=" + deployed.getDeploymentTime());
    }
User TaskNameAssigneeMap
主管审批主管审批${p1}map.put("p1","张三UEL");
财务审批财务审批${p2}map.put("p2","李四UEL");

image.png

image.png

image.png

image.png

  • 发起流程实例,这个时候可以动态配置相关参数执行成功后,可以在act_ru_variable表中看到刚才map中的数据
/**
     * 流程定义实例启动
     * 本方法主要用于启动一个流程实例,并设置相关变量
     */
    @Test
    public void testStart1(){
        //创建流程引擎 processEngine, 会检查库表,如果没有就自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取运行时服务,通过该服务可以启动流程实例
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //创建一个变量映射,用于在启动流程实例时传递变量
        Map<String,Object> map = new HashMap<>();
        //设置变量p1的值为"张三p"
        map.put("p1","张三UEL");
        //设置变量p2的值为"李四p"startProcessInstanceByKey("uel_demo_v1", "888", map);
        map.put("p2","李四UEL");

        //通过流程定义的key启动流程实例,并传递变量映射
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("uel_demo_v1", "888", map);

        System.out.println("流程定义Key="+processInstance.getProcessDefinitionKey());
        System.out.println("流程定义id="+processInstance.getProcessDefinitionId());
        System.out.println("流程实例id="+processInstance.getId());
        System.out.println("实例名称name="+processInstance.getId());
    }

image.png

image.png

流程变量

   在流程处理中,有不同判断条件,需要切换不同的人处理,怎么操作。比如请假超过3天则需要总监同意,但是3天内的话就组长同意即可

什么是流程变量

   用于在流程运转过程中传递业务参数

   流程变量是Activiti在管理工作流时根据管理需要而设置的变量,用于支持流程的逻辑判断和分支选择。 例如:在请假申请流程中,请假天数、请假原因等都可以设置为流程变量,在流程流转时根据这些变量的值进行不同的处理

   数据类型:包括基本数据类型(如String、Integer、Boolean等)和复杂数据类型(如实现了Serializable接口的自定义类)。

   作用域:可以是一个流程实例(processInstance)、一个任务(task)或一个执行实例(execution)。流程变量的默认作用域是流程实例当流程变量的作用域为流程实例时,可以称为global变量。global变量中的变量名不允许重复,设置相同名称的变量时,后设置的值会覆盖前设置的变量值。 如果任务和执行实例的作用域相对较小,仅针对一个任务或一个执行实例范围,称为local变量。local变量名可以与global变量名相同,且在不同的任务或执行实例中互不影响。

使用方法

   在流程连线上设置UEL表达式${day > 3},其中day是一个流程变量名称,该表达式的执行结果是布尔类型,用于决定流程是否跳转到特定的分支

作用域描述使用方法
global变量流程变量的作用域为流程实例在启动流程实例时设置流程变量
local变量如果任务和执行实例的作用域相对较小,仅针对一个任务或一个执行实例范围,称为local变量通过UEL表达式使用流程变量

实例代码
   假设有一个请假申请流程,员工提交请假申请后,根据请假天数的不同,流程会流转到不同的审批节点。例如:如果请假天数大于3天,则需要总经理审批;否则,由部门经理直接审批

  • 定义流程变量​:在流程设计时,定义一个名为day的流程变量,用于存储请假天数。
  • 设置流程节点​:在流程中设置两个审批节点,分别对应部门经理审批和总经理审批。
  • 编写UEL表达式​:在部门经理审批节点和总经理审批节点间的连线上编写UEL表达式${day > 2},决定流程是否跳转到总经理审批节点。
  • 启动流程实例​:在启动流程实例时,通过Map<String, Object>设置流程变量day的值。
连线名称Condition expression
请假大于3天"${day > 3}"
请假小于等于3天"${day <= 3}"
User TaskAssignee
组长张三
部门经理李四
总经理王五

image.png

部署流程定义到数据库

@Test
    public void testDeploy(){
        // 创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取资源服务
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //部署流程定义到数据库
        Deployment deployed = repositoryService.createDeployment().addClasspathResource("bpmn/var_day.bpmn20.xml").deploy();
        System.out.println("id=" + deployed.getId());
        System.out.println("name=" + deployed.getName());
        System.out.println("key=" + deployed.getKey());
        System.out.println("deploymentTime=" + deployed.getDeploymentTime());
    }

发起流程定义的实例

@Test
    public void testStartVar(){
        //创建流程引擎 processEngine, 会检查库表,如果没有就自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取运行时服务,通过该服务可以启动流程实例
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //创建一个变量映射,用于在启动流程实例时传递变量
        Map<String,Object> map = new HashMap<>();
        //传入变量
        map.put("day","4");
        //通过流程定义的key启动流程实例,并传递变量映射
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("var_day", "111", map);
        System.out.println("流程定义Key="+processInstance.getProcessDefinitionKey());
        System.out.println("流程定义id="+processInstance.getProcessDefinitionId());
        System.out.println("流程实例id="+processInstance.getId());
        System.out.println("实例名称name="+processInstance.getId());
    }

image.png

image.png

审批任务

/**
     * 审批任务 ID_    60006
     */
    public void testCompleteTaskV3(){
        //获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        taskService.complete("");
    }

image.png

image.png

任务候选人Candidate

   在Activiti工作流中,任务候选人是指潜在的任务处理者列表,可以有多个

   当任务到达某一阶段时,系统会从候选人列表中选择一人或多人来实际处理该任务

   多个人都可以看到任务,谁先看到就可以先处理任务

   在BPMN流程图中,可以在任务节点的配置中设置candidate-users(候选人),多个候选人之间用逗号分开

   在设置任务候选人时,应确保候选人的用户ID在系统中是唯一的,以避免混淆和错误

示例

  • 画图流程定义

image.png
image.png
image.png

  • 部署流程定义
@Test
    public void testDeploy(){
        // 创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取资源服务
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //部署流程定义到数据库
        Deployment deployed = repositoryService.createDeployment().addClasspathResource("bpmn/candidate_demo.bpmn20.xml").deploy();
        System.out.println("id=" + deployed.getId());
        System.out.println("name=" + deployed.getName());
        System.out.println("key=" + deployed.getKey());
        System.out.println("deploymentTime=" + deployed.getDeploymentTime());
    }

image.png

  • 发起流程实例:发起流程实例后,RU_TASK表中,对应ASIGNEE负责人是空的
@Test
    public void testStart(){
        //创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //得到 RuntimeService 实例
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //根据流程定义的key启动流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("candidate_demo","168");
        System.out.println("流程定义Key="+processInstance.getProcessDefinitionKey());
        System.out.println("流程定义id="+processInstance.getProcessDefinitionId());
        System.out.println("流程实例id="+processInstance.getId());
    }

image.png

  • 查询代办任务:候选人都可以查询
/**
     * 查询候选用户
     */
    @Test
    public void testCandidateUser(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        List<Task> list = taskService.createTaskQuery().taskCandidateUser("张3").list();
        for (Task task : list){
            System.out.println(task.getId()); //67505
            System.out.println(task.getName());
            System.out.println("=======");
        }
    }

image.png

  • 领取任务,领取后,RU_TASK表中,对应ASIGNEE负责人就有了
/**
     * 领取任务  ACT_RU_TASK   ID_ 67505   李4
     */
    @Test
    public void testClaim(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        taskService.claim("67505","李4");
    }

image.png

  • 完成任务
/**
     * 审批任务 ACT_RU_TASK     ID_    67505
     */
    @Test
    public void testCompleteForCandidate(){
        //获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        taskService.complete("67505");
    }

image.png

网关

   工作流中的网关是流程设计中非常重要的组件,它们用于控制流程的流向,实现流程的拆分、合并以及条件判断,有多个。满足一个怎么走?都满足怎么走?都不满足怎么走

排他网关(Exclusive Gateway)

  • 用于在流程中进行条件判断,根据条件选择不同的分支路径。
  • 当流程执行到排他网关时,会评估所有分支的条件,选择第一个条件为true的分支执行
  • 如果多个条件都为true,则按照XML中定义的顺序(通常是第一个,即ID小的)执行
  • 如果没有任何条件满足,流程将异常结束
  • 应用场景​:根据特定条件选择不同的审批流程(如请假天数大于等于3天需要总经理审批,小于3天只需部门经理审批)

image.png

示例

User TaskAssignee
HR张三
部门经理李四
总经理超哥
连线Condition expression
天数大于3"${day > 3}"
天数小于等于3"${day <= 3}"
  • 流程定义画图
    image.png
  • 流程定义部署
@Test
    public void testDeploy(){
        // 创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取资源服务
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //部署流程定义到数据库
        Deployment deployed = repositoryService.createDeployment().addClasspathResource("bpmn/exclusive.bpmn20.xml").deploy();
        System.out.println("id=" + deployed.getId());
        System.out.println("name=" + deployed.getName());
        System.out.println("key=" + deployed.getKey());
        System.out.println("deploymentTime=" + deployed.getDeploymentTime());
    }

image.png

  • 流程实例启动
@Test
    public void testStartVar(){
        //创建流程引擎 processEngine, 会检查库表,如果没有就自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //获取运行时服务,通过该服务可以启动流程实例
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //创建一个变量映射,用于在启动流程实例时传递变量
        Map<String,Object> map = new HashMap<>();
        //传入变量
        map.put("day",2);
        //通过流程定义的key启动流程实例,并传递变量映射
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("exclusive", "222", map);
        System.out.println("流程定义Key="+processInstance.getProcessDefinitionKey());
        System.out.println("流程定义id="+processInstance.getProcessDefinitionId());
        System.out.println("流程实例id="+processInstance.getId());
        System.out.println("实例名称name="+processInstance.getId());
    }

image.png

  • 任务审批
/**
     * 审批任务  ACT_RU_TASK  ID_    80006
     */
    @Test
    public void testCompleteForVar(){
        //获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        taskService.complete("80006");
    }

image.png

  • 验证任务走向
    image.png

并行网关

   用于将流程拆分成多个并行分支,这些分支可以同时执行, ​当所有分支都执行完毕后​,流程才会继续向下执行。

   并行网关分为分支(fork)和汇聚(join)两种类型,分别用于拆分和合并流程

   应用场景​:需要多个部门或人员同时处理的任务(如请假流程需要项目经理和技术经理同时审批)。

image.png

示例

User TaskAssignee
HR张三
产品组长李四
技术组长王五
部门经理赵六
  • 流程定义画图
    image.png
  • 流程定义部署
@Test
    public void testDeploy(){
        // 创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取资源服务
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //部署流程定义到数据库
        Deployment deployed = repositoryService.createDeployment().addClasspathResource("bpmn/par.bpmn20.xml").deploy();
        System.out.println("id=" + deployed.getId());
        System.out.println("name=" + deployed.getName());
        System.out.println("key=" + deployed.getKey());
        System.out.println("deploymentTime=" + deployed.getDeploymentTime());
    }

image.png

  • 流程实例启动
@Test
    public void testStart(){
        //创建流程引擎 processEngine,会检查相关库表情况,如果没有,会自动创建
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //得到 RuntimeService 实例
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //根据流程定义的key启动流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("par","555");
        System.out.println("流程定义Key="+processInstance.getProcessDefinitionKey());
        System.out.println("流程定义id="+processInstance.getProcessDefinitionId());
        System.out.println("流程实例id="+processInstance.getId());
    }

image.png

  • HR审批完成
@Test
    public void testCompleteForVar(){
        //获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        taskService.complete("87505");
    }

image.png

  • 产品组长审批通过
@Test
    public void testCompleteForVar(){
        //获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        taskService.complete("90004");
    }

image.png

  • 技术组长审批通过
@Test
    public void testCompleteForVar(){
        //获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        taskService.complete("90007");
    }

image.png

   

  • 等待部门经理审批
    image.png

   

   
   
   


作者:Soulboy