package handler import ( "net/http" "strconv" "strings" "time" "github.com/freefire/jiang13-bbs/service" "github.com/gin-gonic/gin" ) // ============ 访问统计管理端 API(全部 RequirePerm(PermSettings)) ============ func statsRange(c *gin.Context) string { return c.DefaultQuery("range", "7d") } // GET /api/admin/stats/overview func (h *Handlers) StatsOverview(c *gin.Context) { data, err := h.Visit.Overview(statsRange(c)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取概览失败", "detail": err.Error()}) return } c.JSON(http.StatusOK, data) } // GET /api/admin/stats/realtime func (h *Handlers) StatsRealtime(c *gin.Context) { data, err := h.Visit.Realtime() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取实时数据失败", "detail": err.Error()}) return } c.JSON(http.StatusOK, data) } // GET /api/admin/stats/sources func (h *Handlers) StatsSources(c *gin.Context) { data, err := h.Visit.Sources(statsRange(c)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取来源分析失败"}) return } c.JSON(http.StatusOK, data) } // GET /api/admin/stats/pages?q=&page=&size= func (h *Handlers) StatsPages(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "20")) data, err := h.Visit.Pages(statsRange(c), strings.TrimSpace(c.Query("q")), page, size) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取受访页面失败"}) return } c.JSON(http.StatusOK, data) } // GET /api/admin/stats/visitors func (h *Handlers) StatsVisitors(c *gin.Context) { data, err := h.Visit.Visitors(statsRange(c)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取访客画像失败"}) return } c.JSON(http.StatusOK, data) } // GET /api/admin/stats/security func (h *Handlers) StatsSecurity(c *gin.Context) { data, err := h.Visit.Security(statsRange(c)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取安全数据失败"}) return } c.JSON(http.StatusOK, data) } // GET /api/admin/stats/events?kind=&bot=&path=&ip=&start=&end=&page=&size= func (h *Handlers) StatsEvents(c *gin.Context) { f := service.VisitEventFilter{ Kind: strings.TrimSpace(c.Query("kind")), Bot: strings.TrimSpace(c.Query("bot")), Path: strings.TrimSpace(c.Query("path")), IP: strings.TrimSpace(c.Query("ip")), } if v := c.Query("start"); v != "" { if t, err := time.Parse(time.RFC3339, v); err == nil { f.Start = t } } if v := c.Query("end"); v != "" { if t, err := time.Parse(time.RFC3339, v); err == nil { f.End = t } } f.Page, _ = strconv.Atoi(c.DefaultQuery("page", "1")) f.Size, _ = strconv.Atoi(c.DefaultQuery("size", "50")) data, err := h.Visit.Events(f) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取访问明细失败"}) return } c.JSON(http.StatusOK, data) } // GET /api/admin/stats/settings — 开关/保留天数 + 容量信息 func (h *Handlers) StatsGetSettings(c *gin.Context) { data, err := h.Visit.SettingsPayload() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取设置失败"}) return } c.JSON(http.StatusOK, data) } // PUT /api/admin/stats/settings func (h *Handlers) StatsSaveSettings(c *gin.Context) { var body struct { Enabled *bool `json:"enabled"` RetentionDays *int `json:"retention_days"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "参数无效"}) return } cur, err := h.Visit.SettingsPayload() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "读取当前设置失败"}) return } enabled := cur.Enabled if body.Enabled != nil { enabled = *body.Enabled } days := cur.RetentionDays if body.RetentionDays != nil { days = *body.RetentionDays } if err := h.Visit.SaveSettings(enabled, days); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "保存失败,保留天数需在 7–365 之间"}) return } data, err := h.Visit.SettingsPayload() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取设置失败"}) return } c.JSON(http.StatusOK, data) } // POST /api/admin/stats/purge — 立即清理 N 天前明细(异步分批执行,受理即返回) func (h *Handlers) StatsPurge(c *gin.Context) { var body struct { Days int `json:"days"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "参数无效"}) return } if !h.Visit.PurgeBefore(body.Days) { c.JSON(http.StatusBadRequest, gin.H{"error": "天数需在 7–365 之间"}) return } c.JSON(http.StatusOK, gin.H{"ok": true}) }