钩子(Hook)是在特定操作前后自动执行的函数。GORM 提供了完整的钩子机制,可以在创建、更新、删除、查询时插入自定义逻辑。
钩子是回调函数,在数据库操作的生命周期中自动触发:
func (u *User) BeforeCreate(tx *gorm.DB) error {
u.UUID = uuid.New()
return nil
}
创建用户前,自动生成 UUID。
GORM 支持的钩子:
| 钩子 | 触发时机 |
|---|---|
| BeforeCreate | 创建记录前 |
| AfterCreate | 创建记录后 |
| BeforeUpdate | 更新记录前 |
| AfterUpdate | 更新记录后 |
| BeforeDelete | 删除记录前 |
| AfterDelete | 删除记录后 |
| BeforeSave | 保存前(创建或更新) |
| AfterSave | 保存后(创建或更新) |
| AfterFind | 查询后 |
创建记录:
BeforeSave -> BeforeCreate -> 创建操作 -> AfterCreate -> AfterSave
更新记录:
BeforeSave -> BeforeUpdate -> 更新操作 -> AfterUpdate -> AfterSave
删除记录:
BeforeDelete -> 删除操作 -> AfterDelete
查询记录:
查询操作 -> AfterFind
钩子是模型的方法:
type User struct {
ID uint
Name string
Password string
}
func (u *User) BeforeCreate(tx *gorm.DB) error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.Password = string(hashedPassword)
return nil
}
钩子接收 *gorm.DB 参数,可以访问当前数据库连接:
func (u *User) AfterCreate(tx *gorm.DB) error {
log := Log{
Action: "create_user",
RecordID: u.ID,
CreatedAt: time.Now(),
}
return tx.Create(&log).Error
}
钩子返回错误会阻止操作:
func (u *User) BeforeDelete(tx *gorm.DB) error {
if u.Role == "admin" {
return errors.New("不能删除管理员")
}
return nil
}
某些操作需要跳过钩子:
db.Session(&gorm.Session{SkipHooks: true}).Create(&user)
或者用 UpdateColumn:
db.Model(&user).UpdateColumn("name", "新名字")
UpdateColumn 不触发 BeforeUpdate 和 AfterUpdate。
钩子在事务中执行:
db.Transaction(func(tx *gorm.DB) error {
// BeforeCreate 在这个事务中执行
return tx.Create(&user).Error
})
钩子中的操作和主操作在同一事务中,失败会一起回滚。
同一个模型可以定义多个钩子:
func (u *User) BeforeSave(tx *gorm.DB) error {
u.UpdatedAt = time.Now()
return nil
}
func (u *User) BeforeCreate(tx *gorm.DB) error {
u.CreatedAt = time.Now()
return nil
}
执行顺序:BeforeSave -> BeforeCreate。
除了模型方法,还可以全局注册钩子:
func init() {
callbacks.Register("gorm:create", func(db *gorm.DB) {
if db.Statement.Schema != nil {
for _, field := range db.Statement.Schema.Fields {
if field.Name == "CreatedAt" {
field.Set(db.Statement.ReflectValue, time.Now())
}
}
}
})
}
这种方式更底层,一般不需要。
密码加密
func (u *User) BeforeCreate(tx *gorm.DB) error {
if u.Password != "" {
hashed, _ := bcrypt.GenerateFromPassword([]byte(u.Password), 10)
u.Password = string(hashed)
}
return nil
}
func (u *User) BeforeUpdate(tx *gorm.DB) error {
if u.Password != "" && !strings.HasPrefix(u.Password, "$2a$") {
hashed, _ := bcrypt.GenerateFromPassword([]byte(u.Password), 10)
u.Password = string(hashed)
}
return nil
}
审计日志
func (u *User) AfterCreate(tx *gorm.DB) error {
return tx.Create(&AuditLog{
Table: "users",
Action: "create",
RecordID: u.ID,
UserID: getCurrentUserID(tx),
CreatedAt: time.Now(),
}).Error
}
数据验证
func (u *User) BeforeCreate(tx *gorm.DB) error {
if u.Name == "" {
return errors.New("用户名不能为空")
}
if len(u.Password) < 6 {
return errors.New("密码长度至少6位")
}
return nil
}
钩子是 GORM 的强大功能,可以在数据库操作中插入自定义逻辑。理解钩子的执行顺序和生命周期,合理使用能简化很多业务代码。