From a6ab6b7abe2338657300889ecb08f5256cd43a82 Mon Sep 17 00:00:00 2001 From: shaw Date: Wed, 30 Jul 2025 14:27:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=9F=BA=E4=BA=8E?= =?UTF-8?q?=E6=BB=91=E5=8A=A8=E7=AA=97=E5=8F=A3=E7=9A=84=E5=AE=9E=E6=97=B6?= =?UTF-8?q?RPM/TPM=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加系统级分钟统计,支持1-60分钟可配置时间窗口 - 新增 getRealtimeSystemMetrics 方法计算滑动窗口内的平均值 - 前端显示实时RPM/TPM,标注时间窗口和数据来源 - 修复 EditApiKeyModal 中模型限制和客户端限制复选框状态错误 - 优化性能:使用Pipeline批量操作替代Promise.all - TPM包含所有token类型:input、output、cache_creation、cache_read - 添加降级方案:实时数据不可用时返回历史平均值 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .env.example | 1 + src/models/redis.js | 280 ++++++++++++------ src/routes/admin.js | 13 +- src/services/claudeConsoleRelayService.js | 4 +- .../components/apikeys/EditApiKeyModal.vue | 5 +- web/admin-spa/src/stores/dashboard.js | 9 + web/admin-spa/src/views/DashboardView.vue | 28 +- 7 files changed, 237 insertions(+), 103 deletions(-) diff --git a/.env.example b/.env.example index 33a34d06..a796b496 100644 --- a/.env.example +++ b/.env.example @@ -45,6 +45,7 @@ TOKEN_USAGE_RETENTION=2592000000 HEALTH_CHECK_INTERVAL=60000 SYSTEM_TIMEZONE=Asia/Shanghai TIMEZONE_OFFSET=8 +METRICS_WINDOW=5 # 实时指标统计窗口(分钟),可选1-60,默认5分钟 # 🎨 Web 界面配置 WEB_TITLE=Claude Relay Service diff --git a/src/models/redis.js b/src/models/redis.js index f8f10723..57e4b013 100644 --- a/src/models/redis.js +++ b/src/models/redis.js @@ -186,6 +186,10 @@ class RedisClient { const keyModelMonthly = `usage:${keyId}:model:monthly:${model}:${currentMonth}`; const keyModelHourly = `usage:${keyId}:model:hourly:${model}:${currentHour}`; // 新增API Key模型小时级别 + // 新增:系统级分钟统计 + const minuteTimestamp = Math.floor(now.getTime() / 60000); + const systemMinuteKey = `system:metrics:minute:${minuteTimestamp}`; + // 智能处理输入输出token分配 const finalInputTokens = inputTokens || 0; const finalOutputTokens = outputTokens || (finalInputTokens > 0 ? 0 : tokens); @@ -197,96 +201,122 @@ class RedisClient { // 核心token(不包括缓存)- 用于与历史数据兼容 const coreTokens = finalInputTokens + finalOutputTokens; - await Promise.all([ - // 核心token统计(保持向后兼容) - this.client.hincrby(key, 'totalTokens', coreTokens), - this.client.hincrby(key, 'totalInputTokens', finalInputTokens), - this.client.hincrby(key, 'totalOutputTokens', finalOutputTokens), - // 缓存token统计(新增) - this.client.hincrby(key, 'totalCacheCreateTokens', finalCacheCreateTokens), - this.client.hincrby(key, 'totalCacheReadTokens', finalCacheReadTokens), - this.client.hincrby(key, 'totalAllTokens', totalTokens), // 包含所有类型的总token - // 请求计数 - this.client.hincrby(key, 'totalRequests', 1), - // 每日统计 - this.client.hincrby(daily, 'tokens', coreTokens), - this.client.hincrby(daily, 'inputTokens', finalInputTokens), - this.client.hincrby(daily, 'outputTokens', finalOutputTokens), - this.client.hincrby(daily, 'cacheCreateTokens', finalCacheCreateTokens), - this.client.hincrby(daily, 'cacheReadTokens', finalCacheReadTokens), - this.client.hincrby(daily, 'allTokens', totalTokens), - this.client.hincrby(daily, 'requests', 1), - // 每月统计 - this.client.hincrby(monthly, 'tokens', coreTokens), - this.client.hincrby(monthly, 'inputTokens', finalInputTokens), - this.client.hincrby(monthly, 'outputTokens', finalOutputTokens), - this.client.hincrby(monthly, 'cacheCreateTokens', finalCacheCreateTokens), - this.client.hincrby(monthly, 'cacheReadTokens', finalCacheReadTokens), - this.client.hincrby(monthly, 'allTokens', totalTokens), - this.client.hincrby(monthly, 'requests', 1), - // 按模型统计 - 每日 - this.client.hincrby(modelDaily, 'inputTokens', finalInputTokens), - this.client.hincrby(modelDaily, 'outputTokens', finalOutputTokens), - this.client.hincrby(modelDaily, 'cacheCreateTokens', finalCacheCreateTokens), - this.client.hincrby(modelDaily, 'cacheReadTokens', finalCacheReadTokens), - this.client.hincrby(modelDaily, 'allTokens', totalTokens), - this.client.hincrby(modelDaily, 'requests', 1), - // 按模型统计 - 每月 - this.client.hincrby(modelMonthly, 'inputTokens', finalInputTokens), - this.client.hincrby(modelMonthly, 'outputTokens', finalOutputTokens), - this.client.hincrby(modelMonthly, 'cacheCreateTokens', finalCacheCreateTokens), - this.client.hincrby(modelMonthly, 'cacheReadTokens', finalCacheReadTokens), - this.client.hincrby(modelMonthly, 'allTokens', totalTokens), - this.client.hincrby(modelMonthly, 'requests', 1), - // API Key级别的模型统计 - 每日 - this.client.hincrby(keyModelDaily, 'inputTokens', finalInputTokens), - this.client.hincrby(keyModelDaily, 'outputTokens', finalOutputTokens), - this.client.hincrby(keyModelDaily, 'cacheCreateTokens', finalCacheCreateTokens), - this.client.hincrby(keyModelDaily, 'cacheReadTokens', finalCacheReadTokens), - this.client.hincrby(keyModelDaily, 'allTokens', totalTokens), - this.client.hincrby(keyModelDaily, 'requests', 1), - // API Key级别的模型统计 - 每月 - this.client.hincrby(keyModelMonthly, 'inputTokens', finalInputTokens), - this.client.hincrby(keyModelMonthly, 'outputTokens', finalOutputTokens), - this.client.hincrby(keyModelMonthly, 'cacheCreateTokens', finalCacheCreateTokens), - this.client.hincrby(keyModelMonthly, 'cacheReadTokens', finalCacheReadTokens), - this.client.hincrby(keyModelMonthly, 'allTokens', totalTokens), - this.client.hincrby(keyModelMonthly, 'requests', 1), - - // 小时级别统计 - this.client.hincrby(hourly, 'tokens', coreTokens), - this.client.hincrby(hourly, 'inputTokens', finalInputTokens), - this.client.hincrby(hourly, 'outputTokens', finalOutputTokens), - this.client.hincrby(hourly, 'cacheCreateTokens', finalCacheCreateTokens), - this.client.hincrby(hourly, 'cacheReadTokens', finalCacheReadTokens), - this.client.hincrby(hourly, 'allTokens', totalTokens), - this.client.hincrby(hourly, 'requests', 1), - // 按模型统计 - 每小时 - this.client.hincrby(modelHourly, 'inputTokens', finalInputTokens), - this.client.hincrby(modelHourly, 'outputTokens', finalOutputTokens), - this.client.hincrby(modelHourly, 'cacheCreateTokens', finalCacheCreateTokens), - this.client.hincrby(modelHourly, 'cacheReadTokens', finalCacheReadTokens), - this.client.hincrby(modelHourly, 'allTokens', totalTokens), - this.client.hincrby(modelHourly, 'requests', 1), - // API Key级别的模型统计 - 每小时 - this.client.hincrby(keyModelHourly, 'inputTokens', finalInputTokens), - this.client.hincrby(keyModelHourly, 'outputTokens', finalOutputTokens), - this.client.hincrby(keyModelHourly, 'cacheCreateTokens', finalCacheCreateTokens), - this.client.hincrby(keyModelHourly, 'cacheReadTokens', finalCacheReadTokens), - this.client.hincrby(keyModelHourly, 'allTokens', totalTokens), - this.client.hincrby(keyModelHourly, 'requests', 1), - - // 设置过期时间 - this.client.expire(daily, 86400 * 32), // 32天过期 - this.client.expire(monthly, 86400 * 365), // 1年过期 - this.client.expire(hourly, 86400 * 7), // 小时统计7天过期 - this.client.expire(modelDaily, 86400 * 32), // 模型每日统计32天过期 - this.client.expire(modelMonthly, 86400 * 365), // 模型每月统计1年过期 - this.client.expire(modelHourly, 86400 * 7), // 模型小时统计7天过期 - this.client.expire(keyModelDaily, 86400 * 32), // API Key模型每日统计32天过期 - this.client.expire(keyModelMonthly, 86400 * 365), // API Key模型每月统计1年过期 - this.client.expire(keyModelHourly, 86400 * 7) // API Key模型小时统计7天过期 - ]); + // 使用Pipeline优化性能 + const pipeline = this.client.pipeline(); + + // 现有的统计保持不变 + // 核心token统计(保持向后兼容) + pipeline.hincrby(key, 'totalTokens', coreTokens); + pipeline.hincrby(key, 'totalInputTokens', finalInputTokens); + pipeline.hincrby(key, 'totalOutputTokens', finalOutputTokens); + // 缓存token统计(新增) + pipeline.hincrby(key, 'totalCacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(key, 'totalCacheReadTokens', finalCacheReadTokens); + pipeline.hincrby(key, 'totalAllTokens', totalTokens); // 包含所有类型的总token + // 请求计数 + pipeline.hincrby(key, 'totalRequests', 1); + + // 每日统计 + pipeline.hincrby(daily, 'tokens', coreTokens); + pipeline.hincrby(daily, 'inputTokens', finalInputTokens); + pipeline.hincrby(daily, 'outputTokens', finalOutputTokens); + pipeline.hincrby(daily, 'cacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(daily, 'cacheReadTokens', finalCacheReadTokens); + pipeline.hincrby(daily, 'allTokens', totalTokens); + pipeline.hincrby(daily, 'requests', 1); + + // 每月统计 + pipeline.hincrby(monthly, 'tokens', coreTokens); + pipeline.hincrby(monthly, 'inputTokens', finalInputTokens); + pipeline.hincrby(monthly, 'outputTokens', finalOutputTokens); + pipeline.hincrby(monthly, 'cacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(monthly, 'cacheReadTokens', finalCacheReadTokens); + pipeline.hincrby(monthly, 'allTokens', totalTokens); + pipeline.hincrby(monthly, 'requests', 1); + + // 按模型统计 - 每日 + pipeline.hincrby(modelDaily, 'inputTokens', finalInputTokens); + pipeline.hincrby(modelDaily, 'outputTokens', finalOutputTokens); + pipeline.hincrby(modelDaily, 'cacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(modelDaily, 'cacheReadTokens', finalCacheReadTokens); + pipeline.hincrby(modelDaily, 'allTokens', totalTokens); + pipeline.hincrby(modelDaily, 'requests', 1); + + // 按模型统计 - 每月 + pipeline.hincrby(modelMonthly, 'inputTokens', finalInputTokens); + pipeline.hincrby(modelMonthly, 'outputTokens', finalOutputTokens); + pipeline.hincrby(modelMonthly, 'cacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(modelMonthly, 'cacheReadTokens', finalCacheReadTokens); + pipeline.hincrby(modelMonthly, 'allTokens', totalTokens); + pipeline.hincrby(modelMonthly, 'requests', 1); + + // API Key级别的模型统计 - 每日 + pipeline.hincrby(keyModelDaily, 'inputTokens', finalInputTokens); + pipeline.hincrby(keyModelDaily, 'outputTokens', finalOutputTokens); + pipeline.hincrby(keyModelDaily, 'cacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(keyModelDaily, 'cacheReadTokens', finalCacheReadTokens); + pipeline.hincrby(keyModelDaily, 'allTokens', totalTokens); + pipeline.hincrby(keyModelDaily, 'requests', 1); + + // API Key级别的模型统计 - 每月 + pipeline.hincrby(keyModelMonthly, 'inputTokens', finalInputTokens); + pipeline.hincrby(keyModelMonthly, 'outputTokens', finalOutputTokens); + pipeline.hincrby(keyModelMonthly, 'cacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(keyModelMonthly, 'cacheReadTokens', finalCacheReadTokens); + pipeline.hincrby(keyModelMonthly, 'allTokens', totalTokens); + pipeline.hincrby(keyModelMonthly, 'requests', 1); + + // 小时级别统计 + pipeline.hincrby(hourly, 'tokens', coreTokens); + pipeline.hincrby(hourly, 'inputTokens', finalInputTokens); + pipeline.hincrby(hourly, 'outputTokens', finalOutputTokens); + pipeline.hincrby(hourly, 'cacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(hourly, 'cacheReadTokens', finalCacheReadTokens); + pipeline.hincrby(hourly, 'allTokens', totalTokens); + pipeline.hincrby(hourly, 'requests', 1); + + // 按模型统计 - 每小时 + pipeline.hincrby(modelHourly, 'inputTokens', finalInputTokens); + pipeline.hincrby(modelHourly, 'outputTokens', finalOutputTokens); + pipeline.hincrby(modelHourly, 'cacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(modelHourly, 'cacheReadTokens', finalCacheReadTokens); + pipeline.hincrby(modelHourly, 'allTokens', totalTokens); + pipeline.hincrby(modelHourly, 'requests', 1); + + // API Key级别的模型统计 - 每小时 + pipeline.hincrby(keyModelHourly, 'inputTokens', finalInputTokens); + pipeline.hincrby(keyModelHourly, 'outputTokens', finalOutputTokens); + pipeline.hincrby(keyModelHourly, 'cacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(keyModelHourly, 'cacheReadTokens', finalCacheReadTokens); + pipeline.hincrby(keyModelHourly, 'allTokens', totalTokens); + pipeline.hincrby(keyModelHourly, 'requests', 1); + + // 新增:系统级分钟统计 + pipeline.hincrby(systemMinuteKey, 'requests', 1); + pipeline.hincrby(systemMinuteKey, 'totalTokens', totalTokens); + pipeline.hincrby(systemMinuteKey, 'inputTokens', finalInputTokens); + pipeline.hincrby(systemMinuteKey, 'outputTokens', finalOutputTokens); + pipeline.hincrby(systemMinuteKey, 'cacheCreateTokens', finalCacheCreateTokens); + pipeline.hincrby(systemMinuteKey, 'cacheReadTokens', finalCacheReadTokens); + + // 设置过期时间 + pipeline.expire(daily, 86400 * 32); // 32天过期 + pipeline.expire(monthly, 86400 * 365); // 1年过期 + pipeline.expire(hourly, 86400 * 7); // 小时统计7天过期 + pipeline.expire(modelDaily, 86400 * 32); // 模型每日统计32天过期 + pipeline.expire(modelMonthly, 86400 * 365); // 模型每月统计1年过期 + pipeline.expire(modelHourly, 86400 * 7); // 模型小时统计7天过期 + pipeline.expire(keyModelDaily, 86400 * 32); // API Key模型每日统计32天过期 + pipeline.expire(keyModelMonthly, 86400 * 365); // API Key模型每月统计1年过期 + pipeline.expire(keyModelHourly, 86400 * 7); // API Key模型小时统计7天过期 + + // 系统级分钟统计的过期时间(窗口时间的2倍) + const config = require('../../config/config'); + const metricsWindow = config.system.metricsWindow; + pipeline.expire(systemMinuteKey, metricsWindow * 60 * 2); + + // 执行Pipeline + await pipeline.exec(); } // 📊 记录账户级别的使用统计 @@ -974,6 +1004,76 @@ class RedisClient { } } + // 📊 获取实时系统指标(基于滑动窗口) + async getRealtimeSystemMetrics() { + try { + const config = require('../../config/config'); + const windowMinutes = config.system.metricsWindow; + + const now = new Date(); + const currentMinute = Math.floor(now.getTime() / 60000); + + // 使用Pipeline批量获取窗口内的所有分钟数据 + const pipeline = this.client.pipeline(); + for (let i = 0; i < windowMinutes; i++) { + const minuteKey = `system:metrics:minute:${currentMinute - i}`; + pipeline.hgetall(minuteKey); + } + + const results = await pipeline.exec(); + + // 聚合计算 + let totalRequests = 0; + let totalTokens = 0; + let totalInputTokens = 0; + let totalOutputTokens = 0; + let totalCacheCreateTokens = 0; + let totalCacheReadTokens = 0; + + results.forEach(([err, data]) => { + if (!err && data) { + totalRequests += parseInt(data.requests || 0); + totalTokens += parseInt(data.totalTokens || 0); + totalInputTokens += parseInt(data.inputTokens || 0); + totalOutputTokens += parseInt(data.outputTokens || 0); + totalCacheCreateTokens += parseInt(data.cacheCreateTokens || 0); + totalCacheReadTokens += parseInt(data.cacheReadTokens || 0); + } + }); + + // 计算平均值(每分钟) + const realtimeRPM = windowMinutes > 0 ? Math.round((totalRequests / windowMinutes) * 100) / 100 : 0; + const realtimeTPM = windowMinutes > 0 ? Math.round((totalTokens / windowMinutes) * 100) / 100 : 0; + + return { + realtimeRPM, + realtimeTPM, + windowMinutes, + totalRequests, + totalTokens, + totalInputTokens, + totalOutputTokens, + totalCacheCreateTokens, + totalCacheReadTokens + }; + } catch (error) { + console.error('Error getting realtime system metrics:', error); + // 如果出错,返回历史平均值作为降级方案 + const historicalMetrics = await this.getSystemAverages(); + return { + realtimeRPM: historicalMetrics.systemRPM, + realtimeTPM: historicalMetrics.systemTPM, + windowMinutes: 0, // 标识使用了历史数据 + totalRequests: 0, + totalTokens: historicalMetrics.totalTokens, + totalInputTokens: historicalMetrics.totalInputTokens, + totalOutputTokens: historicalMetrics.totalOutputTokens, + totalCacheCreateTokens: 0, + totalCacheReadTokens: 0 + }; + } + } + // 🔗 会话sticky映射管理 async setSessionAccountMapping(sessionHash, accountId, ttl = 3600) { const key = `sticky_session:${sessionHash}`; diff --git a/src/routes/admin.js b/src/routes/admin.js index adb7ef8a..de0a4be4 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -1261,13 +1261,14 @@ router.get('/accounts/:accountId/usage-stats', authenticateAdmin, async (req, re // 获取系统概览 router.get('/dashboard', authenticateAdmin, async (req, res) => { try { - const [, apiKeys, claudeAccounts, geminiAccounts, todayStats, systemAverages] = await Promise.all([ + const [, apiKeys, claudeAccounts, geminiAccounts, todayStats, systemAverages, realtimeMetrics] = await Promise.all([ redis.getSystemStats(), apiKeyService.getAllApiKeys(), claudeAccountService.getAllAccounts(), geminiAccountService.getAllAccounts(), redis.getTodayStats(), - redis.getSystemAverages() + redis.getSystemAverages(), + redis.getRealtimeSystemMetrics() ]); // 计算使用统计(统一使用allTokens) @@ -1316,6 +1317,12 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => { rpm: systemAverages.systemRPM, tpm: systemAverages.systemTPM }, + realtimeMetrics: { + rpm: realtimeMetrics.realtimeRPM, + tpm: realtimeMetrics.realtimeTPM, + windowMinutes: realtimeMetrics.windowMinutes, + isHistorical: realtimeMetrics.windowMinutes === 0 // 标识是否使用了历史数据 + }, systemHealth: { redisConnected: redis.isConnected, claudeAccountsHealthy: activeClaudeAccounts > 0, @@ -1483,7 +1490,7 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => { endTime = new Date(endDate); // 调试日志 - logger.info(`📊 Usage trend hour granularity - received times:`); + logger.info('📊 Usage trend hour granularity - received times:'); logger.info(` startDate (raw): ${startDate}`); logger.info(` endDate (raw): ${endDate}`); logger.info(` startTime (parsed): ${startTime.toISOString()}`); diff --git a/src/services/claudeConsoleRelayService.js b/src/services/claudeConsoleRelayService.js index 71455151..43b239c1 100644 --- a/src/services/claudeConsoleRelayService.js +++ b/src/services/claudeConsoleRelayService.js @@ -88,11 +88,11 @@ class ClaudeConsoleRelayService { logger.debug(`[DEBUG] Adding beta header: ${options.betaHeader}`); requestConfig.headers['anthropic-beta'] = options.betaHeader; } else { - logger.debug(`[DEBUG] No beta header to add`); + logger.debug('[DEBUG] No beta header to add'); } // 发送请求 - logger.debug(`📤 Sending request to Claude Console API with headers:`, JSON.stringify(requestConfig.headers, null, 2)); + logger.debug('📤 Sending request to Claude Console API with headers:', JSON.stringify(requestConfig.headers, null, 2)); const response = await axios(requestConfig); // 移除监听器(请求成功完成) diff --git a/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue b/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue index 9eaf5ad2..de0b77b4 100644 --- a/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue +++ b/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue @@ -522,8 +522,9 @@ onMounted(async () => { form.restrictedModels = props.apiKey.restrictedModels || [] form.allowedClients = props.apiKey.allowedClients || [] form.tags = props.apiKey.tags || [] - form.enableModelRestriction = form.restrictedModels.length > 0 - form.enableClientRestriction = form.allowedClients.length > 0 + // 从后端数据中获取实际的启用状态,而不是根据数组长度推断 + form.enableModelRestriction = props.apiKey.enableModelRestriction || false + form.enableClientRestriction = props.apiKey.enableClientRestriction || false }) diff --git a/web/admin-spa/src/stores/dashboard.js b/web/admin-spa/src/stores/dashboard.js index 19a83bda..9f91c907 100644 --- a/web/admin-spa/src/stores/dashboard.js +++ b/web/admin-spa/src/stores/dashboard.js @@ -26,6 +26,10 @@ export const useDashboardStore = defineStore('dashboard', () => { todayCacheReadTokens: 0, systemRPM: 0, systemTPM: 0, + realtimeRPM: 0, + realtimeTPM: 0, + metricsWindow: 5, + isHistoricalMetrics: false, systemStatus: '正常', uptime: 0, systemTimezone: 8 // 默认 UTC+8 @@ -129,6 +133,7 @@ export const useDashboardStore = defineStore('dashboard', () => { const overview = dashboardResponse.data.overview || {} const recentActivity = dashboardResponse.data.recentActivity || {} const systemAverages = dashboardResponse.data.systemAverages || {} + const realtimeMetrics = dashboardResponse.data.realtimeMetrics || {} const systemHealth = dashboardResponse.data.systemHealth || {} dashboardData.value = { @@ -151,6 +156,10 @@ export const useDashboardStore = defineStore('dashboard', () => { todayCacheReadTokens: recentActivity.cacheReadTokensToday || 0, systemRPM: systemAverages.rpm || 0, systemTPM: systemAverages.tpm || 0, + realtimeRPM: realtimeMetrics.rpm || 0, + realtimeTPM: realtimeMetrics.tpm || 0, + metricsWindow: realtimeMetrics.windowMinutes || 5, + isHistoricalMetrics: realtimeMetrics.isHistorical || false, systemStatus: systemHealth.redisConnected ? '正常' : '异常', uptime: systemHealth.uptime || 0, systemTimezone: dashboardResponse.data.systemTimezone || 8 diff --git a/web/admin-spa/src/views/DashboardView.vue b/web/admin-spa/src/views/DashboardView.vue index 68b4e2a3..c728f5da 100644 --- a/web/admin-spa/src/views/DashboardView.vue +++ b/web/admin-spa/src/views/DashboardView.vue @@ -111,9 +111,17 @@
-

平均RPM

-

{{ dashboardData.systemRPM || 0 }}

-

每分钟请求数

+

+ 实时RPM + ({{ dashboardData.metricsWindow }}分钟) +

+

{{ dashboardData.realtimeRPM || 0 }}

+

+ 每分钟请求数 + + 历史数据 + +

@@ -124,9 +132,17 @@
-

平均TPM

-

{{ dashboardData.systemTPM || 0 }}

-

每分钟Token数

+

+ 实时TPM + ({{ dashboardData.metricsWindow }}分钟) +

+

{{ dashboardData.realtimeTPM || 0 }}

+

+ 每分钟Token数 + + 历史数据 + +