package model import ( "context" "git.noahlan.cn/northlan/ntools-go/gorm-zero/gormc" "github.com/pkg/errors" "gorm.io/gorm" "gorm.io/plugin/optimisticlock" "live-service/common/nerr" ) var _ UserIntegralModel = (*customUserIntegralModel)(nil) type ( // UserIntegralModel is an interface to be customized, add more methods here, // and implement the added methods in customUserIntegralModel. UserIntegralModel interface { userIntegralModel Transact(ctx context.Context, tx *gorm.DB, fn func(tx *gorm.DB) error) error InsertTx(ctx context.Context, tx *gorm.DB, data *UserIntegral) error FindOneTx(ctx context.Context, tx *gorm.DB, userId int64) (*UserIntegral, error) UpdateTx(ctx context.Context, tx *gorm.DB, integral *UserIntegral) error // ChangeIntegral 用户积分变动 ChangeIntegral(ctx context.Context, tx *gorm.DB, userId int64, change int64) (int64, error) } customUserIntegralModel struct { *defaultUserIntegralModel } ) // NewUserIntegralModel returns a model for the database table. func NewUserIntegralModel(conn *gorm.DB) UserIntegralModel { return &customUserIntegralModel{ defaultUserIntegralModel: newUserIntegralModel(conn), } } func (m *customUserIntegralModel) Transact(ctx context.Context, tx *gorm.DB, fn func(tx *gorm.DB) error) error { return withTx(ctx, m.conn, tx).Transaction(fn) } func (m *customUserIntegralModel) InsertTx(ctx context.Context, tx *gorm.DB, data *UserIntegral) error { err := withTx(ctx, m.conn, tx).Create(&data).Error return err } func (m *customUserIntegralModel) UpdateTx(ctx context.Context, tx *gorm.DB, integral *UserIntegral) error { if integral.Integral < 0 { return errors.New("无法将积分更新至负数") } db := withTx(ctx, m.conn, tx) result := db.Model(&integral).Updates(&UserIntegral{Integral: integral.Integral, Version: optimisticlock.Version{Int64: 1}}) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return ErrRowsAffectedZero } return nil } func (m *customUserIntegralModel) FindOneTx(ctx context.Context, tx *gorm.DB, userId int64) (*UserIntegral, error) { var resp UserIntegral err := withTx(ctx, m.conn, tx).Model(&UserIntegral{}). Where("`user_id` = ?", userId).Take(&resp).Error switch err { case nil: return &resp, nil case gormc.ErrNotFound: return nil, ErrNotFound default: return nil, err } } func (m *customUserIntegralModel) ChangeIntegral(ctx context.Context, tx *gorm.DB, userId int64, change int64) (int64, error) { resp := change var err error for i := VersionRetryCount; i > 0; i-- { err = withTx(ctx, m.conn, tx).Transaction(func(tx *gorm.DB) error { data, err := m.FindOneTx(ctx, tx, userId) if err != nil { if errors.Is(err, ErrNotFound) { if change < 0 { return nerr.NewWithCode(nerr.UserIntegralNotEnoughError) } // 用户积分记录不存在,进行插入 if err = m.InsertTx(ctx, tx, &UserIntegral{ UserId: userId, Integral: change, }); err != nil { return errors.Wrap(err, "插入用户积分失败") } return nil } else { return errors.Wrap(err, "获取当前用户积分失败") } } if data.Integral+change < 0 { return errors.New("用户积分不足") } data.Integral += change if err = m.UpdateTx(ctx, tx, data); err != nil { if errors.Is(err, ErrRowsAffectedZero) { return err } return errors.Wrap(err, "更新用户积分失败") } resp = data.Integral return nil }) if err != nil && errors.Is(err, ErrRowsAffectedZero) { // 未能正确更新,直接重试 continue } else { // 其它错误退出循环 break } } return resp, err }