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

@@ -49,6 +49,7 @@ def test_list_users_uses_wallet_batch_lookup(monkeypatch: pytest.MonkeyPatch) ->
allowed_providers=None,
allowed_api_formats=None,
allowed_models=None,
rate_limit=None,
is_active=True,
created_at=now,
updated_at=now,
@@ -62,6 +63,7 @@ def test_list_users_uses_wallet_batch_lookup(monkeypatch: pytest.MonkeyPatch) ->
allowed_providers=None,
allowed_api_formats=None,
allowed_models=None,
rate_limit=None,
is_active=True,
created_at=now,
updated_at=None,
@@ -101,24 +103,14 @@ async def test_create_user_adapter_preserves_empty_restriction_lists(
db = MagicMock()
captured: dict[str, Any] = {}
def _create_user(**kwargs: Any) -> SimpleNamespace:
captured.update(kwargs)
return SimpleNamespace(
id="user-3",
email="u3@example.com",
username="user3",
role=SimpleNamespace(value="user"),
is_active=True,
allowed_providers=[],
allowed_api_formats=[],
allowed_models=[],
)
def _create_user_sync(request: Any, role: Any) -> tuple[dict[str, Any], dict[str, Any]]:
captured["allowed_providers"] = request.allowed_providers
captured["allowed_api_formats"] = request.allowed_api_formats
captured["allowed_models"] = request.allowed_models
captured["role"] = role
return {"id": "user-3"}, {}
monkeypatch.setattr("src.api.admin.users.routes.UserService.create_user", _create_user)
monkeypatch.setattr(
"src.api.admin.users.routes._serialize_user",
lambda _db, user: {"id": user.id},
)
monkeypatch.setattr("src.api.admin.users.routes._create_user_sync", _create_user_sync)
context = SimpleNamespace(
db=db,