package middleware import ( "strings" "github.com/freefire/jiang13-bbs/model" "github.com/freefire/jiang13-bbs/service" "github.com/gin-gonic/gin" ) // VisitCapture 服务端访问捕获:只记「UA 命中爬虫库」与「响应状态 ≥ 400」两类请求, // 普通人类浏览由客户端埋点上报(互补、不重复)。事件非阻塞入队,不影响响应。 func VisitCapture(svc *service.VisitStatsService) gin.HandlerFunc { return func(c *gin.Context) { c.Next() if svc == nil || !svc.Enabled() { return } // 静态资源与健康检查/WS 无统计意义 path := c.Request.URL.Path if strings.HasPrefix(path, "/uploads") || path == "/health" || path == "/api/ws" { return } ua := c.Request.UserAgent() bot := service.ClassifyBot(ua) status := c.Writer.Status() if bot == "" && status < 400 { return // 正常人类请求:不记 } ev := model.VisitEvent{} if bot != "" { ev.Kind = model.VisitKindBot ev.Bot = bot } else { ev.Kind = model.VisitKindProbe ev.Status = status } if q := c.Request.URL.RawQuery; q != "" { ev.Path = path + "?" + q } else { ev.Path = path } if len(ev.Path) > 512 { ev.Path = ev.Path[:512] } ev.IP = service.CanonicalIP(c.ClientIP()) if vid, err := c.Cookie(service.VisitorCookieName); err == nil && service.ValidVisitorID(vid) { ev.VidHash = service.HashVisitorID(vid) } if u := CurrentUser(c); u != nil { ev.UserID = u.ID } p := service.ParseUserAgent(ua) ev.Device = p.Kind ev.OS = p.OS ev.Browser = p.Browser svc.Enqueue(ev) } }