package admin import ( "encoding/hex" "encoding/json" "net/http" "strconv" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" "bindbox-game/internal/repository/mysql/model" ) type batchDrawRequest struct { UserIDs []int64 `json:"user_ids" binding:"required,min=1,max=1000,dive,min=1"` } type batchDrawResponse struct { Draws []drawResultItem `json:"draws"` } type drawResultItem struct { UserID int64 `json:"user_id"` DrawID int64 `json:"draw_id"` RewardID int64 `json:"reward_id"` RewardName string `json:"reward_name"` IsWinner bool `json:"is_winner"` Receipt interface{} `json:"receipt"` } func (h *handler) BatchDrawForUsers() core.HandlerFunc { return func(ctx core.Context) { activityIDStr := ctx.Param("activity_id") if activityIDStr == "" { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递活动ID")) return } issueIDStr := ctx.Param("issue_id") if issueIDStr == "" { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID")) return } if _, err := strconv.ParseInt(issueIDStr, 10, 64); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } issueID, _ := strconv.ParseInt(issueIDStr, 10, 64) _, _ = strconv.ParseInt(activityIDStr, 10, 64) var req batchDrawRequest if err := ctx.ShouldBindJSON(&req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } rewards, err := h.activity.ListIssueRewards(ctx.RequestContext(), issueID) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ExecuteDrawError, err.Error())) return } rewardMap := make(map[int64]string, len(rewards)) for _, r := range rewards { rewardMap[r.ID] = r.Name } // 开始事务处理 tx := h.writeDB.Begin() if tx.Error != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ExecuteDrawError, "启动事务失败")) return } defer func() { if r := recover(); r != nil { tx.Rollback() panic(r) } }() draws := make([]drawResultItem, 0, len(req.UserIDs)) for _, uid := range req.UserIDs { // 执行抽奖(应用头衔效果) rec, err := h.activity.ExecuteDrawWithEffects(ctx.RequestContext(), issueID, uid) if err != nil { tx.Rollback() ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ExecuteDrawError, err.Error())) return } // 创建抽奖日志 isWinner := int32(0) if rec.SelectedItemId > 0 { isWinner = 1 } drawLog := &model.ActivityDrawLogs{ UserID: uid, IssueID: issueID, RewardID: rec.SelectedItemId, IsWinner: isWinner, Level: 1, // 默认等级 } if err := tx.ActivityDrawLogs.Create(drawLog); err != nil { tx.Rollback() ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ExecuteDrawError, "创建抽奖日志失败")) return } // 保存抽奖收据 itemsSnapshot, _ := json.Marshal(rec.Items) receipt := &model.ActivityDrawReceipts{ DrawLogID: drawLog.ID, AlgoVersion: rec.AlgoVersion, RoundID: rec.RoundId, DrawID: rec.DrawId, ClientID: rec.ClientId, Timestamp: rec.Timestamp, ServerSeedHash: hex.EncodeToString(rec.ServerSeedHash), ServerSubSeed: hex.EncodeToString(rec.ServerSubSeed), ClientSeed: hex.EncodeToString(rec.ClientSeed), Nonce: int64(rec.Nonce), ItemsRoot: hex.EncodeToString(rec.ItemsRoot), WeightsTotal: int64(rec.WeightsTotal), SelectedIndex: int32(rec.SelectedIndex), RandProof: hex.EncodeToString(rec.RandProof), Signature: "", // 可以后续添加签名 ItemsSnapshot: string(itemsSnapshot), } if err := tx.ActivityDrawReceipts.Create(receipt); err != nil { tx.Rollback() ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ExecuteDrawError, "保存抽奖收据失败")) return } name := "" if rec.SelectedItemId > 0 { name = rewardMap[rec.SelectedItemId] } draws = append(draws, drawResultItem{ UserID: uid, DrawID: rec.DrawId, RewardID: rec.SelectedItemId, RewardName: name, IsWinner: rec.SelectedItemId > 0, Receipt: rec, }) } // 提交事务 if err := tx.Commit(); err != nil { tx.Rollback() ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ExecuteDrawError, "提交事务失败")) return } ctx.Payload(&batchDrawResponse{Draws: draws}) } }