diff --git a/Dcodegithubclaude-relay-servicelogs.claude-relay-audit.log.json b/Dcodegithubclaude-relay-servicelogs.claude-relay-audit.log.json new file mode 100644 index 00000000..6690b5de --- /dev/null +++ b/Dcodegithubclaude-relay-servicelogs.claude-relay-audit.log.json @@ -0,0 +1,35 @@ +{ + "keep": { + "days": false, + "amount": 5 + }, + "auditLog": "D:\\code\\github\\claude-relay-service\\logs\\.claude-relay-audit.log.json", + "files": [ + { + "date": 1766937830728, + "name": "D:\\code\\github\\claude-relay-service\\logs\\claude-relay-2025-12-29.log", + "hash": "d82c1d686e66788fe8083e7e0905e214a400c033e83cb6d2d30bfca72b079a5c" + }, + { + "date": 1767024423693, + "name": "D:\\code\\github\\claude-relay-service\\logs\\claude-relay-2025-12-30.log", + "hash": "22671b37716c6044051919421a80eedbb42fff28937b467ff9a58eefe70a084c" + }, + { + "date": 1767098022995, + "name": "/mnt/d/code/github/claude-relay-service/logs/claude-relay-2025-12-30.log", + "hash": "17b4d378f134d2d9d8a4e929b085d7e2121bcfced44b4ffe2860455b027bd2a7" + }, + { + "date": 1767110489197, + "name": "/mnt/d/code/github/claude-relay-service/logs/claude-relay-2025-12-31.log", + "hash": "29305b4cd76e3e64212c41de27ee5c44d1525e090e3a6d157a18d3f4a14cb633" + }, + { + "date": 1767197382349, + "name": "/mnt/d/code/github/claude-relay-service/logs/claude-relay-2026-01-01.log", + "hash": "57dea51b19fd2115b1b20ee5ecf1908ded5f709feb102c7210501b20786c1e35" + } + ], + "hashType": "sha256" +} \ No newline at end of file diff --git a/src/routes/admin/apiKeys.js b/src/routes/admin/apiKeys.js index 5a92937a..5d67e0b7 100644 --- a/src/routes/admin/apiKeys.js +++ b/src/routes/admin/apiKeys.js @@ -79,7 +79,8 @@ 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() - const client = redis.getClientSafe() + // eslint-disable-next-line no-unused-vars + const _client = redis.getClientSafe() // 获取所有相关的Redis键 const costKeys = await redis.scanKeys(`usage:cost:*:${keyId}:*`) @@ -298,7 +299,9 @@ router.get('/api-keys', authenticateAdmin, async (req, res) => { 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 21e84e6c..82d7184e 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') +// const openaiAccountService = require('../../services/openaiAccountService') // TODO: 未来用于OpenAI账户统计 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') +// const pricingService = require('../../services/pricingService') // TODO: 未来用于成本计算 const config = require('../../../config/config') const router = express.Router() @@ -144,7 +144,9 @@ 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 ba045de2..90a44550 100644 --- a/src/routes/admin/droidAccounts.js +++ b/src/routes/admin/droidAccounts.js @@ -157,7 +157,9 @@ 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) } } @@ -167,7 +169,9 @@ 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 598adc91..9c773de6 100644 --- a/src/routes/admin/geminiApiAccounts.js +++ b/src/routes/admin/geminiApiAccounts.js @@ -46,7 +46,9 @@ 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 dc66ffd9..0fff3196 100644 --- a/src/routes/admin/openaiResponsesAccounts.js +++ b/src/routes/admin/openaiResponsesAccounts.js @@ -54,7 +54,9 @@ 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 7264a387..9b72cb35 100644 --- a/src/routes/admin/usageStats.js +++ b/src/routes/admin/usageStats.js @@ -64,14 +64,18 @@ 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(':') @@ -537,7 +541,7 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { } // 使用索引获取数据,按小时批量查询 - const dates = [...dateSet] + const _dates = [...dateSet] const modelDataMap = new Map() const usageDataMap = new Map() @@ -571,7 +575,9 @@ 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) } } @@ -579,7 +585,9 @@ 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) } } @@ -599,11 +607,15 @@ 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 @@ -710,7 +722,9 @@ 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) } } @@ -718,7 +732,9 @@ 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) } } @@ -738,11 +754,15 @@ 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 @@ -827,7 +847,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( @@ -895,9 +915,13 @@ 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, { @@ -933,11 +957,15 @@ 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, { @@ -1255,7 +1283,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() @@ -1289,7 +1317,9 @@ 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) } } @@ -1299,7 +1329,9 @@ 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) } } @@ -1316,13 +1348,19 @@ 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 @@ -1338,10 +1376,14 @@ 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 = { @@ -1434,7 +1476,9 @@ 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) } } @@ -1444,7 +1488,9 @@ 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) } } @@ -1460,13 +1506,19 @@ 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 @@ -1482,10 +1534,14 @@ 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 = { @@ -1613,7 +1669,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() @@ -1646,7 +1702,9 @@ 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) } } @@ -1654,7 +1712,9 @@ 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) } } @@ -1674,11 +1734,15 @@ 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 @@ -1700,12 +1764,16 @@ 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, @@ -1795,7 +1863,9 @@ 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) } } @@ -1803,7 +1873,9 @@ 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) } } @@ -1822,11 +1894,15 @@ 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 @@ -1848,12 +1924,16 @@ 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, @@ -1972,7 +2052,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( @@ -1980,11 +2060,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() @@ -2014,10 +2094,14 @@ 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) @@ -2112,10 +2196,14 @@ 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] @@ -2241,10 +2329,14 @@ 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 96ca590c..23c6d94a 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 f4413204..3a79413b 100644 --- a/src/services/accountGroupService.js +++ b/src/services/accountGroupService.js @@ -18,7 +18,9 @@ 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) @@ -39,10 +41,14 @@ 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 cd8117e3..1a5963a1 100644 --- a/src/services/apiKeyIndexService.js +++ b/src/services/apiKeyIndexService.js @@ -71,7 +71,9 @@ class ApiKeyIndexService { * 扫描所有 API Key,确保 hash -> keyId 映射存在 */ async rebuildHashMap() { - if (!this.redis) return + if (!this.redis) { + return + } try { const client = this.redis.getClientSafe() @@ -187,7 +189,9 @@ 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 @@ -249,7 +253,9 @@ 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() @@ -297,7 +303,9 @@ class ApiKeyIndexService { * 更新索引(状态、名称、标签变化时调用) */ async updateIndex(keyId, updates, oldData = {}) { - if (!this.redis || !keyId) return + if (!this.redis || !keyId) { + return + } try { const client = this.redis.getClientSafe() @@ -376,7 +384,9 @@ class ApiKeyIndexService { * 从索引中移除 API Key */ async removeFromIndex(keyId, oldData = {}) { - if (!this.redis || !keyId) return + if (!this.redis || !keyId) { + return + } try { const client = this.redis.getClientSafe() @@ -598,7 +608,9 @@ 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 b71f4af2..982c9740 100644 --- a/src/services/apiKeyService.js +++ b/src/services/apiKeyService.js @@ -921,7 +921,9 @@ 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 42fc73e3..38c1c84a 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 c074d6cf..bc87e6cb 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/droidAccountService.js b/src/services/droidAccountService.js index 01e6fd91..a087b310 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') diff --git a/src/services/geminiAccountService.js b/src/services/geminiAccountService.js index c40ecf5b..2360a561 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,6 +19,8 @@ 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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' @@ -572,7 +574,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 21fd1569..32f0590b 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 708fa8b4..d61d3f3a 100644 --- a/src/services/openaiResponsesAccountService.js +++ b/src/services/openaiResponsesAccountService.js @@ -201,7 +201,9 @@ 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 批量查询所有账户数据 @@ -210,11 +212,15 @@ 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/utils/commonHelper.js b/src/utils/commonHelper.js index 2615082f..81a11d72 100644 --- a/src/utils/commonHelper.js +++ b/src/utils/commonHelper.js @@ -17,33 +17,45 @@ 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() @@ -52,7 +64,9 @@ 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 @@ -73,8 +87,8 @@ const createEncryptor = (salt) => { // 默认加密器(向后兼容) const defaultEncryptor = createEncryptor('claude-relay-salt') -const encrypt = defaultEncryptor.encrypt -const decrypt = defaultEncryptor.decrypt +const { encrypt } = defaultEncryptor +const { decrypt } = defaultEncryptor const getEncryptionKey = defaultEncryptor.getKey const clearDecryptCache = defaultEncryptor.clearCache const getDecryptCacheStats = defaultEncryptor.getStats @@ -90,7 +104,7 @@ const toBoolean = (value) => (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' @@ -100,8 +114,12 @@ 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) } @@ -112,7 +130,9 @@ const isAccountHealthy = (account) => { // 安全解析 JSON const safeParseJson = (value, fallback = null) => { - if (!value || typeof value !== 'string') return fallback + if (!value || typeof value !== 'string') { + return fallback + } try { return JSON.parse(value) } catch { @@ -138,7 +158,9 @@ 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 @@ -151,26 +173,38 @@ const normalizeModelName = (model) => { // 规范化端点类型 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) } // 获取映射后的模型名称 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 } @@ -180,30 +214,34 @@ const getMappedModelName = (modelMapping, requestedModel) => { // ============================================ // 按优先级和最后使用时间排序账户 -const sortAccountsByPriority = (accounts) => { - return [...accounts].sort((a, b) => { +const sortAccountsByPriority = (accounts) => + [...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) => { - return accounts.filter((acc) => acc && isAccountHealthy(acc) && isSchedulable(acc.schedulable)) -} +const filterAvailableAccounts = (accounts) => + accounts.filter((acc) => acc && isAccountHealthy(acc) && isSchedulable(acc.schedulable)) // ============================================ // 字符串处理 @@ -211,13 +249,17 @@ 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) } @@ -246,9 +288,8 @@ const clamp = (value, min, max) => Math.min(Math.max(value, min), max) // ============================================ // 获取时区偏移后的日期 -const getDateInTimezone = (date = new Date(), offset = config.system?.timezoneOffset || 8) => { - return new Date(date.getTime() + offset * 3600000) -} +const getDateInTimezone = (date = new Date(), offset = config.system?.timezoneOffset || 8) => + new Date(date.getTime() + offset * 3600000) // 获取时区日期字符串 YYYY-MM-DD const getDateStringInTimezone = (date = new Date()) => { @@ -258,13 +299,17 @@ 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)) }