初始化

This commit is contained in:
2026-07-03 16:01:08 +08:00
parent dc90e889e1
commit b51ee98afa
37 changed files with 6282 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
package middleware
import (
"net/http"
"strings"
"sync"
"github.com/gin-gonic/gin"
)
// IPBlacklist IP黑名单管理
var IPBlacklist = &ipBlacklist{
blockedIPs: make(map[string]bool),
allowedIPs: make(map[string]bool),
blockedCIDR: []string{},
}
type ipBlacklist struct {
mu sync.RWMutex
blockedIPs map[string]bool
allowedIPs map[string]bool
blockedCIDR []string // 预留CIDR支持
enabled bool
}
// SetEnabled 启用/禁用IP黑名单
func (bl *ipBlacklist) SetEnabled(enabled bool) {
bl.mu.Lock()
defer bl.mu.Unlock()
bl.enabled = enabled
}
// IsEnabled 是否启用
func (bl *ipBlacklist) IsEnabled() bool {
bl.mu.RLock()
defer bl.mu.RUnlock()
return bl.enabled
}
// AddBlockedIP 添加封禁IP
func (bl *ipBlacklist) AddBlockedIP(ip string) {
bl.mu.Lock()
defer bl.mu.Unlock()
bl.blockedIPs[ip] = true
}
// RemoveBlockedIP 移除封禁IP
func (bl *ipBlacklist) RemoveBlockedIP(ip string) {
bl.mu.Lock()
defer bl.mu.Unlock()
delete(bl.blockedIPs, ip)
}
// IsBlocked 检查IP是否被封禁
func (bl *ipBlacklist) IsBlocked(ip string) bool {
bl.mu.RLock()
defer bl.mu.RUnlock()
if !bl.enabled {
return false
}
// 白名单优先
if bl.allowedIPs[ip] {
return false
}
return bl.blockedIPs[ip]
}
// GetBlockedIPs 获取所有封禁IP
func (bl *ipBlacklist) GetBlockedIPs() []string {
bl.mu.RLock()
defer bl.mu.RUnlock()
ips := make([]string, 0, len(bl.blockedIPs))
for ip := range bl.blockedIPs {
ips = append(ips, ip)
}
return ips
}
// SetBlockedIPs 批量设置封禁IP
func (bl *ipBlacklist) SetBlockedIPs(ips []string) {
bl.mu.Lock()
defer bl.mu.Unlock()
bl.blockedIPs = make(map[string]bool)
for _, ip := range ips {
ip = strings.TrimSpace(ip)
if ip != "" {
bl.blockedIPs[ip] = true
}
}
}
// AddAllowedIP 添加白名单IP
func (bl *ipBlacklist) AddAllowedIP(ip string) {
bl.mu.Lock()
defer bl.mu.Unlock()
bl.allowedIPs[ip] = true
}
// IPBlacklistMiddleware IP黑名单中间件
func IPBlacklistMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if !IPBlacklist.IsEnabled() {
c.Next()
return
}
clientIP := c.ClientIP()
if IPBlacklist.IsBlocked(clientIP) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"code": 403,
"message": "您的IP已被封禁",
})
return
}
c.Next()
}
}

167
server/middleware/logger.go Normal file
View File

@@ -0,0 +1,167 @@
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
}

View File

@@ -0,0 +1,167 @@
package middleware
import (
"log"
"sync"
"sync/atomic"
"time"
"github.com/gin-gonic/gin"
)
// TrafficStats 全局流量统计
var TrafficStats = &trafficStats{
startTime: time.Now(),
}
type trafficStats struct {
startTime time.Time
totalRequests int64
totalBytesIn int64
totalBytesOut int64
activeConns int64
// 按状态码统计
status2xx int64
status3xx int64
status4xx int64
status5xx int64
// 最近请求记录(滑动窗口)
recentRequests []requestRecord
mu sync.Mutex
}
type requestRecord struct {
Timestamp time.Time
IP string
Path string
Status int
Bytes int
}
// TrafficInfo 流量信息
type TrafficInfo struct {
Uptime string `json:"uptime"`
TotalRequests int64 `json:"total_requests"`
TotalBytesIn int64 `json:"total_bytes_in"`
TotalBytesOut int64 `json:"total_bytes_out"`
ActiveConns int64 `json:"active_conns"`
QPS float64 `json:"qps"` // 每秒请求数
Status2xx int64 `json:"status_2xx"`
Status3xx int64 `json:"status_3xx"`
Status4xx int64 `json:"status_4xx"`
Status5xx int64 `json:"status_5xx"`
}
// GetTrafficInfo 获取流量统计信息
func GetTrafficInfo() TrafficInfo {
uptime := time.Since(TrafficStats.startTime).Truncate(time.Second)
// 计算QPS(最近60秒)
TrafficStats.mu.Lock()
cutoff := time.Now().Add(-60 * time.Second)
recent := 0
for _, r := range TrafficStats.recentRequests {
if r.Timestamp.After(cutoff) {
recent++
}
}
TrafficStats.mu.Unlock()
var qps float64
if uptime.Seconds() > 0 {
qps = float64(recent) / 60.0
}
return TrafficInfo{
Uptime: uptime.String(),
TotalRequests: atomic.LoadInt64(&TrafficStats.totalRequests),
TotalBytesIn: atomic.LoadInt64(&TrafficStats.totalBytesIn),
TotalBytesOut: atomic.LoadInt64(&TrafficStats.totalBytesOut),
ActiveConns: atomic.LoadInt64(&TrafficStats.activeConns),
QPS: qps,
Status2xx: atomic.LoadInt64(&TrafficStats.status2xx),
Status3xx: atomic.LoadInt64(&TrafficStats.status3xx),
Status4xx: atomic.LoadInt64(&TrafficStats.status4xx),
Status5xx: atomic.LoadInt64(&TrafficStats.status5xx),
}
}
// TrafficMonitor 流量监控中间件
func TrafficMonitor() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
// 增加活跃连接数
atomic.AddInt64(&TrafficStats.activeConns, 1)
defer atomic.AddInt64(&TrafficStats.activeConns, -1)
// 统计请求大小
bytesIn := int64(c.Request.ContentLength)
if bytesIn < 0 {
bytesIn = 0
}
atomic.AddInt64(&TrafficStats.totalBytesIn, bytesIn)
atomic.AddInt64(&TrafficStats.totalRequests, 1)
c.Next()
// 统计响应
status := c.Writer.Status()
bytesOut := int64(c.Writer.Size())
switch {
case status >= 200 && status < 300:
atomic.AddInt64(&TrafficStats.status2xx, 1)
case status >= 300 && status < 400:
atomic.AddInt64(&TrafficStats.status3xx, 1)
case status >= 400 && status < 500:
atomic.AddInt64(&TrafficStats.status4xx, 1)
case status >= 500:
atomic.AddInt64(&TrafficStats.status5xx, 1)
}
// 记录最近请求
TrafficStats.mu.Lock()
TrafficStats.recentRequests = append(TrafficStats.recentRequests, requestRecord{
Timestamp: start,
IP: c.ClientIP(),
Path: c.Request.URL.Path,
Status: status,
Bytes: int(bytesOut),
})
// 只保留最近5分钟的记录
cutoff := time.Now().Add(-5 * time.Minute)
valid := TrafficStats.recentRequests[:0]
for _, r := range TrafficStats.recentRequests {
if r.Timestamp.After(cutoff) {
valid = append(valid, r)
}
}
TrafficStats.recentRequests = valid
TrafficStats.mu.Unlock()
// 慢请求日志
duration := time.Since(start)
if duration > 5*time.Second {
log.Printf("[SLOW] %s %s %d %v", c.ClientIP(), c.Request.URL.Path, status, duration)
}
}
}
// GetRecentRequests 获取最近的请求记录
func GetRecentRequests(limit int) []requestRecord {
TrafficStats.mu.Lock()
defer TrafficStats.mu.Unlock()
total := len(TrafficStats.recentRequests)
if limit <= 0 || limit > total {
limit = total
}
start := total - limit
result := make([]requestRecord, limit)
copy(result, TrafficStats.recentRequests[start:])
return result
}