SYSTEM OK | ACTION CONTRACTS 247 DOMAINS 35 DEMO ENVIRONMENT 无需申请 | 服务热线 18926139835
首页/洞察/技术系列/审批工作流引擎

审批工作流引擎

审批规则写在业务代码内部,是流程系统长期维护成本的来源之一。EAMX 的审批插件挂载在 LifecycleBus 的业务事件上,业务代码不涉及审批逻辑,规则变更也不需要修改业务代码。本文说明两种事件模式的差别、审批单的状态流转与高危动作的拒绝执行条件。

作者 EAMX 产品团队 发布 2026-09-28 阅读 约 4 分钟 专题 技术系列

01问题:审批逻辑与业务代码的耦合

审批流的常规实现方式是把判定条件直接写进业务函数:设备价值超过阈值时创建审批单并中断写入。

// 反模式:审批逻辑侵入业务代码 async function createEquipment(data) { if (data.value > 100000) { await createApprovalRequest(data); return { status: "pending_approval" }; } await insertEquipment(data); return { status: "created" }; }

这一写法带来三项代价,且三项都随系统规模放大。

  • 规则变更需要改业务代码。阈值调整、审批层级增加,均落在业务函数的修改范围内。
  • 审批检查被重复实现。设备、采购、工单各有写入入口,各自写一遍判定。
  • 插件启停需要改业务代码。停用审批流意味着回退业务函数的实现。

02拦截模式:业务代码不涉及审批

EAMX 把审批从业务函数中移出:业务代码只负责写入与返回结果,审批插件通过 lifecycleHandler 注册到业务事件上完成门槛判定。

// equipment 业务代码——不涉及审批 async function createEquipment(data, ctx) { const result = await db.insert(equipmentsTable).values(data); return { id: result.id, status: "created" }; } // approval 插件——独立于业务代码 lifecycleHandler("equipment:beforeCreate", async ({ payload, ctx, abort }) => { if (payload.value > 100000) { const needsApproval = await checkApprovalGate(ctx, { entityType: "Equipment", threshold: 100000, }); if (needsApproval) { await createApprovalRequest(ctx, payload); abort("设备价值超过阈值,已提交审批"); } } });

业务代码对审批的存在没有感知;调用 abort 时由事件总线中断主流程,并返回原因。

03emit 与 intercept 两种模式

事件总线提供两种订阅方式,差别在失败处理与数据修改权限上。

表 1 · emit(观察)与 intercept(拦截)的差别
属性emit(观察)intercept(拦截)
执行方式并行,Promise.allSettled串行,for-of
失败处理记录日志,不中断主流程抛出 AbortError,中断主流程
数据修改不可修改 payload经 ctx.intercept(modified) 修改 payload
典型用例通知、日志、缓存失效审批、校验、权限注入

依据:packages/core/src/lifecycle-bus.ts 的双模式实现,共 79 行。

串行是拦截模式的必要条件:多个拦截器按注册顺序执行,任一环节终止则后续环节不再执行。

04审批单的状态流转

门槛判定通过后创建审批单,主流程中断;审批单的流转由批准与驳回两个接口触发,结果以事件形式广播。

equipment:beforeCreate → 门槛判定 → 需要审批 ├─ 创建审批单 → abort(中断主流程)→ approval:created → 通知主管 ├─ POST /api/approval/:id/approve → approval:approved → 重新执行 createEquipment └─ POST /api/approval/:id/reject → approval:rejected → 通知申请人
表 2 · 审批单状态与后续去向
状态进入条件后续去向
待审批门槛判定通过,审批单已创建批准或驳回
已通过批准接口返回成功订阅 approval:approved 的业务插件重新执行原动作
已驳回驳回接口返回成功审批单保留原参数与意见,申请人修改后重提

驳回不删除审批单;重新提交时门槛判定再次执行,审批路径按新参数重新确定。 审批人亦可改派其他审批人复核,改派记录保留在审批单上。

05与统一执行管道的对齐

审批插件的拦截点挂在业务事件上,写操作本身仍在统一执行管道内完成:参数校验、权限裁决、预演、人工确认、幂等键与审计。

高危动作在动作定义上标注 confirm,未携带确认一律拒绝执行。 这条判定与调用通道无关:Web 控制台、内嵌 AI 助手与经 MCP 接入的外部 Agent 走同一条管道。

因此「Agent 不绕过审批」并非一项约定,而是结构性的结果:它没有可以绕过的路径。

06插件注册:一份清单与零侵入

审批插件以一份清单完成注册,包含依赖、权限、路由前缀与拦截点;启用或停用审批流即启用或停用这份清单。

export const approvalPlugin: PluginManifest = { id: "approval", name: "审批流", tier: 1, dependencies: ["auth"], permissions: [ { action: "approve", subject: "WorkOrder" }, { action: "approve", subject: "PurchaseRequest" }, ], routePrefix: "/api/approval", createRouter: () => { /* CRUD routes */ }, lifecycleHandlers: { "equipment:beforeCreate": equipmentCreateGate, "purchase:beforeSubmit": purchaseSubmitGate, }, };

07核心文件与边界

表 3 · 核心文件
文件行数职责
packages/core/src/lifecycle-bus.ts79emit / intercept 双模式事件总线
packages/core/src/types.ts—LifecycleEvent 类型定义

其一,审批仍由人完成。引擎负责判定与流转,不承诺免于人工审批。

其二,规则变更落在配置上。适用实体与阈值由 checkApprovalGate 的入参决定。

其三,拦截器需可重复执行。拦截点内的判定会被多次触发,拦截器应在终止主流程之前完成审批单的创建。

08作者与依据

作者
EAMX 产品团队 · 产品与解决方案
依据
packages/core/src/lifecycle-bus.ts 与审批插件清单的实现记录
数据来源
事件总线双模式实现(79 行);审批门槛判定与插件注册代码
引用标准
ISO 55000 系列仅为方法论依据,不构成认证;认证对象是组织,而非软件
更新日期
2026-09-28
01相关产品能力

本文所述机制对应的产品能力

02相关文章

同系列与跨系列的延伸

审批边界的判定方式可以当场核对

拦截点与门槛判定、审批单状态流转、高危动作的拒绝执行条件,均可由演示环境核对。

信息中心 / 技术评估
要看事件总线与插件注册的实现
体系办 / 内控
关注审批留痕与责任环节