Activiti7工作流

news2024/12/26 21:09:21

 

一、Activiti7概述


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

 二、Activiti7的入门案例


官方手册: http://jeecg.com/activiti5.21/

1.创建SpringBoot项目

现在开发中或者我们自己学习写案例都是通过SpringBoot脚手架工具来快速构建项目的。那么我们也
就直接通过创建SpringBoot项目来给大家讲解相关的案例。创建一个普通的SpringBoot项目。指定版本为
2.4.2即可

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

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

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.1.0.M6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

到这儿基本环境就OK了。

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>

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

 如果缺少历史表(只有17张表的情况下,应该有25张表)则在application.yml中添加以下配置

spring:
  activiti:
    db-history-used: true

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的所有数据库表都以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_*: 通用数据。用于不同场景下

注意:MySQL数据库最好使用5.7及以上的版本

3.流程设计器

3.1在线流程设计器

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

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

 把这war包拷贝到Tomcat服务器中即可。注意Tomcat的版本不要高于8.5,然后Tomcat服务。访问 http://l
ocalhost: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>
然后我们就可以做流程的部署操作了
4.流程操作
4.1 流程部署
设计好了流程图我们就可以通过如下的代码完成流程的部署。
流程部署的行为会涉及到数据库中的这两张表
<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>

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

3.2 Activiti BPMN visualizer插件

 创建bpm文件

 

点击生成的test.bpmn20.xml文件

 

 

 

点击空白处,鼠标右键添加组件

 

 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来获取流程部署和流程定义的相关信息
4.2 发起流程
部署流程成功后。我们就可以发起一个流程。发起流程需要通过RuntimeService 来实现。
 

/**
* 查询当前部署的流程有哪些
*/
@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());
发起流程成功后。在对应的act_ru_task 中就有一条对应的待办记录。
对应的流程状态如下:
4.3 查询流程
用户登录后要查看待办的任务信息。我们需要通过TaskService 来实现查询操作。具体代码如下:
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.涉及表结构

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

 

 6.流程设计器持久化

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

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

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

 

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

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

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

 该操作中需要注意的点:
1. 修改配置文件中的信息关键是连接地址的路径:jdbc:mysql://localhost:3306/activiti6ui?
serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&nullCatalogMeansCurrent=true
2. 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": "更改密码",
三、任务分配
1.固定分配
在指派用户任务的审批人时。我们是直接指派的固定账号。但是为了保证流程设计审批的灵活性。
我们需要各种不同的分配方式,所以这节我们就详细的来介绍先在Activiti7中我们可以使用的相关的分配
方式.
固定分配就是我们前面介绍的,在绘制流程图或者直接在流程文件中通过Assignee来指定的方式.
"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
我们发起流程后。根据流程的设计应该需要进入到人事审批。但是呢。审批的用户是${assign1} 是一
个流程变量。那么还没有赋值的情况下。那么系统是没有办法识别的。
我们需要在进入该节点前对流程变量赋值
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();
启动流程实例后。在act_ru_task 中的审批人是空的,
但是在对应的act_ru_identitylink 中我们可以看到对应的候选人信息
// 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(){
3.候选人组
当候选人很多的情况下,我们可以分组来处理。先创建组,然后把用户分配到这个组中。
3.1 流程图绘制
然后在设置审批人的时候通过候选人组来设定
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 中可以
看到这个边界事件的定义

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

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

 同时会开启我们的中间边界事件。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 边界事件

在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来发布信号
同时因为是非中断的,所以用户任务1 还在。接下来我们就需要做审批操作。审批通过就会进入到用户
任务2 。
进入到用户任务2 后。继续审批就会触发信号抛出事件,然后被信号边界事件捕获。
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>

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

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5个最流行的免费AI应用托管平台

完成机器学习项目后&#xff0c;是时候展示你的模型的性能了。 你可以创建前端应用程序或使用 REST API。 随着 Streamlit、Gradio 和 FAST API 的引入&#xff0c;创建前端应用程序变得无忧无虑。 这些 Web 框架需要几行代码来创建交互式用户界面。 与公众分享你的工作有助于你…

0-1搭建vue项目工程

一、下载node.js 简单介绍&#xff1a; Node.js是一个基于V8引擎的JavaScript运行时环境&#xff0c;它允许开发者在服务器端使用JavaScript进行开发。Node.js是一个非常强大的工具&#xff0c;可以帮助开发者构建高性能、可扩展的Web应用程序&#xff0c;并且可以与各种技术…

使用appuploader工具流程(Windows版本)

转载&#xff1a;使用appuploader工具流程&#xff08;Windows版本&#xff09; 目录 转载&#xff1a;使用appuploader工具流程&#xff08;Windows版本&#xff09; 一.登录apple官网&#xff0c;注册账号 二.下载Appuploader和登录 三.bundle ID 四.设备管理 五.证书管…

【python】 油管外挂字幕下载位srt歌词字幕文本文件

【python】 油管外挂字幕下载位srt文本文件 案例截图 案例代码 # python程序&#xff0c;可以下youtube视频的字幕文件。输入一个视频的url&#xff0c;就会下载它的字幕文件到一个文件夹里。 # Author WeChat:****请私信, # Date:2023-8-2, # Email:ack1024#hotmail.com # 本…

全国首创!法大大助力深圳率先在企业开办领域引入音视频双录签名模式

为了进一步规范市场主体登记行为&#xff0c;提高企业办事便利度&#xff0c;近日深圳引入录音录像双录签名新模式&#xff0c;实现用户无介质全流程快捷申报&#xff0c;进一步降低了开办企业成本&#xff0c;为企业开办注入加速度。 无需法人、监事等企业负责人再到业务办理大…

Python - series和dataframe的关系

目录 1 series和dataframe的关系 2 创建一个df 3 用index过滤不同行 4 用row 过滤 5 用series构建dataframe 1 series和dataframe的关系 类似集合与元素的关系DataFrame中的一行or一列的取值&#xff0c;返回的结果都是series通过几个series&#xff0c;可以创建一个da…

Redis的安装方法与基本操作

目录 前言 一、REDIS概述 二、REDIS安装 1、编译安装 2.yum安装 三、Redis的目录结构 四、基础命令解析 五、在一台服务器上启动多个redis 六、数据库的基本操作 &#xff08;一&#xff09;登录数据库 &#xff08;二&#xff09;基础命令 七、Redis持久化 &#xff08;一&…

【EI复现】梯级水光互补系统最大化可消纳电量期望短期优化调度模型(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Tomcat线程池原理

1. 一个 SpringBoot 项目能同时处理多少请求&#xff1f;tomcat容器&#xff0c; 200 次。 2. 怎么来的&#xff1f; 而点击这些线程&#xff0c;查看其堆栈消息&#xff0c;可以看到 Tomcat、threads、ThreadPoolExecutor 等关键字 基于“短时间内有 200 个请求被立马处理…

【Docker】Docker中network的概要、常用命令、网络模式以及底层ip和容器映射变化的详细讲解

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;陈童学哦&#xff0c;目前学习C/C、算法、Python、Java等方向&#xff0c;一个正在慢慢前行的普通人。 &#x1f3c0;系列专栏&#xff1a;陈童学的日记 &#x1f4a1;其他专栏&#xff1a;CSTL&…

Java课题笔记~ 关于错误与异常

非检查异常(unckecked exception)&#xff1a;Error 和 RuntimeException 以及他们的子类。javac在编译时&#xff0c;不会提示和发现这样的异常&#xff0c;不要求程序员必须处理这些异常。在运行阶段&#xff0c;倘若发生Error则虚拟机几乎崩溃&#xff0c;倘若发生RuntimeEx…

会这个Python的测试员,工作都不会太差!

Python语言得天独厚的优势使之在业界的火热程度有增无减&#xff0c;尤其是在经历了互联网&#xff0c;物联网&#xff0c;云计算&#xff0c;大数据&#xff0c;人工智能等浪潮的推动下&#xff0c;其关注度&#xff0c;普适度一路走高。 对于测试人员来说&#xff0c;很多人…

2023上半年京东吸尘器行业品牌销售排行榜(京东数据挖掘)

如今&#xff0c;伴随生活节奏加快、懒人经济兴起&#xff0c;致力于解放双手的清洁类电器产品愈加受到用户青睐。作为清洁类电器行业的细分品类之一&#xff0c;在懒人经济的市场红利下&#xff0c;吸尘器也受到不少用户的喜爱。不过在扫地机器人、洗地机等新兴品类的冲击下&a…

华三H3C S5120V3交换机的配置之组建IRF

IRF&#xff08;Intelligent Resilient Framework&#xff0c;智能弹性架构&#xff09;&#xff0c;是华三交换机实现虚拟堆叠的一种技术&#xff0c;其核心思想是将多台交换机连接在一起&#xff0c;虚拟成一台交换机&#xff0c;进而实现统一管理。和传统的堆叠概念不同&…

基于连续Ziegler_Nichols的频域响应pid整定

连续Ziegler_Nichols的频域响应pid整定 Ziegler_Nichols频域响应pid整定的方法是基于稳定性分析的频域响应pid整定方法。该方法整定的思想是&#xff1a;对于给定的被控对象传递函数&#xff0c;可以得到其根轨迹&#xff0c;对应穿越Jw轴的点&#xff0c;增益即为Km&#xff…

【Java可执行命令】(二十)堆转储快照文件及堆信息查看工具 jmap:生成多格式堆转储文件、打印类加载器信息及查看共享对象映射信息 ~

Java可执行命令之jmap 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 语法格式3.2 生成堆转储文件3.3 执行jmap命令查看内存使用情况3.4 执行jmap命令打印对象统计信息 4️⃣ 应用场景&#x1f33e; 总结 1️⃣ 概念 jmap 是 Java Development Kit&#xff08;JDK&#xff09;自带…

【Kubernetes】Kubernetes之YAML文件详解

YAML 一、YAML 的概述1. Kubernetes 支持资源管理格式2. YAML 语法格式 二、YAML 文件1. 如何获取 api 资源相关信息2. 编写资源配置文件2.1 手动编写 yaml 文件详解K8S中的port 2.2 使用镜像生成 yaml 文件2.3 根据现有资源导出 yaml 文件 总结1. 如何获取资源清单文件&#x…

[LeetCode - Python]349. 两个数组的交集(Easy);350. 两个数组的交集 II(Easy)

题目&#xff1a; 349. 两个数组的交集(Easy) 代码 1.哈希表 set清重 两次遍历&#xff1a; class Solution:def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:dict {}ret []for i in set(nums1) :if i not in dict:dict[i] 0 for j in …

selenium+python —— 实现基本自动化测试

安装selenium 打开命令控制符输入&#xff1a;pip install -U selenium 火狐浏览器安装firebug&#xff1a;www.firebug.com&#xff0c;调试所有网站语言&#xff0c;调试功能 Selenium IDE 是嵌入到Firefox 浏览器中的一个插件&#xff0c;实现简单的浏览器操 作的录制与回…

centos7 ESXi 磁盘扩充容量

1、背景 有一天&#xff0c;突然程序报空间不足了。。。。。。 2023-06-23 02:26:51.631 UTC [26190] LOG: could not open temporary statistics file "pg_stat_tmp/global.tmp": No space left on device 2023-06-23 02:26:51.631 UTC [26190] LOG: could not …