diff --git a/.DS_Store b/.DS_Store
index e05aa09..b3c81ee 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/build/.DS_Store b/build/.DS_Store
index 8d2cfde..b898caf 100644
Binary files a/build/.DS_Store and b/build/.DS_Store differ
diff --git a/build/resources/.DS_Store b/build/resources/.DS_Store
index ae3f9bb..f0e483e 100644
Binary files a/build/resources/.DS_Store and b/build/resources/.DS_Store differ
diff --git a/build/resources/admin/index.html b/build/resources/admin/index.html
index ab48353..aca2404 100644
--- a/build/resources/admin/index.html
+++ b/build/resources/admin/index.html
@@ -38,7 +38,7 @@
}
})()
-
+
diff --git a/docs/docs.go b/docs/docs.go
index 26a7133..58f6528 100644
--- a/docs/docs.go
+++ b/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": {
diff --git a/docs/swagger.json b/docs/swagger.json
index 888b115..2d0aacd 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -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": {
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 8201170..b98af41 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -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:
diff --git a/internal/api/activity/draw_logs_app.go b/internal/api/activity/draw_logs_app.go
index 99395fb..cc8b2ee 100644
--- a/internal/api/activity/draw_logs_app.go
+++ b/internal/api/activity/draw_logs_app.go
@@ -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
diff --git a/internal/api/app/categories.go b/internal/api/app/categories.go
index a572803..f9b62eb 100644
--- a/internal/api/app/categories.go
+++ b/internal/api/app/categories.go
@@ -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())
diff --git a/internal/api/app/notice.go b/internal/api/app/notice.go
index 66697c9..8442ea9 100644
--- a/internal/api/app/notice.go
+++ b/internal/api/app/notice.go
@@ -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)
diff --git a/internal/api/app/store.go b/internal/api/app/store.go
index d40ff80..a3f3f57 100644
--- a/internal/api/app/store.go
+++ b/internal/api/app/store.go
@@ -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)
diff --git a/internal/api/internal/minesweeper/handler.go b/internal/api/internal/minesweeper/handler.go
new file mode 100644
index 0000000..3747bda
--- /dev/null
+++ b/internal/api/internal/minesweeper/handler.go
@@ -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"`
+}
diff --git a/internal/api/task_center/tasks_app.go b/internal/api/task_center/tasks_app.go
index 6067ca0..25cde8c 100644
--- a/internal/api/task_center/tasks_app.go
+++ b/internal/api/task_center/tasks_app.go
@@ -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)
diff --git a/internal/api/user/points_redeem_item_card_app.go b/internal/api/user/points_redeem_item_card_app.go
index 0aff562..3d2b285 100644
--- a/internal/api/user/points_redeem_item_card_app.go
+++ b/internal/api/user/points_redeem_item_card_app.go
@@ -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)
diff --git a/internal/router/router.go b/internal/router/router.go
index 4ac2adc..270461d 100644
--- a/internal/router/router.go
+++ b/internal/router/router.go
@@ -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")
{
diff --git a/internal/service/activity/activity.go b/internal/service/activity/activity.go
index 4da7c29..e544dc5 100644
--- a/internal/service/activity/activity.go
+++ b/internal/service/activity/activity.go
@@ -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数组
diff --git a/internal/service/activity/draw_logs_list.go b/internal/service/activity/draw_logs_list.go
index 98356e6..514faab 100644
--- a/internal/service/activity/draw_logs_list.go
+++ b/internal/service/activity/draw_logs_list.go
@@ -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