beforedelete钩子唯一正确写法是func (u user) beforedelete(tx gorm.db) error且与user同包;软删时它是权限拦截唯一时机,需手动注入上下文并用setcolumn写审计字段,硬删需通过tx.statement.unscoped区分并加强校验。

BeforeDelete钩子签名必须严格匹配
权限校验逻辑写在BeforeDelete里,但GORM v2根本不会调用它,除非方法签名完全正确。常见静默失效场景包括:
-
func (u User) BeforeDelete(tx *gorm.DB) error→ 值接收者,改的是副本,原对象不变,且GORM不识别 -
func (u *User) BeforeDelete() error→ 缺少tx *gorm.DB参数,GORM直接跳过 -
func (u *User) beforedelete(tx *gorm.DB) error→ 方法名大小写错误,GORM只认BeforeDelete - 钩子定义在
hooks/子包里,而User在models/包 → 包不一致,GORM当不存在
正确写法只有一种:func (u *User) BeforeDelete(tx *gorm.DB) error,且必须和User结构体在同一个包内。
软删除下BeforeDelete是唯一能做权限检查的时机
调用db.Delete(&user)时,GORM默认走软删除(UPDATE),此时AfterDelete永远不会触发,BeforeDelete却一定会执行——这是做权限拦截的最后机会。
典型权限校验逻辑示例:
func (u *User) BeforeDelete(tx *gorm.DB) error {
// 从上下文或tx.Statement中提取当前操作人ID(需提前注入)
userID, ok := tx.Statement.Context.Value("current_user_id").(uint)
if !ok {
return errors.New("未认证用户,禁止删除")
}
// 管理员可删一切,普通用户只能删自己
if userID != u.ID && !isAdmin(tx, userID) {
return errors.New("权限不足:仅管理员或本人可删除该用户")
}
return nil
}
注意:tx.Statement.Context需在调用Delete()前手动注入,GORM本身不提供请求上下文透传机制。
物理删除也要走BeforeDelete,但得靠Unscoped判断
如果业务需要支持硬删(如后台强制清理),调用的是db.Unscoped().Delete(&user),此时BeforeDelete仍会触发,但你得区分软删还是硬删:
-
tx.Statement.Unscoped == true→ 表示本次是物理删除(Unscoped()已启用) -
tx.Statement.Unscoped == false→ 默认软删流程
权限策略可据此差异化处理,例如硬删要求更高权限等级,或记录额外审计字段:
func (u *User) BeforeDelete(tx *gorm.DB) error {
if tx.Statement.Unscoped {
// 物理删除:只允许超级管理员
if !isSuperAdmin(tx) {
return errors.New("物理删除需SUPER_ADMIN权限")
}
} else {
// 软删除:按常规权限校验
if !canSoftDelete(tx, u.ID) {
return errors.New("无软删除权限")
}
}
return nil
}
时间字段更新必须用SetColumn,不能直接赋值
权限校验通过后,常需记录“谁删的”“何时删的”,比如更新DeletedBy或UpdatedAt字段。但直接写u.DeletedBy = userID无效——因为u是模型实例副本,且GORM生成SQL时仍用原始值。
正确方式是用tx.Statement.SetColumn():
-
tx.Statement.SetColumn("deleted_by", userID)→ 下次INSERT/UPDATE会带上该字段 -
tx.Statement.SetColumn("updated_at", time.Now())→ 替换默认零值时间戳
注意:DeletedAt字段由GORM自动填充,无需手动设;但DeletedBy这类自定义字段必须显式SetColumn,否则数据库里仍是NULL或零值。
真正难的不是写钩子,而是让BeforeDelete里的权限逻辑拿到真实上下文、准确区分软硬删、并把审计字段可靠写进数据库——这三个点漏掉任何一个,权限校验就形同虚设。











