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'
+}