From 08f93e98725530874051f435cdcdd5021ea34a0d Mon Sep 17 00:00:00 2001 From: shaw Date: Wed, 16 Jul 2025 10:21:17 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E5=99=A8=E5=9C=A8=E8=AF=B7=E6=B1=82=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E6=97=B6=E4=B8=8D=E8=83=BD=E6=AD=A3=E7=A1=AE=E5=87=8F?= =?UTF-8?q?=E5=B0=91=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增强事件监听机制 - 监听 req 的 close 和 aborted 事件 - 监听 res 的 finish 和 error 事件 - 使用标志位确保只减少一次计数 - 改进日志记录 - 增加并发计数增减的详细日志 - 记录请求关闭和中断事件 - 确保计数器安全性 - 使用 Lua 脚本原子操作防止负数 - 优化 Redis 操作逻辑 - 增强管理界面 - API Keys 列表显示当前并发数 - 并发数超过 0 时用橙色标记 - 显示当前并发数/限制数格式 🤖 Generated with Claude Code Co-Authored-By: Claude --- src/middleware/auth.js | 42 ++++++++++++++++++++++++++--------- src/models/redis.js | 27 +++++++++++++++++----- src/services/apiKeyService.js | 3 ++- web/admin/index.html | 8 +++++++ 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/middleware/auth.js b/src/middleware/auth.js index ff056d80..c2550f93 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -65,6 +65,7 @@ const authenticateApiKey = async (req, res, next) => { const concurrencyLimit = validation.keyData.concurrencyLimit || 0; if (concurrencyLimit > 0) { const currentConcurrency = await redis.incrConcurrency(validation.keyData.id); + logger.api(`📈 Incremented concurrency for key: ${validation.keyData.id} (${validation.keyData.name}), current: ${currentConcurrency}, limit: ${concurrencyLimit}`); if (currentConcurrency > concurrencyLimit) { // 如果超过限制,立即减少计数 @@ -78,19 +79,38 @@ const authenticateApiKey = async (req, res, next) => { }); } - // 在响应结束时减少并发计数 - res.on('finish', () => { - redis.decrConcurrency(validation.keyData.id).catch(error => { - logger.error('Failed to decrement concurrency:', error); - }); + // 使用标志位确保只减少一次 + let concurrencyDecremented = false; + + const decrementConcurrency = async () => { + if (!concurrencyDecremented) { + concurrencyDecremented = true; + try { + const newCount = await redis.decrConcurrency(validation.keyData.id); + logger.api(`📉 Decremented concurrency for key: ${validation.keyData.id} (${validation.keyData.name}), new count: ${newCount}`); + } catch (error) { + logger.error(`Failed to decrement concurrency for key ${validation.keyData.id}:`, error); + } + } + }; + + // 监听多个事件以确保在各种情况下都能正确减少计数 + res.on('finish', decrementConcurrency); + res.on('error', decrementConcurrency); + req.on('close', () => { + logger.api(`🔌 Request closed for key: ${validation.keyData.id} (${validation.keyData.name})`); + decrementConcurrency(); + }); + req.on('aborted', () => { + logger.api(`⚠️ Request aborted for key: ${validation.keyData.id} (${validation.keyData.name})`); + decrementConcurrency(); }); - // 在响应错误时也减少并发计数 - res.on('error', () => { - redis.decrConcurrency(validation.keyData.id).catch(error => { - logger.error('Failed to decrement concurrency on error:', error); - }); - }); + // 存储并发信息到请求对象,便于后续处理 + req.concurrencyInfo = { + apiKeyId: validation.keyData.id, + decrementConcurrency + }; } // 将验证信息添加到请求对象(只包含必要信息) diff --git a/src/models/redis.js b/src/models/redis.js index b44a259f..cfe47c78 100644 --- a/src/models/redis.js +++ b/src/models/redis.js @@ -724,6 +724,7 @@ class RedisClient { // 设置过期时间为5分钟,防止计数器永远不清零 await this.client.expire(key, 300); + logger.database(`🔢 Incremented concurrency for key ${apiKeyId}: ${count}`); return count; } catch (error) { logger.error('❌ Failed to increment concurrency:', error); @@ -735,14 +736,28 @@ class RedisClient { async decrConcurrency(apiKeyId) { try { const key = `concurrency:${apiKeyId}`; - const count = await this.client.decr(key); - // 如果计数降到0或以下,删除键 - if (count <= 0) { - await this.client.del(key); - return 0; - } + // 使用Lua脚本确保原子性操作,防止计数器变成负数 + const luaScript = ` + local key = KEYS[1] + local current = tonumber(redis.call('get', key) or "0") + + if current <= 0 then + redis.call('del', key) + return 0 + else + local new_value = redis.call('decr', key) + if new_value <= 0 then + redis.call('del', key) + return 0 + else + return new_value + end + end + `; + const count = await this.client.eval(luaScript, 1, key); + logger.database(`🔢 Decremented concurrency for key ${apiKeyId}: ${count}`); return count; } catch (error) { logger.error('❌ Failed to decrement concurrency:', error); diff --git a/src/services/apiKeyService.js b/src/services/apiKeyService.js index b500cdd1..c4365199 100644 --- a/src/services/apiKeyService.js +++ b/src/services/apiKeyService.js @@ -132,12 +132,13 @@ class ApiKeyService { try { const apiKeys = await redis.getAllApiKeys(); - // 为每个key添加使用统计 + // 为每个key添加使用统计和当前并发数 for (const key of apiKeys) { key.usage = await redis.getUsageStats(key.id); key.tokenLimit = parseInt(key.tokenLimit); key.requestLimit = parseInt(key.requestLimit); key.concurrencyLimit = parseInt(key.concurrencyLimit || 0); + key.currentConcurrency = await redis.getConcurrency(key.id); key.isActive = key.isActive === 'true'; delete key.apiKey; // 不返回哈希后的key } diff --git a/web/admin/index.html b/web/admin/index.html index 46c28c8f..7bd48660 100644 --- a/web/admin/index.html +++ b/web/admin/index.html @@ -430,6 +430,14 @@ 并发限制: {{ key.concurrencyLimit > 0 ? key.concurrencyLimit : '无限制' }} + +
+ 当前并发: + + {{ key.currentConcurrency || 0 }} + / {{ key.concurrencyLimit }} + +
输入: {{ formatNumber((key.usage && key.usage.total && key.usage.total.inputTokens) || 0) }}