初始提交:OCI 面板后端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user