初始提交:姜十三论坛 Jiang13 Forum
轻量自用论坛,Go 单二进制 + React SPA 内嵌 + SQLite。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
69
service/ratelimit.go
Normal file
69
service/ratelimit.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RateLimiter 简单内存限流器,防止重复刷屏
|
||||
type RateLimiter struct {
|
||||
mu sync.Mutex
|
||||
records map[string][]time.Time
|
||||
limit int // 窗口内最大次数
|
||||
window time.Duration // 时间窗口
|
||||
}
|
||||
|
||||
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
|
||||
r := &RateLimiter{
|
||||
records: make(map[string][]time.Time),
|
||||
limit: limit,
|
||||
window: window,
|
||||
}
|
||||
go r.cleanup()
|
||||
return r
|
||||
}
|
||||
|
||||
// Allow 检查 key(如 userID+action)是否允许操作
|
||||
func (r *RateLimiter) Allow(key string) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
now := time.Now()
|
||||
cutoff := now.Add(-r.window)
|
||||
times := r.records[key]
|
||||
var valid []time.Time
|
||||
for _, t := range times {
|
||||
if t.After(cutoff) {
|
||||
valid = append(valid, t)
|
||||
}
|
||||
}
|
||||
if len(valid) >= r.limit {
|
||||
r.records[key] = valid
|
||||
return false
|
||||
}
|
||||
valid = append(valid, now)
|
||||
r.records[key] = valid
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *RateLimiter) cleanup() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
for range ticker.C {
|
||||
r.mu.Lock()
|
||||
now := time.Now()
|
||||
cutoff := now.Add(-r.window * 2)
|
||||
for k, times := range r.records {
|
||||
var valid []time.Time
|
||||
for _, t := range times {
|
||||
if t.After(cutoff) {
|
||||
valid = append(valid, t)
|
||||
}
|
||||
}
|
||||
if len(valid) == 0 {
|
||||
delete(r.records, k)
|
||||
} else {
|
||||
r.records[k] = valid
|
||||
}
|
||||
}
|
||||
r.mu.Unlock()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user