168 lines
3.6 KiB
Go
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
|
|
}
|