package middleware import ( "net/http" "sync" "time" "github.com/gin-gonic/gin" ) type RateLimiter struct { mu sync.Mutex attempts map[string][]time.Time limit int window time.Duration } func NewRateLimiter(limit int, window time.Duration) *RateLimiter { rl := &RateLimiter{ attempts: make(map[string][]time.Time), limit: limit, window: window, } go rl.cleanup() return rl } func (rl *RateLimiter) Allow(key string) bool { rl.mu.Lock() defer rl.mu.Unlock() now := time.Now() cutoff := now.Add(-rl.window) var recent []time.Time for _, t := range rl.attempts[key] { if t.After(cutoff) { recent = append(recent, t) } } if len(recent) >= rl.limit { rl.attempts[key] = recent return false } recent = append(recent, now) rl.attempts[key] = recent return true } func (rl *RateLimiter) cleanup() { ticker := time.NewTicker(rl.window) defer ticker.Stop() for range ticker.C { rl.mu.Lock() cutoff := time.Now().Add(-rl.window) for key, times := range rl.attempts { var recent []time.Time for _, t := range times { if t.After(cutoff) { recent = append(recent, t) } } if len(recent) == 0 { delete(rl.attempts, key) } else { rl.attempts[key] = recent } } rl.mu.Unlock() } } func LoginRateLimit() gin.HandlerFunc { limiter := NewRateLimiter(5, time.Minute) return func(c *gin.Context) { key := c.ClientIP() if !limiter.Allow(key) { c.JSON(http.StatusTooManyRequests, gin.H{"error": "请求过于频繁,请稍后再试"}) c.Abort() return } c.Next() } } // BodyLimit 限制请求体大小,防止内存耗尽型 DoS。 // 对所有 /api JSON 端点生效;WebSocket 走连接劫持,握手阶段不受影响。 func BodyLimit(max int64) gin.HandlerFunc { return func(c *gin.Context) { c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, max) c.Next() } }