From 17311f2d3bc107ecc8d809e44f35a8452cb0d10c Mon Sep 17 00:00:00 2001 From: shaw Date: Fri, 17 Oct 2025 22:36:31 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dapikey=E6=9C=80?= =?UTF-8?q?=E5=90=8E=E4=BD=BF=E7=94=A8=E6=9F=A5=E6=89=BE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/apiKeyService.js | 118 +++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 52 deletions(-) diff --git a/src/services/apiKeyService.js b/src/services/apiKeyService.js index 626ae171..bd2f55c6 100644 --- a/src/services/apiKeyService.js +++ b/src/services/apiKeyService.js @@ -5,25 +5,35 @@ const redis = require('../models/redis') const logger = require('../utils/logger') const ACCOUNT_TYPE_CONFIG = { - claude: { prefix: 'claude_account:', category: 'claude' }, - 'claude-console': { prefix: 'claude_console_account:', category: 'claude' }, - openai: { prefix: 'openai:account:', category: 'openai' }, - 'openai-responses': { prefix: 'openai_responses_account:', category: 'openai' }, - 'azure-openai': { prefix: 'azure_openai:account:', category: 'openai' }, - gemini: { prefix: 'gemini_account:', category: 'gemini' }, - droid: { prefix: 'droid:account:', category: 'droid' } + claude: { prefix: 'claude_account:' }, + 'claude-console': { prefix: 'claude_console_account:' }, + openai: { prefix: 'openai:account:' }, + 'openai-responses': { prefix: 'openai_responses_account:' }, + 'azure-openai': { prefix: 'azure_openai:account:' }, + gemini: { prefix: 'gemini_account:' }, + droid: { prefix: 'droid:account:' } } -const DEFAULT_LAST_USAGE_TYPES = [ - 'claude', - 'claude-console', +const ACCOUNT_TYPE_PRIORITY = [ 'openai', 'openai-responses', 'azure-openai', + 'claude', + 'claude-console', 'gemini', 'droid' ] +const ACCOUNT_CATEGORY_MAP = { + claude: 'claude', + 'claude-console': 'claude', + openai: 'openai', + 'openai-responses': 'openai', + 'azure-openai': 'openai', + gemini: 'gemini', + droid: 'droid' +} + function normalizeAccountTypeKey(type) { if (!type) { return null @@ -1293,61 +1303,60 @@ class ApiKeyService { return null } - async _resolveLastUsageAccount(apiKey, usageRecord, cache, client) { - if (!client || !usageRecord) { + async _resolveAccountByUsageRecord(usageRecord, cache, client) { + if (!usageRecord || !client) { + return null + } + + const rawAccountId = usageRecord.accountId || null + const rawAccountType = normalizeAccountTypeKey(usageRecord.accountType) + const modelName = usageRecord.model || usageRecord.actualModel || usageRecord.service || null + + if (!rawAccountId && !rawAccountType) { return null } const candidateIds = new Set() - const addId = (value) => { - if (!value) { - return - } - candidateIds.add(value) - if (typeof value === 'string' && value.startsWith('responses:')) { - candidateIds.add(value.replace(/^responses:/, '')) + if (rawAccountId) { + candidateIds.add(rawAccountId) + if (typeof rawAccountId === 'string' && rawAccountId.startsWith('responses:')) { + candidateIds.add(rawAccountId.replace(/^responses:/, '')) } } - addId(usageRecord.accountId) - addId(apiKey?.openaiAccountId) - addId(apiKey?.azureOpenaiAccountId) - addId(apiKey?.claudeAccountId) - addId(apiKey?.claudeConsoleAccountId) - addId(apiKey?.geminiAccountId) - addId(apiKey?.droidAccountId) + if (candidateIds.size === 0) { + return null + } - const candidateTypes = [] - const addType = (type) => { + const typeCandidates = [] + const pushType = (type) => { const normalized = normalizeAccountTypeKey(type) - if (normalized && !candidateTypes.includes(normalized)) { - candidateTypes.push(normalized) + if (normalized && ACCOUNT_TYPE_CONFIG[normalized] && !typeCandidates.includes(normalized)) { + typeCandidates.push(normalized) } } - addType(usageRecord.accountType) - if (apiKey?.claudeAccountId) { - addType('claude') - } - if (apiKey?.claudeConsoleAccountId) { - addType('claude-console') - } - if (apiKey?.geminiAccountId) { - addType('gemini') - } - if (apiKey?.openaiAccountId) { - addType(apiKey.openaiAccountId.startsWith('responses:') ? 'openai-responses' : 'openai') - } - if (apiKey?.azureOpenaiAccountId) { - addType('azure-openai') - } - if (apiKey?.droidAccountId) { - addType('droid') + pushType(rawAccountType) + + if (modelName) { + const lowerModel = modelName.toLowerCase() + if (lowerModel.includes('gpt') || lowerModel.includes('openai')) { + pushType('openai') + pushType('openai-responses') + pushType('azure-openai') + } else if (lowerModel.includes('gemini')) { + pushType('gemini') + } else if (lowerModel.includes('claude') || lowerModel.includes('anthropic')) { + pushType('claude') + pushType('claude-console') + } else if (lowerModel.includes('droid')) { + pushType('droid') + } } - DEFAULT_LAST_USAGE_TYPES.forEach(addType) + ACCOUNT_TYPE_PRIORITY.forEach(pushType) - for (const type of candidateTypes) { + for (const type of typeCandidates) { const accountConfig = ACCOUNT_TYPE_CONFIG[type] if (!accountConfig) { continue @@ -1358,10 +1367,11 @@ class ApiKeyService { const accountInfo = await this._fetchAccountInfo(normalizedId, type, cache, client) if (accountInfo) { return { - accountId: accountInfo.id, + accountId: normalizedId, accountName: accountInfo.name, accountType: type, - accountCategory: accountConfig.category + accountCategory: ACCOUNT_CATEGORY_MAP[type] || 'other', + rawAccountId: rawAccountId || normalizedId } } } @@ -1370,6 +1380,10 @@ class ApiKeyService { return null } + async _resolveLastUsageAccount(apiKey, usageRecord, cache, client) { + return await this._resolveAccountByUsageRecord(usageRecord, cache, client) + } + // 🔔 发布计费事件(内部方法) async _publishBillingEvent(eventData) { try {