Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m40s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 42m16s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
231 lines
6.8 KiB
Go
231 lines
6.8 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/common/limiter"
|
|
"github.com/QuantumNous/new-api/constant"
|
|
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
|
"github.com/QuantumNous/new-api/setting"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-redis/redis/v8"
|
|
)
|
|
|
|
const (
|
|
ModelRequestRateLimitCountMark = "MRRL"
|
|
ModelRequestRateLimitSuccessCountMark = "MRRLS"
|
|
modelRateLimitTimeFormat = "2006-01-02T15:04:05.000Z"
|
|
)
|
|
|
|
// 检查Redis中的请求限制
|
|
func checkRedisRateLimit(ctx context.Context, rdb *redis.Client, key string, maxCount int, duration int64) (bool, error) {
|
|
// 如果maxCount为0,表示不限制
|
|
if maxCount == 0 {
|
|
return true, nil
|
|
}
|
|
|
|
// 获取当前计数
|
|
length, err := rdb.LLen(ctx, key).Result()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// 如果未达到限制,允许请求
|
|
if length < int64(maxCount) {
|
|
return true, nil
|
|
}
|
|
|
|
// 检查时间窗口
|
|
oldTimeStr, _ := rdb.LIndex(ctx, key, -1).Result()
|
|
oldTime, err := time.Parse(modelRateLimitTimeFormat, oldTimeStr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
nowTimeStr := time.Now().UTC().Format(modelRateLimitTimeFormat)
|
|
nowTime, err := time.Parse(modelRateLimitTimeFormat, nowTimeStr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// 如果在时间窗口内已达到限制,拒绝请求
|
|
subTime := nowTime.Sub(oldTime).Seconds()
|
|
if int64(subTime) < duration {
|
|
rdb.Expire(ctx, key, time.Duration(setting.ModelRequestRateLimitDurationMinutes)*time.Minute)
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// 记录Redis请求
|
|
func recordRedisRequest(ctx context.Context, rdb *redis.Client, key string, maxCount int) {
|
|
// 如果maxCount为0,不记录请求
|
|
if maxCount == 0 {
|
|
return
|
|
}
|
|
|
|
now := time.Now().UTC().Format(modelRateLimitTimeFormat)
|
|
rdb.LPush(ctx, key, now)
|
|
rdb.LTrim(ctx, key, 0, int64(maxCount-1))
|
|
rdb.Expire(ctx, key, time.Duration(setting.ModelRequestRateLimitDurationMinutes)*time.Minute)
|
|
}
|
|
|
|
// Redis限流处理器
|
|
func redisRateLimitHandler(duration int64, totalMaxCount, successMaxCount int) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
userId := strconv.Itoa(c.GetInt("id"))
|
|
ctx := context.Background()
|
|
rdb := common.RDB
|
|
|
|
// 1. 检查成功请求数限制
|
|
successKey := fmt.Sprintf("rateLimit:%s:%s", ModelRequestRateLimitSuccessCountMark, userId)
|
|
allowed, err := checkRedisRateLimit(ctx, rdb, successKey, successMaxCount, duration)
|
|
if err != nil {
|
|
fmt.Println("检查成功请求数限制失败:", err.Error())
|
|
abortWithOpenAiMessage(c, http.StatusInternalServerError, "rate_limit_check_failed")
|
|
return
|
|
}
|
|
if !allowed {
|
|
abortWithOpenAiMessage(c, http.StatusTooManyRequests, fmt.Sprintf("您已达到请求数限制:%d分钟内最多请求%d次", setting.ModelRequestRateLimitDurationMinutes, successMaxCount))
|
|
return
|
|
}
|
|
|
|
//2.检查总请求数限制并记录总请求(当totalMaxCount为0时会自动跳过,使用令牌桶限流器
|
|
if totalMaxCount > 0 {
|
|
totalKey := fmt.Sprintf("rateLimit:%s", userId)
|
|
// 初始化
|
|
tb := limiter.New(ctx, rdb)
|
|
allowed, err = tb.Allow(
|
|
ctx,
|
|
totalKey,
|
|
limiter.WithCapacity(rateLimitCapacity(totalMaxCount, duration)),
|
|
limiter.WithRate(int64(totalMaxCount)),
|
|
limiter.WithRequested(duration),
|
|
)
|
|
|
|
if err != nil {
|
|
fmt.Println("检查总请求数限制失败:", err.Error())
|
|
abortWithOpenAiMessage(c, http.StatusInternalServerError, "rate_limit_check_failed")
|
|
return
|
|
}
|
|
|
|
if !allowed {
|
|
abortWithOpenAiMessage(c, http.StatusTooManyRequests, fmt.Sprintf("您已达到总请求数限制:%d分钟内最多请求%d次,包括失败次数,请检查您的请求是否正确", setting.ModelRequestRateLimitDurationMinutes, totalMaxCount))
|
|
return
|
|
}
|
|
}
|
|
|
|
// 4. 处理请求
|
|
c.Next()
|
|
|
|
// 5. 如果请求成功,记录成功请求
|
|
if modelRequestSucceeded(c) {
|
|
recordRedisRequest(ctx, rdb, successKey, successMaxCount)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 内存限流处理器
|
|
func memoryRateLimitHandler(duration int64, totalMaxCount, successMaxCount int) gin.HandlerFunc {
|
|
inMemoryRateLimiter.Init(time.Duration(setting.ModelRequestRateLimitDurationMinutes) * time.Minute)
|
|
|
|
return func(c *gin.Context) {
|
|
userId := strconv.Itoa(c.GetInt("id"))
|
|
totalKey := ModelRequestRateLimitCountMark + userId
|
|
successKey := ModelRequestRateLimitSuccessCountMark + userId
|
|
|
|
// 1. 检查总请求数限制(当totalMaxCount为0时跳过)
|
|
if totalMaxCount > 0 && !inMemoryRateLimiter.Request(totalKey, totalMaxCount, duration) {
|
|
c.Status(http.StatusTooManyRequests)
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
var reservation *common.RateLimitReservation
|
|
if successMaxCount > 0 {
|
|
reservation = inMemoryRateLimiter.Reserve(successKey, successMaxCount, duration)
|
|
if reservation == nil {
|
|
c.AbortWithStatus(http.StatusTooManyRequests)
|
|
return
|
|
}
|
|
defer reservation.Complete(false)
|
|
}
|
|
|
|
// 3. 处理请求
|
|
c.Next()
|
|
|
|
// 4. 如果请求成功,记录到实际的成功请求计数中
|
|
reservation.Complete(modelRequestSucceeded(c))
|
|
}
|
|
}
|
|
|
|
func modelRequestSucceeded(c *gin.Context) bool {
|
|
status, _ := common.GetContextKeyType[*relaycommon.StreamStatus](c, constant.ContextKeyResponseStreamStatus)
|
|
return c.Writer.Status() < 400 && !status.ResponseFailed()
|
|
}
|
|
|
|
// ModelRequestRateLimit 模型请求限流中间件
|
|
func ModelRequestRateLimit() func(c *gin.Context) {
|
|
return func(c *gin.Context) {
|
|
// 在每个请求时检查是否启用限流
|
|
if !setting.ModelRequestRateLimitEnabled {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// 计算限流参数
|
|
duration := rateLimitDurationSeconds(setting.ModelRequestRateLimitDurationMinutes)
|
|
totalMaxCount := setting.ModelRequestRateLimitCount
|
|
successMaxCount := setting.ModelRequestRateLimitSuccessCount
|
|
|
|
// 获取分组
|
|
group := common.GetContextKeyString(c, constant.ContextKeyTokenGroup)
|
|
if group == "" {
|
|
group = common.GetContextKeyString(c, constant.ContextKeyUserGroup)
|
|
}
|
|
|
|
//获取分组的限流配置
|
|
groupTotalCount, groupSuccessCount, found := setting.GetGroupRateLimit(group)
|
|
if found {
|
|
totalMaxCount = groupTotalCount
|
|
successMaxCount = groupSuccessCount
|
|
}
|
|
|
|
// 根据存储类型选择并执行限流处理器
|
|
if common.RedisEnabled {
|
|
redisRateLimitHandler(duration, totalMaxCount, successMaxCount)(c)
|
|
} else {
|
|
memoryRateLimitHandler(duration, totalMaxCount, successMaxCount)(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func rateLimitDurationSeconds(durationMinutes int) int64 {
|
|
if durationMinutes <= 0 {
|
|
return 0
|
|
}
|
|
minutes := int64(durationMinutes)
|
|
if minutes > math.MaxInt64/60 {
|
|
return math.MaxInt64
|
|
}
|
|
return minutes * 60
|
|
}
|
|
|
|
func rateLimitCapacity(count int, durationSeconds int64) int64 {
|
|
if count <= 0 || durationSeconds <= 0 {
|
|
return 0
|
|
}
|
|
c := int64(count)
|
|
if c > math.MaxInt64/durationSeconds {
|
|
return math.MaxInt64
|
|
}
|
|
return c * durationSeconds
|
|
}
|