- 安全:移除 CSP 中 esm.sh/cdnjs.cloudflare.com,highlight.js 主题已本地化 73 个文件 - 安全:StatusBanned 分支补 dummy hash 防时序攻击 - 安全:手写 constantTimeEq 替换为 crypto/subtle.ConstantTimeCompare - Bug:锁定操作消息已删除→已锁定 - YAGNI:删除 12 个空预留模板目录 - KISS:删除 common.CheckPassword 薄封装,统一用 bcrypt 调用 - KISS:删除 tokenCtrl 别名字段,api.go 统一用 authCtrl - RateLimiter:check()+recordFail 闭包模式重构为 try() 原子操作,消除竞态 - RateLimiter:新增 ClearIP() 方法,注册成功时清除 IP 计数 - 文档:修正 audit_service.go 注释编号跳跃(3→5→4) - 文档:修正 deps_core.go 过清理→过期清理
74 lines
1.8 KiB
Go
74 lines
1.8 KiB
Go
package middleware
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// RateLimitResult 限流检查结果
|
|
type RateLimitResult struct {
|
|
Blocked bool
|
|
RetryAfter int
|
|
Message string
|
|
}
|
|
|
|
const (
|
|
accountWindow = 1 * time.Minute
|
|
accountMaxFails = 3
|
|
accountBlockDur = 1 * time.Minute
|
|
ipWindow = 1 * time.Minute
|
|
ipMaxFails = 10
|
|
ipBlockDur = 1 * time.Minute
|
|
cleanupInterval = 2 * time.Minute
|
|
maxEntries = 10000
|
|
)
|
|
|
|
// windowState 单个维度的限流状态
|
|
type windowState struct {
|
|
count int
|
|
windowStart time.Time
|
|
blockedUntil time.Time
|
|
}
|
|
|
|
// try 原子检查+递增:在持锁状态下判断是否被限流,若允许则递增计数。
|
|
// 检查与递增在同一临界区内完成,无竞态窗口。
|
|
// 调用方需在操作成功后调用 Clear/或 ClearIP 清除计数。
|
|
func (rl *RateLimiter) try(m map[string]*windowState, key string, window, blockDur time.Duration, maxFails int, lowKey bool) RateLimitResult {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
state, exists := m[key]
|
|
if !exists {
|
|
state = &windowState{}
|
|
if len(m) < maxEntries {
|
|
m[key] = state
|
|
}
|
|
}
|
|
|
|
// 已被封禁中
|
|
if !state.blockedUntil.IsZero() && now.Before(state.blockedUntil) {
|
|
retry := int(state.blockedUntil.Sub(now).Seconds()) + 1
|
|
msg := "请求过于频繁,请稍后重试"
|
|
if lowKey {
|
|
msg = fmt.Sprintf("该账号登录尝试过于频繁,请 %d 秒后重试", retry)
|
|
}
|
|
return RateLimitResult{Blocked: true, RetryAfter: retry, Message: msg}
|
|
}
|
|
|
|
// 窗口过期 → 重置
|
|
if now.Sub(state.windowStart) > window {
|
|
state.count = 0
|
|
state.windowStart = now
|
|
state.blockedUntil = time.Time{}
|
|
}
|
|
|
|
// 原子递增计数(本次尝试已记录)
|
|
state.count++
|
|
if state.count >= maxFails {
|
|
state.blockedUntil = now.Add(blockDur)
|
|
}
|
|
|
|
return RateLimitResult{Blocked: false}
|
|
}
|