mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-12 19:04:45 +08:00
61 lines
1.3 KiB
Go
61 lines
1.3 KiB
Go
|
|
package middleware
|
||
|
|
|
||
|
|
import (
|
||
|
|
"net/http"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/gin-gonic/gin"
|
||
|
|
"github.com/redis/go-redis/v9"
|
||
|
|
)
|
||
|
|
|
||
|
|
// RateLimiter Redis 速率限制器
|
||
|
|
type RateLimiter struct {
|
||
|
|
redis *redis.Client
|
||
|
|
prefix string
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewRateLimiter 创建速率限制器实例
|
||
|
|
func NewRateLimiter(redisClient *redis.Client) *RateLimiter {
|
||
|
|
return &RateLimiter{
|
||
|
|
redis: redisClient,
|
||
|
|
prefix: "rate_limit:",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Limit 返回速率限制中间件
|
||
|
|
// key: 限制类型标识
|
||
|
|
// limit: 时间窗口内最大请求数
|
||
|
|
// window: 时间窗口
|
||
|
|
func (r *RateLimiter) Limit(key string, limit int, window time.Duration) gin.HandlerFunc {
|
||
|
|
return func(c *gin.Context) {
|
||
|
|
ip := c.ClientIP()
|
||
|
|
redisKey := r.prefix + key + ":" + ip
|
||
|
|
|
||
|
|
ctx := c.Request.Context()
|
||
|
|
|
||
|
|
// 使用 INCR 原子操作增加计数
|
||
|
|
count, err := r.redis.Incr(ctx, redisKey).Result()
|
||
|
|
if err != nil {
|
||
|
|
// Redis 错误时放行,避免影响正常服务
|
||
|
|
c.Next()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// 首次访问时设置过期时间
|
||
|
|
if count == 1 {
|
||
|
|
r.redis.Expire(ctx, redisKey, window)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 超过限制
|
||
|
|
if count > int64(limit) {
|
||
|
|
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
|
||
|
|
"error": "rate limit exceeded",
|
||
|
|
"message": "Too many requests, please try again later",
|
||
|
|
})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
c.Next()
|
||
|
|
}
|
||
|
|
}
|