Files
nl-game-api/internal/handler/user.go
2026-08-14 13:17:03 +08:00

278 lines
9.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"sort"
"strconv"
"time"
"github.com/gin-gonic/gin"
"nl-game-api-gin/internal/database"
"nl-game-api-gin/internal/middleware"
"nl-game-api-gin/internal/model"
"nl-game-api-gin/internal/service"
"nl-game-api-gin/pkg/resp"
)
// pageParams 解析分页参数页码默认1、每页默认10、上限50
func pageParams(c *gin.Context) (page, size, offset int) {
page, _ = strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ = strconv.Atoi(c.DefaultQuery("size", "10"))
if page < 1 {
page = 1
}
// 上限 500支持前端「批量拉取 + 虚拟滚动」模式一次取一大页(查询始终带 LIMIT无全表风险
if size < 1 || size > 500 {
size = 10
}
return page, size, (page - 1) * size
}
// Profile 获取当前用户资料
func Profile(c *gin.Context) {
var user model.User
if err := database.DB.First(&user, middleware.UID(c)).Error; err != nil {
resp.Unauthorized(c, "用户不存在")
return
}
resp.OK(c, user)
}
// updateProfileReq 修改资料请求体
type updateProfileReq struct {
Nickname string `json:"nickname" binding:"required,max=20"` // 新昵称
Avatar string `json:"avatar" binding:"required,max=8"` // 新 emoji 头像
}
// UpdateProfile 修改昵称与头像
func UpdateProfile(c *gin.Context) {
var req updateProfileReq
if err := c.ShouldBindJSON(&req); err != nil {
resp.Fail(c, "参数有误")
return
}
database.DB.Model(&model.User{}).Where("id = ?", middleware.UID(c)).
Updates(map[string]any{"nickname": req.Nickname, "avatar": req.Avatar})
resp.OK(c, nil)
}
// PointRecords 我的积分流水(分页)
func PointRecords(c *gin.Context) {
page, size, offset := pageParams(c)
uid := middleware.UID(c)
var total int64
list := make([]model.PointRecord, 0)
database.DB.Model(&model.PointRecord{}).Where("user_id = ?", uid).Count(&total)
database.DB.Where("user_id = ?", uid).Order("id DESC").Limit(size).Offset(offset).Find(&list)
resp.OK(c, gin.H{"list": list, "total": total, "page": page, "size": size})
}
// LoginLogs 我的登录记录(分页)
func LoginLogs(c *gin.Context) {
page, size, offset := pageParams(c)
uid := middleware.UID(c)
var total int64
list := make([]model.LoginLog, 0)
database.DB.Model(&model.LoginLog{}).Where("user_id = ?", uid).Count(&total)
database.DB.Where("user_id = ?", uid).Order("id DESC").Limit(size).Offset(offset).Find(&list)
resp.OK(c, gin.H{"list": list, "total": total, "page": page, "size": size})
}
// gameRecordItem 游戏记录列表项(附游戏名称与图标)
type gameRecordItem struct {
model.GameRecord
GameName string `json:"game_name"` // 游戏名称
GameIcon string `json:"game_icon"` // 游戏图标
}
// GameRecords 我的单机游戏记录(分页,联表补游戏信息)
func GameRecords(c *gin.Context) {
page, size, offset := pageParams(c)
uid := middleware.UID(c)
var total int64
list := make([]gameRecordItem, 0)
database.DB.Model(&model.GameRecord{}).Where("user_id = ?", uid).Count(&total)
database.DB.Table("game_records gr").
Select("gr.*, g.name AS game_name, g.icon AS game_icon").
Joins("LEFT JOIN games g ON g.id = gr.game_id").
Where("gr.user_id = ?", uid).
Order("gr.id DESC").Limit(size).Offset(offset).Scan(&list)
resp.OK(c, gin.H{"list": list, "total": total, "page": page, "size": size})
}
// battleRecordItem 对战记录列表项(附游戏名称与图标)
type battleRecordItem struct {
model.BattleRecord
GameName string `json:"game_name"` // 游戏名称
GameIcon string `json:"game_icon"` // 游戏图标
}
// BattleRecords 我的联机对战记录(分页)
func BattleRecords(c *gin.Context) {
page, size, offset := pageParams(c)
uid := middleware.UID(c)
var total int64
list := make([]battleRecordItem, 0)
database.DB.Model(&model.BattleRecord{}).Where("user_id = ?", uid).Count(&total)
database.DB.Table("battle_records br").
Select("br.*, g.name AS game_name, g.icon AS game_icon").
Joins("LEFT JOIN games g ON g.id = br.game_id").
Where("br.user_id = ?", uid).
Order("br.id DESC").Limit(size).Offset(offset).Scan(&list)
resp.OK(c, gin.H{"list": list, "total": total, "page": page, "size": size})
}
// userPropItem 用户道具列表项(附道具详情)
type userPropItem struct {
PropID int `json:"prop_id"` // 道具ID
Code string `json:"code"` // 道具编码
Name string `json:"name"` // 道具名称
Icon string `json:"icon"` // 图标
Description string `json:"description"` // 用途说明
Quantity int `json:"quantity"` // 持有数量
}
// UserProps 我的道具背包只返回数量大于0的
func UserProps(c *gin.Context) {
list := make([]userPropItem, 0)
database.DB.Table("user_props up").
Select("up.prop_id, p.code, p.name, p.icon, p.description, up.quantity").
Joins("JOIN props p ON p.id = up.prop_id").
Where("up.user_id = ? AND up.quantity > 0", middleware.UID(c)).
Order("p.sort").Scan(&list)
resp.OK(c, list)
}
// UserGames 我已购买的游戏ID列表判断解锁状态用
func UserGames(c *gin.Context) {
ids := make([]int, 0)
database.DB.Model(&model.UserGame{}).Where("user_id = ?", middleware.UID(c)).Pluck("game_id", &ids)
resp.OK(c, ids)
}
// myGameItem 「我的游戏」列表项:游戏信息 + 来源 + 我的游玩统计
type myGameItem struct {
model.Game
Source string `json:"source"` // 来源bought=已购 weekly=本周周免 played=玩过的免费游戏
GotAt int64 `json:"got_at"` // 获得时间(已购=购买时间,其余=0
Plays int `json:"plays"` // 我的总局数(单机+对战)
Best int `json:"best"` // 我的最高分(单机游戏)
LastPlayed int64 `json:"last_played"` // 最近一次游玩时间0=没玩过)
}
// UserMyGames 个人中心「我的游戏」:已购游戏 + 本周VIP周免 + 玩过的免费游戏,附游玩统计
// 排序:已购按购买时间倒序 → 周免 → 玩过的按最近游玩倒序
func UserMyGames(c *gin.Context) {
uid := middleware.UID(c)
// 我的游玩统计(单机记录:局数/最高分/最近时间,按游戏聚合)
type statRow struct {
GameID int `gorm:"column:game_id"`
Plays int `gorm:"column:plays"`
Best int `gorm:"column:best"`
Last int64 `gorm:"column:last"`
}
statMap := map[int]statRow{}
var stats []statRow
database.DB.Raw(`SELECT game_id, COUNT(*) AS plays, MAX(score) AS best, MAX(created_at) AS last
FROM game_records WHERE user_id = ? GROUP BY game_id`, uid).Scan(&stats)
for _, s := range stats {
statMap[s.GameID] = s
}
// 对战局数并入统计联机游戏没有单机得分best 保持 0
var bstats []statRow
database.DB.Raw(`SELECT game_id, COUNT(*) AS plays, 0 AS best, MAX(created_at) AS last
FROM battle_records WHERE user_id = ? GROUP BY game_id`, uid).Scan(&bstats)
for _, s := range bstats {
m := statMap[s.GameID]
m.GameID = s.GameID
m.Plays += s.Plays
if s.Last > m.Last {
m.Last = s.Last
}
statMap[s.GameID] = m
}
// 已购游戏(购买时间倒序)
var ugs []model.UserGame
database.DB.Where("user_id = ?", uid).Order("id DESC").Find(&ugs)
boughtAt := map[int]int64{}
boughtOrder := make([]int, 0, len(ugs))
for _, ug := range ugs {
if _, ok := boughtAt[ug.GameID]; !ok {
boughtAt[ug.GameID] = ug.CreatedAt
boughtOrder = append(boughtOrder, ug.GameID)
}
}
// 本周周免集合非VIP为空
weeklySet := map[int]bool{}
var user model.User
if err := database.DB.First(&user, uid).Error; err == nil {
weeklySet = service.VipWeeklyFreeSet(&user, time.Now())
}
// 汇总需要展示的游戏ID已购 + 周免 + 玩过的
idSet := map[int]bool{}
for _, id := range boughtOrder {
idSet[id] = true
}
for id := range weeklySet {
idSet[id] = true
}
for id := range statMap {
idSet[id] = true
}
if len(idSet) == 0 {
resp.OK(c, []myGameItem{})
return
}
ids := make([]int, 0, len(idSet))
for id := range idSet {
ids = append(ids, id)
}
var games []model.Game
database.DB.Where("id IN ? AND status = 1", ids).Find(&games)
gameMap := map[int]model.Game{}
for _, g := range games {
gameMap[g.ID] = g
}
build := func(id int, source string) (myGameItem, bool) {
g, ok := gameMap[id]
if !ok {
return myGameItem{}, false
}
s := statMap[id]
return myGameItem{
Game: g, Source: source, GotAt: boughtAt[id],
Plays: s.Plays, Best: s.Best, LastPlayed: s.Last,
}, true
}
list := make([]myGameItem, 0, len(idSet))
// 1. 已购(购买时间倒序;已下架的会被 build 过滤)
for _, id := range boughtOrder {
if it, ok := build(id, "bought"); ok {
list = append(list, it)
}
}
boughtCount := len(list)
// 2. 本周周免(未购买的才标周免),按游戏排序权重稳定展示
for id := range weeklySet {
if boughtAt[id] == 0 {
if it, ok := build(id, "weekly"); ok {
list = append(list, it)
}
}
}
weeklyPart := list[boughtCount:]
sort.SliceStable(weeklyPart, func(i, j int) bool { return weeklyPart[i].Sort < weeklyPart[j].Sort })
// 3. 玩过的其它游戏(免费游戏等,按最近游玩倒序)
played := make([]myGameItem, 0)
for id := range statMap {
if boughtAt[id] == 0 && !weeklySet[id] {
if it, ok := build(id, "played"); ok {
played = append(played, it)
}
}
}
sort.SliceStable(played, func(i, j int) bool { return played[i].LastPlayed > played[j].LastPlayed })
list = append(list, played...)
resp.OK(c, list)
}