feat(backend): 限流/登录失败锁状态外置 Redis(todo #2)
新包 internal/ratelimit:内存/redis(GCRA+Lua) 双实现 + 出错逐调用降级内存 (fail-open 到内存不 fail-closed)。REDIS_ADDR 空=内存模式,行为与既往一致; 配置后跨重启保状态、支持多实例。miniredis 全覆盖测试,零真实外部依赖。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -107,29 +107,7 @@ func TestRateLimitDisabledPassthrough(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyedLimiterSweepEvictsIdle(t *testing.T) {
|
||||
kl := newKeyedLimiter(PerMinute(60), 1)
|
||||
kl.get("ip:a")
|
||||
kl.get("ip:b")
|
||||
if len(kl.entries) != 2 {
|
||||
t.Fatalf("应有 2 个 entry,得到 %d", len(kl.entries))
|
||||
}
|
||||
// 把 a 的活动时间推到很久以前,sweep 应只淘汰 a。
|
||||
kl.mu.Lock()
|
||||
kl.entries["ip:a"].lastSeen = time.Now().Add(-time.Hour)
|
||||
kl.mu.Unlock()
|
||||
|
||||
kl.sweep(10 * time.Minute)
|
||||
|
||||
kl.mu.Lock()
|
||||
defer kl.mu.Unlock()
|
||||
if _, ok := kl.entries["ip:a"]; ok {
|
||||
t.Fatal("空闲 key a 应被淘汰")
|
||||
}
|
||||
if _, ok := kl.entries["ip:b"]; !ok {
|
||||
t.Fatal("活跃 key b 不应被淘汰")
|
||||
}
|
||||
}
|
||||
// keyedLimiter 内部淘汰测试已随实现迁至 internal/ratelimit/memory_test.go。
|
||||
|
||||
// TestTrustedProxyRealIP 复刻 main.go 的可信代理配置:只信任本机写的 X-Real-IP,
|
||||
// 客户端伪造的 X-Forwarded-For 不被采信 → c.ClientIP() 返回真实 IP,限流不可被请求头绕过。
|
||||
|
||||
Reference in New Issue
Block a user