feat: 实现基于滑动窗口的实时RPM/TPM统计

- 添加系统级分钟统计,支持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 <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-30 14:27:34 +08:00
parent 363d1c3ed3
commit a6ab6b7abe
7 changed files with 237 additions and 103 deletions

View File

@@ -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()}`);