package admin import ( "net/http" "strconv" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" ) type activityRankingsRequest struct { SortBy string `form:"sort_by"` Page int `form:"page"` PageSize int `form:"page_size"` } type activityRankingItem struct { Rank int64 `json:"rank"` UserID int64 `json:"user_id"` Nickname string `json:"nickname"` Avatar string `json:"avatar"` TotalAmount int64 `json:"total_amount"` OrderCount int64 `json:"order_count"` } type activityRankingsResponse struct { Page int `json:"page"` PageSize int `json:"page_size"` Total int64 `json:"total"` List []activityRankingItem `json:"list"` } // GetActivityRankings 获取活动内用户消费/订单排行榜 // @Summary 活动排行榜 // @Description 按活动维度统计用户消费总额与订单数排行榜 // @Tags 管理端.活动 // @Accept json // @Produce json // @Param activity_id path int true "活动ID" // @Param sort_by query string false "排序字段: amount|orders" // @Param page query int false "页码" default(1) // @Param page_size query int false "每页条数(最大100)" default(20) // @Success 200 {object} activityRankingsResponse // @Failure 400 {object} code.Failure // @Router /api/admin/activities/{activity_id}/rankings [get] // @Security LoginVerifyToken func (h *handler) GetActivityRankings() core.HandlerFunc { return func(ctx core.Context) { activityID, err := strconv.ParseInt(ctx.Param("activity_id"), 10, 64) if err != nil || activityID <= 0 { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的活动ID")) return } req := new(activityRankingsRequest) if err := ctx.ShouldBindForm(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 { req.PageSize = 20 } if req.PageSize > 100 { req.PageSize = 100 } if req.SortBy != "orders" { req.SortBy = "amount" } baseQuery := h.repo.GetDbR().WithContext(ctx.RequestContext()). Table("orders"). Joins(` JOIN ( SELECT DISTINCT activity_draw_logs.order_id, activity_issues.activity_id FROM activity_draw_logs JOIN activity_issues ON activity_issues.id = activity_draw_logs.issue_id ) AS order_activities ON order_activities.order_id = orders.id `). Where("order_activities.activity_id = ?", activityID). Where("orders.status = ?", 2) userSubQuery := baseQuery. Select("orders.user_id"). Group("orders.user_id") var total int64 if err := h.repo.GetDbR().WithContext(ctx.RequestContext()). Table("(?) AS ranked_users", userSubQuery). Count(&total).Error; err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } type rankingRow struct { UserID int64 Nickname string Avatar string TotalAmount int64 OrderCount int64 } var rows []rankingRow listQuery := h.repo.GetDbR().WithContext(ctx.RequestContext()). Table("orders"). Joins(` JOIN ( SELECT DISTINCT activity_draw_logs.order_id, activity_issues.activity_id FROM activity_draw_logs JOIN activity_issues ON activity_issues.id = activity_draw_logs.issue_id ) AS order_activities ON order_activities.order_id = orders.id `). Joins("LEFT JOIN users ON users.id = orders.user_id"). Where("order_activities.activity_id = ?", activityID). Where("orders.status = ?", 2). Select(` orders.user_id, COALESCE(users.nickname, '') AS nickname, COALESCE(users.avatar, '') AS avatar, CAST(SUM(orders.actual_amount + orders.discount_amount) AS SIGNED) AS total_amount, COUNT(DISTINCT orders.id) AS order_count `). Group("orders.user_id, users.nickname, users.avatar") if req.SortBy == "orders" { listQuery = listQuery.Order("order_count DESC").Order("total_amount DESC").Order("orders.user_id ASC") } else { listQuery = listQuery.Order("total_amount DESC").Order("order_count DESC").Order("orders.user_id ASC") } offset := (req.Page - 1) * req.PageSize if err := listQuery.Offset(offset).Limit(req.PageSize).Scan(&rows).Error; err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error())) return } res := activityRankingsResponse{ Page: req.Page, PageSize: req.PageSize, Total: total, List: make([]activityRankingItem, 0, len(rows)), } for i, row := range rows { res.List = append(res.List, activityRankingItem{ Rank: int64(offset + i + 1), UserID: row.UserID, Nickname: row.Nickname, Avatar: row.Avatar, TotalAmount: row.TotalAmount, OrderCount: row.OrderCount, }) } ctx.Payload(res) } }