初始化
This commit is contained in:
124
server/middleware/ip_blacklist.go
Normal file
124
server/middleware/ip_blacklist.go
Normal 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
167
server/middleware/logger.go
Normal 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
|
||||
}
|
||||
167
server/middleware/traffic.go
Normal file
167
server/middleware/traffic.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user