likes
comments
collection
share

梳理流程引擎 Flowable 四大常见任务

作者站长头像
站长
· 阅读数 15

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

本专栏第三篇已发布,尚未看过的小伙伴请移步这里:

  1. Flowable 开篇,流程引擎扫盲
  2. 通过 Flowable-UI 来体验一把 Flowable 流程引擎
  3. 搞懂 Flowable 中的流程定义和流程实例

今天这篇文章,松哥和大家梳理一下 Flowable 中常见的四种任务以及其对应的玩法。

1. ReceiveTask

1.1 使用场景

接受任务(Receive Task),接触过 Flowable 的小伙伴应该是见过或者听说过,它的图标如下图:

梳理流程引擎 Flowable 四大常见任务

ReceiveTask 可以算是 Flowable 中最简单的一种任务,当该任务到达的时候,它不做任何逻辑,而是被动地等待用户 Trigger。

ReceiveTask 往往适用于一些不明确的阻塞,例如:一个复杂的计算需要等待很多条件,这些条件是需要人为来判断是否可以执行,而不是直接执行,这个时候,工作人员如果判断可以继续了,那么就 Trigger 一下使流程继续向下执行。

基于以上介绍,ReceiveTask 还有一个中文名字叫做等待任务,也就是说,流程走到 ReceiveTask 这个节点的时候,就卡住了,需要用户手动点一下,流程才会继续向下走。

1.2 实践

1.2.1 绘制流程图

我们绘制一个简单的流程图来看下 ReceiveTask 到底是啥样子,流程图如下:

梳理流程引擎 Flowable 四大常见任务

ReceiveTask 图标上有一个信封。

小伙伴们绘制的时候,首先选择用户任务:

梳理流程引擎 Flowable 四大常见任务

然后点击设置按钮,将用户任务切换为 ReceiveTask 即可:

梳理流程引擎 Flowable 四大常见任务

绘制完成后,我们下载这个流程图对应的 XML 文件。

来看看,带 ReceiveTask 的流程图是下面这样的:

<process id="receiveTask_demo" name="接收任务测试流程" isExecutable="true">
  <documentation>接收任务测试流程</documentation>
  <startEvent id="startEvent" flowable:formFieldValidation="true"></startEvent>
  <sequenceFlow id="sid-9E7B327E-EFC8-4D29-8C6F-157D5E1B7A4E" sourceRef="startEvent" targetRef="todaySales"></sequenceFlow>
  <receiveTask id="todaySales" name="统计今日销售额"></receiveTask>
  <receiveTask id="sendMsg" name="发送今日销售业绩给老板"></receiveTask>
  <endEvent id="endEvent"></endEvent>
  <sequenceFlow id="s2" sourceRef="todaySales" targetRef="sendMsg"></sequenceFlow>
  <sequenceFlow id="s3" sourceRef="sendMsg" targetRef="endEvent"></sequenceFlow>
</process>

1.2.2 部署

这个松哥在之前的文章中已经反复介绍过多次了,这里就不再赘述了,大家参考我们之前的文章部署并启动上面这个流程。

1.2.3 分析

当流程启动之后,按照我们前面文章的分析,我们先去数据库中 ACT_RU_TASK 表进行查看,发现该表空空如也。也就是 ReceiveTask 并不会被记录在 ACT_RU_TASK 表中,他们只是单纯的被记录在 ACT_RU_EXECUTION 表中,因为在该表中,我们可以查看 ReceiveTask 的记录。

对于 ReceiveTask 的触发方式也很简单,如下:

@Test
void test10() {
    List<Execution> list = runtimeService.createExecutionQuery().activityId("todaySales").list();
    for (Execution execution : list) {
        runtimeService.trigger(execution.getId());
    }
}

由于 ReceiveTask 的触发需要传入的参数是执行实例 ID 而不是流程实例 ID,所以我们要查询出来当前待触发的执行实例 ID。具体的查询方式就是根据 ReceiveTask 的节点名称去查询。

查询到执行实例 ID 之后,调用 trigger 方法完成触发,使得流程继续向下走。

好啦,现在流程进入到发送今日销售业绩给老板这个环节了,老办法继续查询并执行:

@Test
void test10() {
    List<Execution> list = runtimeService.createExecutionQuery().activityId("sendMsg").list();
    for (Execution execution : list) {
        runtimeService.trigger(execution.getId());
    }
}

这个执行完层后,这个流程就结束了。现在我们去查看 ACT_RU_ACTINST 表已经空了,查看 ACT_RU_EXECUTION 表也空了。

2. UserTask

UserTask 看名字就知道,需要人工干预,而人工处理的方式有很多种,我们可以设置节点是由哪个用户处理,也可以设置是由哪个用户组来处理(相当于是由哪个角色来处理)。

现在,假设我有如下一个简单的流程图:

梳理流程引擎 Flowable 四大常见任务

那么我该如何设置这个用户节点的处理人呢?

2.1 指定具体用户

第一种方式,是我们在绘制流程图的时候,可以选中这个节点,然后直接设置流程的处理人,像下面这样:

梳理流程引擎 Flowable 四大常见任务

然后在打开的窗口中选择固定值,设置具体分配的用户是 javaboy,如下图:

梳理流程引擎 Flowable 四大常见任务

好了,现在这个节点就固定的由一个名为 javaboy 的用户去处理了。

对应的 XML 文件如下:

<process id="demo01" name="demo01" isExecutable="true">
  <documentation>demo01</documentation>
  <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
  <userTask id="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" flowable:assignee="javaboy" 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-71FB3A81-F753-419D-9A0A-2FC6E5361CED" sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
  <endEvent id="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></endEvent>
  <sequenceFlow id="sid-DEBE03CD-F247-4EF3-BB67-ABBA94739B0A" sourceRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" targetRef="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></sequenceFlow>
</process>

在上面这段 XML 中,小伙伴们看到 UserTask 节点中有一个 flowable:assignee="javaboy",这句话就是设置这个 UserTask 的处理人。

接下来,我们部署并启动这个流程(具体的部署启动方式可以参考本系列之前的文章),启动之后,我们可以在数据库的 ACT_RU_TASK 表中看到,这个 UserTask 的处理人是 javaboy,如下图:

梳理流程引擎 Flowable 四大常见任务

现在我们可以通过 Java 代码去查询 javaboy 需要处理的 UserTask 了,如下:

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

这个查询,本质上其实就是去 ACT_RU_TASK 表中查询的,我们来看看执行的 SQL:

梳理流程引擎 Flowable 四大常见任务

查询到这个任务之后,javaboy 有两种选择:

  1. 将这个任务指定给另外一个人,例如 zhangsan。
  2. 自己处理。

2.1.1 重新指定任务处理人

假设 javaboy 查询到自己的任务之后,想把这个任务交给 zhangsan 去处理,方式如下:

@Autowired
TaskService taskService;
@Test
void test11() {
    List<Task> list = taskService.createTaskQuery().taskAssignee("javaboy").list();
    for (Task task : list) {
        taskService.setAssignee(task.getId(),"zhangsan");
    }
}

这行代码执行完成后,我们看数据库中的 ACT_RU_TASK 表,还是刚才那条记录,但是处理人变了,变成了 zhangsan:

梳理流程引擎 Flowable 四大常见任务

小伙伴们看到,版本号从 1 变为 2 了,说明这条记录被更新过了,处理人则从 javaboy 变为了 zhangsan。

最后我们再来看下这个操作所执行的 SQL,来验证一下我们前面的结论:

梳理流程引擎 Flowable 四大常见任务

小伙伴们注意看这里执行的 SQL,以及对应的参数,说明我们上面的分析是没有问题的。

2.1.2 自己处理

如果 javaboy 想自己处理这个任务也是可以的,方式如下:

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

处理完成后,ACT_RU_TASK 表中的记录也会被自动删除掉(执行过的 UserTask 会被自动删除)。

这种方式是指定了具体的用户,很显然这种硬编码的方式使用起来很不方便,我们需要的是能够动态指定任务处理人的方式。

2.2. 通过变量设置

如果想动态指定 UserTask 的处理人,则可以通过变量来实现,具体方式如下:

在绘制流程图的时候,还是指定流程的具体处理人,但是在指定的时候,使用变量代替,如下图:

梳理流程引擎 Flowable 四大常见任务

这里的 #{manager} 表示这个 UserTask 由一个名为 manager 的变量来指定,此时的 XML 文件则是下面这样:

<process id="demo01" name="demo01" isExecutable="true">
  <documentation>demo01</documentation>
  <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
  <userTask id="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" flowable:assignee="#{manager}" 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-71FB3A81-F753-419D-9A0A-2FC6E5361CED" sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
  <endEvent id="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></endEvent>
  <sequenceFlow id="sid-DEBE03CD-F247-4EF3-BB67-ABBA94739B0A" sourceRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" targetRef="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></sequenceFlow>
</process>

小伙伴们看到,UserTask 节点中的 flowable:assignee="#{manager}" 就表示这个 UserTask 的处理人由 manager 变量指定。

对于这样的流程,我们在上一个节点处就需要指定下一个节点的处理人,对于当前案例来说,当然是要在流程启动的时候,指定这个 UserTask 的处理人,方式如下:

@Test
void test01() {
    Map<String, Object> variables = new HashMap<>();
    variables.put("manager", "javaboy");
    ProcessInstance pi = runtimeService.startProcessInstanceByKey("demo01",variables);
    logger.info("id:{},activityId:{}", pi.getId(), pi.getActivityId());
}

当流程启动成功之后,大家去查看 ACT_RU_TASK 表,就可以看到,有一个待处理的 UserTask,处理人是 javaboy,如下图:

梳理流程引擎 Flowable 四大常见任务

能看到这条记录,就说明这个 UserTask 的处理人我们已经设置成功了。

接下来具体的处理逻辑,则参考 1.1 和 1.2 小节。

2.3. 通过监听器设置

当然,我们也可以通过监听器来设置任务的处理人。具体方式如下:

首先我们在绘制流程图的时候,不需要给 UserTask 分配用户,如下图:

梳理流程引擎 Flowable 四大常见任务

然后我们为这个 UserTask 设置一个任务监听器,步骤如下:

梳理流程引擎 Flowable 四大常见任务

首先点击 + 号,然后选择 CREATE 事件,最后再给出事件对应的实体类,如下:

梳理流程引擎 Flowable 四大常见任务

当然这个实体类是我们项目中真实存在的一个类,如下:

public class MyTaskListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        delegateTask.setAssignee("javaboy");
    }
}

当这个 UserTask 创建的时候,就会触发这个监听器,为该 UserTask 设置处理人。

我们来看看这个流程图对应的 XML 文件是什么样子的:

<process id="demo01" name="demo01" isExecutable="true">
  <documentation>demo01</documentation>
  <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
  <userTask id="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" flowable:formFieldValidation="true">
    <extensionElements>
      <flowable:taskListener event="create" class="org.javaboy.flowableidm.MyTaskListener"></flowable:taskListener>
    </extensionElements>
  </userTask>
  <sequenceFlow id="sid-71FB3A81-F753-419D-9A0A-2FC6E5361CED" sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
  <endEvent id="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></endEvent>
  <sequenceFlow id="sid-DEBE03CD-F247-4EF3-BB67-ABBA94739B0A" sourceRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" targetRef="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></sequenceFlow>
</process>

小伙伴们看到,event="create" class="org.javaboy.flowableidm.MyTaskListener" 就是我们设置的内容了。

现在我们部署并启动这个流程,当我们流程启动后,就可以在 ACT_RU_TASK 表中看到一条 javaboy 待处理的任务了。

2.4. 其他情况

最后再来说说一种特殊情况,就是这个 UserTask 由任务的发起人处理,任务是谁发起的,谁来处理人这个 UserTask。

这个首先需要在流程启动事件上设置任务的发起人变量名,如下,流程的启动节点,然后设置任务的发起人:

梳理流程引擎 Flowable 四大常见任务

接下来,在给 UserTask 设置处理人的时候,设置处理人和任务的发起人的变量是同一个,如下图:

梳理流程引擎 Flowable 四大常见任务

好啦,这就可以了。来看看对应的 XML 文件:

<process id="demo01" name="demo01" isExecutable="true">
  <documentation>demo01</documentation>
  <startEvent id="startEvent1" flowable:initiator="INITATOR" flowable:formFieldValidation="true"></startEvent>
  <userTask id="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" flowable:assignee="#{INITATOR}" 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-71FB3A81-F753-419D-9A0A-2FC6E5361CED" sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
  <endEvent id="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></endEvent>
  <sequenceFlow id="sid-DEBE03CD-F247-4EF3-BB67-ABBA94739B0A" sourceRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" targetRef="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></sequenceFlow>
</process>

startEvent 中有一个 flowable:initiator="INITATOR" 表示设置流程发起人的变量为 INITATOR。后续在 UserTask 中使用该变量即可。

将这个流程部署成功之后,按照如下方式启动流程:

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

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

前面都是针对单个任务处理人,有的时候,一个任务节点会存在多个候选人,例如 zhangsan 提交一个任务,这个任务即可以 lisi 处理,又可以 wangwu 处理,那么针对这种多个任务候选人的情况,我们该如何处理?

2.5 流程图指定多个候选人

首先我们还是使用之前旧的流程图,但是在为 UserTask 设置分配用户的时候,我们设置多个用户,如下图:

梳理流程引擎 Flowable 四大常见任务

设置完成后,我们下载这个流程文件,来看下对应的 XML 文件,内容如下:

<process id="demo01" name="demo01" isExecutable="true">
  <documentation>demo01</documentation>
  <startEvent id="startEvent1" flowable:initiator="INITATOR" flowable:formFieldValidation="true"></startEvent>
  <userTask id="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" flowable:candidateUsers="javaboy,zhangsan,lisi" flowable:formFieldValidation="true"></userTask>
  <sequenceFlow id="sid-71FB3A81-F753-419D-9A0A-2FC6E5361CED" sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
  <endEvent id="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></endEvent>
  <sequenceFlow id="sid-DEBE03CD-F247-4EF3-BB67-ABBA94739B0A" sourceRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" targetRef="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></sequenceFlow>
</process>

小伙伴们看到,UserTask 中的 flowable:candidateUsers="javaboy,zhangsan,lisi" 就表示这个 UserTask 由 javaboy、zhangsan 和 lisi 三个用户处理,用户名之间用 , 隔开。

2.6 查询任务处理人

接下来我们部署并启动上面这个流程,具体如何部署如何启动,这个在之前的文章中松哥已经和大家聊过了,这里不再赘述。

当流程启动成功之后,现在我们很容易想到像之前文章那样,去查询 javaboy 需要处理的 UserTask,如下:

List<Task> list = taskService.createTaskQuery().taskAssignee("javaboy").list();
for (Task task : list) {
    logger.info("id:{};name:{};taskDefinitionKey:{}", task.getId(), task.getName(), task.getTaskDefinitionKey());
}

但是我们却发现这个 SQL 执行完成后,查询不到任何数据!为什么呢?我们来分析下。

经过前面几篇文章的介绍,现在小伙伴们都知道了,上面这个方法最终查询的是数据库中的 ACT_RU_TASK 表,查询的 SQL 如下:

梳理流程引擎 Flowable 四大常见任务

那我们就去检查 ACT_RU_TASK 表以及它的 ASSIGNEE_ 字段,结果如下:

梳理流程引擎 Flowable 四大常见任务

我们发现 ACT_RU_TASK 表中记录的 ASSIGNEE_ 字段值为 null!

为 null 这个其实也好理解,毕竟这个 UserTask 有多个人可以处理,但是只有一个字段,没法储存,肯定有其他存储方式。

好啦,不和大家卖关子了,这种有多个候选人的任务,我们应该按照如下方式来查询:

@Test
void test12() {
    List<Task> list = taskService.createTaskQuery().taskCandidateUser("javaboy").list();
    for (Task task : list) {
        logger.info("id:{};name:{};taskDefinitionKey:{}", task.getId(), task.getName(), task.getTaskDefinitionKey());
    }
}

小伙伴们看到,这里应该调用 taskCandidateUser 方法进行处理。那么这个方法查询的是哪张表呢?我们来看下上面方法最终执行的 SQL,如下:

: ==>  Preparing: SELECT RES.* from ACT_RU_TASK RES WHERE RES.ASSIGNEE_ is null and exists(select LINK.ID_ from ACT_RU_IDENTITYLINK LINK where LINK.TYPE_ = 'candidate' and LINK.TASK_ID_ = RES.ID_ and ( LINK.USER_ID_ = ? ) ) order by RES.ID_ asc
: ==> Parameters: javaboy(String)
: <==      Total: 1

小伙伴们看到,这里的查询涉及到两张表,分别是 ACT_RU_TASKACT_RU_IDENTITYLINK,两张表联合查询查出来的,那我们来看看 ACT_RU_IDENTITYLINK 表的内容:

梳理流程引擎 Flowable 四大常见任务

小伙伴们看到,TYPE_ 为 candidate 的就表示这个 Task 的候选人,id 为 c5693038-3f42-11ed-b9e2-acde48001122 的 Task 一共有三个候选人,两张表联合查询,才可以查到这个 UserTask 该由谁来处理。

另外一种常见的需求就是,已经知道了要处理的流程实例了,但是不知道应该由谁来处理,此时通过查询 ACT_RU_IDENTITYLINK 表就可以确定一个流程实例都有哪些参与者,如下:

@Test
void test13() {
    List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().list();
    for (ProcessInstance pi : list) {
        List<IdentityLink> identityLinksForProcessInstance = runtimeService.getIdentityLinksForProcessInstance(pi.getId());
        for (IdentityLink identityLink : identityLinksForProcessInstance) {
            logger.info("ProcessInstanceId:{},UserId:{}",identityLink.getProcessInstanceId(),identityLink.getUserId());
        }
    }
}

我们来看看上面这个执行的 SQL,如下:

梳理流程引擎 Flowable 四大常见任务

可以看到,其实就是通过查询 ACT_RU_IDENTITYLINK 表获取我们想要的数据。

2.7 认领任务

对于这种有候选人的任务,我们需要先认领,再处理。认领的本质,其实就是给 ACT_RU_TASK 表中,这个 UserTask 记录的 ASSIGNEE_ 字段设置上值。

认领任务的方式如下:

@Test
void test12() {
    List<Task> list = taskService.createTaskQuery().taskCandidateUser("javaboy").list();
    for (Task task : list) {
        taskService.claim(task.getId(),"javaboy");
    }
}

认领之后,我们再来看 ACT_RU_TASK 表中的数据,如下:

梳理流程引擎 Flowable 四大常见任务

可以看到,此时 ASSIGNEE_ 字段就有值了,同时 CLAIM_TIME 字段也记录了任务的认领时间。

再来看看任务认领执行的 SQL,基本上和我们所想的一致。

梳理流程引擎 Flowable 四大常见任务

2.8 处理任务

认领后的任务该如何处理,这个就和我们上篇文章中介绍的方式一致了,如下:

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

具体原理上篇文章中已经介绍过了,这里就不再赘述了。

任务执行完成后,ACT_RU_IDENTITYLINK 表中的记录也会随之删除。

2.9 变量与监听器

前面这种方式设置的任务候选人我们是在绘制流程图的时候直接硬编码的,这显然不是一个好办法。如果能通过变量来传递任务的候选人,就会方便很多。

2.9.1 候选人变量

我们可以在绘制流程图的时候,用变量代替直接指定候选人,方式如下:

梳理流程引擎 Flowable 四大常见任务

此时,生成的流程 XML 文件中,UserTask 节点的处理人也就变成了下面这个样子:

<process id="demo01" name="demo01" isExecutable="true">
  <documentation>demo01</documentation>
  <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
  <userTask id="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" flowable:candidateUsers="${userIds}" flowable:formFieldValidation="true"></userTask>
  <sequenceFlow id="sid-71FB3A81-F753-419D-9A0A-2FC6E5361CED" sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
  <endEvent id="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></endEvent>
  <sequenceFlow id="sid-DEBE03CD-F247-4EF3-BB67-ABBA94739B0A" sourceRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" targetRef="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></sequenceFlow>
</process>

UserTask 节点中的 flowable:candidateUsers="${userIds}" 就表示流程的处理人由 userIds 变量控制。

好了,接下来我们来启动流程,注意,此时启动流程需要传递 userIds 变量,如下:

@Test
void test01() {
    Map<String, Object> userIds = new HashMap<>();
    userIds.put("userIds", "javaboy,zhangsan,lisi");
    ProcessInstance pi = runtimeService.startProcessInstanceByKey("demo01",userIds);
    logger.info("id:{},activityId:{}", pi.getId(), pi.getActivityId());
}

多个用户之间,用英文 , 隔开。

好了,流程启动成功后,接下来的操作参考 3、4 小节,这里我就不再赘述了。

2.9.2 监听器

当然,我们也可以通过监听器来为 UserTask 设置多个候选处理人用户,首先我们创建一个监听器如下:

public class MyUserTaskListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        delegateTask.addCandidateUser("javaboy");
        delegateTask.addCandidateUser("zhangsan");
        delegateTask.addCandidateUser("lisi");
    }
}

然后在绘制流程图的时候,删除掉 UserTask 分配的用户,然后重新为 UserTask 设置一个监听器:

梳理流程引擎 Flowable 四大常见任务

然后设置一个在创建 UserTask 的时候触发的监听器:

梳理流程引擎 Flowable 四大常见任务

然后我们下载这个流程图对应的 XML 文件,如下:

<process id="demo01" name="demo01" isExecutable="true">
  <documentation>demo01</documentation>
  <startEvent id="startEvent1" flowable:initiator="INITATOR" flowable:formFieldValidation="true"></startEvent>
  <userTask id="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" flowable:formFieldValidation="true">
    <extensionElements>
      <flowable:taskListener event="create" class="org.javaboy.flowableidm.MyUserTaskListener"></flowable:taskListener>
    </extensionElements>
  </userTask>
  <sequenceFlow id="sid-71FB3A81-F753-419D-9A0A-2FC6E5361CED" sourceRef="startEvent1" targetRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3"></sequenceFlow>
  <endEvent id="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></endEvent>
  <sequenceFlow id="sid-DEBE03CD-F247-4EF3-BB67-ABBA94739B0A" sourceRef="sid-5F901234-AFF1-480E-9D66-2D196B910BA3" targetRef="sid-D0B9E5BF-8C1A-4F8F-B2C2-F423F5DC556D"></sequenceFlow>
</process>

可以看到,在 userTask 节点中,通过 extensionElements 指定了额外的监听器。

好啦,这个流程现在就可以直接启动了,启动时也不需要额外的变量。

流程启动成功后,接下来的操作参考 3、4 小节,这里我就不再赘述了。

2.10 任务回退

当一个任务认领(Claim)之后,但是又不想处理,此时我们可以将任务退回。方式如下:

@Test
void test16() {
    List<Task> list = taskService.createTaskQuery().taskAssignee("javaboy").list();
    for (Task task : list) {
        taskService.setAssignee(task.getId(), null);
    }
}

其实思路很简答,就是重新为任务设置处理人,且处理人为 null,这就是将任务回退了,接下来其他人可以重新认领该任务了。

2.11 修改任务候选人

2.11.1 增加

任务候选人也不是一成不变的,也可以动态修改,当一个流程启动之后,流程已经走到某一个 Task 了,此时我们想要修改该 Task 的候选人,也是可以的,方式如下:

@Test
void test17() {
    List<Task> list = taskService.createTaskQuery().taskCandidateUser("javaboy").list();
    for (Task task : list) {
        taskService.addCandidateUser(task.getId(),"wangwu");
    }
}

添加完成后,查看 ACT_RU_IDENTITYLINK 表,我们发现 wangwu 已经添加进来了:

梳理流程引擎 Flowable 四大常见任务

2.11.2 删除

如果想要删除一个候选人,方式如下:

@Test
void test18() {
    List<Task> list = taskService.createTaskQuery().taskCandidateUser("javaboy").list();
    for (Task task : list) {
        taskService.deleteCandidateUser(task.getId(), "wangwu");
    }
}

删除成功之后,ACT_RU_IDENTITYLINK 表中对应的数据也就被清除掉了。

2.12 查询历史数据

如果一个流程执行结束了,我们还想查询这个流程曾经涉及到的参与者,可以通过如下方式查询:

@Test
void test14() {
    List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery().list();
    for (HistoricProcessInstance historicProcessInstance : list) {
        List<HistoricIdentityLink> links = historyService.getHistoricIdentityLinksForProcessInstance(historicProcessInstance.getId());
        for (HistoricIdentityLink link : links) {
            logger.info("userId:{},taskId:{},type:{},processInstanceId:{}",link.getUserId(),link.getTaskId(),link.getType(),link.getProcessInstanceId());
        }
    }
}

这里最终其实就是去 ACT_HI_IDENTITYLINK 表中查询,对应的 SQL 如下:

梳理流程引擎 Flowable 四大常见任务

上面这是查询一个流程的参与人,当然我们也可以查询一个 Task 的候选人与处理人,如下:

@Test
void test15() {
    List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().list();
    for (HistoricTaskInstance historicTaskInstance : list) {
        List<HistoricIdentityLink> links = historyService.getHistoricIdentityLinksForTask(historicTaskInstance.getId());
        for (HistoricIdentityLink link : links) {
            logger.info("userId:{},taskId:{},type:{},processInstanceId:{}", link.getUserId(), link.getTaskId(), link.getType(), link.getProcessInstanceId());
        }
    }
}

查询对应的 SQL 如下:

梳理流程引擎 Flowable 四大常见任务

和我们所想的基本一致。

前面松哥和大家分享的都是给 UserTask 设置处理人或者是候选用户,不过小伙伴们也知道,在我们为 UserTask 设置处理人的时候,除了设置单个的处理人,也可以设置 Group,就是某一个用户组内的所有用户都可以处理该 Task。

在 Flowable 中使用 Group 去归类某一类用户,但是这个实际上类似于我们在自己系统中平时所用的角色 Role。也就是说,我们可以按照角色去给每一个 UserTask 设置处理人。

接下来松哥就来和小伙伴们聊一聊这里的一些细节。

2.13 用户与用户组

首先我们先来看下用户组的一些基本操作。

2.13.1 添加组

组的属性相对来说少一些,添加方式和 user 比较像:

@Test
void test09() {
    GroupEntityImpl g = new GroupEntityImpl();
    g.setName("组长");
    g.setId("leader");
    g.setRevision(0);
    identityService.saveGroup(g);
}

添加之后,组的信息保存在 ACT_ID_GROUP 表中,如下图:

梳理流程引擎 Flowable 四大常见任务

组创建好之后,接下来还要给组添加用户,添加方式如下:

identityService.createMembership("zhangsan", "leader");
identityService.createMembership("lisi", "leader");

这就是设置 zhangsan 和 lisi 是组长(注意用户和组的关联关系表中有外键,所以需要确保两个参数都是真实存在的)。

添加了关联关系之后,我们再去查看 ACT_ID_MEMBERSHIP 表,如下:

梳理流程引擎 Flowable 四大常见任务

掉用如下方法可以删除关联关系:

identityService.deleteMembership("zhangsan","leader");

2.13.2 修改组

如下,将 id 为 leader 的组名更新为主管,如下:

Group g = identityService.createGroupQuery().groupId("leader").singleResult();
g.setName("主管");
identityService.saveGroup(g);

2.13.3 删除组

删除组方式如下:

identityService.deleteGroup("leader");

删除组的同时,也会删除掉组和用户之间的关联关系,不过不用担心用户被删除。

2.13.4 查询组

可以根据 id 或者 name 或者组员信息等去查询组:

//根据 id 查询组信息
Group g1 = identityService.createGroupQuery().groupId("leader").singleResult();
System.out.println("g1.getName() = " + g1.getName());
//根据 name 查询组信息
Group g2 = identityService.createGroupQuery().groupName("组长").singleResult();
System.out.println("g2.getId() = " + g2.getId());
//根据用户查询组信息(组里包含该用户)
List<Group> list = identityService.createGroupQuery().groupMember("zhangsan").list();
for (Group group : list) {
    System.out.println("group.getName() = " + group.getName());
}

2.14 设置候选组

在我们绘制流程图的时候,我们可以为 UserTask 设置一个候选组,方式如下:

梳理流程引擎 Flowable 四大常见任务

从这个地方大家也可以看到,后选择是可以给多个的。

好了,设置完成后,我们下载流程图的 XML 文件,然后来看下这个地方与众不同之处:

<process id="demo01" name="测试流程" isExecutable="true">
  <documentation>测试流程</documentation>
  <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
  <userTask id="sid-F2F3C468-79B9-447B-943F-7CD18CE9BECF" flowable:candidateGroups="leader" flowable:formFieldValidation="true"></userTask>
  <sequenceFlow id="sid-79C79920-2AD8-48FE-A59C-CC4D23C1895D" sourceRef="startEvent1" targetRef="sid-F2F3C468-79B9-447B-943F-7CD18CE9BECF"></sequenceFlow>
  <endEvent id="sid-2236991E-3643-4590-9001-E22C256CA584"></endEvent>
  <sequenceFlow id="sid-51105EB7-07F6-4190-9B2E-8F1F20A307D1" sourceRef="sid-F2F3C468-79B9-447B-943F-7CD18CE9BECF" targetRef="sid-2236991E-3643-4590-9001-E22C256CA584"></sequenceFlow>
</process>

小伙伴们看到,flowable:candidateGroups="leader" 就表示这个任务由一个候选用户组来处理,如果有多个候选的用户组,则不同用户组之间用 , 隔开。

当然,这是硬编码。如果想像候选用户一样,通过动态变量来传递用户组名称也是可以的,具体做法像下面这样:

梳理流程引擎 Flowable 四大常见任务

这样,最终生成的 XML 文件则类似这样: flowable:candidateGroups="${g1}"

2.15 根据用户组查询任务

接下来,我们部署并启动一个流程,具体的部署和启动方式松哥在之前的文章中都已经和大家介绍过了,这里简单看下方法就行了:


@Test
void test01() {
    Map<String, Object> variables = new HashMap<>();
    variables.put("g1", "leader");
    ProcessInstance pi = runtimeService.startProcessInstanceByKey("demo01",variables);
    logger.info("id:{},activityId:{}", pi.getId(), pi.getActivityId());
}

这个就是流程启动的过程,注意启动的时候加了参数,用来描述下一个 UserTask 的处理组。

启动成功之后,我们可以在 ACT_RU_IDENTITYLINK 表中查看到用户组和 UserTask 之间的关系:

梳理流程引擎 Flowable 四大常见任务

接下来我们可以按照查询候选人任务的方式,查询 zhangsan 需要完成的工作,如下:

@Test
void test19() {
    List<Task> list = taskService.createTaskQuery().taskCandidateUser("zhangsan").list();
    for (Task task : list) {
        logger.info("name:{},createTime:{}", task.getName(), task.getCreateTime());
    }
}

这个查询的内部实现,我们可以拆分为两步:

  1. 查询出来 zhangsan 是属于哪个 group,这个查询执行的 SQL 如下:
SELECT RES.* from ACT_ID_GROUP RES WHERE exists(select 1 from ACT_ID_MEMBERSHIP M where M.GROUP_ID_ = RES.ID_ and M.USER_ID_ = ?) order by RES.ID_ asc

这个查询中有一个参数,参数的值就是 zhangsan,上面这个 SQL 可以查询出来 zhangsan 这个用户属于 leader 这个分组,在接下来的查询中,会 zhangsan 和 leader 两个参数都会用到。

  1. 查询 zhangsan 或者 leader 的任务,执行 SQL 如下:
SELECT RES.* from ACT_RU_TASK RES WHERE RES.ASSIGNEE_ is null and exists(select LINK.ID_ from ACT_RU_IDENTITYLINK LINK where LINK.TYPE_ = 'candidate' and LINK.TASK_ID_ = RES.ID_ and ( LINK.USER_ID_ = ? or ( LINK.GROUP_ID_ IN ( ? ) ) ) ) order by RES.ID_ asc

可以看到,这个查询里,有两个参数了,两个参数的值分别是 zhangsan 和 leader。

也就是说,虽然我们这里代码写的是按照 zhangsan 去查询,实际上查询的是 zhangsan 所属的用户组的 Task(这个逻辑也好理解,因为 zhangsan 所属的用户组的 Task 实际上也就是 zhangsan 的 Task)。

当然,我们也可以直接按照 group 去查询,如下:

@Test
void test20() {
    List<Task> list = taskService.createTaskQuery().taskCandidateGroup("leader").list();
    for (Task task : list) {
        logger.info("name:{},createTime:{}", task.getName(), task.getCreateTime());
    }
}

这个查询原理跟上面的差不多,不过省事的是,这里一条 SQL 就搞定了(不需要根据用户名查询用户所属的分组了),如下:

SELECT RES.* from ACT_RU_TASK RES WHERE RES.ASSIGNEE_ is null and exists(select LINK.ID_ from ACT_RU_IDENTITYLINK LINK where LINK.TYPE_ = 'candidate' and LINK.TASK_ID_ = RES.ID_ and ( ( LINK.GROUP_ID_ IN ( ? ) ) ) ) order by RES.ID_ asc

好啦,当这些任务查询出来后,接下来该如何执行,就和前面介绍的内容一样了,我这里就不再赘述了。

3. ServiceTask

ServiceTask 从名字上看就是服务任务,它的图标一般是像下面这样:

梳理流程引擎 Flowable 四大常见任务

ServiceTask 一般由系统自动完成,当流程走到这一步的时候,不会自动停下来,而是会去执行我们提前在 ServiceTask 中配置好的方法。

3.1 实践

我们通过一个简单的例子来看一下 ServiceTask 要怎么玩。

假设我有如下一个简单的流程图:

梳理流程引擎 Flowable 四大常见任务

中间这个就是一个 ServiceTask。

当流程执行到 ServiceTask 的时候,具体要做哪些事情?有三种不同的方式来设置这里的任务,我们分别来看。

3.1.1 监听类

首先我们可以设置一个监听类,这个监听类有一个硬性规定就是需要实现 JavaDelegate 接口,像下面这样:

public class MyServiceTask implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("========MyServiceTask==========");
    }
}

在这个监听类中我们可以完成一些操作,通过这个 execution 也可以获取到在流程节点之间传输的变量。

这个类定义好之后,接下来我们在流程定义的时候,配置这个类的全路径即可,如下图:

梳理流程引擎 Flowable 四大常见任务

这个配置对应的 XML 内容如下:

  <process id="demo01" name="测试流程" isExecutable="true">
    <documentation>测试流程</documentation>
    <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
    <sequenceFlow id="sid-33A78082-C2FD-48BE-8B87-99FB20F0B331" sourceRef="startEvent1" targetRef="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8"></sequenceFlow>
    <serviceTask id="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8" flowable:class="org.javaboy.flowableidm.MyServiceTask"></serviceTask>
    <endEvent id="sid-A5F11956-15EA-4574-98D0-29A4E3DB5495"></endEvent>
    <sequenceFlow id="sid-0698809E-0A6C-4B92-A167-AE96A8CB75F2" sourceRef="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8" targetRef="sid-A5F11956-15EA-4574-98D0-29A4E3DB5495"></sequenceFlow>
  </process>

小伙伴们看到,在 ServiceTask 标签中的 flowable:class="org.javaboy.flowableidm.MyServiceTask" 就表示 ServiceTask 执行的服务类。

配置完成后,我们可以部署并启动这个流程,由于这个流程除了开始和结束,就这一个节点,所以流程一启动就自动结束了。不过在这个过程中,我们可以看到控制台打印出来了日志,说明这个 ServiceTask 确实是执行了。

3.1.2 委托表达式

我们也可以配置委托表达式。

委托表达式是指将一个实现了 JavaDelegate 接口的类注册到 Spring 容器中,然后我们在流程节点的配置中不用写完整的类名了,只需要写 Spring 容器中的 Bean 名称即可。

像下面这样:

@Component
public class MyServiceTask implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("========MyServiceTask==========");
    }
}

这个类注册到 Spring 容器中的默认名称是类名首字母小写,即 myServiceTask。

现在我们在流程图中,可以按照如下方式进行配置:

梳理流程引擎 Flowable 四大常见任务

对应的 XML 文件如下:

<process id="demo01" name="测试流程" isExecutable="true">
  <documentation>测试流程</documentation>
  <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
  <sequenceFlow id="sid-33A78082-C2FD-48BE-8B87-99FB20F0B331" sourceRef="startEvent1" targetRef="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8"></sequenceFlow>
  <serviceTask id="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8" flowable:delegateExpression="${myServiceTask}"></serviceTask>
  <endEvent id="sid-A5F11956-15EA-4574-98D0-29A4E3DB5495"></endEvent>
  <sequenceFlow id="sid-0698809E-0A6C-4B92-A167-AE96A8CB75F2" sourceRef="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8" targetRef="sid-A5F11956-15EA-4574-98D0-29A4E3DB5495"></sequenceFlow>
</process>

可以看到,flowable:delegateExpression="${myServiceTask}" 就表示执行的一个表达式。

测试过程同 2.1 小节,我就不再赘述了。

最后总结一下,委托表达式,一定是 JavaDelegate 接口的实现类,将这个实现类注册到 Spring 容器中,然后在使用的时候,根据 Bean 的名称从 Spring 容器中查找即可。

3.1.3 表达式

我们也可以使用表达式。

表达式就是一个普通类的普通方法,将这个普通类注册到 Spring 容器中,然后表达式中还可以执行这个类中的方法,类似下面这样,任意定义一个 Java 类:

@Component
public class MyServiceTask2 {
    public void hello() {
        System.out.println("========MyServiceTask2==========");
    }
}

然后在流程图中按照如下方式进行配置:

梳理流程引擎 Flowable 四大常见任务

表达式中有一部分内容隐藏了,完整的表达式是 ${myServiceTask2.hello()}

对应的 XML 文件如下:

<process id="demo01" name="测试流程" isExecutable="true">
  <documentation>测试流程</documentation>
  <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
  <sequenceFlow id="sid-33A78082-C2FD-48BE-8B87-99FB20F0B331" sourceRef="startEvent1" targetRef="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8"></sequenceFlow>
  <serviceTask id="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8" flowable:expression="${myServiceTask2.hello()}"></serviceTask>
  <endEvent id="sid-A5F11956-15EA-4574-98D0-29A4E3DB5495"></endEvent>
  <sequenceFlow id="sid-0698809E-0A6C-4B92-A167-AE96A8CB75F2" sourceRef="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8" targetRef="sid-A5F11956-15EA-4574-98D0-29A4E3DB5495"></sequenceFlow>
</process>

可以看到,表达式的内容是 flowable:expression="${myServiceTask2.hello()}

测试方式同 2.1 小节,这里我不再赘述。

3.2 类中字段

可能有小伙伴注意到,我们在绘制流程图的时候,还可以为类设置一个字段。

例如我想给 ServiceTask 的执行类设置一个 username 字段,如下:

梳理流程引擎 Flowable 四大常见任务

梳理流程引擎 Flowable 四大常见任务

设置完成后,对应的 XML 如下:

<process id="demo01" name="测试流程" isExecutable="true">
  <documentation>测试流程</documentation>
  <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
  <sequenceFlow id="sid-33A78082-C2FD-48BE-8B87-99FB20F0B331" sourceRef="startEvent1" targetRef="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8"></sequenceFlow>
  <serviceTask id="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8" flowable:delegateExpression="${myServiceTask}">
    <extensionElements>
      <flowable:field name="username">
        <flowable:string><![CDATA[javaboy]]></flowable:string>
      </flowable:field>
    </extensionElements>
  </serviceTask>
  <endEvent id="sid-A5F11956-15EA-4574-98D0-29A4E3DB5495"></endEvent>
  <sequenceFlow id="sid-0698809E-0A6C-4B92-A167-AE96A8CB75F2" sourceRef="sid-6FA66E2A-F8E6-4F10-8FA2-6450408E17D8" targetRef="sid-A5F11956-15EA-4574-98D0-29A4E3DB5495"></sequenceFlow>
</process>

可以看到,这里通过 extensionElements 节点描述了额外的信息。

接下来,我们就可以在 Java 类中访问到这个变量了,如下:

@Component
public class MyServiceTask implements JavaDelegate {
    Expression username;
    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("username.getExpressionText() = " + username.getExpressionText());
        System.out.println("username.getValue(execution) = " + username.getValue(execution));
        System.out.println("========MyServiceTask==========");
    }
}

想要获取到 username 对应的值,上面这段代码中,松哥给大家演示了两种方式。

4. 脚本任务

个人感觉脚本任务和我们前面说的 ServiceTask 很像,都是流程走到这个节点的时候自动做一些事情,不同的是,在 ServiceTask 中,流程在这个节点中所做的事情是用 Java 代码写的,在脚本任务中,流程在这个节点中所做的事情则是用其他一些脚本语言如 JavaScript、Groovy、Juel 等写的。

脚本任务的图标如下图所示:

梳理流程引擎 Flowable 四大常见任务

4.1 实践

写一个简单的例子我们来一起看下。

4.1.1 JavaScript 脚本

我们先来看用 JavaScript 写这个脚本。

假设我有如下流程图:

梳理流程引擎 Flowable 四大常见任务

中间这个节点就是一个脚本任务。

选中该节点,我们先配置脚本语言是 JavaScript,如下图:

梳理流程引擎 Flowable 四大常见任务

这里也可以使用简写的 js。

然后再点击右边的脚本,配置脚本,如下图:

梳理流程引擎 Flowable 四大常见任务

上面这里我写了两行 JavaScript 脚本:

  1. 第一行表示流程执行到这里的时候,需要做一个简单的加法运算,a 和 b 两个变量则需要流程传入进来。
  2. 第二行表示往流程中存储一个名为 sum 的变量,变量值就是前面计算的结果,其中 execution 是一个内置变量。这个就类似于我们启动流程时候传入的变量一样。

在 ES6 中我们常用的 let 关键字这里并不支持,这个地方小伙伴们要注意。

配置完成之后,我们下载这个脚本来看下对应的 XML 文件是什么样子:

<process id="demo01" name="测试流程" isExecutable="true">
  <documentation>测试流程</documentation>
  <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
  <sequenceFlow id="sid-33A78082-C2FD-48BE-8B87-99FB20F0B331" sourceRef="startEvent1" targetRef="sid-8D88DFF6-0F37-42FA-9F94-29FE30536094"></sequenceFlow>
  <endEvent id="sid-A5F11956-15EA-4574-98D0-29A4E3DB5495"></endEvent>
  <sequenceFlow id="sid-0698809E-0A6C-4B92-A167-AE96A8CB75F2" sourceRef="sid-8D88DFF6-0F37-42FA-9F94-29FE30536094" targetRef="sid-A5F11956-15EA-4574-98D0-29A4E3DB5495"></sequenceFlow>
  <scriptTask id="sid-8D88DFF6-0F37-42FA-9F94-29FE30536094" scriptFormat="JavaScript" flowable:autoStoreVariables="false">
    <script><![CDATA[var sum=a+b;
execution.setVariable("sum",sum);]]></script>
  </scriptTask>
</process>

小伙伴们看到,scriptTask 中内嵌了一个 script 节点,里边就是我们自己写的脚本内容。

好啦,接下来小伙伴们就可以部署并启动这个流程了,启动代码如下:

@Test
void test01() {
    Map<String, Object> variables = new HashMap<>();
    variables.put("a", 99);
    variables.put("b", 98);
    ProcessInstance pi = runtimeService.startProcessInstanceByKey("demo01", variables);
    logger.info("id:{},activityId:{}", pi.getId(), pi.getActivityId());
}

大家注意启动的时候传递 a 和 b 两个变量。这个流程启动之后,直接就执行结束了,因为流程到达 scriptTask 并不会停止。

不过我们可以在 ACT_HI_VARINST 表中查看流程运行信息:

梳理流程引擎 Flowable 四大常见任务

可以看到,相关的变量和变量值都保存着。

4.1.2 Groovy 脚本

看懂了 JavaScript 脚本,Groovy 就好懂了。不过 JavaScript 脚本估计大部分搞 Java 的小伙伴都懂,但是 Groovy 可能会比较陌生,我简单介绍下:

Groovy 是 Apache 旗下的一门基于 JVM 平台的动态/敏捷编程语言,在语言的设计上它吸纳了 Python、Ruby 和 Smalltalk 语言的优秀特性,语法非常简练和优美,开发效率也非常高(编程语言的开发效率和性能是相互矛盾的,越高级的编程语言性能越差,因为意味着更多底层的封装,不过开发效率会更高,需结合使用场景做取舍)。并且,Groovy 可以与 Java 语言无缝对接,在写 Groovy 的时候如果忘记了语法可以直接按 Java 的语法继续写,也可以在 Java 中调用 Groovy 脚本,都可以很好的工作,这有效的降低了 Java 开发者学习 Groovy 的成本。Groovy 也并不会替代 Java,而是相辅相成、互补的关系,具体使用哪门语言这取决于要解决的问题和使用的场景。

如果我们想要在流程中使用 Groovy 脚本,那么首先设置脚本格式为 Groovy:

梳理流程引擎 Flowable 四大常见任务

然后设置脚本内容如下:

梳理流程引擎 Flowable 四大常见任务

这段脚本表示流程执行到这个节点的时候输出一个 "hello groovy"(如果你熟悉 Groovy 脚本的话,就知道这段脚本其实也可以直接写 Java 代码,也能执行)。

另外说一句,使用 Groovy 脚本,千万别忘了加 Groovy 依赖,如下:

<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>3.0.13</version>
</dependency>

4.1.3 Juel 脚本

JuelJava Unified Expression Language 的简称,它具有高性能,插件式缓存,小体积,支持方法调用和多参数调用,可插拔等多种特性,它是 JSP2.1 标准 (JSR-245) 中定义的一部分。尽管 EL 表达式是伴随着 JSP 而生,但现在已经可以在非 JS P应用中使用,相关的 API 放在 javax.el 包里面。

其实像我们之前写的 ${xxx} 这种表达式,其实就是 Juel 了。

来一个简单的例子看下。假设我们想在流程中使用 juel,首先设置脚本格式为 juel:

梳理流程引擎 Flowable 四大常见任务

然后就可以设置具体的脚本内容了,如下:

梳理流程引擎 Flowable 四大常见任务

这段脚本就表示调用一个名为 myServiceTask2 的 Bean 中的 hello 方法。

好了,flowable 四种常见任务就和小伙伴们聊完啦~