- RateLimiter 新增 AllowSensitive 方法(1分钟5次,超限封禁5分钟) - 新增 SensitiveRateLimit Gin 中间件 - 改密(PUT /api/settings/password)增加限流保护 - 注销(POST /api/settings/delete-account)增加限流保护 - 签到(POST /api/level/checkin)增加限流保护
79 lines
1.9 KiB
Go
79 lines
1.9 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
|
|
|
|
// 敏感操作限流:改密/注销/签到
|
|
sensitiveWindow = 1 * time.Minute
|
|
sensitiveMaxRequests = 5
|
|
sensitiveBlockDur = 5 * time.Minute
|
|
)
|
|
|
|
// 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}
|
|
}
|