# 用户任务
用户任务:必须由人工参与,是业务流程中“人机交互”的桥梁。
# 工作原理
在工作流中,用户任务是一个核心的组成元素,它代表业务流程中那些必须由人工参与完成的步骤或节点。当流程引擎执行到用户任务时,它会自动创建一个待办事项,并将其分配给指定的一个或多个用户(或用户组),等待他们来处理。只有经过人工处理(如审批、填写信息、做出选择)后,工作流才会继续向下一个节点流转。
# 核心概念
# 任务分配方式
- 指定办理人 任务直接分配给 特定的一个用户 。该用户成为任务的“执行者”,任务通常会出现在他的个人待办列表中,其他人一般看不到。
<userTask id="task1" name="经理审批" activiti:assignee="zhangsan"/
- 设置候选人 任务共享给多个候选用户。这些用户都拥有处理该任务的资格,通常需要先 “签收”任务 ,才能成为实际的处理者。
<userTask id="task2" name="部门会签" activiti:candidateUsers="lisi, wangwu"/>
- 设置候选组 任务分配给一个组或角色(如“部门经理组”、“财务组”)。该组内的所有成员都是任务的候选人。
<userTask id="task3" name="财务审核" activiti:candidateGroups="finance-group"/>
# 任务生命周期
一个用户任务通常会经历以下几种状态: 未签收/待办理:任务已创建,并分配给了候选人或多个候选人。此时任务位于候选人的“待办任务列表”中,任何候选人都可以将其签收为自己办理的任务。 已签收/办理中:某个候选人执行了“签收”操作,任务正式锁定由该用户处理。此时,任务的“办理人”字段被确定。 已完成/已办结:用户处理完任务并提交,任务结束。流程引擎会根据处理结果,驱动流程流向下一个节点。
# 基础玩法
下面我们以Activiti/Flowable这类主流工作流引擎为例,介绍如何在代码中操作用户任务。核心是通过TaskService这个服务。
- 查询任务
// 获取任务服务
TaskService taskService = processEngine.getTaskService();
// 查询分配给当前用户的任务(他是任务的指定办理人)
List<Task> tasks = taskService.createTaskQuery().taskAssignee("currentUserId").list();
// 查询当前用户作为候选人的任务(他属于候选用户或候选组)
List<Task> candidateTasks = taskService.createTaskQuery().taskCandidateUser("currentUserId").list();
// 通常,查询结果会展示在应用的任务中心或待办列表界面上
for (Task task : tasks) {
System.out.println("任务ID: " + task.getId() + ", 任务名称: " + task.getName());
}
- 签收任务 对于候选任务,需要先签收,表示“这个任务我来处理”。
String taskId = "任务的实际ID"; // 从前端传递而来
String userId = "当前用户ID";
// 将任务签收给特定用户
taskService.claim(taskId, userId);
// 执行签收后,任务的Assignee属性就被设置为当前用户,其他候选人通常就看不到这个任务了[1](@ref)。
//替代方案:你也可以使用 setAssignee方法直接指定办理人,效果与签收类似。
- 完成任务 这是用户任务处理的最后一步,意味着用户已审批通过、填写完毕或做出了决策。
// 最基本的方式,直接完成任务
taskService.complete(taskId);
// 更常用的方式:完成任务并传递流程变量,这些变量会影响后续流程的走向
// 例如,在审批任务中,传递一个 `approvalResult` 变量,值为 true(同意)或 false(拒绝)
Map<String, Object> variables = new HashMap<>();
variables.put("approvalResult", true);
variables.put("comment", "同意发布");
taskService.complete(taskId, variables);
# 进阶玩法
掌握了基础操作后,可以尝试以下进阶玩法,让你的用户任务更加灵活和强大。
- 动态任务分配 任务的办理人不一定要在流程设计时写死,可以通过表达式或监听器动态决定。
<!-- 通过流程变量动态指定办理人 -->
<userTask id="dynamicTask" name="动态任务" activiti:assignee="${approvalManager}"/>
<!-- 调用Spring Bean的方法,根据流程变量emp的值返回经理ID -->
<userTask id="managerTask" name="经理审批" activiti:assignee="${ldapService.findManagerForEmployee(emp)}"/>
- 任务监听器 在任务创建时,通过Java代码实现复杂的分配逻辑
<userTask id="usertask1" name="复杂分配任务">
<extensionElements>
<activiti:taskListener event="create" class="com.yourcompany.YourCustomTaskListener"/>
</extensionElements>
</userTask>
// 对应的监听器实现
public class YourCustomTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
// 这里可以编写复杂的业务逻辑,如根据部门、角色、业务数据等动态分配
String processVariable = (String) delegateTask.getVariable("someVariable");
// ... 计算得出办理人 ...
delegateTask.setAssignee(calculatedUserId);
// 或者添加候选人
delegateTask.addCandidateUser("userA");
delegateTask.addCandidateGroup("groupB");
}
}
在ruoyi,jeecg系统中,可以通过这种动态任务配置的方式,实现组织机构中上级领导审批人员的动态获取。 这样即便在领导出现离职等情况,也不影响流程的使用。
# 多实例任务
多实例活动就是允许同一个任务节点(如用户任务)在流程中被执行多次的机制。它本质上是为一项工作创建了多个任务实例,通常分配给不同的处理人。这在工作流中常被称为 “会签”或“或签”。
多实例主要有两种执行方式:并行和串行。这是理解多实例最关键的一步。
# 核心概念
并行多实例同时创建所有任务实例,处理人之间互不干扰,可以同时进行处理
串行多实例,任务实例按预定顺序逐一创建,只有前一个任务完成后,下一个任务才会被创建并分配。
# 多实例配置三要素
要让多实例工作起来,你需要配置三个核心要素,无论是并行还是串行,其配置原理是相通的。
- 集合(Collection)
这是多实例的“人员名单”,决定了有多少人参与以及是谁参与。
它通常是一个字符串列表(List
// 示例:在启动流程或完成上一个任务时设置参与者列表
List<String> userList = Arrays.asList("userA", "userB", "userC");
execution.setVariable("assigneeList", userList); // execution 是流程执行对象
- 元素变量(Element Variable)
在遍历“人员名单”时,这个变量就像是一个“临时指针”,指向当前正在被处理的候选人。 你需要在用户任务的指派代理人(Assignee)字段中引用这个元素变量,格式为 ${元素变量名}。
- 完成条件(Completion Condition) 这是多实例的“结束规则”,决定了什么时候这个节点算完成,可以流转到下一个节点。如果不设置,默认规则是必须所有实例都完成。你可以使用引擎提供的内置变量来编写规则
| 内置变量 | 含义 | 示例 |
|---|---|---|
| nrOfInstances | 实例总数(总人数) | |
| nrOfCompletedInstances | 已完成的实例数 | |
| nrOfActiveInstances | 尚未完成的实例数 | 串行时通常为1 |
完成条件示例: 或签:只要一个人完成即可。条件为:${nrOfCompletedInstances >= 1}。
半数通过:完成人数超过一半即可。条件为:${nrOfCompletedInstances / nrOfInstances >= 0.5}。
一票否决:结合业务变量,如有人拒绝则立即结束。条件可为:${outcome == 'reject'}。- 配置代码实现案例
<userTask id="multiInstanceTask" name="会签审批" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics
isSequential="false" <!-- false表示并行,true表示串行 -->
activiti:collection="assigneeList" <!-- 指向人员名单的变量 -->
activiti:elementVariable="assignee"> <!-- 定义元素变量 -->
<!-- 完成条件:超过半数通过即结束会签 -->
<completionCondition>${nrOfCompletedInstances / nrOfInstances >= 0.5}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
← 定时器