bindbox-game/internal/api/admin/activity_rankings_admin.go
2026-03-05 12:50:06 +08:00

161 lines
4.8 KiB
Go

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