chore(lint): 修复 golangci-lint unused
- 移除 OpenAIGatewayHandler 未使用字段 - 删除并发缓存中未使用的 Redis 脚本常量 - 将仅供 unit 测试使用的 parseIntegralNumber 移入 unit build tag 文件
This commit is contained in:
@@ -28,7 +28,6 @@ type OpenAIGatewayHandler struct {
|
||||
errorPassthroughService *service.ErrorPassthroughService
|
||||
concurrencyHelper *ConcurrencyHelper
|
||||
maxAccountSwitches int
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewOpenAIGatewayHandler creates a new OpenAIGatewayHandler
|
||||
@@ -55,7 +54,6 @@ func NewOpenAIGatewayHandler(
|
||||
errorPassthroughService: errorPassthroughService,
|
||||
concurrencyHelper: NewConcurrencyHelper(concurrencyService, SSEPingFormatComment, pingInterval),
|
||||
maxAccountSwitches: maxAccountSwitches,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -147,108 +147,6 @@ var (
|
||||
return 1
|
||||
`)
|
||||
|
||||
// WARNING: Redis Cluster 不兼容 — 脚本内部拼接 key,Cluster 模式下可能路由到错误节点。
|
||||
// 调用时传递空 KEYS 数组,所有 key 在 Lua 内通过 ARGV 动态拼接,
|
||||
// 无法被 Redis Cluster 正确路由到对应 slot,仅适用于单节点或 Sentinel 模式。
|
||||
//
|
||||
// getAccountsLoadBatchScript - batch load query with expired slot cleanup
|
||||
// ARGV[1] = slot TTL (seconds)
|
||||
// ARGV[2..n] = accountID1, maxConcurrency1, accountID2, maxConcurrency2, ...
|
||||
getAccountsLoadBatchScript = redis.NewScript(`
|
||||
local result = {}
|
||||
local slotTTL = tonumber(ARGV[1])
|
||||
|
||||
-- Get current server time
|
||||
local timeResult = redis.call('TIME')
|
||||
local nowSeconds = tonumber(timeResult[1])
|
||||
local cutoffTime = nowSeconds - slotTTL
|
||||
|
||||
local i = 2
|
||||
while i <= #ARGV do
|
||||
local accountID = ARGV[i]
|
||||
local maxConcurrency = tonumber(ARGV[i + 1])
|
||||
|
||||
local slotKey = 'concurrency:account:' .. accountID
|
||||
|
||||
-- Clean up expired slots before counting
|
||||
redis.call('ZREMRANGEBYSCORE', slotKey, '-inf', cutoffTime)
|
||||
local currentConcurrency = redis.call('ZCARD', slotKey)
|
||||
|
||||
local waitKey = 'wait:account:' .. accountID
|
||||
local waitingCount = redis.call('GET', waitKey)
|
||||
if waitingCount == false then
|
||||
waitingCount = 0
|
||||
else
|
||||
waitingCount = tonumber(waitingCount)
|
||||
end
|
||||
|
||||
local loadRate = 0
|
||||
if maxConcurrency > 0 then
|
||||
loadRate = math.floor((currentConcurrency + waitingCount) * 100 / maxConcurrency)
|
||||
end
|
||||
|
||||
table.insert(result, accountID)
|
||||
table.insert(result, currentConcurrency)
|
||||
table.insert(result, waitingCount)
|
||||
table.insert(result, loadRate)
|
||||
|
||||
i = i + 2
|
||||
end
|
||||
|
||||
return result
|
||||
`)
|
||||
|
||||
// WARNING: Redis Cluster 不兼容 — 脚本内部拼接 key,Cluster 模式下可能路由到错误节点。
|
||||
// 调用时传递空 KEYS 数组,所有 key 在 Lua 内通过 ARGV 动态拼接,
|
||||
// 无法被 Redis Cluster 正确路由到对应 slot,仅适用于单节点或 Sentinel 模式。
|
||||
//
|
||||
// getUsersLoadBatchScript - batch load query for users with expired slot cleanup
|
||||
// ARGV[1] = slot TTL (seconds)
|
||||
// ARGV[2..n] = userID1, maxConcurrency1, userID2, maxConcurrency2, ...
|
||||
getUsersLoadBatchScript = redis.NewScript(`
|
||||
local result = {}
|
||||
local slotTTL = tonumber(ARGV[1])
|
||||
|
||||
-- Get current server time
|
||||
local timeResult = redis.call('TIME')
|
||||
local nowSeconds = tonumber(timeResult[1])
|
||||
local cutoffTime = nowSeconds - slotTTL
|
||||
|
||||
local i = 2
|
||||
while i <= #ARGV do
|
||||
local userID = ARGV[i]
|
||||
local maxConcurrency = tonumber(ARGV[i + 1])
|
||||
|
||||
local slotKey = 'concurrency:user:' .. userID
|
||||
|
||||
-- Clean up expired slots before counting
|
||||
redis.call('ZREMRANGEBYSCORE', slotKey, '-inf', cutoffTime)
|
||||
local currentConcurrency = redis.call('ZCARD', slotKey)
|
||||
|
||||
local waitKey = 'concurrency:wait:' .. userID
|
||||
local waitingCount = redis.call('GET', waitKey)
|
||||
if waitingCount == false then
|
||||
waitingCount = 0
|
||||
else
|
||||
waitingCount = tonumber(waitingCount)
|
||||
end
|
||||
|
||||
local loadRate = 0
|
||||
if maxConcurrency > 0 then
|
||||
loadRate = math.floor((currentConcurrency + waitingCount) * 100 / maxConcurrency)
|
||||
end
|
||||
|
||||
table.insert(result, userID)
|
||||
table.insert(result, currentConcurrency)
|
||||
table.insert(result, waitingCount)
|
||||
table.insert(result, loadRate)
|
||||
|
||||
i = i + 2
|
||||
end
|
||||
|
||||
return result
|
||||
`)
|
||||
|
||||
// cleanupExpiredSlotsScript - remove expired slots
|
||||
// KEYS[1] = concurrency:account:{accountID}
|
||||
// ARGV[1] = TTL (seconds)
|
||||
|
||||
@@ -189,45 +189,6 @@ func sliceRawFromBody(body []byte, r gjson.Result) []byte {
|
||||
return []byte(r.Raw)
|
||||
}
|
||||
|
||||
// parseIntegralNumber 将 JSON 解码后的数字安全转换为 int。
|
||||
// 仅接受“整数值”的输入,小数/NaN/Inf/越界值都会返回 false。
|
||||
func parseIntegralNumber(raw any) (int, bool) {
|
||||
switch v := raw.(type) {
|
||||
case float64:
|
||||
if math.IsNaN(v) || math.IsInf(v, 0) || v != math.Trunc(v) {
|
||||
return 0, false
|
||||
}
|
||||
if v > float64(math.MaxInt) || v < float64(math.MinInt) {
|
||||
return 0, false
|
||||
}
|
||||
return int(v), true
|
||||
case int:
|
||||
return v, true
|
||||
case int8:
|
||||
return int(v), true
|
||||
case int16:
|
||||
return int(v), true
|
||||
case int32:
|
||||
return int(v), true
|
||||
case int64:
|
||||
if v > int64(math.MaxInt) || v < int64(math.MinInt) {
|
||||
return 0, false
|
||||
}
|
||||
return int(v), true
|
||||
case json.Number:
|
||||
i64, err := v.Int64()
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
if i64 > int64(math.MaxInt) || i64 < int64(math.MinInt) {
|
||||
return 0, false
|
||||
}
|
||||
return int(i64), true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// FilterThinkingBlocks removes thinking blocks from request body
|
||||
// Returns filtered body or original body if filtering fails (fail-safe)
|
||||
// This prevents 400 errors from invalid thinking block signatures
|
||||
|
||||
51
backend/internal/service/parse_integral_number_unit.go
Normal file
51
backend/internal/service/parse_integral_number_unit.go
Normal file
@@ -0,0 +1,51 @@
|
||||
//go:build unit
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
)
|
||||
|
||||
// parseIntegralNumber 将 JSON 解码后的数字安全转换为 int。
|
||||
// 仅接受“整数值”的输入,小数/NaN/Inf/越界值都会返回 false。
|
||||
//
|
||||
// 说明:
|
||||
// - 该函数当前仅用于 unit 测试中的 map-based 解析逻辑验证,因此放在 unit build tag 下,
|
||||
// 避免在默认构建中触发 unused lint。
|
||||
func parseIntegralNumber(raw any) (int, bool) {
|
||||
switch v := raw.(type) {
|
||||
case float64:
|
||||
if math.IsNaN(v) || math.IsInf(v, 0) || v != math.Trunc(v) {
|
||||
return 0, false
|
||||
}
|
||||
if v > float64(math.MaxInt) || v < float64(math.MinInt) {
|
||||
return 0, false
|
||||
}
|
||||
return int(v), true
|
||||
case int:
|
||||
return v, true
|
||||
case int8:
|
||||
return int(v), true
|
||||
case int16:
|
||||
return int(v), true
|
||||
case int32:
|
||||
return int(v), true
|
||||
case int64:
|
||||
if v > int64(math.MaxInt) || v < int64(math.MinInt) {
|
||||
return 0, false
|
||||
}
|
||||
return int(v), true
|
||||
case json.Number:
|
||||
i64, err := v.Int64()
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
if i64 > int64(math.MaxInt) || i64 < int64(math.MinInt) {
|
||||
return 0, false
|
||||
}
|
||||
return int(i64), true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user