Appearance
用户指南 07 · 参与者解析(内置 handler 清单)
审批人是谁?jeeflow 内置 7 个通用参与者处理器,覆盖最常见场景。 本文给出每个 handler 的适用场景、流程定义配置、发起参数与注意事项, 注册名四语言通用(同一份流程 JSON 无需改动)。
1. 参与者是怎么算出来的(解析优先级)
创建任务时 resolveActors:
① tf_nextNodeOperator 变量 → 动态指定下一节点处理人(最高优先)
② assignee 非空 → 固定参与者(逗号分隔多人;"applicant" = 发起人)
③ assignmentHandler 注册名 → 内置或自定义处理器(推荐)
④ 都没有 → 不创建任务静态优先于动态:
assignee配置了就按静态走,assignmentHandler不生效—— 避免"两个都配了,结果不确定"。
2. 内置 handler 速查表
注册名(assignmentHandler 值) | 参与者是谁 | 典型场景 | 依赖 |
|---|---|---|---|
…OperatorAssignmentHandler | 流程发起人 | 发起人确认/自审节点 | 无(纯引擎) |
…FormFieldAssigneeHandler | 表单字段里选的人 | 申请人在表单里指定审批人 | 无(纯引擎) |
…OrgUserAssignmentHandlers$DeptLeaderAssignmentHandler | 当前任务操作人的部门领导 | 谁处理了上一节点,就由他领导审批 | UserProvider + OrgUserProvider |
…$DeptMainLeaderAssignmentHandler | 当前任务操作人的部门分管领导 | 同上,但要分管领导(第一副职) | 同上 |
…$ApplicantDeptLeaderAssignmentHandler | 流程发起人的部门领导 | 无论流程走到哪,都由发起人的领导审批 | 同上 |
…$ApplicantDeptMainLeaderAssignmentHandler | 流程发起人的部门分管领导 | 同上,分管领导 | 同上 |
…$TaskRoleAssigneeHandler | 节点编码关联的角色成员 | 按角色审批(如"财务角色") | OrgUserProvider |
表格里
…是公共前缀com.mldong.jeeflow.interceptor.impl.,完整注册名见下文各节。 组织维度 handler 的数据来自OrgUserProviderSPI——业务方只实现数据接口,不写 handler (见 SPI 设计 05)。
3. 逐个详解
3.1 流程发起人 —— OperatorAssignmentHandler
场景:发起人自己再确认一次(如提交后"确认申请信息");或节点必须由发起人本人处理。
json
{
"id": "confirm",
"type": "snaker:task",
"properties": {
"assignmentHandler": "com.mldong.jeeflow.interceptor.impl.OperatorAssignmentHandler"
}
}行为:参与者 = 流程发起人(instance.operator);发起人为空时兜底 "apply.operator"(与 demo 契约一致)。
3.2 表单里选人 —— FormFieldAssigneeHandler
场景:申请表单里有"审批人/会签人"字段,由申请人填写。节点名与字段名精确匹配; 字段值支持逗号分隔字符串("userA,userB")或数组(["userA","userB"])。
json
{
"id": "task1",
"type": "snaker:task",
"properties": {
"assignmentHandler": "com.mldong.jeeflow.interceptor.impl.FormFieldAssigneeHandler"
}
}发起参数:变量里要有同名字段:
json
{ "task1": "userA,userB" } // → 参与者 userA,userB
{ "task1": ["userA", "userB"] } // → 同上编号后缀规则:节点 id 以 _数字 结尾时自动去掉后缀再匹配——设计器里 task_01/task_02 多个节点可以共用一个 task 字段(会签拆分场景常用):
节点 id: "task_01" → 匹配变量 "task"(找不到 task_01 时)3.3 当前操作人部门领导 —— DeptLeaderAssignmentHandler
场景:谁处理了上一节点,就由他的部门领导审批。多级流程中不同部门的人 提交后,各自的领导审批,无需为每个部门配流程。
json
{
"id": "leader_approve",
"type": "snaker:task",
"properties": {
"assignmentHandler": "com.mldong.jeeflow.interceptor.impl.OrgUserAssignmentHandlers$DeptLeaderAssignmentHandler"
}
}数据链路:operator(当前任务操作人)→ UserProvider.getUser 取 deptId → OrgUserProvider.findDeptLeaders(deptId) 取领导列表。
注意:区分"当前操作人"与"发起人"——上一节点被谁处理(转交/代理后可能是别人), 就取谁的领导。要始终按发起人算,用 3.5。
3.4 当前操作人部门分管领导 —— DeptMainLeaderAssignmentHandler
与 3.3 完全一样,只把 findDeptLeaders 换成 findDeptMainLeaders(部门第一副职/分管领导)。 适用"领导请假时由分管领导审批"等场景。
3.5 发起人部门领导 —— ApplicantDeptLeaderAssignmentHandler
场景:无论流程流转到哪,都由发起人的部门领导审批(跨部门会签时保持"谁发起谁负责")。
json
{
"id": "final_approve",
"type": "snaker:task",
"properties": {
"assignmentHandler": "com.mldong.jeeflow.interceptor.impl.OrgUserAssignmentHandlers$ApplicantDeptLeaderAssignmentHandler"
}
}数据链路:instance.operator(发起人)→ UserProvider.getUser 取 deptId → OrgUserProvider.findDeptLeaders(deptId)。
3.6 发起人部门分管领导 —— ApplicantDeptMainLeaderAssignmentHandler
同 3.5,走 findDeptMainLeaders。
3.7 按角色 —— TaskRoleAssigneeHandler
场景:节点绑定一个角色编码,该角色的所有成员都是候选人(任一可办)。 roleCode = 节点 id——设计器里节点编码即角色编码,无需额外配置。
json
{
"id": "finance",
"type": "snaker:task",
"properties": {
"assignmentHandler": "com.mldong.jeeflow.interceptor.impl.OrgUserAssignmentHandlers$TaskRoleAssigneeHandler"
}
}数据链路:OrgUserProvider.findByRole("finance") → 角色成员列表。
4. 怎么注册(四语言)
| 语言 | 方式 |
|---|---|
| Java | 免注册——引擎按类全限定名 Class.forName 懒加载,流程定义里写类名即可 |
| Go | engine.RegisterBuiltinAssignments(reg, userProv, orgProv) 后 eng.SetRegistry(reg) |
| Python | register_builtin_assignments(registry, user_prov, org_prov) + set_extensions(EngineExtensions(registry=registry)) |
| Node | registerBuiltinAssignments(registry, userProv, orgProv) + engine.setRegistry(registry) |
5. 自定义 handler(还不够用时)
接口签名(v1.6.0 起带 operator,可区分"当前操作人"与"发起人"):
java
// Java
public class MyHandler implements AssignmentHandler {
@Override
public String assign(Execution execution) {
String operator = execution.getOperator(); // 当前任务操作人
String applicant = execution.getProcessInstance().getOperator(); // 发起人
return "userA,userB"; // 逗号分隔返回多人
}
}python
# Python
class MyHandler(IAssignmentHandler):
async def assign(self, node, instance, operator: str) -> list[str]:
applicant = instance.operator
return ["userA", "userB"]go
// Go
type MyHandler struct{ /* 依赖注入 */ }
func (h *MyHandler) Assign(node *model.FlowNode, inst *model.ProcessInstance, operator string) []string {
applicant := ""
if inst != nil { applicant = inst.Operator }
return []string{"userA", "userB"}
}typescript
// Node.js
class MyHandler implements IAssignmentHandler {
async assign(node: FlowNode, inst: ProcessInstance, operator: string): Promise<string[]> {
const applicant = inst?.operator
return ['userA', 'userB']
}
}注册:Java 把类全限定名写进流程定义;Go/Python/Node 在 HandlerRegistry 里注册名字 (流程定义写注册名)。
返回值约定:返回 null/空 = 不处理,引擎继续走下一步优先级;返回逗号分隔字符串 (Java)或数组(Go/Python/Node)多参与者。
5.5 候选人(candidatePage)——执行时指定下个节点处理人
场景:当前节点审核时,经办人可以指定下一个节点的处理人(转交/预指派人)。 processTask/candidatePage 端点返回可选项:
- 节点配置了候选(
candidateUsers/candidateGroups)→ 只能从候选人里选 - 未配置 → 开放全部用户(走
IUserSearchProvider.page用户分页搜索)
候选双源(v1.6.0,对齐 boot4 GlobalCandidateHandler):
| 节点属性 | 语义 | 示例 |
|---|---|---|
candidateUsers | 逗号分隔的指定 userId,直接作为候选人 | "userA,userB" |
candidateGroups | 逗号分隔的角色标识,OrgUserProvider.findByRole 取人 | "finance" → 财务角色成员 |
json
{
"id": "review",
"type": "snaker:task",
"properties": {
"assignee": "leader",
"candidateUsers": "userA,userB",
"candidateGroups": "finance"
}
}语义:candidatePage 传当前任务 id(如 apply 任务),引擎沿流程找后继任务节点 (穿透 fork/join/decision),收集其候选配置并去重。候选命中返回候选列表; 无候选回退用户搜索(依赖用户搜索钩子注入)。
与
assignmentHandler的区别:候选只影响"当前节点可选的下一处理人名单", 不决定任务创建时的参与者——任务创建参与者仍由 assignee/assignmentHandler 决定。
5.5.1 发起时预指派人 —— f_nextNodeOperator(v1.6.0)
场景:发起流程时就指定第一个业务节点的处理人("发起并预指派",如发起报销时直接指定财务审批人)。 startAndExecute 发起时 args 带 f_nextNodeOperator:
startAndExecute({ processDefineId, operator, f_nextNodeOperator: "finA" })
→ 自动完成申请节点(apply)时转换为 tf_nextNodeOperator
→ 第一个业务节点参与者 = finA(覆盖其 assignee/assignmentHandler)与 tf_nextNodeOperator 的关系(对齐 boot3 两个预留 key):
| key | 时机 | 语义 |
|---|---|---|
f_nextNodeOperator | 发起时(startAndExecute args) | 指定第一个业务节点处理人;内部转换为 tf_ |
tf_nextNodeOperator | 任务执行时(execute args) | 指定下一节点处理人(最高优先,v1.0.1 已有) |
两者最终都走引擎同一读取链(resolveActors 第一优先),f_ 只是发起场景的便捷入口。
6. 常见问题
- 组织 handler 没效果? 检查是否注入了
OrgUserProvider(部门领导/角色查不到人 → 不创建任务) - 字段配了但参与者为空? 确认发起参数里变量名与节点 id 完全一致(含大小写);
_数字后缀只支持去掉尾部数字,t1_approve这类中间数字不处理 assignee和assignmentHandler都配了?assignee生效,handler 被忽略