Files
Aether/_deprecated_py_src/api/admin/users/routes.py
fawney19 1d9c77522a refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/
- 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层
- 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构
- 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations)
- 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image
- 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
2026-04-03 16:26:16 +08:00

1005 lines
34 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""用户管理 API 端点。"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from fastapi.concurrency import run_in_threadpool
from pydantic import ValidationError
from sqlalchemy import func
from sqlalchemy.orm import Session
from src.api.base.admin_adapter import AdminApiAdapter
from src.api.base.context import ApiRequestContext
from src.api.base.pipeline import get_pipeline
from src.config.constants import CacheTTL
from src.core.exceptions import InvalidRequestException, NotFoundException, translate_pydantic_error
from src.core.logger import logger
from src.database import get_db, get_db_context
from src.models.admin_requests import UpdateUserRequest
from src.models.api import (
CreateApiKeyRequest,
CreateUserRequest,
UpdateMyApiKeyRequest,
UserSessionResponse,
)
from src.models.database import ApiKey, User, UserRole, Wallet
from src.services.auth.session_service import SessionService
from src.services.cache.user_cache import UserCacheService
from src.services.system.config import SystemConfigService
from src.services.user.apikey import ApiKeyService
from src.services.user.bulk_cleanup import pre_clean_api_key
from src.services.user.service import UserService
from src.services.wallet import WalletService
from src.utils.cache_decorator import cache_result
router = APIRouter(prefix="/api/admin/users", tags=["Admin - Users"])
pipeline = get_pipeline()
class _WalletSentinelType:
pass
_WALLET_SENTINEL = _WalletSentinelType()
def _serialize_user(
db: Session,
user: User,
wallet: Wallet | None | _WalletSentinelType = _WALLET_SENTINEL,
) -> dict[str, Any]:
resolved_wallet: Wallet | None
if wallet is _WALLET_SENTINEL:
resolved_wallet = WalletService.get_wallet(db, user_id=user.id)
else:
resolved_wallet = wallet
return {
"id": user.id,
"email": user.email,
"username": user.username,
"role": user.role.value,
"allowed_providers": user.allowed_providers,
"allowed_api_formats": user.allowed_api_formats,
"allowed_models": user.allowed_models,
"rate_limit": user.rate_limit,
"unlimited": WalletService.is_unlimited_wallet(resolved_wallet),
"is_active": user.is_active,
"created_at": user.created_at.isoformat(),
"updated_at": user.updated_at.isoformat() if user.updated_at else None,
"last_login_at": user.last_login_at.isoformat() if user.last_login_at else None,
}
def _create_user_sync(
request: CreateUserRequest, role: UserRole
) -> tuple[dict[str, Any], dict[str, Any]]:
with get_db_context() as db:
if request.unlimited:
initial_gift_usd = None
elif request.initial_gift_usd is not None:
initial_gift_usd = request.initial_gift_usd
else:
initial_gift_usd = SystemConfigService.get_config(
db, "default_user_initial_gift_usd", default=None
)
user = UserService.create_user(
db=db,
email=request.email,
username=request.username,
password=request.password,
role=role,
initial_gift_usd=initial_gift_usd,
unlimited=request.unlimited,
allowed_providers=request.allowed_providers,
allowed_api_formats=request.allowed_api_formats,
allowed_models=request.allowed_models,
rate_limit=request.rate_limit,
)
return _serialize_user(db, user), {
"action": "create_user",
"target_user_id": user.id,
"target_email": user.email,
"target_username": user.username,
"target_role": user.role.value,
"initial_gift_usd": initial_gift_usd,
"unlimited": request.unlimited,
"is_active": user.is_active,
}
def _update_user_sync(
user_id: str,
request: UpdateUserRequest,
) -> tuple[dict[str, Any], dict[str, Any], bool, str | None]:
with get_db_context() as db:
existing_user = UserService.get_user(db, user_id)
if not existing_user:
raise NotFoundException("用户不存在", "user")
update_data = request.model_dump(exclude_unset=True)
old_role = existing_user.role
existing_wallet = WalletService.get_or_create_wallet(db, user=existing_user)
unlimited_before = WalletService.is_unlimited_wallet(existing_wallet)
requested_unlimited = update_data.pop("unlimited", None)
if "role" in update_data and update_data["role"]:
if not hasattr(update_data["role"], "value"):
update_data["role"] = UserRole[update_data["role"].upper()]
user = UserService.update_user(db, user_id, **update_data)
if not user:
raise NotFoundException("用户不存在", "user")
changed_fields = list(update_data.keys())
if requested_unlimited is not None:
wallet = WalletService.get_or_create_wallet(db, user=user)
if wallet is not None:
WalletService.set_wallet_limit_mode(
db,
wallet=wallet,
limit_mode="unlimited" if requested_unlimited else "finite",
)
changed_fields.append("unlimited")
role_changed = "role" in update_data and update_data["role"] != old_role
response = _serialize_user(db, user)
return (
response,
{
"action": "update_user",
"target_user_id": user.id,
"updated_fields": changed_fields,
"role_before": old_role.value if old_role else None,
"role_after": user.role.value,
"unlimited_before": unlimited_before,
"unlimited_after": (
requested_unlimited if requested_unlimited is not None else unlimited_before
),
"is_active": user.is_active,
},
role_changed,
user.email,
)
def _delete_user_sync(user_id: str) -> tuple[dict[str, Any], dict[str, Any], str | None]:
with get_db_context() as db:
user = UserService.get_user(db, user_id)
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
if user.role == UserRole.ADMIN:
admin_count = int(
db.query(func.count(User.id)).filter(User.role == UserRole.ADMIN).scalar() or 0
)
if admin_count <= 1:
raise InvalidRequestException("不能删除最后一个管理员账户")
try:
success = UserService.delete_user(db, user_id)
except ValueError as exc:
raise InvalidRequestException(str(exc)) from exc
if not success:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
return (
{"message": "用户删除成功"},
{
"action": "delete_user",
"target_user_id": user.id,
"target_email": user.email,
"target_role": user.role.value,
},
user.email,
)
def _list_user_sessions_sync(user_id: str) -> tuple[list[dict[str, Any]], dict[str, Any]]:
with get_db_context() as db:
user = UserService.get_user(db, user_id)
if not user:
raise NotFoundException("用户不存在", "user")
sessions = SessionService.list_user_sessions(db, user_id=user_id)
return (
[UserSessionResponse.from_db(s) for s in sessions],
{
"action": "list_user_sessions",
"target_user_id": user_id,
"session_count": len(sessions),
},
)
def _revoke_user_session_sync(
user_id: str,
session_id: str,
admin_user_id: str,
) -> tuple[dict[str, Any], dict[str, Any]]:
with get_db_context() as db:
user = UserService.get_user(db, user_id)
if not user:
raise NotFoundException("用户不存在", "user")
session = SessionService.get_session_for_user(db, user_id=user_id, session_id=session_id)
if not session:
raise NotFoundException("会话不存在", "session")
SessionService.revoke_session(
db,
session=session,
reason="admin_session_revoked",
audit_user_id=admin_user_id,
)
return (
{"message": "用户设备已强制下线"},
{
"action": "revoke_user_session",
"target_user_id": user_id,
"session_id": session_id,
},
)
def _revoke_all_user_sessions_sync(
user_id: str, admin_user_id: str
) -> tuple[dict[str, Any], dict[str, Any]]:
with get_db_context() as db:
user = UserService.get_user(db, user_id)
if not user:
raise NotFoundException("用户不存在", "user")
revoked_count = SessionService.revoke_all_user_sessions(
db,
user_id=user_id,
reason="admin_revoke_all_sessions",
)
return (
{"message": "已强制下线该用户所有设备", "revoked_count": revoked_count},
{
"action": "revoke_all_user_sessions",
"target_user_id": user_id,
"admin_user_id": admin_user_id,
"revoked_count": revoked_count,
},
)
def _create_user_key_sync(
user_id: str,
key_data: CreateApiKeyRequest,
) -> tuple[dict[str, Any], dict[str, Any], str | None]:
with get_db_context() as db:
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise NotFoundException("用户不存在", "user")
api_key, plain_key = ApiKeyService.create_api_key(
db=db,
user_id=user_id,
name=key_data.name,
allowed_providers=key_data.allowed_providers,
allowed_models=key_data.allowed_models,
rate_limit=key_data.rate_limit,
expire_days=key_data.expire_days,
is_standalone=False,
)
return (
{
"id": api_key.id,
"key": plain_key,
"name": api_key.name,
"key_display": api_key.get_display_key(),
"rate_limit": api_key.rate_limit,
"expires_at": api_key.expires_at.isoformat() if api_key.expires_at else None,
"created_at": api_key.created_at.isoformat(),
"message": "API Key创建成功请妥善保存完整密钥",
},
{
"action": "create_user_api_key",
"target_user_id": user_id,
"key_id": api_key.id,
},
user.email,
)
def _delete_user_key_sync(user_id: str, key_id: str) -> tuple[dict[str, Any], dict[str, Any]]:
with get_db_context() as db:
api_key = (
db.query(ApiKey)
.filter(
ApiKey.id == key_id,
ApiKey.user_id == user_id,
ApiKey.is_standalone == False,
)
.first()
)
if not api_key:
raise NotFoundException("API Key不存在或不属于该用户", "api_key")
pre_clean_api_key(db, api_key.id)
db.delete(api_key)
return {"message": "API Key已删除"}, {
"action": "delete_user_api_key",
"target_user_id": user_id,
"key_id": key_id,
}
def _update_user_key_sync(
user_id: str,
key_id: str,
request: UpdateMyApiKeyRequest,
) -> tuple[dict[str, Any], dict[str, Any]]:
with get_db_context() as db:
api_key = (
db.query(ApiKey)
.filter(
ApiKey.id == key_id,
ApiKey.user_id == user_id,
ApiKey.is_standalone == False,
)
.first()
)
if not api_key:
raise NotFoundException("API Key不存在或不属于该用户", "api_key")
update_data = request.model_dump(exclude_unset=True)
if "rate_limit" in update_data and update_data["rate_limit"] is None:
update_data["rate_limit"] = 0
updated_key = ApiKeyService.update_api_key(db, key_id, **update_data)
if not updated_key:
raise NotFoundException("API Key不存在或不属于该用户", "api_key")
return (
{
"id": updated_key.id,
"name": updated_key.name,
"key_display": updated_key.get_display_key(),
"is_active": updated_key.is_active,
"is_locked": updated_key.is_locked,
"total_requests": updated_key.total_requests,
"total_cost_usd": float(updated_key.total_cost_usd or 0),
"rate_limit": updated_key.rate_limit,
"expires_at": (
updated_key.expires_at.isoformat() if updated_key.expires_at else None
),
"last_used_at": (
updated_key.last_used_at.isoformat() if updated_key.last_used_at else None
),
"created_at": updated_key.created_at.isoformat(),
"message": "API Key更新成功",
},
{
"action": "update_user_api_key",
"target_user_id": user_id,
"key_id": key_id,
"updated_fields": list(update_data.keys()),
},
)
def _toggle_user_key_lock_sync(
user_id: str,
key_id: str,
) -> tuple[dict[str, Any], dict[str, Any]]:
with get_db_context() as db:
api_key = (
db.query(ApiKey)
.filter(
ApiKey.id == key_id,
ApiKey.user_id == user_id,
ApiKey.is_standalone == False,
)
.first()
)
if not api_key:
raise NotFoundException("API Key不存在或不属于该用户", "api_key")
api_key.is_locked = not api_key.is_locked
db.commit()
db.refresh(api_key)
return (
{
"id": api_key.id,
"is_locked": api_key.is_locked,
"message": f"API密钥已{'锁定' if api_key.is_locked else '解锁'}",
},
{
"action": "toggle_user_api_key_lock",
"target_user_id": user_id,
"key_id": key_id,
"new_lock_status": "locked" if api_key.is_locked else "unlocked",
},
)
# 管理员端点
@router.post("")
async def create_user_endpoint(request: Request, db: Session = Depends(get_db)) -> Any:
"""
创建用户
创建新用户账号(管理员专用)。
**请求体**:
- `email`: 邮箱地址
- `username`: 用户名
- `password`: 密码
- `role`: 角色user/admin
- `initial_gift_usd`: 初始赠款USD可选
- `unlimited`: 是否无限制
"""
adapter = AdminCreateUserAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("")
async def list_users(
request: Request,
skip: int = Query(0, ge=0, description="跳过记录数"),
limit: int = Query(100, ge=1, le=1000, description="返回记录数"),
role: str | None = Query(None, description="按角色筛选user/admin"),
is_active: bool | None = Query(None, description="按状态筛选"),
db: Session = Depends(get_db),
) -> Any:
"""
获取用户列表
分页获取用户列表,支持按角色和状态筛选。
**返回字段**: id, email, username, role, unlimited, is_active, created_at 等
"""
adapter = AdminListUsersAdapter(skip=skip, limit=limit, role=role, is_active=is_active)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/{user_id}")
async def get_user(user_id: str, request: Request, db: Session = Depends(get_db)) -> Any:
"""
获取用户详情
获取指定用户的详细信息。
**路径参数**:
- `user_id`: 用户 ID (UUID)
"""
adapter = AdminGetUserAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/{user_id}/sessions")
async def list_user_sessions(user_id: str, request: Request, db: Session = Depends(get_db)) -> Any:
"""获取用户登录设备列表。"""
adapter = AdminListUserSessionsAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/{user_id}/sessions/{session_id}")
async def revoke_user_session(
user_id: str,
session_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""强制下线用户的单个设备会话。"""
adapter = AdminRevokeUserSessionAdapter(user_id=user_id, session_id=session_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/{user_id}/sessions")
async def revoke_all_user_sessions(
user_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""强制下线用户的全部设备会话。"""
adapter = AdminRevokeAllUserSessionsAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.put("/{user_id}")
async def update_user(
user_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""
更新用户信息
更新指定用户的信息,包括角色、无限制开关、权限等。
**路径参数**:
- `user_id`: 用户 ID (UUID)
**请求体** (均为可选):
- `email`: 邮箱地址
- `username`: 用户名
- `role`: 角色
- `unlimited`: 是否无限制
- `is_active`: 是否启用
- `allowed_providers`: 允许的提供商列表
- `allowed_models`: 允许的模型列表
"""
adapter = AdminUpdateUserAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/{user_id}")
async def delete_user(user_id: str, request: Request, db: Session = Depends(get_db)) -> None:
"""
删除用户
永久删除指定用户。不能删除最后一个管理员账户。
**路径参数**:
- `user_id`: 用户 ID (UUID)
"""
adapter = AdminDeleteUserAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/{user_id}/api-keys")
async def get_user_api_keys(
user_id: str,
request: Request,
is_active: bool | None = Query(None, description="按状态筛选"),
db: Session = Depends(get_db),
) -> Any:
"""
获取用户的 API 密钥列表
获取指定用户的所有 API 密钥(不包括独立密钥)。
**路径参数**:
- `user_id`: 用户 ID (UUID)
"""
adapter = AdminGetUserKeysAdapter(user_id=user_id, is_active=is_active)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.post("/{user_id}/api-keys")
async def create_user_api_key(
user_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""
为用户创建 API 密钥
为指定用户创建新的 API 密钥。
**路径参数**:
- `user_id`: 用户 ID (UUID)
**请求体**:
- `name`: 密钥名称
- `allowed_providers`: 允许的提供商(可选)
- `allowed_models`: 允许的模型(可选)
- `rate_limit`: 速率限制(可选)
- `expire_days`: 过期天数(可选)
**返回**: 包含完整密钥值的响应(仅此一次显示)
"""
adapter = AdminCreateUserKeyAdapter(user_id=user_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.delete("/{user_id}/api-keys/{key_id}")
async def delete_user_api_key(
user_id: str,
key_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""
删除用户的 API 密钥
删除指定用户的指定 API 密钥。
**路径参数**:
- `user_id`: 用户 ID (UUID)
- `key_id`: 密钥 ID
"""
adapter = AdminDeleteUserKeyAdapter(user_id=user_id, key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.put("/{user_id}/api-keys/{key_id}")
async def update_user_api_key(
user_id: str,
key_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""
更新用户的 API 密钥
更新指定用户的普通 API 密钥基础配置。
**路径参数**:
- `user_id`: 用户 ID (UUID)
- `key_id`: 密钥 ID
"""
adapter = AdminUpdateUserKeyAdapter(user_id=user_id, key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.patch("/{user_id}/api-keys/{key_id}/lock")
async def toggle_user_api_key_lock(
user_id: str,
key_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""
切换用户 API 密钥锁定状态
仅支持普通用户 Key非独立 Key
**路径参数**:
- `user_id`: 用户 ID (UUID)
- `key_id`: 密钥 ID
"""
adapter = AdminToggleUserKeyLockAdapter(user_id=user_id, key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@router.get("/{user_id}/api-keys/{key_id}/full-key")
async def get_user_api_key_full_key(
user_id: str,
key_id: str,
request: Request,
db: Session = Depends(get_db),
) -> Any:
"""
获取用户 API 密钥完整值
仅支持普通用户 Key非独立 Key
**路径参数**:
- `user_id`: 用户 ID (UUID)
- `key_id`: 密钥 ID
"""
adapter = AdminGetUserKeyFullKeyAdapter(user_id=user_id, key_id=key_id)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
# ============== 管理员适配器实现 ==============
class AdminCreateUserAdapter(AdminApiAdapter):
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
payload = context.ensure_json_body()
try:
request = CreateUserRequest.model_validate(payload)
except ValidationError as e:
errors = e.errors()
if errors:
raise InvalidRequestException(translate_pydantic_error(errors[0]))
raise InvalidRequestException("请求数据验证失败")
try:
role = (
request.role if hasattr(request.role, "value") else UserRole[request.role.upper()]
)
except (KeyError, AttributeError):
raise InvalidRequestException("角色参数不合法")
try:
response, audit_meta = await run_in_threadpool(_create_user_sync, request, role)
except ValueError as exc:
raise InvalidRequestException(str(exc)) from exc
context.add_audit_metadata(**audit_meta)
return response
class AdminListUsersAdapter(AdminApiAdapter):
def __init__(self, skip: int, limit: int, role: str | None, is_active: bool | None):
self.skip = skip
self.limit = limit
self.role = role
self.is_active = is_active
@cache_result(
key_prefix="admin:users:list",
ttl=CacheTTL.USER,
user_specific=False,
vary_by=["skip", "limit", "role", "is_active"],
)
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
db = context.db
try:
role_enum = UserRole[self.role.upper()] if self.role else None
except KeyError as exc:
raise InvalidRequestException("角色参数不合法") from exc
users = UserService.list_users(db, self.skip, self.limit, role_enum, self.is_active)
wallets_by_user_id = WalletService.get_wallets_by_user_ids(db, [user.id for user in users])
return [_serialize_user(db, user, wallets_by_user_id.get(user.id)) for user in users]
class AdminGetUserAdapter(AdminApiAdapter):
def __init__(self, user_id: str):
self.user_id = user_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
db = context.db
user = UserService.get_user(db, self.user_id)
if not user:
raise NotFoundException("用户不存在", "user")
context.add_audit_metadata(
action="get_user_detail",
target_user_id=user.id,
target_role=user.role.value,
include_history=bool(user.last_login_at),
)
return _serialize_user(db, user)
class AdminListUserSessionsAdapter(AdminApiAdapter):
def __init__(self, user_id: str):
self.user_id = user_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
response, audit_meta = await run_in_threadpool(_list_user_sessions_sync, self.user_id)
context.add_audit_metadata(**audit_meta)
return response
class AdminRevokeUserSessionAdapter(AdminApiAdapter):
def __init__(self, user_id: str, session_id: str):
self.user_id = user_id
self.session_id = session_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
admin_user_id = context.user.id if context.user else ""
response, audit_meta = await run_in_threadpool(
_revoke_user_session_sync, self.user_id, self.session_id, admin_user_id
)
context.add_audit_metadata(**audit_meta)
return response
class AdminRevokeAllUserSessionsAdapter(AdminApiAdapter):
def __init__(self, user_id: str):
self.user_id = user_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
admin_user_id = context.user.id if context.user else ""
response, audit_meta = await run_in_threadpool(
_revoke_all_user_sessions_sync, self.user_id, admin_user_id
)
context.add_audit_metadata(**audit_meta)
return response
class AdminUpdateUserAdapter(AdminApiAdapter):
def __init__(self, user_id: str):
self.user_id = user_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
payload = context.ensure_json_body()
try:
request = UpdateUserRequest.model_validate(payload)
except ValidationError as e:
errors = e.errors()
if errors:
raise InvalidRequestException(translate_pydantic_error(errors[0]))
raise InvalidRequestException("请求数据验证失败")
response, audit_meta, role_changed, user_email = await run_in_threadpool(
_update_user_sync,
self.user_id,
request,
)
if user_email:
await UserCacheService.invalidate_user_cache(self.user_id, user_email)
if role_changed:
from src.services.usage.service import UsageService
await UsageService.clear_user_heatmap_cache(self.user_id)
context.add_audit_metadata(**audit_meta)
return response
class AdminDeleteUserAdapter(AdminApiAdapter):
def __init__(self, user_id: str):
self.user_id = user_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
response, audit_meta, user_email = await run_in_threadpool(_delete_user_sync, self.user_id)
if user_email:
await UserCacheService.invalidate_user_cache(self.user_id, user_email)
context.add_audit_metadata(**audit_meta)
return response
class AdminGetUserKeysAdapter(AdminApiAdapter):
"""获取用户的API Keys"""
def __init__(self, user_id: str, is_active: bool | None):
self.user_id = user_id
self.is_active = is_active
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
db = context.db
# 验证用户存在
user = db.query(User).filter(User.id == self.user_id).first()
if not user:
raise NotFoundException("用户不存在", "user")
# 获取用户的Keys不包括独立Keys
api_keys = ApiKeyService.list_user_api_keys(
db=db, user_id=self.user_id, is_active=self.is_active
)
context.add_audit_metadata(
action="list_user_api_keys",
target_user_id=self.user_id,
total=len(api_keys),
)
return {
"api_keys": [
{
"id": key.id,
"name": key.name,
"key_display": key.get_display_key(),
"is_active": key.is_active,
"is_locked": key.is_locked,
"total_requests": key.total_requests,
"total_cost_usd": float(key.total_cost_usd or 0),
"rate_limit": key.rate_limit,
"expires_at": key.expires_at.isoformat() if key.expires_at else None,
"last_used_at": key.last_used_at.isoformat() if key.last_used_at else None,
"created_at": key.created_at.isoformat(),
}
for key in api_keys
],
"total": len(api_keys),
"user_email": user.email,
"username": user.username,
}
class AdminCreateUserKeyAdapter(AdminApiAdapter):
"""为用户创建API Key"""
def __init__(self, user_id: str):
self.user_id = user_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
payload = context.ensure_json_body()
try:
key_data = CreateApiKeyRequest.model_validate(payload)
except ValidationError as e:
errors = e.errors()
if errors:
raise InvalidRequestException(translate_pydantic_error(errors[0]))
raise InvalidRequestException("请求数据验证失败")
response, audit_meta, user_email = await run_in_threadpool(
_create_user_key_sync,
self.user_id,
key_data,
)
if user_email:
logger.info(
"管理员为用户创建API Key: 用户 {}, Key ID {}", user_email, audit_meta["key_id"]
)
context.add_audit_metadata(**audit_meta)
return response
class AdminDeleteUserKeyAdapter(AdminApiAdapter):
"""删除用户的API Key"""
def __init__(self, user_id: str, key_id: str):
self.user_id = user_id
self.key_id = key_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
response, audit_meta = await run_in_threadpool(
_delete_user_key_sync,
self.user_id,
self.key_id,
)
context.add_audit_metadata(**audit_meta)
return response
class AdminUpdateUserKeyAdapter(AdminApiAdapter):
"""更新用户的普通 API Key"""
def __init__(self, user_id: str, key_id: str):
self.user_id = user_id
self.key_id = key_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
payload = context.ensure_json_body()
try:
request = UpdateMyApiKeyRequest.model_validate(payload)
except ValidationError as e:
errors = e.errors()
if errors:
raise InvalidRequestException(translate_pydantic_error(errors[0]))
raise InvalidRequestException("请求数据验证失败")
response, audit_meta = await run_in_threadpool(
_update_user_key_sync,
self.user_id,
self.key_id,
request,
)
context.add_audit_metadata(**audit_meta)
return response
class AdminToggleUserKeyLockAdapter(AdminApiAdapter):
"""切换用户普通 API Key 的锁定状态"""
def __init__(self, user_id: str, key_id: str):
self.user_id = user_id
self.key_id = key_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
response, audit_meta = await run_in_threadpool(
_toggle_user_key_lock_sync,
self.user_id,
self.key_id,
)
context.add_audit_metadata(**audit_meta)
return response
class AdminGetUserKeyFullKeyAdapter(AdminApiAdapter):
"""获取用户普通 API Key 的完整密钥"""
def __init__(self, user_id: str, key_id: str):
self.user_id = user_id
self.key_id = key_id
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
from src.core.crypto import crypto_service
db = context.db
api_key = (
db.query(ApiKey)
.filter(
ApiKey.id == self.key_id,
ApiKey.user_id == self.user_id,
ApiKey.is_standalone == False, # 仅普通用户Key
)
.first()
)
if not api_key:
raise NotFoundException("API Key不存在或不属于该用户", "api_key")
if not api_key.key_encrypted:
raise InvalidRequestException("该密钥没有存储完整密钥信息")
try:
full_key = crypto_service.decrypt(api_key.key_encrypted)
except Exception as exc:
logger.error(
f"解密用户API密钥失败: 用户ID {self.user_id}, Key ID {self.key_id}, 错误: {exc}"
)
raise HTTPException(status_code=500, detail="解密密钥失败")
context.add_audit_metadata(
action="view_user_api_key_full_key",
target_user_id=self.user_id,
key_id=self.key_id,
)
return {"key": full_key}