package activity import ( "bindbox-game/internal/pkg/logger" "bindbox-game/internal/repository/mysql" "bindbox-game/internal/repository/mysql/dao" "bindbox-game/internal/repository/mysql/model" usersvc "bindbox-game/internal/service/user" "context" "crypto/rand" "encoding/binary" "fmt" "time" ) // RewardEffectsService 奖励效果服务 // 统一处理奖励发放和道具卡效果应用 type RewardEffectsService interface { // GrantRewardWithEffects 发放奖励并应用道具卡效果 GrantRewardWithEffects(ctx context.Context, req GrantRewardRequest) (*GrantRewardResult, error) } // GrantRewardRequest 奖励发放请求 type GrantRewardRequest struct { UserID int64 // 用户ID OrderID int64 // 订单ID ActivityID int64 // 活动ID IssueID int64 // 期ID Reward *model.ActivityRewardSettings // 要发放的奖励 AllRewards []*model.ActivityRewardSettings // 所有可用奖励(用于概率提升升级) } // GrantRewardResult 奖励发放结果 type GrantRewardResult struct { RewardID int64 // 发放的奖励ID RewardName string // 奖励名称 ItemCardApplied bool // 是否应用了道具卡效果 UpgradedReward *model.ActivityRewardSettings // 如果概率提升成功,升级后的奖励 DrawLogID int64 // 创建的抽奖日志ID } type rewardEffectsService struct { logger logger.CustomLogger readDB *dao.Query writeDB *dao.Query repo mysql.Repo user usersvc.Service } // NewRewardEffectsService 创建奖励效果服务 func NewRewardEffectsService(l logger.CustomLogger, db mysql.Repo) RewardEffectsService { return &rewardEffectsService{ logger: l, readDB: dao.Use(db.GetDbR()), writeDB: dao.Use(db.GetDbW()), repo: db, user: usersvc.New(l, db), } } // GrantRewardWithEffects 发放奖励并应用道具卡效果 func (s *rewardEffectsService) GrantRewardWithEffects(ctx context.Context, req GrantRewardRequest) (*GrantRewardResult, error) { if req.Reward == nil { return nil, fmt.Errorf("reward is nil") } result := &GrantRewardResult{ RewardID: req.Reward.ID, RewardName: req.Reward.Name, } // 1. 扣减库存 res, err := s.writeDB.ActivityRewardSettings.WithContext(ctx).Where( s.writeDB.ActivityRewardSettings.ID.Eq(req.Reward.ID), s.writeDB.ActivityRewardSettings.Quantity.Gt(0), ).UpdateSimple(s.writeDB.ActivityRewardSettings.Quantity.Add(-1)) if err != nil { return nil, err } if res.RowsAffected == 0 { return nil, fmt.Errorf("reward out of stock") } // 2. 发放奖励到订单 rid := req.Reward.ID _, err = s.user.GrantRewardToOrder(ctx, req.UserID, usersvc.GrantRewardToOrderRequest{ OrderID: req.OrderID, ProductID: req.Reward.ProductID, Quantity: 1, ActivityID: &req.ActivityID, RewardID: &rid, Remark: req.Reward.Name, }) if err != nil { return nil, err } // 3. 创建抽奖日志 drawLog := &model.ActivityDrawLogs{ UserID: req.UserID, IssueID: req.IssueID, OrderID: req.OrderID, RewardID: req.Reward.ID, IsWinner: 1, Level: req.Reward.Level, CurrentLevel: 1, CreatedAt: time.Now(), } if err := s.writeDB.ActivityDrawLogs.WithContext(ctx).Create(drawLog); err != nil { return nil, err } result.DrawLogID = drawLog.ID // 4. 从订单备注解析道具卡ID并应用效果 ord, _ := s.readDB.Orders.WithContext(ctx).Where(s.readDB.Orders.ID.Eq(req.OrderID)).First() if ord != nil { icID := parseItemCardIDFromRemark(ord.Remark) if icID > 0 { applied, upgradedReward := s.applyItemCardEffects(ctx, req, icID, drawLog.ID) result.ItemCardApplied = applied result.UpgradedReward = upgradedReward } } return result, nil } // applyItemCardEffects 应用道具卡效果 func (s *rewardEffectsService) applyItemCardEffects(ctx context.Context, req GrantRewardRequest, icID int64, drawLogID int64) (bool, *model.ActivityRewardSettings) { fmt.Printf("[道具卡-RewardEffects] 从订单备注解析道具卡ID icID=%d\n", icID) uic, _ := s.readDB.UserItemCards.WithContext(ctx).Where( s.readDB.UserItemCards.ID.Eq(icID), s.readDB.UserItemCards.UserID.Eq(req.UserID), s.readDB.UserItemCards.Status.Eq(1), ).First() if uic == nil { fmt.Printf("[道具卡-RewardEffects] ❌ 未找到用户道具卡 用户ID=%d 道具卡ID=%d\n", req.UserID, icID) return false, nil } ic, _ := s.readDB.SystemItemCards.WithContext(ctx).Where( s.readDB.SystemItemCards.ID.Eq(uic.CardID), s.readDB.SystemItemCards.Status.Eq(1), ).First() if ic == nil { fmt.Printf("[道具卡-RewardEffects] ❌ 未找到系统道具卡 CardID=%d\n", uic.CardID) return false, nil } now := time.Now() if uic.ValidStart.After(now) || uic.ValidEnd.Before(now) { fmt.Printf("[道具卡-RewardEffects] ❌ 道具卡不在有效期\n") return false, nil } // 范围检查 scopeOK := (ic.ScopeType == 1) || (ic.ScopeType == 3 && ic.ActivityID == req.ActivityID) || (ic.ScopeType == 4 && ic.IssueID == req.IssueID) if !scopeOK { fmt.Printf("[道具卡-RewardEffects] ❌ 范围检查失败 ScopeType=%d\n", ic.ScopeType) return false, nil } var upgradedReward *model.ActivityRewardSettings // 应用效果 if ic.EffectType == 1 && ic.RewardMultiplierX1000 >= 2000 { // 双倍奖励 fmt.Printf("[道具卡-RewardEffects] ✅ 应用双倍奖励 倍数=%d 奖品ID=%d 奖品名=%s\n", ic.RewardMultiplierX1000, req.Reward.ID, req.Reward.Name) rid := req.Reward.ID _, _ = s.user.GrantRewardToOrder(ctx, req.UserID, usersvc.GrantRewardToOrderRequest{ OrderID: req.OrderID, ProductID: req.Reward.ProductID, Quantity: 1, ActivityID: &req.ActivityID, RewardID: &rid, Remark: req.Reward.Name + "(倍数)", }) } else if ic.EffectType == 2 && ic.BoostRateX1000 > 0 { // 概率提升 - 尝试升级到更好的奖励 fmt.Printf("[道具卡-RewardEffects] 应用概率提升 BoostRateX1000=%d\n", ic.BoostRateX1000) var better *model.ActivityRewardSettings for _, r := range req.AllRewards { if r.MinScore > req.Reward.MinScore && r.Quantity > 0 { if better == nil || r.MinScore < better.MinScore { better = r } } } if better != nil { randBytes := make([]byte, 4) rand.Read(randBytes) randVal := int32(binary.BigEndian.Uint32(randBytes) % 1000) if randVal < ic.BoostRateX1000 { fmt.Printf("[道具卡-RewardEffects] ✅ 概率提升成功 升级到奖品ID=%d 奖品名=%s\n", better.ID, better.Name) rid := better.ID _, _ = s.user.GrantRewardToOrder(ctx, req.UserID, usersvc.GrantRewardToOrderRequest{ OrderID: req.OrderID, ProductID: better.ProductID, Quantity: 1, ActivityID: &req.ActivityID, RewardID: &rid, Remark: better.Name + "(升级)", }) upgradedReward = better } } } // 核销道具卡 fmt.Printf("[道具卡-RewardEffects] 核销道具卡 用户道具卡ID=%d\n", icID) _, _ = s.writeDB.UserItemCards.WithContext(ctx).Where( s.writeDB.UserItemCards.ID.Eq(icID), s.writeDB.UserItemCards.UserID.Eq(req.UserID), s.writeDB.UserItemCards.Status.Eq(1), ).Updates(map[string]any{ s.writeDB.UserItemCards.Status.ColumnName().String(): 2, s.writeDB.UserItemCards.UsedDrawLogID.ColumnName().String(): drawLogID, s.writeDB.UserItemCards.UsedActivityID.ColumnName().String(): req.ActivityID, s.writeDB.UserItemCards.UsedIssueID.ColumnName().String(): req.IssueID, s.writeDB.UserItemCards.UsedAt.ColumnName().String(): now, }) return true, upgradedReward } // parseItemCardIDFromRemark 从订单备注解析道具卡ID func parseItemCardIDFromRemark(remark string) int64 { if remark == "" { return 0 } // 查找 |itemcard:xxx 模式 prefix := "|itemcard:" idx := -1 for i := 0; i <= len(remark)-len(prefix); i++ { if remark[i:i+len(prefix)] == prefix { idx = i + len(prefix) break } } if idx < 0 { // 也检查开头没有 | 的情况 prefix = "itemcard:" for i := 0; i <= len(remark)-len(prefix); i++ { if remark[i:i+len(prefix)] == prefix { idx = i + len(prefix) break } } } if idx < 0 { return 0 } var n int64 for idx < len(remark) { c := remark[idx] if c < '0' || c > '9' { break } n = n*10 + int64(c-'0') idx++ } return n }