GORM Go语言模型钩子继承问题:复用父类BeforeCreate逻辑方案
GORM继承模型中BeforeCreate钩子复用父类逻辑的解决方案
在GORM的继承模型中,子类重写BeforeCreate钩子后,父类的同名钩子不会自动执行,导致父类中ID生成、时间戳设置等公共逻辑失效。除了手动重复调用ID生成函数外,有以下几种更优雅的解决办法:
方案一:子类钩子显式调用父类钩子方法
这是最符合Go继承逻辑的方案,在子类的BeforeCreate中直接调用父类的同名方法,复用父类的公共逻辑,再添加子类自身的业务逻辑。
修改后的代码示例:
type BaseModel struct { ID string `json:"id" gorm:"primary_key"` CreatedAt int64 `json:"created_at" gorm:"index;not null;"` UpdatedAt int64 `json:"updated_at" gorm:"index;not null;"` } func (b *BaseModel) BeforeCreate(tx *gorm.DB) (err error) { b.CreatedAt = time.Now().Unix() b.UpdatedAt = time.Now().Unix() b.ID = id.New(tx.Statement.Table) return } type BaseModelSoftDelete struct { BaseModel DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index;"` } // 子类调用父类BeforeCreate func (b *BaseModelSoftDelete) BeforeCreate(tx *gorm.DB) (err error) { if err = b.BaseModel.BeforeCreate(tx); err != nil { return err } // 若BaseModelSoftDelete有专属逻辑,可在此添加 return } type APIClientUser struct { BaseModelSoftDelete WhitelistIPs *pq.StringArray `json:"whitelist_ips" gorm:"type:text[];"` } func checkIPAddress(ip *string) error { if net.ParseIP(*ip) == nil { return errors.New("invalid ip address") } return nil } // 调用父类BaseModelSoftDelete的BeforeCreate,间接复用BaseModel逻辑 func (b *APIClientUser) BeforeCreate(tx *gorm.DB) (err error) { if err = b.BaseModelSoftDelete.BeforeCreate(tx); err != nil { return err } // 子类专属的IP校验逻辑 if b.WhitelistIPs != nil { for _, ip := range *b.WhitelistIPs { if err = checkIPAddress(&ip); err != nil { return } } } return }
方案二:抽离公共逻辑为独立函数
将父类中ID生成、时间戳设置的逻辑抽成独立函数,在各个模型的BeforeCreate中调用该函数,避免重复代码。
修改后的代码示例:
// 抽离公共逻辑函数 func SetBaseModelFields(b *BaseModel, tableName string) { b.CreatedAt = time.Now().Unix() b.UpdatedAt = time.Now().Unix() b.ID = id.New(tableName) } func (b *BaseModel) BeforeCreate(tx *gorm.DB) (err error) { SetBaseModelFields(b, tx.Statement.Table) return } func (b *BaseModelSoftDelete) BeforeCreate(tx *gorm.DB) (err error) { SetBaseModelFields(&b.BaseModel, tx.Statement.Table) return } func (b *APIClientUser) BeforeCreate(tx *gorm.DB) (err error) { SetBaseModelFields(&b.BaseModelSoftDelete.BaseModel, tx.Statement.Table) // IP校验逻辑 if b.WhitelistIPs != nil { for _, ip := range *b.WhitelistIPs { if err = checkIPAddress(&ip); err != nil { return } } } return }
方案三:全局注册GORM回调
如果所有继承BaseModel的模型都需要执行ID和时间戳逻辑,可以通过全局注册回调的方式,让公共逻辑自动执行,子类钩子只需处理自身业务。
修改后的代码示例:
func init() { // 在GORM的BeforeCreate阶段前注册全局回调 gorm.DefaultCallback.Create().Before("gorm:before_create").Register("set_base_model_fields", func(tx *gorm.DB) { // 判断当前模型是否实现了SetBaseFields方法 if baseModel, ok := tx.Statement.Model.(interface{ SetBaseFields(string) }); ok { baseModel.SetBaseFields(tx.Statement.Table) } }) } // 给BaseModel添加SetBaseFields方法 func (b *BaseModel) SetBaseFields(tableName string) { b.CreatedAt = time.Now().Unix() b.UpdatedAt = time.Now().Unix() b.ID = id.New(tableName) } // 子类钩子只需处理自身逻辑,无需关心公共字段设置 func (b *APIClientUser) BeforeCreate(tx *gorm.DB) (err error) { if b.WhitelistIPs != nil { for _, ip := range *b.WhitelistIPs { if err = checkIPAddress(&ip); err != nil { return } } } return }
以上三种方案中,方案一最直观且易于维护,完全符合Go的组合继承特性;方案二适合需要单独调用公共逻辑的场景;方案三适合全局统一配置的场景,可根据实际业务需求选择。
内容的提问来源于stack exchange,提问作者rak1n
相关产品推荐
相关产品推荐

