跳到主要内容

钩子

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 签名

单条 create / update / delete

Hook签名概要
PreCreatefunc(model *TModel, params *TParams, query orm.InsertQuery, ctx fiber.Ctx, tx orm.DB) error
PostCreatefunc(model *TModel, params *TParams, ctx fiber.Ctx, tx orm.DB) error
PreUpdatefunc(oldModel, model *TModel, params *TParams, query orm.UpdateQuery, ctx fiber.Ctx, tx orm.DB) error
PostUpdatefunc(oldModel, model *TModel, params *TParams, ctx fiber.Ctx, tx orm.DB) error
PreDeletefunc(model *TModel, query orm.DeleteQuery, ctx fiber.Ctx, tx orm.DB) error
PostDeletefunc(model *TModel, ctx fiber.Ctx, tx orm.DB) error

批量 create / update / delete

Hook签名概要
PreCreateManyfunc(models []TModel, paramsList []TParams, query orm.InsertQuery, ctx fiber.Ctx, tx orm.DB) error
PostCreateManyfunc(models []TModel, paramsList []TParams, ctx fiber.Ctx, tx orm.DB) error
PreUpdateManyfunc(oldModels, models []TModel, paramsList []TParams, query orm.UpdateQuery, ctx fiber.Ctx, tx orm.DB) error
PostUpdateManyfunc(oldModels, models []TModel, paramsList []TParams, ctx fiber.Ctx, tx orm.DB) error
PreDeleteManyfunc(models []TModel, query orm.DeleteQuery, ctx fiber.Ctx, tx orm.DB) error
PostDeleteManyfunc(models []TModel, ctx fiber.Ctx, tx orm.DB) error

导出与导入

Hook签名概要
PreExportfunc(models []TModel, search TSearch, ctx fiber.Ctx, db orm.DB) error
PreImportfunc(models []TModel, query orm.InsertQuery, ctx fiber.Ctx, tx orm.DB) error
PostImportfunc(models []TModel, ctx fiber.Ctx, tx orm.DB) error

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 promoter 协同工作。

这意味着:

  • 文件提升和 CRUD 操作处于同一生命周期
  • Update 回滚时可以恢复被替换文件
  • Delete 成功后可以清理已提升文件

因此 hook 与文件提升是共享同一条 CRUD 生命周期的。

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

下一步

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