feat(活动): 新增抽奖记录等级筛选功能并优化展示信息
refactor(抽奖记录): 重构抽奖记录列表接口,支持按等级筛选 新增用户昵称、头像及奖品名称、图片等展示字段 优化分页逻辑,默认返回最新100条记录 feat(游戏): 添加扫雷游戏验证和结算接口 新增游戏票据验证和结算相关接口定义及Swagger文档 docs(API): 更新Swagger文档 更新抽奖记录和游戏相关接口的文档描述 style(路由): 添加游戏路由注释 添加扫雷游戏接口路由的占位注释
This commit is contained in:
parent
e2782a69d3
commit
c8b04e2bc6
BIN
build/.DS_Store
vendored
BIN
build/.DS_Store
vendored
Binary file not shown.
BIN
build/resources/.DS_Store
vendored
BIN
build/resources/.DS_Store
vendored
Binary file not shown.
@ -38,7 +38,7 @@
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-q3pUuhDl.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-iR6j7F-E.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-q7XdNN2Z.css">
|
||||
</head>
|
||||
|
||||
|
||||
157
docs/docs.go
157
docs/docs.go
@ -3451,7 +3451,7 @@ const docTemplate = `{
|
||||
},
|
||||
"/api/app/activities/{activity_id}/issues/{issue_id}/draw_logs": {
|
||||
"get": {
|
||||
"description": "查看指定活动期数的抽奖记录,支持分页",
|
||||
"description": "查看指定活动期数的抽奖记录,支持等级筛选(默认返回最新的100条,不支持自定义翻页)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@ -3479,19 +3479,9 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "页码",
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 20,
|
||||
"description": "每页数量,最多100",
|
||||
"name": "page_size",
|
||||
"in": "query",
|
||||
"required": true
|
||||
"description": "奖品等级过滤",
|
||||
"name": "level",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@ -5905,6 +5895,74 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/internal/game/settle": {
|
||||
"post": {
|
||||
"description": "游戏结束后结算并发放奖励",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Internal"
|
||||
],
|
||||
"summary": "结算游戏",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "结算信息",
|
||||
"name": "result",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/minesweeper.SettleGameRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/minesweeper.SettleGameResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/internal/game/verify": {
|
||||
"post": {
|
||||
"description": "验证游戏票据是否有效",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Internal"
|
||||
],
|
||||
"summary": "验证游戏票据",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "票据信息",
|
||||
"name": "ticket",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/minesweeper.VerifyTicketRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/minesweeper.VerifyTicketResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pay/wechat/notify": {
|
||||
"post": {
|
||||
"description": "接收微信支付结果通知,验证签名并处理订单状态",
|
||||
@ -8174,6 +8232,9 @@ const docTemplate = `{
|
||||
"app.drawLogItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"type": "string"
|
||||
},
|
||||
"current_level": {
|
||||
"type": "integer"
|
||||
},
|
||||
@ -8195,8 +8256,17 @@ const docTemplate = `{
|
||||
"reward_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"reward_image": {
|
||||
"type": "string"
|
||||
},
|
||||
"reward_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"user_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -9161,6 +9231,65 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"minesweeper.SettleGameRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "string"
|
||||
},
|
||||
"score": {
|
||||
"type": "integer"
|
||||
},
|
||||
"ticket": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"win": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minesweeper.SettleGameResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reward": {
|
||||
"type": "string"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minesweeper.VerifyTicketRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticket": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minesweeper.VerifyTicketResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"remaining_times": {
|
||||
"type": "integer"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"valid": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.ActivityDrawEffects": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@ -3443,7 +3443,7 @@
|
||||
},
|
||||
"/api/app/activities/{activity_id}/issues/{issue_id}/draw_logs": {
|
||||
"get": {
|
||||
"description": "查看指定活动期数的抽奖记录,支持分页",
|
||||
"description": "查看指定活动期数的抽奖记录,支持等级筛选(默认返回最新的100条,不支持自定义翻页)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@ -3471,19 +3471,9 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "页码",
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 20,
|
||||
"description": "每页数量,最多100",
|
||||
"name": "page_size",
|
||||
"in": "query",
|
||||
"required": true
|
||||
"description": "奖品等级过滤",
|
||||
"name": "level",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@ -5897,6 +5887,74 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/internal/game/settle": {
|
||||
"post": {
|
||||
"description": "游戏结束后结算并发放奖励",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Internal"
|
||||
],
|
||||
"summary": "结算游戏",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "结算信息",
|
||||
"name": "result",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/minesweeper.SettleGameRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/minesweeper.SettleGameResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/internal/game/verify": {
|
||||
"post": {
|
||||
"description": "验证游戏票据是否有效",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Internal"
|
||||
],
|
||||
"summary": "验证游戏票据",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "票据信息",
|
||||
"name": "ticket",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/minesweeper.VerifyTicketRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/minesweeper.VerifyTicketResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pay/wechat/notify": {
|
||||
"post": {
|
||||
"description": "接收微信支付结果通知,验证签名并处理订单状态",
|
||||
@ -8166,6 +8224,9 @@
|
||||
"app.drawLogItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"type": "string"
|
||||
},
|
||||
"current_level": {
|
||||
"type": "integer"
|
||||
},
|
||||
@ -8187,8 +8248,17 @@
|
||||
"reward_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"reward_image": {
|
||||
"type": "string"
|
||||
},
|
||||
"reward_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"user_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -9153,6 +9223,65 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"minesweeper.SettleGameRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "string"
|
||||
},
|
||||
"score": {
|
||||
"type": "integer"
|
||||
},
|
||||
"ticket": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"win": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minesweeper.SettleGameResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reward": {
|
||||
"type": "string"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minesweeper.VerifyTicketRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticket": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minesweeper.VerifyTicketResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"remaining_times": {
|
||||
"type": "integer"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"valid": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.ActivityDrawEffects": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@ -1475,6 +1475,8 @@ definitions:
|
||||
type: object
|
||||
app.drawLogItem:
|
||||
properties:
|
||||
avatar:
|
||||
type: string
|
||||
current_level:
|
||||
type: integer
|
||||
id:
|
||||
@ -1489,8 +1491,14 @@ definitions:
|
||||
type: integer
|
||||
reward_id:
|
||||
type: integer
|
||||
reward_image:
|
||||
type: string
|
||||
reward_name:
|
||||
type: string
|
||||
user_id:
|
||||
type: integer
|
||||
user_name:
|
||||
type: string
|
||||
type: object
|
||||
app.getAppProductDetailResponse:
|
||||
properties:
|
||||
@ -2118,6 +2126,44 @@ definitions:
|
||||
description: 描述信息
|
||||
type: string
|
||||
type: object
|
||||
minesweeper.SettleGameRequest:
|
||||
properties:
|
||||
match_id:
|
||||
type: string
|
||||
metadata:
|
||||
type: string
|
||||
score:
|
||||
type: integer
|
||||
ticket:
|
||||
type: string
|
||||
user_id:
|
||||
type: string
|
||||
win:
|
||||
type: boolean
|
||||
type: object
|
||||
minesweeper.SettleGameResponse:
|
||||
properties:
|
||||
reward:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
type: object
|
||||
minesweeper.VerifyTicketRequest:
|
||||
properties:
|
||||
ticket:
|
||||
type: string
|
||||
user_id:
|
||||
type: string
|
||||
type: object
|
||||
minesweeper.VerifyTicketResponse:
|
||||
properties:
|
||||
remaining_times:
|
||||
type: integer
|
||||
user_id:
|
||||
type: string
|
||||
valid:
|
||||
type: boolean
|
||||
type: object
|
||||
model.ActivityDrawEffects:
|
||||
properties:
|
||||
activity_category_id:
|
||||
@ -4973,7 +5019,7 @@ paths:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 查看指定活动期数的抽奖记录,支持分页
|
||||
description: 查看指定活动期数的抽奖记录,支持等级筛选(默认返回最新的100条,不支持自定义翻页)
|
||||
parameters:
|
||||
- description: 活动ID
|
||||
in: path
|
||||
@ -4985,17 +5031,9 @@ paths:
|
||||
name: issue_id
|
||||
required: true
|
||||
type: integer
|
||||
- default: 1
|
||||
description: 页码
|
||||
- description: 奖品等级过滤
|
||||
in: query
|
||||
name: page
|
||||
required: true
|
||||
type: integer
|
||||
- default: 20
|
||||
description: 每页数量,最多100
|
||||
in: query
|
||||
name: page_size
|
||||
required: true
|
||||
name: level
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
@ -6550,6 +6588,50 @@ paths:
|
||||
summary: WangEditor图片上传
|
||||
tags:
|
||||
- Common
|
||||
/internal/game/settle:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 游戏结束后结算并发放奖励
|
||||
parameters:
|
||||
- description: 结算信息
|
||||
in: body
|
||||
name: result
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/minesweeper.SettleGameRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/minesweeper.SettleGameResponse'
|
||||
summary: 结算游戏
|
||||
tags:
|
||||
- Internal
|
||||
/internal/game/verify:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 验证游戏票据是否有效
|
||||
parameters:
|
||||
- description: 票据信息
|
||||
in: body
|
||||
name: ticket
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/minesweeper.VerifyTicketRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/minesweeper.VerifyTicketResponse'
|
||||
summary: 验证游戏票据
|
||||
tags:
|
||||
- Internal
|
||||
/pay/wechat/notify:
|
||||
post:
|
||||
consumes:
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -8,22 +9,28 @@ import (
|
||||
"bindbox-game/internal/code"
|
||||
"bindbox-game/internal/pkg/core"
|
||||
"bindbox-game/internal/pkg/validation"
|
||||
"bindbox-game/internal/repository/mysql/model"
|
||||
)
|
||||
|
||||
type listDrawLogsRequest struct {
|
||||
Page int `form:"page"`
|
||||
PageSize int `form:"page_size"`
|
||||
Page int `form:"page"`
|
||||
PageSize int `form:"page_size"`
|
||||
Level *int32 `form:"level"`
|
||||
}
|
||||
|
||||
type drawLogItem struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
IssueID int64 `json:"issue_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
RewardID int64 `json:"reward_id"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type listDrawLogsResponse struct {
|
||||
@ -35,14 +42,13 @@ type listDrawLogsResponse struct {
|
||||
|
||||
// ListDrawLogs 抽奖记录列表
|
||||
// @Summary 抽奖记录列表
|
||||
// @Description 查看指定活动期数的抽奖记录,支持分页
|
||||
// @Description 查看指定活动期数的抽奖记录,支持等级筛选(默认返回最新的100条,不支持自定义翻页)
|
||||
// @Tags APP端.活动
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param activity_id path integer true "活动ID"
|
||||
// @Param issue_id path integer true "期ID"
|
||||
// @Param page query int true "页码" default(1)
|
||||
// @Param page_size query int true "每页数量,最多100" default(20)
|
||||
// @Param level query int false "奖品等级过滤"
|
||||
// @Success 200 {object} listDrawLogsResponse
|
||||
// @Failure 400 {object} code.Failure
|
||||
// @Router /api/app/activities/{activity_id}/issues/{issue_id}/draw_logs [get]
|
||||
@ -59,22 +65,142 @@ func (h *handler) ListDrawLogs() core.HandlerFunc {
|
||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
|
||||
return
|
||||
}
|
||||
items, total, err := h.activity.ListDrawLogs(ctx.RequestContext(), issueID, req.Page, req.PageSize)
|
||||
|
||||
// 强制固定分页:第一页,100条
|
||||
page := 1
|
||||
pageSize := 100
|
||||
|
||||
// 计算5分钟前的时间点
|
||||
fiveMinutesAgo := time.Now().Add(-5 * time.Minute)
|
||||
|
||||
// 考虑到需要过滤掉5分钟内的数据,我们稍微多取一些数据,以确保过滤后能有足够的数据展示
|
||||
// 但为了防止数据量过大,还是做一个硬性限制,比如取前200条
|
||||
fetchSize := 200
|
||||
|
||||
items, _, err := h.activity.ListDrawLogs(ctx.RequestContext(), issueID, 1, fetchSize, req.Level)
|
||||
if err != nil {
|
||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ListDrawLogsError, err.Error()))
|
||||
return
|
||||
}
|
||||
res.Page = req.Page
|
||||
res.PageSize = req.PageSize
|
||||
|
||||
// 收集ID用于批量查询
|
||||
var userIDs []int64
|
||||
var rewardIDs []int64
|
||||
userSet := make(map[int64]struct{})
|
||||
rewardSet := make(map[int64]struct{})
|
||||
|
||||
// 过滤并收集ID
|
||||
var filteredItems []*model.ActivityDrawLogs
|
||||
for _, v := range items {
|
||||
// 过滤掉5分钟内的记录
|
||||
if v.CreatedAt.After(fiveMinutesAgo) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果已经收集够了100条,就不再收集了
|
||||
if len(filteredItems) >= pageSize {
|
||||
break
|
||||
}
|
||||
|
||||
filteredItems = append(filteredItems, v)
|
||||
|
||||
if v.UserID > 0 {
|
||||
if _, ok := userSet[v.UserID]; !ok {
|
||||
userSet[v.UserID] = struct{}{}
|
||||
userIDs = append(userIDs, v.UserID)
|
||||
}
|
||||
}
|
||||
if v.RewardID > 0 {
|
||||
if _, ok := rewardSet[v.RewardID]; !ok {
|
||||
rewardSet[v.RewardID] = struct{}{}
|
||||
rewardIDs = append(rewardIDs, v.RewardID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新items为过滤后的列表
|
||||
items = filteredItems
|
||||
total := int64(len(items)) // 更新total为实际返回数量
|
||||
|
||||
// 批量查询用户信息
|
||||
userNameMap := make(map[int64]string)
|
||||
userAvatarMap := make(map[int64]string)
|
||||
if len(userIDs) > 0 {
|
||||
users, err := h.readDB.Users.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Users.ID.In(userIDs...)).Find()
|
||||
if err == nil {
|
||||
for _, u := range users {
|
||||
userNameMap[u.ID] = u.Nickname
|
||||
userAvatarMap[u.ID] = u.Avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量查询奖品与商品信息
|
||||
rewardNameMap := make(map[int64]string)
|
||||
rewardImageMap := make(map[int64]string)
|
||||
if len(rewardIDs) > 0 {
|
||||
rewards, err := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityRewardSettings.ID.In(rewardIDs...)).Find()
|
||||
if err == nil {
|
||||
var productIDs []int64
|
||||
productSet := make(map[int64]struct{})
|
||||
rewardProductMap := make(map[int64]int64)
|
||||
|
||||
for _, r := range rewards {
|
||||
rewardNameMap[r.ID] = r.Name
|
||||
if r.ProductID > 0 {
|
||||
if _, ok := productSet[r.ProductID]; !ok {
|
||||
productSet[r.ProductID] = struct{}{}
|
||||
productIDs = append(productIDs, r.ProductID)
|
||||
}
|
||||
rewardProductMap[r.ID] = r.ProductID
|
||||
}
|
||||
}
|
||||
|
||||
if len(productIDs) > 0 {
|
||||
products, err := h.readDB.Products.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Products.ID.In(productIDs...)).Find()
|
||||
if err == nil {
|
||||
productImageMap := make(map[int64]string)
|
||||
productNameMap := make(map[int64]string)
|
||||
for _, p := range products {
|
||||
first := ""
|
||||
if p.ImagesJSON != "" {
|
||||
var arr []string
|
||||
_ = json.Unmarshal([]byte(p.ImagesJSON), &arr)
|
||||
if len(arr) > 0 {
|
||||
first = arr[0]
|
||||
}
|
||||
}
|
||||
productImageMap[p.ID] = first
|
||||
productNameMap[p.ID] = p.Name
|
||||
}
|
||||
// 填充奖品图片(如果奖品没有名字,也可以用商品名字兜底,但这里先只做图片)
|
||||
for rid, pid := range rewardProductMap {
|
||||
rewardImageMap[rid] = productImageMap[pid]
|
||||
// 如果配置里的奖品名为空,尝试用商品名
|
||||
if rewardNameMap[rid] == "" {
|
||||
rewardNameMap[rid] = productNameMap[pid]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.Page = page
|
||||
res.PageSize = pageSize
|
||||
res.Total = total
|
||||
res.List = make([]drawLogItem, len(items))
|
||||
for i, v := range items {
|
||||
res.List[i] = drawLogItem{
|
||||
ID: v.ID,
|
||||
UserID: v.UserID,
|
||||
UserName: userNameMap[v.UserID],
|
||||
Avatar: userAvatarMap[v.UserID],
|
||||
IssueID: v.IssueID,
|
||||
OrderID: v.OrderID,
|
||||
RewardID: v.RewardID,
|
||||
RewardName: rewardNameMap[v.RewardID],
|
||||
RewardImage: rewardImageMap[v.RewardID],
|
||||
IsWinner: v.IsWinner,
|
||||
Level: v.Level,
|
||||
CurrentLevel: v.CurrentLevel,
|
||||
@ -109,7 +235,7 @@ func (h *handler) ListDrawLogsByLevel() core.HandlerFunc {
|
||||
// 这里暂且获取所有(或者一个较大的限制),然后在内存中分组。
|
||||
// 如果数据量巨大,需要由 Service 层提供 Group By 查询。
|
||||
// 考虑到单期中奖人数通常有限(除非是大规模活动),先尝试获取列表后分组。
|
||||
logs, _, err := h.activity.ListDrawLogs(ctx.RequestContext(), issueID, 1, 1000) // 假设最多显示1000条中奖记录
|
||||
logs, _, err := h.activity.ListDrawLogs(ctx.RequestContext(), issueID, 1, 1000, nil) // 假设最多显示1000条中奖记录
|
||||
if err != nil {
|
||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ListDrawLogsError, err.Error()))
|
||||
return
|
||||
|
||||
@ -29,6 +29,15 @@ type listAppCategoriesResponse struct {
|
||||
List []appCategoryItem `json:"list"`
|
||||
}
|
||||
|
||||
// ListCategoriesForApp 获取分类列表
|
||||
// @Summary 获取分类列表
|
||||
// @Description 获取APP端商品分类列表
|
||||
// @Tags APP端.基础
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} listAppCategoriesResponse
|
||||
// @Failure 400 {object} code.Failure
|
||||
// @Router /api/app/categories [get]
|
||||
func (h *categoryHandler) ListCategoriesForApp() core.HandlerFunc {
|
||||
return func(ctx core.Context) {
|
||||
q := h.readDB.ProductCategories.WithContext(ctx.RequestContext())
|
||||
|
||||
@ -27,6 +27,14 @@ type listAppNoticesResponse struct {
|
||||
List []appNoticeItem `json:"list"`
|
||||
}
|
||||
|
||||
// ListNoticesForApp 获取公告列表
|
||||
// @Summary 获取公告列表
|
||||
// @Description 获取APP首页滚动公告
|
||||
// @Tags APP端.基础
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} listAppNoticesResponse
|
||||
// @Router /api/app/notices [get]
|
||||
func (h *noticeHandler) ListNoticesForApp() core.HandlerFunc {
|
||||
return func(ctx core.Context) {
|
||||
rsp := new(listAppNoticesResponse)
|
||||
|
||||
@ -46,6 +46,19 @@ type listStoreItemsResponse struct {
|
||||
List []listStoreItem `json:"list"`
|
||||
}
|
||||
|
||||
// ListStoreItemsForApp 获取积分商城商品列表
|
||||
// @Summary 获取积分商城商品列表
|
||||
// @Description 分页获取积分商城商品列表,支持按类型筛选(product/item_card/coupon)
|
||||
// @Tags APP端.积分商城
|
||||
// @Accept x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Security LoginVerifyToken
|
||||
// @Param kind query string false "商品类型: product(默认), item_card, coupon"
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(20)
|
||||
// @Success 200 {object} listStoreItemsResponse
|
||||
// @Failure 400 {object} code.Failure
|
||||
// @Router /api/app/store/items [get]
|
||||
func (h *storeHandler) ListStoreItemsForApp() core.HandlerFunc {
|
||||
return func(ctx core.Context) {
|
||||
req := new(listStoreItemsRequest)
|
||||
|
||||
119
internal/api/internal/minesweeper/handler.go
Normal file
119
internal/api/internal/minesweeper/handler.go
Normal file
@ -0,0 +1,119 @@
|
||||
package minesweeper
|
||||
|
||||
import (
|
||||
"bindbox-game/internal/pkg/core"
|
||||
"bindbox-game/internal/repository/mysql"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
logger *zap.Logger
|
||||
db mysql.Repo
|
||||
}
|
||||
|
||||
func New(logger *zap.Logger, db mysql.Repo) *Handler {
|
||||
return &Handler{
|
||||
logger: logger,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// i() 辅助函数
|
||||
func (h *Handler) i() {}
|
||||
|
||||
// VerifyTicket 验证游戏票据
|
||||
// @Summary 验证游戏票据
|
||||
// @Description 验证游戏票据是否有效
|
||||
// @Tags Internal
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param ticket body VerifyTicketRequest true "票据信息"
|
||||
// @Success 200 {object} VerifyTicketResponse
|
||||
// @Router /internal/game/verify [post]
|
||||
func (h *Handler) VerifyTicket() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(VerifyTicketRequest)
|
||||
if err := c.ShouldBindJSON(req); err != nil {
|
||||
c.AbortWithError(core.Error(
|
||||
400,
|
||||
10001,
|
||||
"参数错误",
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 实际验证逻辑,这里先Mock通过
|
||||
// 实际应该检查Redis中ticket是否存在且对应正确的user_id
|
||||
|
||||
c.Payload(VerifyTicketResponse{
|
||||
Valid: true,
|
||||
UserID: req.UserID,
|
||||
RemainingTimes: 1, // Mock
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SettleGame 结算游戏
|
||||
// @Summary 结算游戏
|
||||
// @Description 游戏结束后结算并发放奖励
|
||||
// @Tags Internal
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param result body SettleGameRequest true "结算信息"
|
||||
// @Success 200 {object} SettleGameResponse
|
||||
// @Router /internal/game/settle [post]
|
||||
func (h *Handler) SettleGame() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(SettleGameRequest)
|
||||
if err := c.ShouldBindJSON(req); err != nil {
|
||||
c.AbortWithError(core.Error(
|
||||
400,
|
||||
10001,
|
||||
"参数错误",
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 实际结算逻辑
|
||||
// 1. 验证 ticket 状态为“进行中”
|
||||
// 2. 如果 Win=true,发放奖励
|
||||
// 3. 标记 ticket 为“已完成”
|
||||
|
||||
h.logger.Info("Game Settled",
|
||||
zap.String("user_id", req.UserID),
|
||||
zap.Bool("win", req.Win),
|
||||
zap.Int("score", req.Score),
|
||||
)
|
||||
|
||||
c.Payload(SettleGameResponse{
|
||||
Success: true,
|
||||
Reward: "100积分", // Mock
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type VerifyTicketRequest struct {
|
||||
UserID string `json:"user_id"`
|
||||
Ticket string `json:"ticket"`
|
||||
}
|
||||
|
||||
type VerifyTicketResponse struct {
|
||||
Valid bool `json:"valid"`
|
||||
UserID string `json:"user_id"`
|
||||
RemainingTimes int `json:"remaining_times"`
|
||||
}
|
||||
|
||||
type SettleGameRequest struct {
|
||||
UserID string `json:"user_id"`
|
||||
Ticket string `json:"ticket"`
|
||||
MatchID string `json:"match_id"`
|
||||
Win bool `json:"win"`
|
||||
Score int `json:"score"`
|
||||
Metadata string `json:"metadata"`
|
||||
}
|
||||
|
||||
type SettleGameResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Reward string `json:"reward"`
|
||||
}
|
||||
@ -58,7 +58,7 @@ type listTasksResponse struct {
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(20)
|
||||
// @Success 200 {object} listTasksResponse "任务列表"
|
||||
// @Router /app/task_center/tasks [get]
|
||||
// @Router /api/app/task-center/tasks [get]
|
||||
func (h *handler) ListTasksForApp() core.HandlerFunc {
|
||||
return func(ctx core.Context) {
|
||||
req := new(listTasksRequest)
|
||||
@ -115,7 +115,7 @@ type taskProgressResponse struct {
|
||||
// @Param id path int true "任务ID"
|
||||
// @Param user_id path int true "用户ID"
|
||||
// @Success 200 {object} taskProgressResponse "任务进度详情"
|
||||
// @Router /app/task_center/tasks/{id}/progress/{user_id} [get]
|
||||
// @Router /api/app/task-center/tasks/{id}/progress/{user_id} [get]
|
||||
func (h *handler) GetTaskProgressForApp() core.HandlerFunc {
|
||||
return func(ctx core.Context) {
|
||||
taskID, err := strconv.ParseInt(ctx.Param("id"), 10, 64)
|
||||
@ -150,7 +150,7 @@ type claimTaskRequest struct {
|
||||
// @Param user_id path int true "用户ID"
|
||||
// @Param request body claimTaskRequest true "领取请求"
|
||||
// @Success 200 {object} map[string]any "领取成功"
|
||||
// @Router /app/task_center/tasks/{id}/users/{user_id}/claim [post]
|
||||
// @Router /api/app/task-center/tasks/{id}/claim/{user_id} [post]
|
||||
func (h *handler) ClaimTaskTierForApp() core.HandlerFunc {
|
||||
return func(ctx core.Context) {
|
||||
taskID, err := strconv.ParseInt(ctx.Param("id"), 10, 64)
|
||||
|
||||
@ -19,6 +19,18 @@ type redeemItemCardResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// RedeemPointsToItemCard 积分兑换道具卡
|
||||
// @Summary 积分兑换道具卡
|
||||
// @Description 使用积分兑换指定数量的道具卡
|
||||
// @Tags APP端.用户
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security LoginVerifyToken
|
||||
// @Param user_id path int true "用户ID"
|
||||
// @Param request body redeemItemCardRequest true "兑换请求参数"
|
||||
// @Success 200 {object} redeemItemCardResponse
|
||||
// @Failure 400 {object} code.Failure
|
||||
// @Router /api/app/users/{user_id}/points/redeem-item-card [post]
|
||||
func (h *handler) RedeemPointsToItemCard() core.HandlerFunc {
|
||||
return func(ctx core.Context) {
|
||||
req := new(redeemItemCardRequest)
|
||||
|
||||
@ -54,8 +54,17 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, error) {
|
||||
userHandler := userapi.New(logger, db)
|
||||
commonHandler := commonapi.New(logger, db)
|
||||
payHandler := payapi.New(logger, db)
|
||||
// minesweeperHandler := minesweeperapi.New(logger, db)
|
||||
intc := interceptor.New(logger, db)
|
||||
|
||||
// 内部服务接口路由组 (供 Nakama 调用)
|
||||
// internalRouter := mux.Group("/internal")
|
||||
// {
|
||||
// TODO: 添加IP白名单或Internal-Key验证中间件
|
||||
// internalRouter.POST("/game/verify", minesweeperHandler.VerifyTicket())
|
||||
// internalRouter.POST("/game/settle", minesweeperHandler.SettleGame())
|
||||
// }
|
||||
|
||||
// 管理端非认证接口路由组
|
||||
adminNonAuthApiRouter := mux.Group("/api/admin")
|
||||
{
|
||||
|
||||
@ -67,9 +67,9 @@ type Service interface {
|
||||
DeleteIssueReward(ctx context.Context, rewardID int64) error
|
||||
|
||||
// ListDrawLogs 抽奖记录列表
|
||||
// 参数: issueID 期ID, page/pageSize 分页
|
||||
// 参数: issueID 期ID, page/pageSize 分页, level 等级过滤
|
||||
// 返回: 抽奖记录集合、总数与错误
|
||||
ListDrawLogs(ctx context.Context, issueID int64, page, pageSize int) (items []*model.ActivityDrawLogs, total int64, err error)
|
||||
ListDrawLogs(ctx context.Context, issueID int64, page, pageSize int, level *int32) (items []*model.ActivityDrawLogs, total int64, err error)
|
||||
|
||||
// GetCategoryNames 批量查询分类名称
|
||||
// 参数: ids 分类ID数组
|
||||
|
||||
@ -7,10 +7,13 @@ import (
|
||||
)
|
||||
|
||||
// ListDrawLogs 查询抽奖记录列表(支持分页)
|
||||
// 参数: issueID 期ID, page/pageSize 分页参数
|
||||
// 参数: issueID 期ID, page/pageSize 分页参数, level 等级过滤(可选)
|
||||
// 返回: 抽奖记录集合、总数与错误
|
||||
func (s *service) ListDrawLogs(ctx context.Context, issueID int64, page, pageSize int) (items []*model.ActivityDrawLogs, total int64, err error) {
|
||||
func (s *service) ListDrawLogs(ctx context.Context, issueID int64, page, pageSize int, level *int32) (items []*model.ActivityDrawLogs, total int64, err error) {
|
||||
q := s.readDB.ActivityDrawLogs.WithContext(ctx).ReadDB().Where(s.readDB.ActivityDrawLogs.IssueID.Eq(issueID))
|
||||
if level != nil {
|
||||
q = q.Where(s.readDB.ActivityDrawLogs.Level.Eq(*level))
|
||||
}
|
||||
total, err = q.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
||||
@ -19,3 +19,6 @@
|
||||
{"level":"fatal","time":"2025-12-11 00:56:11","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
||||
{"level":"fatal","time":"2025-12-11 15:05:14","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-21 14:34:33","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"}
|
||||
{"level":"info","time":"2025-12-21 17:43:58","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"}
|
||||
{"level":"info","time":"2025-12-21 17:59:49","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"}
|
||||
{"level":"fatal","time":"2025-12-21 17:59:49","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}
|
||||
|
||||
BIN
web/.DS_Store
vendored
BIN
web/.DS_Store
vendored
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user