feat(game): 优化扫雷排行榜查询逻辑
This commit is contained in:
parent
471e21a68b
commit
400cd68d00
@ -64,7 +64,7 @@ func main() {
|
||||
env.Active() // 初始化 env flag(依赖已有的全局 -env/ACTIVE_ENV 配置)
|
||||
configs.Init()
|
||||
|
||||
cookie := "passport_csrf_token=59cdf9f8b9154bb170fbe3718b5c2c41; passport_csrf_token_default=59cdf9f8b9154bb170fbe3718b5c2c41; s_v_web_id=verify_mnkmeu91_r7NhDaDR_4MVT_4Icm_85n7_EDp2hQZTZj6o; is_staff_user=false; has_biz_token=false; zsgw_business_data=%7B%22uuid%22%3A%2295540517-0144-4b48-8d52-a060aa220f27%22%2C%22platform%22%3A%22pc%22%2C%22source%22%3A%22seo.fxg.jinritemai.com%22%7D; gfkadpd=4272,23756; ecom_gray_shop_id=156231010; SHOP_ID=156231010; PIGEON_CID=4339134776748827; Hm_lvt_b6520b076191ab4b36812da4c90f7a5e=1774525909,1774632716,1774968034,1775994293; HMACCOUNT=0D91B8CECCE6C828; csrf_session_id=513aabd94aa6a91c47c47dd861880f60; channel_account_verify_jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b21fY2xhaW0iOiI0MzM5MTM0Nzc2NzQ4ODI3Iiwic3ViIjoiY2hhbm5lbF9hY2NvdW50X3ZlcmlmeSIsImV4cCI6MTc3NjAzNzUzMCwibmJmIjoxNzc1OTk0MzMwLCJpYXQiOjE3NzU5OTQzMzB9.RSCDRSD2d2kEqoREXzVpDG3EYyLAXIFEFyD2fIgy2h4; channel_account_verify=cfcd208495d565ef66e7dff9f98764da; Hm_lpvt_b6520b076191ab4b36812da4c90f7a5e=1776010821; ttwid=1%7Cs-eQn8Q_A0kZTCaP0uZ6tFZ-5nSc-YV48RZrmP6MSxo%7C1776010857%7C5714d0cbd808c2cf2089b67d6539964c9109031c29f23749b1bb8d708dfd7e66; odin_tt=88b295e3e44318f7efde24295646e0724be377f012f90be7a12b1fa4e530c3f5cfc06d9af74411d3b15f348c5f3900e0b52fbdb9089c745fbd6f3921a3aa3783; passport_auth_status=dfeee43cd8f8414913f2a1194f2ed9fc%2C; passport_auth_status_ss=dfeee43cd8f8414913f2a1194f2ed9fc%2C; uid_tt=447779f3f27396b07599eb6fd21aaf34; uid_tt_ss=447779f3f27396b07599eb6fd21aaf34; sid_tt=997579cda00e9f4fee35eadbbb7c7ba8; sessionid=997579cda00e9f4fee35eadbbb7c7ba8; sessionid_ss=997579cda00e9f4fee35eadbbb7c7ba8; ucas_c0=CkEKBTEuMC4wEIaIh9jXy_HtaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0DujO_OBkjuwKvRBlC_vL6Ekt3t1GdYbhIUEG_zKIPeZdy8IvzBnEeUQZh2Jmk; ucas_c0_ss=CkEKBTEuMC4wEIaIh9jXy_HtaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0DujO_OBkjuwKvRBlC_vL6Ekt3t1GdYbhIUEG_zKIPeZdy8IvzBnEeUQZh2Jmk; PHPSESSID=d1a5b3819b815de4643be5556e0eb8d4; PHPSESSID_SS=d1a5b3819b815de4643be5556e0eb8d4; ecom_us_lt=6ee137f29c931bcba00435bff17681cea86fbbab104f1d7a32df87c406c1b2cd; ecom_us_lt_ss=6ee137f29c931bcba00435bff17681cea86fbbab104f1d7a32df87c406c1b2cd; source=seo.fxg.jinritemai.com; sid_guard=997579cda00e9f4fee35eadbbb7c7ba8%7C1776010868%7C5184000%7CThu%2C+11-Jun-2026+16%3A21%3A08+GMT; session_tlb_tag=sttt%7C7%7CmXV5zaAOn0_uNerbu3x7qP_________DvWrNB4ZNs5w88zu8OQBZtctF5iZKpB38WdY8WkW1gV8%3D; sid_ucp_v1=1.0.0-KGFlMWE0OWU3MWFhZjY2YjJmZmNiYWExZjg2ZjA1NjZiNDNiNmJlNmMKGQib1oDYuM3aBxD0jO_OBhiwISAMOAZA9AcaAmxmIiA5OTc1NzljZGEwMGU5ZjRmZWUzNWVhZGJiYjdjN2JhOA; ssid_ucp_v1=1.0.0-KGFlMWE0OWU3MWFhZjY2YjJmZmNiYWExZjg2ZjA1NjZiNDNiNmJlNmMKGQib1oDYuM3aBxD0jO_OBhiwISAMOAZA9AcaAmxmIiA5OTc1NzljZGEwMGU5ZjRmZWUzNWVhZGJiYjdjN2JhOA; BUYIN_SASID=SID2_7627905386221912374"
|
||||
cookie := "passport_csrf_token=59cdf9f8b9154bb170fbe3718b5c2c41; passport_csrf_token_default=59cdf9f8b9154bb170fbe3718b5c2c41; s_v_web_id=verify_mnkmeu91_r7NhDaDR_4MVT_4Icm_85n7_EDp2hQZTZj6o; is_staff_user=false; has_biz_token=false; SHOP_ID=156231010; PIGEON_CID=4339134776748827; ecom_gray_shop_id=156231010; gfkadpd=4272,23756; Hm_lvt_b6520b076191ab4b36812da4c90f7a5e=1774632716,1774968034,1775994293,1776710317; HMACCOUNT=0D91B8CECCE6C828; csrf_session_id=38f99d2b9a62d9770438596859e8afaa; Hm_lpvt_b6520b076191ab4b36812da4c90f7a5e=1776710323; ttwid=1%7Cs-eQn8Q_A0kZTCaP0uZ6tFZ-5nSc-YV48RZrmP6MSxo%7C1776710327%7Cd8f5c7d7c70eb1f6fee2619fded05f2a35312fa0a533927e4a5216c461411d6e; odin_tt=b70087c0142baa34c0f3dd2b30100dcc07a14b5438b47c01bbc37141a76feb092ba8cefe346da028ab9a0cdb517f47882eb347a04398d521e43ac7acefe87ead; passport_auth_status=0e16808156b0d324952de02ab8d0e366%2Cdfeee43cd8f8414913f2a1194f2ed9fc; passport_auth_status_ss=0e16808156b0d324952de02ab8d0e366%2Cdfeee43cd8f8414913f2a1194f2ed9fc; uid_tt=9a2ebe82f2439116d8488a6451fa6ada; uid_tt_ss=9a2ebe82f2439116d8488a6451fa6ada; sid_tt=8fe15f1994f4714600ac16c9c5873e06; sessionid=8fe15f1994f4714600ac16c9c5873e06; sessionid_ss=8fe15f1994f4714600ac16c9c5873e06; PHPSESSID=923a5ec84fb4e5286d758f8565ef89b9; PHPSESSID_SS=923a5ec84fb4e5286d758f8565ef89b9; ecom_us_lt=84055fac4b5c0ec4ab4e7dfb4f8363dc8cf0d3723132461ef45b84d3d10e3ed7; ecom_us_lt_ss=84055fac4b5c0ec4ab4e7dfb4f8363dc8cf0d3723132461ef45b84d3d10e3ed7; ucas_c0=CkEKBTEuMC4wEKKIjYz405zzaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0DA5ZnPBkjAmdbRBlC_vL6Ekt3t1GdYbhIULJNshLh6B-RTna84mldnRD04dCI; ucas_c0_ss=CkEKBTEuMC4wEKKIjYz405zzaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0DA5ZnPBkjAmdbRBlC_vL6Ekt3t1GdYbhIULJNshLh6B-RTna84mldnRD04dCI; zsgw_business_data=%7B%22uuid%22%3A%2295540517-0144-4b48-8d52-a060aa220f27%22%2C%22platform%22%3A%22pc%22%2C%22source%22%3A%22seo.fxg.jinritemai.com%22%7D; source=seo.fxg.jinritemai.com; sid_guard=8fe15f1994f4714600ac16c9c5873e06%7C1776710345%7C5184000%7CFri%2C+19-Jun-2026+18%3A39%3A05+GMT; session_tlb_tag=sttt%7C3%7Cj-FfGZT0cUYArBbJxYc-Bv_________F-R3m7za-NQWGijt8uvp4cecmrChrDjkt3_5ZQbKHXlM%3D; sid_ucp_v1=1.0.0-KGIzMTgwMzk5OWU2YWYxYmZjN2FmZWY1ZThkODNkNDhjZTBhMzZhNjgKGQib1oDYuM3aBxDJ5ZnPBhiwISAMOAZA9AcaAmxmIiA4ZmUxNWYxOTk0ZjQ3MTQ2MDBhYzE2YzljNTg3M2UwNg; ssid_ucp_v1=1.0.0-KGIzMTgwMzk5OWU2YWYxYmZjN2FmZWY1ZThkODNkNDhjZTBhMzZhNjgKGQib1oDYuM3aBxDJ5ZnPBhiwISAMOAZA9AcaAmxmIiA4ZmUxNWYxOTk0ZjQ3MTQ2MDBhYzE2YzljNTg3M2UwNg; BUYIN_SASID=SID2_7630910859501781288"
|
||||
if cookie == "" {
|
||||
fmt.Println("请通过环境变量 DOUYIN_COOKIE 提供抖店 Cookie")
|
||||
os.Exit(1)
|
||||
|
||||
@ -575,9 +575,12 @@ func (h *handler) refreshLeaderboardCache(gameType string) {
|
||||
ORDER BY l.total_rank_points DESC, l.wins DESC, l.best_score DESC, l.user_id ASC
|
||||
LIMIT 100`, gameType).Scan(&rows)
|
||||
|
||||
if len(rows) == 0 {
|
||||
return
|
||||
}
|
||||
data, _ := json.Marshal(rows)
|
||||
cacheKey := fmt.Sprintf("ms:lb:v1:%s:top100", gameType)
|
||||
h.redis.Set(context.Background(), cacheKey, string(data), 24*time.Hour)
|
||||
h.redis.Set(context.Background(), cacheKey, string(data), 5*time.Minute)
|
||||
}
|
||||
|
||||
type consumeTicketRequest struct {
|
||||
@ -696,41 +699,7 @@ func (h *handler) GetLeaderboard() core.HandlerFunc {
|
||||
page := req.Page
|
||||
pageSize := req.PageSize
|
||||
|
||||
// page=1 优先读 Redis 缓存
|
||||
if page == 1 && pageSize <= 100 {
|
||||
cacheKey := fmt.Sprintf("ms:lb:v1:%s:top100", gameType)
|
||||
cached, err := h.redis.Get(ctx.RequestContext(), cacheKey).Result()
|
||||
if err == nil && cached != "" {
|
||||
var list []map[string]any
|
||||
if json.Unmarshal([]byte(cached), &list) == nil {
|
||||
// 加上名次
|
||||
for i := range list {
|
||||
list[i]["rank"] = i + 1
|
||||
}
|
||||
// 截取分页
|
||||
start := (page - 1) * pageSize
|
||||
end := start + pageSize
|
||||
if start >= len(list) {
|
||||
start = len(list)
|
||||
}
|
||||
if end > len(list) {
|
||||
end = len(list)
|
||||
}
|
||||
pageList := list[start:end]
|
||||
ctx.Payload(map[string]any{
|
||||
"game_type": gameType,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total": len(list),
|
||||
"list": pageList,
|
||||
"me": h.queryMyRank(ctx.RequestContext(), myUserID, gameType),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 直接查 MySQL
|
||||
// 直接查 MySQL(实时数据,不走缓存)
|
||||
type lbRow struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
Nickname string `json:"nickname"`
|
||||
@ -802,14 +771,18 @@ func (h *handler) queryMyRank(ctx context.Context, userID int64, gameType string
|
||||
if userID <= 0 {
|
||||
return nil
|
||||
}
|
||||
var count int64
|
||||
h.db.GetDbR().Table("minesweeper_leaderboard").
|
||||
Where("user_id = ? AND game_type = ?", userID, gameType).
|
||||
Count(&count)
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
var myPoints int64
|
||||
h.db.GetDbR().Table("minesweeper_leaderboard").
|
||||
Select("total_rank_points").
|
||||
Where("user_id = ? AND game_type = ?", userID, gameType).
|
||||
Scan(&myPoints)
|
||||
if myPoints == 0 {
|
||||
return nil
|
||||
}
|
||||
var myRank int64
|
||||
h.db.GetDbR().Table("minesweeper_leaderboard").
|
||||
Where("game_type = ? AND total_rank_points > ?", gameType, myPoints).
|
||||
@ -837,6 +810,155 @@ func (h *handler) queryMyRank(ctx context.Context, userID int64, gameType string
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Admin API: 排行榜 & 对战记录 ==========
|
||||
|
||||
// GetAdminLeaderboard Admin查询扫雷排行榜
|
||||
// @Summary 查询扫雷排行榜
|
||||
// @Tags 管理端.游戏
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Param nickname query string false "按玩家昵称模糊搜索"
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Router /api/admin/games/leaderboard [get]
|
||||
func (h *handler) GetAdminLeaderboard() core.HandlerFunc {
|
||||
return func(ctx core.Context) {
|
||||
var req struct {
|
||||
Page int `form:"page"`
|
||||
PageSize int `form:"page_size"`
|
||||
Nickname string `form:"nickname"`
|
||||
}
|
||||
_ = ctx.ShouldBindQuery(&req)
|
||||
if req.Page <= 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize <= 0 || req.PageSize > 100 {
|
||||
req.PageSize = 20
|
||||
}
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
|
||||
type lbRow struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
TotalRankPoints int64 `json:"total_rank_points"`
|
||||
MatchesPlayed int `json:"matches_played"`
|
||||
Wins int `json:"wins"`
|
||||
Losses int `json:"losses"`
|
||||
WinRate float64 `json:"win_rate"`
|
||||
BestScore int `json:"best_score"`
|
||||
AvgScore float64 `json:"avg_score"`
|
||||
}
|
||||
|
||||
query := h.db.GetDbR().Table("minesweeper_leaderboard l").
|
||||
Select("l.user_id, COALESCE(u.nick_name,'') AS nickname, COALESCE(u.avatar_url,'') AS avatar, l.total_rank_points, l.matches_played, l.wins, l.losses, CAST(l.win_rate AS DECIMAL(7,4)) AS win_rate, l.best_score, CAST(l.avg_score AS DECIMAL(12,2)) AS avg_score").
|
||||
Joins("LEFT JOIN users u ON u.id = l.user_id").
|
||||
Where("l.game_type = ?", "minesweeper")
|
||||
|
||||
if req.Nickname != "" {
|
||||
query = query.Where("u.nick_name LIKE ?", "%"+req.Nickname+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
query.Count(&total)
|
||||
|
||||
var rows []lbRow
|
||||
query.Order("l.total_rank_points DESC, l.wins DESC, l.best_score DESC").
|
||||
Limit(req.PageSize).Offset(offset).Scan(&rows)
|
||||
|
||||
list := make([]map[string]any, 0, len(rows))
|
||||
for i, r := range rows {
|
||||
list = append(list, map[string]any{
|
||||
"rank": offset + i + 1,
|
||||
"user_id": r.UserID,
|
||||
"nickname": r.Nickname,
|
||||
"avatar": r.Avatar,
|
||||
"total_rank_points": r.TotalRankPoints,
|
||||
"matches_played": r.MatchesPlayed,
|
||||
"wins": r.Wins,
|
||||
"losses": r.Losses,
|
||||
"win_rate": r.WinRate,
|
||||
"best_score": r.BestScore,
|
||||
"avg_score": r.AvgScore,
|
||||
})
|
||||
}
|
||||
|
||||
ctx.Payload(map[string]any{
|
||||
"total": total,
|
||||
"page": req.Page,
|
||||
"page_size": req.PageSize,
|
||||
"list": list,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetAdminGameRecords Admin查询扫雷对战记录
|
||||
// @Summary 查询扫雷对战记录
|
||||
// @Tags 管理端.游戏
|
||||
// @Param page query int false "页码"
|
||||
// @Param page_size query int false "每页数量"
|
||||
// @Param user_id query int false "按用户ID筛选"
|
||||
// @Param match_id query string false "按局ID筛选"
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Router /api/admin/games/records [get]
|
||||
func (h *handler) GetAdminGameRecords() core.HandlerFunc {
|
||||
return func(ctx core.Context) {
|
||||
var req struct {
|
||||
Page int `form:"page"`
|
||||
PageSize int `form:"page_size"`
|
||||
UserID int64 `form:"user_id"`
|
||||
MatchID string `form:"match_id"`
|
||||
}
|
||||
_ = ctx.ShouldBindQuery(&req)
|
||||
if req.Page <= 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize <= 0 || req.PageSize > 100 {
|
||||
req.PageSize = 20
|
||||
}
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
|
||||
type recRow struct {
|
||||
ID int64 `json:"id"`
|
||||
MatchID string `json:"match_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Nickname string `json:"nickname"`
|
||||
IsWinner bool `json:"is_winner"`
|
||||
RankPosition int `json:"rank_position"`
|
||||
TotalPlayers int `json:"total_players"`
|
||||
Score int `json:"score"`
|
||||
DamageDealt int `json:"damage_dealt"`
|
||||
ChestsCollected int `json:"chests_collected"`
|
||||
RankPoints int `json:"rank_points"`
|
||||
SettledAt string `json:"settled_at"`
|
||||
}
|
||||
|
||||
query := h.db.GetDbR().Table("minesweeper_game_records r").
|
||||
Select("r.id, r.match_id, r.user_id, COALESCE(u.nick_name,'') AS nickname, r.is_winner, r.rank_position, r.total_players, r.score, r.damage_dealt, r.chests_collected, r.rank_points, r.settled_at").
|
||||
Joins("LEFT JOIN users u ON u.id = r.user_id").
|
||||
Where("r.game_type = ?", "minesweeper")
|
||||
|
||||
if req.UserID > 0 {
|
||||
query = query.Where("r.user_id = ?", req.UserID)
|
||||
}
|
||||
if req.MatchID != "" {
|
||||
query = query.Where("r.match_id = ?", req.MatchID)
|
||||
}
|
||||
|
||||
var total int64
|
||||
query.Count(&total)
|
||||
|
||||
var rows []recRow
|
||||
query.Order("r.settled_at DESC").Limit(req.PageSize).Offset(offset).Scan(&rows)
|
||||
|
||||
ctx.Payload(map[string]any{
|
||||
"total": total,
|
||||
"page": req.Page,
|
||||
"page_size": req.PageSize,
|
||||
"list": rows,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Helpers ==========
|
||||
|
||||
func generateTicketToken(userID int64) string {
|
||||
|
||||
@ -354,6 +354,10 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er
|
||||
adminAuthApiRouter.GET("/users/:user_id/game_tickets", gameHandler.ListUserTickets())
|
||||
adminAuthApiRouter.POST("/users/:user_id/game_tickets", gameHandler.GrantUserTicket())
|
||||
|
||||
// 扫雷排行榜 & 对战记录
|
||||
adminAuthApiRouter.GET("/games/leaderboard", gameHandler.GetAdminLeaderboard())
|
||||
adminAuthApiRouter.GET("/games/records", gameHandler.GetAdminGameRecords())
|
||||
|
||||
// 发货统计
|
||||
adminAuthApiRouter.GET("/ops_shipping_stats", intc.RequireAdminAction("ops:shipping:view"), adminHandler.ListShippingStats())
|
||||
adminAuthApiRouter.GET("/ops_shipping_stats/:id", intc.RequireAdminAction("ops:shipping:view"), adminHandler.GetShippingStat())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user