package handler import ( "crypto/rand" "encoding/hex" "fmt" "net/http" "github.com/freefire/jiang13-bbs/middleware" "github.com/freefire/jiang13-bbs/service" "github.com/gin-gonic/gin" ) const visitorCookie = "j13_vid" func newVisitorID() string { var b [16]byte _, _ = rand.Read(b[:]) // 伪 UUID 形态,满足 ValidVisitorID return fmt.Sprintf("%s-%s-%s-%s-%s", hex.EncodeToString(b[0:4]), hex.EncodeToString(b[4:6]), hex.EncodeToString(b[6:8]), hex.EncodeToString(b[8:10]), hex.EncodeToString(b[10:16]), ) } // POST /api/telemetry/pageview — 轻量 PV/UV 埋点(公开,需 CSRF) func (h *Handlers) TelemetryPageView(c *gin.Context) { vid, _ := c.Cookie(visitorCookie) if !service.ValidVisitorID(vid) { vid = newVisitorID() secure := c.Request.TLS != nil || c.GetHeader("X-Forwarded-Proto") == "https" // HttpOnly:仅服务端读 UV,前端埋点不依赖可读 cookie c.SetCookie(visitorCookie, vid, 365*24*3600, "/", "", secure, true) } loggedIn := middleware.CurrentUser(c) != nil if err := h.Telemetry.RecordPageView(vid, loggedIn); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "记录失败"}) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } // GET /api/admin/analytics/overview?range=7d|30d func (h *Handlers) AdminAnalyticsOverview(c *gin.Context) { rangeQ := c.DefaultQuery("range", "7d") data, err := h.Analytics.Overview(rangeQ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取分析数据失败"}) return } c.JSON(http.StatusOK, data) }