package middleware import ( "fmt" "net/http" "github.com/gin-gonic/gin" "laic-backend/common" "laic-backend/logger" ) // rateLimitLua 固定窗口计数:首次计数设置过期时间,返回当前窗口计数 const rateLimitLua = ` local key = KEYS[1] local ttl = tonumber(ARGV[1]) local n = redis.call('INCR', key) if n == 1 then redis.call('EXPIRE', key, ttl) end return n ` // RateLimitMiddleware 基于 Redis 固定窗口的按 IP 限流(默认 300 次/分钟) func RateLimitMiddleware(limit int64, windowSeconds int64) gin.HandlerFunc { if limit <= 0 { limit = 300 } if windowSeconds <= 0 { windowSeconds = 60 } return func(c *gin.Context) { key := fmt.Sprintf("laic:ratelimit:%s", c.ClientIP()) n, err := common.GetLuaInt64(rateLimitLua, []string{key}, windowSeconds) if err != nil { // 限流依赖 Redis,异常时放行(避免 Redis 故障拖垮全站) logger.ERROR("限流计数失败", err) c.Next() return } if n > limit { common.FailWithBusiErrorWithHttpStatus(c, http.StatusTooManyRequests, common.NewBusiError(http.StatusTooManyRequests, "请求过于频繁,请稍后再试")) return } c.Next() } }