Files
Scoreboard/server/internal/auth/ratelimit.go
T
AskaEth bc8b1ee6c0 feat: M3-M7 全模块落地 + 集成接线
- M3 认证/权限/审计/限流 + 全部 REST 接口
- M4 管理面板(MIUIx + 成绩录入/积分榜下钻/大屏控制)
- M5a 大屏页面(六页 + 明细覆盖层 + 缩放适配)
- M6 WebSocket 实时同步中心
- M7 xlsx 导出 + 赛事包 zip + 备份
- 规则包 YAML 导入导出(差异预览)
- 字体 vendor 子集化(4 套 x2 字重)
- 集成:realtime/export/rules 接入 main+httpapi;backup 子命令
- 修复:中间件包装 ResponseWriter 丢失 Hijacker,导致 WS 握手 501
2026-09-09 19:02:24 +08:00

60 lines
1.2 KiB
Go

package auth
import (
"sync"
"time"
)
// Limiter 是按键(IP / 用户)计数的滑动窗口限流器。
// 进程内内存实现:现场单实例部署足够,不引入额外存储。
type Limiter struct {
mu sync.Mutex
hits map[string][]time.Time
limit int
window time.Duration
now func() time.Time
}
// NewLimiter 创建限流器;limit <= 0 表示不限流。
func NewLimiter(limit int, window time.Duration, now func() time.Time) *Limiter {
if now == nil {
now = time.Now
}
return &Limiter{hits: make(map[string][]time.Time), limit: limit, window: window, now: now}
}
// Allow 记录一次访问并报告是否放行。
func (l *Limiter) Allow(key string) bool {
if l == nil || l.limit <= 0 {
return true
}
now := l.now()
cutoff := now.Add(-l.window)
l.mu.Lock()
defer l.mu.Unlock()
kept := l.hits[key][:0]
for _, t := range l.hits[key] {
if t.After(cutoff) {
kept = append(kept, t)
}
}
if len(kept) >= l.limit {
l.hits[key] = kept
return false
}
l.hits[key] = append(kept, now)
return true
}
// Reset 清空某个键的计数,例如登录成功后不再累计失败次数。
func (l *Limiter) Reset(key string) {
if l == nil {
return
}
l.mu.Lock()
delete(l.hits, key)
l.mu.Unlock()
}