2026-01-15 13:51:44 +08:00
|
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-01-20 14:31:39 +08:00
|
|
|
|
"net/url"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
2026-01-15 13:51:44 +08:00
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
"github.com/niangaodev/art-code/repositories"
|
2026-01-16 10:19:30 +08:00
|
|
|
|
"github.com/niangaodev/art-code/utils"
|
2026-01-15 13:51:44 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-16 09:32:46 +08:00
|
|
|
|
// GetDashboardStats 获取仪表盘统计数据
|
|
|
|
|
|
func GetDashboardStats(c *gin.Context) {
|
2026-01-16 10:19:30 +08:00
|
|
|
|
startDate := c.Query("startDate")
|
|
|
|
|
|
endDate := c.Query("endDate")
|
|
|
|
|
|
|
2026-01-20 14:31:39 +08:00
|
|
|
|
// URL解码日期参数,处理+号被编码的情况
|
|
|
|
|
|
if startDate != "" {
|
|
|
|
|
|
if decoded, err := url.QueryUnescape(startDate); err == nil {
|
|
|
|
|
|
// 将+号替换为空格(URL编码中+可能代表空格)
|
|
|
|
|
|
startDate = strings.ReplaceAll(decoded, "+", " ")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if endDate != "" {
|
|
|
|
|
|
if decoded, err := url.QueryUnescape(endDate); err == nil {
|
|
|
|
|
|
// 将+号替换为空格(URL编码中+可能代表空格)
|
|
|
|
|
|
endDate = strings.ReplaceAll(decoded, "+", " ")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 10:19:30 +08:00
|
|
|
|
// 1. 获取博文增长趋势
|
|
|
|
|
|
postsTrend, err := repositories.GetNewPostsTrend(startDate, endDate)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
if err != nil {
|
2026-01-16 10:19:30 +08:00
|
|
|
|
postsTrend = []repositories.TrendData{} // Fallback empty
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 10:19:30 +08:00
|
|
|
|
// 2. 获取访客UV (根据IP)
|
|
|
|
|
|
uvTrend, err := repositories.GetDailyUV(startDate, endDate)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
if err != nil {
|
2026-01-16 10:19:30 +08:00
|
|
|
|
uvTrend = []repositories.UVTrendData{}
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 15:23:37 +08:00
|
|
|
|
// 3. 用户画像-地域分布(使用 user_access_logs 表)
|
|
|
|
|
|
userRegions, err := repositories.GetUserRegionsFromUserAccessLogs(startDate, endDate)
|
2026-01-15 13:51:44 +08:00
|
|
|
|
if err != nil {
|
2026-01-20 14:31:39 +08:00
|
|
|
|
// 即使出错,也返回所有34个省份的默认数据(Count为0)
|
|
|
|
|
|
userRegions = repositories.GetDefaultUserRegions()
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 09:32:46 +08:00
|
|
|
|
// 4. 热门文章 Top 5
|
2026-01-16 13:01:21 +08:00
|
|
|
|
// 优先使用访问日志统计,不足5条则用posts表的read_count补齐
|
2026-01-16 10:19:30 +08:00
|
|
|
|
topPosts, err := repositories.GetTopArticlesByAccess(5)
|
2026-01-16 13:01:21 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
topPosts = []struct {
|
|
|
|
|
|
ArticleID int `json:"article_id"`
|
|
|
|
|
|
Title string `json:"title"`
|
|
|
|
|
|
Count int `json:"count"`
|
|
|
|
|
|
}{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if len(topPosts) < 5 {
|
|
|
|
|
|
// Fetch more than needed to ensure we find unique ones, or just fetch top 5
|
|
|
|
|
|
dbPosts, err := repositories.GetTopPosts(10)
|
2026-01-16 10:19:30 +08:00
|
|
|
|
if err == nil {
|
2026-01-16 13:01:21 +08:00
|
|
|
|
// Create a map of existing IDs to avoid duplicates
|
|
|
|
|
|
existingIDs := make(map[int]bool)
|
|
|
|
|
|
for _, p := range topPosts {
|
|
|
|
|
|
existingIDs[p.ArticleID] = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 10:19:30 +08:00
|
|
|
|
for _, p := range dbPosts {
|
2026-01-16 13:01:21 +08:00
|
|
|
|
if len(topPosts) >= 5 {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
if !existingIDs[int(p.ID)] {
|
|
|
|
|
|
topPosts = append(topPosts, struct {
|
|
|
|
|
|
ArticleID int `json:"article_id"`
|
|
|
|
|
|
Title string `json:"title"`
|
|
|
|
|
|
Count int `json:"count"`
|
|
|
|
|
|
}{
|
|
|
|
|
|
ArticleID: int(p.ID),
|
|
|
|
|
|
Title: p.Title,
|
|
|
|
|
|
Count: int(p.ReadCount),
|
|
|
|
|
|
})
|
|
|
|
|
|
existingIDs[int(p.ID)] = true
|
|
|
|
|
|
}
|
2026-01-16 10:19:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 09:32:46 +08:00
|
|
|
|
// 5. 合作咨询总数
|
2026-01-19 13:53:32 +08:00
|
|
|
|
inquiryCount, _ := repositories.GetInquiryCount()
|
2026-01-16 09:32:46 +08:00
|
|
|
|
|
2026-01-16 10:19:30 +08:00
|
|
|
|
// 6. 作品总数
|
2026-01-16 13:01:21 +08:00
|
|
|
|
workCount, _ := repositories.GetWorkCount()
|
2026-01-16 10:19:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 7. 文章总数
|
2026-01-16 13:01:21 +08:00
|
|
|
|
postCount, _ := repositories.GetPostCount()
|
2026-01-16 10:19:30 +08:00
|
|
|
|
|
2026-01-20 14:31:39 +08:00
|
|
|
|
// 8. 管理员操作统计
|
|
|
|
|
|
operationTrend, err := repositories.GetOperationTrend(startDate, endDate)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
operationTrend = []repositories.OperationTrendData{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 10:19:30 +08:00
|
|
|
|
utils.Success(c, gin.H{
|
2026-01-20 14:31:39 +08:00
|
|
|
|
"postsTrend": postsTrend,
|
|
|
|
|
|
"uvTrend": uvTrend,
|
|
|
|
|
|
"userRegions": userRegions,
|
|
|
|
|
|
"topPosts": topPosts,
|
|
|
|
|
|
"inquiryCount": inquiryCount,
|
|
|
|
|
|
"posts": postCount,
|
|
|
|
|
|
"works": workCount,
|
|
|
|
|
|
"operationTrend": operationTrend,
|
2026-01-16 09:32:46 +08:00
|
|
|
|
})
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|