删除钩子

删除记录时,GORM 会依次触发 BeforeDelete、AfterDelete 钩子。

BeforeDelete

删除记录前执行:

func (u *User) BeforeDelete(tx *gorm.DB) error {
    if u.Role == "admin" {
        return errors.New("不能删除管理员")
    }
    return nil
}

AfterDelete

删除记录后执行:

func (u *User) AfterDelete(tx *gorm.DB) error {
    return tx.Delete(&UserProfile{}, "user_id = ?", u.ID).Error
}

执行顺序

删除记录时的完整顺序:

BeforeDelete
  ↓
执行 DELETE
  ↓
AfterDelete

软删除与钩子

软删除也会触发钩子:

type User struct {
    gorm.Model
    Name string
}

func (u *User) BeforeDelete(tx *gorm.DB) error {
    fmt.Println("BeforeDelete 触发")
    return nil
}

db.Delete(&user)

软删除执行的是 UPDATE,但仍然触发删除钩子。

真正删除的钩子

Unscoped().Delete() 触发同样的钩子:

func (u *User) BeforeDelete(tx *gorm.DB) error {
    if tx.Statement.Unscoped {
        fmt.Println("真正删除")
    } else {
        fmt.Println("软删除")
    }
    return nil
}

db.Unscoped().Delete(&user)

检查是否软删除

判断是软删除还是真正删除:

func (u *User) BeforeDelete(tx *gorm.DB) error {
    if tx.Statement.Unscoped {
        var orders []Order
        if err := tx.Where("user_id = ?", u.ID).Find(&orders).Error; err != nil {
            return err
        }
        if len(orders) > 0 {
            return errors.New("用户有订单记录,不能删除")
        }
    }
    return nil
}

级联删除

在钩子中删除关联数据:

func (u *User) BeforeDelete(tx *gorm.DB) error {
    if err := tx.Where("user_id = ?", u.ID).Delete(&Order{}).Error; err != nil {
        return err
    }
    if err := tx.Where("user_id = ?", u.ID).Delete(&UserProfile{}).Error; err != nil {
        return err
    }
    return nil
}

删除前备份

删除前备份到历史表:

func (u *User) BeforeDelete(tx *gorm.DB) error {
    history := UserHistory{
        UserID:    u.ID,
        Name:      u.Name,
        Email:     u.Email,
        DeletedAt: time.Now(),
    }
    return tx.Create(&history).Error
}

条件删除

根据条件决定是否允许删除:

func (o *Order) BeforeDelete(tx *gorm.DB) error {
    if o.Status == "paid" || o.Status == "shipped" {
        return errors.New("已支付或已发货的订单不能删除")
    }
    return nil
}

删除审计

记录删除操作:

func (u *User) AfterDelete(tx *gorm.DB) error {
    log := AuditLog{
        Table:     "users",
        Action:    "delete",
        RecordID:  u.ID,
        UserID:    getCurrentUserID(tx),
        CreatedAt: time.Now(),
    }
    return tx.Create(&log).Error
}

批量删除

批量删除不触发钩子:

db.Where("status = ?", "inactive").Delete(&User{})

如果需要触发钩子,逐条删除:

var users []User
db.Where("status = ?", "inactive").Find(&users)
for _, user := range users {
    db.Delete(&user)
}

跳过钩子

db.Session(&gorm.Session{SkipHooks: true}).Delete(&user)

实际案例

用户删除

func (u *User) BeforeDelete(tx *gorm.DB) error {
    if u.Role == "admin" {
        return errors.New("管理员不能删除")
    }
    
    var orders int64
    tx.Model(&Order{}).Where("user_id = ? AND status IN ?", u.ID, []string{"paid", "shipped"}).Count(&orders)
    if orders > 0 {
        return errors.New("用户有进行中的订单,不能删除")
    }
    
    return nil
}

func (u *User) AfterDelete(tx *gorm.DB) error {
    tx.Where("user_id = ?", u.ID).Delete(&UserProfile{})
    tx.Where("user_id = ?", u.ID).Delete(&UserSetting{})
    
    return nil
}

订单删除

func (o *Order) BeforeDelete(tx *gorm.DB) error {
    if o.Status == "paid" {
        return errors.New("已支付订单不能删除,请申请退款")
    }
    
    if o.Status == "shipped" {
        return errors.New("已发货订单不能删除")
    }
    
    return nil
}

func (o *Order) AfterDelete(tx *gorm.DB) error {
    return tx.Where("order_id = ?", o.ID).Delete(&OrderItem{}).Error
}

文件删除

type File struct {
    ID      uint
    Path    string
    Size    int64
}

func (f *File) AfterDelete(tx *gorm.DB) error {
    if f.Path != "" {
        os.Remove(f.Path)
    }
    return nil
}

小结

删除钩子适合处理删除前验证、级联删除、审计日志等逻辑。注意软删除也会触发钩子,批量删除不会触发钩子。