d440fb1ae7
CI / docker (push) Has been cancelled
引入用户端对话/生图与管理端 Provider 配置,统一 invoke 编排、Gateway、请求日志与模型管理;前端按设计规范拆分布局,Go module 更名为 github.com/rose_cat707/Atlas;生图长任务 SSE 推送;管理端登录防爆破;协议与服务类型联动;Replicate 模型同步修复;用户端创作台 UI 重构;管理端创作历史替代请求日志。 Co-authored-by: Cursor <cursoragent@cursor.com>
69 lines
1.2 KiB
Go
69 lines
1.2 KiB
Go
package api
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
loginMaxFailures = 5
|
|
loginLockDuration = 15 * time.Minute
|
|
loginMinDelay = time.Second
|
|
)
|
|
|
|
type loginGuard struct {
|
|
mu sync.Mutex
|
|
byIP map[string]*loginGuardEntry
|
|
}
|
|
|
|
type loginGuardEntry struct {
|
|
failures int
|
|
lockedUntil time.Time
|
|
}
|
|
|
|
func newLoginGuard() *loginGuard {
|
|
return &loginGuard{byIP: make(map[string]*loginGuardEntry)}
|
|
}
|
|
|
|
func (g *loginGuard) check(ip string) (locked bool, retryAfter int) {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
|
|
e := g.byIP[ip]
|
|
if e == nil {
|
|
return false, 0
|
|
}
|
|
now := time.Now()
|
|
if now.Before(e.lockedUntil) {
|
|
return true, int(e.lockedUntil.Sub(now).Seconds()) + 1
|
|
}
|
|
if e.failures >= loginMaxFailures {
|
|
e.failures = 0
|
|
e.lockedUntil = time.Time{}
|
|
}
|
|
return false, 0
|
|
}
|
|
|
|
func (g *loginGuard) recordFailure(ip string) (locked bool, retryAfter int) {
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
|
|
e := g.byIP[ip]
|
|
if e == nil {
|
|
e = &loginGuardEntry{}
|
|
g.byIP[ip] = e
|
|
}
|
|
e.failures++
|
|
if e.failures >= loginMaxFailures {
|
|
e.lockedUntil = time.Now().Add(loginLockDuration)
|
|
return true, int(loginLockDuration.Seconds())
|
|
}
|
|
return false, 0
|
|
}
|
|
|
|
func (g *loginGuard) reset(ip string) {
|
|
g.mu.Lock()
|
|
delete(g.byIP, ip)
|
|
g.mu.Unlock()
|
|
}
|