package service import ( "sync" "time" ) // loginGuard 按「IP|用户名」双维度做滑动窗口失败计数与锁定; // 窗口与锁定时长同值、阈值由调用方按安全设置传入(探索文档主题三); // 内存实现(单体面板),重启清零可接受。 type loginGuard struct { mu sync.Mutex failures map[string][]time.Time lockedAt map[string]time.Time } func newLoginGuard() *loginGuard { return &loginGuard{ failures: map[string][]time.Time{}, lockedAt: map[string]time.Time{}, } } // locked 判定 key 是否处于锁定期;过期锁惰性清除。 func (g *loginGuard) locked(key string, now time.Time, lockFor time.Duration) bool { g.mu.Lock() defer g.mu.Unlock() at, ok := g.lockedAt[key] if !ok { return false } if now.Sub(at) >= lockFor { delete(g.lockedAt, key) return false } return true } // fail 记一次失败并裁剪窗口;达到阈值转入锁定并返回 true(仅触发那一次)。 func (g *loginGuard) fail(key string, now time.Time, limit int, window time.Duration) bool { g.mu.Lock() defer g.mu.Unlock() kept := g.failures[key][:0] for _, t := range g.failures[key] { if now.Sub(t) < window { kept = append(kept, t) } } kept = append(kept, now) if len(kept) >= limit { delete(g.failures, key) g.lockedAt[key] = now return true } g.failures[key] = kept return false } // success 清空 key 的失败计数(成功登录重置窗口)。 func (g *loginGuard) success(key string) { g.mu.Lock() defer g.mu.Unlock() delete(g.failures, key) }