跳到主要内容

钩子

VEF 里实际上有两层 hook 面:

  • CRUD builder 提供的操作级 hook,用于框架托管的 create / update / delete / import / export 流程
  • Bun/ORM 级模型 hook,用于更底层的查询生命周期拦截

这两层 hook 解决的问题不同,不应混为一谈。

Hook 家族总览

Hook 家族运行位置作用范围常见用途
CRUD hookCRUD builder 内部单个 CRUD 接口业务约束、事务内副作用、文件提升协同
Bun 模型 hookORM 模型生命周期单个模型与单种查询类型底层查询修改、模型生命周期行为、持久化侧检查

CRUD Hook 面

CRUD builder 暴露的 hook API 如下:

操作前置 hook后置 hook
CreateWithPreCreate(...)WithPostCreate(...)
UpdateWithPreUpdate(...)WithPostUpdate(...)
DeleteWithPreDelete(...)WithPostDelete(...)
CreateManyWithPreCreateMany(...)WithPostCreateMany(...)
UpdateManyWithPreUpdateMany(...)WithPostUpdateMany(...)
DeleteManyWithPreDeleteMany(...)WithPostDeleteMany(...)
ExportWithPreExport(...)没有 post-export hook
ImportWithPreImport(...)WithPostImport(...)

CRUD Hook 签名

精确的 processor 类型声明——单条、批量与导出/导入——只在一处维护: 泛型 CRUD 的 Processor 类型签名。本页聚焦于 "什么时候用哪个 hook"以及事务内到底执行了什么。

CRUD Hook 与事务边界

最重要的一条规则是:CRUD 写操作本身已经在事务中执行。CRUD hook 拿到的 orm.DB 就是当前事务作用域内的 tx,因此你额外做的数据库操作天然处在同一事务里。

示例:

crud.NewCreate[User, UserParams]().
WithPostCreate(func(model *User, params *UserParams, ctx fiber.Ctx, tx orm.DB) error {
_, err := tx.NewInsert().Model(&AuditLog{
UserID: model.ID,
Action: "created",
}).Exec(ctx.Context())
return err
})

CRUD Hook 错误行为

如果 CRUD hook 返回错误:

  • 当前操作失败
  • 外层事务回滚
  • 框架按照正常 result 错误链路返回错误

因此,CRUD hook 很适合承载“必须原子化生效”的业务约束。

CRUD Hook 与文件生命周期

Create、Update、Delete builder 会和 storage.Files / FilesFor[T] 生命周期门面(旧 Promoter[T] 的替代品)协同工作。

这意味着:

  • meta 标签的文件字段会在业务写入的同一事务里完成对账
  • Update 时被替换的字段值会被入队为异步删除
  • Delete 时每个被引用的文件都会被入队为异步删除
  • 事务回滚时不会消费 claim、也不会入队删除 —— 没有什么需要"恢复"

因此 hook 与文件生命周期共享同一条事务;真正的后端删除则由 storage delete worker 异步执行(见 存储)。

Bun 模型 Hook 面

在 ORM 层,VEF 也暴露了 Bun hook 接口:

Hook 接口触发时机
orm.BeforeSelectHookSELECT
orm.AfterSelectHookSELECT
orm.BeforeInsertHookINSERT
orm.AfterInsertHookINSERT
orm.BeforeUpdateHookUPDATE
orm.AfterUpdateHookUPDATE
orm.BeforeDeleteHookDELETE
orm.AfterDeleteHookDELETE

这些 hook 是定义在模型类型上的,作用层级是 ORM 生命周期,而不是 API action 生命周期。

什么时候用 CRUD Hook

以下场景适合 CRUD hook:

  • 额外业务步骤紧贴某一个 CRUD 动作
  • 对外接口语义仍然保持 CRUD
  • 额外逻辑必须参与同一事务
  • hook 需要同时访问 params 和 model 状态

什么时候用 Bun 模型 Hook

以下场景更适合 Bun 模型 hook:

  • 逻辑本身属于模型生命周期
  • 这段行为应该在 API 层之外也生效
  • hook 需要直接修改或检查底层 Bun query
  • 关注点更偏持久化,而不是 endpoint 编排

什么时候不该用 Hook

以下场景 hook 反而不合适:

  • 这个动作已经不再是标准 CRUD
  • 一个接口在编排多个互不相关的流程
  • 该动作语义更适合一个显式业务命令接口
  • hook 越堆越多,导致行为拆散后难以理解

这类场景通常应该改用自定义 handler。

实践建议

  • CRUD hook 保持短小,聚焦单个操作
  • Bun 模型 hook 聚焦持久化层行为
  • 事务内业务步骤优先放 CRUD hook
  • 如果某个行为应该在所有模型访问路径里生效,优先考虑模型 hook
  • 如果一个资源开始堆很多 CRUD hook,就该反思是不是应该改成自定义 handler

下一步

继续阅读 验证错误处理,看请求失败和业务失败是如何向客户端暴露的。