From 90023d15519dd9f61364980833e1ff345d0c9132 Mon Sep 17 00:00:00 2001 From: SunSeekerX Date: Sat, 3 Jan 2026 23:24:59 +0800 Subject: [PATCH] =?UTF-8?q?Revert:=20=E6=92=A4=E9=94=80=20584fa8c=20?= =?UTF-8?q?=E4=B9=8B=E5=90=8E=E7=9A=84=E6=89=80=E6=9C=89=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 1 - .github/FUNDING.yml | 24 +-- scripts/migrate-usage-index.js | 24 +-- src/models/redis.js | 124 +++-------- src/routes/admin/apiKeys.js | 15 +- src/routes/admin/dashboard.js | 8 +- src/routes/admin/droidAccounts.js | 8 +- src/routes/admin/geminiApiAccounts.js | 4 +- src/routes/admin/openaiResponsesAccounts.js | 4 +- src/routes/admin/usageStats.js | 200 +++++------------- src/routes/apiStats.js | 4 +- src/services/accountGroupService.js | 12 +- src/services/apiKeyIndexService.js | 24 +-- src/services/apiKeyService.js | 4 +- src/services/bedrockAccountService.js | 2 +- src/services/ccrAccountService.js | 2 +- src/services/claudeCodeHeadersService.js | 6 +- src/services/claudeRelayService.js | 4 +- src/services/costInitService.js | 20 +- src/services/droidAccountService.js | 13 +- src/services/droidScheduler.js | 15 +- src/services/geminiAccountService.js | 6 +- src/services/openaiAccountService.js | 2 +- src/services/openaiResponsesAccountService.js | 14 +- src/services/unifiedGeminiScheduler.js | 24 ++- src/services/unifiedOpenAIScheduler.js | 6 +- src/utils/commonHelper.js | 137 ++++-------- 27 files changed, 219 insertions(+), 488 deletions(-) delete mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 6313b56c..00000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index dfd68228..5217c07c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,12 +1,12 @@ -# These are supported funding model platforms - -github: # Your GitHub username for GitHub Sponsors -patreon: # Replace with your Patreon username if you have one -open_collective: # Replace with your Open Collective username if you have one -ko_fi: # Replace with your Ko-fi username if you have one -tidelift: # Replace with your Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with your Community Bridge project-name -liberapay: # Replace with your Liberapay username -issuehunt: # Replace with your IssueHunt username -otechie: # Replace with your Otechie username -custom: ['https://afdian.com/a/claude-relay-service'] # Your custom donation link (Afdian) +# These are supported funding model platforms + +github: # Your GitHub username for GitHub Sponsors +patreon: # Replace with your Patreon username if you have one +open_collective: # Replace with your Open Collective username if you have one +ko_fi: # Replace with your Ko-fi username if you have one +tidelift: # Replace with your Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with your Community Bridge project-name +liberapay: # Replace with your Liberapay username +issuehunt: # Replace with your IssueHunt username +otechie: # Replace with your Otechie username +custom: ['https://afdian.com/a/claude-relay-service'] # Your custom donation link (Afdian) diff --git a/scripts/migrate-usage-index.js b/scripts/migrate-usage-index.js index bcbb1629..77c2db0c 100644 --- a/scripts/migrate-usage-index.js +++ b/scripts/migrate-usage-index.js @@ -41,9 +41,7 @@ async function migrate() { stats.dailyIndex++ } } - if (keys.length > 0) { - await pipeline.exec() - } + if (keys.length > 0) await pipeline.exec() } while (cursor !== '0') console.log(` 已处理 ${stats.dailyIndex} 条`) @@ -65,9 +63,7 @@ async function migrate() { stats.hourlyIndex++ } } - if (keys.length > 0) { - await pipeline.exec() - } + if (keys.length > 0) await pipeline.exec() } while (cursor !== '0') console.log(` 已处理 ${stats.hourlyIndex} 条`) @@ -89,9 +85,7 @@ async function migrate() { stats.modelDailyIndex++ } } - if (keys.length > 0) { - await pipeline.exec() - } + if (keys.length > 0) await pipeline.exec() } while (cursor !== '0') console.log(` 已处理 ${stats.modelDailyIndex} 条`) @@ -99,13 +93,7 @@ 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() @@ -119,9 +107,7 @@ async function migrate() { stats.modelHourlyIndex++ } } - if (keys.length > 0) { - await pipeline.exec() - } + if (keys.length > 0) await pipeline.exec() } while (cursor !== '0') console.log(` 已处理 ${stats.modelHourlyIndex} 条`) diff --git a/src/models/redis.js b/src/models/redis.js index 8ea85a13..debfbc7c 100644 --- a/src/models/redis.js +++ b/src/models/redis.js @@ -155,9 +155,7 @@ class RedisClient { stats.daily++ } } - if (keys.length > 0) { - await pipeline.exec() - } + if (keys.length > 0) await pipeline.exec() } while (cursor !== '0') // 迁移 usage:hourly @@ -180,9 +178,7 @@ class RedisClient { stats.hourly++ } } - if (keys.length > 0) { - await pipeline.exec() - } + if (keys.length > 0) await pipeline.exec() } while (cursor !== '0') // 迁移 usage:model:daily @@ -205,9 +201,7 @@ class RedisClient { stats.modelDaily++ } } - if (keys.length > 0) { - await pipeline.exec() - } + if (keys.length > 0) await pipeline.exec() } while (cursor !== '0') // 迁移 usage:model:hourly @@ -230,9 +224,7 @@ class RedisClient { stats.modelHourly++ } } - if (keys.length > 0) { - await pipeline.exec() - } + if (keys.length > 0) await pipeline.exec() } while (cursor !== '0') // 迁移 usage:keymodel:daily (usage:{keyId}:model:daily:{model}:{date}) @@ -257,9 +249,7 @@ class RedisClient { stats.keymodelDaily = (stats.keymodelDaily || 0) + 1 } } - if (keys.length > 0) { - await pipeline.exec() - } + if (keys.length > 0) await pipeline.exec() } while (cursor !== '0') // 迁移 usage:keymodel:hourly (usage:{keyId}:model:hourly:{model}:{hour}) @@ -284,9 +274,7 @@ class RedisClient { stats.keymodelHourly = (stats.keymodelHourly || 0) + 1 } } - if (keys.length > 0) { - await pipeline.exec() - } + if (keys.length > 0) await pipeline.exec() } while (cursor !== '0') // 标记迁移完成 @@ -398,13 +386,9 @@ class RedisClient { for (const key of keys) { // 只接受 apikey: 形态,排除索引 key - if (excludePrefixes.some((prefix) => key.startsWith(prefix))) { - continue - } + if (excludePrefixes.some((prefix) => key.startsWith(prefix))) continue // 确保是 apikey: 格式(只有一个冒号) - if (key.split(':').length !== 2) { - continue - } + if (key.split(':').length !== 2) continue keyIds.add(key.replace('apikey:', '')) } } while (cursor !== '0') @@ -464,22 +448,14 @@ class RedisClient { } const results = await pipeline.exec() - if (!results) { - return [] - } + if (!results) return [] for (const result of results) { - if (!result) { - continue - } + if (!result) continue const [err, values] = result - if (err || !values) { - continue - } + if (err || !values) continue const [tags, isDeleted] = values - if (isDeleted === 'true' || !tags) { - continue - } + if (isDeleted === 'true' || !tags) continue try { const parsed = JSON.parse(tags) @@ -506,9 +482,7 @@ class RedisClient { cursor = newCursor const validKeys = keys.filter((k) => k !== 'apikey:hash_map' && k.split(':').length === 2) - if (validKeys.length === 0) { - continue - } + if (validKeys.length === 0) continue const pipeline = this.client.pipeline() for (const key of validKeys) { @@ -516,22 +490,14 @@ class RedisClient { } const results = await pipeline.exec() - if (!results) { - continue - } + if (!results) continue for (const result of results) { - if (!result) { - continue - } + if (!result) continue const [err, values] = result - if (err || !values) { - continue - } + if (err || !values) continue const [tags, isDeleted] = values - if (isDeleted === 'true' || !tags) { - continue - } + if (isDeleted === 'true' || !tags) continue try { const parsed = JSON.parse(tags) @@ -1606,15 +1572,11 @@ class RedisClient { const entriesByAccount = new Map() for (const entry of allEntries) { const colonIndex = entry.indexOf(':') - if (colonIndex === -1) { - continue - } + if (colonIndex === -1) continue const accountId = entry.substring(0, colonIndex) const model = entry.substring(colonIndex + 1) if (accountIdSet.has(accountId)) { - if (!entriesByAccount.has(accountId)) { - entriesByAccount.set(accountId, []) - } + if (!entriesByAccount.has(accountId)) entriesByAccount.set(accountId, []) entriesByAccount.get(accountId).push(model) } } @@ -1690,9 +1652,7 @@ class RedisClient { for (let i = 0; i < modelKeys.length; i++) { const key = modelKeys[i] const [err, modelUsage] = results[i] - if (err || !modelUsage) { - continue - } + if (err || !modelUsage) continue const parts = key.split(':') const model = parts[4] @@ -1937,9 +1897,7 @@ class RedisClient { 'claude:account:*', /^claude:account:(.+)$/ ) - if (accountIds.length === 0) { - return [] - } + if (accountIds.length === 0) return [] const keys = accountIds.map((id) => `claude:account:${id}`) const pipeline = this.client.pipeline() @@ -1980,9 +1938,7 @@ class RedisClient { 'droid:account:*', /^droid:account:(.+)$/ ) - if (accountIds.length === 0) { - return [] - } + if (accountIds.length === 0) return [] const keys = accountIds.map((id) => `droid:account:${id}`) const pipeline = this.client.pipeline() @@ -2027,9 +1983,7 @@ class RedisClient { 'openai:account:*', /^openai:account:(.+)$/ ) - if (accountIds.length === 0) { - return [] - } + if (accountIds.length === 0) return [] const keys = accountIds.map((id) => `openai:account:${id}`) const pipeline = this.client.pipeline() @@ -2148,18 +2102,14 @@ class RedisClient { // 🔍 通过索引获取 key 列表(替代 SCAN) async getKeysByIndex(indexKey, keyPattern) { const members = await this.client.smembers(indexKey) - if (!members || members.length === 0) { - return [] - } + if (!members || members.length === 0) return [] return members.map((id) => keyPattern.replace('{id}', id)) } // 🔍 批量通过索引获取数据 async getDataByIndex(indexKey, keyPattern) { const keys = await this.getKeysByIndex(indexKey, keyPattern) - if (keys.length === 0) { - return [] - } + if (keys.length === 0) return [] return await this.batchHgetallChunked(keys) } @@ -4308,12 +4258,8 @@ redisClient.batchGetApiKeyStats = async function (keyIds) { * @returns {Promise} 每个 key 对应的数据,失败的返回 null */ redisClient.batchHgetallChunked = async function (keys, chunkSize = 500) { - if (!keys || keys.length === 0) { - return [] - } - if (keys.length <= chunkSize) { - return this.batchHgetall(keys) - } + if (!keys || keys.length === 0) return [] + if (keys.length <= chunkSize) return this.batchHgetall(keys) const results = [] for (let i = 0; i < keys.length; i += chunkSize) { @@ -4331,9 +4277,7 @@ redisClient.batchHgetallChunked = async function (keys, chunkSize = 500) { * @returns {Promise<(string|null)[]>} 每个 key 对应的值 */ redisClient.batchGetChunked = async function (keys, chunkSize = 500) { - if (!keys || keys.length === 0) { - return [] - } + if (!keys || keys.length === 0) return [] const client = this.getClientSafe() if (keys.length <= chunkSize) { @@ -4372,15 +4316,11 @@ redisClient.scanAndProcess = async function (pattern, processor, options = {}) { const processedKeys = new Set() // 全程去重 const processBatch = async (keys) => { - if (keys.length === 0) { - return - } + if (keys.length === 0) return // 过滤已处理的 key const uniqueKeys = keys.filter((k) => !processedKeys.has(k)) - if (uniqueKeys.length === 0) { - return - } + if (uniqueKeys.length === 0) return uniqueKeys.forEach((k) => processedKeys.add(k)) @@ -4447,9 +4387,7 @@ redisClient.scanAndGetAllChunked = async function (pattern, options = {}) { * @returns {Promise} 删除的 key 数量 */ redisClient.batchDelChunked = async function (keys, chunkSize = 500) { - if (!keys || keys.length === 0) { - return 0 - } + if (!keys || keys.length === 0) return 0 const client = this.getClientSafe() let deleted = 0 diff --git a/src/routes/admin/apiKeys.js b/src/routes/admin/apiKeys.js index 5d67e0b7..4a74cfda 100644 --- a/src/routes/admin/apiKeys.js +++ b/src/routes/admin/apiKeys.js @@ -79,8 +79,7 @@ router.get('/api-keys/:keyId/cost-debug', authenticateAdmin, async (req, res) => const costStats = await redis.getCostStats(keyId) const dailyCost = await redis.getDailyCost(keyId) const today = redis.getDateStringInTimezone() - // eslint-disable-next-line no-unused-vars - const _client = redis.getClientSafe() + const client = redis.getClientSafe() // 获取所有相关的Redis键 const costKeys = await redis.scanKeys(`usage:cost:*:${keyId}:*`) @@ -290,18 +289,20 @@ 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]) - } + if (users[i]) userMap.set(id, users[i]) }) } diff --git a/src/routes/admin/dashboard.js b/src/routes/admin/dashboard.js index 82d7184e..21e84e6c 100644 --- a/src/routes/admin/dashboard.js +++ b/src/routes/admin/dashboard.js @@ -6,13 +6,13 @@ const bedrockAccountService = require('../../services/bedrockAccountService') const ccrAccountService = require('../../services/ccrAccountService') const geminiAccountService = require('../../services/geminiAccountService') const droidAccountService = require('../../services/droidAccountService') -// const openaiAccountService = require('../../services/openaiAccountService') // TODO: 未来用于OpenAI账户统计 +const openaiAccountService = require('../../services/openaiAccountService') const openaiResponsesAccountService = require('../../services/openaiResponsesAccountService') const redis = require('../../models/redis') const { authenticateAdmin } = require('../../middleware/auth') const logger = require('../../utils/logger') const CostCalculator = require('../../utils/costCalculator') -// const pricingService = require('../../services/pricingService') // TODO: 未来用于成本计算 +const pricingService = require('../../services/pricingService') const config = require('../../../config/config') const router = express.Router() @@ -144,9 +144,7 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => { totalCacheReadTokensUsed += usage.cacheReadTokens || 0 totalAllTokensUsed += usage.allTokens || 0 } - if (key.isActive) { - activeApiKeys++ - } + if (key.isActive) activeApiKeys++ } // 各平台账户统计(单次遍历) diff --git a/src/routes/admin/droidAccounts.js b/src/routes/admin/droidAccounts.js index 90a44550..ba045de2 100644 --- a/src/routes/admin/droidAccounts.js +++ b/src/routes/admin/droidAccounts.js @@ -157,9 +157,7 @@ router.get('/droid-accounts', authenticateAdmin, async (req, res) => { const groupToAccountIds = new Map() for (const [accountId, groups] of allGroupInfosMap) { for (const group of groups) { - if (!groupToAccountIds.has(group.id)) { - groupToAccountIds.set(group.id, []) - } + if (!groupToAccountIds.has(group.id)) groupToAccountIds.set(group.id, []) groupToAccountIds.get(group.id).push(accountId) } } @@ -169,9 +167,7 @@ router.get('/droid-accounts', authenticateAdmin, async (req, res) => { const groupBindingCount = new Map() for (const key of allApiKeys) { const binding = key.droidAccountId - if (!binding) { - continue - } + if (!binding) continue if (binding.startsWith('group:')) { const groupId = binding.substring('group:'.length) groupBindingCount.set(groupId, (groupBindingCount.get(groupId) || 0) + 1) diff --git a/src/routes/admin/geminiApiAccounts.js b/src/routes/admin/geminiApiAccounts.js index 9c773de6..598adc91 100644 --- a/src/routes/admin/geminiApiAccounts.js +++ b/src/routes/admin/geminiApiAccounts.js @@ -46,9 +46,7 @@ router.get('/gemini-api-accounts', authenticateAdmin, async (req, res) => { const bindingCountMap = new Map() for (const key of allApiKeys) { const binding = key.geminiAccountId - if (!binding) { - continue - } + if (!binding) continue // 处理 api: 前缀 const accountId = binding.startsWith('api:') ? binding.substring(4) : binding bindingCountMap.set(accountId, (bindingCountMap.get(accountId) || 0) + 1) diff --git a/src/routes/admin/openaiResponsesAccounts.js b/src/routes/admin/openaiResponsesAccounts.js index 0fff3196..dc66ffd9 100644 --- a/src/routes/admin/openaiResponsesAccounts.js +++ b/src/routes/admin/openaiResponsesAccounts.js @@ -54,9 +54,7 @@ router.get('/openai-responses-accounts', authenticateAdmin, async (req, res) => const bindingCountMap = new Map() for (const key of allApiKeys) { const binding = key.openaiAccountId - if (!binding) { - continue - } + if (!binding) continue // 处理 responses: 前缀 const accountId = binding.startsWith('responses:') ? binding.substring(10) : binding bindingCountMap.set(accountId, (bindingCountMap.get(accountId) || 0) + 1) diff --git a/src/routes/admin/usageStats.js b/src/routes/admin/usageStats.js index 9b72cb35..7264a387 100644 --- a/src/routes/admin/usageStats.js +++ b/src/routes/admin/usageStats.js @@ -64,18 +64,14 @@ async function getUsageDataByIndex(indexKey, keyPattern, scanPattern) { const match = k.match(/usage:([^:]+):model:daily:(.+):\d{4}-\d{2}-\d{2}$/) || k.match(/usage:([^:]+):model:hourly:(.+):\d{4}-\d{2}-\d{2}:\d{2}$/) - if (match) { - return `${match[1]}:${match[2]}` - } + if (match) return `${match[1]}:${match[2]}` } if (keyPattern.includes('{accountId}') && keyPattern.includes('{model}')) { // account_usage:model:daily 或 hourly const match = k.match(/account_usage:model:daily:([^:]+):(.+):\d{4}-\d{2}-\d{2}$/) || k.match(/account_usage:model:hourly:([^:]+):(.+):\d{4}-\d{2}-\d{2}:\d{2}$/) - if (match) { - return `${match[1]}:${match[2]}` - } + if (match) return `${match[1]}:${match[2]}` } // 通用格式:提取最后一个 : 前的 id const parts = k.split(':') @@ -541,7 +537,7 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { } // 使用索引获取数据,按小时批量查询 - const _dates = [...dateSet] + const dates = [...dateSet] const modelDataMap = new Map() const usageDataMap = new Map() @@ -575,9 +571,7 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { const match = key.match(/usage:model:hourly:.+?:(\d{4}-\d{2}-\d{2}:\d{2})/) if (match) { const hourKey = match[1] - if (!modelKeysByHour.has(hourKey)) { - modelKeysByHour.set(hourKey, []) - } + if (!modelKeysByHour.has(hourKey)) modelKeysByHour.set(hourKey, []) modelKeysByHour.get(hourKey).push(key) } } @@ -585,9 +579,7 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { const match = key.match(/usage:hourly:.+?:(\d{4}-\d{2}-\d{2}:\d{2})/) if (match) { const hourKey = match[1] - if (!usageKeysByHour.has(hourKey)) { - usageKeysByHour.set(hourKey, []) - } + if (!usageKeysByHour.has(hourKey)) usageKeysByHour.set(hourKey, []) usageKeysByHour.get(hourKey).push(key) } } @@ -607,15 +599,11 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { // 处理模型级别数据 for (const modelKey of modelKeys) { const modelMatch = modelKey.match(/usage:model:hourly:(.+?):\d{4}-\d{2}-\d{2}:\d{2}/) - if (!modelMatch) { - continue - } + if (!modelMatch) continue const model = modelMatch[1] const data = modelDataMap.get(modelKey) - if (!data || Object.keys(data).length === 0) { - continue - } + if (!data || Object.keys(data).length === 0) continue const modelInputTokens = parseInt(data.inputTokens) || 0 const modelOutputTokens = parseInt(data.outputTokens) || 0 @@ -722,9 +710,7 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { const match = key.match(/usage:model:daily:.+?:(\d{4}-\d{2}-\d{2})/) if (match) { const dateStr = match[1] - if (!modelKeysByDate.has(dateStr)) { - modelKeysByDate.set(dateStr, []) - } + if (!modelKeysByDate.has(dateStr)) modelKeysByDate.set(dateStr, []) modelKeysByDate.get(dateStr).push(key) } } @@ -732,9 +718,7 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { const match = key.match(/usage:daily:.+?:(\d{4}-\d{2}-\d{2})/) if (match) { const dateStr = match[1] - if (!usageKeysByDate.has(dateStr)) { - usageKeysByDate.set(dateStr, []) - } + if (!usageKeysByDate.has(dateStr)) usageKeysByDate.set(dateStr, []) usageKeysByDate.get(dateStr).push(key) } } @@ -754,15 +738,11 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { // 处理模型级别数据 for (const modelKey of modelKeys) { const modelMatch = modelKey.match(/usage:model:daily:(.+?):\d{4}-\d{2}-\d{2}/) - if (!modelMatch) { - continue - } + if (!modelMatch) continue const model = modelMatch[1] const data = modelDataMap.get(modelKey) - if (!data || Object.keys(data).length === 0) { - continue - } + if (!data || Object.keys(data).length === 0) continue const modelInputTokens = parseInt(data.inputTokens) || 0 const modelOutputTokens = parseInt(data.outputTokens) || 0 @@ -847,7 +827,7 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) = `📊 Getting model stats for API key: ${keyId}, period: ${period}, startDate: ${startDate}, endDate: ${endDate}` ) - const _client = redis.getClientSafe() + const client = redis.getClientSafe() const today = redis.getDateStringInTimezone() const tzDate = redis.getDateInTimezone() const currentMonth = `${tzDate.getUTCFullYear()}-${String(tzDate.getUTCMonth() + 1).padStart( @@ -915,13 +895,9 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) = for (const results of allResults) { for (const { key, data } of results) { // 过滤出属于该 keyId 的记录 - if (!key.startsWith(`usage:${keyId}:model:`)) { - continue - } + if (!key.startsWith(`usage:${keyId}:model:`)) continue const match = key.match(/usage:.+:model:daily:(.+):\d{4}-\d{2}-\d{2}$/) - if (!match) { - continue - } + if (!match) continue const model = match[1] if (!modelStatsMap.has(model)) { modelStatsMap.set(model, { @@ -957,15 +933,11 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) = results = await redis.scanAndGetAllChunked(pattern) } for (const { key, data } of results) { - if (!key.startsWith(`usage:${keyId}:model:`)) { - continue - } + if (!key.startsWith(`usage:${keyId}:model:`)) continue const match = key.match(/usage:.+:model:daily:(.+):\d{4}-\d{2}-\d{2}$/) || key.match(/usage:.+:model:monthly:(.+):\d{4}-\d{2}$/) - if (!match) { - continue - } + if (!match) continue const model = match[1] if (!modelStatsMap.has(model)) { modelStatsMap.set(model, { @@ -1283,7 +1255,7 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { } // 按小时获取 account_usage 数据(避免全库扫描) - const _dates = [...dateSet] + const dates = [...dateSet] const usageDataMap = new Map() const modelDataMap = new Map() @@ -1317,9 +1289,7 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { const match = key.match(/account_usage:hourly:.+?:(\d{4}-\d{2}-\d{2}:\d{2})/) if (match) { const hourKey = match[1] - if (!usageKeysByHour.has(hourKey)) { - usageKeysByHour.set(hourKey, []) - } + if (!usageKeysByHour.has(hourKey)) usageKeysByHour.set(hourKey, []) usageKeysByHour.get(hourKey).push(key) } } @@ -1329,9 +1299,7 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { const accountId = match[1] const hourKey = match[2] const mapKey = `${accountId}:${hourKey}` - if (!modelKeysByHour.has(mapKey)) { - modelKeysByHour.set(mapKey, []) - } + if (!modelKeysByHour.has(mapKey)) modelKeysByHour.set(mapKey, []) modelKeysByHour.get(mapKey).push(key) } } @@ -1348,19 +1316,13 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { for (const key of usageKeys) { const match = key.match(/account_usage:hourly:(.+?):\d{4}-\d{2}-\d{2}:\d{2}/) - if (!match) { - continue - } + if (!match) continue const accountId = match[1] - if (!accountIdSet.has(accountId)) { - continue - } + if (!accountIdSet.has(accountId)) continue const data = usageDataMap.get(key) - if (!data) { - continue - } + if (!data) continue const inputTokens = parseInt(data.inputTokens) || 0 const outputTokens = parseInt(data.outputTokens) || 0 @@ -1376,14 +1338,10 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { const modelKeys = modelKeysByHour.get(`${accountId}:${hourInfo.hourKey}`) || [] for (const modelKey of modelKeys) { const modelData = modelDataMap.get(modelKey) - if (!modelData) { - continue - } + if (!modelData) continue const parts = modelKey.split(':') - if (parts.length < 5) { - continue - } + if (parts.length < 5) continue const modelName = parts[4] const usage = { @@ -1476,9 +1434,7 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { const match = key.match(/account_usage:daily:.+?:(\d{4}-\d{2}-\d{2})/) if (match) { const dateStr = match[1] - if (!usageKeysByDate.has(dateStr)) { - usageKeysByDate.set(dateStr, []) - } + if (!usageKeysByDate.has(dateStr)) usageKeysByDate.set(dateStr, []) usageKeysByDate.get(dateStr).push(key) } } @@ -1488,9 +1444,7 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { const accountId = match[1] const dateStr = match[2] const mapKey = `${accountId}:${dateStr}` - if (!modelKeysByDate.has(mapKey)) { - modelKeysByDate.set(mapKey, []) - } + if (!modelKeysByDate.has(mapKey)) modelKeysByDate.set(mapKey, []) modelKeysByDate.get(mapKey).push(key) } } @@ -1506,19 +1460,13 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { for (const key of usageKeys) { const match = key.match(/account_usage:daily:(.+?):\d{4}-\d{2}-\d{2}/) - if (!match) { - continue - } + if (!match) continue const accountId = match[1] - if (!accountIdSet.has(accountId)) { - continue - } + if (!accountIdSet.has(accountId)) continue const data = usageDataMap.get(key) - if (!data) { - continue - } + if (!data) continue const inputTokens = parseInt(data.inputTokens) || 0 const outputTokens = parseInt(data.outputTokens) || 0 @@ -1534,14 +1482,10 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => { const modelKeys = modelKeysByDate.get(`${accountId}:${dayInfo.dateStr}`) || [] for (const modelKey of modelKeys) { const modelData = modelDataMap.get(modelKey) - if (!modelData) { - continue - } + if (!modelData) continue const parts = modelKey.split(':') - if (parts.length < 5) { - continue - } + if (parts.length < 5) continue const modelName = parts[4] const usage = { @@ -1669,7 +1613,7 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { } // 使用索引获取数据,按小时批量查询 - const _dates = [...dateSet] + const dates = [...dateSet] const usageDataMap = new Map() const modelDataMap = new Map() @@ -1702,9 +1646,7 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { const match = key.match(/usage:hourly:.+?:(\d{4}-\d{2}-\d{2}:\d{2})/) if (match) { const hourKey = match[1] - if (!usageKeysByHour.has(hourKey)) { - usageKeysByHour.set(hourKey, []) - } + if (!usageKeysByHour.has(hourKey)) usageKeysByHour.set(hourKey, []) usageKeysByHour.get(hourKey).push(key) } } @@ -1712,9 +1654,7 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { const match = key.match(/usage:.+?:model:hourly:.+?:(\d{4}-\d{2}-\d{2}:\d{2})/) if (match) { const hourKey = match[1] - if (!modelKeysByHour.has(hourKey)) { - modelKeysByHour.set(hourKey, []) - } + if (!modelKeysByHour.has(hourKey)) modelKeysByHour.set(hourKey, []) modelKeysByHour.get(hourKey).push(key) } } @@ -1734,15 +1674,11 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { const apiKeyDataMap = new Map() for (const key of hourUsageKeys) { const match = key.match(/usage:hourly:(.+?):\d{4}-\d{2}-\d{2}:\d{2}/) - if (!match) { - continue - } + if (!match) continue const apiKeyId = match[1] const data = usageDataMap.get(key) - if (!data || !apiKeyMap.has(apiKeyId)) { - continue - } + if (!data || !apiKeyMap.has(apiKeyId)) continue const inputTokens = parseInt(data.inputTokens) || 0 const outputTokens = parseInt(data.outputTokens) || 0 @@ -1764,16 +1700,12 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { const apiKeyCostMap = new Map() for (const modelKey of hourModelKeys) { const match = modelKey.match(/usage:(.+?):model:hourly:(.+?):\d{4}-\d{2}-\d{2}:\d{2}/) - if (!match) { - continue - } + if (!match) continue const apiKeyId = match[1] const model = match[2] const modelData = modelDataMap.get(modelKey) - if (!modelData || !apiKeyDataMap.has(apiKeyId)) { - continue - } + if (!modelData || !apiKeyDataMap.has(apiKeyId)) continue const usage = { input_tokens: parseInt(modelData.inputTokens) || 0, @@ -1863,9 +1795,7 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { const match = key.match(/usage:daily:.+?:(\d{4}-\d{2}-\d{2})/) if (match) { const dateStr = match[1] - if (!usageKeysByDate.has(dateStr)) { - usageKeysByDate.set(dateStr, []) - } + if (!usageKeysByDate.has(dateStr)) usageKeysByDate.set(dateStr, []) usageKeysByDate.get(dateStr).push(key) } } @@ -1873,9 +1803,7 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { const match = key.match(/usage:.+?:model:daily:.+?:(\d{4}-\d{2}-\d{2})/) if (match) { const dateStr = match[1] - if (!modelKeysByDate.has(dateStr)) { - modelKeysByDate.set(dateStr, []) - } + if (!modelKeysByDate.has(dateStr)) modelKeysByDate.set(dateStr, []) modelKeysByDate.get(dateStr).push(key) } } @@ -1894,15 +1822,11 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { const apiKeyDataMap = new Map() for (const key of dayUsageKeys) { const match = key.match(/usage:daily:(.+?):\d{4}-\d{2}-\d{2}/) - if (!match) { - continue - } + if (!match) continue const apiKeyId = match[1] const data = usageDataMap.get(key) - if (!data || !apiKeyMap.has(apiKeyId)) { - continue - } + if (!data || !apiKeyMap.has(apiKeyId)) continue const inputTokens = parseInt(data.inputTokens) || 0 const outputTokens = parseInt(data.outputTokens) || 0 @@ -1924,16 +1848,12 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => { const apiKeyCostMap = new Map() for (const modelKey of dayModelKeys) { const match = modelKey.match(/usage:(.+?):model:daily:(.+?):\d{4}-\d{2}-\d{2}/) - if (!match) { - continue - } + if (!match) continue const apiKeyId = match[1] const model = match[2] const modelData = modelDataMap.get(modelKey) - if (!modelData || !apiKeyDataMap.has(apiKeyId)) { - continue - } + if (!modelData || !apiKeyDataMap.has(apiKeyId)) continue const usage = { input_tokens: parseInt(modelData.inputTokens) || 0, @@ -2052,7 +1972,7 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { const modelCosts = {} // 按模型统计费用 - const _client = redis.getClientSafe() + const client = redis.getClientSafe() const today = redis.getDateStringInTimezone() const tzDate = redis.getDateInTimezone() const currentMonth = `${tzDate.getUTCFullYear()}-${String(tzDate.getUTCMonth() + 1).padStart( @@ -2060,11 +1980,11 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { '0' )}` - let _pattern + let pattern if (period === 'today') { - _pattern = `usage:model:daily:*:${today}` + pattern = `usage:model:daily:*:${today}` } else if (period === 'monthly') { - _pattern = `usage:model:monthly:*:${currentMonth}` + pattern = `usage:model:monthly:*:${currentMonth}` } else if (period === '7days') { // 最近7天:汇总daily数据(使用 SCAN + Pipeline 优化) const modelUsageMap = new Map() @@ -2094,14 +2014,10 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { // 处理数据 for (const { key, data } of allData) { - if (!data) { - continue - } + if (!data) continue const modelMatch = key.match(/usage:model:daily:(.+):\d{4}-\d{2}-\d{2}$/) - if (!modelMatch) { - continue - } + if (!modelMatch) continue const rawModel = modelMatch[1] const normalizedModel = normalizeModelName(rawModel) @@ -2196,14 +2112,10 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { const modelUsageMap = new Map() for (const { key, data } of allData) { - if (!data) { - continue - } + if (!data) continue const modelMatch = key.match(/usage:model:monthly:(.+):(\d{4}-\d{2})$/) - if (!modelMatch) { - continue - } + if (!modelMatch) continue const model = modelMatch[1] @@ -2329,14 +2241,10 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => { : /usage:model:monthly:(.+):\d{4}-\d{2}$/ for (const { key, data } of allData) { - if (!data) { - continue - } + if (!data) continue const match = key.match(regex) - if (!match) { - continue - } + if (!match) continue const model = match[1] const usage = { diff --git a/src/routes/apiStats.js b/src/routes/apiStats.js index 23c6d94a..96ca590c 100644 --- a/src/routes/apiStats.js +++ b/src/routes/apiStats.js @@ -701,7 +701,7 @@ router.post('/api/batch-model-stats', async (req, res) => { }) } - const _client = redis.getClientSafe() + const client = redis.getClientSafe() const tzDate = redis.getDateInTimezone() const today = redis.getDateStringInTimezone() const currentMonth = `${tzDate.getFullYear()}-${String(tzDate.getMonth() + 1).padStart(2, '0')}` @@ -940,7 +940,7 @@ router.post('/api/user-model-stats', async (req, res) => { ) // 重用管理后台的模型统计逻辑,但只返回该API Key的数据 - const _client = redis.getClientSafe() + const client = redis.getClientSafe() // 使用与管理页面相同的时区处理逻辑 const tzDate = redis.getDateInTimezone() const today = redis.getDateStringInTimezone() diff --git a/src/services/accountGroupService.js b/src/services/accountGroupService.js index 3a79413b..f4413204 100644 --- a/src/services/accountGroupService.js +++ b/src/services/accountGroupService.js @@ -18,9 +18,7 @@ class AccountGroupService { async ensureReverseIndexes() { try { const client = redis.getClientSafe() - if (!client) { - return - } + if (!client) return // 检查是否已迁移 const migrated = await client.get(this.REVERSE_INDEX_MIGRATED_KEY) @@ -41,14 +39,10 @@ class AccountGroupService { for (const groupId of allGroupIds) { const group = await client.hgetall(`${this.GROUP_PREFIX}${groupId}`) - if (!group || !group.platform) { - continue - } + if (!group || !group.platform) continue const members = await client.smembers(`${this.GROUP_MEMBERS_PREFIX}${groupId}`) - if (members.length === 0) { - continue - } + if (members.length === 0) continue const pipeline = client.pipeline() for (const accountId of members) { diff --git a/src/services/apiKeyIndexService.js b/src/services/apiKeyIndexService.js index 1a5963a1..cd8117e3 100644 --- a/src/services/apiKeyIndexService.js +++ b/src/services/apiKeyIndexService.js @@ -71,9 +71,7 @@ class ApiKeyIndexService { * 扫描所有 API Key,确保 hash -> keyId 映射存在 */ async rebuildHashMap() { - if (!this.redis) { - return - } + if (!this.redis) return try { const client = this.redis.getClientSafe() @@ -189,9 +187,7 @@ class ApiKeyIndexService { const pipeline = client.pipeline() for (const apiKey of apiKeys) { - if (!apiKey || !apiKey.id) { - continue - } + if (!apiKey || !apiKey.id) continue const keyId = apiKey.id const createdAt = apiKey.createdAt ? new Date(apiKey.createdAt).getTime() : 0 @@ -253,9 +249,7 @@ class ApiKeyIndexService { * 添加单个 API Key 到索引 */ async addToIndex(apiKey) { - if (!this.redis || !apiKey || !apiKey.id) { - return - } + if (!this.redis || !apiKey || !apiKey.id) return try { const client = this.redis.getClientSafe() @@ -303,9 +297,7 @@ class ApiKeyIndexService { * 更新索引(状态、名称、标签变化时调用) */ async updateIndex(keyId, updates, oldData = {}) { - if (!this.redis || !keyId) { - return - } + if (!this.redis || !keyId) return try { const client = this.redis.getClientSafe() @@ -384,9 +376,7 @@ class ApiKeyIndexService { * 从索引中移除 API Key */ async removeFromIndex(keyId, oldData = {}) { - if (!this.redis || !keyId) { - return - } + if (!this.redis || !keyId) return try { const client = this.redis.getClientSafe() @@ -608,9 +598,7 @@ class ApiKeyIndexService { * 更新 lastUsedAt 索引(供 recordUsage 调用) */ async updateLastUsedAt(keyId, lastUsedAt) { - if (!this.redis || !keyId) { - return - } + if (!this.redis || !keyId) return try { const client = this.redis.getClientSafe() diff --git a/src/services/apiKeyService.js b/src/services/apiKeyService.js index 982c9740..b71f4af2 100644 --- a/src/services/apiKeyService.js +++ b/src/services/apiKeyService.js @@ -921,9 +921,7 @@ class ApiKeyService { return keyIds .map((id, i) => { const [err, fields] = results[i] - if (err) { - return null - } + if (err) return null return { id, claudeAccountId: fields[0] || null, diff --git a/src/services/bedrockAccountService.js b/src/services/bedrockAccountService.js index 38c1c84a..42fc73e3 100644 --- a/src/services/bedrockAccountService.js +++ b/src/services/bedrockAccountService.js @@ -127,7 +127,7 @@ class BedrockAccountService { // 📋 获取所有账户列表 async getAllAccounts() { try { - const _client = redis.getClientSafe() + const client = redis.getClientSafe() const accountIds = await redis.getAllIdsByIndex( 'bedrock_account:index', 'bedrock_account:*', diff --git a/src/services/ccrAccountService.js b/src/services/ccrAccountService.js index bc87e6cb..c074d6cf 100644 --- a/src/services/ccrAccountService.js +++ b/src/services/ccrAccountService.js @@ -2,7 +2,7 @@ const { v4: uuidv4 } = require('uuid') const ProxyHelper = require('../utils/proxyHelper') const redis = require('../models/redis') const logger = require('../utils/logger') -// const config = require('../../config/config') +const config = require('../../config/config') const { createEncryptor } = require('../utils/commonHelper') class CcrAccountService { diff --git a/src/services/claudeCodeHeadersService.js b/src/services/claudeCodeHeadersService.js index 4c016494..8a8b48fb 100644 --- a/src/services/claudeCodeHeadersService.js +++ b/src/services/claudeCodeHeadersService.js @@ -5,11 +5,7 @@ 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() { diff --git a/src/services/claudeRelayService.js b/src/services/claudeRelayService.js index 95da1c7a..9f02af1c 100644 --- a/src/services/claudeRelayService.js +++ b/src/services/claudeRelayService.js @@ -24,7 +24,9 @@ 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() { diff --git a/src/services/costInitService.js b/src/services/costInitService.js index 5463871f..c0b55850 100644 --- a/src/services/costInitService.js +++ b/src/services/costInitService.js @@ -94,9 +94,7 @@ 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 @@ -157,9 +155,7 @@ class CostInitService { for (let j = 0; j < results.length; j++) { const [err, values] = results[j] - if (err) { - continue - } + if (err) continue // 将数组转换为对象 const data = {} @@ -186,9 +182,7 @@ class CostInitService { const match = key.match( /usage:(.+):model:(daily|monthly|hourly):(.+):(\d{4}-\d{2}(?:-\d{2})?(?::\d{2})?)$/ ) - if (!match) { - continue - } + if (!match) continue const [, , period, model, dateStr] = match @@ -307,9 +301,7 @@ class CostInitService { cursor = newCursor for (const usageKey of usageKeys) { - if (samplesChecked >= maxSamples) { - break - } + if (samplesChecked >= maxSamples) break const match = usageKey.match(/usage:(.+):model:daily:(.+):(\d{4}-\d{2}-\d{2})$/) if (match) { @@ -327,9 +319,7 @@ class CostInitService { } } - if (samplesChecked >= maxSamples) { - break - } + if (samplesChecked >= maxSamples) break } while (cursor !== '0') logger.info('💰 Cost data appears to be up to date') diff --git a/src/services/droidAccountService.js b/src/services/droidAccountService.js index a087b310..b8669231 100644 --- a/src/services/droidAccountService.js +++ b/src/services/droidAccountService.js @@ -2,7 +2,7 @@ const { v4: uuidv4 } = require('uuid') const crypto = require('crypto') const axios = require('axios') const redis = require('../models/redis') -// const config = require('../../config/config') +const config = require('../../config/config') const logger = require('../utils/logger') const { maskToken } = require('../utils/tokenMask') const ProxyHelper = require('../utils/proxyHelper') @@ -30,13 +30,10 @@ 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']) } diff --git a/src/services/droidScheduler.js b/src/services/droidScheduler.js index 14adcd40..37ea9e4b 100644 --- a/src/services/droidScheduler.js +++ b/src/services/droidScheduler.js @@ -2,12 +2,7 @@ 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() { @@ -21,12 +16,8 @@ class DroidScheduler { _matchesEndpoint(account, endpointType) { const normalizedEndpoint = normalizeEndpointType(endpointType) const accountEndpoint = normalizeEndpointType(account?.endpointType) - if (normalizedEndpoint === accountEndpoint) { - return true - } - if (normalizedEndpoint === 'comm') { - return true - } + if (normalizedEndpoint === accountEndpoint) return true + if (normalizedEndpoint === 'comm') return true const sharedEndpoints = new Set(['anthropic', 'openai']) return sharedEndpoints.has(normalizedEndpoint) && sharedEndpoints.has(accountEndpoint) } diff --git a/src/services/geminiAccountService.js b/src/services/geminiAccountService.js index 2360a561..c40ecf5b 100644 --- a/src/services/geminiAccountService.js +++ b/src/services/geminiAccountService.js @@ -1,7 +1,7 @@ const redisClient = require('../models/redis') const { v4: uuidv4 } = require('uuid') const https = require('https') -// const config = require('../../config/config') +const config = require('../../config/config') const logger = require('../utils/logger') const { OAuth2Client } = require('google-auth-library') const { maskToken } = require('../utils/tokenMask') @@ -19,8 +19,6 @@ const { createEncryptor } = require('../utils/commonHelper') // Gemini 账户键前缀 const GEMINI_ACCOUNT_KEY_PREFIX = 'gemini_account:' -const SHARED_GEMINI_ACCOUNTS_KEY = 'shared_gemini_accounts' -const ACCOUNT_SESSION_MAPPING_PREFIX = 'gemini_session_account_mapping:' // Gemini CLI OAuth 配置 - 这些是公开的 Gemini CLI 凭据 const OAUTH_CLIENT_ID = '681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com' const OAUTH_CLIENT_SECRET = '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' @@ -574,7 +572,7 @@ async function deleteAccount(accountId) { // 获取所有账户 async function getAllAccounts() { - const _client = redisClient.getClientSafe() + const client = redisClient.getClientSafe() const accountIds = await redisClient.getAllIdsByIndex( 'gemini_account:index', `${GEMINI_ACCOUNT_KEY_PREFIX}*`, diff --git a/src/services/openaiAccountService.js b/src/services/openaiAccountService.js index 32f0590b..21fd1569 100644 --- a/src/services/openaiAccountService.js +++ b/src/services/openaiAccountService.js @@ -666,7 +666,7 @@ async function deleteAccount(accountId) { // 获取所有账户 async function getAllAccounts() { - const _client = redisClient.getClientSafe() + const client = redisClient.getClientSafe() const accountIds = await redisClient.getAllIdsByIndex( 'openai:account:index', `${OPENAI_ACCOUNT_KEY_PREFIX}*`, diff --git a/src/services/openaiResponsesAccountService.js b/src/services/openaiResponsesAccountService.js index d61d3f3a..708fa8b4 100644 --- a/src/services/openaiResponsesAccountService.js +++ b/src/services/openaiResponsesAccountService.js @@ -201,9 +201,7 @@ class OpenAIResponsesAccountService { `${this.ACCOUNT_KEY_PREFIX}*`, /^openai_responses_account:(.+)$/ ) - if (accountIds.length === 0) { - return [] - } + if (accountIds.length === 0) return [] const keys = accountIds.map((id) => `${this.ACCOUNT_KEY_PREFIX}${id}`) // Pipeline 批量查询所有账户数据 @@ -212,15 +210,11 @@ class OpenAIResponsesAccountService { const results = await pipeline.exec() const accounts = [] - results.forEach(([err, accountData], _index) => { - if (err || !accountData || !accountData.id) { - return - } + results.forEach(([err, accountData], index) => { + if (err || !accountData || !accountData.id) return // 过滤非活跃账户 - if (!includeInactive && accountData.isActive !== 'true') { - return - } + if (!includeInactive && accountData.isActive !== 'true') return // 隐藏敏感信息 accountData.apiKey = '***' diff --git a/src/services/unifiedGeminiScheduler.js b/src/services/unifiedGeminiScheduler.js index 381ff51a..2581196b 100644 --- a/src/services/unifiedGeminiScheduler.js +++ b/src/services/unifiedGeminiScheduler.js @@ -26,7 +26,11 @@ 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}` ) @@ -59,7 +63,11 @@ 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}` ) @@ -175,7 +183,11 @@ 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) { // 检查模型支持 @@ -222,7 +234,11 @@ 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) { // 检查模型支持 diff --git a/src/services/unifiedOpenAIScheduler.js b/src/services/unifiedOpenAIScheduler.js index 664aae53..a1e8ef44 100644 --- a/src/services/unifiedOpenAIScheduler.js +++ b/src/services/unifiedOpenAIScheduler.js @@ -56,9 +56,9 @@ class UnifiedOpenAIScheduler { let rateLimitChecked = false let stillLimited = false - let _isSchedulable = isSchedulable(account.schedulable) + let isSchedulable = isSchedulable(account.schedulable) - if (!_isSchedulable) { + if (!isSchedulable) { if (!hasRateLimitFlag) { return { canUse: false, reason: 'not_schedulable' } } @@ -75,7 +75,7 @@ class UnifiedOpenAIScheduler { } else { account.schedulable = 'true' } - _isSchedulable = true + isSchedulable = true logger.info(`✅ OpenAI账号 ${account.name || accountId} 已解除限流,恢复调度权限`) } diff --git a/src/utils/commonHelper.js b/src/utils/commonHelper.js index 81a11d72..752166da 100644 --- a/src/utils/commonHelper.js +++ b/src/utils/commonHelper.js @@ -17,45 +17,33 @@ const _encryptorCache = new Map() // 创建加密器实例(每个 salt 独立缓存) const createEncryptor = (salt) => { - if (_encryptorCache.has(salt)) { - return _encryptorCache.get(salt) - } + if (_encryptorCache.has(salt)) return _encryptorCache.get(salt) let keyCache = null const decryptCache = new LRUCache(500) const getKey = () => { - if (!keyCache) { - keyCache = crypto.scryptSync(config.security.encryptionKey, salt, 32) - } + if (!keyCache) keyCache = crypto.scryptSync(config.security.encryptionKey, salt, 32) return keyCache } const encrypt = (text) => { - if (!text) { - return '' - } + if (!text) return '' const key = getKey() const iv = crypto.randomBytes(IV_LENGTH) const cipher = crypto.createCipheriv(ALGORITHM, key, iv) let encrypted = cipher.update(text, 'utf8', 'hex') encrypted += cipher.final('hex') - return `${iv.toString('hex')}:${encrypted}` + return iv.toString('hex') + ':' + encrypted } const decrypt = (text, useCache = true) => { - if (!text) { - return '' - } - if (!text.includes(':')) { - return text - } + if (!text) return '' + if (!text.includes(':')) return text const cacheKey = crypto.createHash('sha256').update(text).digest('hex') if (useCache) { const cached = decryptCache.get(cacheKey) - if (cached !== undefined) { - return cached - } + if (cached !== undefined) return cached } try { const key = getKey() @@ -64,9 +52,7 @@ const createEncryptor = (salt) => { const decipher = crypto.createDecipheriv(ALGORITHM, key, iv) let decrypted = decipher.update(encrypted, 'hex', 'utf8') decrypted += decipher.final('utf8') - if (useCache) { - decryptCache.set(cacheKey, decrypted, 5 * 60 * 1000) - } + if (useCache) decryptCache.set(cacheKey, decrypted, 5 * 60 * 1000) return decrypted } catch (e) { return text @@ -87,8 +73,8 @@ const createEncryptor = (salt) => { // 默认加密器(向后兼容) const defaultEncryptor = createEncryptor('claude-relay-salt') -const { encrypt } = defaultEncryptor -const { decrypt } = defaultEncryptor +const encrypt = defaultEncryptor.encrypt +const decrypt = defaultEncryptor.decrypt const getEncryptionKey = defaultEncryptor.getKey const clearDecryptCache = defaultEncryptor.clearCache const getDecryptCacheStats = defaultEncryptor.getStats @@ -98,13 +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) +const isTruthy = (value) => value != null && toBoolean(value) // 检查是否可调度(默认 true,只有明确 false 才返回 false) const isSchedulable = (value) => value !== false && value !== 'false' @@ -114,12 +97,8 @@ const isActive = (value) => value === true || value === 'true' // 检查账户是否健康(激活且状态正常) const isAccountHealthy = (account) => { - if (!account) { - return false - } - if (!isTruthy(account.isActive)) { - return false - } + if (!account) return false + if (!isTruthy(account.isActive)) return false const status = (account.status || 'active').toLowerCase() return !['error', 'unauthorized', 'blocked', 'temp_error'].includes(status) } @@ -130,14 +109,8 @@ const isAccountHealthy = (account) => { // 安全解析 JSON const safeParseJson = (value, fallback = null) => { - if (!value || typeof value !== 'string') { - return fallback - } - try { - return JSON.parse(value) - } catch { - return fallback - } + if (!value || typeof value !== 'string') return fallback + try { return JSON.parse(value) } catch { return fallback } } // 安全解析 JSON 为对象 @@ -158,53 +131,36 @@ const safeParseJsonArray = (value, fallback = []) => { // 规范化模型名称(用于统计聚合) const normalizeModelName = (model) => { - if (!model || model === 'unknown') { - return 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$/, '') } // 规范化端点类型 const normalizeEndpointType = (endpointType) => { - if (!endpointType) { - return 'anthropic' - } + if (!endpointType) return 'anthropic' const normalized = String(endpointType).toLowerCase() return ['openai', 'comm', 'anthropic'].includes(normalized) ? normalized : 'anthropic' } // 检查模型是否在映射表中 const isModelInMapping = (modelMapping, requestedModel) => { - if (!modelMapping || Object.keys(modelMapping).length === 0) { - return true - } - if (Object.prototype.hasOwnProperty.call(modelMapping, requestedModel)) { - return true - } + 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) } // 获取映射后的模型名称 const getMappedModelName = (modelMapping, requestedModel) => { - if (!modelMapping || Object.keys(modelMapping).length === 0) { - return requestedModel - } - if (modelMapping[requestedModel]) { - return modelMapping[requestedModel] - } + if (!modelMapping || Object.keys(modelMapping).length === 0) return requestedModel + if (modelMapping[requestedModel]) return modelMapping[requestedModel] const lower = requestedModel.toLowerCase() for (const [key, value] of Object.entries(modelMapping)) { - if (key.toLowerCase() === lower) { - return value - } + if (key.toLowerCase() === lower) return value } return requestedModel } @@ -214,34 +170,30 @@ const getMappedModelName = (modelMapping, requestedModel) => { // ============================================ // 按优先级和最后使用时间排序账户 -const sortAccountsByPriority = (accounts) => - [...accounts].sort((a, b) => { +const sortAccountsByPriority = (accounts) => { + return [...accounts].sort((a, b) => { const priorityA = parseInt(a.priority, 10) || 50 const priorityB = parseInt(b.priority, 10) || 50 - if (priorityA !== priorityB) { - return priorityA - priorityB - } + if (priorityA !== priorityB) return priorityA - priorityB const lastUsedA = a.lastUsedAt ? new Date(a.lastUsedAt).getTime() : 0 const lastUsedB = b.lastUsedAt ? new Date(b.lastUsedAt).getTime() : 0 - if (lastUsedA !== lastUsedB) { - return lastUsedA - lastUsedB - } + if (lastUsedA !== lastUsedB) return lastUsedA - lastUsedB const createdA = a.createdAt ? new Date(a.createdAt).getTime() : 0 const createdB = b.createdAt ? new Date(b.createdAt).getTime() : 0 return createdA - createdB }) +} // 生成粘性会话 Key const composeStickySessionKey = (prefix, sessionHash, apiKeyId = null) => { - if (!sessionHash) { - return null - } + if (!sessionHash) return null return `sticky:${prefix}:${apiKeyId || 'default'}:${sessionHash}` } // 过滤可用账户(激活 + 健康 + 可调度) -const filterAvailableAccounts = (accounts) => - accounts.filter((acc) => acc && isAccountHealthy(acc) && isSchedulable(acc.schedulable)) +const filterAvailableAccounts = (accounts) => { + return accounts.filter(acc => acc && isAccountHealthy(acc) && isSchedulable(acc.schedulable)) +} // ============================================ // 字符串处理 @@ -249,17 +201,13 @@ const filterAvailableAccounts = (accounts) => // 截断字符串 const truncate = (str, maxLen = 100, suffix = '...') => { - if (!str || str.length <= maxLen) { - return str - } + if (!str || str.length <= maxLen) return str return str.slice(0, maxLen - suffix.length) + suffix } // 掩码敏感信息(保留前后几位) const maskSensitive = (str, keepStart = 4, keepEnd = 4, maskChar = '*') => { - if (!str || str.length <= keepStart + keepEnd) { - return str - } + if (!str || str.length <= keepStart + keepEnd) return str const maskLen = Math.min(str.length - keepStart - keepEnd, 8) return str.slice(0, keepStart) + maskChar.repeat(maskLen) + str.slice(-keepEnd) } @@ -288,8 +236,9 @@ const clamp = (value, min, max) => Math.min(Math.max(value, min), max) // ============================================ // 获取时区偏移后的日期 -const getDateInTimezone = (date = new Date(), offset = config.system?.timezoneOffset || 8) => - new Date(date.getTime() + offset * 3600000) +const getDateInTimezone = (date = new Date(), offset = config.system?.timezoneOffset || 8) => { + return new Date(date.getTime() + offset * 3600000) +} // 获取时区日期字符串 YYYY-MM-DD const getDateStringInTimezone = (date = new Date()) => { @@ -299,17 +248,13 @@ const getDateStringInTimezone = (date = new Date()) => { // 检查是否过期 const isExpired = (expiresAt) => { - if (!expiresAt) { - return false - } + if (!expiresAt) return false return new Date(expiresAt).getTime() < Date.now() } // 计算剩余时间(秒) const getTimeRemaining = (expiresAt) => { - if (!expiresAt) { - return Infinity - } + if (!expiresAt) return Infinity return Math.max(0, Math.floor((new Date(expiresAt).getTime() - Date.now()) / 1000)) }