diff --git a/src/routes/apiStats.js b/src/routes/apiStats.js index e4db6f4f..1241aad6 100644 --- a/src/routes/apiStats.js +++ b/src/routes/apiStats.js @@ -269,6 +269,28 @@ router.post('/api/user-stats', async (req, res) => { } } + // 获取当前使用量 + let currentWindowRequests = 0; + let currentWindowTokens = 0; + let currentDailyCost = 0; + + try { + // 获取当前时间窗口的请求次数和Token使用量 + if (fullKeyData.rateLimitWindow > 0) { + const client = redis.getClientSafe(); + const requestCountKey = `rate_limit:requests:${keyId}`; + const tokenCountKey = `rate_limit:tokens:${keyId}`; + + currentWindowRequests = parseInt(await client.get(requestCountKey) || '0'); + currentWindowTokens = parseInt(await client.get(tokenCountKey) || '0'); + } + + // 获取当日费用 + currentDailyCost = await redis.getDailyCost(keyId) || 0; + } catch (error) { + logger.warn(`Failed to get current usage for key ${keyId}:`, error); + } + // 构建响应数据(只返回该API Key自己的信息,确保不泄露其他信息) const responseData = { id: keyId, @@ -296,13 +318,17 @@ router.post('/api/user-stats', async (req, res) => { } }, - // 限制信息(只显示配置,不显示当前使用量) + // 限制信息(显示配置和当前使用量) limits: { tokenLimit: fullKeyData.tokenLimit || 0, concurrencyLimit: fullKeyData.concurrencyLimit || 0, rateLimitWindow: fullKeyData.rateLimitWindow || 0, rateLimitRequests: fullKeyData.rateLimitRequests || 0, - dailyCostLimit: fullKeyData.dailyCostLimit || 0 + dailyCostLimit: fullKeyData.dailyCostLimit || 0, + // 当前使用量 + currentWindowRequests: currentWindowRequests, + currentWindowTokens: currentWindowTokens, + currentDailyCost: currentDailyCost }, // 绑定的账户信息(只显示ID,不显示敏感信息) diff --git a/web/admin-spa/src/components/apistats/LimitConfig.vue b/web/admin-spa/src/components/apistats/LimitConfig.vue index f554359a..96371a29 100644 --- a/web/admin-spa/src/components/apistats/LimitConfig.vue +++ b/web/admin-spa/src/components/apistats/LimitConfig.vue @@ -6,64 +6,131 @@ 限制配置 -
-
- Token 限制 - {{ statsData.limits.tokenLimit > 0 ? formatNumber(statsData.limits.tokenLimit) : '无限制' }} -
-
- 并发限制 - {{ statsData.limits.concurrencyLimit > 0 ? statsData.limits.concurrencyLimit : '无限制' }} -
-
- 速率限制 - - {{ statsData.limits.rateLimitRequests > 0 && statsData.limits.rateLimitWindow > 0 - ? `${statsData.limits.rateLimitRequests}次/${statsData.limits.rateLimitWindow}分钟` - : '无限制' }} - -
-
- 每日费用限制 - {{ statsData.limits.dailyCostLimit > 0 ? '$' + statsData.limits.dailyCostLimit : '无限制' }} -
-
- 模型限制 - - - - 限制 {{ statsData.restrictions.restrictedModels.length }} 个模型 +
+ +
+
+ 每日费用限制 + + + ${{ statsData.limits.currentDailyCost.toFixed(4) }} / ${{ statsData.limits.dailyCostLimit.toFixed(2) }} + + + ${{ statsData.limits.currentDailyCost.toFixed(4) }} / + - - - 允许所有模型 - - +
+
+
+
+
+
+
-
- 客户端限制 - - - - 限制 {{ statsData.restrictions.allowedClients.length }} 个客户端 + + +
+
+ + 时间窗口限制 ({{ statsData.limits.rateLimitWindow }}分钟) - - - 允许所有客户端 +
+ + +
+
+ 请求次数 + + {{ formatNumber(statsData.limits.currentWindowRequests) }} / {{ formatNumber(statsData.limits.rateLimitRequests) }} + +
+
+
+
+
+ + +
+
+ Token 使用量 + + {{ formatNumber(statsData.limits.currentWindowTokens) }} / {{ formatNumber(statsData.limits.tokenLimit) }} + +
+
+
+
+
+ +
+ + 请求次数和Token使用量为"或"的关系,任一达到限制即触发限流 +
+
+ + +
+
+ 并发限制 + + + {{ statsData.limits.concurrencyLimit }} + + + + - +
+
+ 模型限制 + + + + 限制 {{ statsData.restrictions.restrictedModels.length }} 个模型 + + + + 允许所有模型 + + +
+
+ 客户端限制 + + + + 限制 {{ statsData.restrictions.allowedClients.length }} 个客户端 + + + + 允许所有客户端 + + +
@@ -158,6 +225,51 @@ const formatNumber = (num) => { return num.toLocaleString() } } + +// 获取每日费用进度 +const getDailyCostProgress = () => { + if (!statsData.value.limits.dailyCostLimit || statsData.value.limits.dailyCostLimit === 0) return 0 + const percentage = (statsData.value.limits.currentDailyCost / statsData.value.limits.dailyCostLimit) * 100 + return Math.min(percentage, 100) +} + +// 获取每日费用进度条颜色 +const getDailyCostProgressColor = () => { + const progress = getDailyCostProgress() + if (progress >= 100) return 'bg-red-500' + if (progress >= 80) return 'bg-yellow-500' + return 'bg-green-500' +} + +// 获取窗口请求进度 +const getWindowRequestProgress = () => { + if (!statsData.value.limits.rateLimitRequests || statsData.value.limits.rateLimitRequests === 0) return 0 + const percentage = (statsData.value.limits.currentWindowRequests / statsData.value.limits.rateLimitRequests) * 100 + return Math.min(percentage, 100) +} + +// 获取窗口请求进度条颜色 +const getWindowRequestProgressColor = () => { + const progress = getWindowRequestProgress() + if (progress >= 100) return 'bg-red-500' + if (progress >= 80) return 'bg-yellow-500' + return 'bg-blue-500' +} + +// 获取窗口Token进度 +const getWindowTokenProgress = () => { + if (!statsData.value.limits.tokenLimit || statsData.value.limits.tokenLimit === 0) return 0 + const percentage = (statsData.value.limits.currentWindowTokens / statsData.value.limits.tokenLimit) * 100 + return Math.min(percentage, 100) +} + +// 获取窗口Token进度条颜色 +const getWindowTokenProgressColor = () => { + const progress = getWindowTokenProgress() + if (progress >= 100) return 'bg-red-500' + if (progress >= 80) return 'bg-yellow-500' + return 'bg-purple-500' +}