查询操作只有一个钩子:AfterFind。在查询完成后执行,适合处理数据转换和初始化。
查询后执行:
func (u *User) AfterFind(tx *gorm.DB) error {
u.Name = strings.TrimSpace(u.Name)
return nil
}
AfterFind 在以下操作后触发:
db.First(&user)
db.Find(&users)
db.Take(&user)
db.Last(&user)
查询后转换数据格式:
type User struct {
ID uint
AvatarURL string
}
func (u *User) AfterFind(tx *gorm.DB) error {
if u.AvatarURL != "" && !strings.HasPrefix(u.AvatarURL, "http") {
u.AvatarURL = "https://cdn.example.com/" + u.AvatarURL
}
return nil
}
查询后计算派生字段:
type Order struct {
ID uint
TotalAmount float64
PaidAmount float64
RemainAmount float64 `gorm:"-"`
}
func (o *Order) AfterFind(tx *gorm.DB) error {
o.RemainAmount = o.TotalAmount - o.PaidAmount
return nil
}
gorm:"-" 标签表示不映射到数据库字段。
查询后加载额外数据:
func (u *User) AfterFind(tx *gorm.DB) error {
var count int64
tx.Model(&Order{}).Where("user_id = ?", u.ID).Count(&count)
u.OrderCount = count
return nil
}
注意:这会产生 N+1 查询问题。更好的方式是用预加载或在查询时计算。
解析存储的 JSON 数据:
type User struct {
ID uint
Config string
Settings map[string]interface{} `gorm:"-"`
}
func (u *User) AfterFind(tx *gorm.DB) error {
if u.Config != "" {
json.Unmarshal([]byte(u.Config), &u.Settings)
}
return nil
}
格式化时间显示:
type Article struct {
ID uint
PublishedAt time.Time
PublishedAtStr string `gorm:"-"`
}
func (a *Article) AfterFind(tx *gorm.DB) error {
if !a.PublishedAt.IsZero() {
a.PublishedAtStr = a.PublishedAt.Format("2006-01-02 15:04:05")
}
return nil
}
根据多个字段计算状态:
type Order struct {
ID uint
Status string
PaidAmount float64
TotalAmount float64
DisplayStatus string `gorm:"-"`
}
func (o *Order) AfterFind(tx *gorm.DB) error {
switch {
case o.Status == "cancelled":
o.DisplayStatus = "已取消"
case o.PaidAmount >= o.TotalAmount:
o.DisplayStatus = "已支付"
case o.PaidAmount > 0:
o.DisplayStatus = "部分支付"
default:
o.DisplayStatus = "待支付"
}
return nil
}
批量查询时,每条记录都会触发 AfterFind:
var users []User
db.Find(&users)
每个 user 都会执行 AfterFind。
预加载的关联也会触发 AfterFind:
type User struct {
ID uint
Profile Profile
}
func (p *Profile) AfterFind(tx *gorm.DB) error {
p.Bio = strings.TrimSpace(p.Bio)
return nil
}
db.Preload("Profile").Find(&users)
Profile 的 AfterFind 也会执行。
AfterFind 在每条记录上执行,可能影响性能:
func (u *User) AfterFind(tx *gorm.DB) error {
// 避免 N+1 查询
return nil
}
如果需要在 AfterFind 中查询数据库,考虑用批量查询或预加载。
db.Session(&gorm.Session{SkipHooks: true}).Find(&users)
商品价格计算
type Product struct {
ID uint
Price float64
Discount float64
FinalPrice float64 `gorm:"-"`
}
func (p *Product) AfterFind(tx *gorm.DB) error {
p.FinalPrice = p.Price * (1 - p.Discount/100)
return nil
}
用户权限加载
type User struct {
ID uint
RoleID uint
Permissions []string `gorm:"-"`
}
func (u *User) AfterFind(tx *gorm.DB) error {
var role Role
if err := tx.Preload("Permissions").First(&role, u.RoleID).Error; err != nil {
return err
}
for _, p := range role.Permissions {
u.Permissions = append(u.Permissions, p.Code)
}
return nil
}
文章内容处理
type Article struct {
ID uint
Content string
Summary string `gorm:"-"`
}
func (a *Article) AfterFind(tx *gorm.DB) error {
if len(a.Content) > 200 {
a.Summary = a.Content[:200] + "..."
} else {
a.Summary = a.Content
}
return nil
}
AfterFind 是唯一的查询钩子,适合数据转换、计算字段、格式化等场景。注意性能影响,避免在钩子中执行额外查询。