mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
405 lines
13 KiB
JavaScript
405 lines
13 KiB
JavaScript
const { v4: uuidv4 } = require('uuid');
|
||
const crypto = require('crypto');
|
||
const redis = require('../models/redis');
|
||
const logger = require('../utils/logger');
|
||
const config = require('../../config/config');
|
||
const bedrockRelayService = require('./bedrockRelayService');
|
||
|
||
class BedrockAccountService {
|
||
constructor() {
|
||
// 加密相关常量
|
||
this.ENCRYPTION_ALGORITHM = 'aes-256-cbc';
|
||
this.ENCRYPTION_SALT = 'salt';
|
||
}
|
||
|
||
// 🏢 创建Bedrock账户
|
||
async createAccount(options = {}) {
|
||
const {
|
||
name = 'Unnamed Bedrock Account',
|
||
description = '',
|
||
region = process.env.AWS_REGION || 'us-east-1',
|
||
awsCredentials = null, // { accessKeyId, secretAccessKey, sessionToken }
|
||
defaultModel = 'us.anthropic.claude-sonnet-4-20250514-v1:0',
|
||
isActive = true,
|
||
accountType = 'shared', // 'dedicated' or 'shared'
|
||
priority = 50, // 调度优先级 (1-100,数字越小优先级越高)
|
||
schedulable = true, // 是否可被调度
|
||
credentialType = 'default' // 'default', 'access_key', 'bearer_token'
|
||
} = options;
|
||
|
||
const accountId = uuidv4();
|
||
|
||
let accountData = {
|
||
id: accountId,
|
||
name,
|
||
description,
|
||
region,
|
||
defaultModel,
|
||
isActive,
|
||
accountType,
|
||
priority,
|
||
schedulable,
|
||
credentialType,
|
||
createdAt: new Date().toISOString(),
|
||
updatedAt: new Date().toISOString(),
|
||
type: 'bedrock' // 标识这是Bedrock账户
|
||
};
|
||
|
||
// 加密存储AWS凭证
|
||
if (awsCredentials) {
|
||
accountData.awsCredentials = this._encryptAwsCredentials(awsCredentials);
|
||
}
|
||
|
||
const client = redis.getClientSafe();
|
||
await client.set(`bedrock_account:${accountId}`, JSON.stringify(accountData));
|
||
|
||
logger.info(`✅ 创建Bedrock账户成功 - ID: ${accountId}, 名称: ${name}, 区域: ${region}`);
|
||
|
||
return {
|
||
success: true,
|
||
data: {
|
||
id: accountId,
|
||
name,
|
||
description,
|
||
region,
|
||
defaultModel,
|
||
isActive,
|
||
accountType,
|
||
priority,
|
||
schedulable,
|
||
credentialType,
|
||
createdAt: accountData.createdAt,
|
||
type: 'bedrock'
|
||
}
|
||
};
|
||
}
|
||
|
||
// 🔍 获取账户信息
|
||
async getAccount(accountId) {
|
||
try {
|
||
const client = redis.getClientSafe();
|
||
const accountData = await client.get(`bedrock_account:${accountId}`);
|
||
if (!accountData) {
|
||
return { success: false, error: 'Account not found' };
|
||
}
|
||
|
||
const account = JSON.parse(accountData);
|
||
|
||
// 解密AWS凭证用于内部使用
|
||
if (account.awsCredentials) {
|
||
account.awsCredentials = this._decryptAwsCredentials(account.awsCredentials);
|
||
}
|
||
|
||
logger.debug(`🔍 获取Bedrock账户 - ID: ${accountId}, 名称: ${account.name}`);
|
||
|
||
return {
|
||
success: true,
|
||
data: account
|
||
};
|
||
} catch (error) {
|
||
logger.error(`❌ 获取Bedrock账户失败 - ID: ${accountId}`, error);
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
// 📋 获取所有账户列表
|
||
async getAllAccounts() {
|
||
try {
|
||
const client = redis.getClientSafe();
|
||
const keys = await client.keys('bedrock_account:*');
|
||
const accounts = [];
|
||
|
||
for (const key of keys) {
|
||
const accountData = await client.get(key);
|
||
if (accountData) {
|
||
const account = JSON.parse(accountData);
|
||
|
||
// 返回给前端时,不包含敏感信息,只显示掩码
|
||
accounts.push({
|
||
id: account.id,
|
||
name: account.name,
|
||
description: account.description,
|
||
region: account.region,
|
||
defaultModel: account.defaultModel,
|
||
isActive: account.isActive,
|
||
accountType: account.accountType,
|
||
priority: account.priority,
|
||
schedulable: account.schedulable,
|
||
credentialType: account.credentialType,
|
||
createdAt: account.createdAt,
|
||
updatedAt: account.updatedAt,
|
||
type: 'bedrock',
|
||
hasCredentials: !!account.awsCredentials
|
||
});
|
||
}
|
||
}
|
||
|
||
// 按优先级和名称排序
|
||
accounts.sort((a, b) => {
|
||
if (a.priority !== b.priority) return a.priority - b.priority;
|
||
return a.name.localeCompare(b.name);
|
||
});
|
||
|
||
logger.debug(`📋 获取所有Bedrock账户 - 共 ${accounts.length} 个`);
|
||
|
||
return {
|
||
success: true,
|
||
data: accounts
|
||
};
|
||
} catch (error) {
|
||
logger.error('❌ 获取Bedrock账户列表失败', error);
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
// ✏️ 更新账户信息
|
||
async updateAccount(accountId, updates = {}) {
|
||
try {
|
||
const accountResult = await this.getAccount(accountId);
|
||
if (!accountResult.success) {
|
||
return accountResult;
|
||
}
|
||
|
||
const account = accountResult.data;
|
||
|
||
// 更新字段
|
||
if (updates.name !== undefined) account.name = updates.name;
|
||
if (updates.description !== undefined) account.description = updates.description;
|
||
if (updates.region !== undefined) account.region = updates.region;
|
||
if (updates.defaultModel !== undefined) account.defaultModel = updates.defaultModel;
|
||
if (updates.isActive !== undefined) account.isActive = updates.isActive;
|
||
if (updates.accountType !== undefined) account.accountType = updates.accountType;
|
||
if (updates.priority !== undefined) account.priority = updates.priority;
|
||
if (updates.schedulable !== undefined) account.schedulable = updates.schedulable;
|
||
if (updates.credentialType !== undefined) account.credentialType = updates.credentialType;
|
||
|
||
// 更新AWS凭证
|
||
if (updates.awsCredentials !== undefined) {
|
||
if (updates.awsCredentials) {
|
||
account.awsCredentials = this._encryptAwsCredentials(updates.awsCredentials);
|
||
} else {
|
||
delete account.awsCredentials;
|
||
}
|
||
}
|
||
|
||
account.updatedAt = new Date().toISOString();
|
||
|
||
const client = redis.getClientSafe();
|
||
await client.set(`bedrock_account:${accountId}`, JSON.stringify(account));
|
||
|
||
logger.info(`✅ 更新Bedrock账户成功 - ID: ${accountId}, 名称: ${account.name}`);
|
||
|
||
return {
|
||
success: true,
|
||
data: {
|
||
id: account.id,
|
||
name: account.name,
|
||
description: account.description,
|
||
region: account.region,
|
||
defaultModel: account.defaultModel,
|
||
isActive: account.isActive,
|
||
accountType: account.accountType,
|
||
priority: account.priority,
|
||
schedulable: account.schedulable,
|
||
credentialType: account.credentialType,
|
||
updatedAt: account.updatedAt,
|
||
type: 'bedrock'
|
||
}
|
||
};
|
||
} catch (error) {
|
||
logger.error(`❌ 更新Bedrock账户失败 - ID: ${accountId}`, error);
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
// 🗑️ 删除账户
|
||
async deleteAccount(accountId) {
|
||
try {
|
||
const accountResult = await this.getAccount(accountId);
|
||
if (!accountResult.success) {
|
||
return accountResult;
|
||
}
|
||
|
||
const client = redis.getClientSafe();
|
||
await client.del(`bedrock_account:${accountId}`);
|
||
|
||
logger.info(`✅ 删除Bedrock账户成功 - ID: ${accountId}`);
|
||
|
||
return { success: true };
|
||
} catch (error) {
|
||
logger.error(`❌ 删除Bedrock账户失败 - ID: ${accountId}`, error);
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
// 🎯 选择可用的Bedrock账户 (用于请求转发)
|
||
async selectAvailableAccount() {
|
||
try {
|
||
const accountsResult = await this.getAllAccounts();
|
||
if (!accountsResult.success) {
|
||
return { success: false, error: 'Failed to get accounts' };
|
||
}
|
||
|
||
const availableAccounts = accountsResult.data.filter(account =>
|
||
account.isActive && account.schedulable
|
||
);
|
||
|
||
if (availableAccounts.length === 0) {
|
||
return { success: false, error: 'No available Bedrock accounts' };
|
||
}
|
||
|
||
// 简单的轮询选择策略 - 选择优先级最高的账户
|
||
const selectedAccount = availableAccounts[0];
|
||
|
||
// 获取完整账户信息(包含解密的凭证)
|
||
const fullAccountResult = await this.getAccount(selectedAccount.id);
|
||
if (!fullAccountResult.success) {
|
||
return { success: false, error: 'Failed to get selected account details' };
|
||
}
|
||
|
||
logger.debug(`🎯 选择Bedrock账户 - ID: ${selectedAccount.id}, 名称: ${selectedAccount.name}`);
|
||
|
||
return {
|
||
success: true,
|
||
data: fullAccountResult.data
|
||
};
|
||
} catch (error) {
|
||
logger.error('❌ 选择Bedrock账户失败', error);
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
// 🧪 测试账户连接
|
||
async testAccount(accountId) {
|
||
try {
|
||
const accountResult = await this.getAccount(accountId);
|
||
if (!accountResult.success) {
|
||
return accountResult;
|
||
}
|
||
|
||
const account = accountResult.data;
|
||
|
||
logger.info(`🧪 测试Bedrock账户连接 - ID: ${accountId}, 名称: ${account.name}`);
|
||
|
||
// 尝试获取模型列表来测试连接
|
||
const models = await bedrockRelayService.getAvailableModels(account);
|
||
|
||
if (models && models.length > 0) {
|
||
logger.info(`✅ Bedrock账户测试成功 - ID: ${accountId}, 发现 ${models.length} 个模型`);
|
||
return {
|
||
success: true,
|
||
data: {
|
||
status: 'connected',
|
||
modelsCount: models.length,
|
||
region: account.region,
|
||
credentialType: account.credentialType
|
||
}
|
||
};
|
||
} else {
|
||
return {
|
||
success: false,
|
||
error: 'Unable to retrieve models from Bedrock'
|
||
};
|
||
}
|
||
} catch (error) {
|
||
logger.error(`❌ 测试Bedrock账户失败 - ID: ${accountId}`, error);
|
||
return {
|
||
success: false,
|
||
error: error.message
|
||
};
|
||
}
|
||
}
|
||
|
||
// 🔐 加密AWS凭证
|
||
_encryptAwsCredentials(credentials) {
|
||
try {
|
||
const key = crypto.createHash('sha256').update(config.security.encryptionKey).digest();
|
||
const iv = crypto.randomBytes(16);
|
||
const cipher = crypto.createCipheriv(this.ENCRYPTION_ALGORITHM, key, iv);
|
||
|
||
const credentialsString = JSON.stringify(credentials);
|
||
let encrypted = cipher.update(credentialsString, 'utf8', 'hex');
|
||
encrypted += cipher.final('hex');
|
||
|
||
return {
|
||
encrypted: encrypted,
|
||
iv: iv.toString('hex')
|
||
};
|
||
} catch (error) {
|
||
logger.error('❌ AWS凭证加密失败', error);
|
||
throw new Error('Credentials encryption failed');
|
||
}
|
||
}
|
||
|
||
// 🔓 解密AWS凭证
|
||
_decryptAwsCredentials(encryptedData) {
|
||
try {
|
||
// 检查数据格式
|
||
if (!encryptedData || typeof encryptedData !== 'object') {
|
||
logger.error('❌ 无效的加密数据格式:', encryptedData);
|
||
throw new Error('Invalid encrypted data format');
|
||
}
|
||
|
||
// 检查是否为加密格式 (有 encrypted 和 iv 字段)
|
||
if (encryptedData.encrypted && encryptedData.iv) {
|
||
// 加密数据 - 进行解密
|
||
const key = crypto.createHash('sha256').update(config.security.encryptionKey).digest();
|
||
const iv = Buffer.from(encryptedData.iv, 'hex');
|
||
const decipher = crypto.createDecipheriv(this.ENCRYPTION_ALGORITHM, key, iv);
|
||
|
||
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
|
||
decrypted += decipher.final('utf8');
|
||
|
||
return JSON.parse(decrypted);
|
||
} else if (encryptedData.accessKeyId) {
|
||
// 纯文本数据 - 直接返回 (向后兼容)
|
||
logger.warn('⚠️ 发现未加密的AWS凭证,建议更新账户以启用加密');
|
||
return encryptedData;
|
||
} else {
|
||
// 既不是加密格式也不是有效的凭证格式
|
||
logger.error('❌ 缺少加密数据字段:', {
|
||
hasEncrypted: !!encryptedData.encrypted,
|
||
hasIv: !!encryptedData.iv,
|
||
hasAccessKeyId: !!encryptedData.accessKeyId
|
||
});
|
||
throw new Error('Missing encrypted data fields or valid credentials');
|
||
}
|
||
} catch (error) {
|
||
logger.error('❌ AWS凭证解密失败', error);
|
||
throw new Error('Credentials decryption failed');
|
||
}
|
||
}
|
||
|
||
// 🔍 获取账户统计信息
|
||
async getAccountStats() {
|
||
try {
|
||
const accountsResult = await this.getAllAccounts();
|
||
if (!accountsResult.success) {
|
||
return { success: false, error: accountsResult.error };
|
||
}
|
||
|
||
const accounts = accountsResult.data;
|
||
const stats = {
|
||
total: accounts.length,
|
||
active: accounts.filter(acc => acc.isActive).length,
|
||
inactive: accounts.filter(acc => !acc.isActive).length,
|
||
schedulable: accounts.filter(acc => acc.schedulable).length,
|
||
byRegion: {},
|
||
byCredentialType: {}
|
||
};
|
||
|
||
// 按区域统计
|
||
accounts.forEach(acc => {
|
||
stats.byRegion[acc.region] = (stats.byRegion[acc.region] || 0) + 1;
|
||
stats.byCredentialType[acc.credentialType] = (stats.byCredentialType[acc.credentialType] || 0) + 1;
|
||
});
|
||
|
||
return { success: true, data: stats };
|
||
} catch (error) {
|
||
logger.error('❌ 获取Bedrock账户统计失败', error);
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
}
|
||
|
||
module.exports = new BedrockAccountService(); |