当前位置: 首页 > article >正文

Springboot +Flowable,详细解释啥叫流程实例(二)

一.简介

上一篇中学习了Flowable 中的流程模板(流程定义)的部署问题,这一篇来学习什么叫流程实例。
部署之后的流程模板,还不能直接运行,例如我们部署了一个请假流程,现在 张三想要请假,他就需要开启一个请假流程,李四 想请假,他也需要开启一个请假流程,这开启的请假流程就是流程实例,**而我们一开始部署的请假流程,则类似于一个模版,基于此模版,我们可以开启很多个具体的流程实例。
**从这个角度来看,之前定义的 ProcessDefinition 就类似于一个 Java 类,今天要学习的 ProcessInstance 则相当于一个 Java 对象。

二.捋清概念:

首先我们需要先捋清三个概念:

流程定义 ProcessDefinition
流程实例 ProcessInstance
执行实例 Execution

1.流程定义 ProcessDefinition

流程定义 ProcessDefinition 。将一个流程 XML 文件部署到 flowable 中,这就是一个定义好的流程了,基于这个定义好的流程,我们可以开启很多流程实例。

2.流程实例 ProcessInstance

流程实例 ProcessInstance 就是通过流程定义启动的一个流程,他表示一个流程从开始到结束的最大的流程分支,流程实例和流程定义的关系就类似于 Java 对象和 Java 类之间的关系。

3.执行实例 Execution

执行实例 Execution 稍微有点难以理解。

首先从类的关系上来看,ProcessInstance 就是 Execution 的子类。

流程实例通常是执行实例的根结点,即在一个流程中,出口和入口可以算是一个流程实例的节点,而中间的过程则是执行实例。

假如流程本身就是一条线,那么流程实例和执行实例基本上是一样的,但是如果流程中包含多条线,例如下图:

在这里插入图片描述
这张图中有并行网关,并行任务执行的时候,每一个并行任务就是一个执行实例,这样大家就好理解了。

结论就是:在一个流程实例中,除了开始和结束之外,其他的都是执行实例。即使流程只有一条线,中间的也都是执行实例,只不过此时的执行实例等于流程实例而已。

三.五种流程启动方式

当我们将流程部署好之后,接下来启动流程,我们有五种不同的方式去启动一个流程。

1.通过流程定义的 id 去启动

首先就是通过流程定义的 id 去启动一个流程,对应的方法名称就是 RuntimeService#startProcessInstanceById,该方法有好几个重载的方法,不同的重载方法只是传递的参数不同而已,其他基本上都是一样的。

2.通过流程的 key 去启动

也可以通过流程定义的 key 去启动一个流程,这个流程定义的 key 其实就是流程 XML 文件中的 id,这个对应的方法名是 RuntimeService#startProcessInstanceByKey。

3.通过流程的 key+tenantId 去启动

有这样一种情况,例如我有两个子系统 A 和 B,A 和 B 中都有一个请假流程的定义,现在当我想要启动一个流程的时候,怎么知道是启动 A 的请假流程还是启动 B 的请假流程呢?此时我们可以通过租户 ID 即 tenantId 去区分,所以,流程启动就还有一个方法 RuntimeService#startProcessInstanceByKeyAndTenantId。

4.通过流程的 message 去启动

通过消息去启动一个流程,对应的方法是 RuntimeService#startProcessInstanceByMessage。

5.通过流程的 message+tenanId 去启动

通过消息+租户 ID 去启动一个流程,对应的方法是 RuntimeService#startProcessInstanceByMessageAndTenantId。

四.实践

绘制一个简单的流程图,流程图如下:
在这里插入图片描述
流程 XML 文件,代码如下:

<process id="leave" name="请假流程" isExecutable="true">
  <startEvent id="startEvent1" flowable:formFieldValidation="true" flowable:initiator="INITIATOR"></startEvent>
  <userTask id="sid-EF721F14-B1F1-4B3B-8018-608757EF5391" name="提交请假申请" flowable:assignee="${INITIATOR}" flowable:formFieldValidation="true">
    <extensionElements>
      <modeler:activiti-idm-initiator xmlns:modeler="http://flowable.org/modeler"><![CDATA[true]]></modeler:activiti-idm-initiator>
    </extensionElements>
  </userTask>
  <sequenceFlow id="sid-9C18B4D2-127C-40FD-BC81-1E947628D316" sourceRef="startEvent1" targetRef="sid-EF721F14-B1F1-4B3B-8018-608757EF5391"></sequenceFlow>
  <userTask id="sid-CC8E2905-C524-4C32-86DE-8C76102EBDF2" name="主管审批" flowable:assignee="zhangsan" flowable:formFieldValidation="true">
    <extensionElements>
      <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
    </extensionElements>
  </userTask>
  <sequenceFlow id="sid-62E837FF-DF33-414C-AC21-2DA84E478856" sourceRef="sid-EF721F14-B1F1-4B3B-8018-608757EF5391" targetRef="sid-CC8E2905-C524-4C32-86DE-8C76102EBDF2"></sequenceFlow>
  <userTask id="sid-D033E54B-4388-46B1-A8F9-472ABD2E1435" name="经理审批" flowable:assignee="lisi" flowable:formFieldValidation="true">
    <extensionElements>
      <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
    </extensionElements>
  </userTask>
  <sequenceFlow id="sid-7EDE624F-1D8F-4DF3-BB97-F8D9066A7A75" sourceRef="sid-CC8E2905-C524-4C32-86DE-8C76102EBDF2" targetRef="sid-D033E54B-4388-46B1-A8F9-472ABD2E1435"></sequenceFlow>
  <endEvent id="sid-FB77ACAC-DB24-4F44-9925-2FE2EAE09EF8"></endEvent>
  <sequenceFlow id="sid-21345500-1FCF-4356-9FB1-834C09BEA9CB" sourceRef="sid-D033E54B-4388-46B1-A8F9-472ABD2E1435" targetRef="sid-FB77ACAC-DB24-4F44-9925-2FE2EAE09EF8"></sequenceFlow>
</process>

这个 XML 文件,在启动节点上设置了 flowable:initiator=“INITIATOR”,相当于定义了流程发起人的变量为 INITIATOR,这个变量名是自定义的,定义好之后,将来就可以在其他节点中就可以使用这个变量了。

流程介绍,其中:

  1. 提交请假申请是由流程的发起人完成。
  2. 主管是 张三。
  3. 经理是 李四。

接下来就要启动流程,假设用流程定义的 key 来启动一个流程实例:

@SpringBootTest
public class RuTest {
    @Autowired
    RuntimeService runtimeService;

    private static final Logger logger = LoggerFactory.getLogger(RuTest.class);

    @Test
    void test01() {
        Authentication.setAuthenticatedUserId("wangwu");
        ProcessInstance pi = runtimeService.startProcessInstanceByKey("leave");
        logger.info("id:{},activityId:{}",pi.getId(),pi.getActivityId());
    }
}

启动的代码其实很简单,当流程启动成功之后,流程中的每一步都会记录在 ACT_RU_EXECUTION 表中,同时,如果这个节点是一个用户任务节点(UserTask),那么同时还会在 ACT_RU_TASK 表中添加一条记录。

Authentication.setAuthenticatedUserId("wangwu"); 表示设置流程的发起人。

另外一种设置流程发起人的方式如下:

@Autowired
IdentityService identityService;
@Test
void test01() {
    identityService.setAuthenticatedUserId("wangwu");
    ProcessInstance pi = runtimeService.startProcessInstanceByKey("leave");
    logger.info("id:{},activityId:{}", pi.getId(), pi.getActivityId());
}

对于我们上面的流程来说,启动之后,就会进入到提交请假申请这个节点中,所以一共走了两个节点,那么 ACT_RU_EXECUTION 表中应该有两条记录了,如下图:
在这里插入图片描述
再来看看 ACT_RU_TASK 表中的内容:
在这里插入图片描述
可以看到,该表中有一条记录,这条记录其实就是提交请假申请这个节点,现在流程就停在这一步了,需要用户手动操作,才会继续向下走。

从这两张表中我们也可以大致上看出来,EXECUTION 和 ProcessInstance 之间的关系,ACT_RU_EXECUTION 表中的每一条记录就是一个 EXECUTION,多个 EXECUTION 对应同一个 PROC_INST_ID_,而 ACT_RU_TASK 表中的每一条 Task 记录也都对应了一个 EXECUTION。

现在我们就先去查询 wangwu 需要完成的 Task(wangwu 是流程的发起人),代码如下:

@Autowired
TaskService taskService;
@Test
void test02() {
    List<Task> list = taskService.createTaskQuery().taskAssignee("wangwu").list();
    for (Task task : list) {
        logger.info("id:{};name:{};taskDefinitionKey:{}",task.getId(),task.getName(),task.getTaskDefinitionKey());
    }
}

根据前面的介绍,我们知道,这个查询肯定是去 ACT_RU_TASK 表中进行查询的,我们来看下执行的 SQL:
在这里插入图片描述

可以看到,这里就是根据 ASSIGNEE_ 字段去查询任务的。

查询到任务之后,接下来去完成任务,代码如下:

@Test
void test03() {
    List<Task> list = taskService.createTaskQuery().taskAssignee("wangwu").list();
    for (Task task : list) {
        taskService.complete(task.getId());
    }
}

这个表示查询到 wangwu 的任务然后完成,这个方法执行完成之后,首先会在 ACT_RU_TASK 表中插入一条新的需要 zhangsan 完成的 Task,然后会更新 ACT_RU_EXECUTION 表中对应的执行实例信息,最后再从 ACT_RU_TASK 表中删除需要 wangwu 完成的记录,这些操作是在同一个事务当中完成的。

好了,现在再去执行 test02 的查询方法,就会发现查不到了,因为没有 wangwu 需要完成的 task 了,接下来应该去查询 zhangsan 需要完成的 task。

当一个流程实例完成后,ACT_RU_TASK 和 ACT_RU_EXECUTION 表中的记录都会被删除,所以我们可以通过查询 ACT_RU_EXECUTION 表中是否还有记录,去判断一个一个流程目前是处于执行状态还是完成状态,代码如下:

@Test
void test04() {
    String pId = "9c8557dd-3727-11ed-9404-acde48001122";
    ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(pId).singleResult();
    if (pi == null) {
        logger.info("{} 流程执行结束", pId);
    }else{
        logger.info("{} 流程正在执行中", pId);
    }
}

最后,如果你想要去 ACT_RU_EXECUTION 表中查询执行实例也是 OK 的,代码如下:

@Test
void test05() {
    List<Execution> list = runtimeService.createExecutionQuery().processInstanceId("6d0341c7-3729-11ed-8e4e-acde48001122").list();
    for (Execution execution : list) {
        logger.info("id:{};processInstanceId:{};name:{}",execution.getId(),execution.getProcessInstanceId(),execution.getName());
    }
}

查看执行的 SQL 如下:


: ==>  Preparing: SELECT RES.* , P.KEY_ as ProcessDefinitionKey, P.ID_ as ProcessDefinitionId, P.NAME_ as ProcessDefinitionName, P.VERSION_ as ProcessDefinitionVersion, P.DEPLOYMENT_ID_ as DeploymentId from ACT_RU_EXECUTION RES inner join ACT_RE_PROCDEF P on RES.PROC_DEF_ID_ = P.ID_ WHERE RES.PROC_INST_ID_ = ? order by RES.ID_ asc
: ==> Parameters: 6d0341c7-3729-11ed-8e4e-acde48001122(String)
: <==      Total: 2

可以看到,就是去 ACT_RU_EXECUTION 表中查询的。

五.删除流程实例

删除一个流程实例,代码如下:

@Test
void test06() {
    runtimeService.deleteProcessInstance("65ab0b38-38f3-11ed-b103-acde48001122", "javaboy想删除了");
}

注意这个是删除正在执行的流程实例信息,并不会删除历史流程信息。

六.获取运行的活动节点

可以根据执行实例的 ID 去查询活动节点的 ID,代码如下:

@Test
void test07() {
    List<Execution> list = runtimeService.createExecutionQuery().list();
    for (Execution execution : list) {
        List<String> activeActivityIds = runtimeService.getActiveActivityIds(execution.getId());
        for (String activeActivityId : activeActivityIds) {
            System.out.println("activeActivityId = " + activeActivityId);
        }
    }
}

查询的就是 ACT_RU_EXECUTION 表,查询到的 activeActivityId 其实就是该表的 ACT_ID 字段,看下查询的 SQL:
在这里插入图片描述

《肖申克的救赎》

生命可以归结为一种简单的选择:要么忙于生存,要么赶着去死。

懦怯囚禁人的灵魂,希望可以感受自由。强者自救,圣者渡人。

希望是件美丽的东西,也许是最好的东西。美好的东西是永远不会死的。

每个人都是自己的上帝。如果你自己都放弃自己了,还有谁会救你?

《熔炉》

我们一路奋战,不是为了能改变世界,而是为了不让世界改变我们。

现实如水母,看似美好无害实质总是致命伤人。

我们来到世界上,都是孤独的旅行,即使身边有人相伴,最终也会各奔东西!

世界上最美丽最珍贵的,反而是听不见且看不清的,只有用心才能感受得到。

《教父》

人可以不断犯错,但绝不能犯要命的错。

不要憎恨你的敌人,那会影响你的判断力。

人并非生来就伟大,而是越活越伟大。

《活着》

人是为了活着本身而活着,而不是为了活着之外的任何事物而活着。

以笑的方式哭,在死亡的伴随下活着。

没有什么比时间更具有说服力了,因为时间无需通知我们就可以改变一切。

你的命是爹娘给的,你不要命了也得先去问问他们。

《我不是药神》

世界上只有一种病,穷病,这种病你没法治,你也治不过来。

人间最高贵的是善良,是对生命的致敬。

《指环王》

把手握紧,里面什么也没有;把手放开,你得到的是一切。

我宁愿和你共度凡人短暂的一生,也不愿一个人看尽这世界的沧海桑田。

20.幸福的家庭都是相似的,不幸的家庭各有各的不幸。

或许有一天,人类变得萎缩懦弱,舍弃朋友,断绝友谊,但今天决不会这样。
《饮食男女》

22.人生不能像做菜,把所有的材料都准备好了才下锅。

什么叫做“可惜”啊,要心中有个“惜”字儿,才知道可惜。

其实一家人,住在一个屋檐下,照样可以各过各的日子,可是从心里产生的那种顾忌,才是一个家之所以为家的意义。

《让子弹飞》

世界上本没有路,有了腿便有了路。

如果你活着,早晚都会死;如果你死了,你就永远活着。

赚钱嘛,不寒碜


http://www.kler.cn/a/15549.html

相关文章:

  • 无人机场景 - 目标检测数据集 - 车辆检测数据集下载「包含VOC、COCO、YOLO三种格式」
  • 单片机学习笔记 1. 点亮一个LED灯
  • 搭建es环境
  • vue3 如何调用第三方npm包内部的 pinia 状态管理库方法
  • PHP代码审计 --MVC模型开发框架rce示例
  • 大数据-226 离线数仓 - Flume 优化配置 自定义拦截器 拦截原理 了 拦截器实现 Java
  • 跌倒检测和识别3:Android实现跌倒检测(含源码,可实时跌倒检测)
  • QFIELD-GIS工具版如何编辑数据
  • 入职华为外包一个月后,我离职向“北上广深”流浪了...
  • Ubuntu22.04部署Pytorch2.0深度学习环境
  • SQL性能调优简介
  • EPIT定时器实验(一)
  • 区块链学习一(FISCO BCOS部署控制台部署第一个HelloWorld)
  • 射频电路设计常见问题以及经验总结
  • 【MATLAB图像处理实用案例详解(12)】——利用BP神经网络实现图像压缩
  • redis 过期消息订阅实现(java实现)
  • Java数组的学习(基础)
  • [ 云原生 | Docker ] 构建高可用性的 SQL Server:Docker 容器下的主从同步实现指南
  • 带你看懂 Vue Hook和React Hook
  • Java工程项目管理系统源码 工程项目源码
  • Prometheus 监控系统安装
  • 5.Java中抽象类和接口
  • 一曲微茫度余生 ——川剧《李亚仙》唱响香港西九戏曲中心
  • bagging(main: RF随机森林) 回归器
  • 神奇的饼状图:如何用最简单的方式呈现复杂的数据
  • layui入门使用文档(包含几个重要的组件)