Activiti7 基础类


# Activiti7 基础类

# 流程部署 Deployment

执行部署后,会在以下三个表中写入数据:

  • act_re_deployment:部署信息表
  • act_ge_bytearray:二进制表,存储通用的流程资源(比如 bpmn 和图片)
  • act_re_procdef:流程定义数据表

# 通过 bpmn 部署流程

即,将 bpmn 写入到 activiti 的数据表(act_re_deployment)中:

流程部署相关的 API 需要注入 RepositoryService 这个类。

package com.example.workflow;

import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Part1_Deployment {

    @Autowired
    private RepositoryService repositoryService;

    @Test
    public void initDeploymentBPMN() {
        String filename = "BPMN/Part1_Deployment.bpmn"; // 路径从 resources 目录下开始
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource(filename)
                .name("流程的名字")
                .deploy();
        System.out.println(deployment.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

注意:只要流程 key 不变,就认为是同一个流程,会在写入数据表时给名称后面拼接上字符串 _V2,以此类推。

# 查询流程部署

查询的 API 都是 repositoryService.createXxxQuery()

package com.example.workflow;

import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part1_Deployment {

    @Autowired
    private RepositoryService repositoryService;

    @Test
    public void getDeployments() {
        List<Deployment> list = repositoryService.createDeploymentQuery().list(); // 查询所有部署过的流程
        for(Deployment dep : list) {
            System.out.println("Id:"+dep.getId());
            System.out.println("Name:"+dep.getName());
            System.out.println("DeploymentTime:"+dep.getDeploymentTime());
            System.out.println("Key:"+dep.getKey());
        }
    }
}
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

# 流程定义 ProcessDefinition

  • Deployment 类的作用:添加资源文件、获取部署信息、部署时间。
    • 数据库表:act_re_deployment
  • ProcessDefinition 的作用:获取版本号、key、资源名称、部署ID 等。
    • 数据库表:act_re_procdef
  • 这两个类都是描述流程定义的类

其中 ProcessDefinition 的表中有一个外键(DEPLOYMENT_ID_)指向 Deployment 的表,构成多对一的关系,这个设计似乎是多余的,因为它们明明是一对一关系,完全可以做成一个表还方便查询信息,目前没有其它资料显示其用意,暂时认为是框架作者的设计所致。

# 查询流程定义

查询的 API 都是 repositoryService.createXxxQuery()

package com.example.workflow;

import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.ProcessDefinition;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part2_ProcessDefinition {

    @Autowired
    private RepositoryService repositoryService;

    @Test
    public void getDefinitions() {
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
        for(ProcessDefinition pd : list){
            System.out.println("------流程定义--------");
            System.out.println("Name:"+pd.getName());
            System.out.println("Key:"+pd.getKey());
            System.out.println("ResourceName:"+pd.getResourceName());
            System.out.println("DeploymentId:"+pd.getDeploymentId());
            System.out.println("Version:"+pd.getVersion());
        }
    }
}
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

其中,ID_ 即流程定义的 ID 值是由 BPMN的KEY:版本号:流程定义实际的UUID 组成。

# 删除流程定义

一次只删一个。

package com.example.workflow;

import org.activiti.engine.RepositoryService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Part2_ProcessDefinition {

    @Autowired
    private RepositoryService repositoryService;

    @Test
    public void getDefinitions() {
        String pdID = "44b15cfe-ce3e-11ea-92a3-dcfb4875e032";  // 流程部署的ID
        repositoryService.deleteDeployment(pdID, true); // true: 连同流程的历史一起删除干净
        System.out.println("删除流程定义成功");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 流程实例 ProcessInstance

ProcessDefinition 与 ProcessInstance 是一对多的关系:即流程实例是流程定义的具体实现,每次执行流程,都会根据流程定义创建相应的流程实例。

初始化流程实例(调用 .startProcessInstanceByKey)后,会在以下两个表中写入数据:

  • act_ru_execution:运行时执行实例表,这个表存储当前正在运行的流程实例的执行数据
    • 启动时会先创建两条数据,一个是开始节点,一个是任务节点。
    • 之后每执行一个节点就创建一条数据。
    • 当流程实例结束时(完成所有任务、手动终止流程、删除流程),对应的记录会被删除。同时,流程实例的历史数据会被转移到 act_hi_procinst(历史流程实例表)中永久保存。
  • act_ru_identitylink:运行时身份连接表
    • 用于关联用户实例、流程实例和任务实例
    • 当流程实例终止时,对应的记录会被删除;同时,相关的身份链接信息会被转移到 act_hi_identitylink 历史表中。
    • 根据需要可以调用 historyService.deleteHistoricProcessInstance(processInstanceId); 同时删除运行时和历史数据。

# 初始化流程实例

流程实例相关的 API 需要注入 RuntimeService 这个类。

startProcessInstanceByKey() 可以接收两个参数:

  • 第一个参数:流程定义 key(就是 BPMN 图的 ID)
  • 第二个参数:业务标识 businessKey(就是业务表单的 ID,比如请假单的ID)
  • 第三个参数:流程变量 variables(Map 类型,这里添加的是全局变量)
package com.example.workflow;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Part3_ProcessInstance {

    @Autowired
    private RuntimeService runtimeService;

    @Test
    public void initProcessInstance() {
        // 实际业务中的执行步骤如下:
        // 1. 获取页面表单填报的内容,比如请假时间,请假事由:String fromData
        // 2. fromData 写入业务表,返回业务表主键ID,后 businessKey
        // 3. 初始化实例,把业务数据与 Activiti7 流程数据关联,即 业务表主键 == businessKey
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess_claim","bKey002");
        System.out.println("流程实例ID:"+processInstance.getProcessDefinitionId());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 获取流程实例列表

查询的 API 都是 runtimeService.createXxxQuery()

package com.example.workflow;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part3_ProcessInstance {

    @Autowired
    private RuntimeService runtimeService;

    @Test
    public void getProcessInstances(){
        List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().list();
        for(ProcessInstance pi : list) {
            System.out.println("--------流程实例------");
            // 流程实例ID
            System.out.println("ProcessInstanceId:"+pi.getProcessInstanceId());
            // 所属流程定义ID
            System.out.println("ProcessDefinitionId:"+pi.getProcessDefinitionId());
            // 流程实例是否执行完成
            System.out.println("isEnded"+pi.isEnded());
            // 流程实例是否被挂起
            System.out.println("isSuspended:"+pi.isSuspended());
        }
    }
}
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

# 暂停与激活流程实例

挂起后,后面的任务就都执行不了了。

package com.example.workflow;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Part3_ProcessInstance {

    @Autowired
    private RuntimeService runtimeService;

    @Test
    public void activitieProcessInstance(){
        // runtimeService.suspendProcessInstanceById("73f0fb9a-ce5b-11ea-bf67-dcfb4875e032");
        // System.out.println("挂起流程实例");

        runtimeService.activateProcessInstanceById("73f0fb9a-ce5b-11ea-bf67-dcfb4875e032");
        System.out.println("激活流程实例");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

注意:如果要挂起/激活的流程实例已经被挂起/激活,会报错,实际使用时需要 try-catch。

# 删除流程实例

deleteProcessInstance() 接收两个参数:

  • 第一个参数:processInstanceId(就是流程定义的ID)
  • 第二个参数:deleteReason(就是删除理由)
package com.example.workflow;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Part3_ProcessInstance {

    @Autowired
    private RuntimeService runtimeService;

    @Test
    public void delProcessInstance(){
        runtimeService.deleteProcessInstance("73f0fb9a-ce5b-11ea-bf67-dcfb4875e032","删着玩");
        // 如果使用下面的方法,会同时删除运行时和历史数据
        // historyService.deleteHistoricProcessInstance(processInstanceId);
        System.out.println("删除流程实例");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

注意:如果要删除的流程实例不存在,会报错,实际使用时需要 try-catch。

# 任务处理 Task

「任务」在 bpmn-js 的可视化页面中是一个矩形,然后在矩形的左侧添加具体的图表表示一个特定的任务类型。

Activiti7 中主要有以下几种任务类型:

  • 用户任务(UserTask):需要人来参与,需要人为触发
  • 服务任务(ServiceTask)
  • 脚本任务(ScriptTask)
  • 接收任务(ReceiveTask)
  • 其他自动执行的任务节点

# 用户任务

用户任务有以下几个元素。

  • Assignee:执行人/代理人
  • Candidate Users:候选人(多个,英文逗号 , 分隔),谁先拾取任务就谁来执行任务
  • Candidate Groups:候选组(多个候选人可以分为一个组)
  • Due Date:任务到期时间(有的事件可以在此触发)

当流程执行到用户任务节点时,会在以下两个表中写入数据:

  • act_ru_task:运行时任务表,存储每个用户的执行数据
    • 存储任务的基本信息(如任务ID、名称、办理人等)。
    • 任务完成后,数据会从 act_ru_task 移至 act_hi_taskinst 历史表。
  • act_ru_variable:运行时流程变量表,存储运行时传递的变量参数
    • 启动流程实例、执行过程中、任务执行时都可以设置变量。
    • 流程实例结束、流程被删除,数据会从 act_ru_variable 移至 act_hi_varinst 历史表。

以报销流程为例,包含以下节点:

开始节点 -> 用户任务(发起报销)-> 用户任务(财务审批)-> 结束节点
1

对应在 Activiti7 中就需要编写以下方法:

  • 查询任务
    • 在流程实例创建后,第一个节点的人就能够查询到任务了
  • 执行任务
    • 发起报销的人看到任务后,填写并上传表单,此时就是执行任务,将任务流转到下一环节
    • 财务审批的人看到任务后,可以执行任务,将任务流转到下一环节
  • 拾取任务:给候选人用的方法

任务相关的 API 需要注入 TaskService 这个类。

package com.example.workflow;

import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part4_Task {

    @Autowired
    private TaskService taskService;

    // 查询所有任务(一般给管理员使用)
    @Test
    public void getTasks(){
        List<Task> list = taskService.createTaskQuery().list();
        for(Task tk : list){
            System.out.println("Id:"+tk.getId());
            System.out.println("Name:"+tk.getName());
            System.out.println("Assignee:"+tk.getAssignee());
        }
    }

    // 查询我的代办任务
    @Test
    public void getTasksByAssignee(){
        List<Task> list = taskService.createTaskQuery()
                .taskAssignee("bajie") // 此处参数为执行人
                .list();
        for(Task tk : list){
            System.out.println("Id:"+tk.getId());
            System.out.println("Name:"+tk.getName());
            System.out.println("Assignee:"+tk.getAssignee());
        }
    }

    // 执行任务
    @Test
    public void completeTask(){
        taskService.complete("d07d6026-cef8-11ea-a5f7-dcfb4875e032"); // 此处参数为 taskId,也可以传一些变量
        System.out.println("完成任务");
    }

    // 拾取任务
    @Test
    public void claimTask(){
        Task task = taskService.createTaskQuery().taskId("1f2a8edf-cefa-11ea-84aa-dcfb4875e032").singleResult();
        taskService.claim("1f2a8edf-cefa-11ea-84aa-dcfb4875e032", "bajie");
    }

    // 归还与交办任务
    @Test
    public void setTaskAssignee(){
        Task task = taskService.createTaskQuery().taskId("1f2a8edf-cefa-11ea-84aa-dcfb4875e032").singleResult();
        taskService.setAssignee("1f2a8edf-cefa-11ea-84aa-dcfb4875e032", "null");  // 归还候选任务
        taskService.setAssignee("1f2a8edf-cefa-11ea-84aa-dcfb4875e032", "wukong"); // 交办任务
    }
}
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

# 服务任务

常见用途:

  • 发短信
  • 发邮件
  • 调用其他接口

服务任务是自动执行的,最简单的实现方式是「Java 类」。需要给它指定一个执行类,该执行类必须继承 JavaDelegate 接口。

在 BPMN XML 中,服务任务需要使用 <serviceTask> 元素,并且需要指定以下关键属性:

<serviceTask id="服务任务的唯一标识符" name="服务任务的显示名称"
    activiti:class="com.example.workflow.delegate.MyServiceTask"
    activiti:async="true">
</serviceTask>
1
2
3
4

执行类的代码示例如下:

package com.example.workflow.delegate;

import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;

public class SendEmailDelegate implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        // 执行发送邮件的具体逻辑
        // 获取流程相关的值
        System.out.println(execution.getEventName());
        System.out.println(execution.getProcessDefinitionId());
        System.out.println(execution.getProcessInstanceId());
        // 获取流程变量
        String username = (String) execution.getVariable("email");
        // 也可以设置流程变量
        execution.setVariable("name", "张三");
        
        // ... 处理业务逻辑 ...
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

但在 BPMN XML 中直接填写 Java 类的完整类名存在代码泄露的风险,因此比较推荐使用委托表达式。

BPMN XML文件的修改:

<serviceTask id="服务任务的唯一标识符" name="服务任务的显示名称" 
    activiti:delegateExpression="${sendEmailDelegate}" 
    activiti:async="true">
</serviceTask>
1
2
3
4

Java类的修改:

package com.example.workflow.delegate;

import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.springframework.stereotype.Component;

@Component("sendEmailDelegate")  // Bean 名称要与流程定义中的表达式一致
public class SendEmailDelegate implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        // 执行发送邮件的具体逻辑
        // 获取流程相关的值
        System.out.println(execution.getEventName());
        System.out.println(execution.getProcessDefinitionId());
        System.out.println(execution.getProcessInstanceId());
        // 获取流程变量
        String username = (String) execution.getVariable("email");
        // 也可以设置流程变量
        execution.setVariable("name", "张三");
        
        // ... 处理业务逻辑 ...
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 手工任务

它的作用就是在 BPMN 图中留个痕迹(标记),表示这件事情线下去办且不用做表单记录。因此这种任务实际上不会做任何代码处理,就算画了,也是直接通过的。

# 历史任务 HistoricTaskInstance

也就是查询历史记录,往往查询历史数据会涉及到两个类:

  • 历史综合信息:HistoricTaskInstance
  • 历史变量:HistoricVariableInstance

# 根据用户名查询历史

历史相关的 API 需要注入 HistoryService 这个类。

package com.example.workflow;

import org.activiti.engine.HistoryService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part5_HistoricTaskInstance {
    @Autowired
    private HistoryService historyService;

    @Test
    public void HistoricTaskInstanceByUser() {
        List<HistoricTaskInstance> list = historyService
                .createHistoricTaskInstanceQuery()
                .orderByHistoricTaskInstanceEndTime().asc()
                .taskAssignee("bajie")
                .list();
        for(HistoricTaskInstance hi : list) {
            System.out.println("Id:"+ hi.getId());
            System.out.println("ProcessInstanceId:"+ hi.getProcessInstanceId());
            System.out.println("Name:"+ hi.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
25
26
27
28
29

# 根据流程实例ID查询历史

历史相关的 API 需要注入 HistoryService 这个类。

package com.example.workflow;

import org.activiti.engine.HistoryService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part5_HistoricTaskInstance {
    @Autowired
    private HistoryService historyService;

    @Test
    public void HistoricTaskInstanceByPiID(){
        List<HistoricTaskInstance> list = historyService
                .createHistoricTaskInstanceQuery()
                .orderByHistoricTaskInstanceEndTime().asc()
                .processInstanceId("1f2314cb-cefa-11ea-84aa-dcfb4875e032")
                .list();
        for(HistoricTaskInstance hi : list){
            System.out.println("Id:"+ hi.getId());
            System.out.println("ProcessInstanceId:"+ hi.getProcessInstanceId());
            System.out.println("Name:"+ hi.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
25
26
27
28
29

(完)