diff --git a/internal/logic/admin/application/deleteSubscribeApplicationLogic.go b/internal/logic/admin/application/deleteSubscribeApplicationLogic.go index 57cdd70..1b8456d 100644 --- a/internal/logic/admin/application/deleteSubscribeApplicationLogic.go +++ b/internal/logic/admin/application/deleteSubscribeApplicationLogic.go @@ -29,7 +29,7 @@ func (l *DeleteSubscribeApplicationLogic) DeleteSubscribeApplication(req *types. err := l.svcCtx.ClientModel.Delete(l.ctx, req.Id) if err != nil { l.Errorf("Failed to delete subscribe application with ID %d: %v", req.Id, err) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error()) + return errors.Wrap(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error()) } return nil } diff --git a/internal/logic/admin/server/resetSortWithNodeLogic.go b/internal/logic/admin/server/resetSortWithNodeLogic.go index 3866f54..c44b9dd 100644 --- a/internal/logic/admin/server/resetSortWithNodeLogic.go +++ b/internal/logic/admin/server/resetSortWithNodeLogic.go @@ -80,7 +80,7 @@ func (l *ResetSortWithNodeLogic) ResetSortWithNode(req *types.ResetSortRequest) }) if err != nil { l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error()) + return errors.Wrap(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error()) } return nil } diff --git a/internal/logic/admin/server/resetSortWithServerLogic.go b/internal/logic/admin/server/resetSortWithServerLogic.go index 3fbe237..5851b13 100644 --- a/internal/logic/admin/server/resetSortWithServerLogic.go +++ b/internal/logic/admin/server/resetSortWithServerLogic.go @@ -80,7 +80,7 @@ func (l *ResetSortWithServerLogic) ResetSortWithServer(req *types.ResetSortReque }) if err != nil { l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error())) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error()) + return errors.Wrap(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error()) } return nil } diff --git a/internal/logic/common/promoEligibility.go b/internal/logic/common/promoEligibility.go index 85ef01f..e2edd9f 100644 --- a/internal/logic/common/promoEligibility.go +++ b/internal/logic/common/promoEligibility.go @@ -53,6 +53,7 @@ func (promoRule) TableName() string { type subscribePromo struct { Id int64 `gorm:"column:id" json:"id"` SubscribeId int64 `gorm:"column:subscribe_id" json:"subscribe_id"` + Quantity int64 `gorm:"column:quantity" json:"quantity"` PromoRuleId int64 `gorm:"column:promo_rule_id" json:"promo_rule_id"` PromoPrice int64 `gorm:"column:promo_price" json:"promo_price"` CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` @@ -77,12 +78,12 @@ type gormPromoEligibilitySource struct { db *gorm.DB } -func EvaluatePromo(ctx context.Context, svcCtx *svc.ServiceContext, userID int64, subscribeID int64) (*PromoResult, error) { +func EvaluatePromo(ctx context.Context, svcCtx *svc.ServiceContext, userID int64, subscribeID int64, quantity int64) (*PromoResult, error) { if svcCtx == nil || svcCtx.DB == nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "service context is empty") } - if userID <= 0 || subscribeID <= 0 { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "user id or subscribe id is empty") + if userID <= 0 || subscribeID <= 0 || quantity <= 0 { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "user id, subscribe id or quantity is empty") } rules, err := loadEnabledPromoRules(ctx, svcCtx) @@ -93,7 +94,7 @@ func EvaluatePromo(ctx context.Context, svcCtx *svc.ServiceContext, userID int64 return &PromoResult{}, nil } - prices, err := loadSubscribePromos(ctx, svcCtx, subscribeID) + prices, err := loadSubscribePromos(ctx, svcCtx, subscribeID, quantity) if err != nil { return nil, err } @@ -292,16 +293,14 @@ func loadEnabledPromoRules(ctx context.Context, svcCtx *svc.ServiceContext) ([]p return rules, nil } -func loadSubscribePromos(ctx context.Context, svcCtx *svc.ServiceContext, subscribeID int64) ([]subscribePromo, error) { - cacheKey := fmt.Sprintf("%s%d", promoSubscribeCachePrefix, subscribeID) +func loadSubscribePromos(ctx context.Context, svcCtx *svc.ServiceContext, subscribeID int64, quantity int64) ([]subscribePromo, error) { + cacheKey := fmt.Sprintf("%s%d:%d", promoSubscribeCachePrefix, subscribeID, quantity) if cached, ok := getPromoCache[[]subscribePromo](ctx, svcCtx, cacheKey); ok { return cached, nil } var promos []subscribePromo - if err := svcCtx.DB.WithContext(ctx). - Model(&subscribePromo{}). - Where("subscribe_id = ?", subscribeID). + if err := subscribePromoQuery(svcCtx.DB.WithContext(ctx), subscribeID, quantity). Find(&promos).Error; err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query subscribe promos failed") } @@ -310,6 +309,12 @@ func loadSubscribePromos(ctx context.Context, svcCtx *svc.ServiceContext, subscr return promos, nil } +func subscribePromoQuery(db *gorm.DB, subscribeID int64, quantity int64) *gorm.DB { + return db. + Model(&subscribePromo{}). + Where("subscribe_id = ? AND quantity = ?", subscribeID, quantity) +} + func getPromoCache[T any](ctx context.Context, svcCtx *svc.ServiceContext, key string) (T, bool) { var zero T if svcCtx == nil || svcCtx.Redis == nil { diff --git a/internal/logic/common/promoEligibility_test.go b/internal/logic/common/promoEligibility_test.go index b01f273..b4663b0 100644 --- a/internal/logic/common/promoEligibility_test.go +++ b/internal/logic/common/promoEligibility_test.go @@ -46,8 +46,8 @@ func TestEvaluatePromoRulesAtPriorityFirstMatch(t *testing.T) { }, } prices := []subscribePromo{ - {SubscribeId: 100, PromoRuleId: 1, PromoPrice: 599}, - {SubscribeId: 100, PromoRuleId: 2, PromoPrice: 499}, + {SubscribeId: 100, Quantity: 1, PromoRuleId: 1, PromoPrice: 599}, + {SubscribeId: 100, Quantity: 1, PromoRuleId: 2, PromoPrice: 499}, } got, err := evaluatePromoRulesAt(context.Background(), rules, prices, fakePromoEligibilitySource{ @@ -76,11 +76,11 @@ func TestEvaluatePromoRulesAtSkipsUnavailableRules(t *testing.T) { {Id: 5, Type: PromoRuleTypeCampaign, Enabled: true}, } prices := []subscribePromo{ - {SubscribeId: 100, PromoRuleId: 1, PromoPrice: 100}, - {SubscribeId: 100, PromoRuleId: 2, PromoPrice: 100}, - {SubscribeId: 100, PromoRuleId: 3, PromoPrice: 100}, - {SubscribeId: 100, PromoRuleId: 4, PromoPrice: 100}, - {SubscribeId: 100, PromoRuleId: 5, PromoPrice: 88}, + {SubscribeId: 100, Quantity: 1, PromoRuleId: 1, PromoPrice: 100}, + {SubscribeId: 100, Quantity: 1, PromoRuleId: 2, PromoPrice: 100}, + {SubscribeId: 100, Quantity: 1, PromoRuleId: 3, PromoPrice: 100}, + {SubscribeId: 100, Quantity: 1, PromoRuleId: 4, PromoPrice: 100}, + {SubscribeId: 100, Quantity: 1, PromoRuleId: 5, PromoPrice: 88}, } got, err := evaluatePromoRulesAt(context.Background(), rules, prices, fakePromoEligibilitySource{}, 10, now) @@ -176,3 +176,32 @@ func TestLastSubscribeExpireAtIncludesUnlimitedSubscription(t *testing.T) { t.Fatalf("last subscribe query should prioritize unlimited subscription, sql: %s", stmt.SQL.String()) } } + +func TestSubscribePromoQueryMatchesQuantity(t *testing.T) { + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: "gorm:password@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local", + SkipInitializeWithVersion: true, + }), &gorm.Config{ + DryRun: true, + DisableAutomaticPing: true, + Logger: logger.Default.LogMode(logger.Silent), + }) + if err != nil { + t.Fatalf("open gorm db: %v", err) + } + + stmt := subscribePromoQuery(db, 100, 3).Find(&[]subscribePromo{}).Statement + sql := strings.ToLower(stmt.SQL.String()) + if !strings.Contains(sql, "subscribe_id = ?") { + t.Fatalf("subscribe promo query should filter subscribe_id, sql: %s", stmt.SQL.String()) + } + if !strings.Contains(sql, "quantity = ?") { + t.Fatalf("subscribe promo query should filter quantity, sql: %s", stmt.SQL.String()) + } + if got, want := len(stmt.Vars), 2; got != want { + t.Fatalf("query vars len = %d, want %d, vars: %#v", got, want, stmt.Vars) + } + if stmt.Vars[0] != int64(100) || stmt.Vars[1] != int64(3) { + t.Fatalf("query vars = %#v, want subscribe_id=100 quantity=3", stmt.Vars) + } +} diff --git a/internal/logic/public/user/ws/deviceWsConnectLogic.go b/internal/logic/public/user/ws/deviceWsConnectLogic.go index 9e56e12..24f3adb 100644 --- a/internal/logic/public/user/ws/deviceWsConnectLogic.go +++ b/internal/logic/public/user/ws/deviceWsConnectLogic.go @@ -46,7 +46,7 @@ func (l *DeviceWsConnectLogic) DeviceWsConnect(c *gin.Context) error { _, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, identifier) if err != nil && !sysErr.Is(err, gorm.ErrRecordNotFound) { l.Errorf("DeviceWsConnectLogic DeviceWsConnect FindOneDeviceByIdentifier err: %v", err) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error()) + return errors.Wrap(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error()) } value = l.ctx.Value(constant.CtxKeyUser) @@ -67,7 +67,7 @@ func (l *DeviceWsConnectLogic) DeviceWsConnect(c *gin.Context) error { err := l.svcCtx.UserModel.InsertDevice(l.ctx, &device) if err != nil { l.Errorf("DeviceWsConnectLogic DeviceWsConnect InsertDevice err: %v", err) - return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), err.Error()) + return errors.Wrap(xerr.NewErrCode(xerr.DatabaseInsertError), err.Error()) } } //默认在线设备1 diff --git a/scripts/convert_recovery_orders.go b/scripts/convert_recovery_orders.go index e2d0e23..6f19e56 100644 --- a/scripts/convert_recovery_orders.go +++ b/scripts/convert_recovery_orders.go @@ -1,3 +1,5 @@ +//go:build tools + package main import ( diff --git a/scripts/reconcile_mihapay_orders.go b/scripts/reconcile_mihapay_orders.go index aba8fb3..a9dec01 100644 --- a/scripts/reconcile_mihapay_orders.go +++ b/scripts/reconcile_mihapay_orders.go @@ -1,3 +1,5 @@ +//go:build tools + package main import (