Juega con la instancia de proceso de Flowable

@[toc] En el último artículo, Song Ge habló contigo sobre la implementación de procesos en Flowable. Hoy continuaremos hablando sobre instancias de procesos.

El proceso posterior a la implementación no se puede ejecutar directamente. Por ejemplo, hemos implementado un proceso de licencia. Ahora, si Zhangsan quiere solicitar una licencia, debe iniciar un proceso de licencia. Si Lisi quiere solicitar una licencia, también debe iniciar un proceso de licencia. El proceso de solicitud de licencia es una instancia de proceso, y el proceso de solicitud de licencia que implementamos al principio es similar a una plantilla. Con base en esta plantilla, podemos abrir muchas instancias de proceso específicas. Desde este punto de vista, la ProcessDefinition que definimos en el último artículo es similar a una clase Java, y la ProcessInstance que vamos a presentar hoy es equivalente a un objeto Java.

1. Aclarar tres conceptos

En primer lugar, debemos aclarar tres conceptos:

  • Definición de proceso Definición de proceso
  • Instancia de proceso ProcessInstance
  • Instancia de ejecución Ejecución

Definición de proceso

Definición de proceso ProcessDefinition Esto es fácil de decir, pero en realidad es lo que le presentamos en el último artículo . La implementación de un archivo XML de proceso en flowable es un proceso definido. Según este proceso definido, podemos abrir muchas instancias de proceso.

Instancia de proceso

Una instancia de proceso ProcessInstance es un proceso iniciado a través de una definición de proceso. Representa la rama de proceso más grande de un proceso de principio a fin. En un proceso, solo hay una instancia de proceso, y la relación entre las instancias de proceso y las definiciones de proceso es similar a Objetos Java y Relaciones entre clases Java.

Instancia de ejecución

Instancia de ejecución La ejecución es un poco difícil de entender.

En primer lugar, en términos de relación de clases, ProcessInstance es una subclase de Execution.

La instancia de proceso suele ser el nodo raíz de la instancia de ejecución, es decir, en un proceso, la salida y la entrada pueden considerarse como los nodos de una instancia de proceso, y el proceso intermedio es la instancia de ejecución.

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

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

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

好啦,三个基本概念先捋清楚。

2. 五种流程启动方式

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

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

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

  1. 通过流程的 key 去启动

也可以通过流程定义的 key 去启动一个流程,根据上篇文章的介绍,大家知道,这个流程定义的 key 其实就是流程 XML 文件中的 id,这个对应的方法名是 RuntimeService#startProcessInstanceByKey

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

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

  1. 通过流程的 message 去启动

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

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

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

3. 简单实践

首先我们绘制一个简单的流程图,然后按照上篇文章所介绍的方式进行部署,流程图如下:

流程 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,这个变量名是自定义的,定义好之后,将来我就可以在其他节点中就可以使用这个变量了。

很简单的流程,其中:

  • 提交请假申请是由流程的发起人完成。
  • 主管是 zhangsan。
  • 经理是 lisi。

好了,先按照上篇文章我们介绍的方式部署流程。

接下来我们要启动流程,假设我们用流程定义的 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 表中查询的。

4. 删除流程实例

如果我们想删除一个流程实例,操作方式如下:

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

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

5. 获取运行的活动节点

可以根据执行实例的 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:

好啦,流程实例先聊这么多,下篇文章我们继续~

Supongo que te gusta

Origin juejin.im/post/7147581964323651591
Recomendado
Clasificación