feat(活动): 新增抽奖记录等级筛选功能并优化展示信息

refactor(抽奖记录): 重构抽奖记录列表接口,支持按等级筛选
新增用户昵称、头像及奖品名称、图片等展示字段
优化分页逻辑,默认返回最新100条记录

feat(游戏): 添加扫雷游戏验证和结算接口
新增游戏票据验证和结算相关接口定义及Swagger文档

docs(API): 更新Swagger文档
更新抽奖记录和游戏相关接口的文档描述

style(路由): 添加游戏路由注释
添加扫雷游戏接口路由的占位注释
This commit is contained in:
邹方成 2025-12-21 23:45:01 +08:00
parent e2782a69d3
commit c8b04e2bc6
21 changed files with 706 additions and 64 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
build.zip

Binary file not shown.

BIN
build/.DS_Store vendored

Binary file not shown.

Binary file not shown.

View File

@ -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>

View File

@ -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": {

View File

@ -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": {

View File

@ -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:

View File

@ -1,6 +1,7 @@
package app
import (
"encoding/json"
"net/http"
"strconv"
"time"
@ -8,19 +9,25 @@ 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"`
Level *int32 `form:"level"`
}
type drawLogItem struct {
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"`
@ -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

View File

@ -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())

View File

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

View File

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

View 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"`
}

View File

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

View File

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

View File

@ -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")
{

View File

@ -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数组

View File

@ -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

View File

@ -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
tmp_build

Binary file not shown.

BIN
web/.DS_Store vendored

Binary file not shown.