216 lines
7.2 KiB
Go
216 lines
7.2 KiB
Go
package app
|
|
|
|
import (
|
|
"bindbox-game/internal/code"
|
|
"bindbox-game/internal/pkg/core"
|
|
"bindbox-game/internal/pkg/validation"
|
|
"bindbox-game/internal/repository/mysql/model"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"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"`
|
|
}
|
|
|
|
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
|
|
}
|
|
iss := parseIssueIDFromRemark(ord.Remark)
|
|
dc := parseCountFromRemark(ord.Remark)
|
|
if dc <= 0 {
|
|
dc = 1
|
|
}
|
|
|
|
// 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 _, lg := range logs {
|
|
drawLogIDs = append(drawLogIDs, lg.ID)
|
|
rid := lg.RewardID
|
|
name := rewardNameCache[rid]
|
|
if name == "" && rid > 0 {
|
|
rw, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).Where(h.readDB.ActivityRewardSettings.ID.Eq(rid)).First()
|
|
if rw != nil {
|
|
name = rw.Name
|
|
rewardNameCache[rid] = name
|
|
}
|
|
}
|
|
// 使用数据库中原始的 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 {
|
|
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)
|
|
}
|
|
}
|