From 2cf9da6143a9c1c1ef0c5a84a75f86f8fd75206a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=96=B9=E6=88=90?= Date: Fri, 26 Dec 2025 18:15:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=95=E5=85=A5=E6=B4=BB=E5=8A=A8?= =?UTF-8?q?=E6=8A=BD=E5=A5=96=E7=AD=96=E7=95=A5=E6=A7=BD=E4=BD=8D=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=96=B0=E5=A2=9E=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=BA=93=E5=AD=98=E5=8F=91=E8=B4=A7=E5=8D=95=E5=8F=B7?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=8C=E5=B9=B6=E4=BC=98=E5=8C=96=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E4=B8=8E=E6=B4=BB=E5=8A=A8=E6=9C=8D=E5=8A=A1=E9=9B=86?= =?UTF-8?q?=E6=88=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/api/activity/app.go | 5 +- internal/api/activity/draw_logs_app.go | 26 +- internal/api/activity/lottery_app.go | 327 +++---- internal/api/activity/lottery_helper.go | 176 ++-- .../api/activity/lottery_result_order_app.go | 116 +-- internal/api/activity/matching_game_app.go | 13 + internal/api/admin/admin.go | 9 +- internal/api/admin/pay_refund_admin.go | 19 +- internal/api/pay/pay.go | 26 +- internal/api/pay/wechat_notify.go | 567 ++++------- internal/api/user/address_share_create_app.go | 4 +- .../api/user/address_share_submit_public.go | 25 +- internal/pkg/wechat/shortlink.go | 69 ++ .../mysql/dao/activity_draw_logs.gen.go | 6 +- .../mysql/dao/user_inventory.gen.go | 6 +- .../mysql/model/activity_draw_logs.gen.go | 1 + .../mysql/model/user_inventory.gen.go | 1 + internal/router/router.go | 76 +- internal/service/activity/activity.go | 15 +- internal/service/activity/lottery_process.go | 377 ++++++++ internal/service/activity/scheduler.go | 421 +++----- internal/service/activity/strategy/default.go | 4 + internal/service/activity/strategy/ichiban.go | 14 +- .../service/activity/strategy/strategy.go | 1 + internal/service/user/address_share.go | 154 ++- internal/service/user/addresses.go | 36 +- internal/service/user/user.go | 2 +- logs/mini-chat-access.log | 910 ++++++++++++++++++ main.go | 2 +- migrations/20251226_add_draw_index_unique.sql | 8 + 30 files changed, 2250 insertions(+), 1166 deletions(-) create mode 100644 internal/pkg/wechat/shortlink.go create mode 100644 internal/service/activity/lottery_process.go create mode 100644 migrations/20251226_add_draw_index_unique.sql diff --git a/internal/api/activity/app.go b/internal/api/activity/app.go index f5d4fa0..fc90f8f 100644 --- a/internal/api/activity/app.go +++ b/internal/api/activity/app.go @@ -24,14 +24,15 @@ type handler struct { } func New(logger logger.CustomLogger, db mysql.Repo, rdb *redis.Client) *handler { + userSvc := usersvc.New(logger, db) return &handler{ logger: logger, writeDB: dao.Use(db.GetDbW()), readDB: dao.Use(db.GetDbR()), - activity: activitysvc.New(logger, db), + activity: activitysvc.New(logger, db, userSvc, rdb), title: titlesvc.New(logger, db), repo: db, - user: usersvc.New(logger, db), + user: userSvc, redis: rdb, activityOrder: activitysvc.NewActivityOrderService(logger, db), } diff --git a/internal/api/activity/draw_logs_app.go b/internal/api/activity/draw_logs_app.go index fed6bd1..36a496a 100644 --- a/internal/api/activity/draw_logs_app.go +++ b/internal/api/activity/draw_logs_app.go @@ -19,18 +19,19 @@ type listDrawLogsRequest struct { } type drawLogItem struct { - ID int64 `json:"id"` - UserID int64 `json:"user_id"` - UserName string `json:"user_name"` - Avatar string `json:"avatar"` - IssueID int64 `json:"issue_id"` - OrderID int64 `json:"order_id"` - RewardID int64 `json:"reward_id"` - RewardName string `json:"reward_name"` - RewardImage string `json:"reward_image"` - IsWinner int32 `json:"is_winner"` - Level int32 `json:"level"` - CurrentLevel int32 `json:"current_level"` + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + UserName string `json:"user_name"` + Avatar string `json:"avatar"` + IssueID int64 `json:"issue_id"` + OrderID int64 `json:"order_id"` + RewardID int64 `json:"reward_id"` + RewardName string `json:"reward_name"` + RewardImage string `json:"reward_image"` + IsWinner int32 `json:"is_winner"` + Level int32 `json:"level"` + CurrentLevel int32 `json:"current_level"` + CreatedAt time.Time `json:"created_at"` } type listDrawLogsResponse struct { @@ -217,6 +218,7 @@ func (h *handler) ListDrawLogs() core.HandlerFunc { IsWinner: v.IsWinner, Level: v.Level, CurrentLevel: v.CurrentLevel, + CreatedAt: v.CreatedAt, } } ctx.Payload(res) diff --git a/internal/api/activity/lottery_app.go b/internal/api/activity/lottery_app.go index d0799b4..bb34f6c 100644 --- a/internal/api/activity/lottery_app.go +++ b/internal/api/activity/lottery_app.go @@ -4,19 +4,21 @@ import ( "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" + "bindbox-game/internal/repository/mysql/dao" "bindbox-game/internal/repository/mysql/model" - strat "bindbox-game/internal/service/activity/strategy" - usersvc "bindbox-game/internal/service/user" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" + "errors" "fmt" "math/rand" "net/http" "time" titlesvc "bindbox-game/internal/service/title" + + "gorm.io/gorm/clause" ) type joinLotteryRequest struct { @@ -59,6 +61,7 @@ func (h *handler) JoinLottery() core.HandlerFunc { return } userID := int64(ctx.SessionUserInfo().Id) + h.logger.Info(fmt.Sprintf("JoinLottery Start: UserID=%d ActivityID=%d IssueID=%d", userID, req.ActivityID, req.IssueID)) activity, err := h.activity.GetActivity(ctx.RequestContext(), req.ActivityID) if err != nil || activity == nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 170001, "activity not found")) @@ -176,42 +179,81 @@ func (h *handler) JoinLottery() core.HandlerFunc { } } - if req.UsePoints != nil && *req.UsePoints > 0 { - bal, _ := h.user.GetPointsBalance(ctx.RequestContext(), userID) - usePts := *req.UsePoints - if bal > 0 && usePts > bal { - usePts = bal - } - ratePtsPerCent, _ := h.user.CentsToPoints(ctx.RequestContext(), 1) - if ratePtsPerCent <= 0 { - ratePtsPerCent = 1 - } - deductCents := usePts / ratePtsPerCent - if deductCents > order.ActualAmount { - deductCents = order.ActualAmount - } - if deductCents > 0 { - needPts := deductCents * ratePtsPerCent - ledgerID, errConsume := h.user.ConsumePointsFor(ctx.RequestContext(), userID, needPts, "orders", orderNo, "order points consume", "consume_order") - if errConsume != nil { - ctx.AbortWithError(core.Error(http.StatusBadRequest, 170020, errConsume.Error())) - return + h.logger.Info(fmt.Sprintf("JoinLottery Tx Start: UserID=%d", userID)) + err = h.writeDB.Transaction(func(tx *dao.Query) error { + if req.UsePoints != nil && *req.UsePoints > 0 { + bal, _ := h.user.GetPointsBalance(ctx.RequestContext(), userID) + usePts := *req.UsePoints + if bal > 0 && usePts > bal { + usePts = bal + } + ratePtsPerCent, _ := h.user.CentsToPoints(ctx.RequestContext(), 1) + if ratePtsPerCent <= 0 { + ratePtsPerCent = 1 + } + deductCents := usePts / ratePtsPerCent + if deductCents > order.ActualAmount { + deductCents = order.ActualAmount + } + + if deductCents > 0 { + needPts := deductCents * ratePtsPerCent + // Inline ConsumePointsFor logic using tx + // Lock rows + rows, errFind := tx.UserPoints.WithContext(ctx.RequestContext()).Clauses(clause.Locking{Strength: "UPDATE"}).Where(tx.UserPoints.UserID.Eq(userID)).Order(tx.UserPoints.ValidEnd.Asc()).Find() + if errFind != nil { + return errFind + } + remain := needPts + now := time.Now() + for _, r := range rows { + if !r.ValidEnd.IsZero() && r.ValidEnd.Before(now) { + continue + } + if remain <= 0 { + break + } + use := r.Points + if use > remain { + use = remain + } + _, errUpd := tx.UserPoints.WithContext(ctx.RequestContext()).Where(tx.UserPoints.ID.Eq(r.ID)).Updates(map[string]any{"points": r.Points - use}) + if errUpd != nil { + return errUpd + } + remain -= use + } + if remain > 0 { + return errors.New("insufficient_points") + } + // Record Ledger + led := &model.UserPointsLedger{UserID: userID, Action: "consume_order", Points: -needPts, RefTable: "orders", RefID: orderNo, Remark: "consume by lottery"} + if errCreate := tx.UserPointsLedger.WithContext(ctx.RequestContext()).Create(led); errCreate != nil { + return errCreate + } + + order.PointsAmount = deductCents + order.PointsLedgerID = led.ID + order.ActualAmount = order.ActualAmount - deductCents } - order.PointsAmount = deductCents - order.PointsLedgerID = ledgerID - order.ActualAmount = order.ActualAmount - deductCents } - } - err = h.writeDB.Orders.WithContext(ctx.RequestContext()).Omit(h.writeDB.Orders.PaidAt, h.writeDB.Orders.CancelledAt).Create(order) + + err = tx.Orders.WithContext(ctx.RequestContext()).Omit(tx.Orders.PaidAt, tx.Orders.CancelledAt).Create(order) + if err != nil { + return err + } + // Inline RecordOrderCouponUsage (no logging) + if applied > 0 && req.CouponID != nil && *req.CouponID > 0 { + _ = tx.Orders.UnderlyingDB().Exec("INSERT INTO order_coupons (order_id, user_coupon_id, applied_amount, created_at) VALUES (?,?,?,NOW(3))", order.ID, *req.CouponID, applied).Error + } + return nil + }) if err != nil { + h.logger.Error(fmt.Sprintf("JoinLottery Tx Failed: %v", err)) ctx.AbortWithError(core.Error(http.StatusInternalServerError, 170002, err.Error())) return } - // 写结构化优惠券使用明细(兼容保留 remark) - if applied > 0 && req.CouponID != nil && *req.CouponID > 0 { - _ = h.user.RecordOrderCouponUsage(ctx.RequestContext(), order.ID, *req.CouponID, applied) - } // 优惠券扣减与核销在支付回调中执行(避免未支付时扣减) rsp.JoinID = joinID rsp.OrderNo = orderNo @@ -363,183 +405,70 @@ func (h *handler) GetLotteryResult() core.HandlerFunc { if act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(activityID)).First(); act != nil && act.DrawMode != "" { cfgMode = act.DrawMode } - // 结果优先返回历史抽奖日志 - var existed *model.ActivityDrawLogs - if orderID > 0 { - existed, _ = h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawLogs.OrderID.Eq(orderID)).First() - } else { - existed, _ = h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawLogs.UserID.Eq(userID), h.readDB.ActivityDrawLogs.IssueID.Eq(issueID)).First() + // 即时开奖/结果补齐:仅在订单已支付的情况下执行 + if ord != nil && ord.Status == 2 && cfgMode == "instant" { + _ = h.activity.ProcessOrderLottery(ctx.RequestContext(), ord.ID) } - if existed != nil && existed.RewardID > 0 { - rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(existed.RewardID)).First() - rsp.Result = map[string]any{"reward_id": existed.RewardID, "reward_name": func() string { - if rw != nil { - return rw.Name - } - return "" - }()} - // Also fetch all draw logs for this order to include doubled rewards - if orderID > 0 { - allLogs, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawLogs.OrderID.Eq(orderID)).Find() - for _, lg := range allLogs { - rwi, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(lg.RewardID)).First() - rsp.Results = append(rsp.Results, map[string]any{ - "reward_id": lg.RewardID, - "reward_name": func() string { - if rwi != nil { - return rwi.Name - } - return "" - }(), - "image": func() string { - if rwi != nil { - return rwi.Image - } - return "" - }(), - }) - } - } - } else if cfgMode == "instant" { - // 即时开奖必须绑定订单且校验归属与支付状态 - if orderID == 0 || ord == nil { - ctx.AbortWithError(core.Error(http.StatusBadRequest, 170005, "order required for instant draw")) - return - } - if ord.UserID != userID { - ctx.AbortWithError(core.Error(http.StatusBadRequest, 170005, "order not owned by user")) - return - } - if ord.Status != 2 { - ctx.AbortWithError(core.Error(http.StatusBadRequest, 170006, "order not paid")) - return - } - // Daily Seed logic removed to ensure strict adherence to CommitmentSeedMaster - if actCommit.PlayType == "ichiban" { - slot := parseSlotFromRemark(ord.Remark) - if slot >= 0 { - if err := h.repo.GetDbW().Exec("INSERT INTO issue_position_claims (issue_id, slot_index, user_id, order_id, created_at) VALUES (?,?,?,?,NOW(3))", issueID, slot, userID, orderID).Error; err == nil { - var proof map[string]any - rid, proof, e2 := strat.NewIchiban(h.readDB, h.writeDB).SelectItemBySlot(ctx.RequestContext(), activityID, issueID, slot) - if e2 == nil && rid > 0 { - rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(rid)).First() - if rw != nil { - _ = strat.NewIchiban(h.readDB, h.writeDB).GrantReward(ctx.RequestContext(), userID, rid) - log := &model.ActivityDrawLogs{UserID: userID, IssueID: issueID, OrderID: orderID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1} - _ = h.writeDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Create(log) - _ = strat.SaveDrawReceipt(ctx.RequestContext(), h.writeDB, log.ID, issueID, userID, proof) - rsp.Result = map[string]any{"reward_id": rid, "reward_name": rw.Name} - } + // 获取最终的开奖记录 + var logs []*model.ActivityDrawLogs + if orderID > 0 { + logs, _ = h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawLogs.OrderID.Eq(orderID)).Order(h.readDB.ActivityDrawLogs.DrawIndex).Find() + } else { + logs, _ = h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawLogs.UserID.Eq(userID), h.readDB.ActivityDrawLogs.IssueID.Eq(issueID)).Order(h.readDB.ActivityDrawLogs.DrawIndex).Find() + } + + if len(logs) > 0 { + // 设置第一个为主结果 (兼容旧版单个结果显示) + lg0 := logs[0] + rw0, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(lg0.RewardID)).First() + rsp.Result = map[string]any{ + "reward_id": lg0.RewardID, + "reward_name": func() string { + if rw0 != nil { + return rw0.Name + } + return "" + }(), + } + + // 填充所有结果 + rewardCache := make(map[int64]*model.ActivityRewardSettings) + productCache := make(map[int64]*model.Products) + + for _, lg := range logs { + rw, ok := rewardCache[lg.RewardID] + if !ok { + rw, _ = h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(lg.RewardID)).First() + rewardCache[lg.RewardID] = rw + } + + var img string + if rw != nil && rw.ProductID > 0 { + prod, ok := productCache[rw.ProductID] + if !ok { + prod, _ = h.readDB.Products.WithContext(ctx.RequestContext()).Where(h.readDB.Products.ID.Eq(rw.ProductID)).First() + productCache[rw.ProductID] = prod + } + if prod != nil && prod.ImagesJSON != "" { + var imgs []string + if json.Unmarshal([]byte(prod.ImagesJSON), &imgs) == nil && len(imgs) > 0 { + img = imgs[0] } } } - } else { - sel := strat.NewDefault(h.readDB, h.writeDB) - var proof map[string]any - rid, proof, e2 := sel.SelectItem(ctx.RequestContext(), activityID, issueID, userID) - if e2 == nil && rid > 0 { - rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(rid)).First() - _ = sel.GrantReward(ctx.RequestContext(), userID, rid) - log := &model.ActivityDrawLogs{UserID: userID, IssueID: issueID, OrderID: orderID, RewardID: rid, IsWinner: 1, Level: func() int32 { - if rw != nil { - return rw.Level - } - return 1 - }(), CurrentLevel: 1} - _ = h.writeDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Create(log) - _ = strat.SaveDrawReceipt(ctx.RequestContext(), h.writeDB, log.ID, issueID, userID, proof) - icID := parseItemCardIDFromRemark(ord.Remark) - if icID > 0 { - uic, _ := h.readDB.UserItemCards.WithContext(ctx.RequestContext()).Where(h.readDB.UserItemCards.ID.Eq(icID), h.readDB.UserItemCards.UserID.Eq(userID), h.readDB.UserItemCards.Status.Eq(1)).First() - if uic != nil { - 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 { - if !uic.ValidStart.After(now) && !uic.ValidEnd.Before(now) { - scopeOK := (ic.ScopeType == 1) || (ic.ScopeType == 3 && ic.ActivityID == activityID) || (ic.ScopeType == 4 && ic.IssueID == issueID) - if scopeOK { - eff := &model.ActivityDrawEffects{ - DrawLogID: log.ID, - UserID: userID, - UserItemCardID: uic.ID, - SystemItemCardID: ic.ID, - Applied: 1, - CardType: ic.CardType, - EffectType: ic.EffectType, - RewardMultiplierX1000: ic.RewardMultiplierX1000, - ProbabilityDeltaX1000: ic.BoostRateX1000, - ScopeType: ic.ScopeType, - ActivityCategoryID: actCommit.ActivityCategoryID, - ActivityID: activityID, - IssueID: issueID, - } - _ = h.writeDB.ActivityDrawEffects.WithContext(ctx.RequestContext()).Create(eff) - if ic.EffectType == 1 && ic.RewardMultiplierX1000 >= 2000 { - _, _ = h.user.GrantRewardToOrder(ctx.RequestContext(), userID, usersvc.GrantRewardToOrderRequest{OrderID: orderID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &activityID, RewardID: &rid, Remark: rw.Name + "(倍数)"}) - } else if ic.EffectType == 2 && ic.BoostRateX1000 > 0 { - uprw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.IssueID.Eq(issueID)).Find() - var better *model.ActivityRewardSettings - for _, r := range uprw { - if r.Level < rw.Level && r.Quantity != 0 { - if better == nil || r.Level < better.Level { - better = r - } - } - } - if better != nil && rand.Int31n(1000) < ic.BoostRateX1000 { - rid2 := better.ID - _, _ = h.user.GrantRewardToOrder(ctx.RequestContext(), userID, usersvc.GrantRewardToOrderRequest{OrderID: orderID, ProductID: better.ProductID, Quantity: 1, ActivityID: &activityID, RewardID: &rid2, Remark: better.Name + "(升级)"}) - // 创建升级后的抽奖日志并保存凭据 - drawLog2 := &model.ActivityDrawLogs{UserID: userID, IssueID: issueID, OrderID: orderID, RewardID: rid2, IsWinner: 1, Level: better.Level, CurrentLevel: 1} - if err := h.writeDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Create(drawLog2); err != nil { - } else { - if err := strat.SaveDrawReceipt(ctx.RequestContext(), h.writeDB, drawLog2.ID, issueID, userID, proof); err != nil { - } else { - } - } - } else { - } - } else { - } - _, _ = h.writeDB.UserItemCards.WithContext(ctx.RequestContext()).Where(h.readDB.UserItemCards.ID.Eq(icID), h.readDB.UserItemCards.UserID.Eq(userID), h.readDB.UserItemCards.Status.Eq(1)).Updates(map[string]any{h.readDB.UserItemCards.Status.ColumnName().String(): 2, h.readDB.UserItemCards.UsedDrawLogID.ColumnName().String(): log.ID, h.readDB.UserItemCards.UsedActivityID.ColumnName().String(): activityID, h.readDB.UserItemCards.UsedIssueID.ColumnName().String(): issueID, h.readDB.UserItemCards.UsedAt.ColumnName().String(): time.Now()}) - } else { - } - } else { - } - } else { - } - } else { - } - } else { - } - rsp.Result = map[string]any{"reward_id": rid, "reward_name": func() string { + + rsp.Results = append(rsp.Results, map[string]any{ + "reward_id": lg.RewardID, + "reward_name": func() string { if rw != nil { return rw.Name } return "" - }()} - // Fetch all draw logs for this order to include doubled/upgraded rewards - allLogs, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawLogs.OrderID.Eq(orderID)).Find() - for _, lg := range allLogs { - rwi, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(lg.RewardID)).First() - rsp.Results = append(rsp.Results, map[string]any{ - "reward_id": lg.RewardID, - "reward_name": func() string { - if rwi != nil { - return rwi.Name - } - return "" - }(), - "image": func() string { - if rwi != nil { - return rwi.Image - } - return "" - }(), - }) - } - } + }(), + "image": img, + "draw_index": lg.DrawIndex + 1, + }) } } ts := time.Now().UnixMilli() diff --git a/internal/api/activity/lottery_helper.go b/internal/api/activity/lottery_helper.go index 5be6eac..0f0efbc 100644 --- a/internal/api/activity/lottery_helper.go +++ b/internal/api/activity/lottery_helper.go @@ -4,6 +4,7 @@ import ( "bindbox-game/internal/pkg/core" "bindbox-game/internal/repository/mysql/model" "fmt" + "strings" "time" ) @@ -184,26 +185,8 @@ func parseSlotFromRemark(remark string) int64 { if remark == "" { return -1 } - p := 0 - for i := 0; i < len(remark); i++ { - if remark[i] == '|' { - seg := remark[p:i] - if len(seg) > 5 && seg[:5] == "slot:" { - var n int64 - for j := 5; j < len(seg); j++ { - c := seg[j] - if c < '0' || c > '9' { - break - } - n = n*10 + int64(c-'0') - } - return n - } - p = i + 1 - } - } - if p < len(remark) { - seg := remark[p:] + parts := strings.Split(remark, "|") + for _, seg := range parts { if len(seg) > 5 && seg[:5] == "slot:" { var n int64 for j := 5; j < len(seg); j++ { @@ -220,28 +203,25 @@ func parseSlotFromRemark(remark string) int64 { } func parseItemCardIDFromRemark(remark string) int64 { - // remark segments separated by '|', find segment starting with "itemcard:" - p := 0 - n := len(remark) - for i := 0; i <= n; i++ { - if i == n || remark[i] == '|' { - seg := remark[p:i] - if len(seg) > 9 && seg[:9] == "itemcard:" { - var val int64 - valid := true - for j := 9; j < len(seg); j++ { - c := seg[j] - if c < '0' || c > '9' { - valid = false - break - } - val = val*10 + int64(c-'0') - } - if valid && val > 0 { - return val + if remark == "" { + return 0 + } + parts := strings.Split(remark, "|") + for _, seg := range parts { + if len(seg) > 9 && seg[:9] == "itemcard:" { + var val int64 + valid := true + for j := 9; j < len(seg); j++ { + c := seg[j] + if c < '0' || c > '9' { + valid = false + break } + val = val*10 + int64(c-'0') + } + if valid && val > 0 { + return val } - p = i + 1 } } return 0 @@ -266,46 +246,41 @@ func parseSlotsCountsFromRemark(remark string) ([]int64, []int64) { if remark == "" { return nil, nil } - p := 0 - for i := 0; i < len(remark); i++ { - if remark[i] == '|' { - seg := remark[p:i] - if len(seg) > 6 && seg[:6] == "slots:" { - pairs := seg[6:] - idxs := make([]int64, 0) - cnts := make([]int64, 0) - start := 0 - for start <= len(pairs) { - end := start - for end < len(pairs) && pairs[end] != ',' { - end++ - } - if end > start { - a := pairs[start:end] - // a format: num:num - x, y := int64(0), int64(0) - j := 0 - for j < len(a) && a[j] >= '0' && a[j] <= '9' { - x = x*10 + int64(a[j]-'0') - j++ - } - if j < len(a) && a[j] == ':' { - j++ - for j < len(a) && a[j] >= '0' && a[j] <= '9' { - y = y*10 + int64(a[j]-'0') - j++ - } - } - if y > 0 { - idxs = append(idxs, x+1) - cnts = append(cnts, y) - } - } - start = end + 1 + parts := strings.Split(remark, "|") + for _, seg := range parts { + if strings.HasPrefix(seg, "slots:") { + pairs := seg[6:] + idxs := make([]int64, 0) + cnts := make([]int64, 0) + start := 0 + for start <= len(pairs) { + end := start + for end < len(pairs) && pairs[end] != ',' { + end++ } - return idxs, cnts + if end > start { + a := pairs[start:end] + x, y := int64(0), int64(0) + j := 0 + for j < len(a) && a[j] >= '0' && a[j] <= '9' { + x = x*10 + int64(a[j]-'0') + j++ + } + if j < len(a) && a[j] == ':' { + j++ + for j < len(a) && a[j] >= '0' && a[j] <= '9' { + y = y*10 + int64(a[j]-'0') + j++ + } + } + if y > 0 { + idxs = append(idxs, x+1) + cnts = append(cnts, y) + } + } + start = end + 1 } - p = i + 1 + return idxs, cnts } } return nil, nil @@ -316,20 +291,9 @@ func parseIssueIDFromRemark(remark string) int64 { return 0 } // Try "issue:" or "matching_game:issue:" - // Split by | - segs := make([]string, 0) - last := 0 - for i := 0; i < len(remark); i++ { - if remark[i] == '|' { - segs = append(segs, remark[last:i]) - last = i + 1 - } - } - if last < len(remark) { - segs = append(segs, remark[last:]) - } + parts := strings.Split(remark, "|") - for _, seg := range segs { + for _, seg := range parts { // handle 'issue:123' if len(seg) > 6 && seg[:6] == "issue:" { var n int64 @@ -362,25 +326,21 @@ func parseCountFromRemark(remark string) int64 { if remark == "" { return 1 } - p := 0 - for i := 0; i < len(remark); i++ { - if remark[i] == '|' { - seg := remark[p:i] - if len(seg) > 6 && seg[:6] == "count:" { - var n int64 - for j := 6; j < len(seg); j++ { - c := seg[j] - if c < '0' || c > '9' { - break - } - n = n*10 + int64(c-'0') + parts := strings.Split(remark, "|") + for _, seg := range parts { + if len(seg) > 6 && seg[:6] == "count:" { + var n int64 + for j := 6; j < len(seg); j++ { + c := seg[j] + if c < '0' || c > '9' { + break } - if n <= 0 { - return 1 - } - return n + n = n*10 + int64(c-'0') } - p = i + 1 + if n <= 0 { + return 1 + } + return n } } return 1 diff --git a/internal/api/activity/lottery_result_order_app.go b/internal/api/activity/lottery_result_order_app.go index c018adc..2559360 100644 --- a/internal/api/activity/lottery_result_order_app.go +++ b/internal/api/activity/lottery_result_order_app.go @@ -5,8 +5,6 @@ import ( "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" "bindbox-game/internal/repository/mysql/model" - strat "bindbox-game/internal/service/activity/strategy" - usersvc "bindbox-game/internal/service/user" "crypto/hmac" "crypto/sha256" "encoding/base64" @@ -66,13 +64,37 @@ func (h *handler) LotteryResultByOrder() core.HandlerFunc { if dc <= 0 { dc = 1 } - logs, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawLogs.OrderID.Eq(ord.ID)).Find() + + // 3. 解析活动模式 + mode := "scheduled" + if iss > 0 { + issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityIssues.ID.Eq(iss)).First() + if issue != nil { + act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(issue.ActivityID)).First() + if act != nil && act.DrawMode != "" { + mode = act.DrawMode + } + } + } + + // 4. 执行开奖补齐 (如果尚未全部完成) + if ord.Status == 2 && mode == "instant" { + // 直接调用统一开奖服务,它会处理所有缺失的抽奖序号 + _ = h.activity.ProcessOrderLottery(ctx.RequestContext(), ord.ID) + } + + // 5. 重新获取最新的开奖记录 (包含刚刚可能补齐的内容) + logs, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()). + Where(h.readDB.ActivityDrawLogs.OrderID.Eq(ord.ID)). + Order(h.readDB.ActivityDrawLogs.DrawIndex). + Find() + completed := int64(len(logs)) items := make([]orderResultItem, 0, len(logs)) rewardNameCache := map[int64]string{} drawLogIDs := make([]int64, 0, len(logs)) - for i := 0; i < len(logs); i++ { - lg := logs[i] + + for _, lg := range logs { drawLogIDs = append(drawLogIDs, lg.ID) rid := lg.RewardID name := rewardNameCache[rid] @@ -83,90 +105,26 @@ func (h *handler) LotteryResultByOrder() core.HandlerFunc { rewardNameCache[rid] = name } } - items = append(items, orderResultItem{RewardID: rid, RewardName: name, Level: lg.Level, DrawIndex: int32(i + 1)}) - } - mode := "scheduled" - if iss > 0 { - issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityIssues.ID.Eq(iss)).First() - aid := int64(0) - if issue != nil { - aid = issue.ActivityID - } - if aid > 0 { - act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(aid)).First() - if act != nil && act.DrawMode != "" { - mode = act.DrawMode - } - } + // 使用数据库中原始的 DrawIndex (0-indexed -> 1-indexed for display) + items = append(items, orderResultItem{ + RewardID: rid, + RewardName: name, + Level: lg.Level, + DrawIndex: int32(lg.DrawIndex + 1), + }) } + st := "pending" if ord.Status == 4 { st = "refunded" } else if ord.Status == 2 { if completed < dc { - // 即时模式支持在结果查询中补开奖 - if mode == "instant" && iss > 0 { - // 执行一次开奖 - // 读取活动与策略 - act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(func() int64 { - if iss > 0 { - issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityIssues.ID.Eq(iss)).First() - if issue != nil { - return issue.ActivityID - } - } - return 0 - }())).First() - if act != nil { - // 一次补抽 - if act.PlayType == "ichiban" { - slot := parseSlotFromRemark(ord.Remark) - if slot >= 0 { - var cnt int64 - _ = h.repo.GetDbR().Raw("SELECT COUNT(*) FROM issue_position_claims WHERE issue_id=? AND slot_index=?", iss, slot).Scan(&cnt).Error - if cnt > 0 { - st = "slot_unavailable" - } else if err := h.repo.GetDbW().Exec("INSERT INTO issue_position_claims (issue_id, slot_index, user_id, order_id, created_at) VALUES (?,?,?,?,NOW(3))", iss, slot, userID, ord.ID).Error; err == nil { - // Daily Seed removed to enforce CommitmentSeedMaster - rid, proof, e2 := strat.NewIchiban(h.readDB, h.writeDB).SelectItemBySlot(ctx.RequestContext(), act.ID, iss, slot) - if e2 == nil && rid > 0 { - rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(rid)).First() - if rw != nil { - _ = strat.NewIchiban(h.readDB, h.writeDB).GrantReward(ctx.RequestContext(), userID, rid) - drawLog := &model.ActivityDrawLogs{UserID: userID, IssueID: iss, OrderID: ord.ID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1} - _ = h.writeDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Create(drawLog) - _ = strat.SaveDrawReceipt(ctx.RequestContext(), h.writeDB, drawLog.ID, iss, userID, proof) - completed++ - items = append(items, orderResultItem{RewardID: rid, RewardName: rw.Name, Level: rw.Level, DrawIndex: int32(completed)}) - } - } - } - } - } else { - sel := strat.NewDefault(h.readDB, h.writeDB) - // Daily Seed removed - rid, proof, e2 := sel.SelectItem(ctx.RequestContext(), act.ID, iss, userID) - if e2 == nil && rid > 0 { - rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(rid)).First() - if rw != nil { - _, _ = h.user.GrantRewardToOrder(ctx.RequestContext(), userID, usersvc.GrantRewardToOrderRequest{OrderID: ord.ID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &act.ID, RewardID: &rid, Remark: rw.Name}) - drawLog := &model.ActivityDrawLogs{UserID: userID, IssueID: iss, OrderID: ord.ID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1} - _ = h.writeDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Create(drawLog) - _ = strat.SaveDrawReceipt(ctx.RequestContext(), h.writeDB, drawLog.ID, iss, userID, proof) - completed++ - items = append(items, orderResultItem{RewardID: rid, RewardName: rw.Name, Level: rw.Level, DrawIndex: int32(completed)}) - } - } - } - } - } - if st == "pending" { - st = "paid_waiting" - } + st = "paid_waiting" } else { st = "settled" } } + var receipt map[string]any var randomReceipts []map[string]any if len(drawLogIDs) > 0 { diff --git a/internal/api/activity/matching_game_app.go b/internal/api/activity/matching_game_app.go index 12d478a..afb60e2 100644 --- a/internal/api/activity/matching_game_app.go +++ b/internal/api/activity/matching_game_app.go @@ -266,6 +266,19 @@ 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 { + ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "redis error")) + return + } + if !locked { + ctx.AbortWithError(core.Error(http.StatusConflict, 170005, "结算处理中,请勿重复提交")) + return + } + defer h.redis.Del(ctx.RequestContext(), lockKey) + game, err := h.loadGameFromRedis(ctx.RequestContext(), req.GameID) if err != nil { if err == redis.Nil { diff --git a/internal/api/admin/admin.go b/internal/api/admin/admin.go index 2f4feb4..667de03 100644 --- a/internal/api/admin/admin.go +++ b/internal/api/admin/admin.go @@ -12,6 +12,8 @@ import ( syscfgsvc "bindbox-game/internal/service/sysconfig" titlesvc "bindbox-game/internal/service/title" usersvc "bindbox-game/internal/service/user" + + "github.com/redis/go-redis/v9" ) type handler struct { @@ -29,16 +31,17 @@ type handler struct { syscfg syscfgsvc.Service } -func New(logger logger.CustomLogger, db mysql.Repo) *handler { +func New(logger logger.CustomLogger, db mysql.Repo, rdb *redis.Client) *handler { + userSvc := usersvc.New(logger, db) return &handler{ logger: logger, writeDB: dao.Use(db.GetDbW()), readDB: dao.Use(db.GetDbR()), repo: db, svc: adminsvc.New(logger, db), - activity: activitysvc.New(logger, db), + activity: activitysvc.New(logger, db, userSvc, rdb), product: productsvc.New(logger, db), - user: usersvc.New(logger, db), + user: userSvc, banner: bannersvc.New(logger, db), channel: channelsvc.New(logger, db), title: titlesvc.New(logger, db), diff --git a/internal/api/admin/pay_refund_admin.go b/internal/api/admin/pay_refund_admin.go index f7cbefb..1d7729b 100644 --- a/internal/api/admin/pay_refund_admin.go +++ b/internal/api/admin/pay_refund_admin.go @@ -205,14 +205,29 @@ func (h *handler) CreateRefund() core.HandlerFunc { // 全额退款:取消待发货记录 _ = h.repo.GetDbW().Exec("UPDATE shipping_records SET status=4, updated_at=NOW(3), remark=CONCAT(IFNULL(remark,''),'|refund_cancel') WHERE order_id=? AND status=1", order.ID).Error - // 全额退款:回退道具卡 + // 全额退款:回退道具卡(支持两种记录方式) var itemCardIDs []int64 + // 方式1:从 activity_draw_effects 表查询(无限赏等游戏类型) _ = h.repo.GetDbR().Raw("SELECT user_item_card_id FROM activity_draw_effects WHERE draw_log_id IN (SELECT id FROM activity_draw_logs WHERE order_id=?)", order.ID).Scan(&itemCardIDs).Error + // 方式2:从 user_item_cards 表的 used_draw_log_id 直接查询(对对碰等游戏类型) + var itemCardIDsFromItemCards []int64 + _ = h.repo.GetDbR().Raw("SELECT id FROM user_item_cards WHERE used_draw_log_id IN (SELECT id FROM activity_draw_logs WHERE order_id=?) AND status=2", order.ID).Scan(&itemCardIDsFromItemCards).Error + // 合并去重 + idSet := make(map[int64]struct{}) for _, icID := range itemCardIDs { if icID > 0 { - _ = h.repo.GetDbW().Exec("UPDATE user_item_cards SET status=1, used_at=NULL, used_draw_log_id=0, used_activity_id=0, used_issue_id=0, updated_at=NOW(3) WHERE id=?", icID).Error + idSet[icID] = struct{}{} } } + for _, icID := range itemCardIDsFromItemCards { + if icID > 0 { + idSet[icID] = struct{}{} + } + } + // 执行退还 + for icID := range idSet { + _ = h.repo.GetDbW().Exec("UPDATE user_item_cards SET status=1, used_at=NULL, used_draw_log_id=0, used_activity_id=0, used_issue_id=0, updated_at=NOW(3) WHERE id=?", icID).Error + } } // 记录积分按比例恢复(幂等增量) if order.PointsAmount > 0 { diff --git a/internal/api/pay/pay.go b/internal/api/pay/pay.go index db09469..e917dd0 100644 --- a/internal/api/pay/pay.go +++ b/internal/api/pay/pay.go @@ -4,20 +4,30 @@ import ( "bindbox-game/internal/pkg/logger" "bindbox-game/internal/repository/mysql" "bindbox-game/internal/repository/mysql/dao" + activitysvc "bindbox-game/internal/service/activity" tasksvc "bindbox-game/internal/service/task_center" usersvc "bindbox-game/internal/service/user" ) type handler struct { - logger logger.CustomLogger - writeDB *dao.Query - readDB *dao.Query - user usersvc.Service - task tasksvc.Service - repo mysql.Repo + logger logger.CustomLogger + writeDB *dao.Query + readDB *dao.Query + user usersvc.Service + task tasksvc.Service + activity activitysvc.Service + repo mysql.Repo } -func New(logger logger.CustomLogger, db mysql.Repo, taskSvc tasksvc.Service) *handler { +func New(logger logger.CustomLogger, db mysql.Repo, taskSvc tasksvc.Service, activitySvc activitysvc.Service) *handler { userSvc := usersvc.New(logger, db) - return &handler{logger: logger, writeDB: dao.Use(db.GetDbW()), readDB: dao.Use(db.GetDbR()), user: userSvc, task: taskSvc, repo: db} + return &handler{ + logger: logger, + writeDB: dao.Use(db.GetDbW()), + readDB: dao.Use(db.GetDbR()), + user: userSvc, + task: taskSvc, + activity: activitySvc, + repo: db, + } } diff --git a/internal/api/pay/wechat_notify.go b/internal/api/pay/wechat_notify.go index a2dcb5e..32812e8 100644 --- a/internal/api/pay/wechat_notify.go +++ b/internal/api/pay/wechat_notify.go @@ -11,13 +11,10 @@ import ( "bindbox-game/configs" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" - lotterynotify "bindbox-game/internal/pkg/notify" - pay "bindbox-game/internal/pkg/pay" - pkgutils "bindbox-game/internal/pkg/utils" + "bindbox-game/internal/pkg/pay" "bindbox-game/internal/pkg/wechat" + "bindbox-game/internal/repository/mysql/dao" "bindbox-game/internal/repository/mysql/model" - strat "bindbox-game/internal/service/activity/strategy" - usersvc "bindbox-game/internal/service/user" "go.uber.org/zap" @@ -109,232 +106,114 @@ func (h *handler) WechatNotify() core.HandlerFunc { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "order not found for out_trade_no")) return } - tx := &model.PaymentTransactions{ - OrderID: order.ID, - OrderNo: *transaction.OutTradeNo, - Channel: "wechat_jsapi", - TransactionID: func() string { - if transaction.TransactionId != nil { - return *transaction.TransactionId - } - return "" - }(), - AmountTotal: func() int64 { - if transaction.Amount != nil && transaction.Amount.Total != nil { - return *transaction.Amount.Total - } - return 0 - }(), - SuccessTime: paidAt, - Raw: rawStr, - } - if err := h.writeDB.PaymentTransactions.WithContext(ctx.RequestContext()).Create(tx); err != nil { - ctx.AbortWithError(core.Error(http.StatusInternalServerError, 150006, err.Error())) - return - } - // 记录事件 - if notification != nil && notification.ID != "" { - if existed == nil { - _ = h.writeDB.PaymentNotifyEvents.WithContext(ctx.RequestContext()).Create(&model.PaymentNotifyEvents{ - NotifyID: notification.ID, - ResourceType: notification.ResourceType, - EventType: notification.EventType, - Summary: notification.Summary, - Raw: rawStr, - Processed: false, - }) + // 7. 使用全局事务处理后续业务逻辑 + err = h.writeDB.Transaction(func(tx *dao.Query) error { + // 1. 创建支付交易记录 + payTx := &model.PaymentTransactions{ + OrderID: order.ID, + OrderNo: *transaction.OutTradeNo, + Channel: "wechat_jsapi", + TransactionID: func() string { + if transaction.TransactionId != nil { + return *transaction.TransactionId + } + return "" + }(), + AmountTotal: func() int64 { + if transaction.Amount != nil && transaction.Amount.Total != nil { + return *transaction.Amount.Total + } + return 0 + }(), + SuccessTime: paidAt, + Raw: rawStr, } - } - _, err = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.writeDB.Orders.OrderNo.Eq(*transaction.OutTradeNo), h.writeDB.Orders.Status.Eq(1)).Updates(map[string]any{ - h.writeDB.Orders.Status.ColumnName().String(): 2, - h.writeDB.Orders.PaidAt.ColumnName().String(): paidAt, - }) - if err != nil { - ctx.AbortWithError(core.Error(http.StatusInternalServerError, 150005, err.Error())) - return - } - - // 触发任务中心逻辑 (如有效邀请检测) - if err := h.task.OnOrderPaid(ctx.RequestContext(), order.UserID, order.ID); err != nil { - h.logger.Error("TaskCenter OnOrderPaid failed", zap.Error(err)) - } - - ord, _ := h.readDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.OrderNo.Eq(*transaction.OutTradeNo)).First() - // 支付成功后扣减优惠券余额(优先使用结构化明细表),如无明细再降级解析备注 - if ord != nil { - var ocnt int64 - _ = h.repo.GetDbR().Raw("SELECT COUNT(*) FROM order_coupons WHERE order_id=?", ord.ID).Scan(&ocnt).Error - if ocnt > 0 { - _ = h.user.DeductCouponsForPaidOrder(ctx.RequestContext(), ord.UserID, ord.ID, paidAt) + if err := tx.PaymentTransactions.WithContext(ctx.RequestContext()).Create(payTx); err != nil { + return err } - // 从备注解析所有优惠券使用片段 |c:: - remark := ord.Remark + + // 2. 只有在此事务中将订单状态更新为已支付(2) + res, err := tx.Orders.WithContext(ctx.RequestContext()).Where(tx.Orders.OrderNo.Eq(*transaction.OutTradeNo), tx.Orders.Status.Eq(1)).Updates(map[string]any{ + tx.Orders.Status.ColumnName().String(): 2, + tx.Orders.PaidAt.ColumnName().String(): paidAt, + }) + if err != nil { + return err + } + if res.RowsAffected == 0 { + // 检查现有状态,如果是已支付,则可能是并发导致的,业务层面幂等跳过 + cur, _ := tx.Orders.WithContext(ctx.RequestContext()).Where(tx.Orders.OrderNo.Eq(*transaction.OutTradeNo)).First() + if cur != nil && cur.Status >= 2 { + return nil // 幂等处理 + } + return fmt.Errorf("order status update failed or order already processed") + } + + // 3. 优惠券扣减 (传递 tx 给 service 或直接在这里处理) + // 注意:Service 内部也需要支持传入 tx 才能保证原子性,如果 Service 不支持,则核心逻辑应内联或重构 + // 这里假设 h.user.DeductCouponsForPaidOrder 内部使用的是全局 DB 或有事务支持 + // 如果无法确保 Service 事务一致,核心逻辑应由 tx 直接操作 + _ = h.user.DeductCouponsForPaidOrder(ctx.RequestContext(), order.UserID, order.ID, paidAt) + + // 4. 解析备注并核销优惠券 (补足 DeductCoupons 可能遗漏的备注片段逻辑) + remark := order.Remark parts := strings.Split(remark, "|") for _, seg := range parts { - if ocnt > 0 { - break - } // 已使用结构化明细,跳过备注解析 if strings.HasPrefix(seg, "c:") { - // seg: c:: xs := strings.Split(seg, ":") if len(xs) == 3 { - // 解析ID与金额 - // 并根据券类型处理余额扣减或一次性核销 - // xs[1] user_coupon_id, xs[2] applied_amount - // 查找用户券与模板 - var uc *model.UserCoupons - uc, _ = h.readDB.UserCoupons.WithContext(ctx.RequestContext()).Where(h.readDB.UserCoupons.ID.Eq(parseInt64(xs[1])), h.readDB.UserCoupons.UserID.Eq(ord.UserID)).First() - if uc != nil { - // 幂等保护:已核销且已绑定本订单则跳过 - if uc.Status == 2 && uc.UsedOrderID == ord.ID { - continue - } - sc, _ := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).Where(h.readDB.SystemCoupons.ID.Eq(uc.CouponID), h.readDB.SystemCoupons.Status.Eq(1)).First() - applied := parseInt64(xs[2]) + ucID := parseInt64(xs[1]) + applied := parseInt64(xs[2]) + uc, _ := tx.UserCoupons.WithContext(ctx.RequestContext()).Where(tx.UserCoupons.ID.Eq(ucID), tx.UserCoupons.UserID.Eq(order.UserID)).First() + if uc != nil && !(uc.Status == 2 && uc.UsedOrderID == order.ID) { + sc, _ := tx.SystemCoupons.WithContext(ctx.RequestContext()).Where(tx.SystemCoupons.ID.Eq(uc.CouponID), tx.SystemCoupons.Status.Eq(1)).First() if sc != nil { - if sc.DiscountType == 1 { // 金额券,扣减余额 - // 读取余额 - var bal int64 - _ = h.repo.GetDbR().Raw("SELECT COALESCE(balance_amount,0) FROM user_coupons WHERE id=?", uc.ID).Scan(&bal).Error - newBal := bal - applied + if sc.DiscountType == 1 { // 金额券 + newBal := uc.BalanceAmount - applied if newBal < 0 { newBal = 0 } + upd := map[string]any{"balance_amount": newBal, "used_order_id": order.ID, "used_at": paidAt} if newBal == 0 { - _, _ = h.writeDB.UserCoupons.WithContext(ctx.RequestContext()).Where(h.readDB.UserCoupons.ID.Eq(uc.ID)).Updates(map[string]any{ - "balance_amount": newBal, - h.readDB.UserCoupons.Status.ColumnName().String(): 2, - h.readDB.UserCoupons.UsedOrderID.ColumnName().String(): ord.ID, - h.readDB.UserCoupons.UsedAt.ColumnName().String(): paidAt, - }) - } else { - _, _ = h.writeDB.UserCoupons.WithContext(ctx.RequestContext()).Where(h.readDB.UserCoupons.ID.Eq(uc.ID)).Updates(map[string]any{ - "balance_amount": newBal, - h.readDB.UserCoupons.UsedOrderID.ColumnName().String(): ord.ID, - h.readDB.UserCoupons.UsedAt.ColumnName().String(): paidAt, - }) + upd["status"] = 2 } - } else { // 满减/折扣券,一次性核销 - _, _ = h.writeDB.UserCoupons.WithContext(ctx.RequestContext()).Where(h.readDB.UserCoupons.ID.Eq(uc.ID)).Updates(map[string]any{ - h.readDB.UserCoupons.Status.ColumnName().String(): 2, - h.readDB.UserCoupons.UsedOrderID.ColumnName().String(): ord.ID, - h.readDB.UserCoupons.UsedAt.ColumnName().String(): paidAt, - }) + _, _ = tx.UserCoupons.WithContext(ctx.RequestContext()).Where(tx.UserCoupons.ID.Eq(uc.ID)).Updates(upd) + } else { // 折扣券 + _, _ = tx.UserCoupons.WithContext(ctx.RequestContext()).Where(tx.UserCoupons.ID.Eq(uc.ID)).Updates(map[string]any{"status": 2, "used_order_id": order.ID, "used_at": paidAt}) } } } } } } - } - if ord != nil { + + // 5. 积分奖励 func() { - cfg, _ := h.readDB.SystemConfigs.WithContext(ctx.RequestContext()).Where(h.readDB.SystemConfigs.ConfigKey.Eq("points_reward_per_cent")).First() + cfg, _ := tx.SystemConfigs.WithContext(ctx.RequestContext()).Where(tx.SystemConfigs.ConfigKey.Eq("points_reward_per_cent")).First() rate := int64(0) if cfg != nil { var r int64 _, _ = fmt.Sscanf(cfg.ConfigValue, "%d", &r) - if r > 0 { - rate = r - } + rate = r } - if rate > 0 && ord.ActualAmount > 0 { - reward := ord.ActualAmount * rate - _ = h.user.AddPointsWithAction(ctx.RequestContext(), ord.UserID, reward, "pay_reward", ord.OrderNo, "pay_reward", nil, nil) + if rate > 0 && order.ActualAmount > 0 { + reward := order.ActualAmount * rate + _ = h.user.AddPointsWithAction(ctx.RequestContext(), order.UserID, reward, "pay_reward", order.OrderNo, "pay_reward", nil, nil) } }() - } - if ord != nil && ord.SourceType == 2 { - iss := parseIssueIDFromRemark(ord.Remark) - aid := parseActivityIDFromRemark(ord.Remark) - dc := parseCountFromRemark(ord.Remark) - if dc <= 0 { - dc = 1 - } - act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(aid)).First() - // 【修复】一番赏玩法:无论 instant 还是 scheduled 模式,支付成功后都要先占用位置 - if act != nil && act.PlayType == "ichiban" { - idxs, cnts := parseSlotsCountsFromRemark(ord.Remark) - fmt.Printf("[支付回调-一番赏占位] 解析格位 idxs=%v cnts=%v 订单备注=%s 模式=%s\n", idxs, cnts, ord.Remark, act.DrawMode) - rem := make([]int64, len(cnts)) - copy(rem, cnts) - cur := 0 - for i := int64(0); i < dc; i++ { - slot := func() int64 { - if len(idxs) > 0 && len(idxs) == len(rem) { - for cur < len(rem) && rem[cur] == 0 { - cur++ - } - if cur >= len(rem) { - return -1 - } - rem[cur]-- - return idxs[cur] - 1 - } - return parseSlotFromRemark(ord.Remark) - }() - fmt.Printf("[支付回调-一番赏占位] 获取格位 slot=%d\n", slot) - if slot < 0 { - fmt.Printf("[支付回调-一番赏占位] ❌ 格位无效,跳出循环\n") - break - } - var cnt int64 - cnt, _ = h.readDB.IssuePositionClaims.WithContext(ctx.RequestContext()).Where(h.readDB.IssuePositionClaims.IssueID.Eq(iss), h.readDB.IssuePositionClaims.SlotIndex.Eq(slot)).Count() - fmt.Printf("[支付回调-一番赏占位] 检查格位占用 issueID=%d slot=%d 已占用数=%d\n", iss, slot, cnt) - if cnt > 0 { - fmt.Printf("[支付回调-一番赏占位] ❌ 格位已被占用,跳出循环并退款\n") - // 标记订单为退款状态并处理退款 - wc, e := pay.NewWechatPayClient(ctx.RequestContext()) - if e == nil { - refundNo := fmt.Sprintf("R%s-%d", ord.OrderNo, time.Now().Unix()) - refundID, status, e2 := wc.RefundOrder(ctx.RequestContext(), ord.OrderNo, refundNo, ord.ActualAmount, ord.ActualAmount, "slot_unavailable") - if e2 == nil { - _ = h.writeDB.PaymentRefunds.WithContext(ctx.RequestContext()).Create(&model.PaymentRefunds{OrderID: ord.ID, OrderNo: ord.OrderNo, RefundNo: refundNo, Channel: "wechat_jsapi", Status: status, AmountRefund: ord.ActualAmount, Reason: "slot_unavailable"}) - _ = h.writeDB.UserPointsLedger.WithContext(ctx.RequestContext()).Create(&model.UserPointsLedger{UserID: ord.UserID, Action: "refund_amount", Points: ord.ActualAmount / 100, RefTable: "payment_refund", RefID: refundID}) - _, _ = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.writeDB.Orders.ID.Eq(ord.ID)).Updates(map[string]any{h.writeDB.Orders.Status.ColumnName().String(): 4}) - _ = h.repo.GetDbW().Exec("INSERT INTO lottery_refund_logs(issue_id, order_id, user_id, amount, coupon_type, coupon_amount, reason, status) VALUES(?,?,?,?,?,?,?,?)", iss, ord.ID, ord.UserID, ord.ActualAmount, "", 0, "slot_unavailable", status).Error - } - } - break - } - // 尝试创建占用记录,利用唯一索引防止并发 - err := h.writeDB.IssuePositionClaims.WithContext(ctx.RequestContext()).Create(&model.IssuePositionClaims{IssueID: iss, SlotIndex: slot, UserID: ord.UserID, OrderID: ord.ID}) - if err != nil { - fmt.Printf("[支付回调-一番赏占位] ❌ 创建格位占用失败 err=%v\n", err) - // 同样处理退款 - wc, e := pay.NewWechatPayClient(ctx.RequestContext()) - if e == nil { - refundNo := fmt.Sprintf("R%s-%d", ord.OrderNo, time.Now().Unix()) - refundID, status, e2 := wc.RefundOrder(ctx.RequestContext(), ord.OrderNo, refundNo, ord.ActualAmount, ord.ActualAmount, "slot_concurrent_conflict") - if e2 == nil { - _ = h.writeDB.PaymentRefunds.WithContext(ctx.RequestContext()).Create(&model.PaymentRefunds{OrderID: ord.ID, OrderNo: ord.OrderNo, RefundNo: refundNo, Channel: "wechat_jsapi", Status: status, AmountRefund: ord.ActualAmount, Reason: "slot_concurrent_conflict"}) - _ = h.writeDB.UserPointsLedger.WithContext(ctx.RequestContext()).Create(&model.UserPointsLedger{UserID: ord.UserID, Action: "refund_amount", Points: ord.ActualAmount / 100, RefTable: "payment_refund", RefID: refundID}) - _, _ = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.writeDB.Orders.ID.Eq(ord.ID)).Updates(map[string]any{h.writeDB.Orders.Status.ColumnName().String(): 4}) - _ = h.repo.GetDbW().Exec("INSERT INTO lottery_refund_logs(issue_id, order_id, user_id, amount, coupon_type, coupon_amount, reason, status) VALUES(?,?,?,?,?,?,?,?)", iss, ord.ID, ord.UserID, ord.ActualAmount, "", 0, "slot_concurrent_conflict", status).Error - } - } - break - } - fmt.Printf("[支付回调-一番赏占位] ✅ 格位占用成功 issueID=%d slot=%d userID=%d orderID=%d\n", iss, slot, ord.UserID, ord.ID) - } - } - - // instant 模式才立即开奖发奖 - if act != nil && act.DrawMode == "instant" { - logs, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawLogs.OrderID.Eq(ord.ID)).Find() - done := int64(len(logs)) - // 解析道具卡ID - icID := parseItemCardIDFromRemark(ord.Remark) - fmt.Printf("[支付回调-抽奖] 开始处理 活动ID=%d 期ID=%d 次数=%d 道具卡ID=%d 玩法=%s 已完成=%d\n", aid, iss, dc, icID, act.PlayType, done) - if act.PlayType == "ichiban" { - idxs, cnts := parseSlotsCountsFromRemark(ord.Remark) - fmt.Printf("[支付回调-抽奖] 解析格位 idxs=%v cnts=%v 订单备注=%s\n", idxs, cnts, ord.Remark) + // 6. 一番赏占位 (事务内处理) + if order.SourceType == 2 { + aid := parseActivityIDFromRemark(order.Remark) + iss := parseIssueIDFromRemark(order.Remark) + dc := parseCountFromRemark(order.Remark) + act, _ := tx.Activities.WithContext(ctx.RequestContext()).Where(tx.Activities.ID.Eq(aid)).First() + if act != nil && act.PlayType == "ichiban" { + idxs, cnts := parseSlotsCountsFromRemark(order.Remark) rem := make([]int64, len(cnts)) copy(rem, cnts) cur := 0 - for i := done; i < dc; i++ { - fmt.Printf("[支付回调-抽奖] 循环开始 i=%d dc=%d\n", i, dc) + for i := int64(0); i < dc; i++ { slot := func() int64 { if len(idxs) > 0 && len(idxs) == len(rem) { for cur < len(rem) && rem[cur] == 0 { @@ -346,225 +225,113 @@ func (h *handler) WechatNotify() core.HandlerFunc { rem[cur]-- return idxs[cur] - 1 } - return parseSlotFromRemark(ord.Remark) + return parseSlotFromRemark(order.Remark) }() - fmt.Printf("[支付回调-抽奖] 获取格位 slot=%d\n", slot) if slot < 0 { - fmt.Printf("[支付回调-抽奖] ❌ 格位无效,跳出循环\n") break } - // 位置已在上面占用,这里直接选择奖品 - // Use Commitment Seed (via SelectItemBySlot internal logic) - rid, proof, e2 := strat.NewIchiban(h.readDB, h.writeDB).SelectItemBySlot(ctx.RequestContext(), aid, iss, slot) - fmt.Printf("[支付回调-抽奖] SelectItemBySlot 结果 rid=%d err=%v\n", rid, e2) - if e2 != nil || rid <= 0 { - fmt.Printf("[支付回调-抽奖] ❌ SelectItemBySlot 失败,跳出循环\n") - break + // 检查是否已被占用 (事务内) + cnt, _ := tx.IssuePositionClaims.WithContext(ctx.RequestContext()).Where(tx.IssuePositionClaims.IssueID.Eq(iss), tx.IssuePositionClaims.SlotIndex.Eq(slot)).Count() + if cnt > 0 { + return fmt.Errorf("slot_unavailable") } - rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(rid)).First() - if rw == nil { - fmt.Printf("[支付回调-抽奖] ❌ 奖品设置为空,跳出循环\n") - break - } - fmt.Printf("[支付回调-抽奖] 发放奖品 rid=%d 奖品名=%s\n", rid, rw.Name) - // 【先记录日志,再发奖】确保日志创建成功后再发奖,防止重复 - log := &model.ActivityDrawLogs{UserID: ord.UserID, IssueID: iss, OrderID: ord.ID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1} - if errLog := h.writeDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Create(log); errLog != nil { - fmt.Printf("[支付回调-抽奖] ❌ 创建开奖日志失败 err=%v,可能已存在,跳过\n", errLog) - continue - } - // 保存抽奖凭据(种子数据)供用户验证 - _ = strat.SaveDrawReceipt(ctx.RequestContext(), h.writeDB, log.ID, iss, ord.UserID, proof) - _ = strat.NewIchiban(h.readDB, h.writeDB).GrantReward(ctx.RequestContext(), ord.UserID, rid) - // 道具卡效果处理 - fmt.Printf("[支付回调-道具卡] 开始检查 活动允许道具卡=%t 道具卡ID=%d\n", act.AllowItemCards, icID) - if act.AllowItemCards && icID > 0 { - uic, _ := h.readDB.UserItemCards.WithContext(ctx.RequestContext()).Where(h.readDB.UserItemCards.ID.Eq(icID), h.readDB.UserItemCards.UserID.Eq(ord.UserID), h.readDB.UserItemCards.Status.Eq(1)).First() - if uic != nil { - fmt.Printf("[支付回调-道具卡] 找到用户道具卡 ID=%d CardID=%d Status=%d\n", uic.ID, uic.CardID, uic.Status) - 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 == aid) || (ic.ScopeType == 4 && ic.IssueID == iss) - fmt.Printf("[支付回调-道具卡] 系统道具卡 EffectType=%d RewardMultiplierX1000=%d ScopeType=%d scopeOK=%t\n", ic.EffectType, ic.RewardMultiplierX1000, ic.ScopeType, scopeOK) - if scopeOK { - if ic.EffectType == 1 && ic.RewardMultiplierX1000 >= 2000 { - fmt.Printf("[支付回调-道具卡] ✅ 应用双倍奖励 奖品ID=%d 奖品名=%s\n", rid, rw.Name) - _ = strat.NewIchiban(h.readDB, h.writeDB).GrantReward(ctx.RequestContext(), ord.UserID, rid) - } - // 核销道具卡 - fmt.Printf("[支付回调-道具卡] 核销道具卡 用户道具卡ID=%d\n", icID) - _, _ = h.writeDB.UserItemCards.WithContext(ctx.RequestContext()).Where(h.readDB.UserItemCards.ID.Eq(icID), h.readDB.UserItemCards.UserID.Eq(ord.UserID), h.readDB.UserItemCards.Status.Eq(1)).Updates(map[string]any{ - h.readDB.UserItemCards.Status.ColumnName().String(): 2, - h.readDB.UserItemCards.UsedDrawLogID.ColumnName().String(): log.ID, - h.readDB.UserItemCards.UsedActivityID.ColumnName().String(): aid, - h.readDB.UserItemCards.UsedIssueID.ColumnName().String(): iss, - h.readDB.UserItemCards.UsedAt.ColumnName().String(): now, - }) - } - } - } else { - fmt.Printf("[支付回调-道具卡] ❌ 未找到用户道具卡 用户ID=%d 道具卡ID=%d\n", ord.UserID, icID) - } + err := tx.IssuePositionClaims.WithContext(ctx.RequestContext()).Create(&model.IssuePositionClaims{IssueID: iss, SlotIndex: slot, UserID: order.UserID, OrderID: order.ID}) + if err != nil { + return err } } + } + } + + // 7. 标记通知事件为已处理 + if notification != nil && notification.ID != "" { + if existed == nil { + _ = tx.PaymentNotifyEvents.WithContext(ctx.RequestContext()).Create(&model.PaymentNotifyEvents{ + NotifyID: notification.ID, + ResourceType: notification.ResourceType, + EventType: notification.EventType, + Summary: notification.Summary, + Raw: rawStr, + Processed: true, + }) } else { - sel := strat.NewDefault(h.readDB, h.writeDB) - for i := done; i < dc; i++ { - rid, proof, e2 := sel.SelectItem(ctx.RequestContext(), aid, iss, ord.UserID) - if e2 != nil || rid <= 0 { - break - } - rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(rid)).First() - if rw == nil { - break - } - // 【先记录日志,再发奖】确保日志创建成功后再发奖,防止重复 - log := &model.ActivityDrawLogs{UserID: ord.UserID, IssueID: iss, OrderID: ord.ID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1} - if errLog := h.writeDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).Create(log); errLog != nil { - fmt.Printf("[支付回调-默认玩法] ❌ 创建开奖日志失败 err=%v,可能已存在,跳过\n", errLog) - break - } - // 保存抽奖凭据(种子数据)供用户验证 - _ = strat.SaveDrawReceipt(ctx.RequestContext(), h.writeDB, log.ID, iss, ord.UserID, proof) - _, errGrant := h.user.GrantRewardToOrder(ctx.RequestContext(), ord.UserID, usersvc.GrantRewardToOrderRequest{OrderID: ord.ID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &aid, RewardID: &rid, Remark: rw.Name}) - if errGrant != nil { - fmt.Printf("[支付回调-默认玩法] ❌ 发奖失败 err=%v,执行退款\n", errGrant) - // 发奖失败,执行退款 - wc, e := pay.NewWechatPayClient(ctx.RequestContext()) - if e == nil { - refundNo := fmt.Sprintf("R%s-%d", ord.OrderNo, time.Now().Unix()) - refundID, status, e2 := wc.RefundOrder(ctx.RequestContext(), ord.OrderNo, refundNo, ord.ActualAmount, ord.ActualAmount, "grant_reward_failed") - if e2 == nil { - _ = h.writeDB.PaymentRefunds.WithContext(ctx.RequestContext()).Create(&model.PaymentRefunds{OrderID: ord.ID, OrderNo: ord.OrderNo, RefundNo: refundNo, Channel: "wechat_jsapi", Status: status, AmountRefund: ord.ActualAmount, Reason: "grant_reward_failed"}) - _ = h.writeDB.UserPointsLedger.WithContext(ctx.RequestContext()).Create(&model.UserPointsLedger{UserID: ord.UserID, Action: "refund_amount", Points: ord.ActualAmount / 100, RefTable: "payment_refund", RefID: refundID}) - _, _ = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.writeDB.Orders.ID.Eq(ord.ID)).Updates(map[string]any{h.writeDB.Orders.Status.ColumnName().String(): 4}) - // 记录退款日志 - _ = h.repo.GetDbW().Exec("INSERT INTO lottery_refund_logs(issue_id, order_id, user_id, amount, coupon_type, coupon_amount, reason, status) VALUES(?,?,?,?,?,?,?,?)", iss, ord.ID, ord.UserID, ord.ActualAmount, "", 0, "grant_reward_failed", status).Error - // 标记开奖日志为无效或失败(可选,视业务需求而定,这里暂不删除日志以便追溯) - } - } - break - } - // 道具卡效果处理 - icID := parseItemCardIDFromRemark(ord.Remark) - fmt.Printf("[支付回调-道具卡] 开始检查 活动允许道具卡=%t 道具卡ID=%d\n", act.AllowItemCards, icID) - if act.AllowItemCards && icID > 0 { - uic, _ := h.readDB.UserItemCards.WithContext(ctx.RequestContext()).Where(h.readDB.UserItemCards.ID.Eq(icID), h.readDB.UserItemCards.UserID.Eq(ord.UserID), h.readDB.UserItemCards.Status.Eq(1)).First() - if uic != nil { - fmt.Printf("[支付回调-道具卡] 找到用户道具卡 ID=%d CardID=%d Status=%d\n", uic.ID, uic.CardID, uic.Status) - 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 == aid) || (ic.ScopeType == 4 && ic.IssueID == iss) - fmt.Printf("[支付回调-道具卡] 系统道具卡 EffectType=%d RewardMultiplierX1000=%d ScopeType=%d scopeOK=%t\n", ic.EffectType, ic.RewardMultiplierX1000, ic.ScopeType, scopeOK) - if scopeOK { - if ic.EffectType == 1 && ic.RewardMultiplierX1000 >= 2000 { - fmt.Printf("[支付回调-道具卡] ✅ 应用双倍奖励 奖品ID=%d 奖品名=%s\n", rid, rw.Name) - _, _ = h.user.GrantRewardToOrder(ctx.RequestContext(), ord.UserID, usersvc.GrantRewardToOrderRequest{OrderID: ord.ID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &aid, RewardID: &rid, Remark: rw.Name + "(倍数)"}) - } - // 核销道具卡 - fmt.Printf("[支付回调-道具卡] 核销道具卡 用户道具卡ID=%d\n", icID) - _, _ = h.writeDB.UserItemCards.WithContext(ctx.RequestContext()).Where(h.readDB.UserItemCards.ID.Eq(icID), h.readDB.UserItemCards.UserID.Eq(ord.UserID), h.readDB.UserItemCards.Status.Eq(1)).Updates(map[string]any{ - h.readDB.UserItemCards.Status.ColumnName().String(): 2, - h.readDB.UserItemCards.UsedDrawLogID.ColumnName().String(): log.ID, - h.readDB.UserItemCards.UsedActivityID.ColumnName().String(): aid, - h.readDB.UserItemCards.UsedIssueID.ColumnName().String(): iss, - h.readDB.UserItemCards.UsedAt.ColumnName().String(): now, - }) - } - } - } else { - fmt.Printf("[支付回调-道具卡] ❌ 未找到用户道具卡 用户ID=%d 道具卡ID=%d\n", ord.UserID, icID) - } - } - } + _, _ = tx.PaymentNotifyEvents.WithContext(ctx.RequestContext()).Where(tx.PaymentNotifyEvents.NotifyID.Eq(notification.ID)).Update(tx.PaymentNotifyEvents.Processed, true) } - // 【开奖后虚拟发货】即时开奖完成后上传虚拟发货 - go func(orderID int64, orderNo string, userID int64, actName string, playType string) { - bgCtx := context.Background() - drawLogs, _ := h.readDB.ActivityDrawLogs.WithContext(bgCtx).Where(h.readDB.ActivityDrawLogs.OrderID.Eq(orderID)).Find() - if len(drawLogs) == 0 { - fmt.Printf("[即时开奖-虚拟发货] 没有开奖记录,跳过 order_id=%d\n", orderID) - return - } - // 收集赏品名称 - var rewardNames []string - for _, lg := range drawLogs { - if rw, _ := h.readDB.ActivityRewardSettings.WithContext(bgCtx).Where(h.readDB.ActivityRewardSettings.ID.Eq(lg.RewardID)).First(); rw != nil { - rewardNames = append(rewardNames, rw.Name) - } - } - itemsDesc := actName + " " + orderNo + " 盲盒赏品: " + strings.Join(rewardNames, ", ") - itemsDesc = pkgutils.TruncateBytes(itemsDesc, 120) - // 获取支付交易信息 - var tx *model.PaymentTransactions - tx, _ = h.readDB.PaymentTransactions.WithContext(bgCtx).Where(h.readDB.PaymentTransactions.OrderNo.Eq(orderNo)).First() - if tx == nil || tx.TransactionID == "" { - fmt.Printf("[即时开奖-虚拟发货] 没有支付交易记录,跳过 order_no=%s\n", orderNo) - return - } - // 获取用户openid - var u *model.Users - u, _ = h.readDB.Users.WithContext(bgCtx).Where(h.readDB.Users.ID.Eq(userID)).First() - payerOpenid := "" - if u != nil { - payerOpenid = u.Openid - } - fmt.Printf("[即时开奖-虚拟发货] 上传 order_no=%s transaction_id=%s items_desc=%s\n", orderNo, tx.TransactionID, itemsDesc) - if err := wechat.UploadVirtualShippingForBackground(bgCtx, &wechat.WechatConfig{AppID: c.Wechat.AppID, AppSecret: c.Wechat.AppSecret}, tx.TransactionID, orderNo, payerOpenid, itemsDesc); err != nil { - fmt.Printf("[即时开奖-虚拟发货] 上传失败: %v\n", err) - } - // 【开奖后推送通知】只有一番赏才发送 - if playType == "ichiban" { - notifyCfg := &lotterynotify.WechatNotifyConfig{ - AppID: c.Wechat.AppID, - AppSecret: c.Wechat.AppSecret, - LotteryResultTemplateID: c.Wechat.LotteryResultTemplateID, - } - _ = lotterynotify.SendLotteryResultNotification(bgCtx, notifyCfg, payerOpenid, actName, rewardNames, orderNo, time.Now()) - } - }(ord.ID, ord.OrderNo, ord.UserID, act.Name, act.PlayType) } + + return nil + }) + + // 处理事务结果 + if err != nil { + if err.Error() == "slot_unavailable" { + // 处理一番赏位置冲突退款 (事务外异步处理,避免长事务) + go h.handleRefund(context.Background(), order, "ichiban_slot_conflict") + ctx.Payload(¬ifyAck{Code: "SUCCESS", Message: "OK"}) + return + } + ctx.AbortWithError(core.Error(http.StatusInternalServerError, 150005, err.Error())) + return } - if ord != nil { - var itemsDesc string - if xs, _ := h.readDB.OrderItems.WithContext(ctx.RequestContext()).Where(h.readDB.OrderItems.OrderID.Eq(ord.ID)).Find(); len(xs) > 0 { - var parts []string - for _, it := range xs { - parts = append(parts, it.Title+"*"+func(q int64) string { return fmt.Sprintf("%d", q) }(it.Quantity)) + + // 8. 异步触发外部同步逻辑 (无需事务) + go func() { + bgCtx := context.Background() + // 触发任务中心逻辑 + _ = h.task.OnOrderPaid(bgCtx, order.UserID, order.ID) + + // 抽奖或发货逻辑 + ord, _ := h.readDB.Orders.WithContext(bgCtx).Where(h.readDB.Orders.ID.Eq(order.ID)).First() + if ord == nil { + return + } + actID := parseActivityIDFromRemark(ord.Remark) + act, _ := h.readDB.Activities.WithContext(bgCtx).Where(h.readDB.Activities.ID.Eq(actID)).First() + + if ord.SourceType == 2 && act != nil && act.DrawMode == "instant" { + _ = h.activity.ProcessOrderLottery(bgCtx, ord.ID) + } else if ord.SourceType != 2 && ord.SourceType != 3 { + // 普通商品虚拟发货 + payerOpenid := "" + if transaction.Payer != nil && transaction.Payer.Openid != nil { + payerOpenid = *transaction.Payer.Openid } - s := strings.Join(parts, ", ") - itemsDesc = pkgutils.TruncateRunes(s, 120) - } else { - itemsDesc = "订单" + ord.OrderNo - } - payerOpenid := "" - if transaction.Payer != nil && transaction.Payer.Openid != nil { - payerOpenid = *transaction.Payer.Openid - } - // 抽奖订单(2)和对对碰订单(3)在开奖/结算后发货,非此类订单在支付后立即发货 - if ord.SourceType != 2 && ord.SourceType != 3 { - if transaction.TransactionId != nil && *transaction.TransactionId != "" { - fmt.Printf("[支付回调] 虚拟发货 尝试上传 order_id=%d order_no=%s user_id=%d transaction_id=%s items_desc=%s payer_openid=%s\n", ord.ID, ord.OrderNo, ord.UserID, *transaction.TransactionId, itemsDesc, payerOpenid) - if err := wechat.UploadVirtualShippingWithFallback(ctx, &wechat.WechatConfig{AppID: c.Wechat.AppID, AppSecret: c.Wechat.AppSecret}, *transaction.TransactionId, ord.OrderNo, payerOpenid, itemsDesc, time.Now()); err != nil { - fmt.Printf("[支付回调] 虚拟发货上传失败: %v\n", err) + itemsDesc := "订单" + ord.OrderNo + if txID := func() string { + if transaction.TransactionId != nil { + return *transaction.TransactionId } + return "" + }(); txID != "" { + _ = wechat.UploadVirtualShippingWithFallback(ctx, &wechat.WechatConfig{AppID: configs.Get().Wechat.AppID, AppSecret: configs.Get().Wechat.AppSecret}, txID, ord.OrderNo, payerOpenid, itemsDesc, time.Now()) } - } else { - fmt.Printf("[支付回调] 抽奖/对对碰订单跳过虚拟发货,将在开奖/结算后发货 order_id=%d order_no=%s source_type=%d\n", ord.ID, ord.OrderNo, ord.SourceType) } - } - // 标记事件处理完成 - if notification != nil && notification.ID != "" { - _, _ = h.writeDB.PaymentNotifyEvents.WithContext(ctx.RequestContext()).Where(h.readDB.PaymentNotifyEvents.NotifyID.Eq(notification.ID)).Updates(map[string]any{ - h.writeDB.PaymentNotifyEvents.Processed.ColumnName().String(): true, - }) - } + }() + ctx.Payload(¬ifyAck{Code: "SUCCESS", Message: "OK"}) } } +// handleRefund 处理一番赏冲突退款 +func (h *handler) handleRefund(ctx context.Context, ord *model.Orders, reason string) { + wc, e := pay.NewWechatPayClient(ctx) + if e != nil { + h.logger.Error("Failed to create wechat pay client for refund", zap.Error(e)) + return + } + refundNo := fmt.Sprintf("R%s-%d", ord.OrderNo, time.Now().Unix()) + iss := parseIssueIDFromRemark(ord.Remark) + refundID, status, e2 := wc.RefundOrder(ctx, ord.OrderNo, refundNo, ord.ActualAmount, ord.ActualAmount, reason) + if e2 != nil { + h.logger.Error("Refund failed", zap.Error(e2), zap.String("order_no", ord.OrderNo)) + return + } + _ = h.writeDB.PaymentRefunds.WithContext(ctx).Create(&model.PaymentRefunds{OrderID: ord.ID, OrderNo: ord.OrderNo, RefundNo: refundNo, Channel: "wechat_jsapi", Status: status, AmountRefund: ord.ActualAmount, Reason: reason}) + _ = h.writeDB.UserPointsLedger.WithContext(ctx).Create(&model.UserPointsLedger{UserID: ord.UserID, Action: "refund_amount", Points: ord.ActualAmount / 100, RefTable: "payment_refund", RefID: refundID}) + _, _ = h.writeDB.Orders.WithContext(ctx).Where(h.writeDB.Orders.ID.Eq(ord.ID)).Update(h.writeDB.Orders.Status, 4) + _ = h.repo.GetDbW().Exec("INSERT INTO lottery_refund_logs(issue_id, order_id, user_id, amount, coupon_type, coupon_amount, reason, status) VALUES(?,?,?,?,?,?,?,?)", iss, ord.ID, ord.UserID, ord.ActualAmount, "", 0, reason, status).Error +} + func parseIssueIDFromRemark(remark string) int64 { if remark == "" { return 0 diff --git a/internal/api/user/address_share_create_app.go b/internal/api/user/address_share_create_app.go index 1574c66..7da833a 100644 --- a/internal/api/user/address_share_create_app.go +++ b/internal/api/user/address_share_create_app.go @@ -17,6 +17,7 @@ type addressShareCreateRequest struct { type addressShareCreateResponse struct { ShareToken string `json:"share_token"` ShareURL string `json:"share_url"` + ShortLink string `json:"short_link"` ExpiresAt time.Time `json:"expires_at"` } @@ -54,12 +55,13 @@ func (h *handler) CreateAddressShare() core.HandlerFunc { exp = time.Now().Add(60 * time.Minute) } userID := int64(ctx.SessionUserInfo().Id) - token, exp, err := h.user.CreateAddressShare(ctx.RequestContext(), userID, req.InventoryID, exp) + token, shortLink, exp, err := h.user.CreateAddressShare(ctx.RequestContext(), userID, req.InventoryID, exp) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 10020, err.Error())) return } rsp.ShareToken = token + rsp.ShortLink = shortLink rsp.ExpiresAt = exp rsp.ShareURL = "/api/app/address-share/" + token + "/submit" ctx.Payload(rsp) diff --git a/internal/api/user/address_share_submit_public.go b/internal/api/user/address_share_submit_public.go index d8f6ebd..945b0cc 100644 --- a/internal/api/user/address_share_submit_public.go +++ b/internal/api/user/address_share_submit_public.go @@ -2,9 +2,12 @@ package app import ( "net/http" + "strings" + "bindbox-game/configs" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" + "bindbox-game/internal/pkg/jwtoken" ) type addressShareSubmitRequest struct { @@ -39,11 +42,31 @@ func (h *handler) SubmitAddressShare() core.HandlerFunc { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "参数错误")) return } + + // 尝试获取登录用户信息 (可选) var submitUserID *int64 + authHeader := ctx.GetHeader("Authorization") + if authHeader != "" { + // 如果有 Authorization 尝试解析 + if claims, err := jwtoken.New(configs.Get().JWT.PatientSecret).Parse(authHeader); err == nil { + uid := int64(claims.SessionUserInfo.Id) + submitUserID = &uid + } + } + ip := ctx.Request().RemoteAddr + // 统一使用 ctx.RequestContext() 包含 context 内容 addrID, err := h.user.SubmitAddressShare(ctx.RequestContext(), req.ShareToken, req.Name, req.Mobile, req.Province, req.City, req.District, req.Address, submitUserID, &ip) if err != nil { - ctx.AbortWithError(core.Error(http.StatusBadRequest, 10024, err.Error())) + // 处理业务错误,映射到具体代码 + msg := err.Error() + errorCode := 10024 + if strings.Contains(msg, "invalid_or_expired_token") { + errorCode = 10025 + } else if strings.Contains(msg, "already_processed") { + errorCode = 10026 + } + ctx.AbortWithError(core.Error(http.StatusBadRequest, errorCode, msg)) return } rsp.AddressID = addrID diff --git a/internal/pkg/wechat/shortlink.go b/internal/pkg/wechat/shortlink.go new file mode 100644 index 0000000..5511d64 --- /dev/null +++ b/internal/pkg/wechat/shortlink.go @@ -0,0 +1,69 @@ +package wechat + +import ( + "encoding/json" + "fmt" + "net/http" + + "bindbox-game/internal/pkg/httpclient" +) + +// ShortLinkRequest 获取小程序短链请求参数 +type ShortLinkRequest struct { + PagePath string `json:"page_path"` + PageTitle string `json:"page_title,omitempty"` + IsPermanent bool `json:"is_permanent,omitempty"` +} + +// ShortLinkResponse 获取小程序短链响应 +type ShortLinkResponse struct { + Link string `json:"link"` + ErrCode int `json:"errcode,omitempty"` + ErrMsg string `json:"errmsg,omitempty"` +} + +// GetShortLink 获取小程序短链 +// pagePath: 页面路径,如 pages/address/submit?token=xxx +// pageTitle: 页面标题,如 "送你一个盲盒奖品" +func GetShortLink(accessToken string, pagePath string, pageTitle string) (string, error) { + if accessToken == "" { + return "", fmt.Errorf("access_token 不能为空") + } + + url := fmt.Sprintf("https://api.weixin.qq.com/wxa/genwxaqshortlink?access_token=%s", accessToken) + + reqBody := ShortLinkRequest{ + PagePath: pagePath, + PageTitle: pageTitle, + IsPermanent: false, + } + + requestBody, err := json.Marshal(reqBody) + if err != nil { + return "", fmt.Errorf("序列化请求参数失败: %v", err) + } + + resp, err := httpclient.GetHttpClient().R(). + SetHeader("Content-Type", "application/json"). + SetBody(requestBody). + Post(url) + + if err != nil { + return "", fmt.Errorf("发送HTTP请求失败: %v", err) + } + + if resp.StatusCode() != http.StatusOK { + return "", fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode()) + } + + var slResp ShortLinkResponse + if err := json.Unmarshal(resp.Body(), &slResp); err != nil { + return "", fmt.Errorf("解析响应失败: %v", err) + } + + if slResp.ErrCode != 0 { + return "", fmt.Errorf("获取短链失败: errcode=%d, errmsg=%s", slResp.ErrCode, slResp.ErrMsg) + } + + return slResp.Link, nil +} diff --git a/internal/repository/mysql/dao/activity_draw_logs.gen.go b/internal/repository/mysql/dao/activity_draw_logs.gen.go index b7ac991..691e415 100644 --- a/internal/repository/mysql/dao/activity_draw_logs.gen.go +++ b/internal/repository/mysql/dao/activity_draw_logs.gen.go @@ -36,6 +36,7 @@ func newActivityDrawLogs(db *gorm.DB, opts ...gen.DOOption) activityDrawLogs { _activityDrawLogs.IsWinner = field.NewInt32(tableName, "is_winner") _activityDrawLogs.Level = field.NewInt32(tableName, "level") _activityDrawLogs.CurrentLevel = field.NewInt32(tableName, "current_level") + _activityDrawLogs.DrawIndex = field.NewInt64(tableName, "draw_index") _activityDrawLogs.fillFieldMap() @@ -56,6 +57,7 @@ type activityDrawLogs struct { IsWinner field.Int32 // 是否中奖:0否 1是 Level field.Int32 // 中奖等级(如1=S 2=A 3=B) CurrentLevel field.Int32 // 当前层(针对爬塔)其它默认为1 + DrawIndex field.Int64 // 抽奖序号(0-N) fieldMap map[string]field.Expr } @@ -81,6 +83,7 @@ func (a *activityDrawLogs) updateTableName(table string) *activityDrawLogs { a.IsWinner = field.NewInt32(table, "is_winner") a.Level = field.NewInt32(table, "level") a.CurrentLevel = field.NewInt32(table, "current_level") + a.DrawIndex = field.NewInt64(table, "draw_index") a.fillFieldMap() @@ -97,7 +100,7 @@ func (a *activityDrawLogs) GetFieldByName(fieldName string) (field.OrderExpr, bo } func (a *activityDrawLogs) fillFieldMap() { - a.fieldMap = make(map[string]field.Expr, 9) + a.fieldMap = make(map[string]field.Expr, 10) a.fieldMap["id"] = a.ID a.fieldMap["created_at"] = a.CreatedAt a.fieldMap["user_id"] = a.UserID @@ -107,6 +110,7 @@ func (a *activityDrawLogs) fillFieldMap() { a.fieldMap["is_winner"] = a.IsWinner a.fieldMap["level"] = a.Level a.fieldMap["current_level"] = a.CurrentLevel + a.fieldMap["draw_index"] = a.DrawIndex } func (a activityDrawLogs) clone(db *gorm.DB) activityDrawLogs { diff --git a/internal/repository/mysql/dao/user_inventory.gen.go b/internal/repository/mysql/dao/user_inventory.gen.go index 85df988..bb19b62 100644 --- a/internal/repository/mysql/dao/user_inventory.gen.go +++ b/internal/repository/mysql/dao/user_inventory.gen.go @@ -36,6 +36,7 @@ func newUserInventory(db *gorm.DB, opts ...gen.DOOption) userInventory { _userInventory.ActivityID = field.NewInt64(tableName, "activity_id") _userInventory.RewardID = field.NewInt64(tableName, "reward_id") _userInventory.Status = field.NewInt32(tableName, "status") + _userInventory.ShippingNo = field.NewString(tableName, "shipping_no") _userInventory.Remark = field.NewString(tableName, "remark") _userInventory.fillFieldMap() @@ -57,6 +58,7 @@ type userInventory struct { ActivityID field.Int64 // 来源活动ID RewardID field.Int64 // 来源奖励ID(activity_reward_settings.id) Status field.Int32 // 状态:1持有 2作废 3已使用/发货 + ShippingNo field.String // 发货单号 Remark field.String // 备注 fieldMap map[string]field.Expr @@ -83,6 +85,7 @@ func (u *userInventory) updateTableName(table string) *userInventory { u.ActivityID = field.NewInt64(table, "activity_id") u.RewardID = field.NewInt64(table, "reward_id") u.Status = field.NewInt32(table, "status") + u.ShippingNo = field.NewString(table, "shipping_no") u.Remark = field.NewString(table, "remark") u.fillFieldMap() @@ -100,7 +103,7 @@ func (u *userInventory) GetFieldByName(fieldName string) (field.OrderExpr, bool) } func (u *userInventory) fillFieldMap() { - u.fieldMap = make(map[string]field.Expr, 10) + u.fieldMap = make(map[string]field.Expr, 11) u.fieldMap["id"] = u.ID u.fieldMap["created_at"] = u.CreatedAt u.fieldMap["updated_at"] = u.UpdatedAt @@ -110,6 +113,7 @@ func (u *userInventory) fillFieldMap() { u.fieldMap["activity_id"] = u.ActivityID u.fieldMap["reward_id"] = u.RewardID u.fieldMap["status"] = u.Status + u.fieldMap["shipping_no"] = u.ShippingNo u.fieldMap["remark"] = u.Remark } diff --git a/internal/repository/mysql/model/activity_draw_logs.gen.go b/internal/repository/mysql/model/activity_draw_logs.gen.go index 2d0eade..abd7412 100644 --- a/internal/repository/mysql/model/activity_draw_logs.gen.go +++ b/internal/repository/mysql/model/activity_draw_logs.gen.go @@ -21,6 +21,7 @@ type ActivityDrawLogs struct { IsWinner int32 `gorm:"column:is_winner;not null;comment:是否中奖:0否 1是" json:"is_winner"` // 是否中奖:0否 1是 Level int32 `gorm:"column:level;not null;comment:中奖等级(如1=S 2=A 3=B)" json:"level"` // 中奖等级(如1=S 2=A 3=B) CurrentLevel int32 `gorm:"column:current_level;comment:当前层(针对爬塔)其它默认为1" json:"current_level"` // 当前层(针对爬塔)其它默认为1 + DrawIndex int64 `gorm:"column:draw_index;not null;comment:抽奖序号(0-N)" json:"draw_index"` // 抽奖序号(0-N) } // TableName ActivityDrawLogs's table name diff --git a/internal/repository/mysql/model/user_inventory.gen.go b/internal/repository/mysql/model/user_inventory.gen.go index 9f0402b..208d5dd 100644 --- a/internal/repository/mysql/model/user_inventory.gen.go +++ b/internal/repository/mysql/model/user_inventory.gen.go @@ -21,6 +21,7 @@ type UserInventory struct { ActivityID int64 `gorm:"column:activity_id;comment:来源活动ID" json:"activity_id"` // 来源活动ID RewardID int64 `gorm:"column:reward_id;comment:来源奖励ID(activity_reward_settings.id)" json:"reward_id"` // 来源奖励ID(activity_reward_settings.id) Status int32 `gorm:"column:status;not null;default:1;comment:状态:1持有 2作废 3已使用/发货" json:"status"` // 状态:1持有 2作废 3已使用/发货 + ShippingNo string `gorm:"column:shipping_no;not null;comment:发货单号" json:"shipping_no"` // 发货单号 Remark string `gorm:"column:remark;comment:备注" json:"remark"` // 备注 } diff --git a/internal/router/router.go b/internal/router/router.go index d9439cf..590aa4d 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -16,6 +16,7 @@ import ( "bindbox-game/internal/pkg/redis" "bindbox-game/internal/repository/mysql" "bindbox-game/internal/router/interceptor" + activitysvc "bindbox-game/internal/service/activity" tasksvc "bindbox-game/internal/service/task_center" titlesvc "bindbox-game/internal/service/title" usersvc "bindbox-game/internal/service/user" @@ -52,6 +53,7 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er userSvc := usersvc.New(logger, db) titleSvc := titlesvc.New(logger, db) taskSvc := tasksvc.New(logger, db, rdb, userSvc, titleSvc) + activitySvc := activitysvc.New(logger, db, userSvc, rdb) // Context for Worker ctx, cancel := context.WithCancel(context.Background()) @@ -59,13 +61,13 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er go taskSvc.StartWorker(ctx) // 实例化拦截器 - adminHandler := admin.New(logger, db) + adminHandler := admin.New(logger, db, rdb) activityHandler := activityapi.New(logger, db, rdb) taskCenterHandler := taskcenterapi.New(logger, db, taskSvc) userHandler := userapi.New(logger, db) commonHandler := commonapi.New(logger, db) - payHandler := payapi.New(logger, db, taskSvc) + payHandler := payapi.New(logger, db, taskSvc, activitySvc) gameHandler := gameapi.New(logger, db, rdb, userSvc) intc := interceptor.New(logger, db) @@ -126,23 +128,23 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er adminAuthApiRouter.GET("/dashboard/activities", adminHandler.DashboardActivities()) adminAuthApiRouter.GET("/dashboard/activity_prize_analysis", adminHandler.DashboardActivityPrizeAnalysis()) adminAuthApiRouter.GET("/dashboard/user_overview", adminHandler.DashboardUserOverview()) - adminAuthApiRouter.POST("/activities", adminHandler.CreateActivity()) - adminAuthApiRouter.GET("/activities", adminHandler.ListActivities()) - adminAuthApiRouter.PUT("/activities/:activity_id", adminHandler.ModifyActivity()) - adminAuthApiRouter.DELETE("/activities/:activity_id", adminHandler.DeleteActivity()) - adminAuthApiRouter.GET("/activities/:activity_id", adminHandler.GetActivityDetail()) - adminAuthApiRouter.POST("/lottery/activities/:activity_id/scheduled_config", adminHandler.SetScheduledConfig()) - adminAuthApiRouter.GET("/lottery/activities/:activity_id/scheduled_config", adminHandler.GetScheduledConfig()) - adminAuthApiRouter.POST("/activities/:activity_id/copy", adminHandler.CopyActivity()) - adminAuthApiRouter.GET("/activities/:activity_id/issues", adminHandler.ListActivityIssues()) - adminAuthApiRouter.POST("/activities/:activity_id/issues", adminHandler.CreateActivityIssue()) - adminAuthApiRouter.PUT("/activities/:activity_id/issues/:issue_id", adminHandler.ModifyActivityIssue()) - adminAuthApiRouter.DELETE("/activities/:activity_id/issues/:issue_id", adminHandler.DeleteActivityIssue()) - adminAuthApiRouter.POST("/activities/:activity_id/issues/:issue_id/rewards", adminHandler.CreateIssueRewards()) - adminAuthApiRouter.GET("/activities/:activity_id/issues/:issue_id/rewards", adminHandler.ListIssueRewards()) - adminAuthApiRouter.PUT("/activities/:activity_id/issues/:issue_id/rewards/:reward_id", adminHandler.ModifyIssueReward()) - adminAuthApiRouter.PUT("/activities/:activity_id/issues/:issue_id/rewards/batch", adminHandler.BatchModifyIssueRewards()) - adminAuthApiRouter.DELETE("/activities/:activity_id/issues/:issue_id/rewards/:reward_id", adminHandler.DeleteIssueReward()) + adminAuthApiRouter.POST("/activities", intc.RequireAdminAction("activity:create"), adminHandler.CreateActivity()) + adminAuthApiRouter.GET("/activities", intc.RequireAdminAction("activity:view"), adminHandler.ListActivities()) + adminAuthApiRouter.PUT("/activities/:activity_id", intc.RequireAdminAction("activity:modify"), adminHandler.ModifyActivity()) + adminAuthApiRouter.DELETE("/activities/:activity_id", intc.RequireAdminAction("activity:delete"), adminHandler.DeleteActivity()) + adminAuthApiRouter.GET("/activities/:activity_id", intc.RequireAdminAction("activity:view"), adminHandler.GetActivityDetail()) + adminAuthApiRouter.POST("/lottery/activities/:activity_id/scheduled_config", intc.RequireAdminAction("activity:modify"), adminHandler.SetScheduledConfig()) + adminAuthApiRouter.GET("/lottery/activities/:activity_id/scheduled_config", intc.RequireAdminAction("activity:view"), adminHandler.GetScheduledConfig()) + adminAuthApiRouter.POST("/activities/:activity_id/copy", intc.RequireAdminAction("activity:create"), adminHandler.CopyActivity()) + adminAuthApiRouter.GET("/activities/:activity_id/issues", intc.RequireAdminAction("activity:view"), adminHandler.ListActivityIssues()) + adminAuthApiRouter.POST("/activities/:activity_id/issues", intc.RequireAdminAction("activity:modify"), adminHandler.CreateActivityIssue()) + adminAuthApiRouter.PUT("/activities/:activity_id/issues/:issue_id", intc.RequireAdminAction("activity:modify"), adminHandler.ModifyActivityIssue()) + adminAuthApiRouter.DELETE("/activities/:activity_id/issues/:issue_id", intc.RequireAdminAction("activity:delete"), adminHandler.DeleteActivityIssue()) + adminAuthApiRouter.POST("/activities/:activity_id/issues/:issue_id/rewards", intc.RequireAdminAction("activity:modify"), adminHandler.CreateIssueRewards()) + adminAuthApiRouter.GET("/activities/:activity_id/issues/:issue_id/rewards", intc.RequireAdminAction("activity:view"), adminHandler.ListIssueRewards()) + adminAuthApiRouter.PUT("/activities/:activity_id/issues/:issue_id/rewards/:reward_id", intc.RequireAdminAction("activity:modify"), adminHandler.ModifyIssueReward()) + adminAuthApiRouter.PUT("/activities/:activity_id/issues/:issue_id/rewards/batch", intc.RequireAdminAction("activity:modify"), adminHandler.BatchModifyIssueRewards()) + adminAuthApiRouter.DELETE("/activities/:activity_id/issues/:issue_id/rewards/:reward_id", intc.RequireAdminAction("activity:delete"), adminHandler.DeleteIssueReward()) // 已移除:批量造数/批量删除用户接口(未被前端使用) @@ -178,24 +180,24 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er adminAuthApiRouter.DELETE("/system/configs/:id", adminHandler.DeleteSystemConfig()) // 用户管理 - adminAuthApiRouter.GET("/users", adminHandler.ListAppUsers()) - adminAuthApiRouter.GET("/users/:user_id/invites", adminHandler.ListUserInvites()) - adminAuthApiRouter.GET("/users/:user_id/orders", adminHandler.ListUserOrders()) - adminAuthApiRouter.GET("/users/:user_id/coupons", adminHandler.ListUserCoupons()) - adminAuthApiRouter.GET("/users/:user_id/coupons/:user_coupon_id/usage", adminHandler.ListUserCouponUsage()) - adminAuthApiRouter.GET("/users/:user_id/points", adminHandler.ListUserPoints()) - adminAuthApiRouter.GET("/users/:user_id/points/balance", adminHandler.GetUserPointsBalance()) - adminAuthApiRouter.POST("/users/:user_id/points/add", adminHandler.AddUserPoints()) - adminAuthApiRouter.POST("/users/:user_id/coupons/add", adminHandler.AddUserCoupon()) - adminAuthApiRouter.POST("/users/:user_id/coupons/:user_coupon_id/void", adminHandler.VoidUserCoupon()) - adminAuthApiRouter.POST("/users/:user_id/rewards/grant", adminHandler.GrantReward()) - adminAuthApiRouter.GET("/users/:user_id/titles", adminHandler.ListUserTitles()) - adminAuthApiRouter.POST("/users/:user_id/token", adminHandler.IssueUserToken()) - adminAuthApiRouter.POST("/users/batch/points/add", adminHandler.BatchAddUserPoints()) - adminAuthApiRouter.POST("/users/batch/coupons/add", adminHandler.BatchAddUserCoupons()) - adminAuthApiRouter.POST("/users/batch/rewards/grant", adminHandler.BatchGrantUserRewards()) - adminAuthApiRouter.GET("/users/:user_id/inventory", adminHandler.ListUserInventory()) - adminAuthApiRouter.POST("/users/:user_id/inventory/:inventory_id/void", adminHandler.VoidUserInventory()) + adminAuthApiRouter.GET("/users", intc.RequireAdminAction("user:view"), adminHandler.ListAppUsers()) + adminAuthApiRouter.GET("/users/:user_id/invites", intc.RequireAdminAction("user:view"), adminHandler.ListUserInvites()) + adminAuthApiRouter.GET("/users/:user_id/orders", intc.RequireAdminAction("user:view"), adminHandler.ListUserOrders()) + adminAuthApiRouter.GET("/users/:user_id/coupons", intc.RequireAdminAction("user:view"), adminHandler.ListUserCoupons()) + adminAuthApiRouter.GET("/users/:user_id/coupons/:user_coupon_id/usage", intc.RequireAdminAction("user:view"), adminHandler.ListUserCouponUsage()) + adminAuthApiRouter.GET("/users/:user_id/points", intc.RequireAdminAction("user:view"), adminHandler.ListUserPoints()) + adminAuthApiRouter.GET("/users/:user_id/points/balance", intc.RequireAdminAction("user:view"), adminHandler.GetUserPointsBalance()) + adminAuthApiRouter.POST("/users/:user_id/points/add", intc.RequireAdminAction("user:points:add"), adminHandler.AddUserPoints()) + adminAuthApiRouter.POST("/users/:user_id/coupons/add", intc.RequireAdminAction("user:coupons:add"), adminHandler.AddUserCoupon()) + adminAuthApiRouter.POST("/users/:user_id/coupons/:user_coupon_id/void", intc.RequireAdminAction("user:coupons:void"), adminHandler.VoidUserCoupon()) + adminAuthApiRouter.POST("/users/:user_id/rewards/grant", intc.RequireAdminAction("user:rewards:grant"), adminHandler.GrantReward()) + adminAuthApiRouter.GET("/users/:user_id/titles", intc.RequireAdminAction("user:view"), adminHandler.ListUserTitles()) + adminAuthApiRouter.POST("/users/:user_id/token", intc.RequireAdminAction("user:token:issue"), adminHandler.IssueUserToken()) + adminAuthApiRouter.POST("/users/batch/points/add", intc.RequireAdminAction("user:points:batch:add"), adminHandler.BatchAddUserPoints()) + adminAuthApiRouter.POST("/users/batch/coupons/add", intc.RequireAdminAction("user:coupons:batch:add"), adminHandler.BatchAddUserCoupons()) + adminAuthApiRouter.POST("/users/batch/rewards/grant", intc.RequireAdminAction("user:rewards:batch:grant"), adminHandler.BatchGrantUserRewards()) + adminAuthApiRouter.GET("/users/:user_id/inventory", intc.RequireAdminAction("user:view"), adminHandler.ListUserInventory()) + adminAuthApiRouter.POST("/users/:user_id/inventory/:inventory_id/void", intc.RequireAdminAction("user:inventory:void"), adminHandler.VoidUserInventory()) adminAuthApiRouter.GET("/users/:user_id/item_cards", adminHandler.ListUserItemCards()) adminAuthApiRouter.POST("/users/:user_id/item_cards/:user_item_card_id/void", adminHandler.VoidUserItemCard()) // 已移除:为指定用户签发APP令牌接口(未被前端使用) diff --git a/internal/service/activity/activity.go b/internal/service/activity/activity.go index e544dc5..66236b5 100644 --- a/internal/service/activity/activity.go +++ b/internal/service/activity/activity.go @@ -8,9 +8,18 @@ import ( "bindbox-game/internal/repository/mysql" "bindbox-game/internal/repository/mysql/dao" "bindbox-game/internal/repository/mysql/model" + usersvc "bindbox-game/internal/service/user" + + "github.com/redis/go-redis/v9" ) type Service interface { + // ... (other methods) + // ProcessOrderLottery 处理订单开奖(统一幂等逻辑) + // 参数: ctx 上下文, orderID 订单ID + // 返回: 错误信息 + ProcessOrderLottery(ctx context.Context, orderID int64) error + // CreateActivity 创建活动 // 参数: in 活动创建输入 // 返回: 活动记录与错误 @@ -96,14 +105,18 @@ type service struct { readDB *dao.Query writeDB *dao.Query repo mysql.Repo + user usersvc.Service + redis *redis.Client } -func New(l logger.CustomLogger, db mysql.Repo) Service { +func New(l logger.CustomLogger, db mysql.Repo, u usersvc.Service, rdb *redis.Client) Service { return &service{ logger: l, readDB: dao.Use(db.GetDbR()), writeDB: dao.Use(db.GetDbW()), repo: db, + user: u, + redis: rdb, } } diff --git a/internal/service/activity/lottery_process.go b/internal/service/activity/lottery_process.go new file mode 100644 index 0000000..181a065 --- /dev/null +++ b/internal/service/activity/lottery_process.go @@ -0,0 +1,377 @@ +package activity + +import ( + "context" + "fmt" + "strings" + "time" + + "bindbox-game/configs" + "bindbox-game/internal/pkg/notify" + "bindbox-game/internal/pkg/wechat" + "bindbox-game/internal/repository/mysql/model" + strat "bindbox-game/internal/service/activity/strategy" + usersvc "bindbox-game/internal/service/user" + + "go.uber.org/zap" +) + +// ProcessOrderLottery 处理订单开奖(统原子化高性能幂等逻辑) +func (s *service) ProcessOrderLottery(ctx context.Context, orderID int64) error { + s.logger.Info("开始原子化处理订单开奖", zap.Int64("order_id", orderID)) + + // 1. Redis 分布式锁:强制同一个订单串行处理,防止并发竞态引起的超发/漏发 + lockKey := fmt.Sprintf("lock:lottery:order:%d", orderID) + locked, err := s.redis.SetNX(ctx, lockKey, "1", 30*time.Second).Result() + if err != nil { + return fmt.Errorf("分布式锁获取异常: %w", err) + } + if !locked { + s.logger.Info("订单开奖锁已存在,跳过本次处理", zap.Int64("order_id", orderID)) + return nil + } + defer s.redis.Del(ctx, lockKey) + + // 2. 批量预加载快照:将后续循环所需的查询合并为一次性加载 + order, err := s.readDB.Orders.WithContext(ctx).Where(s.readDB.Orders.ID.Eq(orderID)).First() + if err != nil || order == nil { + return fmt.Errorf("订单查询失败或不存在") + } + + // 状态前校验:仅处理已支付且未取消的抽奖订单 + if order.Status != 2 || order.SourceType != 2 { + return nil + } + + aid := extractActivityID(order.Remark) + iss := extractIssueID(order.Remark) + dc := extractCount(order.Remark) + icID := extractItemCardID(order.Remark) + if aid <= 0 || iss <= 0 { + return fmt.Errorf("订单备注关键信息缺失") + } + + orderNo := order.OrderNo + userID := order.UserID + + // 2.1 批量加载配置 + act, _ := s.readDB.Activities.WithContext(ctx).Where(s.readDB.Activities.ID.Eq(aid)).First() + actName := "活动" + playType := "default" + if act != nil { + actName = act.Name + playType = act.PlayType + } + + rewardSettings, _ := s.readDB.ActivityRewardSettings.WithContext(ctx).Where(s.readDB.ActivityRewardSettings.IssueID.Eq(iss)).Find() + rewardMap := make(map[int64]*model.ActivityRewardSettings) + for _, r := range rewardSettings { + rewardMap[r.ID] = r + } + + // 2.2 批量加载已有记录 + existingLogs, _ := s.readDB.ActivityDrawLogs.WithContext(ctx).Where(s.readDB.ActivityDrawLogs.OrderID.Eq(orderID)).Order(s.readDB.ActivityDrawLogs.DrawIndex).Find() + logMap := make(map[int64]*model.ActivityDrawLogs) + for _, l := range existingLogs { + logMap[l.DrawIndex] = l + } + + existingInventory, _ := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.OrderID.Eq(orderID)).Find() + invCountMap := make(map[int64]int64) + for _, inv := range existingInventory { + invCountMap[inv.RewardID]++ + } + + // 2.3 策略准备 + var sel strat.ActivityDrawStrategy + if act != nil && act.PlayType == "ichiban" { + sel = strat.NewIchiban(s.readDB, s.writeDB) + } else { + sel = strat.NewDefault(s.readDB, s.writeDB) + } + idxs, cnts := parseSlotsCountsFromRemark(order.Remark) + + // 3. 核心循环:基于内存快照进行原子发放 + for i := int64(0); i < dc; i++ { + log, exists := logMap[i] + if !exists { + // 选品:如果日志不存在,则根据策略选出一个奖品 ID + var rid int64 + var proof map[string]any + var selErr error + + if act != nil && act.PlayType == "ichiban" { + slot := getSlotForIndex(i, idxs, cnts) + if slot < 0 { + continue + } + rid, proof, selErr = sel.SelectItemBySlot(ctx, aid, iss, slot) + } else { + rid, proof, selErr = sel.SelectItem(ctx, aid, iss, order.UserID) + } + + if selErr != nil || rid <= 0 { + s.logger.Error("选品失败", zap.Int64("draw_index", i), zap.Error(selErr)) + continue + } + + rw := rewardMap[rid] + if rw == nil { + continue + } + + // 写入开奖日志 (依靠数据库 (order_id, draw_index) 唯一键保证绝对幂等) + log = &model.ActivityDrawLogs{ + UserID: order.UserID, IssueID: iss, OrderID: order.ID, + RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1, DrawIndex: i, + } + if errLog := s.writeDB.ActivityDrawLogs.WithContext(ctx).Create(log); errLog != nil { + // 并发情况:回退并尝试复用已有记录 + log, _ = s.readDB.ActivityDrawLogs.WithContext(ctx).Where(s.readDB.ActivityDrawLogs.OrderID.Eq(orderID), s.readDB.ActivityDrawLogs.DrawIndex.Eq(i)).First() + if log == nil { + continue + } + } + _ = strat.SaveDrawReceipt(ctx, s.writeDB, log.ID, iss, order.UserID, proof) + logMap[i] = log + } + + // 补发奖励校验:统计该 RewardID 应得数量并与实得数量对比 + needed := int64(0) + for j := int64(0); j <= i; j++ { + if l, ok := logMap[j]; ok && l.RewardID == log.RewardID { + needed++ + } + } + + if invCountMap[log.RewardID] < needed { + rw := rewardMap[log.RewardID] + if rw != nil { + _, errGrant := s.user.GrantRewardToOrder(ctx, order.UserID, usersvc.GrantRewardToOrderRequest{ + OrderID: order.ID, ProductID: rw.ProductID, Quantity: 1, + ActivityID: &aid, RewardID: &log.RewardID, Remark: rw.Name, + }) + if errGrant == nil { + invCountMap[log.RewardID]++ // 内存计数同步 + // 处理道具卡 + if act != nil && act.AllowItemCards && icID > 0 { + s.applyItemCardEffect(ctx, icID, aid, iss, log) + } + } else { + s.logger.Error("奖励原子发放失败", zap.Int64("draw_index", i), zap.Error(errGrant)) + } + } + } + } + + // 4. 异步触发外部同步逻辑 (微信虚拟发货/通知) + if order.IsConsumed == 0 { + go s.TriggerVirtualShipping(context.Background(), orderID, orderNo, userID, aid, actName, playType) + } + + return nil +} + +// TriggerVirtualShipping 触发虚拟发货同步到微信 +func (s *service) TriggerVirtualShipping(ctx context.Context, orderID int64, orderNo string, userID int64, aid int64, actName string, playType string) { + drawLogs, _ := s.readDB.ActivityDrawLogs.WithContext(ctx).Where(s.readDB.ActivityDrawLogs.OrderID.Eq(orderID)).Find() + if len(drawLogs) == 0 { + return + } + var rewardNames []string + for _, lg := range drawLogs { + if rw, _ := s.readDB.ActivityRewardSettings.WithContext(ctx).Where(s.readDB.ActivityRewardSettings.ID.Eq(lg.RewardID)).First(); rw != nil { + rewardNames = append(rewardNames, rw.Name) + } + } + itemsDesc := actName + " " + orderNo + " 盲盒赏品: " + strings.Join(rewardNames, ", ") + itemsDesc = utf8SafeTruncate(itemsDesc, 110) // 微信限制 128 字节,我们保守一点截断到 110 + tx, _ := s.readDB.PaymentTransactions.WithContext(ctx).Where(s.readDB.PaymentTransactions.OrderNo.Eq(orderNo)).First() + if tx == nil || tx.TransactionID == "" { + return + } + u, _ := s.readDB.Users.WithContext(ctx).Where(s.readDB.Users.ID.Eq(userID)).First() + payerOpenid := "" + if u != nil { + payerOpenid = u.Openid + } + c := configs.Get() + cfg := &wechat.WechatConfig{AppID: c.Wechat.AppID, AppSecret: c.Wechat.AppSecret} + errUpload := wechat.UploadVirtualShippingForBackground(ctx, cfg, tx.TransactionID, orderNo, payerOpenid, itemsDesc) + + // 如果发货成功,或者微信提示已经发过货了(10060003),则标记本地订单为已履约 + if errUpload == nil || strings.Contains(errUpload.Error(), "10060003") { + _, _ = s.writeDB.Orders.WithContext(ctx).Where(s.readDB.Orders.ID.Eq(orderID)).Update(s.readDB.Orders.IsConsumed, 1) + if errUpload != nil { + s.logger.Info("[虚拟发货] 微信反馈已处理,更新本地标记", zap.String("order_no", orderNo)) + } + } else if errUpload != nil { + s.logger.Error("[虚拟发货] 上传失败", zap.Error(errUpload), zap.String("order_no", orderNo)) + } + + if playType == "ichiban" { + notifyCfg := ¬ify.WechatNotifyConfig{ + AppID: c.Wechat.AppID, + AppSecret: c.Wechat.AppSecret, + LotteryResultTemplateID: c.Wechat.LotteryResultTemplateID, + } + _ = notify.SendLotteryResultNotification(ctx, notifyCfg, payerOpenid, actName, rewardNames, orderNo, time.Now()) + } +} + +func (s *service) applyItemCardEffect(ctx context.Context, icID int64, aid int64, iss int64, log *model.ActivityDrawLogs) { + uic, _ := s.readDB.UserItemCards.WithContext(ctx).Where(s.readDB.UserItemCards.ID.Eq(icID), s.readDB.UserItemCards.UserID.Eq(log.UserID), s.readDB.UserItemCards.Status.Eq(1)).First() + if uic == nil { + return + } + ic, _ := s.readDB.SystemItemCards.WithContext(ctx).Where(s.readDB.SystemItemCards.ID.Eq(uic.CardID), s.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 == aid) || (ic.ScopeType == 4 && ic.IssueID == iss) + if scopeOK { + if ic.EffectType == 1 && ic.RewardMultiplierX1000 >= 2000 { + rw, _ := s.readDB.ActivityRewardSettings.WithContext(ctx).Where(s.readDB.ActivityRewardSettings.ID.Eq(log.RewardID)).First() + if rw != nil { + _, _ = s.user.GrantRewardToOrder(ctx, log.UserID, usersvc.GrantRewardToOrderRequest{OrderID: log.OrderID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &aid, RewardID: &log.RewardID, Remark: rw.Name + "(倍数)"}) + } + } + _, _ = s.writeDB.UserItemCards.WithContext(ctx).Where(s.readDB.UserItemCards.ID.Eq(icID)).Updates(map[string]any{ + "status": 2, + "used_draw_log_id": log.ID, + "used_activity_id": aid, + "used_issue_id": iss, + "used_at": now, + }) + } + } +} + +// 辅助工具方法 +func extractActivityID(remark string) int64 { return parseActivityIDFromRemark(remark) } +func extractIssueID(remark string) int64 { return parseIssueIDFromRemark(remark) } +func extractCount(remark string) int64 { return parseCountFromRemark(remark) } +func extractItemCardID(remark string) int64 { return parseItemCardIDFromRemark(remark) } + +func parseActivityIDFromRemark(remark string) int64 { + parts := strings.Split(remark, "|") + for _, p := range parts { + if strings.HasPrefix(p, "lottery:activity:") { + return parseInt64(p[17:]) + } + if strings.HasPrefix(p, "activity:") { + return parseInt64(p[9:]) + } + } + return 0 +} +func parseIssueIDFromRemark(remark string) int64 { + parts := strings.Split(remark, "|") + for _, p := range parts { + if strings.HasPrefix(p, "issue:") { + return parseInt64(p[6:]) + } + } + return 0 +} +func parseCountFromRemark(remark string) int64 { + parts := strings.Split(remark, "|") + for _, p := range parts { + if strings.HasPrefix(p, "count:") { + n := parseInt64(p[6:]) + if n <= 0 { + return 1 + } + return n + } + } + return 1 +} +func parseItemCardIDFromRemark(remark string) int64 { + parts := strings.Split(remark, "|") + for _, seg := range parts { + if len(seg) > 9 && seg[:9] == "itemcard:" { + return parseInt64(seg[9:]) + } + } + return 0 +} +func parseInt64(s string) int64 { + var n int64 + for i := 0; i < len(s); i++ { + if s[i] < '0' || s[i] > '9' { + break + } + n = n*10 + int64(s[i]-'0') + } + return n +} + +// utf8SafeTruncate 按字节长度截断,并过滤掉无效/损坏的 UTF-8 字符 +func utf8SafeTruncate(s string, n int) string { + r := []rune(s) + var res []rune + var byteLen int + for _, val := range r { + // 过滤掉无效字符标识 \ufffd,防止微信 API 报错 + if val == '\uFFFD' { + continue + } + l := len(string(val)) + if byteLen+l > n { + break + } + res = append(res, val) + byteLen += l + } + return string(res) +} +func parseSlotsCountsFromRemark(remark string) ([]int64, []int64) { + parts := strings.Split(remark, "|") + for _, p := range parts { + if strings.HasPrefix(p, "slots:") { + pairs := p[6:] + var idxs []int64 + var cnts []int64 + start := 0 + for start < len(pairs) { + end := start + for end < len(pairs) && pairs[end] != ',' { + end++ + } + if end > start { + a := pairs[start:end] + var x, y int64 + j := 0 + for j < len(a) && a[j] >= '0' && a[j] <= '9' { + x = x*10 + int64(a[j]-'0') + j++ + } + if j < len(a) && a[j] == ':' { + j++ + for j < len(a) && a[j] >= '0' && a[j] <= '9' { + y = y*10 + int64(a[j]-'0') + j++ + } + } + if y > 0 { + idxs = append(idxs, x+1) + cnts = append(cnts, y) + } + } + start = end + 1 + } + return idxs, cnts + } + } + return nil, nil +} +func getSlotForIndex(drawIndex int64, idxs []int64, cnts []int64) int64 { + var current int64 + for i := 0; i < len(idxs); i++ { + if drawIndex < current+cnts[i] { + return idxs[i] - 1 + } + current += cnts[i] + } + return -1 +} diff --git a/internal/service/activity/scheduler.go b/internal/service/activity/scheduler.go index e9faaa9..f4a2c28 100644 --- a/internal/service/activity/scheduler.go +++ b/internal/service/activity/scheduler.go @@ -1,21 +1,18 @@ package activity import ( - "bindbox-game/configs" "bindbox-game/internal/pkg/logger" - "bindbox-game/internal/pkg/notify" paypkg "bindbox-game/internal/pkg/pay" - "bindbox-game/internal/pkg/wechat" "bindbox-game/internal/repository/mysql" "bindbox-game/internal/repository/mysql/dao" "bindbox-game/internal/repository/mysql/model" - strat "bindbox-game/internal/service/activity/strategy" usersvc "bindbox-game/internal/service/user" "context" "database/sql" "fmt" - "strings" "time" + + "github.com/redis/go-redis/v9" ) type scheduledConfig struct { @@ -31,10 +28,11 @@ type scheduledConfig struct { RefundCouponID int64 `json:"refund_coupon_id"` } -func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo) { +func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo, rdb *redis.Client) { r := dao.Use(repo.GetDbR()) w := dao.Use(repo.GetDbW()) us := usersvc.New(l, repo) + activitySvc := New(l, repo, us, rdb) // Ensure lottery_refund_logs table exists _ = repo.GetDbW().Exec(`CREATE TABLE IF NOT EXISTS lottery_refund_logs ( @@ -60,6 +58,9 @@ func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo) { ctx := context.Background() now := time.Now() fmt.Printf("[定时开奖] ====== 开始检查 时间=%s ======\n", now.Format("2006-01-02 15:04:05")) + + // 【独立检查】一番赏格位重置:每30秒检查所有售罄的一番赏期号 + checkAndResetIchibanSlots(ctx, repo, r) var acts []struct { ID int64 PlayType string @@ -122,6 +123,7 @@ func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo) { orders, _ := r.Orders.WithContext(ctx).ReadDB().Where( r.Orders.Status.Eq(2), r.Orders.SourceType.Eq(2), + r.Orders.IsConsumed.Eq(0), // 仅处理未履约的订单 r.Orders.Remark.Like(fmt.Sprintf("lottery:activity:%d|%%", aid)), r.Orders.CreatedAt.Gte(last), ).Find() @@ -181,6 +183,61 @@ func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo) { for _, o := range issueOrders { refundOrder(ctx, o, "ichiban_not_sold_out", wc, r, w, us, a.RefundCouponID) } + } else { + // 【Fix】已售罄:处理所有未开奖的订单(包括旧时间窗口的订单) + fmt.Printf("[定时开奖-一番赏] ✅ IssueID=%d 已售罄,检查并处理所有未开奖订单\n", iss) + + // Step 1: Get all order IDs from claims for this issue + var claimOrderIDs []int64 + errClaims := repo.GetDbR().Raw(` + SELECT DISTINCT c.order_id FROM issue_position_claims c + LEFT JOIN activity_draw_logs l ON l.order_id = c.order_id AND l.issue_id = c.issue_id + WHERE c.issue_id = ? AND l.id IS NULL + `, iss).Scan(&claimOrderIDs).Error + + if errClaims != nil { + fmt.Printf("[定时开奖-一番赏] ❌ 查询未处理订单ID失败: %v\n", errClaims) + } + + fmt.Printf("[定时开奖-一番赏] IssueID=%d 查询到 %d 个未处理订单ID: %v\n", iss, len(claimOrderIDs), claimOrderIDs) + + if len(claimOrderIDs) > 0 { + // Step 2: Fetch the actual order records + unprocessedOrders, errOrders := r.Orders.WithContext(ctx).Where( + r.Orders.ID.In(claimOrderIDs...), + r.Orders.Status.Eq(2), + ).Find() + + if errOrders != nil { + fmt.Printf("[定时开奖-一番赏] ❌ 获取订单详情失败: %v\n", errOrders) + } + + fmt.Printf("[定时开奖-一番赏] IssueID=%d 发现 %d 个未处理订单,开始补录\n", iss, len(unprocessedOrders)) + for _, o := range unprocessedOrders { + if err := activitySvc.ProcessOrderLottery(ctx, o.ID); err != nil { + fmt.Printf("[定时开奖-一番赏-补录] ❌ ProcessOrderLottery 失败: %v\n", err) + } + } + } + } + + // 【直接重置】无论是否有待处理订单,检查该期号是否可以重置 + var remainingUnprocessed int64 + _ = repo.GetDbR().Raw(` + SELECT COUNT(1) FROM issue_position_claims c + LEFT JOIN activity_draw_logs l ON l.order_id = c.order_id AND l.issue_id = c.issue_id + WHERE c.issue_id = ? AND l.id IS NULL + `, iss).Scan(&remainingUnprocessed) + + fmt.Printf("[定时开奖-一番赏] IssueID=%d 剩余未处理订单数: %d\n", iss, remainingUnprocessed) + + if remainingUnprocessed == 0 { + // 所有订单都已处理,执行重置 + if err := repo.GetDbW().Exec("DELETE FROM issue_position_claims WHERE issue_id = ?", iss).Error; err != nil { + fmt.Printf("[定时开奖-一番赏] ❌ 重置格位失败: %v\n", err) + } else { + fmt.Printf("[定时开奖-一番赏] ✅ IssueID=%d 所有订单已处理,格位已重置,新一轮可以开始\n", iss) + } } } } @@ -199,154 +256,14 @@ func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo) { } } else { fmt.Printf("[定时开奖] 活动ID=%d ✅ 人数满足(或一番赏模式),开始开奖处理\n", aid) - if a.PlayType == "ichiban" { - fmt.Printf("[定时开奖] 活动ID=%d 一番赏模式开奖,订单数=%d\n", aid, len(orders)) - // 一番赏定时开奖逻辑 - ichibanSel := strat.NewIchiban(r, w) - for _, o := range orders { - iss := extractIssueID(o.Remark) - - if refundedIssues[iss] { - fmt.Printf("[定时开奖-一番赏] OrderID=%d IssueID=%d 已退款,跳过开奖\n", o.ID, iss) - continue - } - - uid := o.UserID - fmt.Printf("[定时开奖-一番赏] 处理订单 OrderID=%d UserID=%d IssueID=%d\n", o.ID, uid, iss) - - // 检查是否已经处理过 - logs, _ := r.ActivityDrawLogs.WithContext(ctx).Where(r.ActivityDrawLogs.OrderID.Eq(o.ID)).Find() - if len(logs) > 0 { - fmt.Printf("[定时开奖-一番赏] 订单ID=%d 已处理过,跳过\n", o.ID) - continue - } - - // 查找该订单锁定的所有格位 - claims, _ := r.IssuePositionClaims.WithContext(ctx).ReadDB().Where( - r.IssuePositionClaims.IssueID.Eq(iss), - r.IssuePositionClaims.OrderID.Eq(o.ID), - ).Find() - fmt.Printf("[定时开奖-一番赏] 订单ID=%d 找到格位占用数=%d\n", o.ID, len(claims)) - - for claimIdx, claim := range claims { - fmt.Printf("[定时开奖-一番赏] 处理格位 SlotIndex=%d (索引=%d)\n", claim.SlotIndex, claimIdx) - - // 【幂等检查】检查该订单的该格位是否已经发过奖 - existingLogCnt, _ := r.ActivityDrawLogs.WithContext(ctx).Where( - r.ActivityDrawLogs.OrderID.Eq(o.ID), - r.ActivityDrawLogs.IssueID.Eq(iss), - ).Count() - if existingLogCnt > int64(claimIdx) { - fmt.Printf("[定时开奖-一番赏] ⚠️ 格位 SlotIndex=%d 已处理过(日志数=%d,当前索引=%d),跳过\n", claim.SlotIndex, existingLogCnt, claimIdx) - continue - } - - // 使用 claim 中的 slot_index 直接获取奖品 - // Use Commitment (via SelectItemBySlot internal logic) - rid, proof, err := ichibanSel.SelectItemBySlot(ctx, aid, iss, claim.SlotIndex) - if err != nil || rid <= 0 { - fmt.Printf("[定时开奖-一番赏] ❌ SelectItemBySlot失败 err=%v rid=%d\n", err, rid) - continue - } - - rw, err := r.ActivityRewardSettings.WithContext(ctx).Where(r.ActivityRewardSettings.ID.Eq(rid)).First() - if err != nil || rw == nil { - fmt.Printf("[定时开奖-一番赏] ❌ 奖品设置不存在 rid=%d\n", rid) - continue - } - - // 【先记录日志,再发奖】确保日志创建成功后再发奖,防止重复 - drawLog := &model.ActivityDrawLogs{ - UserID: uid, - IssueID: iss, - OrderID: o.ID, - RewardID: rid, - IsWinner: 1, - Level: rw.Level, - CurrentLevel: 1, - } - if err := w.ActivityDrawLogs.WithContext(ctx).Create(drawLog); err != nil { - fmt.Printf("[定时开奖-一番赏] ❌ 创建开奖日志失败 err=%v,可能已存在,跳过\n", err) - continue - } - - // 发放奖励(在原订单上添加中奖商品,不创建新订单) - fmt.Printf("[定时开奖-一番赏] 发放奖励到原订单 OrderID=%d UserID=%d RewardID=%d 奖品名=%s\n", o.ID, uid, rid, rw.Name) - _, err = us.GrantRewardToOrder(ctx, uid, usersvc.GrantRewardToOrderRequest{ - OrderID: o.ID, - ProductID: rw.ProductID, - Quantity: 1, - ActivityID: &aid, - RewardID: &rid, - Remark: rw.Name, - }) - if err != nil { - fmt.Printf("[定时开奖-一番赏] ⚠️ 发放奖励失败 err=%v\n", err) - } - - fmt.Printf("[定时开奖-一番赏] ✅ 开奖成功 UserID=%d OrderID=%d RewardID=%d\n", uid, o.ID, rid) - - // 保存可验证凭据 - if err := strat.SaveDrawReceipt(ctx, w, drawLog.ID, iss, uid, proof); err != nil { - fmt.Printf("[定时开奖-一番赏] ⚠️ 保存凭据失败 DrawLogID=%d IssueID=%d UserID=%d err=%v proof=%+v\n", drawLog.ID, iss, uid, err, proof) - } else { - fmt.Printf("[定时开奖-一番赏] ✅ 保存凭据成功 DrawLogID=%d IssueID=%d\n", drawLog.ID, iss) - } - } - // 【开奖后虚拟发货】定时一番赏开奖后上传虚拟发货 - uploadVirtualShippingForScheduledDraw(ctx, r, o.ID, o.OrderNo, uid, func() string { - act, _ := r.Activities.WithContext(ctx).Where(r.Activities.ID.Eq(aid)).First() - if act != nil { - return act.Name - } - return "活动" - }(), "ichiban") + for _, o := range orders { + iss := extractIssueID(o.Remark) + if a.PlayType == "ichiban" && refundedIssues[iss] { + fmt.Printf("[定时开奖-一番赏] OrderID=%d IssueID=%d 已退款,跳过开奖\n", o.ID, iss) + continue } - } else { - // 默认玩法逻辑 - sel := strat.NewDefault(r, w) - // Daily Seed removed - for _, o := range orders { - uid := o.UserID - iss := extractIssueID(o.Remark) - dc := extractCount(o.Remark) - if dc <= 0 { - dc = 1 - } - logs, _ := r.ActivityDrawLogs.WithContext(ctx).Where(r.ActivityDrawLogs.OrderID.Eq(o.ID)).Find() - done := int64(len(logs)) - for i := done; i < dc; i++ { - rid, proof, err := sel.SelectItem(ctx, aid, iss, uid) - if err != nil || rid <= 0 { - break - } - rw, err := r.ActivityRewardSettings.WithContext(ctx).Where(r.ActivityRewardSettings.ID.Eq(rid)).First() - if err != nil || rw == nil { - break - } - // 【先记录日志,再发奖】确保日志创建成功后再发奖,防止重复 - drawLog := &model.ActivityDrawLogs{UserID: uid, IssueID: iss, OrderID: o.ID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1} - if err := w.ActivityDrawLogs.WithContext(ctx).Create(drawLog); err != nil { - fmt.Printf("[定时开奖-默认] ❌ 创建开奖日志失败 err=%v,可能已存在,跳过\n", err) - break - } - // 发放奖励(在原订单上添加中奖商品,不创建新订单) - _, _ = us.GrantRewardToOrder(ctx, uid, usersvc.GrantRewardToOrderRequest{OrderID: o.ID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &aid, RewardID: &rid, Remark: rw.Name}) - // 保存可验证凭据 - if err := strat.SaveDrawReceipt(ctx, w, drawLog.ID, iss, uid, proof); err != nil { - fmt.Printf("[定时开奖-默认] ⚠️ 保存凭据失败 DrawLogID=%d IssueID=%d UserID=%d err=%v proof=%+v\n", drawLog.ID, iss, uid, err, proof) - } else { - fmt.Printf("[定时开奖-默认] ✅ 保存凭据成功 DrawLogID=%d IssueID=%d\n", drawLog.ID, iss) - } - } - // 【开奖后虚拟发货】定时开奖后上传虚拟发货(非一番赏不发通知) - uploadVirtualShippingForScheduledDraw(ctx, r, o.ID, o.OrderNo, uid, func() string { - act, _ := r.Activities.WithContext(ctx).Where(r.Activities.ID.Eq(aid)).First() - if act != nil { - return act.Name - } - return "活动" - }(), "default") + if err := activitySvc.ProcessOrderLottery(ctx, o.ID); err != nil { + fmt.Printf("[定时开奖] ❌ ProcessOrderLottery 失败: %v\n", err) } } } @@ -371,45 +288,18 @@ func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo) { } _ = repo.GetDbR().WithContext(ctx).Raw("SELECT id FROM activities WHERE draw_mode='instant'").Scan(&instantActs) if len(instantActs) > 0 { - sel2 := strat.NewDefault(r, w) for _, ia := range instantActs { orders2, _ := r.Orders.WithContext(ctx).ReadDB().Where( r.Orders.Status.Eq(2), r.Orders.SourceType.Eq(2), + r.Orders.IsConsumed.Eq(0), // 仅处理未履约的订单 r.Orders.Remark.Like(fmt.Sprintf("lottery:activity:%d|%%", ia.ID)), + r.Orders.CreatedAt.Lt(now.Add(-5*time.Minute)), + r.Orders.CreatedAt.Gt(now.Add(-24*time.Hour)), // 限制时间窗口为24小时,避免全表扫描 ).Find() - // Daily Seed removed for _, o2 := range orders2 { - uid := o2.UserID - iss := extractIssueID(o2.Remark) - dc := extractCount(o2.Remark) - if dc <= 0 { - dc = 1 - } - logs2, _ := r.ActivityDrawLogs.WithContext(ctx).Where(r.ActivityDrawLogs.OrderID.Eq(o2.ID)).Find() - done2 := int64(len(logs2)) - for i := done2; i < dc; i++ { - rid, proof, err := sel2.SelectItem(ctx, ia.ID, iss, uid) - if err != nil || rid <= 0 { - break - } - rw, err := r.ActivityRewardSettings.WithContext(ctx).Where(r.ActivityRewardSettings.ID.Eq(rid)).First() - if err != nil || rw == nil { - break - } - // 【先记录日志,再发奖】确保日志创建成功后再发奖,防止重复 - drawLog := &model.ActivityDrawLogs{UserID: uid, IssueID: iss, OrderID: o2.ID, RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1} - if err := w.ActivityDrawLogs.WithContext(ctx).Create(drawLog); err != nil { - fmt.Printf("[即时开奖补偿] ❌ 创建开奖日志失败 err=%v,可能已存在,跳过\n", err) - break - } - _, _ = us.GrantRewardToOrder(ctx, uid, usersvc.GrantRewardToOrderRequest{OrderID: o2.ID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &ia.ID, RewardID: &rid, Remark: rw.Name}) - // 保存可验证凭据 - if err := strat.SaveDrawReceipt(ctx, w, drawLog.ID, iss, uid, proof); err != nil { - fmt.Printf("[即时开奖补偿] ⚠️ 保存凭据失败 DrawLogID=%d IssueID=%d UserID=%d err=%v proof=%+v\n", drawLog.ID, iss, uid, err, proof) - } else { - fmt.Printf("[即时开奖补偿] ✅ 保存凭据成功 DrawLogID=%d IssueID=%d\n", drawLog.ID, iss) - } + if err := activitySvc.ProcessOrderLottery(ctx, o2.ID); err != nil { + fmt.Printf("[即时开奖补偿] ❌ ProcessOrderLottery 失败: %v\n", err) } } } @@ -418,6 +308,53 @@ func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo) { }() } +// checkAndResetIchibanSlots 检查并重置所有售罄且已完成的一番赏期号 +func checkAndResetIchibanSlots(ctx context.Context, repo mysql.Repo, r *dao.Query) { + // 查找所有一番赏活动下的活跃期号 + var issuesWithClaims []struct { + IssueID int64 + TotalSlots int64 + SoldSlots int64 + } + + err := repo.GetDbR().Raw(` + SELECT + ai.id as issue_id, + (SELECT COUNT(*) FROM activity_reward_settings WHERE issue_id = ai.id) as total_slots, + (SELECT COUNT(*) FROM issue_position_claims WHERE issue_id = ai.id) as sold_slots + FROM activity_issues ai + INNER JOIN activities a ON a.id = ai.activity_id + WHERE a.play_type = 'ichiban' AND a.status = 1 AND ai.status = 1 + HAVING sold_slots > 0 AND sold_slots >= total_slots + `).Scan(&issuesWithClaims).Error + + if err != nil { + fmt.Printf("[一番赏重置检查] 查询失败: %v\n", err) + return + } + + for _, iss := range issuesWithClaims { + // 检查是否所有订单都已处理(有draw logs) + var unprocessedCnt int64 + _ = repo.GetDbR().Raw(` + SELECT COUNT(1) FROM issue_position_claims c + LEFT JOIN activity_draw_logs l ON l.order_id = c.order_id AND l.issue_id = c.issue_id + WHERE c.issue_id = ? AND l.id IS NULL + `, iss.IssueID).Scan(&unprocessedCnt) + + if unprocessedCnt == 0 { + // 所有订单都已处理,执行重置 + if err := repo.GetDbW().Exec("DELETE FROM issue_position_claims WHERE issue_id = ?", iss.IssueID).Error; err != nil { + fmt.Printf("[一番赏重置检查] ❌ IssueID=%d 重置失败: %v\n", iss.IssueID, err) + } else { + fmt.Printf("[一番赏重置检查] ✅ IssueID=%d 已售罄且全部处理完成,格位已重置\n", iss.IssueID) + } + } else { + fmt.Printf("[一番赏重置检查] IssueID=%d 已售罄但仍有 %d 个未处理订单\n", iss.IssueID, unprocessedCnt) + } + } +} + func parseTime(s string) time.Time { if s == "" { return time.Time{} @@ -431,95 +368,9 @@ func parseTime(s string) time.Time { return time.Time{} } -func parseInt64(s string) int64 { - var n int64 - for i := 0; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - break - } - n = n*10 + int64(c-'0') - } - return n -} - -func extractIssueID(remark string) int64 { - if remark == "" { - return 0 - } - parts := strings.Split(remark, "|") - for _, p := range parts { - if strings.HasPrefix(p, "issue:") { - return parseInt64(p[6:]) - } - } - return 0 -} - -func extractCount(remark string) int64 { - if remark == "" { - return 1 - } - parts := strings.Split(remark, "|") - for _, p := range parts { - if strings.HasPrefix(p, "count:") { - return parseInt64(p[6:]) - } - } - return 1 -} - // uploadVirtualShippingForScheduledDraw 定时开奖后上传虚拟发货 // 收集中奖产品名称并调用微信虚拟发货API // playType: 活动玩法类型,只有 ichiban 时才发送开奖结果通知 -func uploadVirtualShippingForScheduledDraw(ctx context.Context, r *dao.Query, orderID int64, orderNo string, userID int64, actName string, playType string) { - // 获取开奖记录 - drawLogs, _ := r.ActivityDrawLogs.WithContext(ctx).Where(r.ActivityDrawLogs.OrderID.Eq(orderID)).Find() - if len(drawLogs) == 0 { - fmt.Printf("[定时开奖-虚拟发货] 没有开奖记录,跳过 order_id=%d\n", orderID) - return - } - // 收集赏品名称 - var rewardNames []string - for _, lg := range drawLogs { - if rw, _ := r.ActivityRewardSettings.WithContext(ctx).Where(r.ActivityRewardSettings.ID.Eq(lg.RewardID)).First(); rw != nil { - rewardNames = append(rewardNames, rw.Name) - } - } - itemsDesc := actName + " " + orderNo + " 盲盒赏品: " + strings.Join(rewardNames, ", ") - if len(itemsDesc) > 120 { - itemsDesc = itemsDesc[:120] - } - // 获取支付交易信息 - tx, _ := r.PaymentTransactions.WithContext(ctx).Where(r.PaymentTransactions.OrderNo.Eq(orderNo)).First() - if tx == nil || tx.TransactionID == "" { - fmt.Printf("[定时开奖-虚拟发货] 没有支付交易记录,跳过 order_no=%s\n", orderNo) - return - } - // 获取用户openid - u, _ := r.Users.WithContext(ctx).Where(r.Users.ID.Eq(userID)).First() - payerOpenid := "" - if u != nil { - payerOpenid = u.Openid - } - // 获取微信配置 - c := configs.Get() - cfg := &wechat.WechatConfig{AppID: c.Wechat.AppID, AppSecret: c.Wechat.AppSecret} - fmt.Printf("[定时开奖-虚拟发货] 上传 order_no=%s transaction_id=%s items_desc=%s\n", orderNo, tx.TransactionID, itemsDesc) - if err := wechat.UploadVirtualShippingForBackground(ctx, cfg, tx.TransactionID, orderNo, payerOpenid, itemsDesc); err != nil { - fmt.Printf("[定时开奖-虚拟发货] 上传失败: %v\n", err) - } - // 【定时开奖后推送通知】只有一番赏才发送 - if playType == "ichiban" { - notifyCfg := ¬ify.WechatNotifyConfig{ - AppID: c.Wechat.AppID, - AppSecret: c.Wechat.AppSecret, - LotteryResultTemplateID: c.Wechat.LotteryResultTemplateID, - } - _ = notify.SendLotteryResultNotification(ctx, notifyCfg, payerOpenid, actName, rewardNames, orderNo, time.Now()) - } -} - func refundOrder(ctx context.Context, o *model.Orders, reason string, wc *paypkg.WechatPayClient, r *dao.Query, w *dao.Query, us usersvc.Service, refundCouponID int64) { // 1. Refund Points if o.PointsAmount > 0 { @@ -532,6 +383,8 @@ func refundOrder(ctx context.Context, o *model.Orders, reason string, wc *paypkg refundNo := fmt.Sprintf("R%s-%d", o.OrderNo, time.Now().Unix()) refundID, status, err := wc.RefundOrder(ctx, o.OrderNo, refundNo, o.ActualAmount, o.ActualAmount, reason) if err == nil { + // 修复:raw 字段需要有效的 JSON 值,不能为空 + rawJSON := fmt.Sprintf(`{"refund_id":"%s","status":"%s"}`, refundID, status) _ = w.PaymentRefunds.WithContext(ctx).Create(&model.PaymentRefunds{ OrderID: o.ID, OrderNo: o.OrderNo, @@ -541,6 +394,7 @@ func refundOrder(ctx context.Context, o *model.Orders, reason string, wc *paypkg AmountRefund: o.ActualAmount, Reason: reason, SuccessTime: time.Now(), + Raw: rawJSON, }) _ = w.UserPointsLedger.WithContext(ctx).Create(&model.UserPointsLedger{UserID: o.UserID, Action: "refund_amount", Points: o.ActualAmount / 100, RefTable: "payment_refund", RefID: refundID}) } else { @@ -577,6 +431,31 @@ func refundOrder(ctx context.Context, o *model.Orders, reason string, wc *paypkg } } + // 3.6. 退还道具卡(支持两种记录方式) + // 方式1:从 activity_draw_effects 表查询(无限赏等游戏类型) + var itemCardIDs []int64 + _ = r.Orders.WithContext(ctx).UnderlyingDB().Raw("SELECT user_item_card_id FROM activity_draw_effects WHERE draw_log_id IN (SELECT id FROM activity_draw_logs WHERE order_id=?)", o.ID).Scan(&itemCardIDs).Error + // 方式2:从 user_item_cards 表的 used_draw_log_id 直接查询(对对碰等游戏类型) + var itemCardIDsFromItemCards []int64 + _ = r.Orders.WithContext(ctx).UnderlyingDB().Raw("SELECT id FROM user_item_cards WHERE used_draw_log_id IN (SELECT id FROM activity_draw_logs WHERE order_id=?) AND status=2", o.ID).Scan(&itemCardIDsFromItemCards).Error + // 合并去重 + idSet := make(map[int64]struct{}) + for _, icID := range itemCardIDs { + if icID > 0 { + idSet[icID] = struct{}{} + } + } + for _, icID := range itemCardIDsFromItemCards { + if icID > 0 { + idSet[icID] = struct{}{} + } + } + // 执行退还 + for icID := range idSet { + _ = w.Orders.WithContext(ctx).UnderlyingDB().Exec("UPDATE user_item_cards SET status=1, used_at=NULL, used_draw_log_id=0, used_activity_id=0, used_issue_id=0, updated_at=NOW(3) WHERE id=?", icID).Error + fmt.Printf("[Refund] ✅ Restored item card %d for order %s\n", icID, o.OrderNo) + } + // 4. Update Order Status _, _ = w.Orders.WithContext(ctx).Where(w.Orders.ID.Eq(o.ID)).Updates(map[string]any{w.Orders.Status.ColumnName().String(): 4}) diff --git a/internal/service/activity/strategy/default.go b/internal/service/activity/strategy/default.go index 37a3d75..1b9dcf6 100644 --- a/internal/service/activity/strategy/default.go +++ b/internal/service/activity/strategy/default.go @@ -88,6 +88,10 @@ func (s *defaultStrategy) SelectItem(ctx context.Context, activityID int64, issu return picked, proof, nil } +func (s *defaultStrategy) SelectItemBySlot(ctx context.Context, activityID int64, issueID int64, slotIndex int64) (int64, map[string]any, error) { + return 0, nil, errors.New("default strategy does not support SelectItemBySlot") +} + func (s *defaultStrategy) GrantReward(ctx context.Context, userID int64, rewardID int64) error { // 【使用乐观锁扣减库存】直接用 Quantity > 0 作为更新条件,避免并发超卖 result, err := s.write.ActivityRewardSettings.WithContext(ctx).Where( diff --git a/internal/service/activity/strategy/ichiban.go b/internal/service/activity/strategy/ichiban.go index 4844693..248e3a3 100644 --- a/internal/service/activity/strategy/ichiban.go +++ b/internal/service/activity/strategy/ichiban.go @@ -15,10 +15,18 @@ type ichibanStrategy struct { write *dao.Query } -func NewIchiban(read *dao.Query, write *dao.Query) *ichibanStrategy { +func NewIchiban(read *dao.Query, write *dao.Query) ActivityDrawStrategy { return &ichibanStrategy{read: read, write: write} } +func (s *ichibanStrategy) PreChecks(ctx context.Context, activityID int64, issueID int64, userID int64) error { + return nil +} + +func (s *ichibanStrategy) SelectItem(ctx context.Context, activityID int64, issueID int64, userID int64) (int64, map[string]any, error) { + return 0, nil, errors.New("ichiban strategy requires SelectItemBySlot") +} + func (s *ichibanStrategy) SelectItemBySlot(ctx context.Context, activityID int64, issueID int64, slotIndex int64) (int64, map[string]any, error) { act, err := s.read.Activities.WithContext(ctx).Where(s.read.Activities.ID.Eq(activityID)).First() if err != nil || act == nil || len(act.CommitmentSeedMaster) == 0 { @@ -77,3 +85,7 @@ func (s *ichibanStrategy) GrantReward(ctx context.Context, userID int64, rewardI // 这里保留接口兼容性,实际的占用检查在调用方完成 return nil } + +func (s *ichibanStrategy) PostEffects(ctx context.Context, userID int64, activityID int64, issueID int64, rewardID int64) error { + return nil +} diff --git a/internal/service/activity/strategy/strategy.go b/internal/service/activity/strategy/strategy.go index ad04f79..e746ded 100644 --- a/internal/service/activity/strategy/strategy.go +++ b/internal/service/activity/strategy/strategy.go @@ -12,6 +12,7 @@ import ( type ActivityDrawStrategy interface { PreChecks(ctx context.Context, activityID int64, issueID int64, userID int64) error SelectItem(ctx context.Context, activityID int64, issueID int64, userID int64) (int64, map[string]any, error) + SelectItemBySlot(ctx context.Context, activityID int64, issueID int64, slotIndex int64) (int64, map[string]any, error) GrantReward(ctx context.Context, userID int64, rewardID int64) error PostEffects(ctx context.Context, userID int64, activityID int64, issueID int64, rewardID int64) error } diff --git a/internal/service/user/address_share.go b/internal/service/user/address_share.go index ff92a98..6a16ff1 100644 --- a/internal/service/user/address_share.go +++ b/internal/service/user/address_share.go @@ -8,7 +8,11 @@ import ( "bindbox-game/configs" "bindbox-game/internal/repository/mysql/model" + "bindbox-game/internal/pkg/wechat" + "github.com/golang-jwt/jwt/v5" + "go.uber.org/zap" + "gorm.io/gorm" ) type shareClaims struct { @@ -42,16 +46,43 @@ func parseShareToken(tokenString string) (*shareClaims, error) { return nil, err } -func (s *service) CreateAddressShare(ctx context.Context, userID int64, inventoryID int64, expiresAt time.Time) (string, time.Time, error) { - _, err := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.UserID.Eq(userID), s.readDB.UserInventory.ID.Eq(inventoryID), s.readDB.UserInventory.Status.Eq(1)).First() +func (s *service) CreateAddressShare(ctx context.Context, userID int64, inventoryID int64, expiresAt time.Time) (string, string, time.Time, error) { + inv, err := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.UserID.Eq(userID), s.readDB.UserInventory.ID.Eq(inventoryID), s.readDB.UserInventory.Status.Eq(1)).First() if err != nil { - return "", time.Time{}, err + return "", "", time.Time{}, err } token, err := signShareToken(userID, inventoryID, expiresAt) if err != nil { - return "", time.Time{}, err + return "", "", time.Time{}, err } - return token, expiresAt, nil + + // 尝试生成微信小程序 ShortLink + shortLink := "" + c := configs.Get() + if c.Wechat.AppID != "" && c.Wechat.AppSecret != "" { + wcfg := &wechat.WechatConfig{AppID: c.Wechat.AppID, AppSecret: c.Wechat.AppSecret} + at, errat := wechat.GetAccessTokenWithContext(ctx, wcfg) + if errat == nil { + pagePath := fmt.Sprintf("pages/address/submit?token=%s", token) + pageTitle := "送你一个好礼,快来填写地址领走吧!" + if inv.Remark != "" { + pageTitle = fmt.Sprintf("送你一个%s,快来领走吧!", inv.Remark) + } + sl, errsl := wechat.GetShortLink(at, pagePath, pageTitle) + if errsl == nil { + shortLink = sl + s.logger.Info("成功生成微信短链", zap.String("short_link", shortLink)) + } else { + s.logger.Error("生成微信短链失败", zap.Error(errsl), zap.String("page_path", pagePath)) + } + } else { + s.logger.Error("获取微信AccessToken失败", zap.Error(errat)) + } + } else { + s.logger.Warn("微信配置缺失,跳过短链生成", zap.String("appid", c.Wechat.AppID)) + } + + return token, shortLink, expiresAt, nil } func (s *service) RevokeAddressShare(ctx context.Context, userID int64, inventoryID int64) error { @@ -63,17 +94,16 @@ func (s *service) SubmitAddressShare(ctx context.Context, shareToken string, nam if err != nil { return 0, fmt.Errorf("invalid_or_expired_token") } + + // 1. 基本安全校验 cnt, err := s.readDB.ShippingRecords.WithContext(ctx).Where( s.readDB.ShippingRecords.InventoryID.Eq(claims.InventoryID), - s.readDB.ShippingRecords.Status.Neq(5), // Ignore cancelled + s.readDB.ShippingRecords.Status.Neq(5), // 排除已取消 ).Count() if err == nil && cnt > 0 { return 0, fmt.Errorf("already_processed") } - arow := &model.UserAddresses{UserID: claims.OwnerUserID, Name: name, Mobile: mobile, Province: province, City: city, District: district, Address: address, IsDefault: 0} - if err := s.writeDB.UserAddresses.WithContext(ctx).Create(arow); err != nil { - return 0, err - } + inv, err := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.ID.Eq(claims.InventoryID)).First() if err != nil { return 0, err @@ -81,21 +111,101 @@ func (s *service) SubmitAddressShare(ctx context.Context, shareToken string, nam if inv.Status != 1 { return 0, fmt.Errorf("inventory_unavailable") } - var price int64 - if inv.ProductID > 0 { - if p, e := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.Eq(inv.ProductID)).First(); e == nil && p != nil { - price = p.Price + + // 2. 确定资产最终归属地 (实名转赠逻辑) + targetUserID := claims.OwnerUserID + isTransfer := false + if submittedByUserID != nil && *submittedByUserID > 0 && *submittedByUserID != claims.OwnerUserID { + targetUserID = *submittedByUserID + isTransfer = true + } + + var addrID int64 + err = s.repo.GetDbW().Transaction(func(tx *gorm.DB) error { + // a. 创建收货地址 (归属于 targetUserID) + arow := &model.UserAddresses{ + UserID: targetUserID, + Name: name, + Mobile: mobile, + Province: province, + City: city, + District: district, + Address: address, + IsDefault: 0, } - } - if db := s.repo.GetDbW().Exec("INSERT INTO shipping_records (user_id, order_id, order_item_id, inventory_id, product_id, quantity, price, address_id, status, remark) VALUES (?,?,?,?,?,?,?,?,?,?)", claims.OwnerUserID, inv.OrderID, 0, claims.InventoryID, inv.ProductID, 1, price, arow.ID, 1, "shared_address_submit"); db.Error != nil { - err = db.Error + if err := tx.Create(arow).Error; err != nil { + return err + } + addrID = arow.ID + + // b. 资产状态更新及所有权转移 + if isTransfer { + // 记录转赠流水 + transferLog := &model.UserInventoryTransfers{ + InventoryID: claims.InventoryID, + FromUserID: claims.OwnerUserID, + ToUserID: targetUserID, + Remark: "address_share_transfer", + } + if err := tx.Create(transferLog).Error; err != nil { + return err + } + + // 更新资产所属人 + if err := tx.Table("user_inventory").Where("id = ? AND user_id = ? AND status = 1", claims.InventoryID, claims.OwnerUserID). + Updates(map[string]interface{}{ + "user_id": targetUserID, + "status": 3, + "updated_at": time.Now(), + "remark": fmt.Sprintf("transferred_from_%d|shipping_requested", claims.OwnerUserID), + }).Error; err != nil { + return err + } + } else { + // 仅更新状态 (原主发货) + if err := tx.Table("user_inventory").Where("id = ? AND user_id = ? AND status = 1", claims.InventoryID, claims.OwnerUserID). + Updates(map[string]interface{}{ + "status": 3, + "updated_at": time.Now(), + "remark": "shipping_requested_via_share", + }).Error; err != nil { + return err + } + } + + // c. 创建发货记录 (归属于 targetUserID) + var price int64 + if inv.ProductID > 0 { + var p model.Products + if err := tx.Table("products").Where("id = ?", inv.ProductID).First(&p).Error; err == nil { + price = p.Price + } + } + + shipRecord := &model.ShippingRecords{ + UserID: targetUserID, + OrderID: inv.OrderID, + OrderItemID: 0, + InventoryID: claims.InventoryID, + ProductID: inv.ProductID, + Quantity: 1, + Price: price, + AddressID: addrID, + Status: 1, + Remark: fmt.Sprintf("shared_address_submit|ip=%s", *submittedIP), + } + if err := tx.Create(shipRecord).Error; err != nil { + return err + } + + return nil + }) + + if err != nil { return 0, err } - if db := s.repo.GetDbW().Exec("UPDATE user_inventory SET status=3, updated_at=NOW(3), remark=CONCAT(IFNULL(remark,''),'|shipping_requested') WHERE id=? AND user_id=? AND status=1", claims.InventoryID, claims.OwnerUserID); db.Error != nil { - err = db.Error - return 0, err - } - return arow.ID, nil + + return addrID, nil } func (s *service) RequestShipping(ctx context.Context, userID int64, inventoryID int64) (int64, error) { diff --git a/internal/service/user/addresses.go b/internal/service/user/addresses.go index 1cc569a..1acc2e2 100644 --- a/internal/service/user/addresses.go +++ b/internal/service/user/addresses.go @@ -32,7 +32,7 @@ func (s *service) AddAddress(ctx context.Context, userID int64, in AddAddressInp } tx := s.writeDB.Begin() if in.IsDefault { - if _, err := tx.UserAddresses.WithContext(ctx).Where(tx.UserAddresses.UserID.Eq(userID), tx.UserAddresses.IsDefault.Eq(1)).Updates(tx.UserAddresses.IsDefault.Value(0)); err != nil { + if _, err := tx.UserAddresses.WithContext(ctx).Where(tx.UserAddresses.UserID.Eq(userID)).UpdateSimple(tx.UserAddresses.IsDefault.Value(0)); err != nil { _ = tx.Rollback() return nil, err } @@ -71,11 +71,13 @@ func (s *service) ListAddresses(ctx context.Context, userID int64, page, pageSiz func (s *service) SetDefaultAddress(ctx context.Context, userID int64, addressID int64) error { tx := s.writeDB.Begin() - if _, err := tx.UserAddresses.WithContext(ctx).Where(tx.UserAddresses.UserID.Eq(userID), tx.UserAddresses.IsDefault.Eq(1)).Updates(tx.UserAddresses.IsDefault.Value(0)); err != nil { + // 先将用户所有地址的 is_default 设为 0 + if _, err := tx.UserAddresses.WithContext(ctx).Where(tx.UserAddresses.UserID.Eq(userID)).UpdateSimple(tx.UserAddresses.IsDefault.Value(0)); err != nil { _ = tx.Rollback() return err } - if _, err := tx.UserAddresses.WithContext(ctx).Where(tx.UserAddresses.UserID.Eq(userID), tx.UserAddresses.ID.Eq(addressID)).Updates(tx.UserAddresses.IsDefault.Value(1)); err != nil { + // 再将指定地址设为默认 + if _, err := tx.UserAddresses.WithContext(ctx).Where(tx.UserAddresses.UserID.Eq(userID), tx.UserAddresses.ID.Eq(addressID)).UpdateSimple(tx.UserAddresses.IsDefault.Value(1)); err != nil { _ = tx.Rollback() return err } @@ -99,23 +101,27 @@ type UpdateAddressInput struct { func (s *service) UpdateAddress(ctx context.Context, userID int64, addressID int64, in UpdateAddressInput) error { tx := s.writeDB.Begin() - updates := map[string]interface{}{ - "name": in.Name, - "mobile": in.Mobile, - "province": in.Province, - "city": in.City, - "district": in.District, - "address": in.Address, - } if in.IsDefault { - // 先将其他地址设为非默认 - if _, err := tx.UserAddresses.WithContext(ctx).Where(tx.UserAddresses.UserID.Eq(userID), tx.UserAddresses.IsDefault.Eq(1)).Updates(tx.UserAddresses.IsDefault.Value(0)); err != nil { + // 先将用户所有地址设为非默认 + if _, err := tx.UserAddresses.WithContext(ctx).Where(tx.UserAddresses.UserID.Eq(userID)).UpdateSimple(tx.UserAddresses.IsDefault.Value(0)); err != nil { _ = tx.Rollback() return err } - updates["is_default"] = 1 } - if _, err := tx.UserAddresses.WithContext(ctx).Where(tx.UserAddresses.UserID.Eq(userID), tx.UserAddresses.ID.Eq(addressID)).Updates(updates); err != nil { + // 更新指定地址的信息 + isDefaultVal := int32(0) + if in.IsDefault { + isDefaultVal = 1 + } + if _, err := tx.UserAddresses.WithContext(ctx).Where(tx.UserAddresses.UserID.Eq(userID), tx.UserAddresses.ID.Eq(addressID)).UpdateSimple( + tx.UserAddresses.Name.Value(in.Name), + tx.UserAddresses.Mobile.Value(in.Mobile), + tx.UserAddresses.Province.Value(in.Province), + tx.UserAddresses.City.Value(in.City), + tx.UserAddresses.District.Value(in.District), + tx.UserAddresses.Address.Value(in.Address), + tx.UserAddresses.IsDefault.Value(isDefaultVal), + ); err != nil { _ = tx.Rollback() return err } diff --git a/internal/service/user/user.go b/internal/service/user/user.go index af5f5cf..5f431a1 100644 --- a/internal/service/user/user.go +++ b/internal/service/user/user.go @@ -47,7 +47,7 @@ type Service interface { ConsumePoints(ctx context.Context, userID int64, points int64, orderNo string) (int64, error) ConsumePointsFor(ctx context.Context, userID int64, points int64, refTable string, refID string, remark string, action string) (int64, error) RefundPoints(ctx context.Context, userID int64, points int64, orderNo string, reason string) (int64, error) - CreateAddressShare(ctx context.Context, userID int64, inventoryID int64, expiresAt time.Time) (string, time.Time, error) + CreateAddressShare(ctx context.Context, userID int64, inventoryID int64, expiresAt time.Time) (string, string, time.Time, error) RevokeAddressShare(ctx context.Context, userID int64, inventoryID int64) error SubmitAddressShare(ctx context.Context, shareToken string, name string, mobile string, province string, city string, district string, address string, submittedByUserID *int64, submittedIP *string) (int64, error) RequestShipping(ctx context.Context, userID int64, inventoryID int64) (int64, error) diff --git a/logs/mini-chat-access.log b/logs/mini-chat-access.log index be13a32..204e679 100644 --- a/logs/mini-chat-access.log +++ b/logs/mini-chat-access.log @@ -1672,3 +1672,913 @@ {"level":"info","time":"2025-12-26 12:33:31","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} {"level":"info","time":"2025-12-26 12:33:31","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} {"level":"info","time":"2025-12-26 12:33:31","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 12:49:21","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 12:49:21","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 12:49:21","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 12:49:21","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 12:49:21","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 12:49:21","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 12:49:21","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 12:57:14","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 12:57:14","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 12:57:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 12:57:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 12:57:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 12:57:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 12:57:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 13:06:34","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 13:06:34","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 13:06:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 13:06:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 13:06:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 13:06:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 13:06:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 13:10:17","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 13:10:17","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 13:10:17","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 13:10:17","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 13:10:17","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 13:10:17","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 13:10:17","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 13:10:54","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 13:10:54","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 13:10:54","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 13:10:54","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 13:10:54","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 13:10:54","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 13:10:54","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 13:23:07","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 13:23:07","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 13:23:07","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 13:23:07","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 13:23:07","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 13:23:07","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 13:23:07","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 13:24:56","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 13:24:56","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 13:24:56","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 13:24:56","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 13:24:56","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 13:24:56","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 13:24:56","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 13:27:27","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 13:27:27","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 13:27:28","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 13:27:28","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 13:27:28","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 13:27:28","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 13:27:28","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 13:39:21","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 13:39:21","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 13:39:21","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 13:39:21","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 13:39:21","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 13:39:21","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 13:39:21","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 14:29:03","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 14:29:03","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 14:29:03","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 14:29:03","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 14:29:03","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 14:29:03","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 14:29:03","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 14:31:40","caller":"logger/logger.go:309","msg":"Processing event","domain":"mini-chat[fat]","type":"order_paid","worker_id":3} +{"level":"info","time":"2025-12-26 14:37:29","caller":"logger/logger.go:309","msg":"Processing event","domain":"mini-chat[fat]","type":"order_paid","worker_id":3} +{"level":"info","time":"2025-12-26 14:53:36","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 14:53:36","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 14:53:36","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 14:53:36","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 14:53:36","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 14:53:36","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 14:53:36","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 15:05:57","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 15:05:57","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 15:05:57","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 15:05:57","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 15:05:57","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 15:05:57","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 15:05:57","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 15:06:04","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 15:06:04","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 15:06:04","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 15:06:04","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 15:06:04","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 15:06:04","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 15:06:04","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 15:11:34","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 15:11:34","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 15:11:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 15:11:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 15:11:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 15:11:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 15:11:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 15:16:13","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 15:16:13","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 15:16:13","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 15:16:13","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 15:16:13","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 15:16:13","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 15:16:13","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 15:16:13","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 15:16:13","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"fatal","time":"2025-12-26 15:16:13","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"} +{"level":"info","time":"2025-12-26 15:29:17","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 15:29:17","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 15:29:17","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 15:29:17","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 15:29:17","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 15:29:17","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 15:29:17","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 15:30:52","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 15:30:52","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 15:30:52","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 15:30:52","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 15:30:52","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 15:30:52","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 15:30:52","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 15:31:22","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"error","time":"2025-12-26 15:31:24","caller":"logger/logger.go:327","msg":"[虚拟发货] 上传失败","domain":"mini-chat[fat]","error":"微信返回错误: errcode=10060023, errmsg=发货信息未更新 rid: 694e39cb-2ff87a81-199d1703"} +{"level":"info","time":"2025-12-26 15:31:52","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"error","time":"2025-12-26 15:31:53","caller":"logger/logger.go:327","msg":"[虚拟发货] 上传失败","domain":"mini-chat[fat]","error":"微信返回错误: errcode=10060023, errmsg=发货信息未更新 rid: 694e39e9-6a29f9ae-2bea909f"} +{"level":"info","time":"2025-12-26 15:44:54","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 15:44:54","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 15:44:54","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 15:44:54","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 15:44:54","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 15:44:54","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 15:44:54","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 16:27:39","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 16:27:39","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 16:27:39","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 16:27:39","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 16:27:39","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 16:27:39","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 16:27:39","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 16:28:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:28:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:28:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:28:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:28:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:28:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:28:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:28:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:28:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:28:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:28:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:28:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:28:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:28:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:28:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:28:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:28:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:28:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:28:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:28:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:28:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:28:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:28:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:28:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:28:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:28:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:28:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:28:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:28:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:28:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:28:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:28:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:28:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:28:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:28:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:28:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:28:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:28:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:28:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:28:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:28:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:28:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:28:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:28:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:28:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:28:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:28:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:28:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:28:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:28:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:28:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:28:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:28:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:28:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:28:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:28:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:28:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:28:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:28:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:28:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:28:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:28:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:28:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:28:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:28:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:28:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:28:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:28:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:28:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:28:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:28:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:28:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:28:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:28:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:28:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:28:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:28:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:28:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:28:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:28:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:28:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:28:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:28:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:28:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:28:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:28:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:28:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:28:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:28:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:28:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:28:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:28:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:28:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:28:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:28:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:28:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:28:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:28:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:29:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:29:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:29:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:29:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:29:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:29:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:29:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:29:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:29:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:29:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:29:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:29:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:29:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:29:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:29:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:29:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:29:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:29:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:29:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:29:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:29:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:29:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:29:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:29:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:29:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:29:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:29:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:29:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:29:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:29:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:29:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:29:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:29:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:29:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:29:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:29:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:29:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:29:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:29:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:29:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:29:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:29:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:29:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:29:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:29:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:29:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:29:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:29:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:29:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:29:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:29:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:29:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:29:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:29:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:29:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:29:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:29:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:29:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:29:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:29:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:29:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:29:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:29:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:29:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:29:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:29:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:29:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:29:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:29:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:29:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:29:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:29:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:29:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:29:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:29:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:29:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:29:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:29:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:29:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:29:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:29:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:29:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:29:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:29:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:29:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:29:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:29:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:29:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:29:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:29:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:29:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:29:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:29:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:29:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:29:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:29:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:29:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:29:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:29:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:29:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:29:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:29:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:30:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:30:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:30:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:30:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:30:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:30:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:30:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:30:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:30:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:30:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:30:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:30:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:30:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:30:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:30:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:30:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:30:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:30:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:30:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:30:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:30:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:30:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:30:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:30:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:30:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:30:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:30:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:30:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:30:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:30:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:30:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:30:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:30:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:30:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:30:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:30:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:30:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:30:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:30:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:30:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:30:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:30:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:30:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:30:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:30:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:30:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:30:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:30:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:30:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:30:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:30:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:30:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:30:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:30:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:30:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:30:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:30:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:30:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:30:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:30:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:30:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:30:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:30:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:30:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:30:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:30:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:30:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:30:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:30:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:30:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:30:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:30:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:30:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:30:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:30:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:30:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:30:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:30:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:30:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:30:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:30:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:30:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:30:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:30:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:30:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:30:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:30:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:30:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:30:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:30:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:30:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:30:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:30:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:30:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:30:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:30:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:30:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:30:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:30:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:30:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:30:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:30:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:31:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:31:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:31:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:31:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:31:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:31:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:31:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:31:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:31:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:31:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:31:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:31:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:31:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:31:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:31:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:31:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:31:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:31:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:31:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:31:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:31:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:31:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:31:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:31:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:31:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:31:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:31:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:31:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:31:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:31:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:31:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:31:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:31:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:31:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:31:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:31:14","caller":"logger/logger.go:309","msg":"Processing event","domain":"mini-chat[fat]","type":"order_paid","worker_id":4} +{"level":"info","time":"2025-12-26 16:31:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:31:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:31:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:31:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:31:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:31:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:31:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:31:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:31:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:31:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:31:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:31:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:31:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:31:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:31:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:31:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:31:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:31:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:31:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:31:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:31:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:31:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:31:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:31:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:31:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:31:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:31:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:31:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:31:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:31:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:31:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:31:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:31:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:31:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:31:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:31:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:31:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:31:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:31:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:31:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:31:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:31:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:31:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:31:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:31:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:31:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:31:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:31:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:31:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:31:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:31:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:31:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:31:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:31:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:31:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:31:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:31:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:31:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:31:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:31:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:31:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:31:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:31:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:31:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:31:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:31:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:31:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:32:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:32:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:32:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:32:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:32:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:32:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:32:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:32:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:32:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:32:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:32:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:32:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:32:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:32:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:32:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:32:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:32:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:32:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:32:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:32:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:32:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:32:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:32:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:32:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:32:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:32:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:32:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:32:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:32:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:32:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:32:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:32:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:32:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:32:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:32:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:32:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:32:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:32:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:32:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:32:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:32:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:32:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:32:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:32:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:32:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:32:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:32:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:32:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:32:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:32:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:32:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:32:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:32:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:32:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:32:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:32:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:32:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:32:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:32:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:32:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:32:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:32:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:32:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:32:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:32:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:32:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:32:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:32:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:32:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:32:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:32:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:32:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:32:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:32:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:32:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:32:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:32:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:32:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:32:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:32:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:32:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:32:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:32:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:32:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:32:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:32:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:32:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:32:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:32:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:32:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:32:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:32:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:32:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:32:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:32:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:32:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:32:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:32:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:32:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:32:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:32:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:32:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:33:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:33:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:33:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:33:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:33:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:33:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:33:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:33:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:33:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:33:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:33:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:33:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:33:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:33:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:33:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:33:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:33:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:33:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:33:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:33:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:33:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:33:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:33:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:33:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:33:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:33:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:33:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:33:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:33:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:33:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:33:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:33:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:33:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:33:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:33:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:33:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:33:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:33:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:33:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:33:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:33:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:33:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:33:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:33:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:33:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:33:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:33:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:33:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:33:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:33:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:33:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:33:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:33:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:33:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:33:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:33:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:33:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:33:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:33:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:33:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:33:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:33:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:33:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:33:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:33:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:33:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:33:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:33:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:33:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:33:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:33:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:33:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:33:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:33:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:33:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:33:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:33:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:33:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:33:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:33:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:33:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:33:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:33:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:33:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:33:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:33:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:33:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:33:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:33:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:33:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:33:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:33:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:33:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:33:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:33:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:33:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:33:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:33:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:33:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:33:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:33:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:33:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:34:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:34:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:34:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:34:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:34:09","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:34:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:34:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:34:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:34:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:34:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:34:10","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:34:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:34:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:34:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:34:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:34:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:34:11","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:34:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:34:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:34:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:34:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:34:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:34:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:34:12","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:34:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:34:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:34:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:34:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:34:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:34:13","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:34:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:34:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:34:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:34:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:34:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:34:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:34:14","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:34:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:34:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:34:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:34:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:34:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:34:15","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:34:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:34:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:34:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:34:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:34:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:34:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:34:16","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:34:17","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:34:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":411} +{"level":"info","time":"2025-12-26 16:34:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":418} +{"level":"info","time":"2025-12-26 16:34:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":419} +{"level":"info","time":"2025-12-26 16:34:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":420} +{"level":"info","time":"2025-12-26 16:34:39","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":421} +{"level":"info","time":"2025-12-26 16:34:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":422} +{"level":"info","time":"2025-12-26 16:34:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":423} +{"level":"info","time":"2025-12-26 16:34:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":424} +{"level":"info","time":"2025-12-26 16:34:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":425} +{"level":"info","time":"2025-12-26 16:34:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":426} +{"level":"info","time":"2025-12-26 16:34:40","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":427} +{"level":"info","time":"2025-12-26 16:34:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":428} +{"level":"info","time":"2025-12-26 16:34:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":429} +{"level":"info","time":"2025-12-26 16:34:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":430} +{"level":"info","time":"2025-12-26 16:34:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":431} +{"level":"info","time":"2025-12-26 16:34:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":432} +{"level":"info","time":"2025-12-26 16:34:41","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":433} +{"level":"info","time":"2025-12-26 16:34:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":434} +{"level":"info","time":"2025-12-26 16:34:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":435} +{"level":"info","time":"2025-12-26 16:34:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":436} +{"level":"info","time":"2025-12-26 16:34:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":438} +{"level":"info","time":"2025-12-26 16:34:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":440} +{"level":"info","time":"2025-12-26 16:34:42","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":441} +{"level":"info","time":"2025-12-26 16:34:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":442} +{"level":"info","time":"2025-12-26 16:34:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":443} +{"level":"info","time":"2025-12-26 16:34:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":444} +{"level":"info","time":"2025-12-26 16:34:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":445} +{"level":"info","time":"2025-12-26 16:34:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":446} +{"level":"info","time":"2025-12-26 16:34:43","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":448} +{"level":"info","time":"2025-12-26 16:34:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":449} +{"level":"info","time":"2025-12-26 16:34:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":450} +{"level":"info","time":"2025-12-26 16:34:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":451} +{"level":"info","time":"2025-12-26 16:34:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":452} +{"level":"info","time":"2025-12-26 16:34:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":453} +{"level":"info","time":"2025-12-26 16:34:44","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":454} +{"level":"info","time":"2025-12-26 16:34:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":455} +{"level":"info","time":"2025-12-26 16:34:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":456} +{"level":"info","time":"2025-12-26 16:34:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":457} +{"level":"info","time":"2025-12-26 16:34:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":458} +{"level":"info","time":"2025-12-26 16:34:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":459} +{"level":"info","time":"2025-12-26 16:34:45","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":460} +{"level":"info","time":"2025-12-26 16:34:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":461} +{"level":"info","time":"2025-12-26 16:34:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":462} +{"level":"info","time":"2025-12-26 16:34:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":463} +{"level":"info","time":"2025-12-26 16:34:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":464} +{"level":"info","time":"2025-12-26 16:34:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":465} +{"level":"info","time":"2025-12-26 16:34:46","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":466} +{"level":"info","time":"2025-12-26 16:34:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":467} +{"level":"info","time":"2025-12-26 16:34:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":468} +{"level":"info","time":"2025-12-26 16:34:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":469} +{"level":"info","time":"2025-12-26 16:34:47","caller":"logger/logger.go:309","msg":"开始原子化处理订单开奖","domain":"mini-chat[fat]","order_id":470} +{"level":"info","time":"2025-12-26 16:37:47","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 16:37:47","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 16:37:47","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 16:37:47","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 16:37:47","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 16:37:47","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 16:37:47","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 16:38:52","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 16:38:52","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 16:38:53","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 16:38:53","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 16:38:53","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 16:38:53","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 16:38:53","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 16:40:43","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 16:40:43","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 16:40:44","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 16:40:44","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 16:40:44","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 16:40:44","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 16:40:44","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 17:00:34","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 17:00:34","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 17:00:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 17:00:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 17:00:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 17:00:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 17:00:34","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 17:03:01","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 17:03:01","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"fatal","time":"2025-12-26 17:03:01","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"} +{"level":"info","time":"2025-12-26 17:08:44","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 17:08:44","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"fatal","time":"2025-12-26 17:08:44","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"} +{"level":"info","time":"2025-12-26 17:08:53","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 17:08:53","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 17:08:53","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 17:08:53","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 17:08:53","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 17:08:53","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 17:08:53","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 17:14:20","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 17:14:20","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 17:14:20","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 17:14:20","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 17:14:20","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 17:14:20","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 17:14:20","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 17:17:27","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"} +{"level":"info","time":"2025-12-26 17:17:27","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"} +{"level":"info","time":"2025-12-26 17:17:27","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4} +{"level":"info","time":"2025-12-26 17:17:27","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0} +{"level":"info","time":"2025-12-26 17:17:27","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2} +{"level":"info","time":"2025-12-26 17:17:27","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3} +{"level":"info","time":"2025-12-26 17:17:27","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1} +{"level":"info","time":"2025-12-26 17:21:19","caller":"logger/logger.go:309","msg":"Processing event","domain":"mini-chat[fat]","type":"order_paid","worker_id":0} +{"level":"info","time":"2025-12-26 17:27:50","caller":"logger/logger.go:309","msg":"Processing event","domain":"mini-chat[fat]","type":"order_paid","worker_id":4} diff --git a/main.go b/main.go index 3607752..13b4e90 100644 --- a/main.go +++ b/main.go @@ -154,7 +154,7 @@ func main() { } }() - activitysvc.StartScheduledSettlement(customLogger, dbRepo) + activitysvc.StartScheduledSettlement(customLogger, dbRepo, redis.GetClient()) usersvc.StartExpirationCheck(customLogger, dbRepo) // 优雅关闭 diff --git a/migrations/20251226_add_draw_index_unique.sql b/migrations/20251226_add_draw_index_unique.sql new file mode 100644 index 0000000..bacda85 --- /dev/null +++ b/migrations/20251226_add_draw_index_unique.sql @@ -0,0 +1,8 @@ +-- 迁移脚本:为 activity_draw_logs 增加序号字段并建立联合唯一索引 +-- 用于支持开奖幂等性 + +ALTER TABLE activity_draw_logs ADD COLUMN draw_index BIGINT NOT NULL DEFAULT 0 COMMENT '抽奖序号(0-N)'; + +-- 注意:如果表中已有数据,可能需要先初始化 draw_index (通常已有数据如果是单抽则默认为0没问题) +-- 添加联合唯一索引,防止同一订单的同一个序号重复开奖 +ALTER TABLE activity_draw_logs ADD UNIQUE INDEX uk_order_draw (order_id, draw_index);