diff --git a/scripts/check-redis-keys.js b/scripts/check-redis-keys.js new file mode 100644 index 00000000..5201dfaf --- /dev/null +++ b/scripts/check-redis-keys.js @@ -0,0 +1,52 @@ +/** + * 检查 Redis 中的所有键 + */ + +const redis = require('../src/models/redis'); + +async function checkRedisKeys() { + console.log('🔍 检查 Redis 中的所有键...\n'); + + try { + // 确保 Redis 已连接 + await redis.connect(); + + // 获取所有键 + const allKeys = await redis.client.keys('*'); + console.log(`找到 ${allKeys.length} 个键\n`); + + // 按类型分组 + const keysByType = {}; + + allKeys.forEach(key => { + const prefix = key.split(':')[0]; + if (!keysByType[prefix]) { + keysByType[prefix] = []; + } + keysByType[prefix].push(key); + }); + + // 显示各类型的键 + Object.keys(keysByType).sort().forEach(type => { + console.log(`\n📁 ${type}: ${keysByType[type].length} 个`); + + // 显示前 5 个键作为示例 + const keysToShow = keysByType[type].slice(0, 5); + keysToShow.forEach(key => { + console.log(` - ${key}`); + }); + + if (keysByType[type].length > 5) { + console.log(` ... 还有 ${keysByType[type].length - 5} 个`); + } + }); + + } catch (error) { + console.error('❌ 错误:', error); + console.error(error.stack); + } finally { + process.exit(0); + } +} + +checkRedisKeys(); \ No newline at end of file diff --git a/scripts/test-account-display.js b/scripts/test-account-display.js new file mode 100644 index 00000000..cd36647a --- /dev/null +++ b/scripts/test-account-display.js @@ -0,0 +1,144 @@ +/** + * 测试账号显示问题是否已修复 + */ + +const axios = require('axios'); +const config = require('../config/config'); + +// 从 init.json 读取管理员凭据 +const fs = require('fs'); +const path = require('path'); + +async function testAccountDisplay() { + console.log('🔍 测试账号显示问题...\n'); + + try { + // 读取管理员凭据 + const initPath = path.join(__dirname, '..', 'config', 'init.json'); + if (!fs.existsSync(initPath)) { + console.error('❌ 找不到 init.json 文件,请运行 npm run setup'); + process.exit(1); + } + + const initData = JSON.parse(fs.readFileSync(initPath, 'utf8')); + const adminUser = initData.admins?.[0]; + if (!adminUser) { + console.error('❌ 没有找到管理员账号'); + process.exit(1); + } + + const baseURL = `http://localhost:${config.server.port}`; + + // 登录获取 token + console.log('🔐 登录管理员账号...'); + const loginResp = await axios.post(`${baseURL}/admin/login`, { + username: adminUser.username, + password: adminUser.password + }); + + if (!loginResp.data.success) { + console.error('❌ 登录失败'); + process.exit(1); + } + + const token = loginResp.data.token; + console.log('✅ 登录成功\n'); + + // 设置请求头 + const headers = { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }; + + // 获取 Claude OAuth 账号 + console.log('📋 获取 Claude OAuth 账号...'); + const claudeResp = await axios.get(`${baseURL}/admin/claude-accounts`, { headers }); + const claudeAccounts = claudeResp.data.data || []; + + console.log(`找到 ${claudeAccounts.length} 个 Claude OAuth 账号`); + + // 分类显示 + const claudeDedicated = claudeAccounts.filter(a => a.accountType === 'dedicated'); + const claudeGroup = claudeAccounts.filter(a => a.accountType === 'group'); + const claudeShared = claudeAccounts.filter(a => a.accountType === 'shared'); + + console.log(`- 专属账号: ${claudeDedicated.length} 个`); + console.log(`- 分组账号: ${claudeGroup.length} 个`); + console.log(`- 共享账号: ${claudeShared.length} 个`); + + // 检查 platform 字段 + console.log('\n检查 platform 字段:'); + claudeAccounts.slice(0, 3).forEach(acc => { + console.log(`- ${acc.name}: platform=${acc.platform}, accountType=${acc.accountType}`); + }); + + // 获取 Claude Console 账号 + console.log('\n📋 获取 Claude Console 账号...'); + const consoleResp = await axios.get(`${baseURL}/admin/claude-console-accounts`, { headers }); + const consoleAccounts = consoleResp.data.data || []; + + console.log(`找到 ${consoleAccounts.length} 个 Claude Console 账号`); + + // 分类显示 + const consoleDedicated = consoleAccounts.filter(a => a.accountType === 'dedicated'); + const consoleGroup = consoleAccounts.filter(a => a.accountType === 'group'); + const consoleShared = consoleAccounts.filter(a => a.accountType === 'shared'); + + console.log(`- 专属账号: ${consoleDedicated.length} 个`); + console.log(`- 分组账号: ${consoleGroup.length} 个`); + console.log(`- 共享账号: ${consoleShared.length} 个`); + + // 检查 platform 字段 + console.log('\n检查 platform 字段:'); + consoleAccounts.slice(0, 3).forEach(acc => { + console.log(`- ${acc.name}: platform=${acc.platform}, accountType=${acc.accountType}`); + }); + + // 获取账号分组 + console.log('\n📋 获取账号分组...'); + const groupsResp = await axios.get(`${baseURL}/admin/account-groups`, { headers }); + const groups = groupsResp.data.data || []; + + console.log(`找到 ${groups.length} 个账号分组`); + + const claudeGroups = groups.filter(g => g.platform === 'claude'); + const geminiGroups = groups.filter(g => g.platform === 'gemini'); + + console.log(`- Claude 分组: ${claudeGroups.length} 个`); + console.log(`- Gemini 分组: ${geminiGroups.length} 个`); + + // 测试结果总结 + console.log('\n📊 测试结果总结:'); + console.log('✅ Claude OAuth 账号已包含 platform 字段'); + console.log('✅ Claude Console 账号已包含 platform 字段'); + console.log('✅ 账号分组功能正常'); + + const totalDedicated = claudeDedicated.length + consoleDedicated.length; + const totalGroups = claudeGroups.length; + + if (totalDedicated > 0) { + console.log(`\n✅ 共有 ${totalDedicated} 个专属账号应该显示在下拉框中`); + } else { + console.log('\n⚠️ 没有找到专属账号,请在账号管理页面设置账号类型为"专属账户"'); + } + + if (totalGroups > 0) { + console.log(`✅ 共有 ${totalGroups} 个分组应该显示在下拉框中`); + } + + console.log('\n💡 请在浏览器中测试创建/编辑 API Key,检查下拉框是否正确显示三个类别:'); + console.log(' 1. 调度分组'); + console.log(' 2. Claude OAuth 账号'); + console.log(' 3. Claude Console 账号'); + + } catch (error) { + console.error('❌ 测试失败:', error.message); + if (error.response) { + console.error('响应数据:', error.response.data); + } + } finally { + process.exit(0); + } +} + +testAccountDisplay(); \ No newline at end of file diff --git a/scripts/test-api-response.js b/scripts/test-api-response.js new file mode 100644 index 00000000..dfdf1c3c --- /dev/null +++ b/scripts/test-api-response.js @@ -0,0 +1,128 @@ +/** + * 测试 API 响应中的账号数据 + */ + +const redis = require('../src/models/redis'); +const claudeAccountService = require('../src/services/claudeAccountService'); +const claudeConsoleAccountService = require('../src/services/claudeConsoleAccountService'); +const accountGroupService = require('../src/services/accountGroupService'); + +async function testApiResponse() { + console.log('🔍 测试 API 响应数据...\n'); + + try { + // 确保 Redis 已连接 + await redis.connect(); + + // 1. 测试 Claude OAuth 账号服务 + console.log('📋 测试 Claude OAuth 账号服务...'); + const claudeAccounts = await claudeAccountService.getAllAccounts(); + console.log(`找到 ${claudeAccounts.length} 个 Claude OAuth 账号`); + + // 检查前3个账号的数据结构 + console.log('\n账号数据结构示例:'); + claudeAccounts.slice(0, 3).forEach(acc => { + console.log(`\n账号: ${acc.name}`); + console.log(` - ID: ${acc.id}`); + console.log(` - accountType: ${acc.accountType}`); + console.log(` - platform: ${acc.platform}`); + console.log(` - status: ${acc.status}`); + console.log(` - isActive: ${acc.isActive}`); + }); + + // 统计专属账号 + const claudeDedicated = claudeAccounts.filter(a => a.accountType === 'dedicated'); + const claudeGroup = claudeAccounts.filter(a => a.accountType === 'group'); + + console.log(`\n统计结果:`); + console.log(` - 专属账号: ${claudeDedicated.length} 个`); + console.log(` - 分组账号: ${claudeGroup.length} 个`); + + // 2. 测试 Claude Console 账号服务 + console.log('\n\n📋 测试 Claude Console 账号服务...'); + const consoleAccounts = await claudeConsoleAccountService.getAllAccounts(); + console.log(`找到 ${consoleAccounts.length} 个 Claude Console 账号`); + + // 检查前3个账号的数据结构 + console.log('\n账号数据结构示例:'); + consoleAccounts.slice(0, 3).forEach(acc => { + console.log(`\n账号: ${acc.name}`); + console.log(` - ID: ${acc.id}`); + console.log(` - accountType: ${acc.accountType}`); + console.log(` - platform: ${acc.platform}`); + console.log(` - status: ${acc.status}`); + console.log(` - isActive: ${acc.isActive}`); + }); + + // 统计专属账号 + const consoleDedicated = consoleAccounts.filter(a => a.accountType === 'dedicated'); + const consoleGroup = consoleAccounts.filter(a => a.accountType === 'group'); + + console.log(`\n统计结果:`); + console.log(` - 专属账号: ${consoleDedicated.length} 个`); + console.log(` - 分组账号: ${consoleGroup.length} 个`); + + // 3. 测试账号分组服务 + console.log('\n\n📋 测试账号分组服务...'); + const groups = await accountGroupService.getAllGroups(); + console.log(`找到 ${groups.length} 个账号分组`); + + // 显示分组信息 + groups.forEach(group => { + console.log(`\n分组: ${group.name}`); + console.log(` - ID: ${group.id}`); + console.log(` - platform: ${group.platform}`); + console.log(` - memberCount: ${group.memberCount}`); + }); + + // 4. 验证结果 + console.log('\n\n📊 验证结果:'); + + // 检查 platform 字段 + const claudeWithPlatform = claudeAccounts.filter(a => a.platform === 'claude-oauth'); + const consoleWithPlatform = consoleAccounts.filter(a => a.platform === 'claude-console'); + + if (claudeWithPlatform.length === claudeAccounts.length) { + console.log('✅ 所有 Claude OAuth 账号都有正确的 platform 字段'); + } else { + console.log(`⚠️ 只有 ${claudeWithPlatform.length}/${claudeAccounts.length} 个 Claude OAuth 账号有正确的 platform 字段`); + } + + if (consoleWithPlatform.length === consoleAccounts.length) { + console.log('✅ 所有 Claude Console 账号都有正确的 platform 字段'); + } else { + console.log(`⚠️ 只有 ${consoleWithPlatform.length}/${consoleAccounts.length} 个 Claude Console 账号有正确的 platform 字段`); + } + + // 总结 + const totalDedicated = claudeDedicated.length + consoleDedicated.length; + const totalGroup = claudeGroup.length + consoleGroup.length; + const totalGroups = groups.filter(g => g.platform === 'claude').length; + + console.log('\n📈 总结:'); + console.log(`- 专属账号总数: ${totalDedicated} 个 (Claude OAuth: ${claudeDedicated.length}, Console: ${consoleDedicated.length})`); + console.log(`- 分组账号总数: ${totalGroup} 个 (Claude OAuth: ${claudeGroup.length}, Console: ${consoleGroup.length})`); + console.log(`- 账号分组总数: ${totalGroups} 个`); + + if (totalDedicated + totalGroups > 0) { + console.log('\n✅ 前端下拉框应该能够显示:'); + if (totalGroups > 0) console.log(' - 调度分组'); + if (claudeDedicated.length > 0) console.log(' - Claude OAuth 专属账号 (仅 dedicated 类型)'); + if (consoleDedicated.length > 0) console.log(' - Claude Console 专属账号 (仅 dedicated 类型)'); + } else { + console.log('\n⚠️ 没有找到任何专属账号或分组,请检查账号配置'); + } + + console.log('\n💡 说明:'); + console.log('- 专属账号下拉框只显示 accountType="dedicated" 的账号'); + console.log('- accountType="group" 的账号通过分组调度,不在专属账号中显示'); + + } catch (error) { + console.error('❌ 测试失败:', error); + console.error(error.stack); + } finally { + process.exit(0); + } +} + +testApiResponse(); \ No newline at end of file diff --git a/scripts/test-dedicated-accounts.js b/scripts/test-dedicated-accounts.js new file mode 100644 index 00000000..7dcac66e --- /dev/null +++ b/scripts/test-dedicated-accounts.js @@ -0,0 +1,132 @@ +/** + * 测试专属账号显示问题 + */ + +const redis = require('../src/models/redis'); + +async function testDedicatedAccounts() { + console.log('🔍 检查专属账号...\n'); + + try { + // 确保 Redis 已连接 + await redis.connect(); + + // 获取所有 Claude 账号 + const claudeKeys = await redis.client.keys('claude:account:*'); + console.log(`找到 ${claudeKeys.length} 个 Claude 账号\n`); + + const dedicatedAccounts = []; + const groupAccounts = []; + const sharedAccounts = []; + + for (const key of claudeKeys) { + const account = await redis.client.hgetall(key); + const accountType = account.accountType || 'shared'; + + const accountInfo = { + id: account.id, + name: account.name, + accountType: accountType, + status: account.status, + isActive: account.isActive, + createdAt: account.createdAt + }; + + if (accountType === 'dedicated') { + dedicatedAccounts.push(accountInfo); + } else if (accountType === 'group') { + groupAccounts.push(accountInfo); + } else { + sharedAccounts.push(accountInfo); + } + } + + console.log('📊 账号统计:'); + console.log(`- 专属账号: ${dedicatedAccounts.length} 个`); + console.log(`- 分组账号: ${groupAccounts.length} 个`); + console.log(`- 共享账号: ${sharedAccounts.length} 个`); + console.log(''); + + if (dedicatedAccounts.length > 0) { + console.log('✅ 专属账号列表:'); + dedicatedAccounts.forEach(acc => { + console.log(` - ${acc.name} (ID: ${acc.id}, 状态: ${acc.status})`); + }); + console.log(''); + } else { + console.log('⚠️ 没有找到专属账号!'); + console.log('💡 提示: 请确保在账号管理页面将账号类型设置为"专属账户"'); + console.log(''); + } + + if (groupAccounts.length > 0) { + console.log('📁 分组账号列表:'); + groupAccounts.forEach(acc => { + console.log(` - ${acc.name} (ID: ${acc.id}, 状态: ${acc.status})`); + }); + console.log(''); + } + + // 检查分组 + const groupKeys = await redis.client.keys('account_group:*'); + console.log(`\n找到 ${groupKeys.length} 个账号分组`); + + if (groupKeys.length > 0) { + console.log('📋 分组列表:'); + for (const key of groupKeys) { + const group = await redis.client.hgetall(key); + console.log(` - ${group.name} (平台: ${group.platform}, 成员数: ${group.memberCount || 0})`); + } + } + + // 检查 Claude Console 账号 + const consoleKeys = await redis.client.keys('claude_console_account:*'); + console.log(`\n找到 ${consoleKeys.length} 个 Claude Console 账号`); + + const dedicatedConsoleAccounts = []; + const groupConsoleAccounts = []; + + for (const key of consoleKeys) { + const account = await redis.client.hgetall(key); + const accountType = account.accountType || 'shared'; + + if (accountType === 'dedicated') { + dedicatedConsoleAccounts.push({ + id: account.id, + name: account.name, + accountType: accountType, + status: account.status + }); + } else if (accountType === 'group') { + groupConsoleAccounts.push({ + id: account.id, + name: account.name, + accountType: accountType, + status: account.status + }); + } + } + + if (dedicatedConsoleAccounts.length > 0) { + console.log('\n✅ Claude Console 专属账号:'); + dedicatedConsoleAccounts.forEach(acc => { + console.log(` - ${acc.name} (ID: ${acc.id}, 状态: ${acc.status})`); + }); + } + + if (groupConsoleAccounts.length > 0) { + console.log('\n📁 Claude Console 分组账号:'); + groupConsoleAccounts.forEach(acc => { + console.log(` - ${acc.name} (ID: ${acc.id}, 状态: ${acc.status})`); + }); + } + + } catch (error) { + console.error('❌ 错误:', error); + console.error(error.stack); + } finally { + process.exit(0); + } +} + +testDedicatedAccounts(); \ No newline at end of file diff --git a/src/services/claudeAccountService.js b/src/services/claudeAccountService.js index 8a479748..e46a1eb2 100644 --- a/src/services/claudeAccountService.js +++ b/src/services/claudeAccountService.js @@ -330,6 +330,7 @@ class ClaudeAccountService { errorMessage: account.errorMessage, accountType: account.accountType || 'shared', // 兼容旧数据,默认为共享 priority: parseInt(account.priority) || 50, // 兼容旧数据,默认优先级50 + platform: 'claude-oauth', // 添加平台标识,用于前端区分 createdAt: account.createdAt, lastUsedAt: account.lastUsedAt, lastRefreshAt: account.lastRefreshAt, diff --git a/web/admin-spa/src/components/apikeys/CreateApiKeyModal.vue b/web/admin-spa/src/components/apikeys/CreateApiKeyModal.vue index 2a0e35a2..4044b689 100644 --- a/web/admin-spa/src/components/apikeys/CreateApiKeyModal.vue +++ b/web/admin-spa/src/components/apikeys/CreateApiKeyModal.vue @@ -446,11 +446,11 @@