fix(activity): 修复对对碰按配置发放复数奖品
修复对对碰开奖固定发放单个奖品的问题,改为按奖励配置数量发放,并统一手动结算与自动开奖逻辑。
This commit is contained in:
parent
79f2c2236f
commit
cb5061f1da
@ -2,8 +2,6 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -372,7 +370,6 @@ func (h *handler) CheckMatchingGame() core.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Concurrency Lock: Prevent multiple check requests for the same game
|
||||
lockKey := fmt.Sprintf("lock:matching_game:check:%s", req.GameID)
|
||||
locked, err := h.redis.SetNX(ctx.RequestContext(), lockKey, "1", 10*time.Second).Result()
|
||||
if err != nil {
|
||||
@ -396,9 +393,6 @@ func (h *handler) CheckMatchingGame() core.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// 校验:不能超过理论最大对数
|
||||
// 【关键校验】检查订单是否已支付
|
||||
// 对对碰游戏必须先支付才能结算和发奖
|
||||
order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(game.OrderID)).First()
|
||||
if err != nil || order == nil {
|
||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 170003, "订单不存在"))
|
||||
@ -412,7 +406,6 @@ func (h *handler) CheckMatchingGame() core.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查活动状态
|
||||
activity, err := h.activity.GetActivity(ctx.RequestContext(), game.ActivityID)
|
||||
if err != nil || activity == nil {
|
||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 170001, "活动不存在"))
|
||||
@ -423,7 +416,6 @@ func (h *handler) CheckMatchingGame() core.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// 【核心安全校验】使用服务端模拟计算实际对数,不信任客户端提交的值
|
||||
serverSimulatedPairs := game.SimulateMaxPairs()
|
||||
h.logger.Debug("对对碰Check: 服务端模拟验证",
|
||||
zap.Int64("client_pairs", req.TotalPairs),
|
||||
@ -432,11 +424,7 @@ func (h *handler) CheckMatchingGame() core.HandlerFunc {
|
||||
zap.String("position", game.Position),
|
||||
zap.String("game_id", req.GameID))
|
||||
|
||||
// 使用服务端模拟的对数,而非客户端提交的值
|
||||
// 这样即使客户端伪造数据也无法作弊
|
||||
actualPairs := serverSimulatedPairs
|
||||
|
||||
// 如果客户端提交的值与服务端模拟不一致,记录警告日志(可能是作弊尝试)
|
||||
if req.TotalPairs != serverSimulatedPairs {
|
||||
h.logger.Warn("对对碰Check: 客户端提交数值与服务端模拟不一致",
|
||||
zap.Int64("client_pairs", req.TotalPairs),
|
||||
@ -444,220 +432,38 @@ func (h *handler) CheckMatchingGame() core.HandlerFunc {
|
||||
zap.String("game_id", req.GameID))
|
||||
}
|
||||
|
||||
game.TotalPairs = actualPairs // 使用服务端验证后的值
|
||||
game.TotalPairs = actualPairs
|
||||
var rewardInfo *MatchingRewardInfo
|
||||
|
||||
// 【幂等性检查】在发奖前检查该订单是否已经获得过奖励
|
||||
existingLog, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(
|
||||
h.readDB.ActivityDrawLogs.OrderID.Eq(game.OrderID),
|
||||
h.readDB.ActivityDrawLogs.IsWinner.Eq(1),
|
||||
).First()
|
||||
if existingLog != nil {
|
||||
h.logger.Warn("对对碰Check: 订单已获得过奖励,拒绝重复发放",
|
||||
zap.Int64("order_id", game.OrderID),
|
||||
zap.Int64("existing_log_id", existingLog.ID))
|
||||
// 返回已有的奖励信息而不是重复发放
|
||||
if existingLog.RewardID > 0 {
|
||||
rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(
|
||||
h.readDB.ActivityRewardSettings.ID.Eq(existingLog.RewardID)).First()
|
||||
if rw != nil {
|
||||
prodName := ""
|
||||
prodImage := ""
|
||||
if p, _ := h.readDB.Products.WithContext(ctx.RequestContext()).Where(h.readDB.Products.ID.Eq(rw.ProductID)).First(); p != nil {
|
||||
prodName = p.Name
|
||||
prodImage = getFirstImage(p.ImagesJSON)
|
||||
}
|
||||
rewardInfo = &MatchingRewardInfo{
|
||||
RewardID: rw.ID,
|
||||
Name: prodName,
|
||||
ProductName: prodName,
|
||||
ProductImage: prodImage,
|
||||
Level: rw.Level,
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.Payload(&matchingGameCheckResponse{
|
||||
GameID: req.GameID,
|
||||
TotalPairs: req.TotalPairs,
|
||||
Finished: true,
|
||||
Reward: rewardInfo,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Fetch Rewards
|
||||
rewards, err := h.activity.ListIssueRewards(ctx.RequestContext(), game.IssueID)
|
||||
|
||||
if err == nil && len(rewards) > 0 {
|
||||
// 2. Filter & Sort
|
||||
var candidate *model.ActivityRewardSettings
|
||||
for _, r := range rewards {
|
||||
if r.Quantity <= 0 {
|
||||
continue
|
||||
}
|
||||
// 精确匹配:服务端验证的对子数 == 奖品设置的对子数
|
||||
if actualPairs == r.MinScore {
|
||||
candidate = r
|
||||
break // 找到精确匹配,直接使用
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if candidate != nil {
|
||||
// 3. Prepare Grant Params
|
||||
// Fetch real product name for remark
|
||||
productName := ""
|
||||
if p, _ := h.readDB.Products.WithContext(ctx.RequestContext()).Where(h.readDB.Products.ID.Eq(candidate.ProductID)).First(); p != nil {
|
||||
productName = p.Name
|
||||
}
|
||||
|
||||
finalReward := candidate
|
||||
finalQuantity := 1
|
||||
finalRemark := fmt.Sprintf("%s %s", order.OrderNo, productName)
|
||||
var cardToVoid int64 = 0
|
||||
|
||||
// 4. Apply Item Card Effects (Determine final reward and quantity)
|
||||
icID := parseItemCardIDFromRemark(order.Remark)
|
||||
h.logger.Debug("CheckMatchingGame: 道具卡检查",
|
||||
zap.String("order_no", order.OrderNo),
|
||||
zap.String("remark", order.Remark),
|
||||
zap.Int64("icID", icID))
|
||||
|
||||
if icID > 0 {
|
||||
uic, _ := h.readDB.UserItemCards.WithContext(ctx.RequestContext()).Where(
|
||||
h.readDB.UserItemCards.ID.Eq(icID),
|
||||
h.readDB.UserItemCards.UserID.Eq(game.UserID),
|
||||
).First()
|
||||
if uic == nil {
|
||||
h.logger.Warn("CheckMatchingGame: 用户道具卡未找到", zap.Int64("icID", icID), zap.Int64("user_id", game.UserID))
|
||||
} else if uic.Status != 1 {
|
||||
h.logger.Warn("CheckMatchingGame: 用户道具卡状态无效", zap.Int32("status", uic.Status))
|
||||
} else { // Status == 1
|
||||
ic, _ := h.readDB.SystemItemCards.WithContext(ctx.RequestContext()).Where(
|
||||
h.readDB.SystemItemCards.ID.Eq(uic.CardID),
|
||||
h.readDB.SystemItemCards.Status.Eq(1),
|
||||
).First()
|
||||
now := time.Now()
|
||||
if ic != nil && !uic.ValidStart.After(now) && !uic.ValidEnd.Before(now) {
|
||||
scopeOK := (ic.ScopeType == 1) || (ic.ScopeType == 3 && ic.ActivityID == game.ActivityID) || (ic.ScopeType == 4 && ic.IssueID == game.IssueID)
|
||||
h.logger.Debug("道具卡-CheckMatchingGame: 范围检查",
|
||||
zap.Int32("scope_type", ic.ScopeType),
|
||||
zap.Int64("activity_id", game.ActivityID),
|
||||
zap.Int64("issue_id", game.IssueID),
|
||||
zap.Bool("is_ok", scopeOK))
|
||||
|
||||
if scopeOK {
|
||||
// Fix: Don't set cardToVoid immediately. Only set it if an effect is actually applied.
|
||||
|
||||
// Double reward
|
||||
if ic.EffectType == 1 && ic.RewardMultiplierX1000 >= 2000 {
|
||||
cardToVoid = icID // Mark for consumption
|
||||
h.logger.Info("道具卡-CheckMatchingGame: 应用双倍奖励", zap.Int32("multiplier", ic.RewardMultiplierX1000))
|
||||
finalQuantity = 2
|
||||
finalRemark += "(倍数)"
|
||||
} else if ic.EffectType == 2 && ic.BoostRateX1000 > 0 {
|
||||
// Probability boost
|
||||
cardToVoid = icID // Mark for consumption (even if RNG fails, the card is "used")
|
||||
|
||||
h.logger.Debug("道具卡-CheckMatchingGame: 应用概率提升", zap.Int32("boost_rate", ic.BoostRateX1000))
|
||||
allRewards, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(
|
||||
h.readDB.ActivityRewardSettings.IssueID.Eq(game.IssueID),
|
||||
).Find()
|
||||
var better *model.ActivityRewardSettings
|
||||
for _, r := range allRewards {
|
||||
if r.MinScore > candidate.MinScore && r.Quantity > 0 {
|
||||
if better == nil || r.MinScore < better.MinScore {
|
||||
better = r
|
||||
}
|
||||
}
|
||||
}
|
||||
if better != nil {
|
||||
// Use crypto/rand for secure random
|
||||
randBytes := make([]byte, 4)
|
||||
rand.Read(randBytes)
|
||||
randVal := int32(binary.BigEndian.Uint32(randBytes) % 1000)
|
||||
h.logger.Debug("道具卡-CheckMatchingGame: 概率检定",
|
||||
zap.Int32("rand", randVal),
|
||||
zap.Int32("threshold", ic.BoostRateX1000))
|
||||
|
||||
if randVal < ic.BoostRateX1000 {
|
||||
// 获取升级后的商品名称
|
||||
betterProdName := ""
|
||||
if better.ProductID > 0 {
|
||||
if bp, _ := h.readDB.Products.WithContext(ctx.RequestContext()).Where(h.readDB.Products.ID.Eq(better.ProductID)).First(); bp != nil {
|
||||
betterProdName = bp.Name
|
||||
}
|
||||
}
|
||||
h.logger.Info("道具卡-CheckMatchingGame: 概率提升成功",
|
||||
zap.Int64("new_reward_id", better.ID),
|
||||
zap.String("product_name", betterProdName))
|
||||
finalReward = better
|
||||
finalRemark = betterProdName + "(升级)"
|
||||
} else {
|
||||
h.logger.Debug("道具卡-CheckMatchingGame: 概率提升失败")
|
||||
}
|
||||
} else {
|
||||
h.logger.Debug("道具卡-CheckMatchingGame: 未找到更好的奖品可升级", zap.Int64("current_score", candidate.MinScore))
|
||||
}
|
||||
} else {
|
||||
// Effect not recognized or params too low
|
||||
h.logger.Warn("道具卡-CheckMatchingGame: 效果类型未知或参数无效,不消耗卡片",
|
||||
zap.Int32("effect_type", ic.EffectType),
|
||||
zap.Int32("multiplier", ic.RewardMultiplierX1000))
|
||||
}
|
||||
} else {
|
||||
h.logger.Debug("道具卡-CheckMatchingGame: 范围校验失败")
|
||||
}
|
||||
} else {
|
||||
h.logger.Debug("道具卡-CheckMatchingGame: 时间或系统卡状态无效",
|
||||
zap.Bool("has_ic", ic != nil),
|
||||
zap.Time("start", uic.ValidStart),
|
||||
zap.Time("end", uic.ValidEnd),
|
||||
zap.Time("now", now))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Grant Reward
|
||||
if err := h.grantRewardHelper(ctx.RequestContext(), game.UserID, game.OrderID, finalReward, finalQuantity, finalRemark); err != nil {
|
||||
plan, err := h.settleMatchingReward(ctx.RequestContext(), game, order, candidate, false)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to grant matching reward", zap.Int64("order_id", game.OrderID), zap.Error(err))
|
||||
} else {
|
||||
} else if plan != nil {
|
||||
prodImage := ""
|
||||
if p, _ := h.readDB.Products.WithContext(ctx.RequestContext()).Where(h.readDB.Products.ID.Eq(candidate.ProductID)).First(); p != nil {
|
||||
productName = p.Name
|
||||
if p, _ := h.readDB.Products.WithContext(ctx.RequestContext()).Where(h.readDB.Products.ID.Eq(plan.reward.ProductID)).First(); p != nil {
|
||||
prodImage = getFirstImage(p.ImagesJSON)
|
||||
}
|
||||
|
||||
rewardInfo = &MatchingRewardInfo{
|
||||
RewardID: finalReward.ID,
|
||||
Name: productName,
|
||||
ProductName: productName,
|
||||
RewardID: plan.reward.ID,
|
||||
Name: plan.rewardName,
|
||||
ProductName: plan.productName,
|
||||
ProductImage: prodImage,
|
||||
Level: finalReward.Level,
|
||||
}
|
||||
|
||||
// 6. Void Item Card (if used)
|
||||
if cardToVoid > 0 {
|
||||
h.logger.Info("道具卡-CheckMatchingGame: 核销道具卡", zap.Int64("uic_id", cardToVoid))
|
||||
now := time.Now()
|
||||
// Get DrawLog ID for the order
|
||||
drawLog, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(
|
||||
h.readDB.ActivityDrawLogs.OrderID.Eq(game.OrderID),
|
||||
).First()
|
||||
var drawLogID int64
|
||||
if drawLog != nil {
|
||||
drawLogID = drawLog.ID
|
||||
}
|
||||
_, _ = h.writeDB.UserItemCards.WithContext(ctx.RequestContext()).Where(
|
||||
h.writeDB.UserItemCards.ID.Eq(cardToVoid),
|
||||
h.writeDB.UserItemCards.UserID.Eq(game.UserID),
|
||||
h.writeDB.UserItemCards.Status.Eq(1),
|
||||
).Updates(map[string]any{
|
||||
h.writeDB.UserItemCards.Status.ColumnName().String(): 2,
|
||||
h.writeDB.UserItemCards.UsedDrawLogID.ColumnName().String(): drawLogID,
|
||||
h.writeDB.UserItemCards.UsedActivityID.ColumnName().String(): game.ActivityID,
|
||||
h.writeDB.UserItemCards.UsedIssueID.ColumnName().String(): game.IssueID,
|
||||
h.writeDB.UserItemCards.UsedAt.ColumnName().String(): now,
|
||||
})
|
||||
Level: plan.reward.Level,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -670,8 +476,6 @@ func (h *handler) CheckMatchingGame() core.HandlerFunc {
|
||||
Reward: rewardInfo,
|
||||
}
|
||||
|
||||
// 7. Virtual Shipping (Async)
|
||||
// Upload shipping info to WeChat (similar to Ichiban Kuji) so user can see "Shipped" status and reward info.
|
||||
rewardName := "无奖励"
|
||||
if rewardInfo != nil {
|
||||
rewardName = rewardInfo.Name
|
||||
@ -679,14 +483,12 @@ func (h *handler) CheckMatchingGame() core.HandlerFunc {
|
||||
|
||||
go func(orderID int64, orderNo string, userID int64, rName string) {
|
||||
bgCtx := context.Background()
|
||||
// 1. Get Payment Transaction
|
||||
tx, _ := h.readDB.PaymentTransactions.WithContext(bgCtx).Where(h.readDB.PaymentTransactions.OrderNo.Eq(orderNo)).First()
|
||||
if tx == nil || tx.TransactionID == "" {
|
||||
h.logger.Warn("CheckMatchingGame: No payment transaction found for shipping", zap.String("order_no", orderNo))
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Get User OpenID (Prioritize PayerOpenid from transaction)
|
||||
payerOpenid := tx.PayerOpenid
|
||||
if payerOpenid == "" {
|
||||
u, _ := h.readDB.Users.WithContext(bgCtx).Where(h.readDB.Users.ID.Eq(userID)).First()
|
||||
@ -695,13 +497,11 @@ func (h *handler) CheckMatchingGame() core.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Construct Item Desc
|
||||
itemsDesc := fmt.Sprintf("对对碰 %s 赏品: %s", orderNo, rName)
|
||||
if len(itemsDesc) > 120 {
|
||||
itemsDesc = itemsDesc[:120]
|
||||
}
|
||||
|
||||
// 4. Upload
|
||||
c := configs.Get()
|
||||
if err := wechat.UploadVirtualShippingForBackground(bgCtx, &wechat.WechatConfig{AppID: c.Wechat.AppID, AppSecret: c.Wechat.AppSecret}, tx.TransactionID, orderNo, payerOpenid, itemsDesc); err != nil {
|
||||
h.logger.Error("CheckMatchingGame: Failed to upload virtual shipping", zap.Error(err))
|
||||
@ -710,9 +510,7 @@ func (h *handler) CheckMatchingGame() core.HandlerFunc {
|
||||
}
|
||||
}(game.OrderID, order.OrderNo, game.UserID, rewardName)
|
||||
|
||||
// 结算完成,清理会话 (Delete from Redis)
|
||||
_ = h.redis.Del(ctx.RequestContext(), activitysvc.MatchingGameKeyPrefix+req.GameID)
|
||||
|
||||
ctx.Payload(rsp)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -16,6 +18,36 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func matchingRewardQuantity(reward *model.ActivityRewardSettings, isDoubled bool) int {
|
||||
if reward == nil {
|
||||
return 0
|
||||
}
|
||||
quantity := int(reward.DropQuantity)
|
||||
if quantity < 1 {
|
||||
quantity = 1
|
||||
}
|
||||
if isDoubled {
|
||||
quantity *= 2
|
||||
}
|
||||
return quantity
|
||||
}
|
||||
|
||||
func matchingRewardDisplayName(name string, isDoubled bool) string {
|
||||
if !isDoubled {
|
||||
return name
|
||||
}
|
||||
return name + "(翻倍)"
|
||||
}
|
||||
|
||||
type matchingSettlementPlan struct {
|
||||
reward *model.ActivityRewardSettings
|
||||
quantity int
|
||||
productName string
|
||||
rewardName string
|
||||
cardToVoid int64
|
||||
drawLogID int64
|
||||
}
|
||||
|
||||
// grantRewardHelper 发放奖励辅助函数
|
||||
func (h *handler) grantRewardHelper(ctx context.Context, userID, orderID int64, r *model.ActivityRewardSettings, quantity int, remark string) error {
|
||||
// 1. Grant to Order (Delegating stock check to user service)
|
||||
@ -51,6 +83,162 @@ func (h *handler) grantRewardHelper(ctx context.Context, userID, orderID int64,
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *handler) buildMatchingSettlementPlan(ctx context.Context, game *activitysvc.MatchingGame, order *model.Orders, candidate *model.ActivityRewardSettings) *matchingSettlementPlan {
|
||||
if candidate == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
productName := ""
|
||||
if p, _ := h.readDB.Products.WithContext(ctx).Where(h.readDB.Products.ID.Eq(candidate.ProductID)).First(); p != nil {
|
||||
productName = p.Name
|
||||
}
|
||||
|
||||
plan := &matchingSettlementPlan{
|
||||
reward: candidate,
|
||||
quantity: matchingRewardQuantity(candidate, false),
|
||||
productName: productName,
|
||||
rewardName: matchingRewardDisplayName(productName, false),
|
||||
}
|
||||
|
||||
icID := parseItemCardIDFromRemark(order.Remark)
|
||||
h.logger.Debug("MatchingSettlement: 道具卡检查",
|
||||
zap.String("order_no", order.OrderNo),
|
||||
zap.String("remark", order.Remark),
|
||||
zap.Int64("icID", icID))
|
||||
|
||||
if icID <= 0 {
|
||||
return plan
|
||||
}
|
||||
|
||||
uic, _ := h.readDB.UserItemCards.WithContext(ctx).Where(
|
||||
h.readDB.UserItemCards.ID.Eq(icID),
|
||||
h.readDB.UserItemCards.UserID.Eq(game.UserID),
|
||||
).First()
|
||||
if uic == nil || uic.Status != 1 {
|
||||
return plan
|
||||
}
|
||||
|
||||
ic, _ := h.readDB.SystemItemCards.WithContext(ctx).Where(
|
||||
h.readDB.SystemItemCards.ID.Eq(uic.CardID),
|
||||
h.readDB.SystemItemCards.Status.Eq(1),
|
||||
).First()
|
||||
now := time.Now()
|
||||
if ic == nil || uic.ValidStart.After(now) || uic.ValidEnd.Before(now) {
|
||||
return plan
|
||||
}
|
||||
|
||||
scopeOK := (ic.ScopeType == 1) || (ic.ScopeType == 3 && ic.ActivityID == game.ActivityID) || (ic.ScopeType == 4 && ic.IssueID == game.IssueID)
|
||||
if !scopeOK {
|
||||
return plan
|
||||
}
|
||||
|
||||
if ic.EffectType == 1 && ic.RewardMultiplierX1000 >= 2000 {
|
||||
plan.cardToVoid = icID
|
||||
plan.quantity = matchingRewardQuantity(plan.reward, true)
|
||||
plan.rewardName = matchingRewardDisplayName(plan.productName, true)
|
||||
return plan
|
||||
}
|
||||
|
||||
if ic.EffectType != 2 || ic.BoostRateX1000 <= 0 {
|
||||
return plan
|
||||
}
|
||||
|
||||
plan.cardToVoid = icID
|
||||
allRewards, _ := h.readDB.ActivityRewardSettings.WithContext(ctx).Where(
|
||||
h.readDB.ActivityRewardSettings.IssueID.Eq(game.IssueID),
|
||||
).Find()
|
||||
var better *model.ActivityRewardSettings
|
||||
for _, r := range allRewards {
|
||||
if r.MinScore > candidate.MinScore && r.Quantity > 0 {
|
||||
if better == nil || r.MinScore < better.MinScore {
|
||||
better = r
|
||||
}
|
||||
}
|
||||
}
|
||||
if better == nil {
|
||||
return plan
|
||||
}
|
||||
|
||||
randBytes := make([]byte, 4)
|
||||
rand.Read(randBytes)
|
||||
randVal := int32(binary.BigEndian.Uint32(randBytes) % 1000)
|
||||
if randVal >= ic.BoostRateX1000 {
|
||||
return plan
|
||||
}
|
||||
|
||||
betterProdName := ""
|
||||
if better.ProductID > 0 {
|
||||
if bp, _ := h.readDB.Products.WithContext(ctx).Where(h.readDB.Products.ID.Eq(better.ProductID)).First(); bp != nil {
|
||||
betterProdName = bp.Name
|
||||
}
|
||||
}
|
||||
plan.reward = better
|
||||
plan.productName = betterProdName
|
||||
plan.rewardName = matchingRewardDisplayName(betterProdName, false) + "(升级)"
|
||||
plan.quantity = matchingRewardQuantity(better, false)
|
||||
return plan
|
||||
}
|
||||
|
||||
func (h *handler) settleMatchingReward(ctx context.Context, game *activitysvc.MatchingGame, order *model.Orders, candidate *model.ActivityRewardSettings, auto bool) (*matchingSettlementPlan, error) {
|
||||
plan := h.buildMatchingSettlementPlan(ctx, game, order, candidate)
|
||||
if plan == nil || plan.reward == nil || plan.quantity <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
drawLog, _ := h.readDB.ActivityDrawLogs.WithContext(ctx).Where(
|
||||
h.readDB.ActivityDrawLogs.OrderID.Eq(game.OrderID),
|
||||
).First()
|
||||
if drawLog != nil {
|
||||
plan.drawLogID = drawLog.ID
|
||||
}
|
||||
|
||||
var existingCount int64
|
||||
query := h.readDB.UserInventory.WithContext(ctx).Where(
|
||||
h.readDB.UserInventory.UserID.Eq(game.UserID),
|
||||
h.readDB.UserInventory.OrderID.Eq(game.OrderID),
|
||||
h.readDB.UserInventory.RewardID.Eq(plan.reward.ID),
|
||||
)
|
||||
if auto {
|
||||
query = query.Where(h.readDB.UserInventory.Remark.Like(order.OrderNo + "%"))
|
||||
}
|
||||
existingCount, _ = query.Count()
|
||||
missingCount := int64(plan.quantity) - existingCount
|
||||
if missingCount < 0 {
|
||||
missingCount = 0
|
||||
}
|
||||
|
||||
if missingCount > 0 {
|
||||
if err := h.grantRewardHelper(ctx, game.UserID, game.OrderID, plan.reward, int(missingCount), fmt.Sprintf("%s %s", order.OrderNo, plan.rewardName)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if plan.drawLogID > 0 {
|
||||
_, _ = h.writeDB.ActivityDrawLogs.WithContext(ctx).Where(
|
||||
h.writeDB.ActivityDrawLogs.ID.Eq(plan.drawLogID),
|
||||
).Updates(&model.ActivityDrawLogs{
|
||||
IsWinner: 1,
|
||||
RewardID: plan.reward.ID,
|
||||
Level: plan.reward.Level,
|
||||
})
|
||||
}
|
||||
|
||||
if plan.cardToVoid > 0 && plan.drawLogID > 0 {
|
||||
now := time.Now()
|
||||
_, _ = h.writeDB.UserItemCards.WithContext(ctx).Where(
|
||||
h.writeDB.UserItemCards.ID.Eq(plan.cardToVoid),
|
||||
h.writeDB.UserItemCards.UserID.Eq(game.UserID),
|
||||
h.writeDB.UserItemCards.Status.Eq(1),
|
||||
).Updates(map[string]any{
|
||||
h.writeDB.UserItemCards.Status.ColumnName().String(): 2,
|
||||
h.writeDB.UserItemCards.UsedDrawLogID.ColumnName().String(): plan.drawLogID,
|
||||
h.writeDB.UserItemCards.UsedActivityID.ColumnName().String(): game.ActivityID,
|
||||
h.writeDB.UserItemCards.UsedIssueID.ColumnName().String(): game.IssueID,
|
||||
h.writeDB.UserItemCards.UsedAt.ColumnName().String(): now,
|
||||
})
|
||||
}
|
||||
|
||||
return plan, nil
|
||||
}
|
||||
|
||||
var matchingCleanupOnce sync.Once
|
||||
|
||||
func (h *handler) startMatchingGameCleanup() {
|
||||
@ -240,33 +428,28 @@ func (h *handler) doAutoCheck(ctx context.Context, gameID string, game *activity
|
||||
}
|
||||
|
||||
if candidate != nil {
|
||||
productName := ""
|
||||
if p, _ := h.readDB.Products.WithContext(ctx).Where(h.readDB.Products.ID.Eq(candidate.ProductID)).First(); p != nil {
|
||||
productName = p.Name
|
||||
}
|
||||
|
||||
finalRemark := fmt.Sprintf("%s %s (自动开奖)", order.OrderNo, productName)
|
||||
|
||||
if err := h.grantRewardHelper(ctx, game.UserID, game.OrderID, candidate, 1, finalRemark); err != nil {
|
||||
plan, err := h.settleMatchingReward(ctx, game, order, candidate, true)
|
||||
if err != nil {
|
||||
h.logger.Error("对对碰自动开奖: 发放奖励失败", zap.Int64("order_id", game.OrderID), zap.Error(err))
|
||||
} else {
|
||||
} else if plan != nil {
|
||||
prodImage := ""
|
||||
if p, _ := h.readDB.Products.WithContext(ctx).Where(h.readDB.Products.ID.Eq(candidate.ProductID)).First(); p != nil {
|
||||
if p, _ := h.readDB.Products.WithContext(ctx).Where(h.readDB.Products.ID.Eq(plan.reward.ProductID)).First(); p != nil {
|
||||
prodImage = getFirstImage(p.ImagesJSON)
|
||||
}
|
||||
|
||||
rewardInfo = &MatchingRewardInfo{
|
||||
RewardID: candidate.ID,
|
||||
Name: productName,
|
||||
ProductName: productName,
|
||||
RewardID: plan.reward.ID,
|
||||
Name: plan.rewardName,
|
||||
ProductName: plan.productName,
|
||||
ProductImage: prodImage,
|
||||
Level: candidate.Level,
|
||||
Level: plan.reward.Level,
|
||||
}
|
||||
|
||||
h.logger.Info("对对碰自动开奖: 奖励发放成功",
|
||||
zap.Int64("order_id", game.OrderID),
|
||||
zap.String("product_name", productName),
|
||||
zap.Int32("level", candidate.Level))
|
||||
zap.String("product_name", plan.productName),
|
||||
zap.Int32("level", plan.reward.Level),
|
||||
zap.Int("quantity", plan.quantity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user