1、流程变量

1.1、概述

流程变量在 activiti 中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和 activiti 结合时少不了流程变量,流程变量就是 activiti 在管理工作流时根据管理需要而设置的变量。 比如:在出差申请流程流转时如果出差天数大于 3 天则由总经理审核,否则由人事直接审核, 出差天 数就可以设置为流程变量,在流程流转时使用。
注意:虽然流程变量中可以存储业务数据可以通过activiti的api查询流程变量从而实现查询业务数据,但是不建议这样使用,因为业务数据查询由业务系统负责,activiti设置流程变量是为了流程执行需要而创建。

1.2、流程变量类型

如果将 pojo 存储到流程变量中,必须实现序列化接口 serializable,为了防止由于新增字段无 法反序列化,需要生成serialVersionUID。
如果将 pojo 存储到流程变量中,必须实现序列化接口 serializable,为了防止由于新增字段无 法反序列化,需要生成
serialVersionUID。
1656858188037

1.3、流程变量作用域

流程变量的作用域可以是一个流程实例(processInstance),或一个任务(task),或一个执行实例 (execution)。

1.3.1、globa变量

流程变量的默认作用域是流程实例。当一个流程变量的作用域为流程实例时,可以称为 global 变量
注意:
如: Global变量:userId(变量名)、zhangsan(变量值)
global 变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前设置的变量值。

1.3.2、local变量

任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大, 称为 local 变量。
Local 变量由于在不同的任务或不同的执行实例中,作用域互不影响,变量名可以相同没有影响。Local 变量名也可以和 global 变量名相同,没有影响。

1.4、流程变量的使用方法

1.4.1、在属性上使用UEL表达式

可以在 assignee 处设置 UEL 表达式,表达式的值为任务的负责人,比如: ${assignee}, assignee 就是一个流程变量名称。
Activiti获取UEL表达式的值,即流程变量assignee的值 ,将assignee的值作为任务的负责人进行任务分配。

1.4.2、在连线上使用UEL表达式

可以在连线上设置UEL表达式,决定流程走向。
比如:${price<10000} 。price就是一个流程变量名称,uel表达式结果类型为布尔类型。
如果UEL表达式是true,要决定流程执行走向。

1.5、使用Global变量控制流程

1.5.1、需求

员工创建出差申请单,由部门经理审核,部门经理审核通过后出差3天及以下由人财务直接审批,3天以上先由总经理审核,总经理审核通过再由财务审批。
1656858516297

1.5.2、流程定义

1)、出差天数大于等于3连线条件
1656858562612
2)、出差天数小于3连线条件
1656858583823

1.5.3、设置global流程变量

在部门经理审核前设置流程变量,变量值为出差单信息(包括出差天数),部门经理审核后可以根据流程变量的值决定流程走向。
在设置流程变量时,可以在启动流程时设置,也可以在任务办理时设置。

1.5.3.1、启动流程时设置变量

在启动流程时设置流程变量,变量的作用域是整个流程实例。
通过Map<key,value>设置流程变量,map中可以设置多个变量,这个key就是流程变量的名字。

    /**
     * 启动流程实例,设置流程变量的值
     */
    @Test
    public void startProcess() {
        // 获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取RunTimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 流程定义key
        String key = "myEvection2";
        // 创建变量集合
        Map<String, Object> map = new HashMap<>();
        // 创建出差pojo对象 实现序列化接口 serializable并生成serialVersionUID
        Evection evection = new Evection();
        // 设置出差天数
        evection.setNum(2d);
        // 定义流程变量,把出差pojo对象放入map
        map.put("evection", evection);// 设置assignee的取值,用户可以在界面上设置流程的执行
        map.put("assignee0", "张三");
        map.put("assignee1", "李经理");
        map.put("assignee2", "王总经理");
        map.put("assignee3", "赵财务");
        // 启动流程实例,并设置流程变量的值(把map传入)
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey(key, map);
        // 输出
        System.out.println("流程实例名称=" + processInstance.getName());
        System.out.println("流程定义id==" + processInstance.getProcessDefinitionId());
    }
    
  	/**
     * 完成任务,判断当前用户是否有权限
     */
    @Test
    public void completTask() {
        //任务id
        String key = "myEvection2";
        // 任务负责人
        String assingee = "张三";
        //获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 创建TaskService
        TaskService taskService = processEngine.getTaskService();
        // 完成任务前,需要校验该负责人可以完成当前任务
        // 校验方法:根据任务id和任务负责人查询当前任务,如果查到该用户有权限,就完成
        Task task = taskService.createTaskQuery()
                .processDefinitionKey(key)
                .taskAssignee(assingee)
                .singleResult();
        if (task != null) {
            taskService.complete(task.getId());
            System.out.println("任务执行完成");
        }
    }

说明:
startProcessInstanceByKey(processDefinitionKey, variables)
流程变量作用域是一个流程实例,流程变量使用Map存储,同一个流程实例设置变量map中key相同,后者覆盖前者。

1.5.3.2、任务办理时设置变量

在完成任务时设置流程变量,该流程变量只有在该任务完成后其它结点才可使用该变量,它的作用域是整个流程实例,如果设置的流程变量的key在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。
这里需要在创建出差单任务完成时设置流程变量

@Test
public void completTask() {
//任务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.setNum(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.5.3.3、通过当前流程实例设置

通过流程实例id设置全局变量,该流程实例必须未执行完成。

    @Test
    public void setGlobalVariableByExecutionId() {
        // 当前流程实例执行 id,通常设置为当前执行的流程实例
        String executionId = "2601";
        // 获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取RuntimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 创建出差pojo对象
        Evection evection = new Evection();
        // 设置天数
        evection.setNum(3d);
        // 通过流程实例 id设置流程变量
        runtimeService.setVariable(executionId, "evection", evection);
        // 一次设置多个值
        // runtimeService.setVariables(executionId, variables)
    }

注意:
executionId必须当前未结束 流程实例的执行id,通常此id设置流程实例 的id。也可以通runtimeService.getVariable()获取流程变量。

1.5.3.4、通过当前任务设置

@Test
public void setGlobalVariableByTaskId(){
//当前待办任务id
String taskId="1404";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Evection evection = new Evection();
evection.setNum(3);
//通过任务设置流程变量
taskService.setVariable(taskId, "evection", evection);
//一次设置多个值
//taskService.setVariables(taskId, variables)
}

注意:
任务id必须是当前待办任务id,act_ru_task中存在。如果该任务已结束,会报错
也可以通过taskService.getVariable()获取流程变量。

1.6、操作数据库表

设置流程变量会在当前执行流程变量表插入记录,同时也会在历史流程变量表也插入记录。

当前流程变量表
SELECT * FROM act_ru_variable

记录当前运行流程实例可使用的流程变量,包括 global和local变量。

Id_:主键
Type_:变量类型
Name_:变量名称
Execution_id_:所属流程实例执行id,global和local变量都存储
Proc_inst_id_:所属流程实例id,global和local变量都存储
Task_id_:所属任务id,local变量存储
Bytearray_:serializable类型变量存储对应act_ge_bytearray表的id
Double_:double类型变量值
Long_:long类型变量值
Text_:text类型变量值

#历史流程变量表
SELECT * FROM act_hi_varinst

1.7、设置local流程变量

1.7.1、任务办理时设置

任务办理时设置local流程变量,当前运行的流程实例只能在该任务结束前使用,任务结束该变量无法在当前流程实例使用,可以通过查询历史任务查询。

    /*
     *处理任务时设置local流程变量
     */
    @Test
    public void completTask() {
//任务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.setNum(3d);
// 变量名是holiday,变量值是holiday对象
        variables.put("evection", evection);
// 设置local变量,作用域为该任务
        taskService.setVariablesLocal(taskId, variables);
// 完成任务
        taskService.complete(taskId);
    }

说明:
设置作用域为任务的local变量,每个任务可以设置同名的变量,互不影响。

1.7.2、通过当前任务设置

@Test
public void setLocalVariableByTaskId(){
// 当前待办任务id
String taskId="1404";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Evection evection = new Evection ();
evection.setNum(3d);
// 通过任务设置流程变量
taskService.setVariableLocal(taskId, "evection", evection);
// 一次设置多个值
//taskService.setVariablesLocal(taskId, variables)
}

注意:
任务id必须是当前待办任务id,act_ru_task中存在。

2、组任务

2.1、需求

在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。
针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。

2.2、设置任务候选人

在流程图中任务节点的配置中设置 candidate-users(候选人),多个候选人之间用逗号分开。
image-1656860009843

2.3、组任务

2.3.1、组任务办理流程

a、查询组任务

指定候选人,查询该候选人当前的待办任务。
候选人不能立即办理任务。

b、拾取(claim)任务

该组任务的所有候选人都能拾取。
将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。
如果拾取后不想办理该任务?
需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。

c、查询个人任务

查询方式同个人任务部分,根据assignee查询用户负责的个人任务。

d、办理个人任务

2.3.2、 查询组任务

根据候选人查询组任务

@Test
public void findGroupTaskList() {
// 流程定义key
String processDefinitionKey = "evection3";
// 任务候选人
String candidateUser = "lisi";
// 获取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());
}
}

2.3.3 、 拾取组任务

候选人员拾取组任务后该任务变为自己的个人任务。

    @Test
    public void claimTask(){
// 获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
//要拾取的任务id
        String taskId = "6302";
//任务候选人id
        String userId = "lisi";
//拾取任务
//即使该用户不是候选人也能拾取(建议拾取时校验是否有资格)
//校验该用户有没有拾取任务的资格
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .taskCandidateUser(userId)//根据候选人查询
                .singleResult();
        if(task!=null){
//拾取任务
            taskService.claim(taskId, userId);
            System.out.println("任务拾取成功");
        }
    }

说明:即使该用户不是候选人也能拾取,建议拾取时校验是否有资格,
组任务拾取后,该任务已有负责人,通过候选人将查询不到该任务。

2.3.4、 查询个人待办任务

查询方式同个人任务查询

@Test
public void findPersonalTaskList() {
// 流程定义key
String processDefinitionKey = "evection1";
// 任务负责人
String assignee = "zhangsan";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.taskAssignee(assignee)
.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());
}
}

2.3.5、 办理个人任务

同个人任务办理

/*完成任务*/
@Test
public void completeTask(){
// 任务ID
String taskId = "12304";
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
processEngine.getTaskService()
.complete(taskId);
System.out.println("完成任务:"+taskId);
}

说明:建议完成任务前校验该用户是否是该任务的负责人。

2.3.6、 归还组任务

如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人。

/*
*归还组任务,由个人任务变为组任务,还可以进行任务交接
*/
@Test
public void setAssigneeToGroupTask() {
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 查询任务使用TaskService
TaskService taskService = processEngine.getTaskService();
// 当前待办任务
String taskId = "6004";
// 任务负责人
String userId = "zhangsan2";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService
.createTaskQuery()
.taskId(taskId)
.taskAssignee(userId)
.singleResult();
if (task != null) {
// 如果设置为null,归还组任务,该 任务没有负责人
taskService.setAssignee(taskId, null);
}
}

说明:建议归还任务前校验该用户是否是该任务的负责人也可以通过setAssignee方法将任务委托给其它用户负责,注意被委托的用户可以不是候选人(建议不要这样使用)。

2.3.7、 任务交接

任务交接,任务负责人将任务交给其它候选人办理该任务。

    @Test
    public void setAssigneeToCandidateUser() {
// 获取processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 查询任务使用TaskService
        TaskService taskService = processEngine.getTaskService();
// 当前待办任务
        String taskId = "6004";
// 任务负责人
        String userId = "zhangsan2";
// 将此任务交给其它候选人办理该 任务
        String candidateuser = "zhangsan";
// 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
        Task task = taskService
                .createTaskQuery()
                .taskId(taskId)
                .taskAssignee(userId)
                .singleResult();
        if (task != null) {
            taskService.setAssignee(taskId, candidateuser);
        }
    }

2.3.8、 数据库表操作

查询当前任务执行表
SELECT * FROM act_ru_task
任务执行表,记录当前执行的任务,由于该任务当前是组任务,所有assignee为空,当拾取任务后该字段就是拾取用户的id。
查询任务参与者
SELECT * FROM act_ru_identitylink
任务参与者,记录当前参考任务用户或组,当前任务如果设置了候选人,会向该表插入候选人记录,有几个候选就插入几个。
与act_ru_identitylink对应的还有一张历史表act_hi_identitylink,向act_ru_identitylink插入记录的同时也会向历史表插入记录。

3、关联 businessKey

需求: 在 activiti 实际应用时,查询待办任务可能要显示出业务系统的一些相关信息。
比如:查询待审批出差任务列表需要将出差单的日期、 出差天数等信息显示出来。
出差天数等信息在业务系统中存在,而并没有在 activiti 数据库中存在,所以是无法通过 activiti 的 api 查询到出差天数等信息。 实现: 在查询待办任务时,通过 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("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);
}

Q.E.D.