package middleware import ( "net/http" "strconv" "sync" "time" "github.com/gin-gonic/gin" ) type rateLimitBucket struct { count int resetAt time.Time lastSeen time.Time } type rateLimiter struct { mu sync.Mutex limit int window time.Duration buckets map[string]rateLimitBucket } func RateLimitPerMinute(limit int) gin.HandlerFunc { if limit <= 0 { return func(c *gin.Context) { c.Next() } } limiter := &rateLimiter{ limit: limit, window: time.Minute, buckets: make(map[string]rateLimitBucket), } return limiter.handle } func (l *rateLimiter) handle(c *gin.Context) { now := time.Now() key := c.ClientIP() allowed, resetAt := l.allow(key, now) if !allowed { c.Header("Retry-After", retryAfterSeconds(now, resetAt)) c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ "code": "rate_limited", "message": "请求过于频繁,请稍后再试", }) return } c.Next() } func (l *rateLimiter) allow(key string, now time.Time) (bool, time.Time) { l.mu.Lock() defer l.mu.Unlock() l.cleanup(now) bucket := l.buckets[key] if bucket.resetAt.IsZero() || !now.Before(bucket.resetAt) { bucket = rateLimitBucket{resetAt: now.Add(l.window)} } bucket.count++ bucket.lastSeen = now l.buckets[key] = bucket return bucket.count <= l.limit, bucket.resetAt } func (l *rateLimiter) cleanup(now time.Time) { for key, bucket := range l.buckets { if now.Sub(bucket.lastSeen) > 2*l.window { delete(l.buckets, key) } } } func retryAfterSeconds(now time.Time, resetAt time.Time) string { seconds := int(resetAt.Sub(now).Seconds()) if seconds < 1 { seconds = 1 } return strconv.Itoa(seconds) }