package middleware import ( "net/http" "strings" "time" "gotest/internal/geoip" "gotest/internal/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // responseWriter 包装 gin.ResponseWriter 以捕获状态码 type responseWriter struct { gin.ResponseWriter statusCode int } func (w *responseWriter) WriteHeader(code int) { w.statusCode = code w.ResponseWriter.WriteHeader(code) } // AccessLog 访问日志中间件,记录每次 HTTP 请求到数据库 func AccessLog(db *gorm.DB, ipService *geoip.Service) gin.HandlerFunc { return func(c *gin.Context) { // 跳过健康检查和静态资源 path := c.Request.URL.Path if path == "/api/health" || strings.HasPrefix(path, "/shared/") || strings.HasPrefix(path, "/assets/") { c.Next() return } start := time.Now() clientIP := c.ClientIP() // 包装响应写入器以捕获状态码 rw := &responseWriter{ResponseWriter: c.Writer, statusCode: http.StatusOK} c.Writer = rw c.Next() // 异步写入日志(不阻塞请求响应) go func() { cost := int(time.Since(start).Milliseconds()) ua := c.Request.UserAgent() if len(ua) > 500 { ua = ua[:500] } referer := c.Request.Referer() if len(referer) > 500 { referer = referer[:500] } p := c.Request.URL.Path if len(p) > 500 { p = p[:500] } // 查询 IP 归属地 location := ipService.Lookup(clientIP) log := model.AccessLog{ IP: clientIP, Path: p, Method: c.Request.Method, UserAgent: ua, Referer: referer, Location: location, Status: rw.statusCode, Cost: cost, } db.Create(&log) }() } }