mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
refactor: standardize code formatting and linting configuration
- Replace .eslintrc.js with .eslintrc.cjs for better ES module compatibility - Add .prettierrc configuration for consistent code formatting - Update package.json with new lint and format scripts - Add nodemon.json for development hot reloading configuration - Standardize code formatting across all JavaScript and Vue files - Update web admin SPA with improved linting rules and formatting - Add prettier configuration to web admin SPA 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const crypto = require('crypto');
|
||||
const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||
const redis = require('../models/redis');
|
||||
const logger = require('../utils/logger');
|
||||
const config = require('../../config/config');
|
||||
const { v4: uuidv4 } = require('uuid')
|
||||
const crypto = require('crypto')
|
||||
const { SocksProxyAgent } = require('socks-proxy-agent')
|
||||
const { HttpsProxyAgent } = require('https-proxy-agent')
|
||||
const redis = require('../models/redis')
|
||||
const logger = require('../utils/logger')
|
||||
const config = require('../../config/config')
|
||||
|
||||
class ClaudeConsoleAccountService {
|
||||
constructor() {
|
||||
// 加密相关常量
|
||||
this.ENCRYPTION_ALGORITHM = 'aes-256-cbc';
|
||||
this.ENCRYPTION_SALT = 'claude-console-salt';
|
||||
|
||||
this.ENCRYPTION_ALGORITHM = 'aes-256-cbc'
|
||||
this.ENCRYPTION_SALT = 'claude-console-salt'
|
||||
|
||||
// Redis键前缀
|
||||
this.ACCOUNT_KEY_PREFIX = 'claude_console_account:';
|
||||
this.SHARED_ACCOUNTS_KEY = 'shared_claude_console_accounts';
|
||||
this.ACCOUNT_KEY_PREFIX = 'claude_console_account:'
|
||||
this.SHARED_ACCOUNTS_KEY = 'shared_claude_console_accounts'
|
||||
}
|
||||
|
||||
// 🏢 创建Claude Console账户
|
||||
@@ -32,24 +32,24 @@ class ClaudeConsoleAccountService {
|
||||
isActive = true,
|
||||
accountType = 'shared', // 'dedicated' or 'shared'
|
||||
schedulable = true // 是否可被调度
|
||||
} = options;
|
||||
} = options
|
||||
|
||||
// 验证必填字段
|
||||
if (!apiUrl || !apiKey) {
|
||||
throw new Error('API URL and API Key are required for Claude Console account');
|
||||
throw new Error('API URL and API Key are required for Claude Console account')
|
||||
}
|
||||
|
||||
const accountId = uuidv4();
|
||||
|
||||
const accountId = uuidv4()
|
||||
|
||||
// 处理 supportedModels,确保向后兼容
|
||||
const processedModels = this._processModelMapping(supportedModels);
|
||||
|
||||
const processedModels = this._processModelMapping(supportedModels)
|
||||
|
||||
const accountData = {
|
||||
id: accountId,
|
||||
platform: 'claude-console',
|
||||
name,
|
||||
description,
|
||||
apiUrl: apiUrl,
|
||||
apiUrl,
|
||||
apiKey: this._encryptSensitiveData(apiKey),
|
||||
priority: priority.toString(),
|
||||
supportedModels: JSON.stringify(processedModels),
|
||||
@@ -67,24 +67,23 @@ class ClaudeConsoleAccountService {
|
||||
rateLimitStatus: '',
|
||||
// 调度控制
|
||||
schedulable: schedulable.toString()
|
||||
};
|
||||
}
|
||||
|
||||
const client = redis.getClientSafe()
|
||||
logger.debug(
|
||||
`[DEBUG] Saving account data to Redis with key: ${this.ACCOUNT_KEY_PREFIX}${accountId}`
|
||||
)
|
||||
logger.debug(`[DEBUG] Account data to save: ${JSON.stringify(accountData, null, 2)}`)
|
||||
|
||||
await client.hset(`${this.ACCOUNT_KEY_PREFIX}${accountId}`, accountData)
|
||||
|
||||
const client = redis.getClientSafe();
|
||||
logger.debug(`[DEBUG] Saving account data to Redis with key: ${this.ACCOUNT_KEY_PREFIX}${accountId}`);
|
||||
logger.debug(`[DEBUG] Account data to save: ${JSON.stringify(accountData, null, 2)}`);
|
||||
|
||||
await client.hset(
|
||||
`${this.ACCOUNT_KEY_PREFIX}${accountId}`,
|
||||
accountData
|
||||
);
|
||||
|
||||
// 如果是共享账户,添加到共享账户集合
|
||||
if (accountType === 'shared') {
|
||||
await client.sadd(this.SHARED_ACCOUNTS_KEY, accountId);
|
||||
await client.sadd(this.SHARED_ACCOUNTS_KEY, accountId)
|
||||
}
|
||||
|
||||
logger.success(`🏢 Created Claude Console account: ${name} (${accountId})`);
|
||||
|
||||
|
||||
logger.success(`🏢 Created Claude Console account: ${name} (${accountId})`)
|
||||
|
||||
return {
|
||||
id: accountId,
|
||||
name,
|
||||
@@ -99,22 +98,22 @@ class ClaudeConsoleAccountService {
|
||||
accountType,
|
||||
status: 'active',
|
||||
createdAt: accountData.createdAt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 📋 获取所有Claude Console账户
|
||||
async getAllAccounts() {
|
||||
try {
|
||||
const client = redis.getClientSafe();
|
||||
const keys = await client.keys(`${this.ACCOUNT_KEY_PREFIX}*`);
|
||||
const accounts = [];
|
||||
|
||||
const client = redis.getClientSafe()
|
||||
const keys = await client.keys(`${this.ACCOUNT_KEY_PREFIX}*`)
|
||||
const accounts = []
|
||||
|
||||
for (const key of keys) {
|
||||
const accountData = await client.hgetall(key);
|
||||
const accountData = await client.hgetall(key)
|
||||
if (accountData && Object.keys(accountData).length > 0) {
|
||||
// 获取限流状态信息
|
||||
const rateLimitInfo = this._getRateLimitInfo(accountData);
|
||||
|
||||
const rateLimitInfo = this._getRateLimitInfo(accountData)
|
||||
|
||||
accounts.push({
|
||||
id: accountData.id,
|
||||
platform: accountData.platform,
|
||||
@@ -134,356 +133,379 @@ class ClaudeConsoleAccountService {
|
||||
lastUsedAt: accountData.lastUsedAt,
|
||||
rateLimitStatus: rateLimitInfo,
|
||||
schedulable: accountData.schedulable !== 'false' // 默认为true,只有明确设置为false才不可调度
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return accounts;
|
||||
|
||||
return accounts
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to get Claude Console accounts:', error);
|
||||
throw error;
|
||||
logger.error('❌ Failed to get Claude Console accounts:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 🔍 获取单个账户(内部使用,包含敏感信息)
|
||||
async getAccount(accountId) {
|
||||
const client = redis.getClientSafe();
|
||||
logger.debug(`[DEBUG] Getting account data for ID: ${accountId}`);
|
||||
const accountData = await client.hgetall(`${this.ACCOUNT_KEY_PREFIX}${accountId}`);
|
||||
|
||||
const client = redis.getClientSafe()
|
||||
logger.debug(`[DEBUG] Getting account data for ID: ${accountId}`)
|
||||
const accountData = await client.hgetall(`${this.ACCOUNT_KEY_PREFIX}${accountId}`)
|
||||
|
||||
if (!accountData || Object.keys(accountData).length === 0) {
|
||||
logger.debug(`[DEBUG] No account data found for ID: ${accountId}`);
|
||||
return null;
|
||||
logger.debug(`[DEBUG] No account data found for ID: ${accountId}`)
|
||||
return null
|
||||
}
|
||||
|
||||
logger.debug(`[DEBUG] Raw account data keys: ${Object.keys(accountData).join(', ')}`);
|
||||
logger.debug(`[DEBUG] Raw supportedModels value: ${accountData.supportedModels}`);
|
||||
|
||||
|
||||
logger.debug(`[DEBUG] Raw account data keys: ${Object.keys(accountData).join(', ')}`)
|
||||
logger.debug(`[DEBUG] Raw supportedModels value: ${accountData.supportedModels}`)
|
||||
|
||||
// 解密敏感字段(只解密apiKey,apiUrl不加密)
|
||||
const decryptedKey = this._decryptSensitiveData(accountData.apiKey);
|
||||
logger.debug(`[DEBUG] URL exists: ${!!accountData.apiUrl}, Decrypted key exists: ${!!decryptedKey}`);
|
||||
|
||||
accountData.apiKey = decryptedKey;
|
||||
|
||||
const decryptedKey = this._decryptSensitiveData(accountData.apiKey)
|
||||
logger.debug(
|
||||
`[DEBUG] URL exists: ${!!accountData.apiUrl}, Decrypted key exists: ${!!decryptedKey}`
|
||||
)
|
||||
|
||||
accountData.apiKey = decryptedKey
|
||||
|
||||
// 解析JSON字段
|
||||
const parsedModels = JSON.parse(accountData.supportedModels || '[]');
|
||||
logger.debug(`[DEBUG] Parsed supportedModels: ${JSON.stringify(parsedModels)}`);
|
||||
|
||||
accountData.supportedModels = parsedModels;
|
||||
accountData.priority = parseInt(accountData.priority) || 50;
|
||||
accountData.rateLimitDuration = parseInt(accountData.rateLimitDuration) || 60;
|
||||
accountData.isActive = accountData.isActive === 'true';
|
||||
accountData.schedulable = accountData.schedulable !== 'false'; // 默认为true
|
||||
|
||||
const parsedModels = JSON.parse(accountData.supportedModels || '[]')
|
||||
logger.debug(`[DEBUG] Parsed supportedModels: ${JSON.stringify(parsedModels)}`)
|
||||
|
||||
accountData.supportedModels = parsedModels
|
||||
accountData.priority = parseInt(accountData.priority) || 50
|
||||
accountData.rateLimitDuration = parseInt(accountData.rateLimitDuration) || 60
|
||||
accountData.isActive = accountData.isActive === 'true'
|
||||
accountData.schedulable = accountData.schedulable !== 'false' // 默认为true
|
||||
|
||||
if (accountData.proxy) {
|
||||
accountData.proxy = JSON.parse(accountData.proxy);
|
||||
accountData.proxy = JSON.parse(accountData.proxy)
|
||||
}
|
||||
|
||||
logger.debug(`[DEBUG] Final account data - name: ${accountData.name}, hasApiUrl: ${!!accountData.apiUrl}, hasApiKey: ${!!accountData.apiKey}, supportedModels: ${JSON.stringify(accountData.supportedModels)}`);
|
||||
|
||||
return accountData;
|
||||
|
||||
logger.debug(
|
||||
`[DEBUG] Final account data - name: ${accountData.name}, hasApiUrl: ${!!accountData.apiUrl}, hasApiKey: ${!!accountData.apiKey}, supportedModels: ${JSON.stringify(accountData.supportedModels)}`
|
||||
)
|
||||
|
||||
return accountData
|
||||
}
|
||||
|
||||
// 📝 更新账户
|
||||
async updateAccount(accountId, updates) {
|
||||
try {
|
||||
const existingAccount = await this.getAccount(accountId);
|
||||
const existingAccount = await this.getAccount(accountId)
|
||||
if (!existingAccount) {
|
||||
throw new Error('Account not found');
|
||||
throw new Error('Account not found')
|
||||
}
|
||||
|
||||
const client = redis.getClientSafe();
|
||||
const updatedData = {};
|
||||
const client = redis.getClientSafe()
|
||||
const updatedData = {}
|
||||
|
||||
// 处理各个字段的更新
|
||||
logger.debug(`[DEBUG] Update request received with fields: ${Object.keys(updates).join(', ')}`);
|
||||
logger.debug(`[DEBUG] Updates content: ${JSON.stringify(updates, null, 2)}`);
|
||||
|
||||
if (updates.name !== undefined) updatedData.name = updates.name;
|
||||
if (updates.description !== undefined) updatedData.description = updates.description;
|
||||
logger.debug(
|
||||
`[DEBUG] Update request received with fields: ${Object.keys(updates).join(', ')}`
|
||||
)
|
||||
logger.debug(`[DEBUG] Updates content: ${JSON.stringify(updates, null, 2)}`)
|
||||
|
||||
if (updates.name !== undefined) {
|
||||
updatedData.name = updates.name
|
||||
}
|
||||
if (updates.description !== undefined) {
|
||||
updatedData.description = updates.description
|
||||
}
|
||||
if (updates.apiUrl !== undefined) {
|
||||
logger.debug(`[DEBUG] Updating apiUrl from frontend: ${updates.apiUrl}`);
|
||||
updatedData.apiUrl = updates.apiUrl;
|
||||
logger.debug(`[DEBUG] Updating apiUrl from frontend: ${updates.apiUrl}`)
|
||||
updatedData.apiUrl = updates.apiUrl
|
||||
}
|
||||
if (updates.apiKey !== undefined) {
|
||||
logger.debug(`[DEBUG] Updating apiKey (length: ${updates.apiKey?.length})`);
|
||||
updatedData.apiKey = this._encryptSensitiveData(updates.apiKey);
|
||||
logger.debug(`[DEBUG] Updating apiKey (length: ${updates.apiKey?.length})`)
|
||||
updatedData.apiKey = this._encryptSensitiveData(updates.apiKey)
|
||||
}
|
||||
if (updates.priority !== undefined) {
|
||||
updatedData.priority = updates.priority.toString()
|
||||
}
|
||||
if (updates.priority !== undefined) updatedData.priority = updates.priority.toString();
|
||||
if (updates.supportedModels !== undefined) {
|
||||
logger.debug(`[DEBUG] Updating supportedModels: ${JSON.stringify(updates.supportedModels)}`);
|
||||
logger.debug(`[DEBUG] Updating supportedModels: ${JSON.stringify(updates.supportedModels)}`)
|
||||
// 处理 supportedModels,确保向后兼容
|
||||
const processedModels = this._processModelMapping(updates.supportedModels);
|
||||
updatedData.supportedModels = JSON.stringify(processedModels);
|
||||
const processedModels = this._processModelMapping(updates.supportedModels)
|
||||
updatedData.supportedModels = JSON.stringify(processedModels)
|
||||
}
|
||||
if (updates.userAgent !== undefined) {
|
||||
updatedData.userAgent = updates.userAgent
|
||||
}
|
||||
if (updates.rateLimitDuration !== undefined) {
|
||||
updatedData.rateLimitDuration = updates.rateLimitDuration.toString()
|
||||
}
|
||||
if (updates.proxy !== undefined) {
|
||||
updatedData.proxy = updates.proxy ? JSON.stringify(updates.proxy) : ''
|
||||
}
|
||||
if (updates.isActive !== undefined) {
|
||||
updatedData.isActive = updates.isActive.toString()
|
||||
}
|
||||
if (updates.schedulable !== undefined) {
|
||||
updatedData.schedulable = updates.schedulable.toString()
|
||||
}
|
||||
if (updates.userAgent !== undefined) updatedData.userAgent = updates.userAgent;
|
||||
if (updates.rateLimitDuration !== undefined) updatedData.rateLimitDuration = updates.rateLimitDuration.toString();
|
||||
if (updates.proxy !== undefined) updatedData.proxy = updates.proxy ? JSON.stringify(updates.proxy) : '';
|
||||
if (updates.isActive !== undefined) updatedData.isActive = updates.isActive.toString();
|
||||
if (updates.schedulable !== undefined) updatedData.schedulable = updates.schedulable.toString();
|
||||
|
||||
// 处理账户类型变更
|
||||
if (updates.accountType && updates.accountType !== existingAccount.accountType) {
|
||||
updatedData.accountType = updates.accountType;
|
||||
|
||||
updatedData.accountType = updates.accountType
|
||||
|
||||
if (updates.accountType === 'shared') {
|
||||
await client.sadd(this.SHARED_ACCOUNTS_KEY, accountId);
|
||||
await client.sadd(this.SHARED_ACCOUNTS_KEY, accountId)
|
||||
} else {
|
||||
await client.srem(this.SHARED_ACCOUNTS_KEY, accountId);
|
||||
await client.srem(this.SHARED_ACCOUNTS_KEY, accountId)
|
||||
}
|
||||
}
|
||||
|
||||
updatedData.updatedAt = new Date().toISOString();
|
||||
|
||||
logger.debug(`[DEBUG] Final updatedData to save: ${JSON.stringify(updatedData, null, 2)}`);
|
||||
logger.debug(`[DEBUG] Updating Redis key: ${this.ACCOUNT_KEY_PREFIX}${accountId}`);
|
||||
|
||||
await client.hset(
|
||||
`${this.ACCOUNT_KEY_PREFIX}${accountId}`,
|
||||
updatedData
|
||||
);
|
||||
|
||||
logger.success(`📝 Updated Claude Console account: ${accountId}`);
|
||||
|
||||
return { success: true };
|
||||
updatedData.updatedAt = new Date().toISOString()
|
||||
|
||||
logger.debug(`[DEBUG] Final updatedData to save: ${JSON.stringify(updatedData, null, 2)}`)
|
||||
logger.debug(`[DEBUG] Updating Redis key: ${this.ACCOUNT_KEY_PREFIX}${accountId}`)
|
||||
|
||||
await client.hset(`${this.ACCOUNT_KEY_PREFIX}${accountId}`, updatedData)
|
||||
|
||||
logger.success(`📝 Updated Claude Console account: ${accountId}`)
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to update Claude Console account:', error);
|
||||
throw error;
|
||||
logger.error('❌ Failed to update Claude Console account:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 🗑️ 删除账户
|
||||
async deleteAccount(accountId) {
|
||||
try {
|
||||
const client = redis.getClientSafe();
|
||||
const account = await this.getAccount(accountId);
|
||||
|
||||
const client = redis.getClientSafe()
|
||||
const account = await this.getAccount(accountId)
|
||||
|
||||
if (!account) {
|
||||
throw new Error('Account not found');
|
||||
throw new Error('Account not found')
|
||||
}
|
||||
|
||||
|
||||
// 从Redis删除
|
||||
await client.del(`${this.ACCOUNT_KEY_PREFIX}${accountId}`);
|
||||
|
||||
await client.del(`${this.ACCOUNT_KEY_PREFIX}${accountId}`)
|
||||
|
||||
// 从共享账户集合中移除
|
||||
if (account.accountType === 'shared') {
|
||||
await client.srem(this.SHARED_ACCOUNTS_KEY, accountId);
|
||||
await client.srem(this.SHARED_ACCOUNTS_KEY, accountId)
|
||||
}
|
||||
|
||||
logger.success(`🗑️ Deleted Claude Console account: ${accountId}`);
|
||||
|
||||
return { success: true };
|
||||
|
||||
logger.success(`🗑️ Deleted Claude Console account: ${accountId}`)
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to delete Claude Console account:', error);
|
||||
throw error;
|
||||
logger.error('❌ Failed to delete Claude Console account:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 🚫 标记账号为限流状态
|
||||
async markAccountRateLimited(accountId) {
|
||||
try {
|
||||
const client = redis.getClientSafe();
|
||||
const account = await this.getAccount(accountId);
|
||||
|
||||
const client = redis.getClientSafe()
|
||||
const account = await this.getAccount(accountId)
|
||||
|
||||
if (!account) {
|
||||
throw new Error('Account not found');
|
||||
throw new Error('Account not found')
|
||||
}
|
||||
|
||||
const updates = {
|
||||
rateLimitedAt: new Date().toISOString(),
|
||||
rateLimitStatus: 'limited'
|
||||
};
|
||||
}
|
||||
|
||||
await client.hset(
|
||||
`${this.ACCOUNT_KEY_PREFIX}${accountId}`,
|
||||
updates
|
||||
);
|
||||
await client.hset(`${this.ACCOUNT_KEY_PREFIX}${accountId}`, updates)
|
||||
|
||||
logger.warn(`🚫 Claude Console account marked as rate limited: ${account.name} (${accountId})`);
|
||||
return { success: true };
|
||||
logger.warn(
|
||||
`🚫 Claude Console account marked as rate limited: ${account.name} (${accountId})`
|
||||
)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
logger.error(`❌ Failed to mark Claude Console account as rate limited: ${accountId}`, error);
|
||||
throw error;
|
||||
logger.error(`❌ Failed to mark Claude Console account as rate limited: ${accountId}`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 移除账号的限流状态
|
||||
async removeAccountRateLimit(accountId) {
|
||||
try {
|
||||
const client = redis.getClientSafe();
|
||||
|
||||
const client = redis.getClientSafe()
|
||||
|
||||
await client.hdel(
|
||||
`${this.ACCOUNT_KEY_PREFIX}${accountId}`,
|
||||
'rateLimitedAt',
|
||||
'rateLimitStatus'
|
||||
);
|
||||
)
|
||||
|
||||
logger.success(`✅ Rate limit removed for Claude Console account: ${accountId}`);
|
||||
return { success: true };
|
||||
logger.success(`✅ Rate limit removed for Claude Console account: ${accountId}`)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
logger.error(`❌ Failed to remove rate limit for Claude Console account: ${accountId}`, error);
|
||||
throw error;
|
||||
logger.error(`❌ Failed to remove rate limit for Claude Console account: ${accountId}`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 🔍 检查账号是否处于限流状态
|
||||
async isAccountRateLimited(accountId) {
|
||||
try {
|
||||
const account = await this.getAccount(accountId);
|
||||
const account = await this.getAccount(accountId)
|
||||
if (!account) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
if (account.rateLimitStatus === 'limited' && account.rateLimitedAt) {
|
||||
const rateLimitedAt = new Date(account.rateLimitedAt);
|
||||
const now = new Date();
|
||||
const minutesSinceRateLimit = (now - rateLimitedAt) / (1000 * 60);
|
||||
const rateLimitedAt = new Date(account.rateLimitedAt)
|
||||
const now = new Date()
|
||||
const minutesSinceRateLimit = (now - rateLimitedAt) / (1000 * 60)
|
||||
|
||||
// 使用账户配置的限流时间
|
||||
const rateLimitDuration = account.rateLimitDuration || 60;
|
||||
|
||||
const rateLimitDuration = account.rateLimitDuration || 60
|
||||
|
||||
if (minutesSinceRateLimit >= rateLimitDuration) {
|
||||
await this.removeAccountRateLimit(accountId);
|
||||
return false;
|
||||
await this.removeAccountRateLimit(accountId)
|
||||
return false
|
||||
}
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
return false;
|
||||
return false
|
||||
} catch (error) {
|
||||
logger.error(`❌ Failed to check rate limit status for Claude Console account: ${accountId}`, error);
|
||||
return false;
|
||||
logger.error(
|
||||
`❌ Failed to check rate limit status for Claude Console account: ${accountId}`,
|
||||
error
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 🚫 标记账号为封锁状态(模型不支持等原因)
|
||||
async blockAccount(accountId, reason) {
|
||||
try {
|
||||
const client = redis.getClientSafe();
|
||||
|
||||
const client = redis.getClientSafe()
|
||||
|
||||
const updates = {
|
||||
status: 'blocked',
|
||||
errorMessage: reason,
|
||||
blockedAt: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
await client.hset(
|
||||
`${this.ACCOUNT_KEY_PREFIX}${accountId}`,
|
||||
updates
|
||||
);
|
||||
await client.hset(`${this.ACCOUNT_KEY_PREFIX}${accountId}`, updates)
|
||||
|
||||
logger.warn(`🚫 Claude Console account blocked: ${accountId} - ${reason}`);
|
||||
return { success: true };
|
||||
logger.warn(`🚫 Claude Console account blocked: ${accountId} - ${reason}`)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
logger.error(`❌ Failed to block Claude Console account: ${accountId}`, error);
|
||||
throw error;
|
||||
logger.error(`❌ Failed to block Claude Console account: ${accountId}`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 🌐 创建代理agent
|
||||
_createProxyAgent(proxyConfig) {
|
||||
if (!proxyConfig) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig;
|
||||
|
||||
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
|
||||
|
||||
if (proxy.type === 'socks5') {
|
||||
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : '';
|
||||
const socksUrl = `socks5://${auth}${proxy.host}:${proxy.port}`;
|
||||
return new SocksProxyAgent(socksUrl);
|
||||
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
|
||||
const socksUrl = `socks5://${auth}${proxy.host}:${proxy.port}`
|
||||
return new SocksProxyAgent(socksUrl)
|
||||
} else if (proxy.type === 'http' || proxy.type === 'https') {
|
||||
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : '';
|
||||
const httpUrl = `${proxy.type}://${auth}${proxy.host}:${proxy.port}`;
|
||||
return new HttpsProxyAgent(httpUrl);
|
||||
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
|
||||
const httpUrl = `${proxy.type}://${auth}${proxy.host}:${proxy.port}`
|
||||
return new HttpsProxyAgent(httpUrl)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('⚠️ Invalid proxy configuration:', error);
|
||||
logger.warn('⚠️ Invalid proxy configuration:', error)
|
||||
}
|
||||
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
// 🔐 加密敏感数据
|
||||
_encryptSensitiveData(data) {
|
||||
if (!data) return '';
|
||||
|
||||
if (!data) {
|
||||
return ''
|
||||
}
|
||||
|
||||
try {
|
||||
const key = this._generateEncryptionKey();
|
||||
const iv = crypto.randomBytes(16);
|
||||
|
||||
const cipher = crypto.createCipheriv(this.ENCRYPTION_ALGORITHM, key, iv);
|
||||
let encrypted = cipher.update(data, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
|
||||
return iv.toString('hex') + ':' + encrypted;
|
||||
const key = this._generateEncryptionKey()
|
||||
const iv = crypto.randomBytes(16)
|
||||
|
||||
const cipher = crypto.createCipheriv(this.ENCRYPTION_ALGORITHM, key, iv)
|
||||
let encrypted = cipher.update(data, 'utf8', 'hex')
|
||||
encrypted += cipher.final('hex')
|
||||
|
||||
return `${iv.toString('hex')}:${encrypted}`
|
||||
} catch (error) {
|
||||
logger.error('❌ Encryption error:', error);
|
||||
return data;
|
||||
logger.error('❌ Encryption error:', error)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
// 🔓 解密敏感数据
|
||||
_decryptSensitiveData(encryptedData) {
|
||||
if (!encryptedData) return '';
|
||||
|
||||
if (!encryptedData) {
|
||||
return ''
|
||||
}
|
||||
|
||||
try {
|
||||
if (encryptedData.includes(':')) {
|
||||
const parts = encryptedData.split(':');
|
||||
const parts = encryptedData.split(':')
|
||||
if (parts.length === 2) {
|
||||
const key = this._generateEncryptionKey();
|
||||
const iv = Buffer.from(parts[0], 'hex');
|
||||
const encrypted = parts[1];
|
||||
|
||||
const decipher = crypto.createDecipheriv(this.ENCRYPTION_ALGORITHM, key, iv);
|
||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
return decrypted;
|
||||
const key = this._generateEncryptionKey()
|
||||
const iv = Buffer.from(parts[0], 'hex')
|
||||
const encrypted = parts[1]
|
||||
|
||||
const decipher = crypto.createDecipheriv(this.ENCRYPTION_ALGORITHM, key, iv)
|
||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
|
||||
decrypted += decipher.final('utf8')
|
||||
return decrypted
|
||||
}
|
||||
}
|
||||
|
||||
return encryptedData;
|
||||
|
||||
return encryptedData
|
||||
} catch (error) {
|
||||
logger.error('❌ Decryption error:', error);
|
||||
return encryptedData;
|
||||
logger.error('❌ Decryption error:', error)
|
||||
return encryptedData
|
||||
}
|
||||
}
|
||||
|
||||
// 🔑 生成加密密钥
|
||||
_generateEncryptionKey() {
|
||||
return crypto.scryptSync(config.security.encryptionKey, this.ENCRYPTION_SALT, 32);
|
||||
return crypto.scryptSync(config.security.encryptionKey, this.ENCRYPTION_SALT, 32)
|
||||
}
|
||||
|
||||
// 🎭 掩码API URL
|
||||
_maskApiUrl(apiUrl) {
|
||||
if (!apiUrl) return '';
|
||||
|
||||
if (!apiUrl) {
|
||||
return ''
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(apiUrl);
|
||||
return `${url.protocol}//${url.hostname}/***`;
|
||||
const url = new URL(apiUrl)
|
||||
return `${url.protocol}//${url.hostname}/***`
|
||||
} catch {
|
||||
return '***';
|
||||
return '***'
|
||||
}
|
||||
}
|
||||
|
||||
// 📊 获取限流信息
|
||||
_getRateLimitInfo(accountData) {
|
||||
if (accountData.rateLimitStatus === 'limited' && accountData.rateLimitedAt) {
|
||||
const rateLimitedAt = new Date(accountData.rateLimitedAt);
|
||||
const now = new Date();
|
||||
const minutesSinceRateLimit = Math.floor((now - rateLimitedAt) / (1000 * 60));
|
||||
const rateLimitDuration = parseInt(accountData.rateLimitDuration) || 60;
|
||||
const minutesRemaining = Math.max(0, rateLimitDuration - minutesSinceRateLimit);
|
||||
const rateLimitedAt = new Date(accountData.rateLimitedAt)
|
||||
const now = new Date()
|
||||
const minutesSinceRateLimit = Math.floor((now - rateLimitedAt) / (1000 * 60))
|
||||
const rateLimitDuration = parseInt(accountData.rateLimitDuration) || 60
|
||||
const minutesRemaining = Math.max(0, rateLimitDuration - minutesSinceRateLimit)
|
||||
|
||||
return {
|
||||
isRateLimited: minutesRemaining > 0,
|
||||
rateLimitedAt: accountData.rateLimitedAt,
|
||||
minutesSinceRateLimit,
|
||||
minutesRemaining
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -491,57 +513,57 @@ class ClaudeConsoleAccountService {
|
||||
rateLimitedAt: null,
|
||||
minutesSinceRateLimit: 0,
|
||||
minutesRemaining: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 🔄 处理模型映射,确保向后兼容
|
||||
_processModelMapping(supportedModels) {
|
||||
// 如果是空值,返回空对象(支持所有模型)
|
||||
if (!supportedModels || (Array.isArray(supportedModels) && supportedModels.length === 0)) {
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
// 如果已经是对象格式(新的映射表格式),直接返回
|
||||
if (typeof supportedModels === 'object' && !Array.isArray(supportedModels)) {
|
||||
return supportedModels;
|
||||
return supportedModels
|
||||
}
|
||||
|
||||
// 如果是数组格式(旧格式),转换为映射表
|
||||
if (Array.isArray(supportedModels)) {
|
||||
const mapping = {};
|
||||
supportedModels.forEach(model => {
|
||||
const mapping = {}
|
||||
supportedModels.forEach((model) => {
|
||||
if (model && typeof model === 'string') {
|
||||
mapping[model] = model; // 映射到自身
|
||||
mapping[model] = model // 映射到自身
|
||||
}
|
||||
});
|
||||
return mapping;
|
||||
})
|
||||
return mapping
|
||||
}
|
||||
|
||||
// 其他情况返回空对象
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
// 🔍 检查模型是否支持(用于调度)
|
||||
isModelSupported(modelMapping, requestedModel) {
|
||||
// 如果映射表为空,支持所有模型
|
||||
if (!modelMapping || Object.keys(modelMapping).length === 0) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查请求的模型是否在映射表的键中
|
||||
return Object.prototype.hasOwnProperty.call(modelMapping, requestedModel);
|
||||
return Object.prototype.hasOwnProperty.call(modelMapping, requestedModel)
|
||||
}
|
||||
|
||||
// 🔄 获取映射后的模型名称
|
||||
getMappedModel(modelMapping, requestedModel) {
|
||||
// 如果映射表为空,返回原模型
|
||||
if (!modelMapping || Object.keys(modelMapping).length === 0) {
|
||||
return requestedModel;
|
||||
return requestedModel
|
||||
}
|
||||
|
||||
// 返回映射后的模型,如果不存在则返回原模型
|
||||
return modelMapping[requestedModel] || requestedModel;
|
||||
return modelMapping[requestedModel] || requestedModel
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ClaudeConsoleAccountService();
|
||||
module.exports = new ClaudeConsoleAccountService()
|
||||
|
||||
Reference in New Issue
Block a user