目录
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.6.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/flowable-learn?serverTimezone=UTC&nullCatalogMeansCurrent=true
username: root
password: root
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
flowable:
async-executor-activate: false #关闭定时任务JOB
# 将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
database-schema-update: true
server:
port: 8082
系统启动的时候检查如果数据库对应的表结构没有创建,会帮助我们先创建对应的表结构。
processes目录下的任何BPMN 2.0流程定义都会被自动部署。创建processes目录,并在其中创建示例流程定义(命名为one-task-process.bpmn20.xml)。
cases目录下的任何CMMN 1.1事例都会被自动部署。
forms目录下的任何Form定义都会被自动部署。
通过手动方式来部署
package com.oracle.flow;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class FlowableSpringBootApplication {
@Autowired
private ProcessEngine processEngine;
@Autowired
private RepositoryService repositoryService;
@Autowired
private TaskService taskService;
@Autowired
private RuntimeService runtimeService;
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("processes/请假流程-springboot.bpmn20.xml")
.name("holiday-springboot")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
}
启动流程和前面介绍的就没什么差异了,通过RuntimeService来实现。
/**
* start process
*/
@Test
void startFlow(){
Map<String,Object> map = new HashMap();
map.put("assignee0","zhangsan");
map.put("assignee1","lisi");
runtimeService.startProcessInstanceById("holiday-springboot:1:a5ef952b-9ccb-11ee-a7a2-1a473d673661",map);
}
处理流程和前面介绍的也一样,通过TaskService来处理。
/**
* complete Task
*/
@Test
void completeTask(){
Task task = taskService.createTaskQuery()
.processInstanceId("e362d2e6-9ccc-11ee-a2c3-1a473d673661")
.taskAssignee("zhangsan")
.singleResult();
if(task != null){
taskService.complete(task.getId());
System.out.println("complete ....");
}
}
事件(event)通常用于为流程生命周期中发生的事情建模。事件总是图形化为圆圈。在BPMN 2.0中,有两种主要的事件分类:捕获(catching)与抛出(throwing)事件。
捕获: 当流程执行到达这个事件时,会等待直到触发器动作。触发器的类型由其中的图标,或者说XML中的类型声明而定义。捕获事件与抛出事件显示上的区别,是其内部的图标没有填充(即是白色的)。
抛出: 当流程执行到达这个事件时,会触发一个触发器。触发器的类型,由其中的图标,或者说XML中的类型声明而定义。抛出事件与捕获事件显示上的区别,是其内部的图标填充为黑色。
定时触发的相关事件,包括定时器启动事件,定时器捕获中间件事件,定时器边界事件
定时器启动事件(timer start event)在指定时间创建流程实例。在流程只需要启动一次,或者流程需要在特定的时间间隔重复启动时,都可以使用。
*请注意:*子流程不能有定时器启动事件。
*请注意:*定时器启动事件,在流程部署的同时就开始计时。不需要调用startProcessInstanceByXXX就会在时间启动。调用startProcessInstanceByXXX时会在定时启动之外额外启动一个流程。
*请注意:*当部署带有定时器启动事件的流程的更新版本时,上一版本的定时器作业会被移除。这是因为通常并不希望旧版本的流程仍然自动启动新的流程实例。
定时器启动事件,用其中有一个钟表图标的圆圈来表示。
案例:
在定时启动的节点设置时间:
可以通过FlowableUI中的部署来演示,通过FlowableUI我们可以看到,没有启动流程实例的情况下,到里23:14:14秒的时候自动帮助我们创建了一个流程实例。
当第一个人工处理完成后,第二个人工处理的任务需要在2022-03-27T23:25:14 之后执行。
案例:
通过FlowableUI的案例演示我们可以看到后一个任务是在定时时间之后执行的。
人工任务1如果在定义的2022-03-27T23:36:14
这个时间之前还没有处理,那么就会触发定时边界事件,从而从人工任务3。
案例:
发布启动流程
然后在张三这个位置我们不完成,等到定时到来,达到定时的时间,任务进入到了人工审批三。
timeDuration:该元素用于指定某一时间段后触发定时器事件。
? 在定时事件中我们一定要放开如下的配置:
指定计时器在启动前应等待多长的时间,首先一定时器启动事件为例:
Java创建类:
public class MyOneJavaDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
System.out.println("MyOneJavaDelegate执行了..." + new Date());
}
}
flowableUI绑定类
创建Controller用来部署流程:
@RestController
public class FlowableController {
@Autowired
private ProcessEngine processEngine;
@Autowired
private RepositoryService repositoryService;
@RequestMapping("/deploy")
public String deploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("processes/等待定时器启动事件.bpmn20.xml")
.name("event001")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
return "部署任务成功....";
}
}
当我们访问:http://localhost:8082/deploy
部署后等待30秒...
控制台打印了信息,说明部署后30秒,流程自动到了“自动任务”处。
然后来看看中间事件的等待定时器事件案例:
第一步:创建流程
第二步:创建两个JavaDelegate的Java类来处理
public class SignalStartOnedelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("-------触发了-111-------->"+ LocalDateTime.now().toString());
}
}
public class SignalStartTwodelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("-------触发了222--------->"+ LocalDateTime.now().toString());
}
}
第三步:流程图关联类
第四步:定时器中间事件的等待时间设置是10秒钟
第五步:部署流程
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("processes/等待定时器中间事件.bpmn20.xml")
.name("event002")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
第六步:然后我们需要启动流程实例,之后等待10秒钟看效果
@Test
void startFlow() throws InterruptedException {
runtimeService.startProcessInstanceById("event002:2:2b3a7e64-9e50-11ee-b06f-1a473d673661");
// 需要在此阻塞比等待长的时间
TimeUnit.MINUTES.sleep(1);
}
该案例由一个人工审核+两个自动任务+定时器边界事件组成,自动任务一绑定的JavaDelegate是
public class SignalStartOnedelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("-------触发了-111-------->"+ LocalDateTime.now().toString());
}
}
自动任务二绑定的JavaDelegate是:
public class SignalStartTwodelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("-------触发了222--------->"+ LocalDateTime.now().toString());
}
}
定时器边界事件设置的是20S,也就是如果人工审核在20S还没处理就会触发边界事件:通过代码来演示,部署流程后需要启动流程,然后等待20S看控制台输出。
小结:timeDuration在三种定时器的事件中
定时器启动事件:等待指定时间后启动流程实例。
定时器中间事件:AB任务中间有个定时器中间事件,A任务处理后需要等待对应的时间才能流转到B处。
定时器边界事件:任务A绑定了定时器边界事件后,如果在等待时间以内A没有处理任务,那么就会触发对应的边界事件。
指定重复周期,可用于周期性启动流程,或者为超期用户任务多次发送提醒,这个元素可以使用两种格式
第一种是按照ISO 8601标准定义的循环时间周期。例如(三次重复间隔,每次间隔为10小时):R3/PT10H
也可以使用timeCycle的可选属性endDate,或者像这样直接写在时间表达式的结尾:R3/PT10H/${EndDate}
。 当到达endDate时,应用会停止,并为该任务创建其他作业
也可以通过cron表达式来处理
编写案例来演示:
重复时间设置为 R3PT20S 重复3次,间隔20描述,自动任务绑定的是JavaDelegate:
public class SignalStartOnedelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("-------触发了-111-------->"+ LocalDateTime.now().toString());
}
}
然后我们部署看效果
/**
* Deploy
*/
@Test
void testDeploy() throws InterruptedException {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("循环定时器启动事件.bpmn20.xml")
.name("event004")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
TimeUnit.MINUTES.sleep(3);
}
timeCycle作为中间事件的话,只会执行一次,案例如下:
案例中的自动任务一二对应绑定如下的JavaDelegate:
public class SignalStartOnedelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("-------触发了-111-------->"+ LocalDateTime.now().toString());
}
}
public class SignalStartTwodelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("-------触发了222--------->"+ LocalDateTime.now().toString());
}
}
中间事件的设置为R3/PT5S
循环3次,间隔5秒执行,但是这是中间事件,其实只会执行一次,我来看效果:
部署后启动
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("循环定时器中间事件.bpmn20.xml")
.name("event005")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
/**
* start process
*/
@Test
void startFlow() throws InterruptedException {
runtimeService.startProcessInstanceById("event005:2:932b6055-a20a-11ee-be07-1a473d673661");
System.out.println("启动时间:" + new Date());
// 需要在此阻塞比等待长的时间
TimeUnit.MINUTES.sleep(1);
}
可以看到只触发了一次,哪怕你定义循环很多次,但也只会触发一次。
在边界事件中,定义了循环条件R3/PT30S
理论上要循环3次,间隔30S,单其实也只会执行一次。
总结:循环设定
启动事件:根据设置循环启动流程实例。
中间事件:即使设置了循环时间也只会触发异常。
边界事件:即使设置了循环时间也只会触发异常。
消息事件(message event),是指引用具名消息的事件。消息具有名字与载荷。与信号不同,消息事件只有一个接收者。
消息启动事件,也就是我们通过接收到某些消息后来启动流程实例,比如接收到了一封邮件,一条短信等,具体通过案例来讲解。
我们需要先定义一个消息:
然后消息节点再引用此消息定义:
然后通过代码来处理,部署和启动:
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("消息启动事件.bpmn20.xml")
.name("event007")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
/**
* start process
*/
@Test
void startFlow() throws InterruptedException {
// 注意:发送消息发送的应该是消息的名称而不是消息的ID
runtimeService.startProcessInstanceByMessage("第一个消息");
System.out.println("启动时间:" + new Date());
// 我们得保证容器的运行,所以需要阻塞
TimeUnit.MINUTES.sleep(1);
}
消息中间事件就是在流程运作中需要消息来触发的场景,案例演示,自动流程1
处理完成后,需要接收特定的消息之后才能进入到自动流程2。
消息中间事件绑定的消息为:
然后通过代码来演示:
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("消息中间事件.bpmn20.xml")
.name("event008")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
/**
* start process
*/
@Test
void startFlow() {
runtimeService.startProcessInstanceById("event008:1:6fbb8e9f-a22b-11ee-aa91-1a473d673661");
}
/**
* 中间事件-发布消息
*/
@Test
void recevedMsg(){
// 需要查询到executionId,在act_ru_execution表中
String processExecutionId = "a8e99e47-a22b-11ee-b779-1a473d673661";
// 我们需要根据流程实例编号找到对应的执行编号
/* Execution execution = runtimeService.createExecutionQuery()
.processInstanceId("event008:1:6fbb8e9f-a22b-11ee-aa91-1a473d673661")
.singleResult();
System.out.println("----------->"+execution.getId());*/
runtimeService.messageEventReceived("第二个消息",processExecutionId);
}
消息边界事件,如果在消息触发前还没有,案例演示:
注意:服务任务要绑定类,用户任务要分配用户,最后记得要绑定消息定义。
部署流程
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("消息边界事件.bpmn20.xml")
.name("event009")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
/**
* start process
*/
@Test
void startFlow() {
runtimeService.startProcessInstanceById("event009:1:41bc2a96-a22e-11ee-811f-1a473d673661");
}
部署流程后启动流程实例会运转到:
如果人工处理在消息订阅前没有处理就会触发边界事件:
/**
* 中间事件-发布消息
*/
@Test
void recevedMsg(){
// 需要查询到executionId,在act_ru_execution表中
String processExecutionId = "694419a0-a22e-11ee-9ba7-1a473d673661";
runtimeService.messageEventReceived("第三个消息",processExecutionId);
}
错误事件可以用做一个流程的开始事件或者作为一个任务或者子流程的边界事件,错误事件没有提供作用中间事件的功能,这一点和前面介绍的定时器事件和消息事件还有区别的。
错误启动事件(error start event),可用于触发事件子流程(Event Sub-Process)。错误启动事件不能用于启动流程实例。
上面是正常流程,当自动任务一没出异常则正常走。但如果出了异常,下面的错误启动事件就会捕获到异常,走自动任务二的流程。
然后我们再定义一个错误,内容为:
<error id="error01" errorCode="abcd"></error>
然后我们在主流程中的自动任务一
中我们抛出异常:
public class SignalStartOnedelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("完成自动审批任务-----》SignalStartOnedelegate" + LocalDateTime.now().toString());
// 业务执行发现有问题 此处的errorCode需要和定义的error标签中的errorCode保持一致
throw new BpmnError("abcd");
}
}
然后我们在自定义任务二
中简单定义一个输出即可:
public class SignalStartTwodelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("-------触发了222--------->"+ LocalDateTime.now().toString());
}
}
先部署再启动:
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("错误启动事件.bpmn20.xml")
.name("event1001")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
/**
* start process
*/
@Test
void startFlow() {
runtimeService.startProcessInstanceById("event1001:1:d96736de-a3f1-11ee-a6cb-1a473d673661");
}
定义如下的流程图:
部署 + 启动:
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("错误边界事件.bpmn20.xml")
.name("event1002")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
/**
* start process
*/
@Test
void startFlow() throws InterruptedException {
runtimeService.startProcessInstanceById("event1002:1:6cd5c268-a3f4-11ee-9c94-1a473d673661");
TimeUnit.MINUTES.sleep(1);
}
然后点击空白处,定义信号:
然后启动事件引用信号:
部署流程:
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("信号启动事件.bpmn20.xml")
.name("event2001")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
流程是一个信息启动事件,所以我们需要释放对应的信号来触发流程的启动:
/**
* 通过信号发送来触发信号启动事件的执行
* 全局的信息
*/
@Test
void signalReceived() throws Exception {
runtimeService.signalEventReceived("firstSignal");
// 我们得保证容器的运行,所以需要阻塞
TimeUnit.MINUTES.sleep(1);
}
通过输出语句可以看到自定义任务触发了:
我们可以把信息的作用域由原来的golbal全局的调整为processInstance,测试后发现还是执行了,说明在启动事件信息的作用域其实是不起作用的。
案例如下:当我们启动事件后,会阻塞在这个消息获取中间事件处,等待相关信号后才会继续流转。
定义信号并且进行绑定,此处参照上一节。
先部署:
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("信号中间捕获事件.bpmn20.xml")
.name("event2002")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
然后我们需要启动流程:
/**
* start process
*/
@Test
void startFlow() {
runtimeService.startProcessInstanceById("event2002:1:b74c4367-a6af-11ee-b812-1a473d673661");
}
发送信号信息:
/**
* 通过信号发送来触发信号启动事件的执行
* 全局的信息
*/
@Test
void signalGolbal() throws Exception {
runtimeService.signalEventReceived("secondSingal");
// 我们得保证容器的运行,所以需要阻塞
TimeUnit.MINUTES.sleep(1);
}
首先针对processInstance的信号,我们发送global信号是不会被捕获的:
信号中间抛出事件也就是在流程执行中的某个节点抛出了对应的信号,然后对应的信号中间捕获事件就会触发,我们通过具体的案例来演示如:
定义信号信息:
三个自定义任务绑定了三个javaDelegate分别给出打印语句来记录
然后部署任务:
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("信号中间抛出事件.bpmn20.xml")
.name("event4001")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
启动任务:
/**
* start process
*/
@Test
void startFlow() {
runtimeService.startProcessInstanceById("event4001:1:80b7ffdb-a6f3-11ee-8668-1a473d673661");
}
因为是并行任务,所以1、2、3都走了。
最后来看看信号边界事件,案例如下:
点击空白处进行信号定义:
部署项目然后启动流程:
/**
* Deploy
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("信号边界事件.bpmn20.xml")
.name("event4002")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
/**
* start process
*/
@Test
void startFlow() {
runtimeService.startProcessInstanceById("event4002:1:3c56050f-a6f6-11ee-addf-1a473d673661");
}
如果现在人工任务一一直没人处理,这时我们抛出信号,被边界事件捕获到,而走自动任务二。
@Test
public void signalGlobal() throws Exception {
runtimeService.signalEventReceived("signal2");
}
结束事件顾名思义就是流程结束的事件,除了前面遇到的空结束事件外,结束事件还包括如下几种:
错误结束事件
中断结束事件
取消结束事件
当流程执行到达错误结束事件(error end event)时,结束执行的当前分支,并抛出错误。这个错误可以由匹配的错误边界中间事件捕获。如果找不到匹配的错误边界事件,将会抛出异常。通过具体案例来详细讲解:
设置条件:
另一边条件是:${flag<=0}
绑定错误引用:
定义的error:
当子流程触发错误结束事件,就会触发这个边界事件,进而完成自动任务三。
然后我们需要做的操作有,部署,启动流程绑定流程变量flag的值为0,然后就可以看输出结果了。
/**
* 部署
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("错误结束事件.bpmn20.xml")
.name("event5001")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
/**
* 启动
*/
@Test
void startFlow() {
Map<String,Object> map = new HashMap<>();
map.put("flag",0); //设置流程变量flag为0,触发排他网关中的 错误结束事件
runtimeService.startProcessInstanceById("event5001:1:93d5b6c2-a6fc-11ee-bf3d-1a473d673661",map);
}
中断结束事件也称为终止结束事件,主要是对流程进行终止的事件,可以在一个复杂的流程中,如果某方想要提前中断这个流程,可以采用这个事件来处理,可以在并行处理任务中。如果你是在流程实例层处理,整个流程都会被中断,如果是在子流程中使用,那么当前作用和作用域内的所有的内部流程都会被终止。具体还是通过两个案例来给大家介绍:
案例一我们介绍没有子流程的情况下终止的场景,具体案例如下:
排他网关设置条件:
具体操作:部署流程-->启动流程实例-->wanwu 处理任务【流程实例 flag <= 0】 触发任务:
/**
* 部署
*/
@Test
void testDeploy() {
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("终止结束事件01.bpmn20.xml")
.name("终止结束事件01")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println("deploy.getName() = " + deploy.getName());
}
启动流程实例:
/**
* start process
*/
@Test
void startFlow() {
Map<String,Object> map = new HashMap<>();
map.put("flag",0); //设置流程变量flag为0
runtimeService.startProcessInstanceById("event5002:1:cb6429ba-a708-11ee-9539-1a473d673661",map);
}
这时可以看到三个并行的Task任务:
这时我们只需王五来处理即可,在上一步中我们已经对流程变量赋值了:
@Test
public void completeTask(){
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
.processDefinitionId("event5002:1:cb6429ba-a708-11ee-9539-1a473d673661")
.taskAssignee("wangwu")
.singleResult();
taskService.complete(task.getId());
}
然后再去看Task表中已经没有另外两条记录了哦:
通过案例可以看到在没有子流程的情况下,终止结束事件会把整个流程都进程都结束了,而且在历史记录中也可以看到信息。
然后我们来看看在子流程中触发终止结束事件的案例:
当子流程结束后,并不会影响其它流程。