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
|
errorPassthroughService *service.ErrorPassthroughService
|
||||||
concurrencyHelper *ConcurrencyHelper
|
concurrencyHelper *ConcurrencyHelper
|
||||||
maxAccountSwitches int
|
maxAccountSwitches int
|
||||||
cfg *config.Config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOpenAIGatewayHandler creates a new OpenAIGatewayHandler
|
// NewOpenAIGatewayHandler creates a new OpenAIGatewayHandler
|
||||||
@@ -55,7 +54,6 @@ func NewOpenAIGatewayHandler(
|
|||||||
errorPassthroughService: errorPassthroughService,
|
errorPassthroughService: errorPassthroughService,
|
||||||
concurrencyHelper: NewConcurrencyHelper(concurrencyService, SSEPingFormatComment, pingInterval),
|
concurrencyHelper: NewConcurrencyHelper(concurrencyService, SSEPingFormatComment, pingInterval),
|
||||||
maxAccountSwitches: maxAccountSwitches,
|
maxAccountSwitches: maxAccountSwitches,
|
||||||
cfg: cfg,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,108 +147,6 @@ var (
|
|||||||
return 1
|
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
|
// cleanupExpiredSlotsScript - remove expired slots
|
||||||
// KEYS[1] = concurrency:account:{accountID}
|
// KEYS[1] = concurrency:account:{accountID}
|
||||||
// ARGV[1] = TTL (seconds)
|
// ARGV[1] = TTL (seconds)
|
||||||
|
|||||||
@@ -189,45 +189,6 @@ func sliceRawFromBody(body []byte, r gjson.Result) []byte {
|
|||||||
return []byte(r.Raw)
|
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
|
// FilterThinkingBlocks removes thinking blocks from request body
|
||||||
// Returns filtered body or original body if filtering fails (fail-safe)
|
// Returns filtered body or original body if filtering fails (fail-safe)
|
||||||
// This prevents 400 errors from invalid thinking block signatures
|
// 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