Files

130 lines
4.9 KiB
Go
Raw Permalink Normal View History

2026-08-14 13:17:03 +08:00
package handler
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
"nl-game-api-gin/internal/database"
"nl-game-api-gin/internal/model"
"nl-game-api-gin/internal/room"
"nl-game-api-gin/pkg/resp"
)
// dayStart 返回某时刻所在自然日的零点时间戳(本地时区)
func dayStart(t time.Time) int64 {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()).Unix()
}
// AdminAnalytics 运营数据分析:日活趋势、游玩时长、平均时长、最火游戏、当前在线
// 日活口径:当天有登录 / 游玩 / 签到 / 对战任一行为的去重用户数
func AdminAnalytics(c *gin.Context) {
now := time.Now()
todayStart := dayStart(now)
const trendDays = 14
since := todayStart - int64(trendDays-1)*86400
// ---- 日活趋势:四张行为表按天去重合并 ----
type dayRow struct {
D string `gorm:"column:d"`
N int `gorm:"column:n"`
}
var rows []dayRow
database.DB.Raw(`SELECT d, COUNT(DISTINCT uid) AS n FROM (
SELECT user_id AS uid, FROM_UNIXTIME(created_at, '%Y-%m-%d') AS d FROM login_logs WHERE created_at >= ?
UNION ALL SELECT user_id, FROM_UNIXTIME(created_at, '%Y-%m-%d') FROM game_records WHERE created_at >= ?
UNION ALL SELECT user_id, FROM_UNIXTIME(created_at, '%Y-%m-%d') FROM sign_ins WHERE created_at >= ?
UNION ALL SELECT user_id, FROM_UNIXTIME(created_at, '%Y-%m-%d') FROM battle_records WHERE created_at >= ?
) t GROUP BY d ORDER BY d`, since, since, since, since).Scan(&rows)
dauMap := map[string]int{}
for _, r := range rows {
dauMap[r.D] = r.N
}
// 补齐没有数据的日期(趋势图连续)
trend := make([]gin.H, 0, trendDays)
for i := 0; i < trendDays; i++ {
day := time.Unix(todayStart-int64(trendDays-1-i)*86400, 0)
key := day.Format("2006-01-02")
trend = append(trend, gin.H{"date": day.Format("01-02"), "full": key, "count": dauMap[key]})
}
dauToday := dauMap[now.Format("2006-01-02")]
dauYesterday := dauMap[time.Unix(todayStart-86400, 0).Format("2006-01-02")]
// ---- 游玩时长与局数(单机 + 联机对战合并) ----
type sumRow struct {
Dur int64 `gorm:"column:dur"`
Cnt int64 `gorm:"column:cnt"`
}
sumOf := func(table string, from int64) sumRow {
var r sumRow
database.DB.Raw(fmt.Sprintf(
`SELECT COALESCE(SUM(duration),0) AS dur, COUNT(*) AS cnt FROM %s WHERE created_at >= ?`, table), from).Scan(&r)
return r
}
gToday, gTotal := sumOf("game_records", todayStart), sumOf("game_records", 0)
bToday, bTotal := sumOf("battle_records", todayStart), sumOf("battle_records", 0)
secToday := gToday.Dur + bToday.Dur
secTotal := gTotal.Dur + bTotal.Dur
playsToday := gToday.Cnt + bToday.Cnt
playsTotal := gTotal.Cnt + bTotal.Cnt
avgSec := int64(0)
if playsTotal > 0 {
avgSec = secTotal / playsTotal
}
avgSecToday := int64(0)
if playsToday > 0 {
avgSecToday = secToday / playsToday
}
// ---- 最火游戏:近 7 天局数排序(单机+对战合并),附平均时长与历史总局数 ----
weekAgo := now.Unix() - 7*86400
type hotRow struct {
GameID int `gorm:"column:game_id"`
Plays int `gorm:"column:plays"`
Dur int64 `gorm:"column:dur"`
}
var hot []hotRow
database.DB.Raw(`SELECT game_id, COUNT(*) AS plays, COALESCE(SUM(duration),0) AS dur FROM (
SELECT game_id, duration FROM game_records WHERE created_at >= ?
UNION ALL SELECT game_id, duration FROM battle_records WHERE created_at >= ?
) t GROUP BY game_id ORDER BY plays DESC LIMIT 8`, weekAgo, weekAgo).Scan(&hot)
ids := make([]int, 0, len(hot))
for _, h := range hot {
ids = append(ids, h.GameID)
}
gameMap := map[int]model.Game{}
if len(ids) > 0 {
var games []model.Game
database.DB.Where("id IN ?", ids).Find(&games)
for _, g := range games {
gameMap[g.ID] = g
}
}
hotGames := make([]gin.H, 0, len(hot))
for _, h := range hot {
g, ok := gameMap[h.GameID]
if !ok {
continue
}
avg := int64(0)
if h.Plays > 0 {
avg = h.Dur / int64(h.Plays)
}
hotGames = append(hotGames, gin.H{
"game_id": g.ID, "code": g.Code, "name": g.Name, "icon": g.Icon, "category": g.Category,
"plays_7d": h.Plays, "avg_seconds": avg, "play_count": g.PlayCount,
})
}
resp.OK(c, gin.H{
"dau_today": dauToday, // 今日日活
"dau_yesterday": dauYesterday, // 昨日日活(对比)
"dau_trend": trend, // 近 14 天日活趋势
"online_now": room.OnlineCount(), // 当前在线人数WebSocket 连接数)
"play_seconds_today": secToday, // 今日游玩总时长(秒)
"play_seconds_total": secTotal, // 累计游玩总时长(秒)
"plays_today": playsToday, // 今日总局数
"plays_total": playsTotal, // 累计总局数
"avg_seconds": avgSec, // 平均单局时长(秒,历史)
"avg_seconds_today": avgSecToday, // 平均单局时长(秒,今日)
"hot_games": hotGames, // 近 7 天最火游戏 Top8
})
}