package middleware import ( "context" "net/http" "strings" "time" "github.com/gin-gonic/gin" ) const ( maxFailures = 5 // 连续失败 5 次后锁定 lockDuration = 15 * time.Minute failureWindow = 10 * time.Minute // 失败计数窗口 ) // key = ip + "|" + username(小写) func limiterKey(c *gin.Context, username string) string { return c.ClientIP() + "|" + strings.ToLower(strings.TrimSpace(username)) } // CheckLoginLock 检查是否被锁定,被锁定则写 429 并返回 true(在 handler 解析 body 后调用) func CheckLoginLock(c *gin.Context, username string) bool { if authState == nil { c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"error": stateUnavailable("执行登录限流").Error()}) return true } key := limiterKey(c, username) locked, remain, err := authState.CheckLoginLock(context.Background(), key) if err != nil { c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"error": "登录状态服务不可用"}) return true } if locked { c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ "error": "登录尝试过多,已锁定,请稍后再试", "retry": int(remain.Minutes()) + 1, }) return true } return false } // RecordLoginFail 记录登录失败 func RecordLoginFail(c *gin.Context, username string) error { if authState == nil { return stateUnavailable("记录登录失败") } return authState.RecordLoginFailure(context.Background(), limiterKey(c, username)) } // RecordLoginSuccess 登录成功后清空计数 func RecordLoginSuccess(c *gin.Context, username string) error { if authState == nil { return stateUnavailable("清空登录失败计数") } return authState.RecordLoginSuccess(context.Background(), limiterKey(c, username)) }