This commit is contained in:
SunSeekerX
2025-12-31 02:17:10 +08:00
parent 584fa8c9c1
commit b4233033a6
9 changed files with 50 additions and 42 deletions

View File

@@ -93,7 +93,13 @@ async function migrate() {
console.log('\n4. 迁移 usage:model:hourly 索引...')
cursor = '0'
do {
const [newCursor, keys] = await redis.scan(cursor, 'MATCH', 'usage:model:hourly:*', 'COUNT', 500)
const [newCursor, keys] = await redis.scan(
cursor,
'MATCH',
'usage:model:hourly:*',
'COUNT',
500
)
cursor = newCursor
const pipeline = redis.pipeline()

View File

@@ -289,17 +289,13 @@ router.get('/api-keys', authenticateAdmin, async (req, res) => {
}
// 为每个API Key添加owner的displayName批量获取优化
const userIdsToFetch = [
...new Set(result.items.filter((k) => k.userId).map((k) => k.userId))
]
const userIdsToFetch = [...new Set(result.items.filter((k) => k.userId).map((k) => k.userId))]
const userMap = new Map()
if (userIdsToFetch.length > 0) {
// 批量获取用户信息
const users = await Promise.all(
userIdsToFetch.map((id) =>
userService.getUserById(id, false).catch(() => null)
)
userIdsToFetch.map((id) => userService.getUserById(id, false).catch(() => null))
)
userIdsToFetch.forEach((id, i) => {
if (users[i]) userMap.set(id, users[i])

View File

@@ -5,7 +5,11 @@
const redis = require('../models/redis')
const logger = require('../utils/logger')
const { getCachedConfig, setCachedConfig, deleteCachedConfig } = require('../utils/performanceOptimizer')
const {
getCachedConfig,
setCachedConfig,
deleteCachedConfig
} = require('../utils/performanceOptimizer')
class ClaudeCodeHeadersService {
constructor() {

View File

@@ -24,9 +24,7 @@ const {
// structuredClone polyfill for Node < 17
const safeClone =
typeof structuredClone === 'function'
? structuredClone
: (obj) => JSON.parse(JSON.stringify(obj))
typeof structuredClone === 'function' ? structuredClone : (obj) => JSON.parse(JSON.stringify(obj))
class ClaudeRelayService {
constructor() {

View File

@@ -94,7 +94,9 @@ class CostInitService {
}
}
logger.info(`💰 Found ${apiKeyIds.length} active API Keys to process (filtered ${allKeyIds.length - apiKeyIds.length} deleted)`)
logger.info(
`💰 Found ${apiKeyIds.length} active API Keys to process (filtered ${allKeyIds.length - apiKeyIds.length} deleted)`
)
let processedCount = 0
let errorCount = 0

View File

@@ -30,10 +30,13 @@ class DroidAccountService {
this._encryptor = createEncryptor('droid-account-salt')
// 🧹 定期清理缓存每10分钟
setInterval(() => {
this._encryptor.clearCache()
logger.info('🧹 Droid decrypt cache cleanup completed', this._encryptor.getStats())
}, 10 * 60 * 1000)
setInterval(
() => {
this._encryptor.clearCache()
logger.info('🧹 Droid decrypt cache cleanup completed', this._encryptor.getStats())
},
10 * 60 * 1000
)
this.supportedEndpointTypes = new Set(['anthropic', 'openai', 'comm'])
}

View File

@@ -2,7 +2,12 @@ const droidAccountService = require('./droidAccountService')
const accountGroupService = require('./accountGroupService')
const redis = require('../models/redis')
const logger = require('../utils/logger')
const { isTruthy, isAccountHealthy, sortAccountsByPriority, normalizeEndpointType } = require('../utils/commonHelper')
const {
isTruthy,
isAccountHealthy,
sortAccountsByPriority,
normalizeEndpointType
} = require('../utils/commonHelper')
class DroidScheduler {
constructor() {

View File

@@ -26,11 +26,7 @@ class UnifiedGeminiScheduler {
if (apiKeyData.geminiAccountId.startsWith('api:')) {
const accountId = apiKeyData.geminiAccountId.replace('api:', '')
const boundAccount = await geminiApiAccountService.getAccount(accountId)
if (
boundAccount &&
isActive(boundAccount.isActive) &&
boundAccount.status !== 'error'
) {
if (boundAccount && isActive(boundAccount.isActive) && boundAccount.status !== 'error') {
logger.info(
`🎯 Using bound Gemini-API account: ${boundAccount.name} (${accountId}) for API key ${apiKeyData.name}`
)
@@ -63,11 +59,7 @@ class UnifiedGeminiScheduler {
// 普通 Gemini OAuth 专属账户
else {
const boundAccount = await geminiAccountService.getAccount(apiKeyData.geminiAccountId)
if (
boundAccount &&
isActive(boundAccount.isActive) &&
boundAccount.status !== 'error'
) {
if (boundAccount && isActive(boundAccount.isActive) && boundAccount.status !== 'error') {
logger.info(
`🎯 Using bound dedicated Gemini account: ${boundAccount.name} (${apiKeyData.geminiAccountId}) for API key ${apiKeyData.name}`
)
@@ -183,11 +175,7 @@ class UnifiedGeminiScheduler {
if (apiKeyData.geminiAccountId.startsWith('api:')) {
const accountId = apiKeyData.geminiAccountId.replace('api:', '')
const boundAccount = await geminiApiAccountService.getAccount(accountId)
if (
boundAccount &&
isActive(boundAccount.isActive) &&
boundAccount.status !== 'error'
) {
if (boundAccount && isActive(boundAccount.isActive) && boundAccount.status !== 'error') {
const isRateLimited = await this.isAccountRateLimited(accountId)
if (!isRateLimited) {
// 检查模型支持
@@ -234,11 +222,7 @@ class UnifiedGeminiScheduler {
// 普通 Gemini OAuth 账户
else if (!apiKeyData.geminiAccountId.startsWith('group:')) {
const boundAccount = await geminiAccountService.getAccount(apiKeyData.geminiAccountId)
if (
boundAccount &&
isActive(boundAccount.isActive) &&
boundAccount.status !== 'error'
) {
if (boundAccount && isActive(boundAccount.isActive) && boundAccount.status !== 'error') {
const isRateLimited = await this.isAccountRateLimited(boundAccount.id)
if (!isRateLimited) {
// 检查模型支持

View File

@@ -84,7 +84,10 @@ const getDecryptCacheStats = defaultEncryptor.getStats
// ============================================
// 转换为布尔值(宽松模式)
const toBoolean = (value) => value === true || value === 'true' || (typeof value === 'string' && value.toLowerCase() === 'true')
const toBoolean = (value) =>
value === true ||
value === 'true' ||
(typeof value === 'string' && value.toLowerCase() === 'true')
// 检查是否为真值null/undefined 返回 false
const isTruthy = (value) => value != null && toBoolean(value)
@@ -110,7 +113,11 @@ const isAccountHealthy = (account) => {
// 安全解析 JSON
const safeParseJson = (value, fallback = null) => {
if (!value || typeof value !== 'string') return fallback
try { return JSON.parse(value) } catch { return fallback }
try {
return JSON.parse(value)
} catch {
return fallback
}
}
// 安全解析 JSON 为对象
@@ -134,7 +141,10 @@ const normalizeModelName = (model) => {
if (!model || model === 'unknown') return model
// Bedrock 模型: us-east-1.anthropic.claude-3-5-sonnet-v1:0
if (model.includes('.anthropic.') || model.includes('.claude')) {
return model.replace(/^[a-z0-9-]+\./, '').replace('anthropic.', '').replace(/-v\d+:\d+$/, '')
return model
.replace(/^[a-z0-9-]+\./, '')
.replace('anthropic.', '')
.replace(/-v\d+:\d+$/, '')
}
return model.replace(/-v\d+:\d+$|:latest$/, '')
}
@@ -151,7 +161,7 @@ const isModelInMapping = (modelMapping, requestedModel) => {
if (!modelMapping || Object.keys(modelMapping).length === 0) return true
if (Object.prototype.hasOwnProperty.call(modelMapping, requestedModel)) return true
const lower = requestedModel.toLowerCase()
return Object.keys(modelMapping).some(k => k.toLowerCase() === lower)
return Object.keys(modelMapping).some((k) => k.toLowerCase() === lower)
}
// 获取映射后的模型名称
@@ -192,7 +202,7 @@ const composeStickySessionKey = (prefix, sessionHash, apiKeyId = null) => {
// 过滤可用账户(激活 + 健康 + 可调度)
const filterAvailableAccounts = (accounts) => {
return accounts.filter(acc => acc && isAccountHealthy(acc) && isSchedulable(acc.schedulable))
return accounts.filter((acc) => acc && isAccountHealthy(acc) && isSchedulable(acc.schedulable))
}
// ============================================