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:
fawney19
2026-03-15 14:22:59 +08:00
co-authored by LewisPen
parent 920a383136
commit f92b0943b5
35 changed files with 2051 additions and 238 deletions
+29 -3
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
},