feat: 引入活动抽奖策略槽位选择功能,新增用户库存发货单号字段,并优化支付与活动服务集成。

This commit is contained in:
邹方成 2025-12-26 18:15:15 +08:00
parent 635924040a
commit 2cf9da6143
30 changed files with 2250 additions and 1166 deletions

View File

@ -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),
}

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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),

View File

@ -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 {

View File

@ -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,
}
}

View File

@ -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:<id>:<applied>
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:<id>:<amount>
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(&notifyAck{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(&notifyAck{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

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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 // 来源奖励IDactivity_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
}

View File

@ -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

View File

@ -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:来源奖励IDactivity_reward_settings.id" json:"reward_id"` // 来源奖励IDactivity_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"` // 备注
}

View File

@ -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令牌接口未被前端使用

View File

@ -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,
}
}

View File

@ -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 := &notify.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
}

View File

@ -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 := &notify.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})

View File

@ -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(

View File

@ -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
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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)

View File

@ -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}

View File

@ -154,7 +154,7 @@ func main() {
}
}()
activitysvc.StartScheduledSettlement(customLogger, dbRepo)
activitysvc.StartScheduledSettlement(customLogger, dbRepo, redis.GetClient())
usersvc.StartExpirationCheck(customLogger, dbRepo)
// 优雅关闭

View File

@ -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);