mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-10 19:29:50 +08:00
feat(rate-limit): 实现分层 RPM 限速,支持系统默认/用户/独立Key三级配置
- 新增用户级 rate_limit 字段,支持系统默认/用户自定义/不限制三种模式 - 独立 Key 的 rate_limit 语义调整:null=跟随系统默认,0=不限制,>0=自定义 - 实现 UserRpmLimiter 基于 Redis sliding window 的 RPM 限速引擎 - Pipeline 请求流程集成用户级 RPM 检查 - 管理后台和用户面板新增 RPM 限速配置与实时状态查看 - 系统设置新增全局默认 RPM 配置项 - 迁移脚本回填现有 API Key 的 rate_limit 默认值 - 新增用户/Key RPM 状态监控 API 和前端展示 Closes #231 Co-authored-by: LewisPen <[email protected]>
This commit is contained in:
@@ -105,8 +105,8 @@
|
||||
<TableHead class="w-[240px] h-12 font-semibold">
|
||||
钱包
|
||||
</TableHead>
|
||||
<TableHead class="w-[130px] h-12 font-semibold">
|
||||
使用统计
|
||||
<TableHead class="w-[190px] h-12 font-semibold">
|
||||
统计/限速
|
||||
</TableHead>
|
||||
<TableHead class="w-[110px] h-12 font-semibold">
|
||||
有效期
|
||||
@@ -221,7 +221,23 @@
|
||||
请求: <span class="font-medium text-foreground">{{ (apiKey.total_requests || 0).toLocaleString() }}</span>
|
||||
</div>
|
||||
<div class="text-muted-foreground">
|
||||
速率: <span class="font-medium text-foreground">{{ apiKey.rate_limit ? `${apiKey.rate_limit}/min` : '未设置' }}</span>
|
||||
Tokens: <span class="font-medium text-foreground">{{ formatTokens(apiKey.total_tokens || 0) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 text-muted-foreground">
|
||||
<span>限速:</span>
|
||||
<Badge
|
||||
v-if="isRateLimitInherited(apiKey.rate_limit) || isRateLimitUnlimited(apiKey.rate_limit)"
|
||||
variant="secondary"
|
||||
class="h-5 px-1.5 py-0 text-[10px] font-medium"
|
||||
>
|
||||
{{ formatRateLimitInheritable(apiKey.rate_limit) }}
|
||||
</Badge>
|
||||
<span
|
||||
v-else
|
||||
class="font-medium text-foreground"
|
||||
>
|
||||
{{ formatRateLimitInheritable(apiKey.rate_limit) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -387,6 +403,12 @@
|
||||
>
|
||||
{{ walletStatusLabel(getApiKeyWalletStatus(apiKey.id)) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
class="h-5 px-1.5 py-0 text-[10px] font-medium"
|
||||
>
|
||||
{{ formatRateLimitInheritable(apiKey.rate_limit) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="apiKey.auto_delete_on_expiry"
|
||||
variant="secondary"
|
||||
@@ -429,14 +451,6 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2.5 text-xs">
|
||||
<div class="rounded-lg border border-border/50 bg-background/70 p-2.5">
|
||||
<div class="mb-1 text-muted-foreground">
|
||||
速率限制
|
||||
</div>
|
||||
<div class="font-semibold text-foreground">
|
||||
{{ apiKey.rate_limit ? `${apiKey.rate_limit}/min` : '未设置' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-border/50 bg-background/70 p-2.5">
|
||||
<div class="mb-1 text-muted-foreground">
|
||||
请求次数
|
||||
@@ -445,6 +459,14 @@
|
||||
{{ (apiKey.total_requests || 0).toLocaleString() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-border/50 bg-background/70 p-2.5">
|
||||
<div class="mb-1 text-muted-foreground">
|
||||
Tokens
|
||||
</div>
|
||||
<div class="font-semibold text-foreground">
|
||||
{{ formatTokens(apiKey.total_tokens || 0) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 rounded-lg border border-border/50 bg-background/70 p-2.5">
|
||||
<div class="mb-1 text-muted-foreground">
|
||||
有效期
|
||||
@@ -663,6 +685,7 @@ import {
|
||||
|
||||
import { StandaloneKeyFormDialog, type StandaloneKeyFormData } from '@/features/api-keys'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import { formatTokens, formatRateLimitInheritable, isRateLimitInherited, isRateLimitUnlimited } from '@/utils/format'
|
||||
import { log } from '@/utils/logger'
|
||||
|
||||
const { success, error } = useToast()
|
||||
@@ -1028,7 +1051,7 @@ async function handleKeyFormSubmit(data: StandaloneKeyFormData) {
|
||||
const updateData: Partial<CreateStandaloneApiKeyRequest> = {
|
||||
name: data.name || undefined,
|
||||
unlimited_balance: Boolean(data.unlimited_balance),
|
||||
rate_limit: data.rate_limit ?? null, // undefined = 无限制,显式传 null
|
||||
rate_limit: data.rate_limit ?? null, // undefined = 跟随系统默认,显式传 null
|
||||
expires_at: data.expires_at || null, // undefined/空 = 永不过期
|
||||
auto_delete_on_expiry: data.auto_delete_on_expiry,
|
||||
// 空数组表示清除限制(允许全部),后端会将空数组存为 NULL
|
||||
@@ -1057,7 +1080,7 @@ async function handleKeyFormSubmit(data: StandaloneKeyFormData) {
|
||||
const createData: CreateStandaloneApiKeyRequest = {
|
||||
name: data.name || undefined,
|
||||
initial_balance_usd: isUnlimited ? null : (data.initial_balance_usd as number),
|
||||
rate_limit: data.rate_limit ?? null, // undefined = 无限制,显式传 null
|
||||
rate_limit: data.rate_limit ?? null, // undefined = 跟随系统默认,显式传 null
|
||||
expires_at: data.expires_at || null, // undefined/空 = 永不过期
|
||||
auto_delete_on_expiry: data.auto_delete_on_expiry,
|
||||
// 空数组表示不设置限制(允许全部),后端会将空数组存为 NULL
|
||||
|
||||
Reference in New Issue
Block a user