bindbox-game/internal/api/activity/lottery_result_order_app.go

304 lines
9.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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