Activiti7进阶

一.流程实例

流程实例

ProcessInstance)代表流程定义的执行实例。

一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。

例如:用户或程序按照流程定义内容发起一个流程,这就是一个流程实例。

流程定义和流程实例的图解:

image-20230523212659883

启动流程实例 并添加Businesskey(业务标识

流程定义部署在activiti后,就可以在系统中通过activiti去管理该流程的执行,执行流程表示流程的一次执行。

比如部署系统出差流程后,如果某用户要申请出差这时就需要执行这个流程,如果另外一个用户也要申请出差则也需要执行该流程,每个执行互不影响,每个执行是单独的流程实例。

启动流程实例时,指定的businesskey,就会在act_ru_execution #流程实例的执行表中存储businesskey。

Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。

比如:出差流程启动一个流程实例,就可以将出差单的id作为业务标识存储到activiti中,将来查询activiti的流程实例信息就可以获取出差单的id从而关联查询业务系统数据库得到出差单信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 启动流程实例,添加businessKey
*/
@Test
public void addBusinessKey(){
// 得到processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 得到runtimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 启动流程实例,同时还要指定业务标识businessKey,也就是出差申请单id,这里是1001
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myEvection", "1001");
// 输出processInstance相关属性
System.out.println("业务id = " + processInstance.getBusinessKey());
}

Activiti的act_ru_execution中存储业务标识:

image-20230523214445683

操作数据库表

启动流程实例,操作如下数据库表:

SELECT * FROM act_ru_execution #流程实例执行表,记录当前流程实例的执行情况

image-20230523214520033

说明:

流程实例执行,如果当前只有一个分支时,一个流程实例只有一条记录且执行表的主键id和流程实例id相同,如果当前有多个分支正在运行则该执行表中有多条记录,存在执行表的主键和流程实例id不相同的记录。不论当前有几个分支总会有一条记录的执行表的主键和流程实例id相同

一个流程实例运行完成,此表中与流程实例相关的记录删除。

SELECT * FROM act_ru_task #任务执行表,记录当前执行的任务

image-20230523214607802

说明:启动流程实例,流程当前执行到第一个任务结点,此表会插入一条记录表示当前任务的执行情况,如果任务完成则记录删除。

SELECT * FROM act_ru_identitylink #任务参与者,记录当前参与任务的用户或组

image-20230523214654630

SELECT * FROM act_hi_procinst #流程实例历史表

image-20230523214731026

流程实例启动,会在此表插入一条记录,流程实例运行完成记录也不会删除。

image-20230523214832196

开始一个任务,不仅在act_ru_task表插入记录,也会在历史任务表插入一条记录,任务历史表的主键就是任务id,任务完成此表记录不删除。

SELECT * FROM act_hi_actinst #活动历史表,记录所有活动

image-20230523214935396

活动包括任务,所以此表中不仅记录了任务,还记录了流程执行过程的其它活动,比如开始事件、结束事件。

查询流程实例

流程在运行过程中可以查询流程实例的状态,当前运行结点等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void queryProcessInstance(){
// 流程定义key
String processDefinitionKey = "evection";
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.list();
for (ProcessInstance processInstance : list) {
System.out.println("----------------------------------");
System.out.println("流程实例id = " + processInstance.getProcessInstanceId());
System.out.println("所属流程定义id = " + processInstance.getProcessDefinitionId());
System.out.println("是否执行完成 = " + processInstance.isEnded());
System.out.println("是否暂停 = " + processInstance.isSuspended());
System.out.println("当前活动标识 = " + processInstance.getActivityId());
}
}

关联BusinessKey

需求:

在activiti实际应用时,查询流程实例列表时可能要显示出业务系统的一些相关信息,比如:查询当前运行的出差流程列表需要将出差单名称、出差天数等信息显示出来,出差天数等信息在业务系统中存在,而并没有在activiti数据库中存在,所以是无法通过activiti的api查询到出差天数等信息。

实现:

在查询流程实例时,通过businessKey(业务标识 )关联查询业务系统的出差单表,查询出出差天数等信息。

通过下面的代码就可以获取activiti中所对应实例保存的业务Key。而这个业务Key一般都会保存相关联的业务操作表的主键,再通过主键ID去查询业务信息,比如通过出差单的ID,去查询更多的请假信息(出差人,出差时间,出差天数,出差目的地等)

processInstance.getBusinessKey();

在activiti的act_ru_execution表,字段BUSINESS_KEY就是存放业务KEY的。

image-20230523224944513

挂起、激活流程实例

某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行。

全部流程实例挂起

操作流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停:

流程定义为挂起状态该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将全部挂起暂停执

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
/**
* 全部流程实例挂起与暂停
*/
@Test
public void suspendAllProcessInstance(){
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 查询流程定义的对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("myEvenction")
.singleResult();
// 得到当前流程定义的实例是否都是暂停状态
boolean suspended = processDefinition.isSuspended();
// 流程定义id
String processDefinitionId = processDefinition.getId();
// 判断是否为暂停
if (suspended) {
// 如果是暂停,可以执行激活操作,参数1:流程定义id,参数2:是否激活,参数3:激活时间
repositoryService.activateProcessDefinitionById(processDefinitionId,true,null);
System.out.println("流程定义:"+processDefinitionId+",已激活");
}else {
// 如果是激活状态,可以暂停,参数1:流程定义id,参数2:是否暂停,参数3:暂停时间
repositoryService.suspendProcessDefinitionById(processDefinitionId,true,null);
System.out.println("流程定义:"+processDefinitionId+",已挂起");
}
}

单个流程实例挂起

操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再继续执行,完成该流程实例的当前任务将报异常。

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
/**
* 单个流程实例挂起与激活
*/
@Test
public void suspendSingleProcessInstance(){
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// runtimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 查询定义流程对象
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processDefinitionKey("2501")
.singleResult();
// 得到当前流程定义的实例是否都为暂停状态
boolean suspended = processInstance.isSuspended();
// 流程定义id
String processDefinitionId = processInstance.getId();
// 判断是否为暂停
if (suspended) {
// 如果是暂停,可以执行激活操作,参数:流程定义id
runtimeService.activateProcessInstanceById(processDefinitionId);
System.out.println("流程定义:"+processDefinitionId+",已挂起");
}else {
// 如果是激活状态,可以暂停,参数:流程定义id
runtimeService.suspendProcessInstanceById(processDefinitionId);
System.out.println("流程定义:"+processDefinitionId+",已挂起");
}
}

/**
* 测试完成个人任务
*/
@Test
public void completeSingleTask(){
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取操作任务的服务 TaskService
TaskService taskService = processEngine.getTaskService();
// 完成任务,参数:流程实例id,完成zhangsan的任务
Task task = taskService.createTaskQuery()
.processInstanceId("2501")
.taskAssignee("zhangsan")
.singleResult();
System.out.println("流程实例id = " + task.getProcessInstanceId());
System.out.println("任务id = " + task.getId());
System.out.println("任务负责人 = " + task.getAssignee());
System.out.println("任务名称 = " + task.getName());
taskService.complete(task.getId());

}

二.个人任务

2.1、分配任务负责人

2.1.1、固定分配

在进行业务流程建模时指定固定的任务负责人,如图:

image-20230523232327220

并在properties试图中,填写Assignee项为任务负责人。

2.1.2、表达式分配

由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照 bpmn 的配置去分配任 务负责人。

2.1.2.1、UEL表达式

Activiti 使用 UEL 表达式, UEL 是 java EE6 规范的一部分, UEL(Unified Expression Language)即 统一表达式语言,activiti 支持两个 UEL 表达式: UEL-value 和 UEL-method。

1)UEL-value定义

如图:

image-20230523233342061

assignee这个变量是activiti的一个流程变量,

或者使用这种方式定义:

如图:

image-20230523233706248

user 也是 activiti 的一个流程变量, user.assignee 表示通过调用 user 的 getter 方法获取值。

2)UEL-method方式

如图:

image-20230523232937447

UserBean是Spring容器中的一个Bean,表示调用该Bean的getUserId()方法。

3)UEL-method与UEL-value结合

再比如: ${ldapService.findManagerForEmployee(emp)} ldapService 是 spring 容器的一个 bean,

findManagerForEmployee 是该 bean 的一个方法,emp 是 activiti 流程变量, emp 作为参数传到

ldapService.findManagerForEmployee 方法中。

4)其他

表达式支持解析基础类型、 bean、 list、 array 和 map,也可作为条件判断。 如下:

${order.price > 100 && order.price < 250}

2.1.2.2、编写代码配置负责人

1)定义任务分配流程变量

如图:

image-20230523233424469

2)设置流程变量

在启动流程实例时设置流程变量,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 设置流程负责人
*/
@Test
public void assgineeUEL(){
// 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取runtimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 设置assginee的值,用户可以在界面上设置流程的执行
HashMap<String, Object> assgineeMap = new HashMap<>();
assgineeMap.put("assginee0","张三");
assgineeMap.put("assginee1","李经理");
assgineeMap.put("assginee2","王总经理");
assgineeMap.put("assginee3","赵财务");
// 启动流程实例,同时还要设置流程定义的assginee的值
runtimeService.startProcessInstanceByKey("myEvection",assgineeMap);
// 输出
System.out.println(processEngine.getName());

}

执行成功后,在act_ru_variable表中看到刚才map中的数据

image-20230523234304467

2.1.2.3、注意事项

由于使用了表达式分配,必须保证在任务执行过程表达式执行成功,比如:

某个任务使用了表达式${order.price > 100 && order.price < 250},当执行该任务时必须保证 order 在 流程变量中存在,否则 activiti 异常。

2.1.3、监听器分配

可以使用监听器来完成很多Activiti流程的业务。

在本章我们使用监听器的方式来指定负责人,那么在流程设计时就不需要指定assignee。

任务监听器是发生对应的任务相关事件时执行自定义 java 逻辑 或表达式。 任务相当事件包括:

image-20230524011649214

Event的选项包含:

1
2
3
4
Create:任务创建后触发 
Assignment:任务分配后触发
Delete:任务完成后触发
All:所有事件发生都触发

定义任务监听类,且类必须实现 org.activiti.engine.delegate.TaskListener 接口

1
2
3
4
5
6
7
8
9
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
if (delegateTask.getName().equals("创建出差申请") && delegateTask.getEventName().equals("create")) {
// 指定任务负责人
delegateTask.setAssignee("张三");
}
}
}

DelegateTask对象的内容如下

image-20230524011347816

2.1.3.1、注意事项

使用监听器分配方式,按照监听事件去执行监听类的 notify 方法,方法如果不能正常执行也会影响 任务的执行。

2.2、查询任务

代码如下:

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
/**
* 查询个人当前待执行的任务
*/
@Test
public void findPersonalTaskList(){
// 流程定义key
String processDefinitionKey = "demo";
// 任务负责人
String assignee = "张三";
// 获取TaskService

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.includeProcessVariables()
.taskAssignee(assignee)
.list();
for (Task task : taskList) {
System.out.println("---------------------------------------");
System.out.println("流程实例id = " + task.getProcessInstanceId());
System.out.println("任务id = " + task.getId());
System.out.println("任务负责人 = " + task.getAssignee());
System.out.println("任务名称 = " + task.getName());


}
}

关联businessKey

需求:在 activiti 实际应用时,查询待办任务可能要显示出业务系统的一些相关信息。

比如:查询待审批出差任务列表需要将出差单的日期、 出差天数等信息显示出来。

出差天数等信息在业务系统中存在,而并没有在 activiti 数据库中存在,所以是无法通过 activiti 的 api 查询到出差天数等信息。 实现: 在查询待办任务时,通过 businessKey(业务标识 )关联查询业务系统的出差单表,查询出出差天数等信息。

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
/*** 
* businessKey
*/
@Test
public void findProcessInstance(){
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取taskService
TaskService taskService = processEngine.getTaskService();
// 获取runtimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 查询流程定义的对象
Task task = taskService.createTaskQuery()
.processDefinitionKey("demo")
.taskAssignee("张三")
.singleResult();
// 使用task对象获取实例id
String processInstanceId = task.getProcessInstanceId();
// 使用实例id,获取流程实例对象
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
// 使用processInstance,得到businessKey
String businessKey = processInstance.getBusinessKey();
System.out.println("businessKey = " + businessKey);
}

2.3、办理业务

注意:在实际应用中,完成任务需要校验任务的负责人是否具有该任务的办理权限。

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
/**
* 完成任务,判断当前用户是否有权限
*/
@Test
public void completeTask() {
// 任务id
String taskId = "20001";
// 任务负责人
String assignee = "张三";
// 创建processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建taskService
TaskService taskService = processEngine.getTaskService();
// 完成任务前,需要校验该任务负责人可以完成当前任务
// 校验方法
// 根据任务id和任务负责人查询当前任务,如果查询到该用户权限,就完成
Task task = taskService.createTaskQuery()
.taskAssignee(assignee)
.taskId(taskId)
.singleResult();
if (task != null) {
taskService.complete(taskId);
System.out.println("完成任务");
}
}