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

@@ -18,7 +18,8 @@ class ApiKeyService {
requestLimit = config.limits.defaultRequestLimit,
expiresAt = null,
claudeAccountId = null,
isActive = true
isActive = true,
concurrencyLimit = 0
} = options;
// 生成简单的API Key (64字符十六进制)
@@ -33,6 +34,7 @@ class ApiKeyService {
apiKey: hashedKey,
tokenLimit: String(tokenLimit ?? 0),
requestLimit: String(requestLimit ?? 0),
concurrencyLimit: String(concurrencyLimit ?? 0),
isActive: String(isActive),
claudeAccountId: claudeAccountId || '',
createdAt: new Date().toISOString(),
@@ -53,6 +55,7 @@ class ApiKeyService {
description: keyData.description,
tokenLimit: parseInt(keyData.tokenLimit),
requestLimit: parseInt(keyData.requestLimit),
concurrencyLimit: parseInt(keyData.concurrencyLimit),
isActive: keyData.isActive === 'true',
claudeAccountId: keyData.claudeAccountId,
createdAt: keyData.createdAt,
@@ -114,6 +117,7 @@ class ApiKeyService {
claudeAccountId: keyData.claudeAccountId,
tokenLimit: parseInt(keyData.tokenLimit),
requestLimit: parseInt(keyData.requestLimit),
concurrencyLimit: parseInt(keyData.concurrencyLimit || 0),
usage
}
};
@@ -133,6 +137,7 @@ class ApiKeyService {
key.usage = await redis.getUsageStats(key.id);
key.tokenLimit = parseInt(key.tokenLimit);
key.requestLimit = parseInt(key.requestLimit);
key.concurrencyLimit = parseInt(key.concurrencyLimit || 0);
key.isActive = key.isActive === 'true';
delete key.apiKey; // 不返回哈希后的key
}
@@ -153,7 +158,7 @@ class ApiKeyService {
}
// 允许更新的字段
const allowedUpdates = ['name', 'description', 'tokenLimit', 'requestLimit', 'isActive', 'claudeAccountId', 'expiresAt'];
const allowedUpdates = ['name', 'description', 'tokenLimit', 'requestLimit', 'concurrencyLimit', 'isActive', 'claudeAccountId', 'expiresAt'];
const updatedData = { ...keyData };
for (const [field, value] of Object.entries(updates)) {