spark-server/internal/pkg/ratelimit/ratelimit.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 19:52:11 +08:00

70 lines
1.1 KiB
Go

package ratelimit
import (
"sync"
"time"
)
// Limiter is a per-key token bucket. Keys are RealIP (C6), never gnet RemoteAddr.
type Limiter struct {
mu sync.Mutex
rate float64
burst float64
buckets map[string]*bucket
}
type bucket struct {
tokens float64
last time.Time
}
func New(perSec int) *Limiter {
if perSec < 1 {
perSec = 5
}
return &Limiter{
rate: float64(perSec),
burst: float64(perSec),
buckets: make(map[string]*bucket),
}
}
func (l *Limiter) Allow(key string) bool {
if l == nil {
return true
}
if key == "" {
key = "unknown"
}
now := time.Now()
l.mu.Lock()
defer l.mu.Unlock()
b := l.buckets[key]
if b == nil {
b = &bucket{tokens: l.burst, last: now}
l.buckets[key] = b
}
elapsed := now.Sub(b.last).Seconds()
b.tokens += elapsed * l.rate
if b.tokens > l.burst {
b.tokens = l.burst
}
b.last = now
if b.tokens < 1 {
return false
}
b.tokens--
if len(l.buckets) > 8192 {
l.gc(now)
}
return true
}
func (l *Limiter) gc(now time.Time) {
for k, b := range l.buckets {
if now.Sub(b.last) > time.Minute {
delete(l.buckets, k)
}
}
}