278 lines
9.3 KiB
Go
278 lines
9.3 KiB
Go
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)
|
||
}
|