middleware: - 拆分 auth.go → auth.go + auth_token.go + auth_parser.go + auth_admin.go - 拆分 csrf.go → csrf.go + csrf_token.go - 拆分 ratelimit.go → ratelimit.go + ratelimit_core.go + ratelimit_cleanup.go - 修复 import 未使用/缺失问题 router: - 拆分 router.go → admin.go + api.go + frontend.go + deps_core.go + deps_extra.go feat(admin): 站点设置管理页面 - 新增 SSR 页面 /admin/site-settings(仅 Owner) - 审核开关 Toggle 即时保存 - 通用设置展示 - 侧边栏新增站点设置入口 - 注册 UpdateBoolSetting/GetBoolSetting API 路由
82 lines
1.7 KiB
Go
82 lines
1.7 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
|
|
}
|
|
|
|
// check 核心检查逻辑
|
|
func (rl *RateLimiter) check(m map[string]*windowState, key string, window, blockDur time.Duration, maxFails int, lowKey bool) (RateLimitResult, func()) {
|
|
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}, nil
|
|
}
|
|
|
|
if now.Sub(state.windowStart) > window {
|
|
state.count = 0
|
|
state.windowStart = now
|
|
state.blockedUntil = time.Time{}
|
|
}
|
|
|
|
recordFail := func() {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
s := m[key]
|
|
if s == nil {
|
|
return
|
|
}
|
|
if now.Sub(s.windowStart) > window {
|
|
s.count = 1
|
|
s.windowStart = now
|
|
return
|
|
}
|
|
s.count++
|
|
if s.count >= maxFails {
|
|
s.blockedUntil = now.Add(blockDur)
|
|
}
|
|
}
|
|
|
|
return RateLimitResult{Blocked: false}, recordFail
|
|
}
|