乐趣区

关于java:玩转-Flowable-流程实例

@[toc]
上篇文章松哥和大家聊了 Flowable 中的流程部署问题,明天咱们持续来聊聊流程实例。

部署之后的流程,这个还不能间接运行,例如咱们部署了一个销假流程,当初 zhangsan 想要销假,他就须要开启一个销假流程,lisi 想销假,他也须要开启一个销假流程,这一个一个开启的销假流程就是流程实例,而咱们一开始部署的销假流程,则相似于一个模版,基于此模版,咱们能够开启很多个具体的流程实例。从这个角度来说,上篇文章咱们定义的 ProcessDefinition 就相似于一个 Java 类,明天咱们要介绍的 ProcessInstance 则相当于一个 Java 对象。

1. 捋清三个概念

首先咱们须要先捋清三个概念:

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

流程定义

流程定义 ProcessDefinition 这个好说,其实就是咱们上篇文章中和大家介绍的内容。将一个流程 XML 文件部署到 flowable 中,这就是一个定义好的流程了,基于这个定义好的流程,咱们能够开启很多流程实例。

流程实例

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

执行实例

执行实例 Execution 略微有点难以了解。

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

流程实例通常是执行实例的根结点,即在一个流程中,进口和入口能够算是一个流程实例的节点,而两头的过程则是执行实例。

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

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

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

好啦,三个基本概念先捋分明。

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:

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

退出移动版