package apilimitHandler import ( "net/http" "sync" "time" "github.com/gin-gonic/gin" ) type counter struct { Count int WindowStart time.Time } type RouteHourlyLimiter struct { mu sync.Mutex limit int window time.Duration counters map[string]*counter } func NewRouteHourlyLimiter(limit int) *RouteHourlyLimiter { l := &RouteHourlyLimiter{ limit: limit, window: time.Hour, counters: make(map[string]*counter), } go l.cleaner() return l } func (l *RouteHourlyLimiter) Middleware() gin.HandlerFunc { return func(c *gin.Context) { // 用 FullPath 做“接口维度”,例如 /api/user/:id route := c.FullPath() if route == "" { // 路由未匹配时兜底 route = c.Request.URL.Path } now := time.Now() l.mu.Lock() defer l.mu.Unlock() item, ok := l.counters[route] if !ok || now.Sub(item.WindowStart) >= l.window { l.counters[route] = &counter{ Count: 1, WindowStart: now, } c.Next() return } if item.Count >= l.limit { c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ "code": http.StatusTooManyRequests, "message": "rate limit exceeded: max 100 requests per route per hour", }) return } item.Count++ c.Next() } } // 定时清理过期窗口,避免 map 无限增长 func (l *RouteHourlyLimiter) cleaner() { ticker := time.NewTicker(10 * time.Minute) defer ticker.Stop() for now := range ticker.C { l.mu.Lock() for k, v := range l.counters { if now.Sub(v.WindowStart) >= l.window { delete(l.counters, k) } } l.mu.Unlock() } }