Activiti7 基本使用教程

源码示例:activiti7Demo.7z - 蓝奏云

一、概述

官网地址:Open Source Business Automation | Activiti

        Activiti由Alfresco软件开发,目前最高版本Activiti 7。是BPMN的一个基于java的软件实现,不过 Activiti  不仅仅包括BPMN,还有DMN决策表和CMMN Case管理引擎,并且有自己的用户管理、微服务API 等一系列功能,是一个服务平台。

二、入门案例

官方手册:Activiti 用户手册

1、 创建SpringBoot项目

        创建一个普通的SpringBoot项目。指定版本为 2.4.2 即可。

        然后添加对应的依赖:Activiti7的依赖和MySQL的依赖。

<properties>
	<java.version>1.8</java.version>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	<spring-boot.version>2.4.2</spring-boot.version>
</properties>
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>8.0.23</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.activiti</groupId>
		<artifactId>activiti-spring-boot-starter</artifactId>
		<version>7.0.0.GA</version>
	</dependency>
</dependencies>
<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-dependencies</artifactId>
			<version>${spring-boot.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>
<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.8.1</version>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
				<encoding>UTF-8</encoding>
			</configuration>
		</plugin>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
			<version>${spring-boot.version}</version>
			<configuration>
				<mainClass>com.boge.act.PrepareDemo2Application</mainClass>
				<skip>true</skip>
			</configuration>
			<executions>
				<execution>
					<id>repackage</id>
					<goals>
						<goal>repackage</goal>
					</goals>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>

2、获取ProcessEngine

2.1、默认的方式

        在工作流引擎框架中, ProcessEngine 是一个非常核心的对象,我们需要首先解决这个对象的获取。获取方式很多。先来看最简单的一个基于 activiti.cfg.xml 的XML文件的配置方式。

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

        通过 getDefaultProcessEngine 方法加载会默认的从classpath路径下加载 activiti.cfg.xml 配置文件。我们添加该文件。内容如下:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
		<property name="jdbcUrl"value="jdbc:mysql://localhost:3306/activiti7" />
		<property name="jdbcDriver"value="com.mysql.cj.jdbc.Driver" />
		<property name="jdbcUsername"value="root" />
		<property name="jdbcPassword"value="123456" />
		<property name="databaseSchemaUpdate"value="true" />
		<property name="asyncExecutorActivate"value="false" />
		<property name="mailServerHost"value="mail.my-corp.com" />
		<property name="mailServerPort"value="5025" />
	</bean>
</beans>

        然后我们就可以启动,但是出现了如下错误:

        出现这种情况只需要在mysql的连接字符串中添加上nullCatalogMeansCurrent=true,设置为只查当前连接的schema库即可。

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activiti7?
nullCatalogMeansCurrent=true" />
			<property name="jdbcDriver"value="com.mysql.cj.jdbc.Driver" />
			<property name="jdbcUsername"value="root" />
			<property name="jdbcPassword"value="123456" />
			<property name="databaseSchemaUpdate"value="true" />
			<property name="asyncExecutorActivate"value="false" />
			<property name="mailServerHost"value="mail.my-corp.com" />
			<property name="mailServerPort"value="5025" />
		</bean>
	</beans>

        然后执行程序正确。搞定。同时在数据库中创建了相关的表结构:

2.2、编程方式获取

        上面的配置文件的方式中的配置文件其实是一个Spring的配置文件,但是这并不意味着Activiti只能用于Spring环境。我们也可以通过编程的方式来使用配置文件,从而来构建ProcessEngineConfiguration对象,具体的实现如下:

@Test
public void test2() {
        ProcessEngine engine = ProcessEngineConfiguration
            .createStandaloneInMemProcessEngineConfiguration()
            .setJdbcUrl("jdbc:mysql://localhost:3306/activiti7?
                nullCatalogMeansCurrent = true ")
                .setJdbcDriver("com.mysql.cj.jdbc.Driver")
                .setJdbcPassword("123456")
                .setJdbcUsername("root")
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE)
                .buildProcessEngine(); System.out.println(engine);
            }

相关属性说明:

        databaseSchemaUpdate:用于设置流程引擎启动关闭时使用的数据库表结构控制策略。

  • false (默认): 当引擎启动时,检查数据库表结构的版本是否匹配库文件版本。版本不匹配时抛出异常。
  • true : 构建引擎时,检查并在需要时更新表结构。表结构不存在则会创建
  • create-drop : 引擎创建时创建表结构,并在引擎关闭时删除表结构

2.3、表结构介绍

        在Activiti7中。我们启动服务会自动维护Activiti7需要使用到的相关的表结构。在这块我们需要有个大概的了解。首先是支持的数据库有:

Activiti数据库类型示例 JDBC URL备注
h2jdbc:h2:tcp://localhost/activiti默认配置的数据库
mysqljdbc:mysql://localhost:3306/activiti?autoReconnect=true已使用mysqlconnectorjava数据库驱动测试
oraclejdbc:oracle:thin:@localhost:1521:xe
postgresjdbc:postgresql://localhost:5432/activiti
db2jdbc:db2://localhost:50000/activiti
mssqljdbc:sqlserver://localhost:1433;databaseName=activiti (jdbc.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver) OR jdbc:jtds:sqlserver://localhost:1433/activiti (jdbc.driver=net.sourceforge.jtds.jdbc.Driver)已使用 Microsoft JDBC Driver 4.0 (sqljdb

        Activiti的所有数据库表都以ACT_开头。第二部分是说明表用途的两字符标示符。服务API的命名也大略符合这个规则。

  • ACT_RE_*: RE 代表 repository 。带有这个前缀的表包含“静态”信息,例如流程定义与流程资源(图片、规则等)。
  • ACT_RU_*: RU 代表 runtime 。这些表存储运行时信息,例如流程实例(process instance)、用户任务(user task)、变量(variable)、作业(job)等。Activiti只在流程实例运行中保存运行时数据,并在流程实例结束时删除记录。这样保证运行时表小和快。
  • ACT_ID_*: ID 代表 identity 。这些表包含身份信息,例如用户、组等。
  • ACT_HI_*: HI 代表 history 。这些表存储历史数据,例如已完成的流程实例、变量、任务等
  • ACT_GE_*: 通用数据。用于不同场景下

3、在线流程设计器

        接下来我们通过官方提供的流程设计器来实现一个简单流程的设计。然后完成相关的部署和流程整体操作。官网下载地址:下载下来后解压缩。

        进入到wars中。提供的有Activiti-app.war

        把这war包拷贝到Tomcat服务器中即可。注意Tomcat的版本不要高于8.5,然后Tomcat服务。访问 http://localhost:8080/activiti-app 即可。登录的账号密码是 admin test

        点击create process 弹出窗口。录入相关的流程定义信息:

        绘制好流程图后。保存并下载对应的xml文件:

        得到的流程图的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="test1"name="test1"isExecutable="true">
		<documentation>test1</documentation>
		<startEvent id="startEvent1">
		</startEvent>
		<userTask id="sid-470631FF-51BA-4954-96BB-346B99CA0A2C" name="人事审批"
activiti:assignee="zhangsan">
			<extensionElements>
				<modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler">
<!
[CDATA[false]]>
				</modeler:initiator-can-complete>
			</extensionElements>
		</userTask>
		<sequenceFlow id="sid-B53369E8-E698-4F53-AE40-97E7654BFA78" sourceRef="startEvent1"
targetRef="sid-470631FF-51BA-4954-96BB-346B99CA0A2C">
		</sequenceFlow>
		<userTask id="sid-34454522-B109-41C9-8519-59D29B621099" name="经理审批"
activiti:assignee="lisi">
			<extensionElements>
				<modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler">
<!
[CDATA[false]]>
				</modeler:initiator-can-complete>
			</extensionElements>
		</userTask>
		<sequenceFlow id="sid-5AE88ADE-9FD3-48F2-81EF-528DA0C068CB" sourceRef="sid-
470631FF-51BA-4954-96BB-346B99CA0A2C" targetRef="sid-34454522-B109-41C9-8519-
59D29B621099">
		</sequenceFlow>
		<endEvent id="sid-EA0332FA-59B0-45C0-9D24-47C78051D52C">
		</endEvent>
		<sequenceFlow id="sid-F6C0657A-C92F-4DEA-AAB1-93750FFBD7E5" sourceRef="sid-
34454522-B109-41C9-8519-59D29B621099" targetRef="sid-EA0332FA-59B0-45C0-9D24-
47C78051D52C">
		</sequenceFlow>
	</process>
	<bpmndi:BPMNDiagram id="BPMNDiagram_test1">
		<bpmndi:BPMNPlane bpmnElement="test1" id="BPMNPlane_test1">
			<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
				<omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="sid-470631FF-51BA-4954-96BB-346B99CA0A2C"
id="BPMNShape_sid-470631FF-51BA-4954-96BB-346B99CA0A2C">
				<omgdc:Bounds height="80.0" width="100.0" x="175.0" y="138.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="sid-34454522-B109-41C9-8519-59D29B621099"
id="BPMNShape_sid-34454522-B109-41C9-8519-59D29B621099">
				<omgdc:Bounds height="80.0" width="100.0" x="320.0" y="138.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="sid-EA0332FA-59B0-45C0-9D24-47C78051D52C"
id="BPMNShape_sid-EA0332FA-59B0-45C0-9D24-47C78051D52C">
				<omgdc:Bounds height="28.0" width="28.0" x="465.0" y="164.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNEdge bpmnElement="sid-5AE88ADE-9FD3-48F2-81EF-528DA0C068CB"
id="BPMNEdge_sid-5AE88ADE-9FD3-48F2-81EF-528DA0C068CB">
				<omgdi:waypoint x="275.0" y="178.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="320.0" y="178.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="sid-F6C0657A-C92F-4DEA-AAB1-93750FFBD7E5"
id="BPMNEdge_sid-F6C0657A-C92F-4DEA-AAB1-93750FFBD7E5">
				<omgdi:waypoint x="420.0" y="178.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="465.0" y="178.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="sid-B53369E8-E698-4F53-AE40-97E7654BFA78"
id="BPMNEdge_sid-B53369E8-E698-4F53-AE40-97E7654BFA78">
				<omgdi:waypoint x="130.0" y="178.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="175.0" y="178.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
		</bpmndi:BPMNPlane>
	</bpmndi:BPMNDiagram>
</definitions>

        然后我们就可以做流程的部署操作了。

4、流程操作

4.1、 流程部署

        设计好了流程图我们就可以通过如下的代码完成流程的部署。

/**
 * 流程部署操作
 */
@Test
public void test3() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.完成流程的部署操作 需要通过RepositoryService来完成
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成部署操作
    Deployment deploy = repositoryService.createDeployment()
        .addClasspathResource("flow/test1.bpmn20.xml")
        .name("第一个流程")
        .deploy();
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}

        流程部署的行为会涉及到数据库中的这两张表:

        然后我们可以通过Activiti提供的相关的API来获取流程部署和流程定义的相关信息:

/**
 * 查询当前部署的流程有哪些
 */
@Test
public void test4() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RepositoryService repositoryService = engine.getRepositoryService();
    // 查询有哪些部署的流程--》查询相关的流程定义信息
    // repositoryService.createDeploymentQuery() 查询流程部署的相关信息
    // repositoryService.createProcessDefinitionQuery() 查询部署的流程的相关的定义
    List < Deployment > list = repositoryService.createDeploymentQuery().list(); // 查询所有的部署信息
    for (Deployment deployment: list) {
        System.out.println(deployment.getId());
        System.out.println(deployment.getName());
    }
    List < ProcessDefinition > list1 =
        repositoryService.createProcessDefinitionQuery().list();
    for (ProcessDefinition processDefinition: list1) {
        System.out.println(processDefinition.getId());
        System.out.println(processDefinition.getName());
        System.out.println(processDefinition.getDescription());
    }
}

4.2、发起流程

        部署流程成功后。我们就可以发起一个流程。发起流程需要通过 RuntimeService 来实现。

/**
 * 发起一个流程
 */
@Test
public void test5() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 通过流程定义ID来启动流程 返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
        .startProcessInstanceById("test1:1:3");
    System.out.println("processInstance.getId() = " + processInstance.getId());
    System.out.println("processInstance.getDeploymentId() = " +
        processInstance.getDeploymentId());
    System.out.println("processInstance.getDescription() = " +
        processInstance.getDescription());
}

        发起流程成功后。在对应的 act_ru_task 中就有一条对应的待办记录。

        对应的流程状态如下:

4.3、查询流程

        用户登录后要查看待办的任务信息。我们需要通过 TaskService 来实现查询操作。具体代码如下:

/**
 * 待办查询
 */
@Test
public void test6() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 待办查询 执行中的任务处理通过 TaskService来实现
    TaskService taskService = engine.getTaskService();
    // Task 对象对应的其实就是 act_ru_task 这张表的记录
    List < Task > list = taskService.createTaskQuery().taskAssignee("lisi").list();
    if (list != null && list.size() > 0) {
        for (Task task: list) {
            System.out.println(task.getId());
            System.out.println(task.getName());
            System.out.println(task.getAssignee());
        }
    } else {
        System.out.println("当前没有待办任务");
    }
}

4.4、审批流程

        当前登录用户查看到相关的待办信息后。可以做流程的审批处理。

/**
 * 任务审批
 */
@Test
public void test7() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 做任务申请 也需要通过 TaskService 来实现
    TaskService taskService = engine.getTaskService();
    // 根据当前登录用户查询出对应的待办信息
    List < Task > list = taskService.createTaskQuery().taskAssignee("lisi").list();
    if (list != null && list.size() > 0) {
        for (Task task: list) {
            // 做对应的任务审批处理
            taskService.complete(task.getId());
        }
    }
    // 完成任务
    // taskService.complete("2505");
}

5、涉及表结构

上面一个审批涉及到的表结构的介绍:

表名说明
act_re_deployment部署流程的记录表:一次部署行为会产生一张表
act_re_procdef流程定义表:一张流程图对应的表
act_hi_procinst流程实例表:发起一个流程。就会创建对应的一张表
act_ru_task流程待办表:当前需要审批的记录表,节点审批后就会被删除
act_hi_actinst历史记录:流程审批节点的记录信息

6、流程设计器持久化

        流程设计器默认是通过 H2 来完成数据的存储的。而 H2 是基于内存来存储的。所以重启服务后数据就丢失了。这时我们可以设置流程设计器的存储方式为MySQL。这样就能持久化的实现存储了。具体步骤如下:

调整数据库的连接信息。记得同时需要创建对应的数据库:

        切换了数据的存储方案后。我们需要记得把对应的数据库的驱动拷贝进来。

        然后我们就可以重启服务测试了。如果出现下面的错误,降低MySQL驱动的版本到8.0.19。

配置的时区不配支持。我们需要添加:

启动成功后。在数据库中会维护相关的表结构:

该操作中需要注意的点:

  • 修改配置文件中的信息关键是连接地址的路径:jdbc:mysql://localhost:3306/activiti6ui?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&nullCatalogMeansCurrent=true
  • MySQL的驱动版本不要高于8.0.19,不然会出现LocalDataTime转换的问题

7、流程设计器汉化说明

        流程设计器的汉化操作:

{
  "GENERAL": {
    "MAIN-TITLE": "Activiti",
    "ERROR": {
      "GENERIC": "抱歉,发生了一个错误。",
      "UNKNOWN": "抱歉,执行请求的操作时出错。",
      "BAD_REQUEST": "执行请求的操作时出错。",
      "NOT_FOUND": "您试图访问的资源不存在。",
      "UNAUTHORIZED": "您应该登录才能执行请求的操作。",
      "FORBIDDEN": "不允许您执行请求的操作。",
      "INTERNAL_SERVER_ERROR": "抱歉,执行请求的操作时发生意外错误。",
      "QUOTA-EXCEEDED-RUNTIME-APPS": "作为试用用户,您只能同时部署 {{quota}} 应用程序。",
      "QUOTA-EXCEEDED-LICENSE-APPS": "许可证只允许同时部署 {{quota}} 应用程序。"
    },
    "ACTION": {
      "LOGOUT": "退出",
      "HELP": "入门",
      "EDIT-PROFILE": "编辑配置文件",
      "SAVE": "保存",
      "CANCEL": "取消",
      "CLOSE": "关闭",
      "DEPLOY": "部署",
      "ABOUT": "关于Alfresco Activiti"
    }
  },
  "LOGIN": {
    "TITLE": "登录",
    "USERNAME": "用户名",
    "USERNAME-PLACEHOLDER": "输入您的用户名",
    "PASSWORD": "密码",
    "PASSWORD-PLACEHOLDER": "输入您的密码",
    "INVALID-CREDENTIALS": "域相关参数设置有误",
    "ACTION": {
      "CONFIRM": "登录"
    }
  },
  "ACCOUNT": {
    "ACTIVATE": {
      "TITLE": "激活帐户",
      "ACTIVATING-MESSAGE": "请稍候 {{userFullName}} 我们正在激活您的帐户。",
      "SUCCESS-MESSAGE": "您的帐户已激活。请在下面登录以开始设计和运行流程。",
      "FAILURE-MESSAGE": "无法激活您的帐户。它已被激活或激活链接已过期。"
    },
    "RESET-PASSWORD-REQUEST": {
      "MESSAGE": "忘记密码了?在下面输入您的电子邮件地址以接收电子邮件以重置密码。",
      "TITLE": "重置密码",
      "EMAIL": "电子邮件地址",
      "EMAIL-PLACEHOLDER": "输入您的电子邮件",
      "SECURITY-SECTION": "安全检查",
      "CONFIRM": "请求密码重置",
      "SUCCESS-MESSAGE": "您将很快收到一封邮件,其中包含重置密码的链接",
      "ERROR": {
        "UNEXISTING-USER": "具有给定电子邮件地址的用户不存在。"
      }
    },
    "RESET-PASSWORD": {
      "TITLE": "重置密码",
      "PASSWORD": "密码",
      "PASSWORD-CONFIRM": "确认密码",
      "PASSWORD-PLACEHOLDER": "输入新密码",
      "PASSWORD-CONFIRM-PLACEHOLDER": "确认新密码",
      "CONFIRM": "更改密码",
      "LOADING": "正在重置密码...",
      "SUCCESS-MESSAGE": "您的密码已更改。",
      "LOGIN": "马上登录",
      "FAILURE-MESSAGE": "您的密码无法重置。重置链接无效或已过期。"
    }
  },
  "APP": {
    "KICKSTART": {
      "TITLE": "启动程序",
      "DESCRIPTION": "创建流程模型、表单和应用程序定义,然后与其他人共享您的模型和定义。"
    },
    "TASKS": {
      "TITLE": "任务应用程序",
      "DESCRIPTION": "访问您的完整任务列表,并从任何流程应用程序处理分配给您的任何任务。同时,启动新的流程和任务。"
    },
    "IDENTITY-MANAGEMENT": {
      "TITLE": "身份管理",
      "TITLE-TENANT-ADMIN": "身份管理",
      "DESCRIPTION": "管理您的配置文件:更改图片、名称和其他设置。作为管理员用户,管理用户和组。",
      "DESCRIPTION-TENANT-ADMIN": "管理组织中的用户和组。"
    },
    "CUSTOM-APP": {
      "TITLE-TASKS": "任务",
      "TITLE-PROCESSES": "流程"
    },
    "POPUP": {
      "ADD-APP-TITLE": "将App添加到登录页",
      "ADD-APP-SUMMARY": "将App添加到登录页"
    },
    "ACTION": {
      "DELETE": "删除App"
    },
    "MESSAGE": {
      "DELETED": "已成功删除App"
    }
  }
}

三、任务分配

1、固定分配

        在指派 用户任务 的审批人时。我们是直接指派的固定账号。但是为了保证流程设计审批的灵活性。我们需要各种不同的分配方式,所以这节我们就详细的来介绍先在Activiti7中我们可以使用的相关的分配方式.

        固定分配就是我们前面介绍的,在绘制流程图或者直接在流程文件中通过Assignee来指定的方式。

2、表达式

        Activiti使用UEL进行表达式解析。UEL代表Unified Expression Language ,是EE6规范的一部分(查看EE6规范了解更多信息)。为了在所有环境上支持UEL标准的所有最新特性,我们使用JUEL的修改版本。

        表达式可以用于例如Java服务任务 Java Service tasks , 执行监听器 Execution Listeners , 任务监听器 Task Listeners 与 条件流 Conditional sequence flows。尽管有值表达式与方法表达式两种表达式,通过Activiti的抽象,使它们都可以在需要 expression (表达式)的地方使用:

${myVar}
${myBean.myProperty}

2.1、值表达式

        我们在处理的位置通过UEL表达式来占位。

        然后做流程的部署和启动操作:

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.完成流程的部署操作 需要通过RepositoryService来完成
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成部署操作
    Deployment deploy = repositoryService.createDeployment()
        .addClasspathResource("flow/test2.bpmn20.xml")
        .name("请假流程-流程变量")
        .deploy(); // 是一个流程部署的行为 可以部署多个流程定义的
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}

        然后我们发起请假流程:

/**
 * 发起一个流程
 */
@Test
public void test3() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 通过流程定义ID来启动流程 返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
        .startProcessInstanceById("test01:1:12503");
    System.out.println("processInstance.getId() = " + processInstance.getId());
    System.out.println("processInstance.getDeploymentId() = " +
        processInstance.getDeploymentId());
    System.out.println("processInstance.getDescription() = " +
        processInstance.getDescription());
}

        我们发起流程后。根据流程的设计应该需要进入到人事审批。但是呢。审批的用户是 ${assign1} 是一个流程变量。那么还没有赋值的情况下。那么系统是没有办法识别的。

        我们需要在进入该节点前对流程变量赋值:

/**
 * 发起一个流程
 */
@Test
public void test3() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 对流程变量做赋值操作
    Map < String, Object > map = new HashMap < > ();
    map.put("assgin1", "张三");
    map.put("assign2", "李四");
    // 通过流程定义ID来启动流程 返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
        .startProcessInstanceById("test01:1:12503", map);
    System.out.println("processInstance.getId() = " + processInstance.getId());
    System.out.println("processInstance.getDeploymentId() = " +
        processInstance.getDeploymentId());
    System.out.println("processInstance.getDescription() = " +
        processInstance.getDescription());
}

        然后我们就可以看到对应的表结构中的待办记录:

        同时需要了解 : ACT_RU_VARIABLE

2.2、方法表达式

        方法表达式 Method expression: 调用一个方法,可以带或不带参数。当调用不带参数的方法时,要确保在方法名后添加空括号(以避免与值表达式混淆)。传递的参数可以是字面值(literal value),也可以是表达式,它们会被自动解析。例如:

${printer.print()}
${myBean.getAssignee()}
${myBean.addNewOrder('orderName')}
${myBean.doSomething(myVar, execution)}

        myBean是Spring容器中的个Bean对象,表示调用的是bean的addNewOrder方法.我们通过案例来演示下。我们先定义对应的Service;先定义Bean:

public class MyBean {
    public String getAssignee() {
        System.out.println("本方法执行了....");
        return "赵六";
    }
}

        然后在Spring的配置文件中注册:

        然后在绘制流程图的时候就可以对应的指派了。

        然后我们先部署流程:

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.完成流程的部署操作 需要通过RepositoryService来完成
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成部署操作
    Deployment deploy = repositoryService.createDeployment()
        .addClasspathResource("flow/test3.bpmn20.xml")
        .name("请假流程-方法表达式")
        .deploy(); // 是一个流程部署的行为 可以部署多个流程定义的
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}

        然后我们发起新的流程。注意在这块我们可以不用添加流程变量信息了。因为 人事审批节点 的审批人是通过流程方法来赋值的。

/**
 * 发起一个流程
 */
@Test
public void test3() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 通过流程定义ID来启动流程 返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
        .startProcessInstanceById("test01:2:27503");
    System.out.println("processInstance.getId() = " + processInstance.getId());
    System.out.println("processInstance.getDeploymentId() = " +
        processInstance.getDeploymentId());
    System.out.println("processInstance.getDescription() = " +
        processInstance.getDescription());
}

        可以看到操作成功。方法表达式被执行了:

        同时待办中的审批人就是方法表达式返回的结果:

3、监听器分配

        可以使用监听器来完成很多Activiti的流程业务。我们在此处使用监听器来完成负责人的指定,那么我们在流程设计的时候就不需要指定assignee。创建自定义监听器:

public class MyFirstListener implements TaskListener {
    /**
     * 监听器触发的回调方法
     * @param delegateTask
     */
    @Override
    public void notify(DelegateTask delegateTask) {
        System.out.println("---->自定义的监听器执行了");
        if (EVENTNAME_CREATE.equals(delegateTask.getEventName())) {
            // 表示是Task的创建事件被触发了
            // 指定当前Task节点的处理人
            delegateTask.setAssignee("boge666");
        }
    }
}

        在配置流程的时候关联监听器。注意对应的事件。CREATE

        然后我们部署和启动流程后。可以看到自定义的监听器触发了:

        而且待办中的任务的处理人就是监听器中设置的信息:

四、流程变量

        流程变量可以用将数据添加到流程的运行时状态中,或者更具体地说,变量作用域中。改变实体的各种API可以用来更新这些附加的变量。一般来说,一个变量由一个名称和一个值组成。名称用于在整个流程中识别变量。例如,如果一个活动(activity)设置了一个名为 var 的变量,那么后续活动中可以通过使用这个名称来访问它。变量的值是一个 Java 对象。

1、运行时变量

        流程实例运行时的变量,存入act_ru_variable表中。在流程实例运行结束时,此实例的变量在表中删除。在流程实例创建及启动时,可设置流程变量。所有的 startProcessInstanceXXX 方法都有一个可选参数用于设置变量。例如, RuntimeService 中。

ProcessInstance startProcessInstanceById(String processDefinitionId, Map<String,Object> variables);

        也可以在流程执行中加入变量。例如,( RuntimeService ):

void setVariable(String executionId, String variableName, Object value);
void setVariableLocal(String executionId, String variableName, Object value);
void setVariables(String executionId, Map<String, ? extends Object> variables);
void setVariablesLocal(String executionId, Map<String, ? extends Object> variables);

读取变量方法:

Map<String, Object> getVariables(String executionId);
Map<String, Object> getVariablesLocal(String executionId);
Map<String, Object> getVariables(String executionId, Collection<String>
variableNames);
Map<String, Object> getVariablesLocal(String executionId, Collection<String> variableNames);
Object getVariable(String executionId, String variableName);
<T> T getVariable(String executionId, String variableName, Class<T> variableClass);

注意:由于流程实例结束时,对应在运行时表的数据跟着被删除。所以查询一个已经完结流程实例的变量,只能在历史变量表中查找。当然运行时变量我们也可以根据对应的作用域把他分为 全局变量 和 局部变量 .

1.1、全局变量

        流程变量的默认作用域是流程实例。当一个流程变量的作用域为流程实例时,可以称为 global 变量。注意:如: Global变量:userId(变量名)、zhangsan(变量值)global 变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。

案例:定义监听器

public class MySecondListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        // 获取所有的流程变量
        Map < String, Object > variables = delegateTask.getVariables();
        Set < String > keys = variables.keySet();
        for (String key: keys) {
            Object obj = variables.get(key);
            System.out.println(key + " = " + obj);
            if (obj instanceof String) {
                // 修改 流程变量的信息
                // variables.put(key,obj + ":boge3306"); 直接修改Map中的数据 达不到修改流程变量的效果
                delegateTask.setVariable(key + ":boge3306");
            }
        }
    }
}

设计流程:

        然后完成流程的部署操作:

@Test
public void test1() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.完成流程的部署操作 需要通过RepositoryService来完成
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成部署操作
    Deployment deploy = repositoryService.createDeployment()
        .addClasspathResource("flow/holiday1.bpmn20.xml")
        .name("请假流程-流程变量")
        .deploy(); // 是一个流程部署的行为 可以部署多个流程定义的
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}

        然后启动流程实例。注意在启动流程实例时我们需要指定相关的流程变量:

/**
 * 发起一个流程
 */
@Test
public void test3() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 定义流程变量信息
    Map < String, Object > map = new HashMap < > ();
    map.put("assignee1", "张三");
    map.put("assignee2", "李四");
    map.put("ass1", "变量1");
    map.put("ass2", 299);
    map.put("ass3", "湖南长沙");
    map.put("ass4", "波哥666");
    // 通过流程定义ID来启动流程 返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
        .startProcessInstanceById("holiday1:1:42503", map);
    System.out.println("processInstance.getId() = " + processInstance.getId());
    System.out.println("processInstance.getDeploymentId() = " +
        processInstance.getDeploymentId());
    System.out.println("processInstance.getDescription() = " +
        processInstance.getDescription());
}

        启动流程和触发对应的监听器,同时会在 act_ru_variable 中记录当前的变量信息:

        当然我们也可以通过 RunTimeService 来查询当前对应的流程实例的流程变量信息:

/**
 * 查询当前的流程变量信息
 */
@Test
public void testVal() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = engine.getRuntimeService();
    // 获取流程变量信息 获取某个流程实例的变量信息
    Map < String, VariableInstance > variableInstances =
        runtimeService.getVariableInstances("50008");
    Set < String > keys = variableInstances.keySet();
    for (String key: keys) {
        System.out.println(key + "=" + variableInstances.get(key));
    }
}

1.2、局部变量

        任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大, 称为 local 变量。Local 变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。Local 变量名也可以和 global 变量名相同,没有影响。我们通过RuntimeService 设置的Local变量绑定的是 executionId。在该流程中有效:

@Test
public void test4() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 待办查询 执行中的任务处理通过 TaskService来实现
    TaskService taskService = engine.getTaskService();
    RuntimeService runtimeService = engine.getRuntimeService();
    runtimeService.setVariableLocal("60004", "orderId", "100001");
    runtimeService.setVariableLocal("60004", "price", 6666);
}

        我们还可以通过TaskService来绑定本地流程变量。需要指定对应的taskId:

@Test
public void test41() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 待办查询 执行中的任务处理通过 TaskService来实现
    TaskService taskService = engine.getTaskService();
    taskService.setVariableLocal("60007", "wechat", "boge3306");
}

        然后通过测试演示我们可以看到通过TaskService绑定的Local变量的作用域只是在当前的Task有效。而通过RuntimeService绑定的Local变量作用的访问是executionId。需要注意:executionId和processInstanceId的区别。

2、历史变量

        历史变量,存入 act_hi_varinst 表中。在流程启动时,流程变量会同时存入历史变量表中;在流程结束时,历史表中的变量仍然存在。可理解为“永久代”的流程变量。

        获取已完成的、id为’XXX’的流程实例中,所有的 HistoricVariableInstances (历史变量实例),并以变量名排序。

historyService.createHistoricVariableInstanceQuery()
    .processInstanceId("XXX")
    .orderByVariableName.desc()
    .list();

五、身份服务

        在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。针对这种情况可以给任务设置多个候选人或者候选人组,可以从候选人中选择参与者来完成任务。

1、审批人

2、候选人

        一个审批节点可能有多个人同时具有审批的权限。这时我们就可以通过候选人来处理

2.1、绘制流程图

        我们定义一个简单的审批流程图。如下:

        人事审批中我们设置多个候选人来处理,分别是 张三 , 李四 , 王五。

        在总经理的位置我们统一设置几个候选人来审批。

        创建的对应的流程图的xml文件中内容如下:

2.2、部署和启动流程

        流程图设计好后我们就可以部署流程和启动流程实例了。

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.完成流程的部署操作 需要通过RepositoryService来完成
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成部署操作
    Deployment deploy = repositoryService.createDeployment()
        .addClasspathResource("flow/test5.bpmn20.xml")
        .name("候选人")
        .deploy(); // 是一个流程部署的行为 可以部署多个流程定义的
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}
/**
 * 发起一个流程
 */
@Test
public void test3() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 通过流程定义ID来启动流程 返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
        .startProcessInstanceById("holiday1:2:90003");
    System.out.println("processInstance.getId() = " + processInstance.getId());
    System.out.println("processInstance.getDeploymentId() = " +
        processInstance.getDeploymentId());
    System.out.println("processInstance.getDescription() = " +
        processInstance.getDescription());
}

        启动流程实例后。在 act_ru_task 中的审批人是空的。

        但是在对应的 act_ru_identitylink 中我们可以看到对应的候选人信息:

2.3、任务的拾取

        候选要操作我们需要通过 拾取 的行为把 候选人 转换为 处理人 .那么候选人登录后需要能查询出来他可以 拾取 的任务。

/**
 * 候选人 审批任务查询
 * 张三 登录OA系统
 */
@Test
public void test4() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    List < Task > list = taskService.createTaskQuery()
        .taskCandidateUser("张三") // 根据候选人查询审批任务
        .list();
    if (list != null && list.size() > 0) {
        for (Task task: list) {
            System.out.println("task.getId() = " + task.getId());
            //taskService.complete(task.getId());
        }
    }
}
/**
 * 待办任务的 拾取 操作
 * 从候选人 --> 处理人
 * 一个任务如果被拾取后。其他的候选人就查询不到改任务信息了
 */
@Test
public void test5() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    List < Task > list = taskService.createTaskQuery()
        .taskCandidateUser("王五") // 根据候选人查询审批任务
        .list();
    if (list != null && list.size() > 0) {
        for (Task task: list) {
            // 李四 拾取了 这个任务的审批权限 --> 变成了这个任务的审批人
            taskService.claim(task.getId(), "王五");
        }
    }
}

2.4、任务的归还

        拾取任务后如果不想操作那么可以归还任务。

/**
 * 归还:拾取的用户 不审批了。就放弃审批人的操作
 * 其他的候选人可以重新拾取人了
 */
@Test
public void test6() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    List < Task > list = taskService.createTaskQuery()
        .taskCandidateOrAssigned("李四") // 根据 审批人或者 候选人来查询待办任务
        .list();
    if (list != null && list.size() > 0) {
        for (Task task: list) {
            // System.out.println("task.getId() = " + task.getId());
            // 归还操作的本质其实就是设置审批人为空
            taskService.unclaim(task.getId());
        }
    }
}

3、候选人组

        当候选人很多的情况下,我们可以分组来处理。先创建组,然后把用户分配到这个组中。

3.1、流程图绘制

        然后在设置审批人的时候通过候选人组来设定:

        对应的流程图xml中的定义信息:

3.2、流程操作

        流程操作包括 部署 , 启动 , 拾取 , 归还 和 交接 等操作。

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.完成流程的部署操作 需要通过RepositoryService来完成
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成部署操作
    Deployment deploy = repositoryService.createDeployment()
        .addClasspathResource("flow/test6.bpmn20.xml")
        .name("候选人组")
        .deploy(); // 是一个流程部署的行为 可以部署多个流程定义的
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}
/**
 * 发起一个流程
 */
@Test
public void test3() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 通过流程定义ID来启动流程 返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
        .startProcessInstanceById("holiday1:3:97503");
    System.out.println("processInstance.getId() = " + processInstance.getId());
    System.out.println("processInstance.getDeploymentId() = " +
        processInstance.getDeploymentId());
    System.out.println("processInstance.getDescription() = " +
        processInstance.getDescription());
}
/**
 * 候选人组:
 * 具体的用户。比如 张三 登录了系统
 * 查询张三对应的 组 根据 组来查询待办的任务
 */
@Test
public void test4() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    String group = "销售部"; // 根据当前登录用户查询到的
    List < Task > list = taskService.createTaskQuery()
        .taskCandidateGroup(group)
        .list();
    if (list != null && list.size() > 0) {
        for (Task task: list) {
            System.out.println("task.getId() = " + task.getId());
            //taskService.complete(task.getId());
        }
    }
}
/**
 * 待办任务的 拾取 操作
 * 从候选人 --> 处理人
 * 一个任务如果被拾取后。其他的候选人就查询不到改任务信息了
 */
@Test
public void test5() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    String group = "销售部"; // 根据当前登录用户查询到的
    List < Task > list = taskService.createTaskQuery()
        .taskCandidateGroup(group) // 根据组来查询
        .list();
    if (list != null && list.size() > 0) {
        for (Task task: list) {
            // 张三1 拾取了 这个任务的审批权限 --> 变成了这个任务的审批人
            taskService.claim(task.getId(), "张三1");
        }
    }
}
/**
 * 归还:拾取的用户 不审批了。就放弃审批人的操作
 * 其他的候选人可以重新拾取人了
 */
@Test
public void test6() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    String group = "销售部"; // 根据当前登录用户查询到的
    List < Task > list = taskService.createTaskQuery()
        .taskAssignee("张三1")
        .list();
    if (list != null && list.size() > 0) {
        for (Task task: list) {
            // System.out.println("task.getId() = " + task.getId());
            // 归还操作的本质其实就是设置审批人为空
            taskService.unclaim(task.getId());
        }
    }
}
/**
 * 获取用户审批权限的用户没有时间审批了
 * 但是他也可以不用归还而是做任务的交接。把这个任务让另一个人来审批
 */
@Test
public void test8() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    String group = "销售部"; // 根据当前登录用户查询到的
    List < Task > list = taskService.createTaskQuery()
        .taskAssignee("张三1")
        .list();
    if (list != null && list.size() > 0) {
        for (Task task: list) {
            // System.out.println("task.getId() = " + task.getId());
            // 任务交接
            taskService.setAssignee(task.getId(), "李四1");
        }
    }
}
/**
 * 任务审批
 */
@Test
public void test7() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    taskService.complete("92505");
}

        对应的流程中的 候选人组 信息同样记录在 act_ru_identitylink。

六、网关说明

        网关 可控制流程的执行流向,常用于拆分或合并复杂的流程场景。在Activiti7中,有以下几种类型的网关:

  1. 排他网关(Exclusive Gateway):用于在流程中进行条件判断,根据不同的条件选择不同的分支路径。只有满足条件的分支会被执行,其他分支会被忽略。
  2. 并行网关(Parallel Gateway):用于将流程分成多个并行的分支,这些分支可以同时执行。当所有分支都执行完毕后,流程会继续向下执行。
  3. 包容网关(Inclusive Gateway):用于根据多个条件的组合情况选择分支路径。可以选择满足任意一个条件的分支执行,或者选择满足所有条件的分支执行。
  4. 事件网关(Event Gateway):用于根据事件的触发选择分支路径。当指定的事件触发时,流程会选择对应的分支执行。

        这些网关可以根据实际需求灵活地组合使用,以实现不同的流程控制逻辑。Activiti7提供了直观的图形化界面,用户可以通过拖拽和连接网关来定义流程的分支和合并。同时,Activiti7还提供了丰富的API和扩展点,方便开发人员进行二次开发和定制。接下来我们分别介绍下各种网关的应用。

1、排他网关

        排他网关(exclusive gateway)(也叫异或网关 XOR gateway,或者更专业的,基于数据的排他网关exclusive data-based gateway),用于对流程中的决策建模。当执行到达这个网关时,会按照所有出口顺序流定义的顺序对它们进行计算。选择第一个条件计算为true的顺序流(当没有设置条件时,认为顺序流为true)继续流程。

        排他网关用内部带有’X’图标的标准网关(菱形)表示,'X’图标代表异或 的含义。请注意内部没有图标的网关默认为排他网关。BPMN 2.0规范不允许在同一个流程中混合使用有及没有X的菱形标志。

        然后需要添加对应的 排他网关 的流转条件:

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.完成流程的部署操作 需要通过RepositoryService来完成
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成部署操作
    Deployment deploy = repositoryService.createDeployment()
        .addClasspathResource("flow/gateway1.bpmn20.xml")
        .name("排他网关")
        .deploy(); // 是一个流程部署的行为 可以部署多个流程定义的
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}
/**
 * 发起一个流程
 */
@Test
public void test3() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 通过流程定义ID来启动流程 返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
        .startProcessInstanceById("gateway1:1:3");
    System.out.println("processInstance.getId() = " + processInstance.getId());
    System.out.println("processInstance.getDeploymentId() = " +
        processInstance.getDeploymentId());
    System.out.println("processInstance.getDescription() = " +
        processInstance.getDescription());
}
/**
 * 任务审批
 */
@Test
public void test4() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    List < Task > list = taskService.createTaskQuery().taskAssignee("张三").list();
    Map < String, Object > map = new HashMap < > ();
    // 绑定对应的请假天数
    map.put("days", 1);
    for (Task task: list) {
        taskService.complete(task.getId(), map);
    }
}

2、并行网关

        并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:

  • fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支
  • join汇聚: 所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。

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

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.完成流程的部署操作 需要通过RepositoryService来完成
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成部署操作
    Deployment deploy = repositoryService.createDeployment()
        .addClasspathResource("flow/gateway2.bpmn20.xml")
        .name("排他网关")
        .deploy(); // 是一个流程部署的行为 可以部署多个流程定义的
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}
/**
 * 发起一个流程
 */
@Test
public void test3() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 通过流程定义ID来启动流程 返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
        .startProcessInstanceById("gateway2:1:17503");
    System.out.println("processInstance.getId() = " + processInstance.getId());
    System.out.println("processInstance.getDeploymentId() = " +
        processInstance.getDeploymentId());
    System.out.println("processInstance.getDescription() = " +
        processInstance.getDescription());
}
/**
 * 任务审批
 */
@Test
public void test4() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    List < Task > list = taskService.createTaskQuery().taskAssignee("boss").list();
    for (Task task: list) {
        taskService.complete(task.getId());
    }
}

        在并行网关中我们需要注意的是 执行实例 的概念。

  • 主流程实例:流程启动就会维护的一条记录, ACT_RU_EXECUTION 中 parent_id_ 为null的记录
  • 子流程实例:流程的每一步操作。都会更新子流程实例,表示当前流程的执行进度。如果进入的是并行网关。案例中的网关会产生3个子流程实例和一个主流程实例。

3、包容网关

        包含网关可以看做是排他网关和并行网关的结合体。 和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。包含网关的功能是基于进入和外出顺序流的:

  • 分支: 所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
  • 汇聚:所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.完成流程的部署操作 需要通过RepositoryService来完成
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成部署操作
    Deployment deploy = repositoryService.createDeployment()
        .addClasspathResource("flow/gateway3.bpmn20.xml")
        .name("排他网关")
        .deploy(); // 是一个流程部署的行为 可以部署多个流程定义的
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}
/**
 * 发起一个流程
 */
@Test
public void test3() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 通过流程定义ID来启动流程 返回的是流程实例对象
    ProcessInstance processInstance = runtimeService
        .startProcessInstanceById("gateway3:1:35003");
    System.out.println("processInstance.getId() = " + processInstance.getId());
    System.out.println("processInstance.getDeploymentId() = " +
        processInstance.getDeploymentId());
    System.out.println("processInstance.getDescription() = " +
        processInstance.getDescription());
}
/**
 * 任务审批
 */
@Test
public void test4() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    List < Task > list = taskService.createTaskQuery().taskAssignee("boss").list();
    Map < String, Object > map = new HashMap < > ();
    map.put("days", 5);
    for (Task task: list) {
        taskService.complete(task.getId(), map);
    }
}

4、事件网关

        事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。与此同时,会为每个外出顺序流创建相对的事件订阅。

        事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的"执行", 相反它们让流程引擎去决定执行到事件网关的流程需要订阅哪些事件。 要考虑以下条件:

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

七、事件篇

        事件 (event)通常用于为流程生命周期中发生的事情建模。事件总是图形化为圆圈。在BPMN 2.0中,有两种主要的事件分类:捕获(catching) 与抛出(throwing) 事件。

  • 捕获: 当流程执行到达这个事件时,会等待直到触发器动作。触发器的类型由其中的图标,或者说XML中的类型声明而定义。捕获事件与抛出事件显示上的区别,是其内部的图标没有填充(即是白色的)。
  • 抛出: 当流程执行到达这个事件时,会触发一个触发器。触发器的类型,由其中的图标,或者说XML中的类型声明而定义。抛出事件与捕获事件显示上的区别,是其内部的图标填充为黑色。

1、定时器事件

        定时器事件是一种在特定时间触发的事件。在Activiti中,可以通过定时器事件来实现定时执行某个任务或者触发某个流程实例,具体包括定时器启动事件,定时器捕获中间件事件,定时器边界事件,在很多的业务场景中。

1.1、定时器开始事件

        定时器启动事件(timer start event)在指定时间创建流程实例。在流程只需要启动一次,或者流程需要在特定的时间间隔重复启动时,都可以使用。在使用时我们需要注意如下几个点:

  1. 子流程不能有定时器启动事件。
  2. 定时器启动事件,在流程部署的同时就开始计时。不需要调用startProcessInstanceByXXX就会在时间启动。调用startProcessInstanceByXXX时会在定时启动之外额外启动一个流程。
  3. 当部署带有定时器启动事件的流程的更新版本时,上一版本的定时器作业会被移除。这是因为通常并不希望旧版本的流程仍然自动启动新的流程实例。
  4. asyncExecutorActivate:需要设置为 true ,否则定时器不会生效,因为这块需要开启异步任务。

        定时器启动事件,用其中有一个钟表图标的 圆圈 来表示。我们通过具体案例来介绍:

        部署流程后会在我们设置的时间开启一个流程实例,在没有到达定时时间的时候在 act_ru_timer_job 可以看到我们的定时任务信息。

        时间到达后会触发 定时开启 事件:

        定时器开始事件除了上面的指定固定时间启动外我们还可以通过循环和持续时间来处理:

  • timeDate :指定一个具体的日期和时间,例如 2022-01-01T00:00:00
  • timeCycle :指定一个重复周期,例如 R/PT1H 表示每隔1小时触发一次
  • timeDuration :指定一个持续时间,例如 PT2H30M 表示持续2小时30分钟

        然后我们增加一个重复周期的案例。这块我们可以通过自动任务来演示案例:

        在自动任务这块绑定了一个 JavaDelegate 来处理:

public class MyJavaDelegate implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("自动任务执行了..." + LocalDateTime.now());
    }
}

        然后部署流程测试,在 act_ru_timer_job 查看定义信息:

         可以看到执行了3次。都间隔了30秒。

1.2、 定时器中间事件

        在开始事件和结束事件之间发生的事件称为 中间事件 ,定时器中间捕获事件指在流程中将一个定时器作为独立的节点来运行,是一个捕获事件。当流程流转到定时器中间捕获事件时,会启动一个定时器,并一直等待触发,只有到达指定时间定时器才被触发。

        当我们审批通过 申请出库 后,等待一分钟触发定时器。然后会进入到 出库处理 。同时在触发前在act_ru_timer_job 中可以查询到对应的任务信息。

1.3、定时器边界事件

        当某个用户任务或者子流程在规定的时间后还没有执行。那么我们就可以通过定时器边界事件来触发执行特定的处理流程。

        注意在定时器边界事件配置了cancelActivity属性,用于说明该事件是否为中断事件。cancelActivity属性值默认为true,表示它是边界中断事件,当该边界事件触发时,它所依附的活动实例被终止,原有的执行流会被中断,流程将沿边界事件的外出顺序流继续流转。如果将其设置为false,表示它是边界非中断事件,当边界事件触发时,则原来的执行流仍然存在,所依附的活动实例继续执行,同时也执行边界事件的外出顺序流。

        部署后启动流程。那么会进入到 合同审批-总经理审判 的这个节点。同时在 act_ru_timer_job 中可以看到这个边界事件的定义。

        等待了一分钟定时器边界事件触发。我们可以在控制台中看到 JavaDelegate 任务的执行。

        因为这块的边界事件我们定义的是 非中断 。所以用户任务还在,只是在边界事件中触发了服务任务。来通知用户审批处理。

        然后 总经理 审批通过。后会进入到财务审批的节点:

        同时会开启我们的中间边界事件。 act_ru_timer_job 中会生成对应的记录。

        同时 act_ru_task 中的审批是 财务审核 。

        等待一分钟后。因为边界事件设置的是 中断 类型。所以触发后 财务审核 终止。只剩下触发后的新的出口中的 财务实习审批

2、消息事件

        消息事件(message event),是指引用具名消息的事件。消息具有名字与载荷。与信号不同,消息事件只有一个接收者。

2.1、开始事件

        消息开始事件 ,也就是我们通过接收到某些消息后来启动流程实例,比如接收到了一封邮件,一条短信等,具体通过案例来讲解

做消息的定义:

        在消息开始事件中我们需要绑定上面定义的消息。

        然后就可以部署流程:

/**
 * 流程部署操作
 */
@Test
public void test1() {
    // 1.获取ProcessEngine对象
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 2.完成流程的部署操作 需要通过RepositoryService来完成
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 3.完成部署操作
    Deployment deploy = repositoryService.createDeployment()
        .addClasspathResource("flow/event-message-start.bpmn20.xml")
        .name("消息启动事件")
        .deploy(); // 是一个流程部署的行为 可以部署多个流程定义的
    System.out.println(deploy.getId());
    System.out.println(deploy.getName());
}

        部署完流程后。消息启动事件会在 act_ru_event_subscr 中记录我们的定义信息。

        然后就可以发送相关的消息。来激活该流程实例, 注意 :消息的名称我们不要使用 驼峰命名法 来定义。

/**
 * 发送消息。触发流程
 */
@Test
public void test3() throws InterruptedException {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = engine.getRuntimeService();
    // 发送消息 发送的消息应该是具体的消息的名称而不应该是id
    runtimeService.startProcessInstanceByMessage("msg01");
    TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}

        可以看到消息开始事件触发了。

2.2、中间事件

        消息中间事件就是在流程运作中需要消息来触发的场景,案例演示, 自动流程1 处理完成后,需要接收特定的消息之后才能进入到 自动流程2。

        然后在消息中间事件的图标中我们需要绑定刚刚定义的消息:

        部署启动和审批流程后进入到消息中间事件的节点:

        然后发送消息触发消息中间事件。

/**
 * 触发消息中间事件
 */
@Test
public void test5() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = engine.getRuntimeService();
    // 查询出当前的 执行实例的 编号
    Execution execution = runtimeService.createExecutionQuery()
        .processInstanceId("110001")
        .onlyChildExecutions()
        .singleResult();
    runtimeService.messageEventReceived("msg02", execution.getId());
}

        然后进入到了 用户任务2 的审批。说明触发了。

2.3、边界事件

        消息边界事件同样的针对是用户节点在消息触发前如果还没有审批。就会触发消息事件的处理逻辑。同样我们通过具体的案例来介绍。

定义两个消息:

        部署流程、启动流程后进入到 用户任务1 后。在 act_ru_event_subscr 表中就可以看到对应的消息事件,这时我们就可以发送相关的消息。

public void test5() {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = engine.getRuntimeService();
    runtimeService.messageEventReceived("msg03", "170005");
}

        然后在控制台就可以看到 JavaDelegate 被执行调用了:

        这里我们需要注意当前的边界事件是非中断的。所以还是需要 用户任务1 来审批推进,审批后会绑定 msg04

        当我们触发了第二个消息边界事件。那么任务会进入到 用户任务3 。同时 用户任务2 被中断了。然后 msg04 的任务也结束了。

3、错误事件

        错误事件 可以用做一个流程的开始事件或者作为一个任务或者子流程的边界事件,错误事件没有提供作用中间事件的功能,这一点和前面介绍的定时器事件和消息事件还有区别的。在错误事件中提供了错误结束事件 。我们在案例中会详细的讲解

3.1、开始事件

        错误开始事件(error start event)可以触发一个事件 子流程 ,且总是在另外一个流程异常结束时触发。BPMN 2.0规定了错误开始事件只能在事件子流程中被触发,不能在其他流程中被触发,包括顶级流程、嵌套子流程和调用活动。错误启动事件不能用于启动流程实例。错误启动事件总是 中断 。我们通过案例来介绍:

        在对应的 自动任务1 中我们需要显示的抛出异常信息:

/**
 * 自定义的委托类
 */
public class MyFirstDelegate implements JavaDelegate {
    /**
     * 回调方法
     * @param execution
     */
    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("服务任务执行了..." + LocalDateTime.now().toString());
        // 抛出错误 触发 子流程中的错误开始事件
        throw new BpmnError("error01");
    }
}

        那么部署完流程后。然后发起一个新的流程就会走 事件子流程 中的逻辑了。错误开始事件可以在如下的场景中使用:

  1. 输入验证失败:当用户提交工作流启动请求时,需要对输入的数据进行验证。如果数据不符合预期的格式或规则,可以使用错误开始事件来捕获并处理验证失败的情况。
  2. 权限验证失败:在某些情况下,只有特定的用户或用户组才能启动某个工作流。当非授权用户尝试启动工作流时,可以使用错误开始事件来捕获并处理权限验证失败的情况
  3. 前置条件不满足:在工作流启动之前,可能需要满足一些前置条件,例如某个数据已经存在或某个服务可用。如果前置条件不满足,可以使用错误开始事件来捕获并处理这种情况。
  4. 数据源异常:在工作流启动过程中,可能需要从外部数据源获取数据。如果数据源出现异常导致无法获取数据,可以使用错误开始事件来捕获并处理数据源异常的情况。

        总的来说,错误开始事件可以用于捕获工作流启动时可能出现的各种错误情况,并根据具体的业务需求进行相应的处理。

3.2、边界事件

        当某个任务发生错误时,可以通过错误边界事件来捕获并处理该错误,以保证流程的正常执行。错误边界事件 可以在流程中的任务节点上定义,并与该任务节点关联。当任务节点执行过程中发生错误时,错误边界事件会被触发,并执行相应的处理逻辑,如发送错误通知、重新分配任务、跳转到其他节点等。

        错误边界事件可以捕获多种类型的错误,如异常、超时、网络故障等。通过使用错误边界事件,可以增加流程的容错性,并提供更好的错误处理机制,保证流程的稳定性和可靠性。

        需要注意 的是,错误边界事件只能与任务节点关联,而不能与其他类型的节点(如网关、开始节点、结束节点)关联。此外,在设计流程时,需要准确定义错误边界事件的触发条件和处理逻辑,以确保错误能够被正确捕获和处理。具体我们通过案例来演示。

        案例中我们把错误边界事件绑定在了普通的用户任何和一个子流程上。如果对应的节点抛出的相关的错误。对应的边界事件就可以被触发。错误边界事件可能的应用场景:

  1. 任务执行失败:当某个任务执行失败时,可以使用错误边界事件来捕获该异常,并执行一些恢复操作,例如重新分配任务给其他用户或记录错误信息。
  2. 子流程异常:当子流程执行过程中发生异常时,可以使用错误边界事件捕获该异常,并执行一些补救措施,例如回退到上一个节点或重新启动子流程。
  3. 超时处理:当某个任务或子流程在规定的时间内没有完成时,可以使用错误边界事件来捕获超时异常,并执行相应的超时处理逻辑,例如发送提醒邮件或自动终止流程。
  4. 数据校验失败:在某些场景下,需要对流程中的数据进行校验,如果校验失败,则可以使用错误边界事件来捕获校验异常,并进行相应的处理,例如返回错误信息给用户或中止流程

        总之,错误边界事件可以帮助我们在流程执行过程中及时捕获并处理异常情况,提高流程的可靠性和稳定性。

3.3、结束事件

        在Activiti中, 错误结束事件 (Error End Event)是一个用于标记流程实例在特定错误条件下结束的节点。当流程实例执行到错误结束事件时,流程实例将立即终止执行,并且流程实例的状态将被标记为“错误结束”。

        错误结束事件可以与错误边界事件(Error Boundary Event)结合使用,用于在流程中捕获和处理特定的错误。当错误边界事件触发时,流程会跳转到与错误边界事件关联的错误结束事件,从而使流程实例结束。

        错误结束事件可以配置一个错误代码,用于标识特定的错误类型。在流程定义中,可以定义多个错误结束事件,每个事件可以有不同的错误代码。当流程实例执行到错误结束事件时,可以根据错误代码进行相应的处理,例如记录日志、发送通知等。

        错误结束事件可以用于处理各种错误情况,例如系统异常、业务规则异常等。通过使用错误结束事件,可以使流程能够在错误发生时进行合理的处理,提高系统的可靠性和稳定性。

        总之,错误结束事件是Activiti中的一个节点,用于标记流程实例在特定错误条件下结束。它可以与错误边界事件结合使用,用于捕获和处理特定的错误。通过使用错误结束事件,可以实现对流程中各种错误情况的处理和管理。

        当子流程中的支付失败的情况下会触发错误结束事件。该事件会被错误边界事件捕获。错误边界事件捕获后会重新发起支付的流程。这就是我们介绍的案例流程。

4、信号事件

        信号事件是Activiti中的一种事件类型,用于在流程执行过程中通知其他流程实例或任务实例。

        信号事件是一种 全局事件 ,可以在任何流程实例或任务实例中触发和捕获。当一个流程实例或任务实例触发了一个信号事件,其他等待捕获相同信号的流程实例或任务实例将被唤醒并继续执行。信号事件可以用于以下场景:

  1. 并行流程实例之间的协作:当一个流程实例需要与其他并行流程实例进行协作时,可以触发一个信号事件来通知其他流程实例执行相应的任务。
  2. 动态流程控制:当流程的执行需要根据外部条件进行动态调整时,可以使用信号事件来触发相应的流程变化。
  3. 异常处理:当发生异常情况时,可以触发一个信号事件来通知其他流程实例或任务实例进行异常处理。

使用信号事件需要以下几个步骤:

  1. 定义信号事件:在流程定义中定义一个信号事件,指定信号的名称和其他属性。
  2. 触发信号事件:在流程实例或任务实例中触发一个信号事件。
  3. 捕获信号事件:在其他流程实例或任务实例中捕获相同名称的信号事件。
  4. 响应信号事件:在捕获的信号事件中定义相应的处理逻辑,例如执行任务或流程变化。

        信号事件我们可以分为 开始事件 、 中间捕获事件 、 中间抛出事件 、 边界事件 ,具体的介绍如下:

4.1、开始事件

  • 启动事件是一个特殊的信号事件,用于在流程启动时触发。
  • 当流程启动时,如果存在一个启动事件,并且该事件匹配到了被触发的信号,流程将会被启动。
  • 启动事件可以用于实现流程启动前的条件判断,例如当某个条件满足时,才允许启动流程。

定义信号信息:

        在定义信号的时候有一个 Scope 属性可以设置为Global或processInstance:

  • Global:全局范围的信号定义,表示可以在任何流程实例中触发和捕获信号。当一个信号事件被触发时,所有等待捕获该信号的节点都会被唤醒。
  • processInstance:流程实例范围的信号定义,表示只能在当前流程实例中触发和捕获信号。当一个信号事件被触发时,只有等待在当前流程实例中捕获该信号的节点会被唤醒。

        而当前的启动事件是在流程实例启动时触发的事件,用于执行一些初始化操作。启动事件可以在流程定义的开始节点上定义,并在开始节点上设置事件类型为start。启动事件只有一个全局范围的信号定义,即scope属性只能设置为Global。当一个启动事件被触发时,所有等待捕获该信号的节点都会被唤醒。然后在 信号开始节点 中绑定刚刚定义的信号:

        接下就可以部署流程。然后通过 信号 来启动对应的流程实例了。

/**
 * 通过信号启动一个新的流程
 */
@Test
public void test2() throws InterruptedException {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 通过发送信号。触发对应订阅了该信号的流程
    runtimeService.signalEventReceived("signal1");
    TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}

        执行上面的方法就可以看到 act_ru_task 中对应的就有了一条 用户任务 的待办信息。

        同时对应的信号事件存储在了 act_ru_event_subscr 中。

        当然触发该事件的方式并不仅仅只有这一种方案还有:

  • 由流程中的信号中间抛出事件抛出信号,所有订阅了该信号的信号开始事件所在的流程定义都会被启动;
  • 作为普通开始事件,启动流程。

        事件抛出我们在后面的案例中讲解。而作为普通的开始事件。直接执行下面的启动代码即可:

// 通过流程定义ID来启动流程 返回的是流程实例对象
ProcessInstance processInstance = runtimeService
    .startProcessInstanceById("event-signal-start1:1:232503");

4.2、中间事件

        信号中间事件 分为 捕获事件 和 抛出事件 .当流程流转到信号中间捕获事件时会中断并等待触发,直到接收到相应的信号后沿信号中间捕获事件的外出顺序流继续流转。信号事件默认是全局的,与其他事件(如错误事件)不同,其信号不会在捕获之后被消费。如果存在多个引用了相同信号的事件被激活,即使它们不在同一个流程实例中,当接收到该信号时,这些事件也会被一并触发。具体我们通过案例来讲解。

        消息定义我们用的scope是 processInstance。也就是只在当前流程实例生效。部署运行后可以看具体的效果。

        启动流程后在 act_ru_event_subscr 中记录了信号事件的相关信息。同时记录了作用域信息。

        然后我们审批用户节点进入到 抛出信号事件 的节点。

审批任务完成

/**
 * 任务审批
 */
@Test
public void test7() throws Exception {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = engine.getTaskService();
    taskService.complete("245007");
    Thread.sleep(100000000);
}

        可以看到 自动任务2 执行了,

        同时因为 scope 是 processInstance 在 act_ru_event_subscr 中记录的信号事件也被消费了。如果是 global 则该信号事件还是继续监听。

4.3、边界事件

        信号边界事件 会捕获与其信号事件定义引用的信号具有相同信号名称的信号。当流程流转到信号边界事件依附的流程活动(如用户任务、子流程等)时,工作流引擎会创建一个捕获事件,在其依附的流程活动的生命周期内等待一个抛出信号。该信号可以由 信号中间抛出事件 抛出或由 API触发 。信号边界事件被触发后流程会沿其外出顺序流继续流转。如果该边界事件设置为 中断 ,则依附的流程活动将被终止。

        部署流程后启动流程那么具有的相关的数据 act_ru_event_subscr 表中记录的信号事件,

        然后流程会进入到 用户任务1 节点。当然可以正常的审批。还有就是可以发布相关的信号事件。在当前的环境下我们可以通过 runtimeService 的API来触发。

/**
 * 通过信号启动事件
 * 发起一个流程
 * 1.通过runtimeService中提供的API来发送信号
 * 2.通过其他流程实例中的信号中间抛出事件来触发
 * 3.作为普通的流程实例来启动即可
 */
@Test
public void test2() throws InterruptedException {
    ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
    // 发起流程 需要通过 runtimeService来实现
    RuntimeService runtimeService = engine.getRuntimeService();
    // 通过runtimeService的API来发布信号
    runtimeService.signalEventReceived("signal02");
    TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}

        同时因为是 非中断 的,所以 用户任务1 还在。接下来我们就需要做审批操作。审批通过就会进入到 用户任务2 。

        进入到 用户任务2 后。继续审批就会触发 信号抛出事件 ,然后被 信号边界 事件捕获。

5、其他事件

5.1、终止结束事件

        终止结束事件 也称为 中断结束事件 ,主要是对流程进行终止的事件,可以在一个复杂的流程中,如果某方想要提前中断这个流程,可以采用这个事件来处理,可以在并行处理任务中。如果你是在流程实例层处理,整个流程都会被中断,如果是在子流程中使用,那么当前作用和作用域内的所有的内部流程都会被终止。具体还是通过两个案例来给大家介绍:

第一个案例:终止结束事件是在主流程中触发的场景

        设置终止结束事件。里面有一个 terminateAll 默认为false。含义是当 终止结束事件 在多实例或者嵌套的子流程中。那么不会终止整个流程。如果设置为true那么不管是否嵌套都会终止整个的流程实例。

        过案例的演示。我们发下在 用户任务1 和 用户任何2 没有审批的情况下当 用户任务3 审批通过后同时flag 设置为 false 的情况下触发了 终止结束事件 那么整个流程实例都被终止了。另一个流程案例:在子流程中触发 终止结束事件:

        在本案例中我们可以通过 terminateAll 属性非常方便的控制终止的范围。

5.2、取消结束事件

        取消结束事件(cancel end event)只能与BPMN事务子流程(BPMN transaction subprocess)一起使用。当到达取消结束事件时,会抛出取消事件,且必须由取消边界事件(cancel boundary event)捕获。取消边界事件将取消事务,并触发补偿(compensation)。具体通过案例来讲解:

注意 :结束取消事件我们只能在事务子流程中使用

        在流程设计器中没有直接提供 事务子流程 的图标,我们需要通过普通的子流程来设置事务的属性即可:

        然后就是补偿的任务我们需要勾选可补偿的选项:

        部署任务后我们再继续启动流程实例的时候。出现了如下的错误:

        检查xml文件中发现少了该属性。那么我们需要收到的加上。

        然后做正常的审批。触发 取消结束事件 ,结合上面的流程图我们可以看到如下的效果。

        补充任务触发。可以看到控制台的日志信息:

        用户任务4在 act_ru_task 中可以看到对应的记录:

5.3、补偿事件

        在Activiti中,补偿事件(Compensation Event)是一种用于处理流程中发生异常或错误的特殊事件。当流程中的某个任务或活动发生错误或无法继续执行时,补偿事件可以被触发来回滚或修复之前已经完成的任务或活动。

        补偿事件通常与错误边界事件(Error Boundary Event)结合使用。错误边界事件是在流程中的任务或活动周围设置的捕获异常的事件。当任务或活动发生异常时,错误边界事件将被触发,进而触发相应的补偿事件。

        补偿事件可以执行一系列的补偿操作,包括撤销之前已经完成的任务、还原数据、发送通知等。补偿操作的具体步骤和逻辑可以在流程定义中定义,并且可以使用Java代码或脚本来实现。补偿事件的触发和执行是自动完成的,无需人工干预。一旦补偿事件被触发,Activiti引擎会自动查找相应的补偿事件,并按照定义的补偿操作进行执行。

        通过使用补偿事件,可以有效地处理流程中的异常情况,提高流程的稳定性和容错性。补偿事件可以帮助流程在发生错误时自动进行修复,确保流程能够正常完成。

<?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/test">
	<error id="payFail"errorCode="payFail" >
	</error>
	<process id="myProcess"name="My process"isExecutable="true">
		<startEvent id="startevent1"name="开始事件">
		</startEvent>
		<parallelGateway id="parallelgateway1"name="并行网关">
		</parallelGateway>
		<sequenceFlow id="flow1"sourceRef="startevent1"targetRef="parallelgateway1">
		</sequenceFlow>
		<serviceTask id="servicetask1" name="预订机票"
activiti:class="com.bobo.delegate.MyTwoDelegate">
		</serviceTask>
		<serviceTask id="servicetask2" name="微信支付"
activiti:class="com.bobo.delegate.MyOneDelegate">
		</serviceTask>
		<userTask id="usertask1"name="人工出票" activiti:assignee="zhangsan">
		</userTask>
		<sequenceFlow id="flow2"sourceRef="servicetask1"targetRef="usertask1">
		</sequenceFlow>
		<parallelGateway id="parallelgateway2"name="Parallel Gateway">
		</parallelGateway>
		<sequenceFlow id="flow3"sourceRef="usertask1"targetRef="parallelgateway2">
		</sequenceFlow>
		<sequenceFlow id="flow4"sourceRef="parallelgateway1"targetRef="servicetask1">
		</sequenceFlow>
		<sequenceFlow id="flow5"sourceRef="parallelgateway1"targetRef="servicetask2">
		</sequenceFlow>
		<sequenceFlow id="flow6"sourceRef="servicetask2"targetRef="parallelgateway2">
		</sequenceFlow>
		<serviceTask id="servicetask3" name="取消预订" isForCompensation="true"
activiti:class="com.bobo.delegate.MyThreeDelegate">
		</serviceTask>
		<boundaryEvent id="boundarycompensation1" name="补偿边界事件"
attachedToRef="servicetask1" cancelActivity="true">
			<compensateEventDefinition>
			</compensateEventDefinition>
		</boundaryEvent>
		<boundaryEvent id="boundaryerror1" name="错误边界事件"
attachedToRef="servicetask2">
			<errorEventDefinition errorRef="payFail">
			</errorEventDefinition>
		</boundaryEvent>
		<intermediateThrowEvent id="compensationintermediatethrowevent1" name="补偿抛出
中间事件">
			<compensateEventDefinition>
			</compensateEventDefinition>
		</intermediateThrowEvent>
		<sequenceFlow id="flow7" sourceRef="boundaryerror1"
targetRef="compensationintermediatethrowevent1">
		</sequenceFlow>
		<endEvent id="endevent1"name="End">
		</endEvent>
		<sequenceFlow id="flow8" sourceRef="compensationintermediatethrowevent1"
targetRef="endevent1">
		</sequenceFlow>
		<endEvent id="endevent2"name="End">
		</endEvent>
		<sequenceFlow id="flow9"sourceRef="parallelgateway2"targetRef="endevent2">
		</sequenceFlow>
		<association id="association1" sourceRef="boundarycompensation1"
targetRef="servicetask3" associationDirection="None">
		</association>
	</process>
	<bpmndi:BPMNDiagram id="BPMNDiagram_myProcess">
		<bpmndi:BPMNPlane bpmnElement="myProcess" id="BPMNPlane_myProcess">
			<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
				<omgdc:Bounds height="35.0" width="35.0" x="160.0" y="360.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="parallelgateway1"
id="BPMNShape_parallelgateway1">
				<omgdc:Bounds height="40.0" width="40.0" x="380.0" y="357.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="servicetask1" id="BPMNShape_servicetask1">
				<omgdc:Bounds height="55.0" width="105.0" x="580.0" y="220.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="boundarycompensation1"
id="BPMNShape_boundarycompensation1">
				<omgdc:Bounds height="30.0" width="30.0" x="650.0" y="270.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="servicetask2" id="BPMNShape_servicetask2">
				<omgdc:Bounds height="55.0" width="105.0" x="580.0" y="450.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="boundaryerror1"
id="BPMNShape_boundaryerror1">
				<omgdc:Bounds height="30.0" width="30.0" x="650.0" y="490.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
				<omgdc:Bounds height="55.0" width="105.0" x="820.0" y="220.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="parallelgateway2"
id="BPMNShape_parallelgateway2">
				<omgdc:Bounds height="40.0" width="40.0" x="1140.0" y="336.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="servicetask3" id="BPMNShape_servicetask3">
				<omgdc:Bounds height="55.0" width="105.0" x="830.0" y="336.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="compensationintermediatethrowevent1"
id="BPMNShape_compensationintermediatethrowevent1">
				<omgdc:Bounds height="35.0" width="35.0" x="740.0" y="590.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
				<omgdc:Bounds height="35.0" width="35.0" x="820.0" y="590.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="endevent2" id="BPMNShape_endevent2">
				<omgdc:Bounds height="35.0" width="35.0" x="1225.0" y="339.0">
				</omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
				<omgdi:waypoint x="195.0" y="377.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="380.0" y="377.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
				<omgdi:waypoint x="685.0" y="247.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="820.0" y="247.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
				<omgdi:waypoint x="925.0" y="247.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="1160.0" y="247.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="1160.0" y="336.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
				<omgdi:waypoint x="400.0" y="357.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="400.0" y="247.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="580.0" y="247.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
				<omgdi:waypoint x="400.0" y="397.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="400.0" y="477.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="580.0" y="477.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
				<omgdi:waypoint x="685.0" y="477.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="1160.0" y="477.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="1160.0" y="376.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
				<omgdi:waypoint x="665.0" y="520.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="664.0" y="607.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="740.0" y="607.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
				<omgdi:waypoint x="775.0" y="607.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="820.0" y="607.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">
				<omgdi:waypoint x="1180.0" y="356.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="1225.0" y="356.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="association1" id="BPMNEdge_association1">
				<omgdi:waypoint x="665.0" y="300.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="664.0" y="363.0">
				</omgdi:waypoint>
				<omgdi:waypoint x="830.0" y="363.0">
				</omgdi:waypoint>
			</bpmndi:BPMNEdge>
		</bpmndi:BPMNPlane>
	</bpmndi:BPMNDiagram>
</definitions>

        然后部署流程和启动流程实例。通过控制台的输出可以看到微信支付失败后触发了补偿中间事件。然后补偿边界事件触发。触发了补偿自动任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值