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 <LewisPen@nyadoo.com>
This commit is contained in:
fawney19
2026-03-15 14:22:59 +08:00
parent 920a383136
commit f92b0943b5
35 changed files with 2051 additions and 238 deletions

View File

@@ -86,6 +86,7 @@ export interface UserExport {
allowed_providers?: string[] | null
allowed_api_formats?: string[] | null
allowed_models?: string[] | null
rate_limit?: number | null // null = 跟随系统默认0 = 不限制
model_capability_settings?: Record<string, Record<string, boolean>>
unlimited?: boolean
wallet?: BillingSummary | null
@@ -102,7 +103,7 @@ export interface UserApiKeyExport {
allowed_providers?: string[] | null
allowed_api_formats?: string[] | null
allowed_models?: string[] | null
rate_limit?: number | null // null = 无限制
rate_limit?: number | null // legacy/null 兼容1.3+ standalone null = 跟随系统默认
concurrent_limit?: number | null
force_capabilities?: Record<string, boolean>
is_active: boolean
@@ -349,7 +350,7 @@ export interface AdminApiKey {
total_requests?: number
total_tokens?: number
total_cost_usd?: number
rate_limit?: number | null // null = 限制
rate_limit?: number | null // null = 跟随系统默认0 = 不限制
allowed_providers?: string[] | null // 允许的提供商列表
allowed_api_formats?: string[] | null // 允许的 API 格式列表
allowed_models?: string[] | null // 允许的模型列表
@@ -365,7 +366,7 @@ export interface CreateStandaloneApiKeyRequest {
allowed_providers?: string[] | null
allowed_api_formats?: string[] | null
allowed_models?: string[] | null
rate_limit?: number | null // null = 限制
rate_limit?: number | null // null = 跟随系统默认0 = 不限制
expires_at?: string | null // ISO 日期字符串,如 "2025-12-31"null = 永不过期
initial_balance_usd: number | null // 初始余额null = 无限制
unlimited_balance?: boolean | null // 编辑时仅切换额度模式,不调整余额数值

View File

@@ -142,6 +142,7 @@ export interface ApiKey {
created_at: string
total_requests?: number
total_cost_usd?: number
rate_limit?: number | null
allowed_providers?: ProviderConfig[]
force_capabilities?: Record<string, boolean> | null // 强制能力配置
}
@@ -181,8 +182,8 @@ export const meApi = {
return response.data
},
async createApiKey(name: string): Promise<ApiKey> {
const response = await apiClient.post<ApiKey>('/api/users/me/api-keys', { name })
async createApiKey(data: { name: string; rate_limit?: number }): Promise<ApiKey> {
const response = await apiClient.post<ApiKey>('/api/users/me/api-keys', data)
return response.data
},
@@ -212,6 +213,17 @@ export const meApi = {
return response.data
},
async updateApiKey(
keyId: string,
data: { name?: string; rate_limit?: number | null }
): Promise<ApiKey & { message: string }> {
const response = await apiClient.put<ApiKey & { message: string }>(
`/api/users/me/api-keys/${keyId}`,
data
)
return response.data
},
// 使用统计
async getUsage(params?: {
start_date?: string

View File

@@ -10,6 +10,7 @@ export interface User {
allowed_providers: string[] | null // 允许使用的提供商 ID 列表
allowed_api_formats: string[] | null // 允许使用的 API 格式列表
allowed_models: string[] | null // 允许使用的模型名称列表
rate_limit?: number | null // null = 跟随系统默认0 = 不限制
created_at: string
updated_at?: string
last_login_at?: string | null
@@ -25,6 +26,7 @@ export interface CreateUserRequest {
allowed_providers?: string[] | null
allowed_api_formats?: string[] | null
allowed_models?: string[] | null
rate_limit?: number | null
}
export interface UpdateUserRequest {
@@ -36,6 +38,7 @@ export interface UpdateUserRequest {
allowed_providers?: string[] | null
allowed_api_formats?: string[] | null
allowed_models?: string[] | null
rate_limit?: number | null
}
export interface ApiKey {
@@ -49,11 +52,16 @@ export interface ApiKey {
is_active: boolean
is_locked: boolean // 管理员锁定标志
is_standalone: boolean // 是否为独立余额Key
rate_limit?: number // 速率限制(请求/分钟)
rate_limit?: number | null // 普通Key: 0 = 不限制,历史 null 视为跟随系统默认
total_requests?: number // 总请求数
total_cost_usd?: number // 总费用
}
export interface UpsertUserApiKeyRequest {
name?: string
rate_limit?: number | null
}
export const usersApi = {
async getAllUsers(): Promise<User[]> {
const response = await apiClient.get<User[]>('/api/admin/users')
@@ -84,8 +92,26 @@ export const usersApi = {
return response.data.api_keys
},
async createApiKey(userId: string, name?: string): Promise<ApiKey & { key: string }> {
const response = await apiClient.post<ApiKey & { key: string }>(`/api/admin/users/${userId}/api-keys`, { name })
async createApiKey(
userId: string,
data: UpsertUserApiKeyRequest
): Promise<ApiKey & { key: string }> {
const response = await apiClient.post<ApiKey & { key: string }>(
`/api/admin/users/${userId}/api-keys`,
data
)
return response.data
},
async updateApiKey(
userId: string,
keyId: string,
data: UpsertUserApiKeyRequest
): Promise<ApiKey & { message: string }> {
const response = await apiClient.put<ApiKey & { message: string }>(
`/api/admin/users/${userId}/api-keys/${keyId}`,
data
)
return response.data
},