Skip to content

用户指南 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 的数据来自 OrgUserProvider SPI——业务方只实现数据接口,不写 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.getUserdeptIdOrgUserProvider.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.getUserdeptIdOrgUserProvider.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 懒加载,流程定义里写类名即可
Goengine.RegisterBuiltinAssignments(reg, userProv, orgProv)eng.SetRegistry(reg)
Pythonregister_builtin_assignments(registry, user_prov, org_prov) + set_extensions(EngineExtensions(registry=registry))
NoderegisterBuiltinAssignments(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 这类中间数字不处理
  • assigneeassignmentHandler 都配了? assignee 生效,handler 被忽略

jeeflow · 轻量级多语言工作流引擎