package app import ( "net/http" "strconv" "time" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" ) type listDrawLogsRequest struct { Page int `form:"page"` PageSize int `form:"page_size"` } 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"` } type listDrawLogsResponse struct { Page int `json:"page"` PageSize int `json:"page_size"` Total int64 `json:"total"` List []drawLogItem `json:"list"` } // ListDrawLogs 抽奖记录列表 // @Summary 抽奖记录列表 // @Description 查看指定活动期数的抽奖记录,支持分页 // @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) // @Success 200 {object} listDrawLogsResponse // @Failure 400 {object} code.Failure // @Router /api/app/activities/{activity_id}/issues/{issue_id}/draw_logs [get] func (h *handler) ListDrawLogs() core.HandlerFunc { return func(ctx core.Context) { req := new(listDrawLogsRequest) res := new(listDrawLogsResponse) if err := ctx.ShouldBindForm(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } issueID, err := strconv.ParseInt(ctx.Param("issue_id"), 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID")) return } items, total, err := h.activity.ListDrawLogs(ctx.RequestContext(), issueID, req.Page, req.PageSize) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ListDrawLogsError, err.Error())) return } res.Page = req.Page res.PageSize = req.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, IssueID: v.IssueID, OrderID: v.OrderID, RewardID: v.RewardID, IsWinner: v.IsWinner, Level: v.Level, CurrentLevel: v.CurrentLevel, } } ctx.Payload(res) } } // ListDrawLogsByLevel 按奖品等级分类的抽奖记录 // @Summary 按奖品等级分类的抽奖记录 // @Description 查看指定活动期数的抽奖记录,按奖品等级分组返回 // @Tags APP端.活动 // @Accept json // @Produce json // @Param activity_id path integer true "活动ID" // @Param issue_id path integer true "期ID" // @Success 200 {object} listDrawLogsByLevelResponse // @Failure 400 {object} code.Failure // @Router /api/app/activities/{activity_id}/issues/{issue_id}/draw_logs_grouped [get] func (h *handler) ListDrawLogsByLevel() core.HandlerFunc { return func(ctx core.Context) { issueID, err := strconv.ParseInt(ctx.Param("issue_id"), 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID")) return } // 1. 获取所有中奖记录 // 我们假设这里不需要分页,或者分页逻辑比较复杂(每个等级分页?)。 // 根据需求描述“按奖品等级进行归类”,通常 implied 展示所有或者前N个。 // 这里暂且获取所有(或者一个较大的限制),然后在内存中分组。 // 如果数据量巨大,需要由 Service 层提供 Group By 查询。 // 考虑到单期中奖人数通常有限(除非是大规模活动),先尝试获取列表后分组。 logs, _, err := h.activity.ListDrawLogs(ctx.RequestContext(), issueID, 1, 1000) // 假设最多显示1000条中奖记录 if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ListDrawLogsError, err.Error())) return } // 2. 获取奖品配置以获取等级名称 rewards, err := h.activity.ListIssueRewards(ctx.RequestContext(), issueID) if err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.GetActivityError, err.Error())) return } levelNameMap := make(map[int32]string) for _, r := range rewards { // 通常 Level 1 是大奖,名字如 "一等奖" // 也有可能 ActivityRewardSettings 里没有直接存 "一等奖" 这种字样,而是 Name="iPhone 15"。 // 如果需要 "一等奖" 这种分类名,可能需要额外配置或从 Name 推断。 // 此处暂且使用 Reward 的 Name 作为 fallback,或者如果有 LevelName 字段最好。 // 查看 model 定义,ActivityRewardSettings 只有 Name。 // 假如用户希望看到的是 "一等奖", "二等奖",我们需要确立 Level 到 显示名的映射。 // 现阶段简单起见,我们将同一 Level 的奖品视为一组。 // 组名可以使用该 Level 下任意一个奖品的 Name,或者如果不一致,则可能需要前端映射。 // 为了通用性,我们返回 level 值,并尝试找到一个代表性的 Name。 if _, ok := levelNameMap[r.Level]; !ok { levelNameMap[r.Level] = r.Name // 简单取第一个遇到的名字 } } // 3. 分组 (只显示5分钟前的记录) groupsMap := make(map[int32][]drawLogItem) fiveMinutesAgo := time.Now().Add(-5 * time.Minute) for _, v := range logs { // 过滤掉5分钟内的记录 if v.CreatedAt.After(fiveMinutesAgo) { continue } if v.IsWinner == 1 { item := drawLogItem{ ID: v.ID, UserID: v.UserID, IssueID: v.IssueID, OrderID: v.OrderID, RewardID: v.RewardID, IsWinner: v.IsWinner, Level: v.Level, CurrentLevel: v.CurrentLevel, } groupsMap[v.Level] = append(groupsMap[v.Level], item) } } // 4. 构造响应 var resp listDrawLogsByLevelResponse for level, items := range groupsMap { group := drawLogGroup{ Level: level, LevelName: levelNameMap[level], List: items, } resp.Groups = append(resp.Groups, group) } // 排序 Groups (Level 升序? 也就是 1等奖在前) // 简单的冒泡排序或 slice sort for i := 0; i < len(resp.Groups)-1; i++ { for j := 0; j < len(resp.Groups)-1-i; j++ { if resp.Groups[j].Level > resp.Groups[j+1].Level { resp.Groups[j], resp.Groups[j+1] = resp.Groups[j+1], resp.Groups[j] } } } ctx.Payload(resp) } } type drawLogGroup struct { Level int32 `json:"level"` LevelName string `json:"level_name"` List []drawLogItem `json:"list"` } type listDrawLogsByLevelResponse struct { Groups []drawLogGroup `json:"groups"` }