feat: 为API Key添加模型限制功能

- 前端:在API Key创建和编辑表单中添加模型限制开关和标签输入
- 前端:支持动态添加/删除限制的模型列表
- 后端:更新API Key数据结构,新增enableModelRestriction和restrictedModels字段
- 后端:在中转请求时检查模型访问权限
- 修复:Enter键提交表单问题,使用@keydown.enter.prevent
- 优化:限制模型数据持久化,关闭开关时不清空数据

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-19 20:54:26 +08:00
parent f9933f7061
commit f962083752
9 changed files with 278 additions and 22 deletions

View File

@@ -22,6 +22,24 @@ class ClaudeRelayService {
let upstreamRequest = null;
try {
// 检查模型限制
if (apiKeyData.enableModelRestriction && apiKeyData.restrictedModels && apiKeyData.restrictedModels.length > 0) {
const requestedModel = requestBody.model;
if (requestedModel && apiKeyData.restrictedModels.includes(requestedModel)) {
logger.warn(`🚫 Model restriction violation for key ${apiKeyData.name}: Attempted to use restricted model ${requestedModel}`);
return {
statusCode: 403,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: {
type: 'forbidden',
message: '暂无该模型访问权限'
}
})
};
}
}
// 生成会话哈希用于sticky会话
const sessionHash = sessionHelper.generateSessionHash(requestBody);
@@ -419,6 +437,26 @@ class ClaudeRelayService {
// 🌊 处理流式响应带usage数据捕获
async relayStreamRequestWithUsageCapture(requestBody, apiKeyData, responseStream, clientHeaders, usageCallback) {
try {
// 检查模型限制
if (apiKeyData.enableModelRestriction && apiKeyData.restrictedModels && apiKeyData.restrictedModels.length > 0) {
const requestedModel = requestBody.model;
if (requestedModel && apiKeyData.restrictedModels.includes(requestedModel)) {
logger.warn(`🚫 Model restriction violation for key ${apiKeyData.name}: Attempted to use restricted model ${requestedModel}`);
// 对于流式响应,需要写入错误并结束流
const errorResponse = JSON.stringify({
error: {
type: 'forbidden',
message: '暂无该模型访问权限'
}
});
responseStream.writeHead(403, { 'Content-Type': 'application/json' });
responseStream.end(errorResponse);
return;
}
}
// 生成会话哈希用于sticky会话
const sessionHash = sessionHelper.generateSessionHash(requestBody);