feat: 添加API Key并发控制和编辑功能

- 新增API Key并发控制功能
  - 创建API Key时可设置并发限制(0为不限制)
  - 在认证中间件中实现并发检查
  - 使用Redis原子操作确保计数准确
  - 添加自动清理机制处理异常情况

- 新增API Key编辑功能
  - 支持修改Token限制和并发限制
  - 前端添加编辑按钮和模态框
  - 后端限制只能修改指定字段

- 其他改进
  - 添加test-concurrency.js测试脚本
  - 添加详细的功能说明文档
  - 所有代码通过ESLint检查

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-16 09:45:47 +08:00
parent efa7048018
commit 12b41ceb25
9 changed files with 654 additions and 15 deletions

View File

@@ -61,13 +61,46 @@ const authenticateApiKey = async (req, res, next) => {
res.setHeader('X-RateLimit-Reset', rateLimitResult.resetTime);
res.setHeader('X-RateLimit-Policy', `${rateLimitResult.limit};w=60`);
// 检查并发限制
const concurrencyLimit = validation.keyData.concurrencyLimit || 0;
if (concurrencyLimit > 0) {
const currentConcurrency = await redis.incrConcurrency(validation.keyData.id);
if (currentConcurrency > concurrencyLimit) {
// 如果超过限制,立即减少计数
await redis.decrConcurrency(validation.keyData.id);
logger.security(`🚦 Concurrency limit exceeded for key: ${validation.keyData.id} (${validation.keyData.name}), current: ${currentConcurrency - 1}, limit: ${concurrencyLimit}`);
return res.status(429).json({
error: 'Concurrency limit exceeded',
message: `Too many concurrent requests. Limit: ${concurrencyLimit} concurrent requests`,
currentConcurrency: currentConcurrency - 1,
concurrencyLimit
});
}
// 在响应结束时减少并发计数
res.on('finish', () => {
redis.decrConcurrency(validation.keyData.id).catch(error => {
logger.error('Failed to decrement concurrency:', error);
});
});
// 在响应错误时也减少并发计数
res.on('error', () => {
redis.decrConcurrency(validation.keyData.id).catch(error => {
logger.error('Failed to decrement concurrency on error:', error);
});
});
}
// 将验证信息添加到请求对象(只包含必要信息)
req.apiKey = {
id: validation.keyData.id,
name: validation.keyData.name,
tokenLimit: validation.keyData.tokenLimit,
requestLimit: validation.keyData.requestLimit,
claudeAccountId: validation.keyData.claudeAccountId
claudeAccountId: validation.keyData.claudeAccountId,
concurrencyLimit: validation.keyData.concurrencyLimit
};
req.usage = validation.keyData.usage;