package middleware import ( "net/http" "sync" "time" "github.com/gin-gonic/gin" ) type RateLimiter struct { requests map[string][]time.Time mu sync.Mutex limit int window time.Duration } func NewRateLimiter(limit int, window time.Duration) *RateLimiter { rl := &RateLimiter{ requests: make(map[string][]time.Time), limit: limit, window: window, } go func() { for { time.Sleep(window) rl.cleanup() } }() return rl } func (rl *RateLimiter) cleanup() { rl.mu.Lock() defer rl.mu.Unlock() now := time.Now() for key, times := range rl.requests { var valid []time.Time for _, t := range times { if now.Sub(t) < rl.window { valid = append(valid, t) } } if len(valid) == 0 { delete(rl.requests, key) } else { rl.requests[key] = valid } } } func (rl *RateLimiter) isAllowed(key string) bool { rl.mu.Lock() defer rl.mu.Unlock() now := time.Now() times := rl.requests[key] var valid []time.Time for _, t := range times { if now.Sub(t) < rl.window { valid = append(valid, t) } } if len(valid) >= rl.limit { rl.requests[key] = valid return false } rl.requests[key] = append(valid, now) return true } func RateLimit(requestsPerMinute int) gin.HandlerFunc { limiter := NewRateLimiter(requestsPerMinute, time.Minute) return func(c *gin.Context) { ip := c.ClientIP() if !limiter.isAllowed(ip) { c.JSON(http.StatusTooManyRequests, gin.H{ "code": 429, "message": "too many requests, please try again later", }) c.Abort() return } c.Next() } } func RateLimitWithKey(requestsPerMinute int, keyFunc func(c *gin.Context) string) gin.HandlerFunc { limiter := NewRateLimiter(requestsPerMinute, time.Minute) return func(c *gin.Context) { key := keyFunc(c) if key == "" { key = c.ClientIP() } if !limiter.isAllowed(key) { c.JSON(http.StatusTooManyRequests, gin.H{ "code": 429, "message": "too many requests, please try again later", }) c.Abort() return } c.Next() } }