refactor(orders): 重构订单列表查询逻辑,支持按消耗状态筛选 feat(orders): 订单列表返回新增活动分类与玩法类型信息 fix(orders): 修复订单支付时间空指针问题 docs(swagger): 更新订单相关接口文档 test(matching): 添加对对碰奖励匹配测试用例 chore: 清理无用脚本文件
266 lines
8.3 KiB
Go
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
|
|
}
|