版本:Patch Plan V1.3(Slot 三键分离) 栈:FlowableWrapper .NET 6 + Flowable 7.2 + Elasticsearch + Redis + DM8
TestController 提供末节点业务回调模拟:
POST /api/test/process-callback/A:立即返回;POST /api/test/process-callback/B?delayMs=15000:延迟返回;POST /api/test/process-callback/mixed?delayMs=15000&slowPercent=50: 按businessId稳定混合分组;GET /api/test/callback-metrics:返回 A/B 数量、当前并发和峰值并发。
前端可直接使用的指标响应示例:
{
"ok": true,
"retainedRecords": 20,
"totalProcessCallbacks": 20,
"fastProcessCallbacks": 10,
"slowProcessCallbacks": 10,
"activeProcessCallbacks": 0,
"maxActiveProcessCallbacks": 5
}完整 k6 参数和阶梯执行方法见 performance/README.md。
用途:交付测试 Agent 执行完整端到端测试 原则:Flowable 是唯一真相层;流程中心是映射层、包装层、审计层;
NextSlotSelections是唯一最终生效人员来源;slotKey/roleKey/variableName三者职责分离,禁止互相推导
| Header | 必填 | 说明 |
|---|---|---|
Content-Type: application/json |
POST 必填 | — |
X-User-Id: {employeeId} |
视接口 | 操作人工号;不使用 Authorization |
所有接口 HTTP 状态码统一 200,通过 success 区分业务成功与失败。
{ "success": true, "message": "操作成功", "data": {} }
{ "success": false, "message": "错误描述", "errorCode": "ERROR_CODE" }| 错误码 | 含义 |
|---|---|
FLOWABLE_START_FAILED |
Flowable 启动失败 |
PROCESS_METADATA_INDEX_ORPHAN |
流程已启动但 ES 两次写入均失败 |
REJECT_CODE_REQUIRED |
驳回未传 rejectCode |
REJECT_REASON_REQUIRED |
驳回未传 rejectReason |
REJECT_NOT_ALLOWED |
当前节点 canReject=false |
REASSIGN_NOT_ALLOWED |
当前节点未配置 canReassign=true |
REJECT_CODE_INVALID |
rejectCode 不在 rejectOptions 中 |
REJECT_TARGET_NOT_FOUND |
找不到驳回目标节点 |
METADATA_NOT_FOUND |
ES 元数据不存在(触发 Flowable 重试) |
SLOT_ROLE_KEY_REQUIRED |
slot 缺少 roleKey |
SLOT_VARIABLE_NAME_REQUIRED |
slot 缺少 variableName |
SLOT_KEY_INVALID |
请求提交了未知 slotKey |
| 关键字 | 级别 | 含义 |
|---|---|---|
[ES_WRITE_FAIL] |
Error | ES 首次写入失败,开始重试 |
[ES_WRITE_RETRY_SUCCESS] |
Warning | ES 重试成功 |
[ES_WRITE_ORPHAN] |
Critical | ES 两次均失败,孤儿实例 |
[RECOMMEND_RANGE_EXCEEDED] |
Warning | 提交推荐范围外人员,审计不拦截 |
[NODE_COMPLETED] 节点回调成功 |
Information | 节点完成回调发送成功 |
[REJECT_OCCURRED] 节点回调成功 |
Information | 驳回通知发送成功 |
POST /api/flowable/bpmn/deploy
Content-Type: multipart/form-data
| 字段 | 类型 | 说明 |
|---|---|---|
file |
File | .bpmn 文件 |
slotConfigJson |
String | 节点配置 JSON 数组 |
slotConfig 节点字段:
| 字段 | 类型 | 说明 |
|---|---|---|
taskDefinitionKey |
string | 必须与 BPMN userTask id 一致 |
nodeSemantic |
string | 业务语义,前端据此路由表单组件 |
pageCode |
string | 前端渲染地址 |
roleKey |
string | 业务角色 Key,对应 AssigneeContract.roles[].roleKey |
assigneeMode |
string | single / multiple |
callbackUrl |
string | 节点级回调 URL;显式 null/空表示禁用节点通知,未声明才降级到 callback.url |
canReassign |
boolean | 当前节点是否允许转派;只有显式 true 才开放,未配置默认为 false |
canReject |
bool | 当前节点是否可驳回 |
rejectOptions |
array | 驳回目标列表,含 rejectCode / label / description |
isRejectTarget |
bool | 是否可作为驳回落点 |
rejectCode |
string | 本节点作为驳回落点时的标识 |
slots |
array | 选人槽位定义 |
callbackUrl 随节点语义写入 ES 索引 flowable-process-definition-semantic,实际位于 _source.nodeSemanticMap.<taskDefinitionKey>.callbackUrl。nodeSemanticMap 的 key 是动态节点 ID,不是关系库固定列;索引映射对该对象使用 dynamic:false,完整 JSON 仍保留在 _source 并可按流程定义 Key 整体读取,但不会再为每个节点 ID 创建 ES 字段,因此不会持续消耗默认 1000 个 mapping fields。
slot 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
slotKey |
string | 前端提交选人时使用的槽位标识,全流程唯一 |
roleKey |
string | 该 slot 的推荐池来源角色;后端从 RecommendedAssigneesSnapshot[slot.roleKey] 取候选人 |
label |
string | 前端展示标签 |
mode |
string | single / multiple |
variableName |
string | 最终写入 Flowable 的流程变量名;不作为前端提交 key |
required |
bool | 是否必填 |
conditionalOn |
string | 条件表达式(如 needPersonFeedback==true),满足时才需填 |
restrictToRecommended |
bool | true = 建议前端限制该 slot 的可选范围;后端记录越界审计,不强拦截 |
Slot 三键硬约定:
slotKey:前端提交nextSlotSelections/initialSlotSelections时的 key。roleKey:推荐候选人从哪个角色池取,查询链固定为slot.roleKey -> RecommendedAssigneesSnapshot[roleKey]。variableName:后端将选人结果写入 Flowable 的变量名。- 三者禁止互相替代,禁止按命名规则猜测;slot 缺少
roleKey或variableName时视为配置错误。
成功响应:
{
"success": true,
"data": {
"deploymentId": "deploy-uuid-001",
"processDefinitionKey": "personnel_selection_approval",
"nodes": [
{
"taskDefinitionKey": "ut00_starter_submit",
"nodeSemantic": "STARTER_SUBMIT",
"pageCode": "https://httpbin.org/get?node=starter_submit",
"roleKey": "starter",
"assigneeMode": "single",
"callbackUrl": "https://httpbin.org/post?node=starter_submit",
"slotCount": 1
}
]
}
}GET /api/flowable/bpmn/{processDefinitionKey}/nodes
确认所有节点的 roleKey、callbackUrl、slots 已正确写入 ES。
POST /api/processes/start
X-User-Id: EMP_START
| 字段 | 职责 |
|---|---|
initialSlotSelections |
首节点选人 → 生成 Flowable 启动变量(执行路径) |
assigneeContract |
按 roleKey 传推荐人 → 写入 RecommendedAssigneesSnapshot(展示用,不影响执行) |
businessVariables |
网关条件变量、starterAssignee 等 → 直接注入 Flowable |
callback.url |
流程级回调地址,仅在节点未声明 callbackUrl 时作为兼容降级使用 |
RecommendedAssigneesSnapshot 的 Key 是业务角色 roleKey,由 assigneeContract.roles[] 固化而来。当前节点自己的 roleKey 表示“谁处理当前节点”;slot 里的 roleKey 表示“这个选人槽从哪个推荐池取候选人”。两者字段名相同但主体不同,不能用当前节点 roleKey 代替 slot 推荐人组装。
启动和完成任务时,前端只用 slotKey 提交选人;后端根据 slotConfig 找到 SlotDefinition,再写入 slot.variableName 对应的 Flowable 变量。未知 slotKey 直接返回错误,不再兼容旧版本的静默忽略。
{
"businessType": "personnel_selection_approval",
"businessId": "SEMI_AUTO_001",
"initialSlotSelections": [
{ "slotKey": "group_leader", "users": ["EMP_001"] }
],
"assigneeContract": {
"roles": [
{ "roleKey": "inspection_office_reviewer", "users": ["EMP_005"] },
{ "roleKey": "integrity_dept_reviewer", "users": ["EMP_010"] },
{ "roleKey": "integrity_head", "users": ["EMP_015"] },
{ "roleKey": "office_director", "users": ["EMP_020"] },
{ "roleKey": "secretary", "users": ["EMP_025"] }
]
},
"businessVariables": {
"starterAssignee": "EMP_START",
"needPersonFeedback": false
},
"callback": { "url": "https://httpbin.org/post", "timeoutSeconds": 30 }
}验证点:
data.firstNodeSemantic=STARTER_SUBMIT- ES
RecommendedAssigneesSnapshot含各 roleKey → users 映射 - Flowable 变量
groupLeaderAssignee = "EMP_001"已注入
{
"businessType": "personnel_selection_approval",
"businessId": "SEMI_NO_RECOMMEND_001",
"initialSlotSelections": [
{ "slotKey": "group_leader", "users": ["EMP_001"] }
],
"businessVariables": { "starterAssignee": "EMP_START", "needPersonFeedback": false },
"callback": { "url": "https://httpbin.org/post" }
}验证点:GET /progress 中 currentNodes[].slotRecommendedUsers = {}
{
"businessType": "personnel_selection_approval",
"businessId": "FULL_AUTO_001",
"initialSlotSelections": [
{ "slotKey": "group_leader", "users": ["EMP_001"] }
],
"assigneeContract": {
"roles": [
{ "roleKey": "inspection_office_reviewer", "users": ["EMP_005"] },
{ "roleKey": "integrity_dept_reviewer", "users": ["EMP_010"] },
{ "roleKey": "integrity_head", "users": ["EMP_015"] },
{ "roleKey": "office_director", "users": ["EMP_020"] },
{ "roleKey": "secretary", "users": ["EMP_025"] }
]
},
"businessVariables": { "starterAssignee": "EMP_START", "needPersonFeedback": false },
"callback": { "url": "https://httpbin.org/post" }
}验证点:
- ES
RecommendedAssigneesSnapshot含全流程所有 roleKey 推荐人 GET /progresscurrentNodes[].slotRecommendedUsers[slotKey]有值restrictToRecommended=true的 slot,currentNodes[].restrictToRecommended[slotKey] = true
{
"businessType": "personnel_selection_approval",
"businessId": "LEGACY_001",
"initialSlotSelections": [
{ "slotKey": "group_leader", "users": ["EMP_001"] }
],
"businessVariables": { "starterAssignee": "EMP_START" },
"callback": { "url": "https://httpbin.org/post" }
}{
"success": true,
"data": {
"processInstanceId": "proc-uuid-001",
"businessId": "SEMI_AUTO_001",
"firstTaskId": "task-uuid-001",
"firstNodeSemantic": "STARTER_SUBMIT",
"firstPageCode": "https://httpbin.org/get?node=starter_submit"
}
}| 场景 | 预期 |
|---|---|
businessId 重复(已有 running 流程) |
success: false |
businessType 未配置映射 |
success: false |
X-User-Id 未传 |
success: false(无法确定操作人) |
initialSlotSelections 含未知 slotKey |
errorCode: SLOT_KEY_INVALID |
| slotConfig 中 slot 缺少 roleKey / variableName | 部署或运行时失败 |
| Flowable 不可用 | errorCode: FLOWABLE_START_FAILED |
| ES 两次写入均失败 | errorCode: PROCESS_METADATA_INDEX_ORPHAN |
GET /api/tasks/pending
X-User-Id: EMP_001
Pending task responses include slotRecommendedUsers keyed by slotKey, restrictToRecommended keyed by slotKey, and pageUrl when pageCode is an http/https URL. requiredSlots[] includes slotKey / roleKey / variableName so the frontend can render by slot and submit by slotKey.
流程中心不解析 BPMN gateway 来预测后续路径。排他网关、并行网关场景下,如果当前节点需要提前选择多个下游处理人,必须在当前节点 slots 中显式声明多个选人槽;/api/tasks/pending 只返回这些显式声明的 requiredSlots 及其推荐人。
| 参数 | 类型 | 说明 |
|---|---|---|
employeeId |
string | 优先于 Header |
businessType |
string[] | 按业务类型过滤(可选);重复传参,多个值按 OR 匹配 |
pageIndex |
int | 默认 1 |
pageSize |
int | 默认 20 |
示例:GET /api/tasks/pending?employeeId=EMP_001&businessType=type_a&businessType=type_b&pageIndex=1&pageSize=20
响应:
{
"success": true,
"data": {
"items": [
{
"taskId": "task-uuid-001",
"taskName": "巡察组组长确认",
"businessId": "SEMI_AUTO_001",
"businessType": "personnel_selection_approval",
"nodeSemantic": "GROUP_LEADER_CONFIRM",
"roleKey": "group_leader",
"pageCode": "https://httpbin.org/get?node=group_leader_confirm",
"pageUrl": "https://httpbin.org/get?node=group_leader_confirm&businessId=SEMI_AUTO_001&taskId=task-uuid-001&businessType=personnel_selection_approval&nodeId=ut01_group_leader_confirm&nodeSemantic=GROUP_LEADER_CONFIRM",
"canReject": true,
"canReassign": true,
"rejectOptions": [
{ "rejectCode": "TO_STARTER", "label": "退回发起人重新提交" }
],
"requiredSlots": [
{
"slotKey": "inspection_office_reviewer",
"roleKey": "inspection_office_reviewer",
"label": "巡察办审核人",
"variableName": "inspectionOfficeReviewAssignee",
"mode": "single",
"required": true,
"restrictToRecommended": false
}
],
"slotRecommendedUsers": {
"inspection_office_reviewer": ["EMP_005"]
},
"restrictToRecommended": {
"inspection_office_reviewer": false
},
"createTime": "2024-01-15T08:30:00Z"
}
],
"total": 1,
"pageIndex": 1,
"pageSize": 20
}
}验证点:
requiredSlots与 slotConfig 一致slotRecommendedUsers的 key 必须来自requiredSlots[].slotKeycanReject/rejectOptions与 slotConfig 一致- 不属于该用户的任务不出现
GET /api/processes/{businessId}/progress
响应:
{
"success": true,
"data": {
"businessId": "SEMI_AUTO_001",
"processInstanceId": "proc-uuid-001",
"processDefinitionKey": "personnel_selection_approval",
"status": "running",
"createdBy": "EMP_START",
"createdTime": "2024-01-15T08:00:00Z",
"completedTime": null,
"currentNodes": [
{
"taskId": "task-uuid-001",
"nodeId": "ut02_inspection_office_review",
"nodeName": "巡察办审核",
"nodeSemantic": "INSPECTION_OFFICE_REVIEW",
"pageCode": "https://httpbin.org/get?node=inspection_office_review",
"assignee": "EMP_005",
"candidateUsers": [],
"createTime": "2024-01-15T08:01:00Z",
"slotRecommendedUsers": {
"integrity_dept_reviewer": ["EMP_010"]
},
"restrictToRecommended": {
"integrity_dept_reviewer": false
}
}
],
"auditHistory": [
{
"taskDefinitionKey": "ut00_starter_submit",
"nodeSemantic": "STARTER_SUBMIT",
"action": "approve",
"operatorId": "EMP_START",
"comment": "提交申请",
"operatedAt": "2024-01-15T08:00:30Z",
"slotSelections": [
{ "slotKey": "group_leader", "label": "巡察组组长", "users": ["EMP_001"] }
]
}
]
}
}currentNodes 验证矩阵:
| 场景 | slotRecommendedUsers |
restrictToRecommended |
|---|---|---|
传了 assigneeContract |
按当前节点 slotConfig 的 slots[].slotKey 输出推荐人 |
按 slotConfig 的 slotKey 输出 |
未传 assigneeContract |
{} |
所有 slot 均为 false |
| 流程已 completed | currentNodes = [] |
— |
GET /api/processes/{businessId}/flow-render
响应:
{
"success": true,
"data": {
"businessId": "SEMI_AUTO_001",
"bpmnXml": "<definitions>...</definitions>",
"nodes": [
{ "id": "ut00_starter_submit", "name": "发起人提交", "type": "userTask", "x": 100, "y": 200, "width": 100, "height": 80 }
],
"edges": [
{ "id": "flow_ut00_to_ut01", "sourceId": "ut00_starter_submit", "targetId": "ut01_group_leader_confirm" }
],
"activeTaskRenders": [
{ "taskId": "task-uuid-001", "nodeId": "ut01_group_leader_confirm", "assignee": "EMP_001", "status": "active" }
],
"completedRecords": [
{ "nodeId": "ut00_starter_submit", "operatorId": "EMP_START", "outcome": "approved", "comment": "提交申请", "round": 1 }
]
}
}验证点:
bpmnXml有值时前端按坐标渲染;为 null 时前端退化 dagre 自动布局activeTaskRenders当前节点status = activecompletedRecords[].outcome合法值:approved/rejected_return/reassigned- 转派后出现
outcome = reassigned记录
GET /api/processes/{businessId}/audit-history
GET /api/processes/{businessId}/status
status 合法值:running / completed / terminated / callback_failed
GET /api/processes?businessType=xxx&status=running&pageIndex=1&pageSize=20
POST /api/tasks/complete
X-User-Id: {当前处理人}
action = 1
NextSlotSelections 是唯一最终生效人员来源。
{
"businessId": "SEMI_AUTO_001",
"action": 1,
"comment": "同意,人员配置合理",
"nextSlotSelections": [
{ "slotKey": "inspection_office_reviewer", "users": ["EMP_005"] }
]
}验证点:
- Flowable 变量
inspectionOfficeReviewAssignee = "EMP_005"写入 - 审计记录
action = approve,slotSelections含选人快照 GET /progresscurrentNodes推进到下一节点
slotRecommendedUsers 按 slotKey 返回当前节点 requiredSlots 的推荐人;NextSlotSelections 也必须按 slotKey 提交。前端不要用节点级 roleKey 或 variableName 作为提交 key,也不要再做 slotKey/roleKey/variableName 同名兜底。
候选人读取规则固定为:遍历 requiredSlots[],使用 slotRecommendedUsers[slot.slotKey] ?? [] 初始化该 slot 的候选人区域。
{
"businessId": "FULL_AUTO_001",
"action": 1,
"comment": "确认通过",
"nextSlotSelections": [
{ "slotKey": "inspection_office_reviewer", "users": ["EMP_005"] }
]
}{
"businessId": "FULL_AUTO_001",
"action": 1,
"nextSlotSelections": [
{ "slotKey": "inspection_office_reviewer", "users": ["EMP_999"] }
]
}验证点:
- 流程正常推进,不拦截
- 审计记录
hasOutOfRecommendedRange = true - 日志
[RECOMMEND_RANGE_EXCEEDED]
{
"businessId": "SEMI_AUTO_001",
"action": 1,
"businessVariables": { "needPersonFeedback": true },
"nextSlotSelections": [
{ "slotKey": "feedback_person", "users": ["EMP_099"] }
]
}{
"businessId": "PARALLEL_001",
"taskId": "task-uuid-branch-a",
"action": 1,
"comment": "分支 A 通过"
}POST /api/tasks/complete
X-User-Id: {当前处理人}
action = 2
{
"businessId": "SEMI_AUTO_001",
"action": 2,
"rejectCode": "TO_STARTER",
"rejectReason": "材料不完整,请重新填写"
}验证点:
- Flowable 跳回至
rejectCode对应节点 - 审计记录
action = reject,rejectReason正确写入 - 业务系统收到
callbackType = REJECT_OCCURRED通知,含rejectTargetNodeKey GET /progresscurrentNodes变为驳回目标节点GET /flow-rendercompletedRecords含outcome = rejected_return
{
"businessId": "SEMI_AUTO_001",
"processInstanceId": "proc-uuid-001",
"processDefinitionKey": "personnel_selection_approval",
"businessType": "personnel_selection_approval",
"callbackType": "REJECT_OCCURRED",
"taskDefinitionKey": "ut02_inspection_office_review",
"rejectTargetNodeKey": "ut00_starter_submit",
"lastAuditRecord": {
"action": "reject",
"operatorId": "EMP_005",
"comment": null,
"rejectReason": "材料不完整,请重新填写",
"operatedAt": "2024-01-15T10:00:00Z",
"slotSelections": []
},
"triggeredAt": "2024-01-15T10:00:01Z"
}| 场景 | 预期错误码 |
|---|---|
rejectCode 未传 |
REJECT_CODE_REQUIRED |
rejectReason 未传 |
REJECT_REASON_REQUIRED |
canReject=false |
REJECT_NOT_ALLOWED |
rejectCode 不在 rejectOptions |
REJECT_CODE_INVALID |
| 找不到驳回目标节点 | REJECT_TARGET_NOT_FOUND |
POST /api/tasks/reassign
X-User-Id: EMP_ADMIN
转派由当前节点 slotConfig 的 canReassign 控制。只有 canReassign=true 时待办响应才返回可转派,前端显示按钮,后端接口也允许执行;未配置或为 false 时返回 REASSIGN_NOT_ALLOWED。因此发起人节点或其他不支持转派的节点无需配置该字段,不影响流程设计和正常审批。
转派只作用于当前节点当前 Task,不改变其他节点预设推荐人。
{
"businessId": "SEMI_AUTO_001",
"newAssignees": ["EMP_006"],
"reason": "原处理人请假",
"operatorId": "EMP_ADMIN"
}并行节点需指定 taskId:
{
"businessId": "PARALLEL_001",
"taskId": "task-uuid-branch-a",
"newAssignees": ["EMP_007"],
"reason": "转派",
"operatorId": "EMP_ADMIN"
}验证点:
GET /tasks/pending?employeeId=EMP_006出现该任务GET /tasks/pending?employeeId=EMP_001任务消失GET /flow-rendercompletedRecords含outcome = reassignedGET /progresscurrentNodes[].assignee = "EMP_006"
POST /api/processes/terminate
X-User-Id: EMP_ADMIN
{
"businessId": "SEMI_AUTO_001",
"reason": "业务取消,管理员手动终止"
}验证点:GET /status 返回 status = terminated
POST /api/callback/flowable
由 BPMN 中保留的 Flowable HTTP ServiceTask 调用,非业务系统或前端直调。
普通节点完成通知不再通过 BPMN 后置 HTTP ServiceTask 触发。BPMN 只保留最后一个流程完成契约回调,用于通知流程中心将实例状态收口为 completed,并向业务系统发送流程完成通知。
{
"processInstanceId": "proc-uuid-001",
"businessId": "SEMI_AUTO_001",
"processDefinitionKey": "personnel_selection_approval"
}验证点:ES status = completed;幂等重复调用返回 200 不重复通知。
节点完成后,流程中心在 CompleteTaskAsync 成功调用 Flowable CompleteAsync 之后主动发送业务回调。触发依据是当前节点 slotConfig 中的 callbackUrl:有效 URL 发送节点级回调;显式 null/空直接跳过;未声明时才降级使用启动时 callback.url。
| callbackType | 触发时机 | BPMN ServiceTask 挂载位置 |
|---|---|---|
NODE_COMPLETED |
普通用户任务完成 | 不需要挂 ServiceTask |
REJECT_OCCURRED |
用户任务驳回 | 不需要挂 ServiceTask |
节点回调 Payload(流程中心 → 业务系统):
{
"businessId": "SEMI_AUTO_001",
"processInstanceId": "proc-uuid-001",
"processDefinitionKey": "personnel_selection_approval",
"businessType": "personnel_selection_approval",
"callbackType": "NODE_COMPLETED",
"taskDefinitionKey": "ut02_inspection_office_review",
"nodeSemantic": "INSPECTION_OFFICE_REVIEW",
"rejectTargetNodeKey": null,
"lastAuditRecord": {
"action": "approve",
"operatorId": "EMP_005",
"comment": "审核通过",
"rejectReason": null,
"operatedAt": "2024-01-15T09:30:00Z",
"slotSelections": [
{ "slotKey": "integrity_dept_reviewer", "label": "纪检部审核人", "users": ["EMP_010"] }
]
},
"triggeredAt": "2024-01-15T09:30:01Z"
}callbackUrl 解析规则:
1. slotConfig 中节点的 callbackUrl 为有效 URL → 发送节点级回调
2. slotConfig 中节点显式声明 callbackUrl 为 null/空 → 跳过,返回 200,不降级
3. slotConfig 中节点未声明 callbackUrl → 启动时 callback.url 流程级兼容降级
4. 未声明节点 callbackUrl 且流程级 callback.url 为空 → 跳过,返回 200
错误场景:
| 场景 | 预期 |
|---|---|
节点显式声明 callbackUrl 为 null/空 |
跳过节点回调,不降级,主流程继续 |
| 节点回调非 2xx 或异常 | 记录 Error,不阻塞已完成的 Flowable 任务 |
| ES 元数据不存在 | 500(触发 Flowable 重试) |
| 流程结束通知失败(非 2xx) | 500(触发 Flowable 重试) |
1. POST /api/flowable/bpmn/deploy(部署 BPMN + slotConfig)
2. POST /api/processes/start(businessId=E2E_SEMI_001,场景 2.1)
3. GET /api/processes/E2E_SEMI_001/progress(确认首节点 + 推荐人)
4. GET /api/processes/E2E_SEMI_001/flow-render(确认流程图)
5. GET /api/tasks/pending?employeeId=EMP_START(确认首节点待办)
6. POST /api/tasks/complete(EMP_START 完成首节点,传 NextSlotSelections)
7. GET /api/processes/E2E_SEMI_001/progress(确认推进 + 推荐人更新)
8. 重复步骤 5-7,按各节点 assignee 逐步完成
9. GET /api/processes/E2E_SEMI_001/status → status=completed
10. GET /api/processes/E2E_SEMI_001/audit-history → 所有节点 approve 记录
1. 部署 slotConfig(含 restrictToRecommended=true 的节点)
2. POST /api/processes/start(businessId=E2E_FULL_001,场景 2.3)
3. GET /progress → slotRecommendedUsers[slotKey] 有值,restrictToRecommended[slotKey]=true
4. 前端按 requiredSlots 渲染,按 slotKey 确认人选 → 提交 NextSlotSelections
5. 重复完成直到 status=completed
6. 验证审计记录 hasOutOfRecommendedRange=false
1. 启动流程(businessId=E2E_REJECT_001)
2. 完成首节点
3. 第二节点驳回(action=2,rejectCode=TO_STARTER)
4. GET /progress → currentNodes 回到首节点
5. GET /flow-render → completedRecords 含 outcome=rejected_return
6. 验证业务系统收到 REJECT_OCCURRED 回调
7. 首节点重新完成,流程继续推进
1. 启动流程(businessId=E2E_REASSIGN_001)
2. 完成首节点,流程到第二节点(assignee=EMP_001)
3. POST /api/tasks/reassign(EMP_001 → EMP_006)
4. GET /tasks/pending?employeeId=EMP_006 → 出现任务
5. GET /tasks/pending?employeeId=EMP_001 → 任务消失
6. GET /flow-render → completedRecords 含 outcome=reassigned
7. EMP_006 完成该节点,流程继续
普通节点完成回调不再需要配置 HTTP ServiceTask。业务系统 URL 由流程中心从 slotConfig 的节点 callbackUrl 读取,并在用户任务完成后主动调用。
BPMN 中只保留最后一个流程完成契约 HTTP ServiceTask,用于调用 /api/callback/flowable:
<serviceTask id="st02_framework_callback"
name="通知流程中心流程已完成"
flowable:type="http">
<extensionElements>
<flowable:field name="requestMethod"><flowable:string>POST</flowable:string></flowable:field>
<flowable:field name="requestUrl"><flowable:expression>${frameworkCallbackUrl}</flowable:expression></flowable:field>
<flowable:field name="requestHeaders"><flowable:string>Content-Type: application/json</flowable:string></flowable:field>
<flowable:field name="requestBody">
<flowable:expression>{"processInstanceId":"${execution.processInstanceId}","businessId":"${businessId}","processDefinitionKey":"${processDefinitionKey}"}</flowable:expression>
</flowable:field>
</extensionElements>
</serviceTask>| 接口 | 方法 | 路径 |
|---|---|---|
| 部署 BPMN | POST | /api/flowable/bpmn/deploy |
| 查询节点配置 | GET | /api/flowable/bpmn/{key}/nodes |
| 启动流程 | POST | /api/processes/start |
| 终止流程 | POST | /api/processes/terminate |
| 查询待办 | GET | /api/tasks/pending |
| 完成任务 | POST | /api/tasks/complete |
| 转派任务 | POST | /api/tasks/reassign |
| 流程进度 | GET | /api/processes/{businessId}/progress |
| 流程图渲染 | GET | /api/processes/{businessId}/flow-render |
| 审批历史 | GET | /api/processes/{businessId}/audit-history |
| 流程状态 | GET | /api/processes/{businessId}/status |
| 流程列表 | GET | /api/processes |
| Flowable 回调 | POST | /api/callback/flowable |
本节是当前代码和实际 API 验证后的准则。下方历史章节里如有描述冲突,以本节为准。
当前接口使用 Authorization: Bearer {jwt} 解析当前用户,JWT 中按 Jwt:UseridClaim 读取用户工号,默认 claim 为 userid。旧文档中的 X-User-Id 只作为历史说明,不是当前认证入口。
启动流程时,businessType 必须能映射到 Flowable 的 processDefinitionKey。映射来自 appsettings.json:
{
"BusinessTypeProcessMapping": {
"Mappings": {
"problem_zero": "problem_zero"
}
}
}问题归零 BPMN 首节点使用 ${starterAssignee},当前代码不会自动注入该变量,所以启动请求需要在 businessVariables 中传入:
{
"businessType": "problem_zero",
"businessId": "PZ_001",
"initialSlotSelections": [
{ "slotKey": "team_leader", "users": ["EMP_TL1"] }
],
"businessVariables": {
"starterAssignee": "EMP_STARTER"
}
}slotConfigJson 是随 BPMN 部署提交的节点配置数组。数组中每个对象对应一个 BPMN userTask。
{
"taskDefinitionKey": "ut03_quality_group_confirm",
"nodeSemantic": "PROBLEM_ZERO_QUALITY_GROUP_CONFIRM",
"pageCode": "ProblemZero/QualityGroupConfirmForm",
"roleKey": "problem_zero_quality_member",
"assigneeMode": "multiple",
"canReassign": true,
"canReject": true,
"rejectOptions": [
{
"rejectCode": "TO_STARTER",
"label": "退回发起人修改",
"description": "质控确认不通过,退回问题发起人修改"
}
],
"isRejectTarget": false,
"rejectCode": null,
"isStarterNode": false,
"isConvergencePoint": false,
"slots": []
}| 字段 | 含义 |
|---|---|
taskDefinitionKey |
必须等于 BPMN 中的 userTask id |
nodeSemantic |
当前节点业务语义,供前端路由表单和业务系统识别 |
pageCode |
前端页面/组件编码;如果是 http/https URL,待办接口会拼 pageUrl |
roleKey |
当前节点处理角色,即“谁处理当前节点” |
assigneeMode |
当前节点处理模式,single 或 multiple,用于前端/语义展示 |
canReassign |
当前节点是否允许转派;默认 false |
canReject |
当前节点是否允许驳回 |
rejectOptions |
当前节点可驳回到哪些目标,提交驳回时使用其中的 rejectCode |
isRejectTarget |
当前节点是否能作为驳回落点 |
rejectCode |
当前节点作为驳回落点时的代码 |
isStarterNode |
是否发起节点 |
isConvergencePoint |
是否汇聚/不可撤回节点 |
callbackUrl |
节点完成后的业务回调地址;空/null 表示禁用节点级回调 |
slots |
当前节点完成时,前端需要渲染并提交的路径/选人槽 |
slots 描述的是“当前节点完成时,用户需要为后续路径提交什么”。它既可以是下一人工节点的选人槽,也可以是前端用于展示路径选择的 no-op 槽。
| 字段 | 含义 |
|---|---|
slotKey |
前端提交 nextSlotSelections / initialSlotSelections 时使用的唯一 key |
roleKey |
该槽位候选人来自哪个推荐池,即 RecommendedAssigneesSnapshot[slot.roleKey] |
label |
前端展示文案 |
mode |
single 或 multiple;转换为变量时决定写 string 还是 string list |
variableName |
最终写入 Flowable 的流程变量名 |
required |
当 conditionalOn 满足且该槽位参与转换时,是否必须提交 users |
conditionalOn |
条件表达式,如 IS_SOLVED==false;满足时该槽位才生效 |
restrictToRecommended |
是否建议前端限制在推荐人范围内;后端只做越界审计,不强拦截 |
三键不要混用:
| 字段 | 不能替代谁 | 原因 |
|---|---|---|
slotKey |
不能替代 variableName |
它只是前端提交 key,不写入 Flowable |
roleKey |
不能替代 slotKey |
它只是推荐人池 key,不是提交 key |
variableName |
不能替代 slotKey |
它只给 Flowable 用,前端不应按它提交 |
通常,选“下一个人工节点处理人”时,slots[].roleKey 应该与下一个节点外层 roleKey 一致,因为它们指向同一个业务角色推荐池。但这不是靠命名推导,而是配置明确声明。
conditionalOn 由完成任务时的 businessVariables 触发。当前支持:
IS_SOLVED==true
IS_SOLVED==false
PROBLEM_ATTRIBUTE=true
!SOME_FLAG=true
SOME_VALUE=abc
运行时转换规则:
- 条件不满足的 slot 会被跳过。
- 被跳过的 slot 不校验
required,也不写入variableName。 - 条件满足且
required=true时,必须提交非空users。 nextSlotSelections里提交未知slotKey会报错。
注意:当前 /api/tasks/pending 会返回节点配置里的全部 requiredSlots,不会根据尚未提交的 businessVariables 动态过滤条件槽。前端需要用页面上的业务选项,例如“是否已解决”,配合 conditionalOn 自己决定当前显示哪个槽。
从 Flowable 执行角度,直接结束路径不需要下一人工节点,也不需要写 assignee 变量。真正驱动路径的是 BPMN 网关条件变量,例如:
{
"businessVariables": {
"IS_SOLVED": true
},
"nextSlotSelections": []
}如果前端需要依赖 slots 数量和 conditionalOn 渲染“true/false 两个选项”,可以配置一个 no-op 路径槽。它用于前端展示,不负责选人:
{
"slotKey": "direct_end_when_solved",
"roleKey": "problem_zero_quality_member",
"label": "已解决,直接结束",
"mode": "single",
"variableName": "__noopDirectEnd",
"required": false,
"conditionalOn": "IS_SOLVED==true",
"restrictToRecommended": false
}用户选择 true 时可以提交:
{
"businessId": "PZ_001",
"employeeId": "EMP_Q1",
"action": 1,
"businessVariables": {
"IS_SOLVED": true
},
"nextSlotSelections": [
{ "slotKey": "direct_end_when_solved", "users": [] }
]
}因为 required=false 且 users=[],转换器不会写入 __noopDirectEnd。流程仍由 IS_SOLVED=true 走到 BPMN 的结束路径。
{
"taskDefinitionKey": "ut03_quality_group_confirm",
"roleKey": "problem_zero_quality_member",
"assigneeMode": "multiple",
"slots": [
{
"slotKey": "direct_end_when_solved",
"roleKey": "problem_zero_quality_member",
"label": "已解决,直接结束",
"mode": "single",
"variableName": "__noopDirectEnd",
"required": false,
"conditionalOn": "IS_SOLVED==true",
"restrictToRecommended": false
},
{
"slotKey": "responsible_person",
"roleKey": "problem_zero_responsible_person",
"label": "责任人",
"mode": "multiple",
"variableName": "responsibleAssigneeList",
"required": true,
"conditionalOn": "IS_SOLVED==false",
"restrictToRecommended": true
}
]
}用户选择 false 时:
{
"businessVariables": {
"IS_SOLVED": false
},
"nextSlotSelections": [
{ "slotKey": "responsible_person", "users": ["EMP_R1"] }
]
}专项工作:
{
"businessVariables": {
"PROBLEM_ATTRIBUTE": true
},
"nextSlotSelections": [
{ "slotKey": "counterpart_leader", "users": ["EMP_C1"] }
]
}非专项工作:
{
"businessVariables": {
"PROBLEM_ATTRIBUTE": false
},
"nextSlotSelections": [
{ "slotKey": "discoverer_direct", "users": ["EMP_D1"] }
]
}BPMN 中的 multiInstanceLoopCharacteristics 只表示多实例任务,不天然等于会签或或签。
<multiInstanceLoopCharacteristics isSequential="false"
flowable:collection="${teamLeaderAssigneeList}"
flowable:elementVariable="teamLeaderAssignee">
<completionCondition>${nrOfCompletedInstances >= 1}</completionCondition>
</multiInstanceLoopCharacteristics>| 配置 | 含义 |
|---|---|
isSequential="false" |
并行创建多个人的任务 |
flowable:collection |
人员列表变量 |
flowable:elementVariable |
当前实例中的单个人变量 |
completionCondition >= 1 |
任意一人完成后,整个多实例节点结束 |
所以当前问题归零这些节点是“并行或签”。如果要会签,通常使用:
<completionCondition>${nrOfCompletedInstances == nrOfInstances}</completionCondition>或者不配置 completionCondition,让 Flowable 默认等待所有多实例完成。