Activity引擎(初次学习与总结梳理全记录,包括易混淆知识点分析,常用报错解决方案等)

最近工作需要使用Acticity框架处理审批业务,简单了解后能虽能很快的上手,但是对于Activity的整体认识并不够,特此花费很多精力全面的学习并记录。包含对很多的概念的第一次理解过程;对知识点的混淆地方的梳理;对实践过程中出现的bug;以及个人的一些思考。因此本文可以当作一个简易版Activirty学习工具书,可以收藏本文,按需查阅。
因为是初次接触,部分理解可能会存在歧义或不恰当的地方,诚邀批评指正!

目录

  • 1 什么是Activity?
  • 2 初体验Activity
    • 2.1 创建maven项目
    • 2.2 导入依赖
    • 2.3 连接数据库
    • 2.4 配置log4j
    • 2.5 自动生成avtiviti表
    • 2.6 检查生成的表
  • 3 分析Avtivity创建的表
    • 3.1 Activiti 数据表介绍
  • 4 Activiti常用Service服务接口
    • 4.1 理解并简单使用
    • 4.2 服务接口详情
  • 5 Activity流程操作
    • 5.1 流程定义
      • 5.1.1 创建demo.bpmn文件
      • 5.1.2 定义参数
      • 5.1.3 启动部署
      • 5.1.4 运行结果
      • 5.1.5 分析相关表
    • 5.2 启动流程实例
    • 5.3 任务查询
    • 5.4 处理当前任务
    • 5.5 删除流程
      • 5.5.1 删除52501(会失败)
      • 5.5.2 删除60001(会成功)
      • 5.5.3 删除60001(会成功)
    • 5.6 流程资源的下载
    • 5.7 查询历史任务
  • 6 Activity在实际开发中的使用
    • 6.1 让实际业务与activiti表关联(BusinessKey)
      • 6.1.1 理解BusinessKey
      • 6.1.2 代码实现
    • 6.2 流程实例的挂起和激活
      • 6.2.1 全部流程实例挂起
      • 6.2.2 单个流程实例挂起
    • 6.3 分配任务
      • 6.3.1 固定分配
      • 6.3.2 表达式分配
      • 6.3.3 监听器分配
    • 6.4 查询任务
      • 6.4.1 查询任务负责人的代办任务
      • 6.4.2 获取BusinessKey
    • 6.5 办理任务
  • 7 流程变量
    • 7.1 流程变量的使用
    • 7.2 流程变量的作用域
    • 7.3 流程变量的使用
      • 7.3.1 globa变量
        • 7.3.1.1 启动流程时设置变量
        • 7.3.1.2 在任务办理时设置流程变量
        • 7.3.1.3 通过当前流程实例id设置
      • 7.3.2 local变量
        • 7.3.2.1 local变量任务办理时设置
        • 7.3.2.2 当前任务设置
  • 8 任务组
    • 8.1 创建任务组候选人
    • 8.2 组任务办理流程
      • 8.2.1 领取组任务
      • 8.2.2 查询个人代办任务
      • 8.2.3 办理个人任务
      • 8.2.4 归还组任务
      • 8.2.5 任务交接
  • 9 网关
    • 9.1 排他网关
    • 9.2 并行网关
    • 9.3 包含网关

1 什么是Activity?

Acativity是一个专门负责处理工作流程的框架。比如,上班时间需要申请一个病假,这个工作流程就是:创建病假申请单----->部门领导审批--------->经理审批------------>人事领导审批。那现在让我们实现这个业务,我们该怎么做呢?

我们会定义一个状态值,0代表在创建病假申请单处,1代表在部门领导审批处,2代表在经理审批处,3代表在人事领导审批处。然后实现各自的业务逻辑等等等。

实际上会发现,如果有的领导请假了;如果有很多个工作流程,有的处理完了,有的处理一半,是不是所有的资料此时就特别乱,根本无法梳理清楚,很难维护。因此,Activity框架诞生了,专门帮助我们处理这种情况。借助这个框架,就能很清楚的设计多种工作流,查看各自的执行过程,谁负责什么都会十分清晰。

好比让你直接去盖一栋楼,你很迷茫。现在给你一个框架,你可以管理建筑材料,建筑工人,建筑部门,查看各工种的干活状态。

2 初体验Activity

参考资料,B站up:波哥是个憨憨

2.1 创建maven项目

2.2 导入依赖

pom.xml文件

<dependencies>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-engine</artifactId>
            <version>5.22.0</version>
        </dependency>

        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring</artifactId>
            <version>5.22.0</version>
        </dependency>

        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-model</artifactId>
            <version>5.22.0</version>
        </dependency>

        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-converter</artifactId>
            <version>5.22.0</version>
        </dependency>

        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>5.22.0</version>
        </dependency>

        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-layout</artifactId>
            <version>5.22.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.tinyjee.jgraphx</groupId>
                    <artifactId>jgraphx</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.activiti.cloud</groupId>
            <artifactId>activiti-cloud-services-api</artifactId>
            <version>7-201710-EA</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>

        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

2.3 连接数据库

文件路径:/resources/activitis.cfg.xml
(activitis.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 class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration" id="processEngineConfiguration">
           <property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver"/>
           <property name="jdbcUrl" value="jdbc:mysql:///activiti?characterEncoding=utf-8&amp;serverTimezone=UTC"/>
           <property name="jdbcUsername" value="root"/>
           <property name="jdbcPassword" value="1234"/>
           <!--用于自动生成activiti的25张表-->
           <property name="databaseSchemaUpdate" value="true"/>

       </bean>
</beans>

2.4 配置log4j

文件路径:/resources/log4j.properties

log4j.rootLogger=TRACE, stdout, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
#log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
# Print the date in ISO 8601 format
#log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
#下面的是制定输出到打印台格式的内容
log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p  %d{yyyy-MM-dd HH:mm:ssS} %c -%m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log
log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
#log4j.appender.R.layout.ConversionPattern=[%p] %t %c - %m%n -%d{yyyy-MM-dd HH:mm}
log4j.appender.R.layout.ConversionPattern=[%p] %t %d{yyyy-MM-dd HH:mm:ss,SSS} %c - {%m} %n 
# Print only messages of level WARN or above in the package com.foo.
log4j.logger.com.foo=WARN

2.5 自动生成avtiviti表

public class Test01 {

    /**
     * 生成Activiti的相关表结构
     */
    @Test
    public void test01(){
    	//目标:生成ProcessEngine的对象
    	//原理: 使用classpath下的activitis.cfg.xml中的配置创建ProcessEngine的对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        System.out.println(engine);
    }


}

2.6 检查生成的表

在这里插入图片描述
后面常用的就这几个
在这里插入图片描述

3 分析Avtivity创建的表

根据表名前缀分类
act_re:re表示 repository,这个前缀的表包含了流程定义和流程静态资源 (图片、规则、等等)
act_ru:ru 表示 runtime,这些表运行时,会包含流程实例、任务、变量、异步任务等流程业务进行中的数据。Activiti 只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。这样表就可以一直保持很小的体积,并且速度很快
act_hi:hi表示 history,这些表包含一些历史数据,比如历史流程实例、变量、任务等等
act_ge:ge 表示 general,通用数据表示存储一些通用数据。

3.1 Activiti 数据表介绍

(后面涉及到某个表了,跳转过来查看即可)
在这里插入图片描述

4 Activiti常用Service服务接口

4.1 理解并简单使用

我们已经清楚了activity引擎的作用:可以帮助我们处理工作流程。进一步通过分析创建的表,可以确定一个概念:若activity引擎执行一个工作流,内部一定是多个模块协助进行的。例如需要

  • 管理Activity资源
  • 管理Activity流程运行
  • 管理Activity任务
  • 管理Activity历史记录
  • 管理Activity引擎

的协助配合,才能完成一个工作流。

暂时不需要特别清楚这些内部这些模块有什么作用,先确认内部是有这些模块在默默协作的。那么身为程序员,我们想要人工去操作管理Activity资源,管理Activity流程运行,管理Activity任务等,应该如何操作?

其实很方便,框架将这些模块定义成接口,调用接口,就可以操作这些模块了。例如

  • 管理Activity资源: RepositoryService 接口
  • 管理Activity流程运行: RuntimeService 接口
  • 管理Activity任务: TaskService 接口
  • 管理Activity历史记录: HistoryService 接口
  • 管理Activity引擎: ManagementService 接口

每个接口中定义了很多的方法,需要哪个就调用哪个。例如我需要删除部署的Activity引擎,发现RepositoryService 接口中有个deleteDeployment(String var1)方法就可以实现
在这里插入图片描述
这五个常用的接口,基本涵盖了activity提供的主要方法。意思就是你需要使用activity干什么,都可以在这五个接口中找到定义好的方法,不需要你自己创建一个接口,然后实现业务,因为没必要。

这五个接口的命名有个共同点,都是XXXXXXService,因此我们统称为Service服务接口。

OK,以上就是希望对Service服务接口有个理解足矣。接下来简单说明一下,应该如何正确使用Service服务接口

在目录2.5中,我们创建了ProcessEngine的对象---->engine

public class Test01 {

    /**
     * 生成Activiti的相关表结构
     */
    @Test
    public void test01(){
    	//目标:生成ProcessEngine的对象
    	//原理: 使用classpath下的activitis.cfg.xml中的配置创建ProcessEngine的对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        System.out.println(engine);
    }


}

(目标:应该如何正确使用Service服务接口)
观察一下engine对象,发现内部提供了很多get方法。这些get方法是什么呢?

在这里插入图片描述
可以看到,这些get方法返回值都是接口类型
如getRepsoitoryService()方法的返回值是 RepsoitoryService接口类型
在这里插入图片描述
而前面我们知道RepsoitoryService接口中内置了很多方法,如deleteDeployment() -------删除Acativity引擎部署(如下图)

在这里插入图片描述

梳理一下:
getRepsoitoryService() 方法的返回值是 RepsoitoryService 接口
RepsoitoryService 接口中有deleteDeployment()

岂不是意味着 ,只要得到getRepsoitoryService() 返回值,就可以得到 deleteDeployment()

//使用RepsoitoryService对象A 接收 getRepsoitoryService()返回值
RepsoitoryService A = getRepsoitoryService();
//然后就可以调用使用RepsoitoryService的方法了
A.deleteDeployment();

-------------------
//那可以合成一句代码
getRepsoitoryService().deleteDeployment();

然后我们继续看看下图
在这里插入图片描述
原来是这样

//其实惯性思维希望 
engine.RepsoitoryService.deleteDeployment();
//但是RepsoitoryService是接口,无法直接调用,所以声明成一个方法
RepsoitoryService getRepsoitoryService()
//这样再看这行代码,就有思路了
engine.getRepsoitoryService().deleteDeployment();

上文,我们解释了getRepsoitoryService()方法从何而来?deleteDeployment()方法从何而来?如何这些方法如何使用?

4.2 服务接口详情

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

  • RepositoryService

Activiti 的资源管理类,该服务负责部署流程定义,管理流程资源。在使用 Activiti 时,一开始需要先完成流程部署,即将使用建模工具设计的业务流程图通过 RepositoryService 进行部署

  • RuntimeService

Activiti 的流程运行管理类,用于开始一个新的流程实例,获取关于流程执行的相关信息。流程定义用于确定一个流程中的结构和各个节点间行为,而流程实例则是对应的流程定义的一个执行,可以理解为 Java 中类和对象的关系

  • TaskService

Activiti 的任务管理类,用于处理业务运行中的各种任务,例如查询分给用户或组的任务、创建新的任务、分配任务、确定和完成一个任务

  • HistoryService

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

  • ManagementService

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

5 Activity流程操作

5.1 流程定义

环境:idea 2018 插件actiBPM (此插件只能在2019版本之前搜索到)

审批者负责内容
zhangsan创建申请单
lisi经理审批
wangwu总经理审批
zjh财务审批

这里的流程定义,可以理解为创建一个出差申请模版,任何人只要需要出差,就必须申请一份出差申请单,这个出差申请单的证明周期包括:创建申请单-经理审批-总经理审批-财务审批。

5.1.1 创建demo.bpmn文件

在这里插入图片描述

5.1.2 定义参数

在这里插入图片描述
在这里插入图片描述
四个方框都是类似操作

5.1.3 启动部署

文件名:Test01

    /**
     * 启动Activity部署
     */
    @Test
    public void test02(){
    	//前面多次解释一定要获取ProcessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
		
		//获取RepositoryService服务(包含创建部署,启动部署,删除部署等)
        RepositoryService service = engine.getRepositoryService();

        Deployment deploy = service.createDeployment() //创建部署
                                   .addClasspathResource("bpmn/demo.bpmn") //添加bnmn资源
                                   .name("出差申请流程") //
                                   .deploy(); //部署流程
		//流程部署的ID
        System.out.println(deploy.getId());
        //流程部署的名称
        System.out.println(deploy.getName());


    }

首次启动会出现一个bug或者乱码错误,解决方案链接
Activity引擎启动报错: 3 字节的 UTF-8 序列的字节 3 无效 及乱码 完美解决方案

5.1.4 运行结果

在这里插入图片描述

5.1.5 分析相关表

  • act_re_deployment:定义部署表,每部署一次就增加一条记录
  • act_re_procdef:定义流程表,每部署一个新的流程就增加一条记录
  • act_ge_bytearray:流程资源表。流程部署的bpmn文件和png图片会保存在该表中

在测试文件中设置的内容
在这里插入图片描述
在demo.bpmn文件设置的内容
在这里插入图片描述
在这里插入图片描述

至此,第五章节简单介绍了 创建bpmn文件---->启动activity引擎 的过程,并对运行出现的问题提出解决方案。

5.2 启动流程实例

流程实例的意思是,用户A,用户B 两人需要出差,那么这两个人都需要得到一份出差申请单。
如何得到呢

  • zhangsan 创建申请单-lisi 经理审批- wangwu 总经理审批-zjh 财务审批------->给A
  • zhangsan 创建申请单-lisi 经理审批- wangwu 总经理审批-zjh 财务审批------->给B

所以流程实例是具有全生命周期的,不是只有创建申请单,或者只有经理审批,而是整个流程。可以想像成为了一个类而创建的实例。

/**
     * 启动流程实例
     */
    @Test
    public void test04(){
        //创建ProcessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        //获取运行服务对象
        RuntimeService runtimeService = engine.getRuntimeService();

        //根据流程自定义的id启动流程
        String id = "evection";
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(id);

        //输出相关实例
        System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
        System.out.println("流程实例的ID:" + processInstance.getId());
        System.out.println("当前活动的ID:" + processInstance.getActivityId());


    }

在这里插入图片描述
解释一下这一部分内容:
在这里插入图片描述
这个evection是哪来的,是画图的时候定义的
在这里插入图片描述
其实也可以不写 String id = “evection”;
而是直接写成ProcessInstance processInstance = runtimeService.startProcessInstanceByKey( “evection”);
这是可以的。那为什么这里要多此一举呢?

首先是为了保险,如果我们有很多个流程图,这里不强调的话,很难确保启动的是不是指定的流程图。
实际上,无论何种方式你传入这个参数,执行的时候底层会拿着你传入的这个参数和你绘制图的时候定义的id比较,找不到的话抛出异常。

解释一下这几个ID
流程部署ID:创建好了出差申请单模版,只要启动部署一次,就会生成一个ID(如何启动参考5.1.3章节)
因此会出现这个情况

流程部署ID流程部署的名称
52501出差申请流程
52321出差申请流程
52388出差申请流程
56009出差申请流程

流程定义ID:

流程定义ID流程定义的名称
evection:1:52504出差的申请单

流程实例ID:

为什么是zhangsan呢,因为shangsan负责:创建申请单,而创建申请单是第一个步骤。所以是zhangsan创建的流程实例。

就像前面说的,这个流程实例不是只含有【shangsan 创建申请单】,而是包括全生命周期,【 zhangsan 创建申请单-lisi 经理审批- wangwu 总经理审批-zjh 财务审批】。只不够shangsan在第一个,所以该流程实例它命名。

流程实例ID流程实例的名称
55001shangsan

当前活动ID:
整个流程一共需要4个步骤(这里不统计启动和结束),执行到了第一个步骤

当前活动ID当前活动的名称
_3创建申请单
在这里插入图片描述

5.3 任务查询

我们想知道当前任务的情况

/**
     * 查询任务
     */
    @Test
    public void test05(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();
        List<Task> list = taskService.createTaskQuery().processDefinitionKey("evection").taskAssignee("shangsan").list();

        for (Task task : list) {
            System.out.println("流程定义ID:" + task.getProcessDefinitionId());
            System.out.println("当前任务名称:" + task.getName());
            System.out.println("当前任务ID:" + task.getId());
            System.out.println("当前任务负责人:" + task.getAssignee());


        }
    }

在这里插入图片描述
不知道能都理解这里的当前任务ID,任务负责人是 shangsan,他负责第一步骤:创建申请单,这一步骤的ID是:55004

上面的流程实例ID的前面结束过了,流程实例的名字是也shangsan哦

5.4 处理当前任务

 /**
     * 执行任务流程到下一级
     */
    @Test
    public void test06(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();
        Task task = taskService.createTaskQuery()
                               .processDefinitionKey("evection")
                               .taskAssignee("shangsan")
                               .singleResult();  //单个结果

        //完成当前任务(shangsan创建请假单)
        taskService.complete(task.getId());
    }

运行后,任务就到了经理审批
不信的话,我们看看记录历史任务执行步骤的表
在这里插入图片描述
将5.3代码的shangsan修改为lisi,查询一下当前任务
在这里插入图片描述

5.5 删除流程

首先观察这个表
在这里插入图片描述
然后我执行1次5.1.3中的启动部署代码

在这里插入图片描述
可以发现,多了一个流程,是因为我们多启动了一次部署
在这里插入图片描述
删除部署,就是删除这个东西

5.5.1 删除52501(会失败)

    /**
     * 删除部署
     */
    @Test
    public void test03(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();

        RepositoryService service = engine.getRepositoryService();
        //若已经有实例启动,则会报错
        //52501已经创建了实例
        service.deleteDeployment("52501");
        //即使有实例启动,也会删除(true:级联删除   false:非级联删除)
        //service.deleteDeployment("52501",true);

    }

在这里插入图片描述

5.5.2 删除60001(会成功)

 service.deleteDeployment("60001");

因为虽然启动部署得到了60001,但是没有启动它的流程实例

5.5.3 删除60001(会成功)

强制删除会成功

 service.deleteDeployment("60001",true);

5.6 流程资源的下载

假设一个场景,我们本地现在什么资源都没有,根本无法单独学习企业的activity项目。这些资源在哪里呢,在企业的activity数据库呢。所以我们需要连接企业的数据库,从数据库下载对应的 .bnmn 文件和绘制的流程 .png 图片

/**
     * 读取数据库资源文件
     */
    @Test
    public void test07() throws IOException {
        
        //1.创建 ProcessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        //2.创建 存储服务对象
        RepositoryService repositoryService = engine.getRepositoryService();
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery() //获取查询器
                                                        .processDefinitionKey("evection") //根据流程定义名字
                                                        .singleResult(); // 单个结果
        //3.获取名为evection的流程部署的ID
        String deploymentId = definition.getDeploymentId();

        //4.借助 存储服务对象,获取图片和Bpmn文件
        InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, definition.getDiagramResourceName());

        InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, definition.getResourceName());

        //5.文件的保存
        File filePng = new File("e:/evection.png"); //保存路径
        File fileBpmn = new File("e:/evection.bpmn");
        
        OutputStream outPng = new FileOutputStream(filePng);
        OutputStream outBpmn = new FileOutputStream(fileBpmn);

        //6.将获取的文件,复制到输出流
        copy(pngInput,outPng);
        copy(bpmnInput,outBpmn);

        //7.关闭输入输出流
        pngInput.close();
        outPng.close();
        bpmnInput.close();
        outBpmn.close();

    }

发现成功获取对应的文件
在这里插入图片描述

5.7 查询历史任务

    /**
     * 历史任务查询
     */
    @Test
    public void test08(){
        //创建PreocessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        //创建 存储历史资源服务的对象
        HistoryService historyService = engine.getHistoryService();
        //
        List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()  //创建历史活动实例查询器
                                                            .processDefinitionId("evection:1:52504") //根据 流程定义ID
                                                            .list();

        for (HistoricActivityInstance hi : list) {
            System.out.println(hi.getActivityId());  //活动ID
            System.out.println(hi.getActivityName()); //活动名称
            System.out.println(hi.getActivityType()); //活动类型
            System.out.println(hi.getAssignee());    //活动负责人
            System.out.println(hi.getProcessDefinitionId()); //流程定义ID
            System.out.println(hi.getProcessInstanceId()); //流程实例ID
            System.out.println("-----------------");

        }

    }

执行结果
在这里插入图片描述
解释一下这里的活动类型
在这里插入图片描述
本次单元测试所关联的对应的表
在这里插入图片描述

第五章简单介绍了activity的功能使用,让初学者对activity有了基础的认识和实操能力
第六章将简单介绍工作中会如何使用activity

6 Activity在实际开发中的使用

6.1 让实际业务与activiti表关联(BusinessKey)

6.1.1 理解BusinessKey

分析图中这三者之间的联系
在这里插入图片描述
可以发现,第五章我们已经能创建一个流程,并且执行一个流程了,这很好。但是和业务没有关系啊,比如 王冰冰 填写了请假单信息,将信息存储在了业务系统的EVECTION表,然后Activity引擎创建了一个流程实例。

但是这个流程实例和业务系统的EVECTION表没有建立联系啊,现在是相互独立的。若我们需要根据流程实例查看王冰冰的请假单信息,就无法实现。

所以,需要将业务系统的EVECTION表和Activity库建立联系。如何建立联系呢?

EVECTION表一定会有自己的唯一标识,将这个唯一标识存入到Activity库中,这样不就可以在Activity库中根据流程实例ID+唯一标识ID,锁定王冰冰的请假信息。

在这里插入图片描述
这个唯一标识传递到在Activity中后,专业名称叫:业务标识(BusinessKey)
此图证明Activity库中的act_ru_exection表中预留了BusinessKey字段,等待传递。
在这里插入图片描述

有了这个BusinessKey,就可以很多字段关联,例如
在这里插入图片描述
找到了BusinessKey字段,就能找到流程定义ID,流程实例ID,任务ID等等,那再看需求,例如可以继续根据流程实例ID,找到流程实例相关的信息。

6.1.2 代码实现

   /**
     * businessKey唯一标识
     */
    @Test
    public void test01(){

       
        //获取业务系统请假表的唯一标识
        String businessKey = "10001"; //实际工作中,需要连接数据库,获取到这个表中的唯一标识,这里简化未演示,直接定义一个标识

        //创建ProcessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();

        //创建 运行服务的对象
        RuntimeService runtimeService = engine.getRuntimeService();

        //传入 唯一标识 到指定的 流程定义
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("evection", businessKey);

        //在流程实例中获取传入的BusinessKey
        System.out.println(processInstance.getBusinessKey());

    }

在这里插入图片描述

6.2 流程实例的挂起和激活

在实际业务中,若需要变更流程,就需要暂停流程实例,即为挂起。变更结束后,再激活流程实例。

6.2.1 全部流程实例挂起

只需挂起流程定义,就会挂起流程定义下的所有流程实例

    /**
     * 全部流程实例挂起
     */
    @Test
    public void test02(){
        //创建 ProcessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();

        //创建 注册服务的对象
        RepositoryService repositoryService = engine.getRepositoryService();

        //创建 流程定义的对象
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() //创建流程定义查询器
                                                        .processDefinitionKey("evection") //根据 流程定义名称
                                                        .singleResult();
        //获取当前流程定义对象的状态(挂起/激活)
        boolean suspended = processDefinition.isSuspended();

        //获取 流程定义ID
        String id = processDefinition.getId();

        //若挂起则激活,若激活则挂起
        if (suspended){
            repositoryService.activateProcessDefinitionById(id  //流程定义id
                                       ,true //是否激活
                                       ,null  //激活时间
                                      );
            System.out.println("流程定义id:" + id + ",已激活");
        }else{
            repositoryService.suspendProcessDefinitionById(id
                                       ,true //是否挂起
                                       ,null  //激活时间
            );
            System.out.println("流程定义id:" + id + ",已挂起");
        }
    }

结果打印
在这里插入图片描述
测试一下该流程定义下的流程实例是否挂起
执行一下该流程实例,看看能否在从lis经理审批---->wangwu总经理审批
在这里插入图片描述
发现报错,提示无法执行一个挂起的任务。
说明我们挂起成功

表中字段suspendsion_state(暂停状态)

  • 1 激活
  • 2 挂起

在这里插入图片描述
再执行一次
在这里插入图片描述
在这里插入图片描述

6.2.2 单个流程实例挂起

/**
     * 单个流程挂起
     */
    @Test
    public void test03(){

        //创建 ProcessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();

        //创建 运行服务的对象
        RuntimeService runtimeService = engine.getRuntimeService();

        //流程实例Id
        String Id = "55001";

        //创建 流程实例的对象
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                                                        .processInstanceId(Id)
                                                        .singleResult();

        //获取当前运行实例的状态
        boolean suspended = processInstance.isSuspended();

        //获取运行实例的id
        String id = processInstance.getId();


        //若挂起则激活,若激活则挂起
        if(suspended){
            runtimeService.activateProcessInstanceById(id);
            System.out.println("流程定义id:" + id + ",已激活");
        }else {
            runtimeService.suspendProcessInstanceById(id);
            System.out.println("流程定义id:" + id + ",已挂起");
        }

    }

在这里插入图片描述
测试一下,发现确实挂起
在这里插入图片描述

6.3 分配任务

就是指定shangsan负责创建申请单,lisi负责经理审批,有三种方式,分别是固定分配,表达式分配,监听器分配。

6.3.1 固定分配

就是在绘制图是,在assignee上直接指定
在这里插入图片描述

6.3.2 表达式分配

新建文件demo_UEL.bpmn
(未成功)
在这里插入图片描述
新建测试文件Test03
启动部署 ----> 启动流程实例

   /**
     * 需要先启动Activity部署
     */
    @Test
    public void test01(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();

        RepositoryService service = engine.getRepositoryService();


        Deployment deploy = service.createDeployment() //创建部署
                .addClasspathResource("bpmn/demo_UEL.bpmn") //添加bnmn资源
                .name("出差申请流程_UEL") //
                .deploy(); //部署流程

        System.out.println("流程部署的id:"+deploy.getId());
        System.out.println("流程部署的名称:"+deploy.getName());


    }
   /**
     * 使用表达式分配任务 并 启动流程实例
     */
    @Test
    public void test02(){
        //创建ProcessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        //获取运行服务对象
        RuntimeService runtimeService = engine.getRuntimeService();

        Map<String , Object> map = new HashMap<>();
        map.put("assignee1","张三");
        map.put("assignee2","李四");
        map.put("assignee3","王五");
        map.put("assignee4","王冰冰");

        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("evection_UEL",map);

        //输出相关实例
        System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
        System.out.println("流程实例的ID:" + processInstance.getId());
        System.out.println("当前活动的ID:" + processInstance.getActivityId());

    }

6.3.3 监听器分配

6.4 查询任务

6.4.1 查询任务负责人的代办任务

/**
     * 查询任务
     */
    @Test
    public void test04(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();

        Task task = taskService.createTaskQuery()
                               .processDefinitionKey("ExclusiveGateway")
                                .includeProcessVariables() //查询结果包含流程变量
                               .taskAssignee("李四")
                               .singleResult();

        System.out.println("流程定义ID:" + task.getProcessDefinitionId());
        System.out.println("当前任务名称:" + task.getName());
        System.out.println("当前任务ID:" + task.getId());
        System.out.println("当前任务负责人:" + task.getAssignee());
        System.out.println("流程变量" + task.getProcessVariables());
    }

在这里插入图片描述

6.4.2 获取BusinessKey

有时候还需要获取BusinessKey,根绝这个去业务系统查相关数据
如何获取BusinessKey呢,思路如下

   /**
     * 获取BusinessKey
     */
    @Test
    public void test05(){
        //创建ProcessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        //创建任务服务 的对象
        TaskService taskService = engine.getTaskService();
        //创建运行服务 的对象
        RuntimeService runtimeService = engine.getRuntimeService();

        Task task = taskService.createTaskQuery()
                .processDefinitionKey("ExclusiveGateway")
                .includeProcessVariables() //查询结果包含流程变量
                .taskAssignee("李四")
                .singleResult();

        System.out.println("流程定义ID:" + task.getProcessDefinitionId());
        System.out.println("当前任务名称:" + task.getName());
        System.out.println("当前任务ID:" + task.getId());
        System.out.println("当前任务负责人:" + task.getAssignee());
        System.out.println("流程变量" + task.getProcessVariables());

        //获取流程实例ID
        String processInstanceId = task.getProcessInstanceId();
        //根据实例ID,获取流程实例的对象
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        //根据流程实例对象,获取Key
        String businessKey = processInstance.getBusinessKey();
        System.out.println("获取businessKey:" + businessKey);


    }

6.5 办理任务

在实际业务中,完成任务前需要校验任务的负责人是否具有该任务的办理权限

 /**
     * 执行任务流程到下一级
     */
    @Test
    public void test06(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();
        Task task = taskService.createTaskQuery()
                               .processDefinitionKey("evection")
                               .taskAssignee("shangsan")
                               .singleResult();  //单个结果

		//就是添加一个判断,判断Test对象不为空
		if(task != null){
		     //完成当前任务(shangsan创建请假单)
             taskService.complete(task.getId());
		}
       
    }

7 流程变量

场景描述:员工创建申请单,由部门经理审批,部门经理通过后,判断请假天数,小于三天以下的财务直接审批,大于等于三天的总经理审批。

实现一个流程变量案例:

  1. 创建test.dpmn

解释一下${evention.num}。在下面我们会创建一个实体类:Evention。此类中其中一个参数叫做num,表示请假天数。在单元测试中,我们会创建Evection的对象:evection,通过对象调用参数num。

在这里插入图片描述

针对这个流程图,当我们传入的参数num = 2天时,执行的流程是 创建出差申请单–>部门经理审批—>财务审批。 不需要经过总经理审批。因为只有三天以上才需要总经理审批。

  1. 创建实体类 Evection
    为何需要序列化
@Data
public class Evection implements Serializable {

    private long id;

    private String evectionName;

    //重点在这:出差天数
    private double num;

    private Date beginDate;

    private Date endDate;

    private String destination;

    private String reson;
}

  1. 单元测试
   /**
     * 先启动Activity部署
     */
    @Test
    public void test01(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();

        RepositoryService service = engine.getRepositoryService();


        Deployment deploy = service.createDeployment() //创建部署
                .addClasspathResource("bpmn/test.bpmn") //添加bnmn资源
                .name("出差申请流程-流程变量") //
                .deploy(); //部署流程

        System.out.println("流程部署的id:"+deploy.getId());
        System.out.println("流程部署的名称:"+deploy.getName());

    }

    /**
     * 启动流程实例,设置流程变量
     */
    @Test
    public void test02(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();

        RuntimeService runtimeService = engine.getRuntimeService();

        //创建变量集合
        Map<String ,Object> variables = new HashMap<>();

        //创建出差对象
        Evection evection = new Evection();

		//设置请假天数
        evection.setNum(2d);

        //添加
        variables.put("evection",evection);
        variables.put("assignee1","老大");
        variables.put("assignee2","老二");
        variables.put("assignee3","老三");
        variables.put("assignee4","老四");

        //创建流程实例对象
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("evection-variable", variables);


        //输出相关实例
        System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
        System.out.println("流程实例的ID:" + processInstance.getId());
        System.out.println("流程实例的名称:" + processInstance.getName());
        System.out.println("当前活动的ID:" + processInstance.getActivityId());
    }
  1. 执行结果
    分析为何这里是null
    在这里插入图片描述在这里插入图片描述
    第一次执行任务,从老大(创建出差申请单)---->老二(部门经理审批)
    /**
     * 执行任务流程到下一级
     */
    @Test
    public void test03(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();
        Task task = taskService.createTaskQuery()  //创建任务查询器
                .processDefinitionKey("evection-variable") //根据流程定义名字
                .taskAssignee("老大")  //根据任务负责人
                .singleResult();  //单个结果(是因为结果只有一个,所以这里没用list接收)

        //完成当前任务(shangsan创建请假单)
        taskService.complete(task.getId());
    }

在这里插入图片描述
第二次执行任务,从老二(部门经理审批)---->老四(财务审批),不经过老三(总经理审批)

    /**
     * 执行任务流程到下一级
     */
    @Test
    public void test03(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();
        Task task = taskService.createTaskQuery()  //创建任务查询器
                .processDefinitionKey("evection-variable") //根据流程定义名字
                .taskAssignee("老二")  //根据任务负责人
                .singleResult();  //单个结果(是因为结果只有一个,所以这里没用list接收)

        //完成当前任务(shangsan创建请假单)
        taskService.complete(task.getId());
    }

在这里插入图片描述
测试结果达到预期,流程变量案例结束。

7.1 流程变量的使用

通过上述的案例,可以观察到如何使用流程变量的,这里总结一下

    1. 我们可以在任务和任务间的连线上使用UEL表达式,决定流程走向 比如 n u m > = 3 和 { num >= 3 }和 num>=3{ num < 3 },num就是一个流程变量名称。
    1. 之前我们也使用过UEL表达式来设置任务处理人,例如${assignee1},activiti获取UEL表达式的值,即流程变量assignee1的值,将值作为任务的负责人进行任务分配

这些都是通过UEL表达式使用流程变量

7.2 流程变量的作用域

通过上面的案例,我们对流程变量已经有了初步的认识,简单明白了流程变量的含义。接下来再了解更多的一点细节吧。

当我们使用流程变量时:在一个大型项目中,必然存在多个流程变量,那么就需要制定一些规则来约束流程变量的使用,保证各使用多个变量时不乱套。所以activity的发明者提出了使用作用域来约束使用。

作用域的概念:一个流程定义,可以有多个流程实例。流程定义由多个任务组成,因此执行流程实例,就是按序执行内部的一个个任务。当我们使用不同的方式定义一个变量的时候,这个变量的生命周期可能贯穿在整个流程实例中,也有可能仅仅在其中一个任务中发挥作用。

  • globa变量
    当一个流程变量的作用域为流程实例时,可以称为 global 变量。global变量中变量名不允许重复:若设置相同的变量名,后设置的值会覆盖前设置的变量值。(流程变量的默认作用域是流程实例
  • local变量
    Local 变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。Local 变量名也可以和 global 变量名相同,没有影响。
    在这里插入图片描述

7.3 流程变量的使用

明白了流程变量的作用域的概念,这里实践演示一下如何使用的。

7.3.1 globa变量

目的:定义的流程变量作用域是 流程实例

7.3.1.1 启动流程时设置变量

通过 Map<key,value> 设置流程变量,map 中可以设置多个变量,这个 key 就是流程变量的名字

/**
 * 启动流程时设置变量
 **/
@Test
public void test01(){
	//创建ProcessEngine对象,并创建运行服务的对象
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = engine.getRuntimeService();
    
	//使用集合给流程变量赋值
	Map<String, Object> variables = new HashMap<>();
	variables.put("assignee1","zhangsan");
	variables.put("assignee2","lisi");

	//启动流程实例
	ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("evection", variables);
}

    //输出相关实例
     System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
     System.out.println("流程实例的ID:" + processInstance.getId());
     System.out.println("流程实例的名称:" + processInstance.getName());
     System.out.println("当前活动的ID:" + processInstance.getActivityId());

7.3.1.2 在任务办理时设置流程变量

如果设置的流程变量的 key 在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。

/**
 * 在任务办理时设置流程变量
 **/
@Test
public void test02() {
    //创建ProcessEngine对象,并创建任务服务的对象
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
	
	 //在任务办理时设置流程变量
	 //若继续设置assignee1,则覆盖zhangsan,最终结果是 assingee1 = ZJH
     Map<String, Object> variables = new HashMap<>();
     variables.put("assignee1", "ZJH");
    
     //7.3.1.1示例中已经设置了 assignne1 = zhangsan
     Task task = taskService.createTaskQuery()  //创建任务查询器
                            .processDefinitionKey("evection") //根据流程定义名字
                            .taskAssignee("zhangsan")  //根据任务负责人
                            .singleResult();  //返回一条数据
                
	

     //完成当前任务(ZJH创建请假单)
     //注意这里的参数,比起前面多了一个 集合对象variables
     taskService.complete(task.getId(), variables);
}

7.3.1.3 通过当前流程实例id设置

通过流程实例 id 设置全局变量, 它的作用域是整个流程实例 ,该流程实例必须未执行完成。
在这里插入图片描述

/**
 * 通过当前流程实例id设置,将assignee4 值修改为wang
 **/
@Test
public void test03() {
	//流程实例id
	String exectionId="80001";
	
    //创建ProcessEngine对象,并创建运行服务的对象
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = engine.getRuntimeService();

    Map<String, Object> variables = new HashMap<>();
    variables.put("assignee4", "wang");

	//第1个参数:流程实例id
	//第2个参数:流程定义名称
	//第3个参数:流程变量
    runtimeService.setVariables(exectionId,variables); //因为这里传入了集合,所以是一次设置多个值
	
	//Evection evection = new Evection(); 
	//evection.setNum(3d); 设置请假天数
	//runtimeService.setVariables(exectionId,"evection",variables); //因为这里传入了一个值
}

7.3.2 local变量

注意,任务必须是未执行的任务,可以在历史记录查看是不是未执行

7.3.2.1 local变量任务办理时设置

/**
 * 通过当前任务id设置
 **/
@Test
public void test01() {
	
    //创建ProcessEngine对象,并创建运行服务的对象
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = engine.getRuntimeService();

	//定义流程变量
    Map<String, Object> variables = new HashMap<>();
    variables.put("assignee4", "wang");
    
    //获取任务id
    Task task = taskService.createTaskQuery() //创建任务查询器
    					   .processDefinitionKey("evection") //根据流程定义名字
                           .taskAssignee("zhangsan")  //要查询的负责人
                    	   .singleResult();//返回一条
	String taskId = task.getId();
 
    // 设置local变量,作用域为该任务
    taskService.setVariableLocal(taskId,variables);

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

7.3.2.2 当前任务设置

/**
 * 通过当前任务id设置
 **/
@Test
public void test01() {
	
    //创建ProcessEngine对象,并创建运行服务的对象
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = engine.getRuntimeService();

	//定义流程变量
    Map<String, Object> variables = new HashMap<>();
    variables.put("assignee4", "wang");
    
    //获取当前代办任务id
    Task task = taskService.createTaskQuery() //创建任务查询器
    					   .processDefinitionKey("evection") //根据流程定义名字
                           .taskAssignee("zhangsan")  //要查询的负责人
                    	   .singleResult();//返回一条
	String taskId = task.getId();
 
    // 设置local变量,作用域为该任务
    taskService.setVariableLocal(taskId,variables);
}

8 任务组

考虑一下某种场景,我们前面定义了负责每个任务的审批者,例如张三负责 创建请假申请单。若张三请假了,岂不是流程无法执行,要么就得修改流程定义。这很麻烦的,所以实际场景中,同一个任务会有多个候选人进行审批,一个请假了,另外一个还在呢。

8.1 创建任务组候选人

新建bpmn文件,如图,定义负责离职申请单的人是AA和BB。以此类推
在这里插入图片描述
启动任务

 /**
     * 启动Activity部署
     */
    @Test
    public void test01(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();

        RepositoryService service = engine.getRepositoryService();


        Deployment deploy = service.createDeployment() //创建部署
                .addClasspathResource("bpmn/Group.bpmn") //添加bnmn资源
                .name("离职申请流程") //
                .deploy(); //部署流程

        System.out.println("流程部署的id:"+deploy.getId());
        System.out.println("流程部署的名称:"+deploy.getName());

    }

    /**
     * 启动流程实例
     */
    @Test
    public void test02(){
        //创建ProcessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        //获取运行服务对象
        RuntimeService runtimeService = engine.getRuntimeService();

        //根据流程自定义的id启动流程
        String id = "evection_Group";
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(id);

        //输出相关实例
        System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
        System.out.println("流程实例的ID:" + processInstance.getId());
        System.out.println("当前活动的ID:" + processInstance.getActivityId());

    /**
     * 查询任务
     */
    @Test
    public void test03(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();
        List<Task> list = taskService.createTaskQuery().processDefinitionKey("evection_Group").taskCandidateOrAssigned("AA").list();

        for (Task task : list) {
            System.out.println("流程定义ID:" + task.getProcessDefinitionId());
            System.out.println("当前任务名称:" + task.getName());
            System.out.println("当前任务ID:" + task.getId());
            System.out.println("当前任务负责人:" + task.getAssignee());


        }
    }
    }

在这里插入图片描述
在打印结果和表中都发现,这里的候选审批者怎么是空的呢,我们明明定义了AA和BB呀。
在这里插入图片描述
别着急,往后看

8.2 组任务办理流程

前面我们只是创建了任务的审批候选人,后面还需要让候选人领取组任务,归还组任务等等。这里我们梳理一下,处理组任务时都需要哪些步骤。

8.2.1 领取组任务

候选人领取组任务后变为自己的个人任务

    /**
     * 领取任务:候选人领取组任务后变为自己的个人任务
     */
    @Test
    public void test04(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();

        String taskId = "117504";
        //候选人
        String userid = "AA";

        //领取任务
        Task task = taskService.createTaskQuery() //创建任务查询器
                               .taskId(taskId) //根据任务Id
                               .taskCandidateUser(userid) //根据候选人查询
                               .singleResult();
        //若找到了候选人AA
        if(task != null){
            //领取
            taskService.claim(taskId, userid);
            System.out.println("领取成功");
    }

在这里插入图片描述

8.2.2 查询个人代办任务

   /**
     * 查询任务
     */
    @Test
    public void test03(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();
        List<Task> list = taskService.createTaskQuery().processDefinitionKey("evection_Group").taskCandidateOrAssigned("AA").list();

        for (Task task : list) {
            System.out.println("流程定义ID:" + task.getProcessDefinitionId());
            System.out.println("流程实例ID:" + task.getProcessInstanceId());
            System.out.println("当前任务名称:" + task.getName());
            System.out.println("当前任务ID:" + task.getId());
            System.out.println("当前任务负责人:" + task.getAssignee());
        }
    }

在这里插入图片描述

8.2.3 办理个人任务

   /**
     * 完成个人任务
     */
    @Test
    public void test05(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();

        String taskId = "117504";
        taskService.complete(taskId);
        System.out.println("完成任务:" + taskId);

    }

在这里插入图片描述

8.2.4 归还组任务

若拾取任务后,后悔了,不想完成该任务,就需要退回
现在让CC领取了任务,然后退还任务
在这里插入图片描述

    /**
     * 归还任务
     */
    @Test
    public void test06(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();

        String taskId = "120002";
        //候选人
        String userId = "CC";

        //查看任务
        Task task = taskService.createTaskQuery() //创建任务查询器
                .taskId(taskId) //根据任务Id
                .taskAssignee(userId) //根据候选人查询(注意这里调用的方法)
                .singleResult();
        //若找到了候选人CC
        if(task != null){
            //将候选人设置为null,则表示归还
            taskService.setAssignee(taskId, null);
            System.out.println("归还成功");
        }
    }

8.2.5 任务交接

若领取任务后,后悔了,不想完成该任务,也可以指定其他人接手此任务
现在让CC领取了任务,然后转手给DD

 /**
     * 交接任务
     */
    @Test
    public void test07(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();

        String taskId = "120002";
        //候选人
        String userId = "CC";

        //查看任务
        Task task = taskService.createTaskQuery() //创建任务查询器
                .taskId(taskId) //根据任务Id
                .taskAssignee(userId) //根据候选人查询(注意这里调用的方法)
                .singleResult();
        //若找到了候选人CC
        if(task != null){
            //将候选人设置为指定的人,则表示此人负责该任务
            taskService.setAssignee(taskId, "DD");
            System.out.println("交接成功");
        }
    }

9 网关

前面我们使用流程变量的时候,直接在连线上写条件,为了更规范化,引入网关的概念,用于处理条件分支。下面分别介绍三种网关,其实特别简单。

9.1 排他网关

创建Gateway.bpmn文件
当num=3时,路径是 创建请假申请单–>总经理–>人事经理
只走一条路径,若都不满足,则抛出异常
在这里插入图片描述
可以用代码测试一下,这些网关是否都有用,也能复习前面的知识点。

   /**
     * 启动Activity部署
     */
    @Test
    public void test01(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();

        RepositoryService service = engine.getRepositoryService();


        Deployment deploy = service.createDeployment() //创建部署
                .addClasspathResource("bpmn/Gateway.bpmn") //添加bnmn资源
                .name("离职申请流程") //
                .deploy(); //部署流程

        System.out.println("流程部署的id:"+deploy.getId());
        System.out.println("流程部署的名称:"+deploy.getName());

    }

    /**
     * 启动流程实例时,设置流程变量
     */
    @Test
    public void test02(){
        //创建ProcessEngine对象
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        //获取运行服务对象
        RuntimeService runtimeService = engine.getRuntimeService();

		//请假天数设置为3天
        Evection evection = new Evection();
        evection.setNum(3d);

        HashMap<String , Object> variables = new HashMap<>();
        variables.put("evection",evection);


        //根据流程自定义的id启动流程
        String id = "ExclusiveGateway";
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(id,variables);

        //输出相关实例
        System.out.println("流程定义的ID:" + processInstance.getProcessDefinitionId());
        System.out.println("流程实例的ID:" + processInstance.getId());
        System.out.println("当前活动的ID:" + processInstance.getActivityId());

    }

    /**
     * 执行任务流程到下一级
     */
    @Test
    public void test03(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();

        Task task = taskService.createTaskQuery()  //创建任务查询器
                .processDefinitionKey("ExclusiveGateway") //根据流程定义名字
                .taskAssignee("张三")  //根据任务负责人
                .singleResult();  //单个结果(是因为结果只有一个,所以这里没用list接收)

        //完成当前任务(shangsan创建请假单)
        taskService.complete(task.getId());
    }

    /**
     * 查询任务
     */
    @Test
    public void test04(){
        ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = engine.getTaskService();
        List<Task> list = taskService.createTaskQuery().processDefinitionKey("ExclusiveGateway").taskAssignee("李四").list();

        for (Task task : list) {
            System.out.println("流程定义ID:" + task.getProcessDefinitionId());
            System.out.println("当前任务名称:" + task.getName());
            System.out.println("当前任务ID:" + task.getId());
            System.out.println("当前任务负责人:" + task.getAssignee());

        }
    }

一系列操作后,发现确实是预期的执行顺序!

9.2 并行网关

同时执行多条线路,所以不用写条件,写了也会被忽略掉
执行线路:创建请假申请单–>(总经理+部门经理)–>人事经理
在这里插入图片描述
简单概括一下这里的代码操作过程,可能会好奇如何同时执行两个任务呢?
例如
1.创建了流程实例。此时的任务是 (张三:创建请假申请单)
2.执行张三的任务。此时的任务是 (李四:总经理)和(王五:部门经理)
3.执行李四的任务。此时的任务是 (王五:部门经理)
4.执行王五的任务。此时的任务是 (王冰冰:人事经理)

因此说明,需要我们手动分别执行并行的任务,才能得到汇总结果

9.3 包含网关

当num=3时,执行线路:创建请假申请单–>(总经理+监管部门)—>人事经理
当num=1时,执行线路:创建请假申请单–>(部门经理+监管部门)—>人事经理

无论哪条路径,都包含监管部门
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/38146.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习 / 数据处理:如何处理偏态数据

1 前言 当我们使用一个线性回归模型时&#xff0c;通常这个模型是在很大假设的前提下才有一个很好的结果&#xff1a; 1、假设预测因子和预测目标之间的关系是线性的2、数据不存在外在噪声&#xff1a;不存在一些极端的数据3、非共线性&#xff08; collinearity&#xff09;…

区块链生态发展

文章目录 前言以太坊的到来什么是图灵完备&#xff1f;什么是智能合约&#xff1f; 以太坊的应用去中心化应用 DApp代币发行 公有链&联盟链区块链应用总结 前言 前面的区块链文章有介绍区块链的诞生以及底层运行原理&#xff0c; 本文主要介绍一下区块链应用的发展&#x…

Windows Bat实现延时功能的几种常见方式

文章目录 1. 使用ping命令实现延时2. 使用timeout命令实现延时3. 使用choice命令实现延时4. 使用for循环实现延时5. 使用sleep命令实现延时6. 使用VBScript.sleep实现延时总结 在 bat批处理中实现延时功能的几种常用方式 1. 使用ping命令实现延时 使用ping命令可以实现延时的…

最小二乘拟合平面——拉格朗日乘子法

目录 一、算法原理二、代码实现1、python2、matlab 三、算法效果 一、算法原理 设拟合出的平面方程为&#xff1a; a x b y c z d 0 (1) axbyczd0\tag{1} axbyczd0(1) 约束条件为&#xff1a; a 2 b 2 c 2 1 (2) a^2b^2c^21\tag{2} a2b2c21(2)   可以得到平面参数 a…

ahk1.1获取输入光标当前位置坐标(不是鼠标的位置)

F1 Up::Caret:GetCaretPos(1), hasCaretPos:1x坐标 : Caret.xy坐标 : Caret.yToolTip, %x坐标% %y坐标%Return; 获取光标坐标GetCaretPos(Byacc:1){Static initIf (A_CaretX""){Caretx:Carety:CaretH:CaretW:0If (Byacc){If (!init)init:DllCall("LoadLibrary&q…

Access violation at address 00000000. Read of address 00000000.的解决办法

Access violation at address 00000000. Read of address 00000000. 原理解决办法 在使用spacesniffer查看C盘空间的时候报错 原理 这个问题是关于Access Violation&#xff08;非法访问&#xff09;&#xff0c;General Protection Fault&#xff08;一般保护性错误&#x…

pytorch构建深度网络的基本概念——随机梯度下降

文章目录 随机梯度下降定义一个简单的模型定义Loss什么是梯度随机梯度下降 随机梯度下降 现在说说深度学习中的权重更新算法&#xff1a;经典算法SGD&#xff1a;stochastic gradient descent&#xff0c;随机梯度下降。 定义一个简单的模型 假设我们的模型就是要拟合一根直…

IDEA+springboot + ssm +shiro+ easyui +mysql实现的进销存系统

IDEAspringboot ssm shiro easyui mysql实现的进销存系统 一、系统介绍1.环境配置 二、系统展示1. 管理员登录2.首页3.修改密码4.系统日志5. 用户管理6. 角色管理7. 进货入库8.退货出库9.进货单据查询10.退货单据查询11.当前库存查询12.销售出库13.客户退货14. 销售单据查询15…

HTML和CSS配合制作一个简单的登录界面

HTML和CSS配合制作一个简单的登录界面 界面HTMLCSS解释语法 界面 HTML <!DOCTYPE html> <html lang"en"> <head><title>篮球世界</title><meta charset"UTF-8"><link type"text/css" rel"styleshe…

从Web2到Web3:区块链技术的未来前景

随着互联网的发展&#xff0c;Web1.0、Web2.0 和 Web3.0 成为了人们口中津津乐道的话题。那么&#xff0c;这三种网络时代究竟有什么区别呢&#xff1f; Web1.0 是一个只读的时代&#xff0c;那个时候&#xff0c;用户只能浏览网页&#xff0c;无法进行互动和创作。Web2.0 则是…

github搜索案例

目录结构 public/index.html <!DOCTYPE html> <html lang""><head><meta charset"utf-8"><!-- 针对IE浏览器的一个特殊配置&#xff0c;含义是让IE浏览器以最高的渲染级别渲染页面 --><meta http-equiv"X-UA-Comp…

AI绘画Stable Diffusion实战操作: 62个咒语调教-时尚杂志封面

今天来给大家分享&#xff0c;如何用sd简单的咒语输出好看的图片的教程&#xff0c;今天做的是时尚杂志专题&#xff0c;话不多说直入主题。 还不会StableDiffusion的基本操作&#xff0c;推荐看看这篇保姆级教程&#xff1a; AI绘画&#xff1a;Stable Diffusion 终极炼丹宝…

Win32 汇编在对话框上画线

参阅前文&#xff0c;首先要有一个基本的对话框&#xff1b; 把对话框资源文件里的控件定义都删除&#xff0c;得到的一个rc文件&#xff0c;test.rc&#xff1b; #include <resource.h>#define DLG_MAIN 1DLG_MAIN DIALOG 193, 180, 130, 150 STYLE DS_MODALFRAME | …

用主流编程语言解小学题

最近在网上刷到一个视频&#xff0c;内容是奶奶有60 元钱&#xff0c;去超市买了10元水果&#xff0c;收营员应该找奶奶多少钱?我一开始反应就是50元&#xff0c;后来想了想题干里没有说明这60元是怎么构成的&#xff0c;有可能是一张50元和一张10元&#xff0c;或者是3张20元…

图形编辑器开发:一些会用到的简单几何算法

大家好&#xff0c;我是前端西瓜哥。 开发图形编辑器&#xff0c;你会经常要解决一些算法问题。本文盘点一些我开发图形编辑器时遇到的简单几何算法问题。 矩形碰撞检测 判断两个矩形是否发生碰撞&#xff08;或者说相交&#xff09;&#xff0c;即两个矩形有重合的区域。 …

lwip-2.1.3自带的httpd网页服务器使用教程(一)从SD卡读取网页文件并显示

概述 本教程使用的单片机是STM32F103ZE&#xff0c;有线网口芯片为ENC28J60。 本教程里面的网页由于需要兼容Windows XP系统的IE8浏览器&#xff0c;所以采用HTML 4.01编写&#xff0c;不使用任何前端框架。笔者使用的网页设计软件是Adobe Dreamweaver CS3。 开发板PCB文件是公…

我造了一个新的词汇:信息湍流

信息湍流 信息湍流的简介起因有出现信息湍流的领域如何做信息湍流的计算 信息湍流的简介 在物流学中&#xff0c;一个物体从一个位置到另外一个位置&#xff0c;我们可以通过精确的公式计算来预测出新位置。 而水和气体则是大量一个一个物体组成的新物体&#xff0c;称为&…

【UniApp开发小程序】项目创建+整合UI组件(FirstUI和uView)

创建项目 下图为初始化的项目的文件结构 引入组件 俗话说&#xff1a;“工欲善其事&#xff0c;必先利其器”&#xff0c;为了更加方便地开发出页面较为美观的小程序&#xff0c;我们先引入成熟的UI组件&#xff0c;再开始开发之旅。&#xff08;如果你是前端高手&#xff0…

人工智能-神经网络

目录 1 神经元 2 MP模型 3 激活函数 3.1 激活函数 3.2 激活函数作用 3.3 激活函数有多种 4、神经网络模型 5、神经网络应用 6、存在的问题及解决方案 6.1 存在问题 6.2 解决方案-反向传播 1 神经元 神经元是主要由树突、轴突、突出组成&#xff0c;树突是从上面接收很多…

【DeepLabCut】初识姿势估计 | DeepLabCut教程 | 单动物实现

&#x1f4e2;前言&#xff1a;姿势估计作为计算机视觉领域中的一个重要分支&#xff0c;本章将介绍姿势估计和一个用于姿势估计的工具。并以斑马鱼的运动视频为例&#xff0c;手把手教你如何用deeplabcut训练自己的数据。 目录 Ⅰ 初识姿势估计 0x00 姿势估计介绍 Ⅱ 初…