fix(chat): 优化后台限流与 SSE 日志
This commit is contained in:
@@ -7,10 +7,14 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/modules/auth"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type rateLimitKeyFunc func(c *gin.Context) string
|
||||
|
||||
type rateLimitBucket struct {
|
||||
count int
|
||||
resetAt time.Time
|
||||
@@ -32,6 +36,16 @@ type redisRateLimiter struct {
|
||||
}
|
||||
|
||||
func RateLimitPerMinute(limit int, rdb *redis.Client) gin.HandlerFunc {
|
||||
return rateLimitPerMinute(limit, rdb, ipRateLimitKey)
|
||||
}
|
||||
|
||||
// AdminAwareRateLimitPerMinute isolates authenticated admin traffic by admin ID.
|
||||
// Login and other requests without a valid admin access token fall back to IP.
|
||||
func AdminAwareRateLimitPerMinute(limit int, rdb *redis.Client, jwtManager *auth.JWTManager) gin.HandlerFunc {
|
||||
return rateLimitPerMinute(limit, rdb, adminOrIPRateLimitKey(jwtManager))
|
||||
}
|
||||
|
||||
func rateLimitPerMinute(limit int, rdb *redis.Client, keyFunc rateLimitKeyFunc) gin.HandlerFunc {
|
||||
if limit <= 0 {
|
||||
return func(c *gin.Context) {
|
||||
c.Next()
|
||||
@@ -48,34 +62,62 @@ func RateLimitPerMinute(limit int, rdb *redis.Client) gin.HandlerFunc {
|
||||
fallback: limiter,
|
||||
limit: limit,
|
||||
window: time.Minute,
|
||||
}).handle
|
||||
}).handle(keyFunc)
|
||||
}
|
||||
return limiter.handle
|
||||
return limiter.handle(keyFunc)
|
||||
}
|
||||
|
||||
func (l *redisRateLimiter) handle(c *gin.Context) {
|
||||
now := time.Now()
|
||||
key := c.ClientIP()
|
||||
allowed, resetAt, err := l.allow(c.Request.Context(), key, now)
|
||||
if err != nil {
|
||||
allowed, resetAt = l.fallback.allow(key, now)
|
||||
func (l *redisRateLimiter) handle(keyFunc rateLimitKeyFunc) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
now := time.Now()
|
||||
key := keyFunc(c)
|
||||
allowed, resetAt, err := l.allow(c.Request.Context(), key, now)
|
||||
if err != nil {
|
||||
allowed, resetAt = l.fallback.allow(key, now)
|
||||
}
|
||||
if !allowed {
|
||||
writeRateLimited(c, now, resetAt)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
if !allowed {
|
||||
writeRateLimited(c, now, resetAt)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func (l *rateLimiter) handle(c *gin.Context) {
|
||||
now := time.Now()
|
||||
key := c.ClientIP()
|
||||
allowed, resetAt := l.allow(key, now)
|
||||
if !allowed {
|
||||
writeRateLimited(c, now, resetAt)
|
||||
return
|
||||
func (l *rateLimiter) handle(keyFunc rateLimitKeyFunc) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
now := time.Now()
|
||||
key := keyFunc(c)
|
||||
allowed, resetAt := l.allow(key, now)
|
||||
if !allowed {
|
||||
writeRateLimited(c, now, resetAt)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func ipRateLimitKey(c *gin.Context) string {
|
||||
return "ip:" + c.ClientIP()
|
||||
}
|
||||
|
||||
func adminOrIPRateLimitKey(jwtManager *auth.JWTManager) rateLimitKeyFunc {
|
||||
return func(c *gin.Context) string {
|
||||
if value, ok := c.Get(ContextAdminID); ok {
|
||||
if adminID, ok := value.(uint64); ok && adminID != 0 {
|
||||
return "admin:" + strconv.FormatUint(adminID, 10)
|
||||
}
|
||||
}
|
||||
if jwtManager != nil {
|
||||
tokenText, _ := extractAdminToken(c)
|
||||
if tokenText != "" {
|
||||
claims, err := jwtManager.ParseSubject(tokenText, "access", "admin")
|
||||
if err == nil && claims.UserID != 0 {
|
||||
return "admin:" + strconv.FormatUint(claims.UserID, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ipRateLimitKey(c)
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func (l *redisRateLimiter) allow(ctx context.Context, key string, now time.Time) (bool, time.Time, error) {
|
||||
|
||||
Reference in New Issue
Block a user