mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
feat: 添加API Key时间窗口限流功能并移除累计总量限制
- 新增时间窗口限流功能,支持按分钟设置时间窗口 - 支持在时间窗口内限制请求次数和Token使用量 - 移除原有的累计总量限制,只保留时间窗口限制 - Token统计包含所有4种类型:输入、输出、缓存创建、缓存读取 - 前端UI优化,明确显示限流参数的作用范围 - 限流触发时提供友好的错误提示和重置时间 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -117,6 +117,8 @@ const app = createApp({
|
||||
tokenLimit: '',
|
||||
description: '',
|
||||
concurrencyLimit: '',
|
||||
rateLimitWindow: '',
|
||||
rateLimitRequests: '',
|
||||
claudeAccountId: '',
|
||||
enableModelRestriction: false,
|
||||
restrictedModels: [],
|
||||
@@ -158,6 +160,8 @@ const app = createApp({
|
||||
name: '',
|
||||
tokenLimit: '',
|
||||
concurrencyLimit: '',
|
||||
rateLimitWindow: '',
|
||||
rateLimitRequests: '',
|
||||
claudeAccountId: '',
|
||||
enableModelRestriction: false,
|
||||
restrictedModels: [],
|
||||
@@ -1211,6 +1215,8 @@ const app = createApp({
|
||||
tokenLimit: this.apiKeyForm.tokenLimit && this.apiKeyForm.tokenLimit.trim() ? parseInt(this.apiKeyForm.tokenLimit) : null,
|
||||
description: this.apiKeyForm.description || '',
|
||||
concurrencyLimit: this.apiKeyForm.concurrencyLimit && this.apiKeyForm.concurrencyLimit.trim() ? parseInt(this.apiKeyForm.concurrencyLimit) : 0,
|
||||
rateLimitWindow: this.apiKeyForm.rateLimitWindow && this.apiKeyForm.rateLimitWindow.trim() ? parseInt(this.apiKeyForm.rateLimitWindow) : null,
|
||||
rateLimitRequests: this.apiKeyForm.rateLimitRequests && this.apiKeyForm.rateLimitRequests.trim() ? parseInt(this.apiKeyForm.rateLimitRequests) : null,
|
||||
claudeAccountId: this.apiKeyForm.claudeAccountId || null,
|
||||
enableModelRestriction: this.apiKeyForm.enableModelRestriction,
|
||||
restrictedModels: this.apiKeyForm.restrictedModels
|
||||
@@ -1231,7 +1237,7 @@ const app = createApp({
|
||||
|
||||
// 关闭创建弹窗并清理表单
|
||||
this.showCreateApiKeyModal = false;
|
||||
this.apiKeyForm = { name: '', tokenLimit: '', description: '', concurrencyLimit: '', claudeAccountId: '', enableModelRestriction: false, restrictedModels: [], modelInput: '' };
|
||||
this.apiKeyForm = { name: '', tokenLimit: '', description: '', concurrencyLimit: '', rateLimitWindow: '', rateLimitRequests: '', claudeAccountId: '', enableModelRestriction: false, restrictedModels: [], modelInput: '' };
|
||||
|
||||
// 重新加载API Keys列表
|
||||
await this.loadApiKeys();
|
||||
@@ -1275,6 +1281,8 @@ const app = createApp({
|
||||
name: key.name,
|
||||
tokenLimit: key.tokenLimit || '',
|
||||
concurrencyLimit: key.concurrencyLimit || '',
|
||||
rateLimitWindow: key.rateLimitWindow || '',
|
||||
rateLimitRequests: key.rateLimitRequests || '',
|
||||
claudeAccountId: key.claudeAccountId || '',
|
||||
enableModelRestriction: key.enableModelRestriction || false,
|
||||
restrictedModels: key.restrictedModels ? [...key.restrictedModels] : [],
|
||||
@@ -1290,6 +1298,8 @@ const app = createApp({
|
||||
name: '',
|
||||
tokenLimit: '',
|
||||
concurrencyLimit: '',
|
||||
rateLimitWindow: '',
|
||||
rateLimitRequests: '',
|
||||
claudeAccountId: '',
|
||||
enableModelRestriction: false,
|
||||
restrictedModels: [],
|
||||
@@ -1309,6 +1319,8 @@ const app = createApp({
|
||||
body: JSON.stringify({
|
||||
tokenLimit: this.editApiKeyForm.tokenLimit && this.editApiKeyForm.tokenLimit.toString().trim() !== '' ? parseInt(this.editApiKeyForm.tokenLimit) : 0,
|
||||
concurrencyLimit: this.editApiKeyForm.concurrencyLimit && this.editApiKeyForm.concurrencyLimit.toString().trim() !== '' ? parseInt(this.editApiKeyForm.concurrencyLimit) : 0,
|
||||
rateLimitWindow: this.editApiKeyForm.rateLimitWindow && this.editApiKeyForm.rateLimitWindow.toString().trim() !== '' ? parseInt(this.editApiKeyForm.rateLimitWindow) : 0,
|
||||
rateLimitRequests: this.editApiKeyForm.rateLimitRequests && this.editApiKeyForm.rateLimitRequests.toString().trim() !== '' ? parseInt(this.editApiKeyForm.rateLimitRequests) : 0,
|
||||
claudeAccountId: this.editApiKeyForm.claudeAccountId || null,
|
||||
enableModelRestriction: this.editApiKeyForm.enableModelRestriction,
|
||||
restrictedModels: this.editApiKeyForm.restrictedModels
|
||||
|
||||
@@ -534,6 +534,16 @@
|
||||
<span v-if="key.concurrencyLimit > 0" class="text-xs text-gray-500">/ {{ key.concurrencyLimit }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- 时间窗口限流 -->
|
||||
<div v-if="key.rateLimitWindow > 0" class="flex justify-between text-sm">
|
||||
<span class="text-gray-600">时间窗口:</span>
|
||||
<span class="font-medium text-indigo-600">{{ key.rateLimitWindow }} 分钟</span>
|
||||
</div>
|
||||
<!-- 请求次数限制 -->
|
||||
<div v-if="key.rateLimitRequests > 0" class="flex justify-between text-sm">
|
||||
<span class="text-gray-600">请求限制:</span>
|
||||
<span class="font-medium text-indigo-600">{{ key.rateLimitRequests }} 次/窗口</span>
|
||||
</div>
|
||||
<!-- 输入/输出Token -->
|
||||
<div class="flex justify-between text-xs text-gray-500">
|
||||
<span>输入: {{ formatNumber((key.usage && key.usage.total && key.usage.total.inputTokens) || 0) }}</span>
|
||||
@@ -1792,14 +1802,38 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">Token 限制 (可选)</label>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">时间窗口 (可选)</label>
|
||||
<input
|
||||
v-model="apiKeyForm.rateLimitWindow"
|
||||
type="number"
|
||||
min="1"
|
||||
placeholder="留空表示无限制"
|
||||
class="form-input w-full"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-2">设置时间窗口(分钟),在此时间内限制请求次数或Token使用量</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">请求次数限制 (可选)</label>
|
||||
<input
|
||||
v-model="apiKeyForm.rateLimitRequests"
|
||||
type="number"
|
||||
min="1"
|
||||
placeholder="留空表示无限制"
|
||||
class="form-input w-full"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-2">在时间窗口内允许的最大请求次数</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">时间窗口内 Token 限制 (可选)</label>
|
||||
<input
|
||||
v-model="apiKeyForm.tokenLimit"
|
||||
type="number"
|
||||
placeholder="留空表示无限制"
|
||||
class="form-input w-full"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-2">设置此 API Key 的最大 token 使用量</p>
|
||||
<p class="text-xs text-gray-500 mt-2">设置在时间窗口内的最大 Token 使用量(需同时设置时间窗口)</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -1951,7 +1985,31 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">Token 限制</label>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">时间窗口</label>
|
||||
<input
|
||||
v-model="editApiKeyForm.rateLimitWindow"
|
||||
type="number"
|
||||
min="1"
|
||||
placeholder="留空表示无限制"
|
||||
class="form-input w-full"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-2">设置时间窗口(分钟),在此时间内限制请求次数或Token使用量</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">请求次数限制</label>
|
||||
<input
|
||||
v-model="editApiKeyForm.rateLimitRequests"
|
||||
type="number"
|
||||
min="1"
|
||||
placeholder="留空表示无限制"
|
||||
class="form-input w-full"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-2">在时间窗口内允许的最大请求次数</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">时间窗口内 Token 限制</label>
|
||||
<input
|
||||
v-model="editApiKeyForm.tokenLimit"
|
||||
type="number"
|
||||
@@ -1959,7 +2017,7 @@
|
||||
placeholder="0 表示无限制"
|
||||
class="form-input w-full"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-2">设置此 API Key 的最大 token 使用量,0 或留空表示无限制</p>
|
||||
<p class="text-xs text-gray-500 mt-2">设置在时间窗口内的最大 Token 使用量(需同时设置时间窗口),0 或留空表示无限制</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user