Files
toolbox/server/internal/middleware/ratelimit.go
T
2026-08-29 23:34:01 +08:00

65 lines
1.4 KiB
Go

package middleware
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/ulule/limiter/v3"
"github.com/ulule/limiter/v3/drivers/store/memory"
)
// RateLimitConfig 限流配置
type RateLimitConfig struct {
GlobalRPS float64
PerIPRPS float64
PerIPBurst int
}
// RateLimiter 双维度限流:全局令牌桶 + 单 IP 令牌桶
// 开发/测试环境可用内存存储,生产环境建议换 Redis 存储
func RateLimiter(cfg RateLimitConfig) gin.HandlerFunc {
store := memory.NewStore()
// 全局限流
globalRate := limiter.Rate{
Period: time.Second,
Limit: int64(cfg.GlobalRPS),
}
globalLimiter := limiter.New(store, globalRate)
// 单 IP 限流
perIPRate := limiter.Rate{
Period: time.Second,
Limit: int64(cfg.PerIPRPS),
}
perIPLimiter := limiter.New(store, perIPRate)
return func(c *gin.Context) {
// 1. 检查全局限流
globalCtx, err := globalLimiter.Get(c.Request.Context(), "global")
if err != nil || globalCtx.Reached {
c.JSON(http.StatusTooManyRequests, gin.H{
"code": 4290,
"message": "server busy, please retry later",
})
c.Abort()
return
}
// 2. 检查单 IP 限流
ip := c.ClientIP()
ipCtx, err := perIPLimiter.Get(c.Request.Context(), "ip:"+ip)
if err != nil || ipCtx.Reached {
c.JSON(http.StatusTooManyRequests, gin.H{
"code": 4291,
"message": "too many requests from this IP",
})
c.Abort()
return
}
c.Next()
}
}