文章

【若依】25、流程定义与流程实例

  1. 流程定义

使用 flowable 的时候,首先需要画流程图,要在代码中使用流程图,就必须先把流程图部署到项目中。 部署到项目中的流程图,就是流程定义:ProcessDefinition。

  1. 流程实例

启动的流程,就是流程实例:ProcessInstance。 ProcessDefinition 相当于 Java 中的类,ProcessInstance 则相当于根据这个类创建出来的对象。

在 Flowable 中,所有跟流程部署相关的表,都是以ACT_RE_前缀开始的。

1 流程定义 ProcessDefinition

1.1 自动部署

在 Spring Boot 中,凡是放在 resources/processes 目录下的流程文件,默认情况下,都会被自动部署。 创建 Spring Boot 项目,添加 flowable 依赖,并配置 application.properties:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 应用名称
spring.application.name=flowable_process
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/learn_flowable_process?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=123456
# 应用服务 WEB 访问端口
server.port=8080

# 开启 flowable 日志
logging.level.org.flowable=debug

任意绘制一个流程图,放到 resources/processes 目录下: image.png 启动 Spring Boot 项目,启动之后,这个流程就被自动部署了。 ACT_RE_DEPLOYMENTACT_RE_PROCDEF分别保存了流程定义相关的信息。 ACT_GE_BYTEARRAY表则保存了刚刚定义的流程的 XML 文件以及根据这个 XML 文件所自动生成的流程图。 三张表的关系:

  • ACT_RE_DEPLOYMENTACT_RE_PROCDEF是一对一的关系。
  • ACT_RE_DEPLOYMENTACT_GE_BYTEARRAY是一对多的关系,一个流程部署 ID 对应两条ACT_GE_BYTEARRAY表中的记录(默认)。

流程 部署好之后,如果想要修改,可以直接修改,修改之后,流程会自动升级(数据库中的记录会自动更新)。 举个例子:

修改流程定义的名字,重新启动 Spring Boot 项目,ACT_RE_DEPLOYMENT 表中会增加一条部署记录,同时ACT_RE_PROCDEF表也会增加一条新的流程定义信息,新的流程信息中,该变的字段会自动变,同时版本号VERSION_会自增 1。ACT_GE_BYTEARRAY表中也会新增两条记录,和最新的版本号的定义相对应。

注意:流程定义的更新,主要是以流程定义的 id 为依据,如果流程定义的内容发生变化,但是流程定义的 id 没有变,则流程定义升级;如果流程定义的 id 发生变化,则部署新的流程。 在流程定义中,XML 文件中的targetNamespace属性,其实就是流程定义的分类;如果要修改流程定义的分类,直接修改该属性即可。 Spring Boot 中,关于流程定义的重要属性:

1
2
3
4
5
6
# 是否在项目启动的时候,自动去检查流程定义目录下是否有对应的流程定义文件,如果这个属性为 true(默认),就表示去检查,否则不检查(意味着不会自动部署流程)
flowable.check-process-definitions=true
# 设置流程定义文件的位置,默认位置:classpath*:/processes/
flowable.process-definition-location-prefix=classpath*:/processes/
# 指定流程定义 XML 文件的后缀,默认后缀有两个:**.bpmn20.xml,**.bpmn
flowable.process-definition-location-suffixes=**.bpmn20.xml,**.bpmn

1.2 手动部署

手动部署

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
 * 流程部署
 */
@RestController
public class ProcessDeployController {

    /**
     * RepositoryService 该实体类可以用来操作 ACT_RE_XXX 这类表
     */
    @Autowired
    RepositoryService repositoryService;

    /**
     * 部署工作流(输入流)
     * @param file
     * @return
     * @throws IOException
     */
    @PostMapping("/deploy1")
    public Response deploy1(@RequestPart MultipartFile file) throws IOException {
        DeploymentBuilder deploymentBuilder = repositoryService
                // 开始流程部署的构建
                .createDeployment()
                // ACT_RE_DEPLOYMENT 表中的 NAME_ 属性
                .name("部署工作流的名称")
                // ACT_RE_DEPLOYMENT 表中的 CATEGORY_ 属性
                .category("部署工作流的分类")
                // ACT_RE_DEPLOYMENT 表中的 KEY_ 属性
                .key("部署工作流的 KEY")
                // 设置文件的输入流,将来通过这个输入流自动去读取 XML 文件
                // 注意:设置资源名称不能随意取值,建议和文件名保持一致
                .addInputStream(file.getOriginalFilename(), file.getInputStream());
        // 完成部署
        Deployment deployment = deploymentBuilder.deploy();
        return Response.success("部署成功", deployment.getId());
    }

    /**
     * 部署工作流(字节数组)
     * @param file
     * @return
     * @throws IOException
     */
    @PostMapping("/deploy2")
    public Response deploy2(@RequestPart MultipartFile file) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        InputStream is = file.getInputStream();
        int length;
        byte[] buffer = new byte[1024];
        while ((length = is.read(buffer)) != -1) {
            os.write(buffer, 0, length);
        }
        is.close();
        DeploymentBuilder deploymentBuilder = repositoryService
                .createDeployment()
                .name("部署工作流的名称")
                .category("部署工作流的分类")
                .key("部署工作流的 KEY")
                .addBytes(file.getOriginalFilename(), os.toByteArray());
        os.close();
        Deployment deployment = deploymentBuilder.deploy();
        return Response.success("部署成功", deployment.getId());
    }

    /**
     * 部署工作流(字符串)
     * @param file
     * @return
     * @throws IOException
     */
    @PostMapping("/deploy3")
    public Response deploy3(@RequestPart MultipartFile file) throws IOException {
        InputStream is = file.getInputStream();
        String string = IOUtils.toString(is, "UTF-8");
        DeploymentBuilder deploymentBuilder = repositoryService
                .createDeployment()
                .name("部署工作流的名称")
                .category("部署工作流的分类")
                .key("部署工作流的 KEY")
                .addString(file.getOriginalFilename(), string);
        Deployment deployment = deploymentBuilder.deploy();
        return Response.success("部署成功", deployment.getId());
    }
}

查询流程定义

查询所有的流程定义

1
2
3
4
5
6
7
8
9
10
11
/**
* 查询流程定义
* act_re_procdef
*/
@Test
public void queryProcessDefinition() {
	List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
    for (ProcessDefinition processDefinition : list) {
        log.info(")_(" + "id:{},name:{},version:{},category:{}", processDefinition.getId(), processDefinition.getName(), processDefinition.getVersion(), processDefinition.getCategory());
    }
}

查询所有流程定义的最新版本

1
2
3
4
5
6
7
8
9
10
11
/**
 * 查询流程定义的最新版本
 * act_re_procdef
 */
@Test
public void queryProcessDefinitionLatestVersion() {
    List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().latestVersion().list();
    for (ProcessDefinition processDefinition : list) {
        log.info(")_(" + "id:{},name:{},version:{},category:{}", processDefinition.getId(), processDefinition.getName(), processDefinition.getVersion(), processDefinition.getCategory());
    }
}

根据流程定义的 key 查询

1
2
3
4
5
6
7
8
9
10
11
/**
 * 根据流程定义的 key 查询流程定义
 * act_re_procdef
 */
@Test
public void queryProcessDefinitionByKey() {
    List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionKey("submit_an_expense_account").orderByProcessDefinitionVersion().desc().list();
    for (ProcessDefinition processDefinition : list) {
        log.info(")_(" + "id:{},name:{},version:{},category:{}", processDefinition.getId(), processDefinition.getName(), processDefinition.getVersion(), processDefinition.getCategory());
    }
}

自定义查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 自定义查询流程定义
 * act_re_procdef
 */
@Test
public void queryProcessDefinitionBySQL() {
    List<ProcessDefinition> list = repositoryService.createNativeProcessDefinitionQuery()
            .sql("SELECT RES.* from ACT_RE_PROCDEF RES WHERE RES.KEY_ = #{key} order by RES.VERSION_ desc")
            .parameter("key", "submit_an_expense_account")
            .list();
    for (ProcessDefinition processDefinition : list) {
        log.info(")_(" + "id:{},name:{},version:{},category:{}", processDefinition.getId(), processDefinition.getName(), processDefinition.getVersion(), processDefinition.getCategory());
    }
}

查询流程部署

1
2
3
4
5
6
7
8
9
10
11
/**
 * 查询流程部署
 * act_re_deployment
 */
@Test
public void queryDeployment() {
    List<Deployment> list = repositoryService.createDeploymentQuery().list();
    for (Deployment deployment : list) {
        log.info(")_(" + "id:{},name:{},category:{},key:{}", deployment.getId(), deployment.getName(), deployment.getCategory(), deployment.getKey());
    }
}

根据流程部署的分类查询

1
2
3
4
5
6
7
8
9
10
11
/**
 * 根据流程部署的分类查询流程部署
 * act_re_deployment
 */
@Test
public void queryDeploymentByCategory() {
    List<Deployment> list = repositoryService.createDeploymentQuery().deploymentCategory("部署工作流的分类").list();
    for (Deployment deployment : list) {
        log.info(")_(" + "id:{},name:{},category:{},key:{}", deployment.getId(), deployment.getName(), deployment.getCategory(), deployment.getKey());
    }
}

自定义查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 自定义查询流程部署
 * act_re_deployment
 */
@Test
public void queryDeploymentBySQL() {
    List<Deployment> list = repositoryService.createNativeDeploymentQuery()
            .sql("SELECT RES.* from ACT_RE_DEPLOYMENT RES WHERE RES.CATEGORY_ = #{category} order by RES.ID_ asc")
            .parameter("category", "部署工作流的分类")
            .list();
    for (Deployment deployment : list) {
        log.info(")_(" + "id:{},name:{},category:{},key:{}", deployment.getId(), deployment.getName(), deployment.getCategory(), deployment.getKey());
    }
}

根据流程部署的 父流程部署ID 查询流程定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 根据流程部署的 父流程部署ID 查询流程定义
 * act_re_deployment
 * act_re_procdef
 */
@Test
public void queryDeploymentByParentDeploymentId() {
    List<Deployment> list1 = repositoryService.createDeploymentQuery().list();
    for (Deployment deployment : list1) {
        List<ProcessDefinition> list2 = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getParentDeploymentId()).list();
        for (ProcessDefinition processDefinition : list2) {
            log.info(")_(" + "id:{},name:{},version:{},category:{}", processDefinition.getId(), processDefinition.getName(), processDefinition.getVersion(), processDefinition.getCategory());
        }
    }
}

流程定义删除

涉及到流程定义的表,都会被删除

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * 删除流程定义
 * act_re_deployment
 * act_re_procdef
 * act_ge_bytearray
 */
@Test
public void deleteDeployment() {
    List<Deployment> list = repositoryService.createDeploymentQuery().list();
    for (Deployment deployment : list) {
        repositoryService.deleteDeployment(deployment.getId());
    }
}

2 流程实例 Process Instance

  1. 流程实例(ProcessInstance):通过流程定义启动的流程,启动后的流程就是流程实例;在一个流程中,只存在一个流程实例(执行实例可能有多个);流程定义相当于 Java 的类,流程实例相当于 Java 的对象。
  2. 执行实例(Execution):简单来说,在一个流程中,开始节点和结束节点是流程实例,其余节点都是执行实例。从类的继承关系来说,ProcessInstance 实际上是 Execution 的子类,所以,流程实例可以算是执行实例的一种特殊情况。
    1. 如果一个流程图中,只有一条线,那么一般来说,流程实例和执行实例是不同的。
    2. 如果一个流程图中,包含多条线,那么每一条线就是一个执行实例。

      2.1 启动流程

      启动流程 ```java /** * 流程运行 * 流程运行相关表的前缀 ACT_RU_ * 注意:当一个流程执行完成后,前缀为 ACT_RU_ 的表中,该流程的数据都会清除 */ @Slf4j @SpringBootTest public class FlowableProcessActRuTest {

    /**

    • 流程运行相关的操作 */ @Autowired RuntimeService runtimeService; @Autowired IdentityService identityService; @Autowired RepositoryService repositoryService;

    /**

    • 通过流程定义的 key 启动流程 */ @Test public void startProcessInstanceByKey() { // 设置流程发起人 Authentication.setAuthenticatedUserId(“yueyazhui”); // 流程定义的 key,实际上就是流程 xml 文件中的流程 id String processDefinitionKey = “leave”; // 流程启动成功之后,可以获取到一个流程实例 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey); log.info(“)_(“ + “definitionId:{},id:{},name:{}”, processInstance.getProcessDefinitionId(), processInstance.getId(), processInstance.getName()); }

    /**

    • 通过流程定义的 id 启动流程 */ @Test public void startProcessInstanceById() { // 设置流程发起人 identityService.setAuthenticatedUserId(“yueyazhui”); // 查询最新版本的 leave 流程的定义信息 ProcessDefinition leave = repositoryService.createProcessDefinitionQuery().processDefinitionKey(“leave”).latestVersion().singleResult(); ProcessInstance processInstance = runtimeService.startProcessInstanceById(leave.getId()); log.info(“)_(“ + “definitionId:{},id:{},name:{}”, processInstance.getProcessDefinitionId(), processInstance.getId(), processInstance.getName()); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
当一个流程启动成功后,首先查看`ACT_RU_EXECUTION`表,该表中保存了所有流程执行的信息,包括启动节点信息以及任务节点信息。同时,如果这个节点,是一个 UserTask,那么这个节点的信息还会保存在`ACT_RU_TASK`表中。另外在`ACT_RU_ACTINST`表中,会保存流程活动的执行情况。
接下来,根据用户名去查询用户需要执行的任务,并处理:
```java
/**
 * 根据 taskAssignee 查询需要执行的任务,并处理
 * <p>
 * 处理任务的事务:
 * 首先去 ACT_RU_TASK 表中添加一条记录,这个新的记录,就是下一个任务。
 * 然后从 ACT_RU_TASK 表中删除掉之前完成任务的记录。
 * <p>
 * act_ru_task
 */
@Test
public void queryAndDispose() {
    String[] taskAssignees = new String[]{"yueyazhui", "yueya", "yue"};
    String taskAssignee = taskAssignees[0];

    List<Task> list = taskService.createTaskQuery().taskAssignee(taskAssignee).list();
    for (Task task : list) {
        log.info(")_(" + "id:{},assignee:{},name:{}", task.getId(), task.getAssignee(), task.getName());
        // 完成任务
        taskService.complete(task.getId());
    }
}

2.2 查询流程是否执行结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 查询一个流程是否执行结束
 * <p>
 * 如果 ACT_RU_EXECUTION 表中,有关于这个流程的记录,说明这个流程还在执行中
 * 如果 ACT_RU_EXECUTION 表中,没有关于这个流程的记录,说明这个流程已经执行结束了
 * <p>
 * 注意:虽然是去 ACT_RU_EXECUTION 表中查询,并且在该表中同一个流程实例 ID 对应了多条记录,但是,这里查询到的其实还是一个流程实例
 */
@Test
void queryProcessInstanceIsExecuteEnd() {
    String processInstanceId = "2cd16859-5ce9-11ed-a630-e4fd4528656d";
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
    if (ObjectUtil.isNull(processInstance)) {
        log.info(")_(" + "流程执行结束");
    } else {
        log.info(")_(" + "流程执行中");
    }
}

2.3 查询运行的活动节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 查询运行的活动节点
 * ACT_RU_EXECUTION
 */
@Test
void queryActive() {
    List<Execution> executions = runtimeService.createExecutionQuery().list();
    for (Execution execution : executions) {
        // 查询某一个执行实例的活动节点
        List<String> activeActivityIds = runtimeService.getActiveActivityIds(execution.getId());
        for (String activeActivityId : activeActivityIds) {
            // ACT_RU_EXECUTION 表中的 ACT_ID_ 字段
            log.info(")_(" + "activeActivityId:{}", activeActivityId);
        }
    }
}

2.4 删除流程实例

1
2
3
4
5
6
7
8
9
/**
 * 删除流程实例
 */
@Test
void test06() {
    String processInstanceId = "387f7dd4-5cec-11ed-b73e-e4fd4528656d";
    String deleteReason = "删除原因";
    runtimeService.deleteProcessInstance(processInstanceId, deleteReason);
}

3 流程的挂起和恢复

  1. 流程定义的挂起和恢复。
  2. 流程实例的挂起和恢复。

    流程定义

    查询流程定义是否挂起

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    /**
     * 查询流程定义是否挂起
     * 挂起的流程定义,是无法开启流程实例的
     * 表:ACT_RE_PROCDEF
     * 字段:SUSPENSION_STATE_ 1:没有挂起 2:已经挂起
     */
    @Test
    public void isProcessDefinitionSuspended() {
     // 查询所有的流程定义
     List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
     for (ProcessDefinition processDefinition : list) {
         //根据流程定义的 id 判断一个流程定义是否挂起
         boolean processDefinitionSuspended = repositoryService.isProcessDefinitionSuspended(processDefinition.getId());
         if (processDefinitionSuspended) {
             log.info(")_(" + "流程定义 {} 已经挂起", processDefinition.getId());
         }else {
             log.info(")_(" + "流程定义 {} 没有挂起", processDefinition.getId());
         }
     }
    }
    

    挂起一个流程定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    /**
     * 根据流程定义的 id 挂起流程定义
     * 表:ACT_RE_PROCDEF
     * 字段:SUSPENSION_STATE_ 1:没有挂起 2:已经挂起
     */
    @Test
    void suspendProcessDefinitionById() {
     List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
     for (ProcessDefinition processDefinition : list) {
         repositoryService.suspendProcessDefinitionById(processDefinition.getId());
         log.info(")_(" + "流程定义 {} 已经挂起", processDefinition.getId());
     }
    }
    

    激活一个已经挂起的流程定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    /**
     * 根据流程定义的 id 激活已经挂起的流程定义
     * 表:ACT_RE_PROCDEF
     * 字段:SUSPENSION_STATE_ 1:没有挂起 2:已经挂起
     */
    @Test
    void activateProcessDefinitionById() {
     List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
     for (ProcessDefinition processDefinition : list) {
         repositoryService.activateProcessDefinitionById(processDefinition.getId());
         log.info(")_(" + "流程定义 {} 已经激活", processDefinition.getId());
     }
    }
    

    流程实例

    挂起一个流程实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    /**
     * 根据流程定义的 id 挂起流程实例
     * 挂起的流程实例,无法执行相应的 Task
     * 挂起流程实例,最终也会挂起流程定义
     *
     * 挂起流程实例
     * 1. 流程实例被挂起
     * 2. 流程实例相应的 Task 被挂起
     * 3. 程实例对应的流程定义被挂起
     *
     * ACT_RU_EXECUTION(流程实例被挂起)1:没有挂起 2:已经挂起
     * ACT_RU_TASK(流程实例相应的 Task 被挂起)1:没有挂起 2:已经挂起
     * ACT_RE_PROCDEF(流程定义被挂起)1:没有挂起 2:已经挂起
     */
    @Test
    void suspendProcessInstanceByProcessDefinitionId() {
     // 查询所有的流程定义
     List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
     for (ProcessDefinition processDefinition : list) {
         // 1. 流程定义的 ID
         // 2. 是否挂起这个流程定义所对应的流程实例
         // 3. 挂起的时间,null 表示立即挂起;也可以给一个具体的时间,表示到期之后才会被挂起
         repositoryService.suspendProcessDefinitionById(processDefinition.getId(), true, null);
     }
    }
    

    激活一个已经挂起的流程实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    /**
     * 根据流程定义的 id 激活已经挂起的流程实例
     * 激活是挂起的一个反向操作
     *
     * 激活流程实例
     * 1. 流程实例被激活
     * 2. 流程实例相应的 Task 被激活
     * 3. 程实例对应的流程定义被激活
     *
     * ACT_RU_EXECUTION(流程实例被激活)1:没有挂起 2:已经挂起
     * ACT_RU_TASK(流程实例相应的 Task 被激活)1:没有挂起 2:已经挂起
     * ACT_RE_PROCDEF(流程定义被激活)1:没有挂起 2:已经挂起
     */
    @Test
    void activateProcessInstanceByProcessDefinitionId() {
     List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
     for (ProcessDefinition processDefinition : list) {
         repositoryService.activateProcessDefinitionById(processDefinition.getId(), true, null);
     }
    }
    

    4 DataObject

    设置流程的全局属性 绘制流程图时设置全局属性 image.png image.png 生成的 XML 文件 请假.bpmn20.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<process id="leave" name="请假" isExecutable="true">
  <documentation>请假</documentation>
  <dataObject id="name" name="流程定义名称" itemSubjectRef="xsd:string">
    <extensionElements>
      <flowable:value>请假</flowable:value>
    </extensionElements>
  </dataObject>
  <dataObject id="author" name="作者" itemSubjectRef="xsd:string">
    <extensionElements>
      <flowable:value>月牙坠</flowable:value>
    </extensionElements>
  </dataObject>
  <dataObject id="date" name="日期" itemSubjectRef="xsd:datetime">
    <extensionElements>
      <flowable:value>2022-11-06T00:00:00</flowable:value>
    </extensionElements>
  </dataObject>
  <startEvent id="startEvent1" flowable:initiator="INITIATOR" flowable:formFieldValidation="true"></startEvent>
  <userTask id="sid-210DBB56-1F4E-4987-B509-2B39EC89F7BE" 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-D2F15BC2-9ADB-4C8B-9561-0D0458725870" sourceRef="startEvent1" targetRef="sid-210DBB56-1F4E-4987-B509-2B39EC89F7BE"></sequenceFlow>
  <userTask id="sid-BC2D62DD-BCB9-43A2-B37B-803529CCABD3" name="一级审批" flowable:assignee="yueya" 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-3D0B52C4-DD2A-4394-A33B-E4667D0A24CF" sourceRef="sid-210DBB56-1F4E-4987-B509-2B39EC89F7BE" targetRef="sid-BC2D62DD-BCB9-43A2-B37B-803529CCABD3"></sequenceFlow>
  <userTask id="sid-B1363E63-FE2B-474A-859D-A1079B30A83F" name="二级审批" flowable:assignee="yue" 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-222CF899-5201-4F4A-89CD-B77839F13BC4" sourceRef="sid-BC2D62DD-BCB9-43A2-B37B-803529CCABD3" targetRef="sid-B1363E63-FE2B-474A-859D-A1079B30A83F"></sequenceFlow>
  <endEvent id="sid-E2C63CD7-DEFE-4F89-8DBA-2C9F0A48A697"></endEvent>
  <sequenceFlow id="sid-D5C430B8-B55E-4E77-BF48-AD71BB804CF3" sourceRef="sid-B1363E63-FE2B-474A-859D-A1079B30A83F" targetRef="sid-E2C63CD7-DEFE-4F89-8DBA-2C9F0A48A697"></sequenceFlow>
</process>

这里的 dataObject 节点是属于 process 的,而不是属于某一个具体的节点,因此,这里的 dataObject 可以理解为全局属性。 流程启动成功,dataObject 中的数据,会记录在ACT_RU_VARIABLE表中。 image.png 设置流程启动人的数据,也是记录在这个表中的。 查询 DataObject 数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 查询 DataObject 数据
 * ACT_RU_VARIABLE
 */
@Test
void queryDataObject() {
    List<Execution> list = runtimeService.createExecutionQuery().list();
    for (Execution execution : list) {
        Map<String, DataObject> dataObjects = runtimeService.getDataObjects(execution.getId());
        Set<String> keySet = dataObjects.keySet();
        for (String key : keySet) {
            DataObject dataObject = dataObjects.get(key);
            log.info(")_(" + "id:{},name:{},value:{},type:{}", dataObject.getId(), dataObject.getName(), dataObject.getValue(), dataObject.getType());
        }
    }
}

5 租户

tenant 假如有 A、B 两个子系统,现在两个子系统都需要部署一个名为 leave 的流程,租户就是用来做区分的。

如果在部署流程定义的时候,使用到了租户 ID,那么流程启动的时候,也必须指定租户 ID。

部署流程定义的时候,可以通过如下方式指定流程的租户 ID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * 部署工作流(输入流)
 * @param file
 * @param tenantId
 * @return
 * @throws IOException
 */
@PostMapping("/deploy1")
public Response deploy1(@RequestPart MultipartFile file, @RequestParam String tenantId) throws IOException {
    DeploymentBuilder deploymentBuilder = repositoryService
            // 开始流程部署的构建
            .createDeployment()
            // ACT_RE_DEPLOYMENT 表中的 NAME_ 属性
            .name("部署工作流的名称")
            // ACT_RE_DEPLOYMENT 表中的 CATEGORY_ 属性
            .category("部署工作流的分类")
            // ACT_RE_DEPLOYMENT 表中的 KEY_ 属性
            .key("部署工作流的 KEY")
            // 设置租户ID
            .tenantId(tenantId)
            // 设置文件的输入流,将来通过这个输入流自动去读取 XML 文件
            // 注意:设置资源名称不能随意取值,建议和文件名保持一致
            .addInputStream(file.getOriginalFilename(), file.getInputStream());
    // 完成部署
    Deployment deployment = deploymentBuilder.deploy();
    return Response.success("部署成功", deployment.getId());
}

部署成功后,在 ACT_RE_PROCDEF 表中,可以看到 TENANT_ID_ 字段的值 image.png 通过流程定义的 key 和 租户ID 启动流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 通过流程定义的 key 和 租户ID 启动流程
 * act_ru_execution
 * act_ru_task
 * act_ru_actinst
 */
@Test
public void startProcessInstanceByKeyAndTenantId() {
    // 设置流程发起人
    Authentication.setAuthenticatedUserId("yueyazhui");
    // 流程定义的 key,实际上就是流程 xml 文件中的流程 id
    String processDefinitionKey = "leave";
    // 流程启动成功之后,可以获取到一个流程实例
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKeyAndTenantId(processDefinitionKey, "tenant_yueyazhui");
    log.info(")_(" + "definitionId:{},id:{},name:{}", processInstance.getProcessDefinitionId(), processInstance.getId(), processInstance.getName());
}

对于带有租户 ID 的流程,在执行具体的 Task 时,是不需要指定租户 ID 的。 在ACT_RU_TASK表中,存在TENANT_ID_字段,这个字段表示这个 Task 所属的租户。所以,虽然我们执行 Task 不需要租户 ID,但是,我们可以根据租户 ID 去查询 Task。

1
2
3
4
5
6
7
8
9
10
/**
 * 根据租户ID查询 Task
 */
@Test
void queryTaskByTenantId() {
    List<Task> list = taskService.createTaskQuery().taskTenantId("tenant_yueyazhui").list();
    for (Task task : list) {
        log.info(")_(" + "name:{},assignee:{}", task.getName(), task.getAssignee());
    }
}
本文由作者按照 CC BY 4.0 进行授权