diff --git a/src/routes/admin/apiKeys.js b/src/routes/admin/apiKeys.js index e2ee3c88..8e444067 100644 --- a/src/routes/admin/apiKeys.js +++ b/src/routes/admin/apiKeys.js @@ -919,6 +919,62 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { // 去重(避免日数据和月数据重复计算) const uniqueKeys = [...new Set(allKeys)] + // 获取实时限制数据(窗口数据不受时间范围筛选影响,始终获取当前窗口状态) + let dailyCost = 0 + let currentWindowCost = 0 + let windowRemainingSeconds = null + let windowStartTime = null + let windowEndTime = null + let allTimeCost = 0 + + try { + // 先获取 API Key 配置,判断是否需要查询限制相关数据 + const apiKey = await redis.getApiKey(keyId) + const rateLimitWindow = parseInt(apiKey?.rateLimitWindow) || 0 + const dailyCostLimit = parseFloat(apiKey?.dailyCostLimit) || 0 + const totalCostLimit = parseFloat(apiKey?.totalCostLimit) || 0 + + // 只在启用了每日费用限制时查询 + if (dailyCostLimit > 0) { + dailyCost = await redis.getDailyCost(keyId) + } + + // 只在启用了总费用限制时查询 + if (totalCostLimit > 0) { + const totalCostKey = `usage:cost:total:${keyId}` + allTimeCost = parseFloat((await client.get(totalCostKey)) || '0') + } + + // 只在启用了窗口限制时查询窗口数据 + if (rateLimitWindow > 0) { + const costCountKey = `rate_limit:cost:${keyId}` + const windowStartKey = `rate_limit:window_start:${keyId}` + + currentWindowCost = parseFloat((await client.get(costCountKey)) || '0') + + // 获取窗口开始时间和计算剩余时间 + const windowStart = await client.get(windowStartKey) + if (windowStart) { + const now = Date.now() + windowStartTime = parseInt(windowStart) + const windowDuration = rateLimitWindow * 60 * 1000 // 转换为毫秒 + windowEndTime = windowStartTime + windowDuration + + // 如果窗口还有效 + if (now < windowEndTime) { + windowRemainingSeconds = Math.max(0, Math.floor((windowEndTime - now) / 1000)) + } else { + // 窗口已过期 + windowRemainingSeconds = 0 + currentWindowCost = 0 + } + } + } + } catch (error) { + logger.warn(`⚠️ 获取实时限制数据失败 (key: ${keyId}):`, error.message) + } + + // 如果没有使用数据,返回零值但包含窗口数据 if (uniqueKeys.length === 0) { return { requests: 0, @@ -928,7 +984,14 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { cacheCreateTokens: 0, cacheReadTokens: 0, cost: 0, - formattedCost: '$0.00' + formattedCost: '$0.00', + // 实时限制数据(始终返回,不受时间范围影响) + dailyCost, + currentWindowCost, + windowRemainingSeconds, + windowStartTime, + windowEndTime, + allTimeCost } } @@ -1029,55 +1092,6 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) { const tokens = inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens - // 获取实时限制数据 - let dailyCost = 0 - let currentWindowCost = 0 - let windowRemainingSeconds = null - let windowStartTime = null - let windowEndTime = null - let allTimeCost = 0 - - try { - // 获取当日费用 - dailyCost = await redis.getDailyCost(keyId) - - // 获取历史总费用(用于总费用限制进度条,不受时间范围影响) - const totalCostKey = `usage:cost:total:${keyId}` - allTimeCost = parseFloat((await client.get(totalCostKey)) || '0') - - // 获取 API Key 配置信息以判断是否需要窗口数据 - const apiKey = await redis.getApiKey(keyId) - // 显式转换为整数,与 apiStats.js 保持一致,避免字符串比较问题 - const rateLimitWindow = parseInt(apiKey?.rateLimitWindow) || 0 - - if (rateLimitWindow > 0) { - const costCountKey = `rate_limit:cost:${keyId}` - const windowStartKey = `rate_limit:window_start:${keyId}` - - currentWindowCost = parseFloat((await client.get(costCountKey)) || '0') - - // 获取窗口开始时间和计算剩余时间 - const windowStart = await client.get(windowStartKey) - if (windowStart) { - const now = Date.now() - windowStartTime = parseInt(windowStart) - const windowDuration = rateLimitWindow * 60 * 1000 // 转换为毫秒 - windowEndTime = windowStartTime + windowDuration - - // 如果窗口还有效 - if (now < windowEndTime) { - windowRemainingSeconds = Math.max(0, Math.floor((windowEndTime - now) / 1000)) - } else { - // 窗口已过期 - windowRemainingSeconds = 0 - currentWindowCost = 0 - } - } - } - } catch (error) { - logger.warn(`⚠️ 获取实时限制数据失败 (key: ${keyId}):`, error.message) - } - return { requests: totalRequests, tokens,