Files
go_blog/handlers/admin_analytics.go
T
kevin f307781f58 docs: 全部 Go 代码注释汉化
- 48 个 Go 文件所有注释(行注释/块注释/行尾注释,含 _test.go)翻译为中文
- 保留技术标识符:SECURITY_TODO(n)、unsafe-inline、sqlite/mysql、路由参数等
- 代码、字符串字面量、日志消息保持英文原文,零逻辑改动
- go build/vet 通过,go test -count=1 ./... 全绿
2026-08-27 19:03:03 +08:00

124 lines
3.1 KiB
Go

package handlers
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"go_blog/models"
"gorm.io/gorm"
)
// ViewAnalyticsPage 渲染展示文章浏览统计的后台分析页面。
func ViewAnalyticsPage(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
tr := getTr(c)
// 从查询参数解析筛选条件
articleTitle := c.Query("article_title")
userIDStr := c.Query("user_id")
ip := c.Query("ip")
showBots := c.Query("show_bots") == "1"
page := 1
if p := c.Query("page"); p != "" {
if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 {
page = parsed
}
}
// 构建浏览记录查询
query := db.Model(&models.ArticleView{}).
Preload("Article").
Preload("User").
Order("created_at DESC")
// 按文章标题筛选(联表 articles)
if articleTitle != "" {
query = query.Joins("JOIN articles ON article_views.article_id = articles.id").
Where("articles.title LIKE ?", "%"+articleTitle+"%")
}
if userIDStr != "" {
query = query.Where("user_id = ?", userIDStr)
}
if ip != "" {
query = query.Where("ip LIKE ?", "%"+ip+"%")
}
if !showBots {
query = query.Where("is_bot = ?", false)
}
// 分页
pageSize := 50
offset := (page - 1) * pageSize
var views []models.ArticleView
var totalCount int64
query.Count(&totalCount)
query.Limit(pageSize).Offset(offset).Find(&views)
hasMore := int64(offset+len(views)) < totalCount
// 计算全局统计
var stats struct {
TotalViews int64
UniqueIPs int64
UniqueUsers int64
BotViews int64
HumanViews int64
}
db.Model(&models.ArticleView{}).Count(&stats.TotalViews)
db.Model(&models.ArticleView{}).Where("is_bot = ?", true).Count(&stats.BotViews)
db.Model(&models.ArticleView{}).Where("is_bot = ?", false).Count(&stats.HumanViews)
db.Model(&models.ArticleView{}).Distinct("ip").Count(&stats.UniqueIPs)
db.Model(&models.ArticleView{}).Where("user_id IS NOT NULL").Distinct("user_id").Count(&stats.UniqueUsers)
// 按文章统计
type ArticleStat struct {
ArticleID uint
Title string
ViewCount int64
UniqueIPs int64
BotViews int64
HumanViews int64
}
var articleStats []ArticleStat
db.Raw(`
SELECT
av.article_id,
a.title,
COUNT(*) as view_count,
COUNT(DISTINCT av.ip) as unique_ips,
SUM(CASE WHEN av.is_bot THEN 1 ELSE 0 END) as bot_views,
SUM(CASE WHEN NOT av.is_bot THEN 1 ELSE 0 END) as human_views
FROM article_views av
LEFT JOIN articles a ON av.article_id = a.id
WHERE a.deleted_at IS NULL
GROUP BY av.article_id, a.title
ORDER BY view_count DESC
LIMIT 20
`).Scan(&articleStats)
data := DefaultData(c)
data["Title"] = tr["analytics_views_title"]
data["Views"] = views
data["Stats"] = stats
data["ArticleStats"] = articleStats
data["Filters"] = gin.H{
"ArticleTitle": articleTitle,
"UserID": userIDStr,
"IP": ip,
"ShowBots": showBots,
"Page": page,
}
data["Pagination"] = gin.H{
"Page": page,
"NextPage": page + 1,
"HasMore": hasMore,
"Total": totalCount,
}
c.HTML(http.StatusOK, "analytics_views", data)
}
}