package middleware import ( "strings" "seeyon-filesystem/config" "seeyon-filesystem/model" "github.com/gin-gonic/gin" ) // OperationLogger 操作日志中间件 // 自动记录关键操作到 fs_operation_log 表 func OperationLogger() gin.HandlerFunc { return func(c *gin.Context) { c.Next() // 只记录写操作 method := c.Request.Method if method == "GET" || method == "OPTIONS" { return } path := c.Request.URL.Path // 跳过不需要记录的路径 if shouldSkipLog(path) { return } // 获取操作信息 action, resource := classifyOperation(method, path) // 获取用户信息 userID, _ := c.Get("user_id") username, _ := c.Get("username") var uid uint64 var uname string if userID != nil { uid = userID.(uint64) } if username != nil { uname = username.(string) } // 获取请求详情 detail := method + " " + path if query := c.Request.URL.Query().Encode(); query != "" { detail += "?" + query } // 判断是否成功 status := 1 if c.Writer.Status() >= 400 { status = 0 } // 异步写入日志(不阻塞请求) go func() { log := &model.OperationLog{ UserID: uid, Username: uname, Action: action, Resource: resource, Detail: truncate(detail, 500), IP: c.ClientIP(), UserAgent: truncate(c.Request.UserAgent(), 256), Status: status, } config.DB.Create(log) }() } } // classifyOperation 根据方法和路径判断操作类型 func classifyOperation(method, path string) (string, string) { // 认证相关 if strings.Contains(path, "/auth/login") { return "login", "auth" } if strings.Contains(path, "/auth/register") { return "register", "auth" } if strings.Contains(path, "/sso/callback") { return "sso_login", "auth" } if strings.Contains(path, "/openapi/token") { return "token_validate", "auth" } // 文件操作 if strings.Contains(path, "/file/upload") { return "upload", "file" } if strings.Contains(path, "/file") && strings.Contains(path, "/rename") { return "rename", "file" } if strings.Contains(path, "/file") && strings.Contains(path, "/move") { return "move", "file" } if strings.Contains(path, "/file") && method == "DELETE" { return "delete", "file" } if strings.Contains(path, "/file") && strings.Contains(path, "/restore") { return "restore", "file" } // 文件夹操作 if strings.Contains(path, "/folder") && method == "POST" { return "create_folder", "folder" } if strings.Contains(path, "/folder") && strings.Contains(path, "/rename") { return "rename", "folder" } if strings.Contains(path, "/folder") && method == "DELETE" { return "delete", "folder" } // 分享操作 if strings.Contains(path, "/share") && method == "POST" { return "create_share", "share" } if strings.Contains(path, "/share") && method == "DELETE" { return "cancel_share", "share" } // 管理操作 if strings.Contains(path, "/admin/users") { return "manage_user", "user" } if strings.Contains(path, "/admin/roles") { return "manage_role", "role" } if strings.Contains(path, "/admin/storage") { return "manage_storage", "system" } if strings.Contains(path, "/admin/apps") { return "manage_app", "system" } return method, "other" } // shouldSkipLog 判断是否跳过记录 func shouldSkipLog(path string) bool { skipPaths := []string{ "/health", "/api/auth/refresh", "/api/admin/monitor/traffic", "/api/admin/monitor/requests", } for _, p := range skipPaths { if path == p { return true } } return false } // truncate 截断字符串 func truncate(s string, maxLen int) string { if len(s) > maxLen { return s[:maxLen] + "..." } return s }