@@ -29,7 +29,7 @@ func (l *DeleteSubscribeApplicationLogic) DeleteSubscribeApplication(req *types.
|
|||||||
err := l.svcCtx.ClientModel.Delete(l.ctx, req.Id)
|
err := l.svcCtx.ClientModel.Delete(l.ctx, req.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("Failed to delete subscribe application with ID %d: %v", req.Id, err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func (l *ResetSortWithNodeLogic) ResetSortWithNode(req *types.ResetSortRequest)
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error()))
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func (l *ResetSortWithServerLogic) ResetSortWithServer(req *types.ResetSortReque
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error()))
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ func (promoRule) TableName() string {
|
|||||||
type subscribePromo struct {
|
type subscribePromo struct {
|
||||||
Id int64 `gorm:"column:id" json:"id"`
|
Id int64 `gorm:"column:id" json:"id"`
|
||||||
SubscribeId int64 `gorm:"column:subscribe_id" json:"subscribe_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"`
|
PromoRuleId int64 `gorm:"column:promo_rule_id" json:"promo_rule_id"`
|
||||||
PromoPrice int64 `gorm:"column:promo_price" json:"promo_price"`
|
PromoPrice int64 `gorm:"column:promo_price" json:"promo_price"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||||
@@ -77,12 +78,12 @@ type gormPromoEligibilitySource struct {
|
|||||||
db *gorm.DB
|
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 {
|
if svcCtx == nil || svcCtx.DB == nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "service context is empty")
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "service context is empty")
|
||||||
}
|
}
|
||||||
if userID <= 0 || subscribeID <= 0 {
|
if userID <= 0 || subscribeID <= 0 || quantity <= 0 {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "user id or subscribe id is empty")
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "user id, subscribe id or quantity is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
rules, err := loadEnabledPromoRules(ctx, svcCtx)
|
rules, err := loadEnabledPromoRules(ctx, svcCtx)
|
||||||
@@ -93,7 +94,7 @@ func EvaluatePromo(ctx context.Context, svcCtx *svc.ServiceContext, userID int64
|
|||||||
return &PromoResult{}, nil
|
return &PromoResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
prices, err := loadSubscribePromos(ctx, svcCtx, subscribeID)
|
prices, err := loadSubscribePromos(ctx, svcCtx, subscribeID, quantity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -292,16 +293,14 @@ func loadEnabledPromoRules(ctx context.Context, svcCtx *svc.ServiceContext) ([]p
|
|||||||
return rules, nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadSubscribePromos(ctx context.Context, svcCtx *svc.ServiceContext, subscribeID int64) ([]subscribePromo, error) {
|
func loadSubscribePromos(ctx context.Context, svcCtx *svc.ServiceContext, subscribeID int64, quantity int64) ([]subscribePromo, error) {
|
||||||
cacheKey := fmt.Sprintf("%s%d", promoSubscribeCachePrefix, subscribeID)
|
cacheKey := fmt.Sprintf("%s%d:%d", promoSubscribeCachePrefix, subscribeID, quantity)
|
||||||
if cached, ok := getPromoCache[[]subscribePromo](ctx, svcCtx, cacheKey); ok {
|
if cached, ok := getPromoCache[[]subscribePromo](ctx, svcCtx, cacheKey); ok {
|
||||||
return cached, nil
|
return cached, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var promos []subscribePromo
|
var promos []subscribePromo
|
||||||
if err := svcCtx.DB.WithContext(ctx).
|
if err := subscribePromoQuery(svcCtx.DB.WithContext(ctx), subscribeID, quantity).
|
||||||
Model(&subscribePromo{}).
|
|
||||||
Where("subscribe_id = ?", subscribeID).
|
|
||||||
Find(&promos).Error; err != nil {
|
Find(&promos).Error; err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query subscribe promos failed")
|
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
|
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) {
|
func getPromoCache[T any](ctx context.Context, svcCtx *svc.ServiceContext, key string) (T, bool) {
|
||||||
var zero T
|
var zero T
|
||||||
if svcCtx == nil || svcCtx.Redis == nil {
|
if svcCtx == nil || svcCtx.Redis == nil {
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ func TestEvaluatePromoRulesAtPriorityFirstMatch(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
prices := []subscribePromo{
|
prices := []subscribePromo{
|
||||||
{SubscribeId: 100, PromoRuleId: 1, PromoPrice: 599},
|
{SubscribeId: 100, Quantity: 1, PromoRuleId: 1, PromoPrice: 599},
|
||||||
{SubscribeId: 100, PromoRuleId: 2, PromoPrice: 499},
|
{SubscribeId: 100, Quantity: 1, PromoRuleId: 2, PromoPrice: 499},
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := evaluatePromoRulesAt(context.Background(), rules, prices, fakePromoEligibilitySource{
|
got, err := evaluatePromoRulesAt(context.Background(), rules, prices, fakePromoEligibilitySource{
|
||||||
@@ -76,11 +76,11 @@ func TestEvaluatePromoRulesAtSkipsUnavailableRules(t *testing.T) {
|
|||||||
{Id: 5, Type: PromoRuleTypeCampaign, Enabled: true},
|
{Id: 5, Type: PromoRuleTypeCampaign, Enabled: true},
|
||||||
}
|
}
|
||||||
prices := []subscribePromo{
|
prices := []subscribePromo{
|
||||||
{SubscribeId: 100, PromoRuleId: 1, PromoPrice: 100},
|
{SubscribeId: 100, Quantity: 1, PromoRuleId: 1, PromoPrice: 100},
|
||||||
{SubscribeId: 100, PromoRuleId: 2, PromoPrice: 100},
|
{SubscribeId: 100, Quantity: 1, PromoRuleId: 2, PromoPrice: 100},
|
||||||
{SubscribeId: 100, PromoRuleId: 3, PromoPrice: 100},
|
{SubscribeId: 100, Quantity: 1, PromoRuleId: 3, PromoPrice: 100},
|
||||||
{SubscribeId: 100, PromoRuleId: 4, PromoPrice: 100},
|
{SubscribeId: 100, Quantity: 1, PromoRuleId: 4, PromoPrice: 100},
|
||||||
{SubscribeId: 100, PromoRuleId: 5, PromoPrice: 88},
|
{SubscribeId: 100, Quantity: 1, PromoRuleId: 5, PromoPrice: 88},
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := evaluatePromoRulesAt(context.Background(), rules, prices, fakePromoEligibilitySource{}, 10, now)
|
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())
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (l *DeviceWsConnectLogic) DeviceWsConnect(c *gin.Context) error {
|
|||||||
_, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, identifier)
|
_, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, identifier)
|
||||||
if err != nil && !sysErr.Is(err, gorm.ErrRecordNotFound) {
|
if err != nil && !sysErr.Is(err, gorm.ErrRecordNotFound) {
|
||||||
l.Errorf("DeviceWsConnectLogic DeviceWsConnect FindOneDeviceByIdentifier err: %v", err)
|
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)
|
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)
|
err := l.svcCtx.UserModel.InsertDevice(l.ctx, &device)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("DeviceWsConnectLogic DeviceWsConnect InsertDevice err: %v", err)
|
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
|
//默认在线设备1
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build tools
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build tools
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
Reference in New Issue
Block a user