当前位置: 首页 > news >正文

flowable候选人及候选人组(Candidate Users 、Candidate Groups)的应用包含拾取、归还、交接

核心概念:

候选人(Candidate Users)和候选人组(Candidate Groups)是flowable中的一个核心概念,是实现灵活、动态任务分配的核心机制,在了解候选人和候选人组之前,我们需要了解flowable中的几个核心概念:

  1. 用户任务(User Task): BPMN 中的一种活动类型,代表需要人工参与和完成的任务。

  2. 任务分配(Task Assignment): 指定哪个或哪些用户有权限查看、认领(Claim)和完成(Complete)某个用户任务。

  3. 候选人(Candidate Users): 在任务创建时,预先指定一个或多个具体的用户ID。这些用户(以及他们所属的组,见下一点)将成为该任务的潜在处理者。他们能看到任务,可以认领任务使其成为自己的“待办”(Assignee),然后完成任务。

  4. 候选人组(Candidate Groups): 在任务创建时,预先指定一个或多个组ID(通常是代表角色、部门、团队的标识符,如 "managers""hr-department""team-lead")。属于这些组的所有用户将成为该任务的潜在处理者。与候选人用户一样,他们能看到任务,可以认领并完成任务。

  5. 认领(Claim): 当一个候选用户(无论是直接指定的还是通过组继承的)决定处理某个任务时,需要执行“认领”操作。认领后,该任务从“候选任务池”中移出,变成该用户的个人待办任务(Assignee Task),通常其他用户就无法再认领了(除非释放或重新分配)。

  6. 办理人(Assignee): 任务被认领后,任务会有一个具体的办理人(Assignee)。这个办理人负责最终完成任务。任务在创建时可以直接指定办理人(跳过候选步骤),也可以通过候选人/组机制让用户认领后成为办理人。

关键区别与关系:

  • 候选人 vs. 办理人 (Assignee):

    • 候选人是潜在的办理人(可能有多个)。

    • 办理人是实际处理任务的人(通常只有一个)。

    • 任务创建时,可以只设候选人/组不设办理人(需要认领),也可以直接设办理人(无需认领)。

  • 候选人用户 vs. 候选人组:

    • 候选人用户:直接指定具体个人

    • 候选人组:指定一个角色或团队,间接指定了属于该组的所有用户。

    • 一个任务可以同时设置候选人用户和候选人组。 最终的任务候选人是这两部分的并集。例如,任务设置了候选人用户 userA 和候选人组 managers,那么 userA 和所有属于 managers 组的用户(包括 userA 如果也在组内)都是候选人。

  • 可见性: 设置了候选人/组的任务,会出现在这些用户的 “候选任务列表” 中(通常通过 taskService.createTaskQuery().taskCandidateUser(userId) 或 .taskCandidateGroup(groupId) 查询)。直接指定了办理人的任务,只出现在该办理人的 “待办任务列表” 中(通过 .taskAssignee(userId) 查询)。

为什么需要候选人/组机制?

  1. 解耦流程定义与具体人员:

    • 流程设计时,你无法预知流程运行时具体由哪个张三李四处理任务(人员可能变动、请假等)。

    • 使用候选人组(如 "部门经理"),可以将任务分配给一个角色,而不是具体人。实际运行时,由属于该角色的用户来处理。流程定义更稳定,无需因人员变动而修改流程图。

  2. 实现任务池(Task Pool)或工作组(Work Group):

    • 对于某些任务,可能适合由一组人中的任意一个来处理(例如,客服团队处理客户咨询,开发团队处理Bug)。

    • 将任务分配给候选人组(如 "customer-service""dev-team-alpha"),该组内的所有成员都能看到任务,谁有空谁就可以认领处理。提高了任务处理的灵活性和效率。

  3. 多部门/角色协作:

    • 一个任务可能需要来自不同部门或具备不同角色的用户共同关注或处理(可能由其中一人认领)。

    • 同时设置多个候选人组(如 "finance""legal"),让财务部和法务部的人都能看到这个合同审批任务,由相关部门的人认领处理。

  4. 指定备选人 + 角色:

    • 除了分配给角色(组),可能还需要额外指定几个关键人员(如专家、备份人员)作为候选人用户,确保他们也能看到任务。

使用场景: 

  1. 请假审批(角色分配):

    • BPMN 设计: 在“部门经理审批”用户任务上,设置 候选人组 = "dept-manager:{applicantDept}" (动态组,根据申请人部门变量 applicantDept 决定,如 "dept-manager:sales")。

    • 运行时: 任务创建时,引擎根据变量 applicantDept(如 "sales")解析出候选人组 "dept-manager:sales"

    • 效果: 销售部的所有经理都能在候选任务列表中看到这个请假申请任务。其中一位经理认领后成为办理人并审批。

  2. 客户服务工单(工作组/任务池):

    • BPMN 设计: 在“处理客户问题”用户任务上,设置 候选人组 = "customer-support-tier1"

    • 运行时: 所有属于 Tier 1 客服组的成员都能看到新创建的工单任务。

    • 效果: 第一个有空闲的 Tier 1 客服认领该工单进行处理。

  3. 复杂审批(多角色关注 + 指定专家):

    • BPMN 设计: 在“技术方案评审”用户任务上,设置:

      • 候选人组 = "engineering-leads""product-managers"

      • 候选人用户 = "expert-architect-john"

    • 运行时: 所有工程主管、产品经理以及专家 John 都能看到这个评审任务。

    • 效果: 通常由一位工程主管或产品经理认领主导评审,但专家 John 和其他角色的人也能看到任务详情,必要时可参与讨论或提供意见(认领机制确保最终由一人负责完成)。

  4. 直接分配(知道具体负责人):

    • BPMN 设计: 在“张三编写报告”用户任务上,直接设置办理人 (Assignee) = "zhangsan" (或通过变量 ${reportOwner} 动态指定)。

    • 运行时: 任务创建时直接分配给用户 zhangsan

    • 效果: 任务只出现在张三的待办列表中,他直接处理无需认领。(这不是候选人/组机制,但作为对比)

在flowable中设置候选人/组:

主要有三种方法:

1.在BPMN2,0 XML中定义(设计时):
<userTask id="managerApproval" name="部门经理审批" flowable:candidateGroups="${approvalGroup}" flowable:candidateUsers="${approvalUsers}"> <!-- 动态候选人组名 -->  <!-- 动态候选人 --> 
</userTask><userTask id="managerApproval" name="部门经理审批" 
flowable:candidateGroups="managers" flowable:candidateUsers="manager1,manager2"> <!-- 静态组名 -->  <!-- 静态候选人 --> 
</userTask>
2.运行时API设置(启动流程或完成任务时):
// 1. 启动流程时设置任务候选人/组 (需要知道任务定义Key)
Map<String, Object> variables = new HashMap<>();
variables.put("dynamicGroup", "sales-managers"); // 动态组变量// 使用 TaskDefinition 设置 (推荐,清晰)
Map<String, Object> startupTaskVariables = new HashMap<>();
startupTaskVariables.put("_FLOWABLE_CANDIDATE_USERS", Arrays.asList("kermit", "fozzie")); // 候选人用户列表
startupTaskVariables.put("_FLOWABLE_CANDIDATE_GROUPS", Arrays.asList("management", "${dynamicGroup}")); // 候选人组列表 (支持表达式)runtimeService.startProcessInstanceByKey("myProcess",businessKey,variables, // 普通流程变量startupTaskVariables // 专门用于初始化任务变量的Map
);// 2. 在完成上一个任务时设置下一个任务的候选人/组 (在TaskListener或Delegate中)
taskService.complete(taskId, variables); // 普通方式,需要在BPMN中配置或监听器里提前设置// 或者在 ExecutionListener/TaskListener 中设置
public void notify(DelegateTask delegateTask) {if ("managerApproval".equals(delegateTask.getTaskDefinitionKey())) {// 直接设置delegateTask.addCandidateUser("kermit");delegateTask.addCandidateGroup("management");delegateTask.addCandidateGroup((String) delegateTask.getVariable("dynamicGroup"));// 或者使用变量 (需在BPMN中配置对应属性)// delegateTask.setVariableLocal("_FLOWABLE_CANDIDATE_USERS", Arrays.asList("kermit"));}
}
3.任务创建后修改(运行时管理):
// 获取任务
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();// 添加候选人用户
taskService.addCandidateUser(taskId, "newCandidateUser");// 添加候选人组
taskService.addCandidateGroup(taskId, "newCandidateGroup");// 删除候选人用户
taskService.deleteCandidateUser(taskId, "oldCandidateUser");// 删除候选人组
taskService.deleteCandidateGroup(taskId, "oldCandidateGroup");// 注意:也可以完全替换,但通常先删除旧的再添加新的更安全,或者使用 set 操作(Flowable API 可能没有直接的 setCandidates 方法,需组合操作)。

候选组/人之间的操作:

查询某个任务实例的候选任务:
// 1. 查询某个任务实例的所有候选任务 (包括通过用户本身和用户所属组)
List<Task> candidateTasksForUser = taskService.createTaskQuery().proceaaInstanceId("xxx")//.processDefinitionKey("ppp:xx:xx").taskCandidateUser("张三") // 当前登录用户.list();// 2. 查询属于某个特定组的所有候选任务
List<Task> candidateTasksForGroup = taskService.createTaskQuery().proceaaInstanceId("xxx")//.processDefinitionKey("ppp:xx:xx").taskCandidateGroup(groupId) // groupId 如 "managers".list();
拾取任务:
Task candidateTasksForUser = taskService.createTaskQuery().proceaaInstanceId("xxx")//.processDefinitionKey("ppp:xx:xx").taskCandidateUser("张三") // 当前登录用户.singleResult();if(candidateTasksForUser != null){//拾取对应任务taskService.claim(candidateTasksForUser.getId,"张三");
}

拾取后,该候选人就会成为该任务的assginee,其他候选人将不能再查询到该任务

 归还任务:
Task candidateTasksForUser = taskService.createTaskQuery().proceaaInstanceId("xxx")//.processDefinitionKey("ppp:xx:xx").taskAssignee("张三") // 当前登录用户.singleResult();if(candidateTasksForUser != null){//归还对应任务taskService.unclaim(candidateTasksForUser.getId);
}
任务交接:
Task candidateTasksForUser = taskService.createTaskQuery().proceaaInstanceId("xxx")//.processDefinitionKey("ppp:xx:xx").taskAssignee("张三") // 当前登录用户.singleResult();if(candidateTasksForUser != null){//交接对应任务taskService.setAssignee(candidateTasksForUser.getId,"李四");
}

注意:在代码示例中使用.singleResult()来收集查询的数据并进行任务的拾取归还交接,但是在实际业务中这是存在问题的:

1.一个流程实例通常包含多个任务节点,用户可能在同一个流程实例中有多个候选任务 

2.一个用户可能属于多个候选人群,在不同节点都有候选资格

3.业务上允许一个流程实例中存在多个待处理任务

4.如果查询返回多个结果,此代码会抛出 FlowableException
org.flowable.common.engine.api.FlowableException: Query return 2 results instead of max 1

5.当存在多个候选任务时,用户可能想选择特定任务认领 。代码只认领第一个找到的任务,忽略其他可能更重要的任务

正确做法:使用List处理多个任务

// 正确:获取流程实例中用户的所有候选任务(返回列表)
List<Task> candidateTasks = taskService.createTaskQuery().processInstanceId("xxx").taskCandidateUser("张三").orderByTaskCreateTime().asc() // 推荐添加排序.list(); // 关键:使用 list() 而不是 singleResult()if (!candidateTasks.isEmpty()) {// 场景1:自动认领所有任务(罕见需求)for (Task task : candidateTasks) {taskService.claim(task.getId(), "张三");}// 场景2:让用户选择特定任务认领(常见)// 通常需要将任务列表返回前端供用户选择
}

相关文章:

  • neo4j 5.19.0安装、apoc csv导入导出 及相关问题处理
  • 内容中台构建数字化管理新路径
  • 每日c/c++题 备战蓝桥杯(P1204 [USACO1.2] 挤牛奶 Milking Cows)
  • 【多线程初阶】死锁的产生 如何避免死锁
  • 每日c/c++题 备战蓝桥杯(P2240 【深基12.例1】部分背包问题)
  • 湖北理元理律师事务所:债务管理中的人本主义实践
  • 如何在 Ubuntu22.04 上安装并开始使用 RabbitMQ
  • 【代码坏味道】无用物Dispensables
  • 如何查看电脑电池性能
  • 92. Java 数字和字符串 - 字符串
  • 跟单业务并发量分析
  • 将 node.js 项目作为后台进程持续运行
  • 强网杯 2024 PyBlockly
  • RuoYi前后端分离框架实现前后端数据传输加密(一)之后端篇
  • 【PhysUnits】15.5 引入P1后的标准化表示(standardization.rs)
  • Python:操作Excel公式
  • Adobe Acrobat 9.1.2 Pro (install)
  • 用不太严谨的文字介绍遥测自跟踪天线的基本原理
  • Linux设置静态IP
  • python:在 PyMOL 中如何查看和使用内置示例文件?
  • 律师网络推广/北京优化网站建设
  • 国产地图软件哪个好用/苏州seo网站公司
  • 网上动漫设计/汕头网站建设优化
  • 做好门户网站建设/今天的最新消息新闻
  • 网站建设过程规划和准备阶段/怎样推广网站
  • 那家b2c网站建设报价/四川企业seo