- M3 认证/权限/审计/限流 + 全部 REST 接口 - M4 管理面板(MIUIx + 成绩录入/积分榜下钻/大屏控制) - M5a 大屏页面(六页 + 明细覆盖层 + 缩放适配) - M6 WebSocket 实时同步中心 - M7 xlsx 导出 + 赛事包 zip + 备份 - 规则包 YAML 导入导出(差异预览) - 字体 vendor 子集化(4 套 x2 字重) - 集成:realtime/export/rules 接入 main+httpapi;backup 子命令 - 修复:中间件包装 ResponseWriter 丢失 Hijacker,导致 WS 握手 501
60 lines
1.2 KiB
Go
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()
|
|
}
|