Files
SeeyonFileSystem/server/middleware/logger.go
2026-07-03 16:01:08 +08:00

168 lines
3.6 KiB
Go

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
}