查看之前的博客可以点击顶部的【分类专栏】
一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。例如:用户或程序按照流程定义内容发起一个流程,这就是一个流程实例。
个人理解的流程实例:是具体到某一个流程定义的一个实际例子,叫流程实例。比如公司的请假流程叫做【流程定义】,它规定请假要经过多少人审批。而张三发起的请假申请,是流程实例,是具体的某一个案例,因此叫流程实例。
Activiti 工作流系统如何与我们实际业务整合呢?
比如部署系统请假流程后,如果某用户要申请请假这时就需要执行这个流程,如果另外一个用户也要申请请假则也需要执行该流程,每个执行互不影响,每个执行是单独的流程实例。
启动流程实例时,指定的 businesskey,就会在 act_ru_execution #流程实例执行表中存储 businesskey。
Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。
比如:请假流程启动一个流程实例,就可以将出请假的id作为业务标识存储到 activiti 中,将来查询 activiti 的流程实例信息就可以获取出请假的id从而关联查询业务系统数据库得到请假单信息。
启动流程实例的代码:
/**
* 启动流程实例,增加 businessKey
*/
@Test
public void addBusinessKey(){
//创建 ProcessEngine
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到 RuntimeService
RuntimeService runtimeService = engine.getRuntimeService();
//启动流程实例,同时还要指定业务标识 businessKey,也就是请假申请单id,这里是1001
ProcessInstance instance = runtimeService.startProcessInstanceByKey("myApply","1001");
System.out.println("busniessKey="+instance.getBusinessKey());
}
运行结果:
busniessKey=1001
数据库表: act_ru_execution
流程实例执行,如果当前只有一个分支时,一个流程实例只有一条记录且执行表的主键id和流程实例id相同,如果当前有多个分支正在运行则该执行表中有多条记录,存在执行表的主键和流程实例id不相同的记录。不论当前有几个分支总会有一条记录的执行表的主键和流程实例id相同。
一个流程实例运行完成,此表中与流程实例相关的记录删除。
某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行。
2-1 全部流程实例挂起
说明:因为之前为了测试,弄了几条部署的流程,导致冗余数据较多,因此我把数据库删掉,重新建表,只部署了一个实例来测试。
操作流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停:
流程定义为挂起状态该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将全部挂起暂停执行。
全部流程暂停 or 挂起的代码:
/**
* 全部流程挂起与激活
*/
@Test
public void suspendAllProcessInstance(){
//创建 ProcessEngine
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//获取RepositoryService实例
RepositoryService repositoryService = engine.getRepositoryService();
//查询流程定义的对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("myApply")//根据流程定义的key查询
.singleResult();
//得到当前流程定义的实例是否都为暂停状态
boolean flag = processDefinition.isSuspended();
if(flag){//如果暂停,就激活。参数:流程ID,是否激活,激活时间
repositoryService.activateProcessDefinitionById(
processDefinition.getId(),
true,
null
);
System.out.println("流程定义id="+processDefinition.getId()+" 已激活");
}else{//如果是激活状态,我们就暂停。参数:流程ID,是否暂停,暂停时间
repositoryService.suspendProcessDefinitionById(
processDefinition.getId(),
true,
null
);
System.out.println("流程定义id="+processDefinition.getId()+" 已挂起");
}
}
测试:
流程定义id=myApply:1:4 已挂起
查看数据库表:(我们通过对日志文件搜索【update ACT_】就知道它更新了哪些表了。)
act_ru_task 和 act_re_procdef 的状态 SUSPENSION_STATE_ 都改为2。
再点击测试:
流程定义id=myApply:1:4 已激活
状态改为了1。因此:1=激活,2=挂起状态。
2-2 单个流程实例挂起
操作流程实例对象,针对单个流程执行挂起操作,某个流程实例挂起则此流程不再继续执行,完成该流程实例的当前任务将报异常。
代码:(要寻找流程实例ID,可以去表:act_ru_task 寻找。)
/**
* 单个流程实例挂起与激活
*/
@Test
public void suspendSingleProcessInstance(){
//创建 ProcessEngine
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = engine.getRuntimeService();
//查询流程定义的对象
ProcessInstance processInstance = runtimeService.
createProcessInstanceQuery().
processInstanceId("2501").
singleResult();
boolean flag = processInstance.isSuspended();
if(flag){//如果是暂停状态,则激活
runtimeService.activateProcessInstanceById(processInstance.getId());
System.out.println("流程实例 id ="+processInstance.getId()+",已激活");
}else{
runtimeService.suspendProcessInstanceById(processInstance.getId());
System.out.println("流程实例 id ="+processInstance.getId()+",已挂起");
}
}
启动测试:
流程实例 id =2501,已挂起
然后我们查看数据库:(我们通过对日志文件搜索【update ACT_】就知道它更新了哪些表了。)
表:act_ru_task
表:act_ru_execution
在暂停状态,我们是否可以继续完成个人任务呢?
我们增加完成个人任务的测试方法:
/**
* 测试完成个人任务
*/
@Test
public void completeTask(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取任务 Service
TaskService taskService = processEngine.getTaskService();
//完成任务,参数:流程实例id,完成 zhangsan 的任务
Task task = taskService.createTaskQuery()
.processInstanceId("2501")
.taskAssignee("lisi")
.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());
}
点击测试:
org.activiti.engine.ActivitiException: Cannot complete a suspended task
at org.activiti.engine.impl.cmd.NeedsActiveTaskCmd.execute(NeedsActiveTaskCmd.java:53)
at org.activiti.engine.impl.interceptor.CommandInvoker$1.run(CommandInvoker.java:37)
发现报错:不能完成已挂起的任务。
说明当单个任务被挂起之后,是不能继续完成的。
我们可以再次把已挂起的任务激活,重新测试。就是测试上一个方法。
流程实例 id =2501,已激活
然后,再次点击完成任务的测试:
任务已经完成了。
1-1 固定分配(了解即可,实际开发不会用到)
在进行业务流程建模时指定固定的任务负责人,在 properties 视图中,填写 Assignee 项为任务负责人。
1-2 表达式分配
1-2-1 UEL 表达式
Activiti 使用 UEL 表达式, UEL 是 java EE6 规范的一部分, UEL(Unified Expression Language)即统一表达式语言, activiti 支持两个 UEL 表达式: UEL-value 和 UEL-method。
UEL-value 方式,语法:${assignee} 或者 :${user.assignee}
assignee 这个变量是 activiti 的一个流程变量。user 也是 activiti 的一个流程变量, user.assignee 表示通过调用 user 的 getter 方法获取值。
UEL-method 方式,语法:${UserBean.getUserId()}
userBean 是 spring 容器中的一个 bean,表示调用该 bean 的 getUserId()方法。
UEL-method 与 UEL-value 结合
${ldapService.findManagerForEmployee(emp)}
ldapService 是 spring 容器的一个 bean,findManagerForEmployee 是该 bean 的一个方法,emp 是 activiti 是流程变量, emp 作为参数传到 ldapService.findManagerForEmployee 方法中。
其它:
表达式支持解析基础类型、 bean、 list、 array 和 map,也可作为条件判断。
如下:${order.price > 100 && order.price < 250}
1-2-2 编写代码使用 UEL 配置负责人
我们需要重新定义一个流程,以区分之前测试的。那我们就定义一个报销流程吧。
创建一个 reimburse.bpmn 文件,可以查看前面的博客来创建。
完整 xml 代码:
然后生成 reimburse.png 图片。并且放到 resources 目录下。
然后部署流程定义。(跟前面介绍的一样的部署方式)
/**
* 部署流程定义
*/
@Test
public void testUELDeployment(){
//创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//得到 RepositoryService 实例
RepositoryService service = processEngine.getRepositoryService();
//使用 RepositoryService 实例进行部署
Deployment deployment = service.createDeployment()
.addClasspathResource("bpmn/reimburse.bpmn") //添加 bpmn 资源
.addClasspathResource("bpmn/reimburse.png") //添加 png 资源
.name("报销流程")
.deploy();
System.out.println("流程部署Id="+deployment.getId());
System.out.println("流程部署name="+deployment.getName());
}
运行结果:
流程部署Id=22501
流程部署name=报销流程
然后,启动流程实例(需要设置流程的变量)注意,我们不能使用前面说的方法直接启动流程实例,因为还没指定人,会导致审批人为空的情况。代码如下:
/**
* 启动 UEL 流程实例
*/
@Test
public void testStartUELProcess(){
//1、创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2、获取 RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
//设置 assignee 的取值,用户可以在页面上设置流程的执行
Map map = new HashMap<>();
map.put("assignee0","大师兄");
map.put("assignee1","二师兄");
map.put("assignee2","三师兄");
//根据流程定义ID启动流程
ProcessInstance instance = runtimeService.startProcessInstanceByKey("myReimburse",map);
System.out.println("流程定义id=" + instance.getProcessDefinitionId());
System.out.println("流程实例id=" + instance.getId());
System.out.println("当前活动Id=" + instance.getActivityId());
}
测试:
流程定义id=myReimburse:1:22504
流程实例id=25001
当前活动Id=null
act_re_deployment 流程定义部署表:
在表:act_ru_variable 可以看到结果:
1-2-3 注意事项
由于使用了表达式分配,必须保证在任务执行过程表达式执行成功,比如:
某个任务使用了表达式${order.price > 100 && order.price < 250},当执行该任务时必须保证 order 在流程变量中存在,否则 activiti 异常。
1-3 监听器分配
可以使用监听器来完成很多Activiti流程的业务。我们使用监听器的方式来指定负责人,那么在流程设计时就不需要指定assignee。
任务监听器是发生对应的任务相关事件时执行自定义 java 逻辑 或表达式。
我们再创建一个流程定义:离职审批流程。myLeave.bpmn
点击 Task Listeners 右边的文件夹图标,然后 Event 选择 Create。
Event的选项包含:
Create:任务创建后触发。
Assignment:任务分配后触发。
Delete:任务完成后触发。
All:所有事件发生都触发。
然后 Type 选择 Class。这个 Class 从哪里来呢?是需要我们自己创建并监听 Activiti 的接口的。
我们先创建 MyListener:在包:com.study.listener 下面。
package com.study.listener;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
/**
* @author biandan
* @description 监听器
* @signature 让天下没有难写的代码
* @create 2021-06-13 下午 11:44
*/
public class MyListener implements TaskListener {
/**
* 回调函数
*
* @param delegateTask
*/
@Override
public void notify(DelegateTask delegateTask) {
//判断是离职审批,并且是经理审批,才创建审批人
if (delegateTask.getExecution().getProcessDefinitionId().startsWith("myLeave")
&& delegateTask.getName().equals("经理审批")
&& delegateTask.getEventName().equals("create")) {
//指定审批负责人
delegateTask.setAssignee("马芸");
}
//判断是离职审批,并且是人资审批,才创建审批人
if (delegateTask.getExecution().getProcessDefinitionId().startsWith("myLeave")
&& delegateTask.getName().equals("人资审批")
&& delegateTask.getEventName().equals("create")) {
//指定审批负责人
delegateTask.setAssignee("刘备");
}
}
}
然后,我们把 MyListener 的全路径粘贴到 Class 中:
myLeave.xml 如下:
然后还是生成 myLeave.png ,放到 resources 下,部署离职的流程定义。
/**
* 部署流程定义:离职审批
*/
@Test
public void testMyLeaveDeployment(){
//创建 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//得到 RepositoryService 实例
RepositoryService service = processEngine.getRepositoryService();
//使用 RepositoryService 实例进行部署
Deployment deployment = service.createDeployment()
.addClasspathResource("bpmn/myLeave.bpmn") //添加 bpmn 资源
.addClasspathResource("bpmn/myLeave.png") //添加 png 资源
.name("离职审批")
.deploy();
System.out.println("流程部署Id="+deployment.getId());
System.out.println("流程部署name="+deployment.getName());
}
结果:
流程部署Id=27501
流程部署name=离职审批
然后,启动离职流程的流程实例 注意打上断点查看数据的变化:
delegateTask 实例如下:
注意事项:使用监听器分配方式,按照监听事件去执行监听类的 notify 方法,方法如果不能正常执行也会影响任务的执行。
2-2-1 查询任务负责人的待办任务并且关联 businessKey
在 activiti 实际应用时,查询待办任务可能要显示出业务系统的一些相关信息。比如:查询待审批报销任务列表需要将报销单的日期、 报销金额等信息显示出来。
报销金额等信息在业务系统中存在,而并没有在 activiti 数据库中存在,所以是无法通过 activiti 的 api 查询到报销金额等信息。
实现:
在查询待办任务时,通过 businessKey(业务标识 )我们再去关联查询业务系统的报销表,查询报销金额等信息。
/**
* 查询当前个人待执行的任务 并查询出 businessKey
*/
@Test
public void testFindProcessInstance() {
//获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//获取TaskService
TaskService taskService = processEngine.getTaskService();
//获取RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
//查询流程定义的对象
Task task = taskService.createTaskQuery()
.processDefinitionKey("myReimburse")
.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);
}
注意:在实际应用中,完成任务前需要校验任务的负责人是否具有该任务的办理权限。
/**
* 完成任务,判断当前用户是否有权限
*/
@Test
public void testComplete() {
//任务id
String taskId = "25001";
//任务负责人
String assignee = "大师兄";
//获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
// 完成任务前,需要校验该负责人可以完成当前任务
//校验方法:根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成
Task task = taskService.createTaskQuery()
.taskId(taskId)
.taskAssignee(assignee)
.singleResult();
if (task != null) {
taskService.complete(taskId);
System.out.println("完成任务");
}
}