304 lines
9.6 KiB
Go
304 lines
9.6 KiB
Go
package app
|
||
|
||
import (
|
||
"bindbox-game/internal/code"
|
||
"bindbox-game/internal/pkg/core"
|
||
"bindbox-game/internal/pkg/util/remark"
|
||
"bindbox-game/internal/pkg/validation"
|
||
"bindbox-game/internal/repository/mysql/model"
|
||
"crypto/hmac"
|
||
"crypto/sha256"
|
||
"encoding/base64"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
type orderResultQuery struct {
|
||
OrderNo string `form:"order_no"`
|
||
}
|
||
|
||
type orderResultItem struct {
|
||
RewardID int64 `json:"reward_id"`
|
||
RewardName string `json:"reward_name"`
|
||
Level int32 `json:"level"`
|
||
DrawIndex int32 `json:"draw_index"`
|
||
Image string `json:"image"`
|
||
}
|
||
|
||
type orderResultResponse struct {
|
||
Status string `json:"status"`
|
||
DrawMode string `json:"draw_mode"`
|
||
Count int64 `json:"count"`
|
||
Completed int64 `json:"completed"`
|
||
Results []orderResultItem `json:"results"`
|
||
Receipt map[string]any `json:"receipt,omitempty"`
|
||
NextPollMs int `json:"nextPollMs"`
|
||
SeedHex string `json:"seed_hex,omitempty"`
|
||
NextDrawTime string `json:"next_draw_time,omitempty"`
|
||
}
|
||
|
||
// LotteryResultByOrder 抽奖订单结果查询
|
||
// @Summary 抽奖订单结果查询
|
||
// @Description 根据订单号查询抽奖结果与进度,返回结果明细与可验证凭证;需登录
|
||
// @Tags APP端.抽奖
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security LoginVerifyToken
|
||
// @Param order_no query string true "订单号"
|
||
// @Success 200 {object} orderResultResponse
|
||
// @Failure 400 {object} code.Failure
|
||
// @Router /api/app/lottery/result [get]
|
||
func (h *handler) LotteryResultByOrder() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
req := new(orderResultQuery)
|
||
if err := ctx.ShouldBindQuery(req); err != nil || req.OrderNo == "" {
|
||
ctx.AbortWithError(core.Error(400, code.ParamBindError, validation.Error(err)))
|
||
return
|
||
}
|
||
userID := int64(ctx.SessionUserInfo().Id)
|
||
ord, err := h.readDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.OrderNo.Eq(req.OrderNo), h.readDB.Orders.UserID.Eq(userID)).First()
|
||
if err != nil || ord == nil {
|
||
ctx.AbortWithError(core.Error(400, code.ParamBindError, "order not found"))
|
||
return
|
||
}
|
||
|
||
rmk := remark.Parse(ord.Remark)
|
||
iss := rmk.IssueID
|
||
dc := rmk.Count
|
||
|
||
// 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()
|
||
|
||
// 批量预加载所有奖品配置
|
||
rewardIDs := make([]int64, 0, len(logs))
|
||
for _, lg := range logs {
|
||
if lg.RewardID > 0 {
|
||
rewardIDs = append(rewardIDs, lg.RewardID)
|
||
}
|
||
}
|
||
|
||
rewardMap := make(map[int64]*model.ActivityRewardSettings)
|
||
if len(rewardIDs) > 0 {
|
||
rwList, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.In(rewardIDs...)).Find()
|
||
for _, rw := range rwList {
|
||
rewardMap[rw.ID] = rw
|
||
}
|
||
}
|
||
|
||
// 收集所有需要查询的产品ID(包括开奖记录和翻倍记录)
|
||
productIDs := make([]int64, 0)
|
||
productIDSet := make(map[int64]struct{})
|
||
|
||
for _, rw := range rewardMap {
|
||
if rw.ProductID > 0 {
|
||
if _, ok := productIDSet[rw.ProductID]; !ok {
|
||
productIDSet[rw.ProductID] = struct{}{}
|
||
productIDs = append(productIDs, rw.ProductID)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 6. 查询道具卡翻倍的奖励(remark包含"倍数")
|
||
doubledInvs, _ := h.readDB.UserInventory.WithContext(ctx.RequestContext()).
|
||
Where(h.readDB.UserInventory.OrderID.Eq(ord.ID)).
|
||
Find()
|
||
|
||
var validDoubledInvs []*model.UserInventory
|
||
for _, inv := range doubledInvs {
|
||
if inv.Remark != "" && strings.Contains(inv.Remark, "(倍数)") {
|
||
validDoubledInvs = append(validDoubledInvs, inv)
|
||
if inv.ProductID > 0 {
|
||
if _, ok := productIDSet[inv.ProductID]; !ok {
|
||
productIDSet[inv.ProductID] = struct{}{}
|
||
productIDs = append(productIDs, inv.ProductID)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 批量预加载所有产品信息
|
||
productMap := make(map[int64]*model.Products)
|
||
if len(productIDs) > 0 {
|
||
pList, _ := h.readDB.Products.WithContext(ctx.RequestContext()).Where(h.readDB.Products.ID.In(productIDs...)).Find()
|
||
for _, p := range pList {
|
||
productMap[p.ID] = p
|
||
}
|
||
}
|
||
|
||
// 构建结果列表
|
||
completed := int64(len(logs))
|
||
items := make([]orderResultItem, 0, len(logs)+len(validDoubledInvs))
|
||
drawLogIDs := make([]int64, 0, len(logs))
|
||
|
||
// 处理普通奖励
|
||
for _, lg := range logs {
|
||
drawLogIDs = append(drawLogIDs, lg.ID)
|
||
image := ""
|
||
name := ""
|
||
if rw, ok := rewardMap[lg.RewardID]; ok {
|
||
if p, ok := productMap[rw.ProductID]; ok {
|
||
name = p.Name
|
||
if p.ImagesJSON != "" {
|
||
var imgs []string
|
||
if json.Unmarshal([]byte(p.ImagesJSON), &imgs) == nil && len(imgs) > 0 {
|
||
image = imgs[0]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
items = append(items, orderResultItem{
|
||
RewardID: lg.RewardID,
|
||
RewardName: name,
|
||
Level: lg.Level,
|
||
DrawIndex: int32(lg.DrawIndex + 1),
|
||
Image: image,
|
||
})
|
||
}
|
||
|
||
// 处理翻倍奖励
|
||
for _, inv := range validDoubledInvs {
|
||
image := ""
|
||
name := inv.Remark
|
||
if p, ok := productMap[inv.ProductID]; ok {
|
||
name = p.Name
|
||
if p.ImagesJSON != "" {
|
||
var imgs []string
|
||
if json.Unmarshal([]byte(p.ImagesJSON), &imgs) == nil && len(imgs) > 0 {
|
||
image = imgs[0]
|
||
}
|
||
}
|
||
}
|
||
|
||
items = append(items, orderResultItem{
|
||
RewardID: inv.RewardID,
|
||
RewardName: name + "(翻倍)",
|
||
Level: 0,
|
||
DrawIndex: int32(len(logs) + 1), // 翻倍奖励排在最后
|
||
Image: image,
|
||
})
|
||
}
|
||
|
||
st := "pending"
|
||
if ord.Status == 4 {
|
||
st = "refunded"
|
||
} else if ord.Status == 2 {
|
||
if completed < dc {
|
||
st = "paid_waiting"
|
||
} else {
|
||
st = "settled"
|
||
}
|
||
}
|
||
|
||
var receipt map[string]any
|
||
var randomReceipts []map[string]any
|
||
if len(drawLogIDs) > 0 {
|
||
if recs, _ := h.readDB.ActivityDrawReceipts.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityDrawReceipts.DrawLogID.In(drawLogIDs...)).Find(); len(recs) > 0 {
|
||
recMap := make(map[int64]*model.ActivityDrawReceipts, len(recs))
|
||
for _, r := range recs {
|
||
recMap[r.DrawLogID] = r
|
||
}
|
||
randomReceipts = make([]map[string]any, 0, len(recs))
|
||
for i := 0; i < len(logs); i++ {
|
||
lg := logs[i]
|
||
if r, ok := recMap[lg.ID]; ok {
|
||
randomReceipts = append(randomReceipts, map[string]any{
|
||
"draw_log_id": lg.ID,
|
||
"reward_id": lg.RewardID,
|
||
"draw_index": i + 1,
|
||
"algo_version": r.AlgoVersion,
|
||
"round_id": r.RoundID,
|
||
"draw_id": r.DrawID,
|
||
"client_id": r.ClientID,
|
||
"timestamp": r.Timestamp,
|
||
"server_seed_hash": r.ServerSeedHash,
|
||
"server_sub_seed": r.ServerSubSeed,
|
||
"client_seed": r.ClientSeed,
|
||
"nonce": r.Nonce,
|
||
"items_root": r.ItemsRoot,
|
||
"weights_total": r.WeightsTotal,
|
||
"selected_index": r.SelectedIndex,
|
||
"rand_proof": r.RandProof,
|
||
"signature": r.Signature,
|
||
"items_snapshot": r.ItemsSnapshot,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
aid := int64(0)
|
||
if iss > 0 {
|
||
issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityIssues.ID.Eq(iss)).First()
|
||
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 && len(act.CommitmentSeedMaster) > 0 {
|
||
ts := time.Now().UnixMilli()
|
||
nonce := ts
|
||
mac := hmac.New(sha256.New, act.CommitmentSeedMaster)
|
||
mac.Write([]byte(h.joinSigPayload(userID, iss, ts, nonce)))
|
||
sig := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||
receipt = map[string]any{
|
||
"issue_id": iss,
|
||
"seed_version": act.CommitmentStateVersion,
|
||
"timestamp": ts,
|
||
"nonce": nonce,
|
||
"signature": sig,
|
||
"algorithm": "HMAC-SHA256",
|
||
"inputs": map[string]any{"user_id": userID, "issue_id": iss, "order_id": ord.ID, "timestamp": ts, "nonce": nonce},
|
||
}
|
||
}
|
||
}
|
||
if len(randomReceipts) > 0 {
|
||
if receipt == nil {
|
||
receipt = map[string]any{}
|
||
}
|
||
receipt["draw_receipts"] = randomReceipts
|
||
}
|
||
// 构建响应,包含随机种子和下次开奖时间
|
||
var seedHex string
|
||
var nextDrawTime string
|
||
if aid > 0 {
|
||
act, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).Where(h.readDB.Activities.ID.Eq(aid)).First()
|
||
if act != nil {
|
||
// 问题2: 返回随机种子(十六进制格式)
|
||
if len(act.CommitmentSeedMaster) > 0 {
|
||
seedHex = hex.EncodeToString(act.CommitmentSeedMaster)
|
||
}
|
||
// 问题3: 定时抽奖返回下一次开奖时间
|
||
if mode == "scheduled" && !act.ScheduledTime.IsZero() {
|
||
nextDrawTime = act.ScheduledTime.Format(time.RFC3339)
|
||
}
|
||
}
|
||
}
|
||
rsp := &orderResultResponse{Status: st, DrawMode: mode, Count: dc, Completed: completed, Results: items, Receipt: receipt, NextPollMs: 2000, SeedHex: seedHex, NextDrawTime: nextDrawTime}
|
||
ctx.Payload(rsp)
|
||
}
|
||
}
|