package douyin import ( "context" "encoding/json" "fmt" "time" "bindbox-game/internal/repository/mysql/model" "bindbox-game/internal/service/game" "bindbox-game/internal/service/user" ) // TitleAssigner 称号发放接口(只需要 AssignUserTitle 方法) type TitleAssigner interface { AssignUserTitle(ctx context.Context, userID int64, titleID int64, expiresAt *time.Time, remark string) error } // RewardPayload 奖励参数结构 type RewardPayload struct { // game_ticket GameCode string `json:"game_code,omitempty"` // points Points int64 `json:"points,omitempty"` // coupon CouponID int64 `json:"coupon_id,omitempty"` // product ProductID int64 `json:"product_id,omitempty"` // item_card CardID int64 `json:"card_id,omitempty"` // title TitleID int64 `json:"title_id,omitempty"` } // RewardDispatcher 奖励发放器 type RewardDispatcher struct { ticketSvc game.TicketService userSvc user.Service titleSvc TitleAssigner } // NewRewardDispatcher 创建奖励发放器 func NewRewardDispatcher(ticketSvc game.TicketService, userSvc user.Service, titleSvc TitleAssigner) *RewardDispatcher { return &RewardDispatcher{ ticketSvc: ticketSvc, userSvc: userSvc, titleSvc: titleSvc, } } // GrantReward 根据奖励配置发放奖励 // userID: 用户ID // reward: 奖励配置 // productCount: 商品数量(会乘以 reward.Quantity) // source: 来源标识 // sourceID: 来源ID(如订单ID) // extOrderID: 外部订单号(如抖店单号) func (d *RewardDispatcher) GrantReward(ctx context.Context, userID int64, reward model.DouyinProductRewards, productCount int, source string, sourceID int64, extOrderID string) error { if reward.Status != 1 { return nil // 规则未启用,跳过 } // 解析 payload var payload RewardPayload if reward.RewardPayload != "" { _ = json.Unmarshal([]byte(reward.RewardPayload), &payload) } totalQuantity := int(reward.Quantity) * productCount if totalQuantity <= 0 { totalQuantity = productCount } remark := fmt.Sprintf("购买商品奖励 (规则ID: %d)", reward.ID) switch reward.RewardType { case "game_ticket": // 发放游戏资格 gameCode := payload.GameCode if gameCode == "" { gameCode = "minesweeper" // 默认扫雷 } return d.ticketSvc.GrantTicket(ctx, userID, gameCode, totalQuantity, source, sourceID, remark) case "points": // 发放积分 points := payload.Points if points <= 0 { points = 1 } totalPoints := points * int64(totalQuantity) return d.userSvc.AddPointsWithAction(ctx, userID, totalPoints, "order_reward", remark, "douyin_product_reward", nil, nil) case "coupon": // 发放优惠券 if payload.CouponID <= 0 { return fmt.Errorf("coupon_id not configured") } for i := 0; i < totalQuantity; i++ { if err := d.userSvc.AddCoupon(ctx, userID, payload.CouponID); err != nil { return err } } return nil case "product": // 发放商品到用户库存 if payload.ProductID <= 0 { return fmt.Errorf("product_id not configured") } _, err := d.userSvc.GrantReward(ctx, userID, user.GrantRewardRequest{ ProductID: payload.ProductID, Quantity: totalQuantity, Remark: remark, ExtOrderID: extOrderID, }) return err case "item_card": // 发放道具卡 if payload.CardID <= 0 { return fmt.Errorf("card_id not configured") } return d.userSvc.AddItemCard(ctx, userID, payload.CardID, totalQuantity) case "title": // 发放称号(称号不支持数量叠加,只发一次) if payload.TitleID <= 0 { return fmt.Errorf("title_id not configured") } return d.titleSvc.AssignUserTitle(ctx, userID, payload.TitleID, nil, remark) default: return fmt.Errorf("unknown reward type: %s", reward.RewardType) } } // IsFlipCardReward 判断是否为翻牌游戏奖励(需要特殊处理,不自动发放) func (d *RewardDispatcher) IsFlipCardReward(reward model.DouyinProductRewards) bool { if reward.RewardType != "game_ticket" { return false } var payload RewardPayload if reward.RewardPayload != "" { _ = json.Unmarshal([]byte(reward.RewardPayload), &payload) } return payload.GameCode == "flip_card" }