# 用户任务

用户任务​​:​​必须由人工参与​​,是业务流程中“人机交互”的桥梁。

foo

# 工作原理

在工作流中,​​用户任务​​是一个核心的组成元素,它代表业务流程中那些​​必须由人工参与完成​​的步骤或节点。当流程引擎执行到用户任务时,它会自动创建一个待办事项,并将其分配给指定的一个或多个用户(或用户组),等待他们来处理。只有经过人工处理(如审批、填写信息、做出选择)后,工作流才会继续向下一个节点流转。

# 核心概念

# 任务分配方式

  • ​​指定办理人​​ 任务直接分配给 ​​特定的一个用户 ​​。该用户成为任务的“执行者”,任务通常会出现在他的个人待办列表中,其他人一般看不到。
<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系统中,可以通过这种动态任务配置的方式,实现组织机构中上级领导审批人员的动态获取。 这样即便在领导出现离职等情况,也不影响流程的使用。

# 多实例任务

多实例活动​​就是允许同一个任务节点(如用户任务)在流程中被执行多次的机制。它本质上是为一项工作创建了多个任务实例,通常分配给不同的处理人。这在工作流中常被称为 “会签”或“或签”

foo

多实例主要有两种执行方式:​​并行​​和​​串行​​。这是理解多实例最关键的一步。

# 核心概念

并行多实例同时创建​​所有任务实例,处理人之间​​互不干扰​​,可以同时进行处理

串行多实例,任务实例​​按预定顺序逐一创建​​,只有前一个任务完成后,下一个任务才会被创建并分配。

# 多实例配置三要素

要让多实例工作起来,你需要配置三个核心要素,无论是并行还是串行,其配置原理是相通的。

    1. 集合(Collection)

这是多实例的“人员名单”,决定了有多少人参与以及是谁参与。 它通常是一个字符串列表(List),包含了所有处理人的ID。 这个列表需要作为一个流程变量(如 assigneeList)在流程运行过程中传递给引擎。

// 示例:在启动流程或完成上一个任务时设置参与者列表
List<String> userList = Arrays.asList("userA", "userB", "userC");
execution.setVariable("assigneeList", userList); // execution 是流程执行对象
    1. 元素变量(Element Variable)

在遍历“人员名单”时,这个变量就像是一个“临时指针”,指向当前正在被处理的候选人。 你需要在用户任务的​​指派代理人(Assignee)​​字段中引用这个元素变量,格式为 ${元素变量名}。

    1. 完成条件(Completion Condition) 这是多实例的“结束规则”,决定了什么时候这个节点算完成,可以流转到下一个节点。如果不设置,​​默认规则是必须所有实例都完成​​。你可以使用引擎提供的​​内置变量​​来编写规则
内置变量 含义 示例
nrOfInstances 实例总数(总人数)
nrOfCompletedInstances 已完成的实例数
nrOfActiveInstances 尚未完成的实例数 串行时通常为1
  • 完成条件示例:​​ ​​或签​​:只要一个人完成即可。条件为:${nrOfCompletedInstances >= 1}。
    ​​半数通过​​:完成人数超过一半即可。条件为:${nrOfCompletedInstances / nrOfInstances >= 0.5}。
    ​​一票否决​​:结合业务变量,如有人拒绝则立即结束。条件可为:${outcome == 'reject'}。

    1. 配置代码实现案例
<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>