package ratelimit import ( "net" "net/http" "strings" "sync" "time" ) type Limiter struct { mu sync.Mutex visitors map[string]*visitor rate int // 0 = disabled window time.Duration } type visitor struct { count int reset time.Time } // New creates a per-IP limiter. ratePerMinute <= 0 disables limiting. func New(ratePerMinute int) *Limiter { return &Limiter{visitors: map[string]*visitor{}, rate: ratePerMinute, window: time.Minute} } func (l *Limiter) Middleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if l.rate <= 0 { next(w, r) return } ip, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil || ip == "" { ip = r.RemoteAddr } // 本机开发:Vite 代理与所有本机请求共用 127.0.0.1,不做限流 if isLoopback(ip) { next(w, r) return } if !l.allow(ip) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusTooManyRequests) _, _ = w.Write([]byte(`{"code":429,"message":"gateway rate limit exceeded"}`)) return } next(w, r) } } func isLoopback(ip string) bool { ip = strings.Trim(ip, "[]") if ip == "127.0.0.1" || ip == "::1" || ip == "localhost" { return true } parsed := net.ParseIP(ip) return parsed != nil && parsed.IsLoopback() } func (l *Limiter) allow(key string) bool { l.mu.Lock() defer l.mu.Unlock() now := time.Now() v, ok := l.visitors[key] if !ok || now.After(v.reset) { l.visitors[key] = &visitor{count: 1, reset: now.Add(l.window)} return true } if v.count >= l.rate { return false } v.count++ return true }