From 34dca961ef6b74f45a4cd081875864858f24bf04 Mon Sep 17 00:00:00 2001 From: KevinLiao Date: Wed, 30 Jul 2025 09:30:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=201.=20=E4=BF=AE=E5=A4=8D=E8=B0=83?= =?UTF-8?q?=E5=BA=A6=E4=BC=98=E5=85=88=E7=BA=A7=E4=BB=A5=E5=8F=8A=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E7=A6=81=E6=AD=A2=E8=B0=83=E5=BA=A6=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=202.=20=E4=BC=98=E5=8C=96=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=BC=98=E5=85=88=E7=BA=A7=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/admin.js | 44 +++++++++++++++ src/services/claudeAccountService.js | 15 ++++-- src/services/claudeConsoleAccountService.js | 12 +++-- src/services/unifiedClaudeScheduler.js | 20 +++++-- web/admin-spa/dist/index.html | 4 +- web/admin-spa/src/views/AccountsView.vue | 60 ++++++++++++++++++++- 6 files changed, 140 insertions(+), 15 deletions(-) diff --git a/src/routes/admin.js b/src/routes/admin.js index 92c0ec18..45330aa8 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -793,6 +793,29 @@ router.post('/claude-accounts/:accountId/refresh', authenticateAdmin, async (req } }); +// 切换Claude账户调度状态 +router.put('/claude-accounts/:accountId/toggle-schedulable', authenticateAdmin, async (req, res) => { + try { + const { accountId } = req.params; + + const accounts = await claudeAccountService.getAllAccounts(); + const account = accounts.find(acc => acc.id === accountId); + + if (!account) { + return res.status(404).json({ error: 'Account not found' }); + } + + const newSchedulable = !account.schedulable; + await claudeAccountService.updateAccount(accountId, { schedulable: newSchedulable }); + + logger.success(`🔄 Admin toggled Claude account schedulable status: ${accountId} -> ${newSchedulable ? 'schedulable' : 'not schedulable'}`); + res.json({ success: true, schedulable: newSchedulable }); + } catch (error) { + logger.error('❌ Failed to toggle Claude account schedulable status:', error); + res.status(500).json({ error: 'Failed to toggle schedulable status', message: error.message }); + } +}); + // 🎮 Claude Console 账户管理 // 获取所有Claude Console账户 @@ -941,6 +964,27 @@ router.put('/claude-console-accounts/:accountId/toggle', authenticateAdmin, asyn } }); +// 切换Claude Console账户调度状态 +router.put('/claude-console-accounts/:accountId/toggle-schedulable', authenticateAdmin, async (req, res) => { + try { + const { accountId } = req.params; + + const account = await claudeConsoleAccountService.getAccount(accountId); + if (!account) { + return res.status(404).json({ error: 'Account not found' }); + } + + const newSchedulable = !account.schedulable; + await claudeConsoleAccountService.updateAccount(accountId, { schedulable: newSchedulable }); + + logger.success(`🔄 Admin toggled Claude Console account schedulable status: ${accountId} -> ${newSchedulable ? 'schedulable' : 'not schedulable'}`); + res.json({ success: true, schedulable: newSchedulable }); + } catch (error) { + logger.error('❌ Failed to toggle Claude Console account schedulable status:', error); + res.status(500).json({ error: 'Failed to toggle schedulable status', message: error.message }); + } +}); + // 🤖 Gemini 账户管理 // 生成 Gemini OAuth 授权 URL diff --git a/src/services/claudeAccountService.js b/src/services/claudeAccountService.js index 886a69ca..467aa5b1 100644 --- a/src/services/claudeAccountService.js +++ b/src/services/claudeAccountService.js @@ -38,7 +38,8 @@ class ClaudeAccountService { proxy = null, // { type: 'socks5', host: 'localhost', port: 1080, username: '', password: '' } isActive = true, accountType = 'shared', // 'dedicated' or 'shared' - priority = 50 // 调度优先级 (1-100,数字越小优先级越高) + priority = 50, // 调度优先级 (1-100,数字越小优先级越高) + schedulable = true // 是否可被调度 } = options; const accountId = uuidv4(); @@ -66,7 +67,8 @@ class ClaudeAccountService { lastUsedAt: '', lastRefreshAt: '', status: 'active', // 有OAuth数据的账户直接设为active - errorMessage: '' + errorMessage: '', + schedulable: schedulable.toString() // 是否可被调度 }; } else { // 兼容旧格式 @@ -88,7 +90,8 @@ class ClaudeAccountService { lastUsedAt: '', lastRefreshAt: '', status: 'created', // created, active, expired, error - errorMessage: '' + errorMessage: '', + schedulable: schedulable.toString() // 是否可被调度 }; } @@ -328,7 +331,9 @@ class ClaudeAccountService { progress: 0, remainingTime: null, lastRequestTime: null - } + }, + // 添加调度状态 + schedulable: account.schedulable !== 'false' // 默认为true,兼容历史数据 }; })); @@ -348,7 +353,7 @@ class ClaudeAccountService { throw new Error('Account not found'); } - const allowedUpdates = ['name', 'description', 'email', 'password', 'refreshToken', 'proxy', 'isActive', 'claudeAiOauth', 'accountType', 'priority']; + const allowedUpdates = ['name', 'description', 'email', 'password', 'refreshToken', 'proxy', 'isActive', 'claudeAiOauth', 'accountType', 'priority', 'schedulable']; const updatedData = { ...accountData }; // 检查是否新增了 refresh token diff --git a/src/services/claudeConsoleAccountService.js b/src/services/claudeConsoleAccountService.js index afaca6ae..a6b4072e 100644 --- a/src/services/claudeConsoleAccountService.js +++ b/src/services/claudeConsoleAccountService.js @@ -30,7 +30,8 @@ class ClaudeConsoleAccountService { rateLimitDuration = 60, // 限流时间(分钟) proxy = null, isActive = true, - accountType = 'shared' // 'dedicated' or 'shared' + accountType = 'shared', // 'dedicated' or 'shared' + schedulable = true // 是否可被调度 } = options; // 验证必填字段 @@ -60,7 +61,9 @@ class ClaudeConsoleAccountService { errorMessage: '', // 限流相关 rateLimitedAt: '', - rateLimitStatus: '' + rateLimitStatus: '', + // 调度控制 + schedulable: schedulable.toString() }; const client = redis.getClientSafe(); @@ -126,7 +129,8 @@ class ClaudeConsoleAccountService { errorMessage: accountData.errorMessage, createdAt: accountData.createdAt, lastUsedAt: accountData.lastUsedAt, - rateLimitStatus: rateLimitInfo + rateLimitStatus: rateLimitInfo, + schedulable: accountData.schedulable !== 'false' // 默认为true,只有明确设置为false才不可调度 }); } } @@ -166,6 +170,7 @@ class ClaudeConsoleAccountService { 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); @@ -210,6 +215,7 @@ class ClaudeConsoleAccountService { 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) { diff --git a/src/services/unifiedClaudeScheduler.js b/src/services/unifiedClaudeScheduler.js index 17bd2f45..a961782d 100644 --- a/src/services/unifiedClaudeScheduler.js +++ b/src/services/unifiedClaudeScheduler.js @@ -107,7 +107,8 @@ class UnifiedClaudeScheduler { if (account.isActive === 'true' && account.status !== 'error' && account.status !== 'blocked' && - (account.accountType === 'shared' || !account.accountType)) { // 兼容旧数据 + (account.accountType === 'shared' || !account.accountType) && // 兼容旧数据 + account.schedulable !== 'false') { // 检查是否可调度 // 检查是否被限流 const isRateLimited = await claudeAccountService.isAccountRateLimited(account.id); @@ -128,12 +129,13 @@ class UnifiedClaudeScheduler { logger.info(`📋 Found ${consoleAccounts.length} total Claude Console accounts`); for (const account of consoleAccounts) { - logger.info(`🔍 Checking Claude Console account: ${account.name} - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}`); + logger.info(`🔍 Checking Claude Console account: ${account.name} - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}, schedulable: ${account.schedulable}`); // 注意:getAllAccounts返回的isActive是布尔值 if (account.isActive === true && account.status === 'active' && - account.accountType === 'shared') { + account.accountType === 'shared' && + account.schedulable !== false) { // 检查是否可调度 // 检查模型支持(如果有请求的模型) if (requestedModel && account.supportedModels && account.supportedModels.length > 0) { @@ -158,7 +160,7 @@ class UnifiedClaudeScheduler { logger.warn(`⚠️ Claude Console account ${account.name} is rate limited`); } } else { - logger.info(`❌ Claude Console account ${account.name} not eligible - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}`); + logger.info(`❌ Claude Console account ${account.name} not eligible - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}, schedulable: ${account.schedulable}`); } } @@ -189,12 +191,22 @@ class UnifiedClaudeScheduler { if (!account || account.isActive !== 'true' || account.status === 'error') { return false; } + // 检查是否可调度 + if (account.schedulable === 'false') { + logger.info(`🚫 Account ${accountId} is not schedulable`); + return false; + } return !(await claudeAccountService.isAccountRateLimited(accountId)); } else if (accountType === 'claude-console') { const account = await claudeConsoleAccountService.getAccount(accountId); if (!account || !account.isActive || account.status !== 'active') { return false; } + // 检查是否可调度 + if (account.schedulable === false) { + logger.info(`🚫 Claude Console account ${accountId} is not schedulable`); + return false; + } return !(await claudeConsoleAccountService.isAccountRateLimited(accountId)); } return false; diff --git a/web/admin-spa/dist/index.html b/web/admin-spa/dist/index.html index cff50e9e..4e1721f7 100644 --- a/web/admin-spa/dist/index.html +++ b/web/admin-spa/dist/index.html @@ -18,12 +18,12 @@ - + - +
diff --git a/web/admin-spa/src/views/AccountsView.vue b/web/admin-spa/src/views/AccountsView.vue index 830f52e7..ad2c6de1 100644 --- a/web/admin-spa/src/views/AccountsView.vue +++ b/web/admin-spa/src/views/AccountsView.vue @@ -138,6 +138,11 @@ 限流中 ({{ account.rateLimitStatus.minutesRemaining }}分钟) + + + 不可调度 + @@ -153,7 +158,7 @@
+ :style="{ width: ((101 - (account.priority || 50)) + '%') }">
{{ account.priority || 50 }} @@ -232,6 +237,24 @@ account.isRefreshing ? 'animate-spin' : '' ]"> +