基础篇看:链接: https://blog.csdn.net/qq_48721706/article/details/122514133
一、个人任务
1、分配任务负责人
1)、UEL 表达式分配
Activiti 使用 UEL 表达式, UEL 是 java EE6 规范的一部分, UEL(Unified Expression Language)即 统一表达式语言,
activiti 支持两个 UEL 表达式: UEL-value 和 UEL-method。
1.1、UEL-value 方式
${assignee}
,assignee 这个变量是 activiti 的一个流程变量
或者${user.userId}
,user 也是 activiti 的一个流程变量, user.userId表示通过调用 user 的 getter 方法获取值。
1.2、UEL-method方式
${UserBean.getUserId}
,userBean 是 spring 容器中的一个 bean,表示调用该 bean 的 getUserId()方法。
1.3、UEL-method 与 UEL-value 结合
${UserService.getUserId(emp)}
UserService是 spring 容器的一个 bean, getUserId是该 bean 的一个方法,emp 是 activiti 流程变量, emp 作为参数传到UserService.getUserId方法中。
1.4、其它
表达式支持解析基础类型、 bean、 list、 array 和 map,也可作为条件判断。 如下: ${order.price > 100 && order.price < 250}
public static void main(String[] args) {
// 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 设置用户
User user = new User("lt", "lt");
// 设置assignee的值,用uel表达式
HashMap<String, Object> assignee = new HashMap<String, Object>();
assignee.put("assignee0","hmb");
assignee.put("assignee1","lt");
assignee.put("assignee2","hwt");
// 启动流程实例,同时还要设置流程定义的assignee的值
runtimeService.startProcessInstanceByKey("myEvection",assignee); // 输出 System.out.println(processEngine.getName());
}
2)、监听器设置负责人
actiBPMN点不开,不会用。会用了再来补
2、查询关联 businessKey
在 activiti 实际应用时,查询待办任务可能要显示出业务系统的一些相关信息。将businessKey查出来再去查这个业务表的信息
public void findProcessInstance(){
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取TaskService
TaskService taskService = processEngine.getTaskService();
// 获取RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 查询流程定义的对象
Task task = taskService.createTaskQuery()
.processDefinitionKey("myEvection1")
.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);
}
3、办理任务
在实际应用中,完成任务前需要校验任务的负责人是否具有该任务的办理权限 。
就是拿id去查,有就有权限
public void completTask() {
//任务id
String taskId = "15005";
// 任务负责人
String assingee = "张三";
//获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
// 完成任务前,需要校验该负责人可以完成当前任务
// 校验方法:
// 根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成
Task task = taskService.createTaskQuery()
.taskId(taskId)
.taskAssignee(assingee)
.singleResult();
if(task != null){
taskService.complete(taskId);
System.out.println("完成任务");
}
}
二、流程变量
1、什么是流程变量
流程变量在 activiti 中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和 activiti 结合时少不了流程变量,流程变量就是 activiti 在管理工作流时根据管理需要而设置的变量。
比如:在出差申请流程流转时如果出差天数大于 3 天则由总经理审核,否则由人事直接审核, 出差天数就可以设置为流程变量,在流程流转时使用。
虽然流程变量中可以存储业务数据可以通过activiti的api查询流程变量从而实现 查询业务数据,但是不建议这样使用,因为业务数据查询由业务系统负责,activiti设置流程变量是为了流程执行需要而创建。
2、流程变量类型
如果将 pojo 存储到流程变量中,必须实现序列化接口 serializable,为了防止由于新增字段无 法反序列化,需要生成
serialVersionUID。
3、流程变量作用域
流程变量的作用域可以是一个流程实例(processInstance),或一个任务(task),或一个执行实例 (execution)
1)、global变量
流程变量的默认作用域是流程实例
。当一个流程变量的作用域为流程实例时,可以称为 global 变量
注意:
如: Global变量:userId(变量名)、zhangsan(变量值)
global 变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。
2)、local变量
任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大, 称为 local 变量。
Local 变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。Local 变量名也可以和 global 变量名相同,没有影响。
4、流程变量的使用方法
1)、在属性上使用UEL表达式
可以在 assignee 处设置 UEL 表达式,表达式的值为任务的负责人,比如: ${assignee}, assignee 就是一个流程变量名称。
Activiti获取UEL表达式的值,即流程变量assignee的值 ,将assignee的值作为任务的负责人进行任务分配
2)、在连线上使用UEL表达式
可以在连线上设置UEL表达式,决定流程走向。
比如:${price<10000} 。price就是一个流程变量名称,uel表达式结果类型为布尔类型。
如果UEL表达式是true,要决定 流程执行走向。
3)、global(全局)变量控制流程(测试流程,第一个分支)
画好流程,设置好连线变量和负责人变量
连线变量两种方式,与负责人一样:
uel-value:${day>=3}
uel-method:${evection.day<3}
最终画成
1、建一个实体类,放一些出差申请相关字段,要实现Serializable否则会报错。
为了验证${evection.day<3},传参看第三步
2、部署流程实例
1.1.1、启动时设置流程变量
设置连线判断参数,负责人参数,启动流程
${evection.day<3} 中的evection.day值, 直接传evenction,
如:variables.put(“evection”,evection);
public static void main(String[] args) {
// 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 设置流程变量
Evection evection = new Evection();
evection.setDay(2d); // 两天
// 流程变量的map,创建实例时的参数
Map<String, Object> variables = new HashMap();
variables.put("evection",evection);
variables.put("day",2);
// 设置流程负责人,就是${assignee1}, assignee
variables.put("assignee1","hmb");
variables.put("assignee2","lu");
variables.put("assignee3","hwt");
variables.put("assignee4","cw");
// 启动流程
runtimeService.startProcessInstanceByKey("myEvection",variables);
}
查看act_ru_variable表可以看到变量都已经保存进去了
4、完成任务
public static void main(String[] args) {
// 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取TaskService
TaskService taskService = processEngine.getTaskService();
// 查询任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("myEvection")
.taskAssignee("hmb")
.singleResult();
if (task!=null){
// 根据任务id完成任务
taskService.complete(task.getId());
}
}
5、切换taskAssignee,一步一步走下去,组长审批后,发现没有到经理审批,直接到财务,说明成功,到经理审批的流程设置day>=3,这里不重复演示
1.1.2、任务办理时设置变量
在完成任务时设置流程变量,该流程变量只有在该任务完成后其它结点才可使用该变量,它的作用域是整个流程实例,如果设置的流程变量的key在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。
这里需要在创建出差单任务完成时设置流程变量,(就是在完成认识时多加一个变量参数,去替换人)
public void completTaskAndChangeUser() {
//任务id
String key = "myEvection2";
// 任务负责人
String assingee = "张三";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
// 创建变量集合
Map<String, Object> map = new HashMap();
// 创建出差pojo对象
Evection evection = new Evection();
// 设置出差天数
evection.setDay(2d);
// 定义流程变量
map.put("evection",evection);
// 完成任务前,需要校验该负责人可以完成当前任务
// 校验方法: // 根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成
Task task = taskService.createTaskQuery() .processDefinitionKey(key)
.taskAssignee(assingee) .singleResult();
if(task != null){ //完成任务是,设置流程变量的值
taskService.complete(task.getId(),map);
System.out.println("任务执行完成");
}
}
说明:
通过当前任务设置流程变量,需要指定当前任务id,如果当前执行的任务id不存在则抛出异常。
任务办理时也是通过map<key,value>设置流程变量,一次可以设置多个变量。
1.1.3、通过当前流程实例、任务设置
1、通过流程实例id设置全局变量,该流程实例必须未执行完成。
public void setGlobalVariableByExecutionId(){
// 当前流程实例执行 id,通常设置为当前执行的流程实例
String executionId="2601";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 创建出差pojo对象
Evection evection = new Evection();
// 设置天数
evection.setDay(3d);
// 通过流程实例 id设置流程变量
runtimeService.setVariable(executionId, "evection", evection);
// 一次设置多个值
// runtimeService.setVariables(executionId, variables)
}
注意:
executionId必须当前未结束
流程实例的执行id,通常此id设置流程实例 的id。也可以通runtimeService.getVariable()获
取流程变量。
2、通过当前任务设置
public void setGlobalVariableByTaskId(){
//当前待办任务id
String taskId="1404";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Evection evection = new Evection();
evection.setDay(3d);
//通过任务设置流程变量
taskService.setVariable(taskId, "evection", evection);
//一次设置多个值
// taskService.setVariables(taskId, variables)
}
注意:
任务id必须是当前待办任务id
,act_ru_task中存在。如果该任务已结束,会报错
也可以通过taskService.getVariable()获取流程变量。
1.1.4、UEL表达式注意事项
1、 如果UEL表达式中流程变量名不存在则报错。
2、 如果UEL表达式中流程变量值为空NULL,流程不按UEL表达式去执行,而流程结束 。
3、 如果UEL表达式都不符合条件,流程结束
4、 如果连线不设置条件,会走flow序号小的那条线
4)、local(局部)变量
1、任务办理时设置
设置作用域为任务的local变量,每个任务可以设置同名的变量,互不影响。
任务办理时设置local流程变量,当前运行的流程实例只能在该任务结束前使用,任务结束该变量无法在当前流程实例使用,可以通过查询历史任务查询。
与golbal差别:taskService.setVariablesLocal
(taskId, variables);
/**处理任务时设置local流程变量 */
public void completTaskBylocal() {
//任务id
String taskId = "1404";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
// 定义流程变量
Map<String, Object> variables = new HashMap<String, Object>();
Evection evection = new Evection ();
evection.setDay(3d);
// 变量名是holiday,变量值是holiday对象
variables.put("evection", evection);
// 设置local变量,作用域为该任务
taskService.setVariablesLocal(taskId, variables);
// 完成任务
taskService.complete(taskId);
}
2、通过当前任务设置
任务id必须是当前待办任务id,act_ru_task中存在。
public void setLocalVariableByTaskId(){
// 当前待办任务id
String taskId="1404";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Evection evection = new Evection ();
evection.setDay(3d);
// 通过任务设置流程变量
taskService.setVariableLocal(taskId, "evection", evection);
// 一次设置多个值
// taskService.setVariablesLocal(taskId, variables);
}
三、组任务
在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。
针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。
1、设置候选人
在流程图中任务节点的配置中设置 candidate-users(候选人),多个候选人之间用逗号分开。
1)、组任务办理流程
1.1、查询组任务
指定候选人,查询该候选人当前的待办任务(就是拿候选人的名字去查当前有没有候选人里有他的待办任务)。
候选人不能立即办理任务。
public static void main(String arg[]) {
// 流程定义key
String processDefinitionKey = "candidateTest";
// 任务候选人
String candidateUser = "hmb1";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
//查询组任务
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.taskCandidateUser(candidateUser)//根据候选人查询
.list();
for (Task task : list) {
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());
}
}
查出当前有一个组长审批的待办任务里,有一个候选人是我们查的人
1.2、拾取任务
候选人拾取任务,把那个候选人变成主负责人(受让人)。
设置候选人时,没有设置受让人,所以需要指定候选人去完成任务
/**
* 候选人拾取任务
* @param args
*/
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
String taskId ="87502";
String candidateUser ="hmb1";
// 查询这个任务,有没有一个hmb1的候选人
Task task = taskService.createTaskQuery().taskId(taskId)
.taskCandidateUser(candidateUser)
.singleResult();
if (task!=null){ // 如果有,就拾取任务,把hmb1变成主负责人
// 拾取任务
taskService.claim(taskId,candidateUser);
}
}
拾取成功,受让人变成hmb1
即使该用户不是候选人也能拾取,建议拾取时校验是否有资格
组任务拾取后,该任务已有负责人,通过候选人将查询不到该任务
1.3、归还任务
如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人
将刚刚拾取的任务,归还到任务组里去,实际就是把任务责任人设置为null
/**
* 归还任务
* @param args
*/
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
String taskId = "87502";
String assignee = "hmb1";
// 根据key 和 负责人来查询任务
Task task = taskService.createTaskQuery()
.taskId(taskId)
.taskAssignee(assignee)
.singleResult();
if (task!=null){
// 归还任务 就是把负责人设置为null
taskService.setAssignee(taskId,null);
}
}
建议归还任务前校验该用户是否是该任务的负责人
也可以通过setAssignee方法将任务委托给其它用户负责,注意被委托的用户可以不是候选人(建议不要这样使用)
1.4、任务交接
任务交接,任务负责人将任务交给其它候选人办理该任务
就是上一个归还时,这是负责人为你想设置的
// 归还任务 就是把负责人设置为null
taskService.setAssignee(taskId,"hmb2");
四、网关
网关用来控制流程的流向
1、排他网关ExclusiveGateway
1.1 什么是排他网关:
排他网关,用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支,
注意:排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会选择id值较小的一条分支去执行。
为什么要用排他网关?
不用排他网关也可以实现分支,如:在连线的condition条件上设置分支条件。
在连线设置condition条件的缺点:如果条件都不满足,流程就结束了(是异常结束)。
如果从网关出去的线所有条件都不满足则系统抛出异常
1.2 排他网关流程定义
2、并行网关ParallelGateway
1.1、什么是并行网关:
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:
l fork分支:
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支
(两个可以一起执行)。
l join汇聚:
所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关 (就是两个分支都结束了,汇聚在一起,才能进行下一步流程)。
注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所
有进入的顺序流,然后再切分成多个并行分支
与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。
1.2、并行网关流程定义
3、包含网关InclusiveGateway
1.1、什么是包含网关
包含网关可以看做是排他网关和并行网关的结合体。
和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。
包含网关的功能是基于进入和外出顺序流的:
l 分支:
所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
l 汇聚:
所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
1.2、包含网关流程定义
4、事件网关EventGateway
1.1、什么是事件网关
事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。与此同时,会为每个外出顺序流创建相对的事件订阅。
事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的"执行", 相反它们让流程引擎去决定执行到事件网关
的流程需要订阅哪些事件。 要考虑以下条件:
- 事件网关必须有两条或以上外出顺序流;
- 事件网关后,只能使用intermediateCatchEvent类型(activiti不支持基于事件网关后连接ReceiveTask)
- 连接到事件网关的中间捕获事件必须只有一个入口顺序流。
1.2、事件网关流程定义
intermediateCatchEvent支持的事件类型:
Message Event: 消息事件
Singal Event: 信号事件
Timer Event: 定时事件