bindbox-game/internal/service/activity/reward_effects_service.go
邹方成 16e2ede037 feat: 新增订单列表筛选条件与活动信息展示
refactor(orders): 重构订单列表查询逻辑,支持按消耗状态筛选
feat(orders): 订单列表返回新增活动分类与玩法类型信息
fix(orders): 修复订单支付时间空指针问题
docs(swagger): 更新订单相关接口文档
test(matching): 添加对对碰奖励匹配测试用例
chore: 清理无用脚本文件
2025-12-22 15:15:18 +08:00

266 lines
8.3 KiB
Go

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
}