2025-12-10 20:52:44 +08:00
|
|
|
|
"""用户管理 API 端点。"""
|
|
|
|
|
|
|
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
|
|
|
|
|
|
from pydantic import ValidationError
|
|
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
|
|
from src.api.base.admin_adapter import AdminApiAdapter
|
|
|
|
|
|
from src.api.base.pipeline import ApiRequestPipeline
|
|
|
|
|
|
from src.core.exceptions import InvalidRequestException, NotFoundException, translate_pydantic_error
|
|
|
|
|
|
from src.core.logger import logger
|
|
|
|
|
|
from src.database import get_db
|
|
|
|
|
|
from src.models.admin_requests import UpdateUserRequest
|
|
|
|
|
|
from src.models.api import CreateApiKeyRequest, CreateUserRequest
|
|
|
|
|
|
from src.models.database import ApiKey, User, UserRole
|
|
|
|
|
|
from src.services.user.apikey import ApiKeyService
|
|
|
|
|
|
from src.services.user.service import UserService
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/api/admin/users", tags=["Admin - Users"])
|
|
|
|
|
|
pipeline = ApiRequestPipeline()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 管理员端点
|
|
|
|
|
|
@router.post("")
|
|
|
|
|
|
async def create_user_endpoint(request: Request, db: Session = Depends(get_db)):
|
|
|
|
|
|
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),
|
|
|
|
|
|
limit: int = Query(100, ge=1, le=1000),
|
|
|
|
|
|
role: Optional[str] = None,
|
|
|
|
|
|
is_active: Optional[bool] = None,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
):
|
|
|
|
|
|
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)): # UUID
|
|
|
|
|
|
adapter = AdminGetUserAdapter(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),
|
|
|
|
|
|
):
|
|
|
|
|
|
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)): # UUID
|
|
|
|
|
|
adapter = AdminDeleteUserAdapter(user_id=user_id)
|
|
|
|
|
|
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.patch("/{user_id}/quota")
|
|
|
|
|
|
async def reset_user_quota(user_id: str, request: Request, db: Session = Depends(get_db)):
|
|
|
|
|
|
"""Reset user quota (set used_usd to 0)"""
|
|
|
|
|
|
adapter = AdminResetUserQuotaAdapter(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: Optional[bool] = None,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""获取用户的所有API Keys(不包括独立Keys)"""
|
|
|
|
|
|
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),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""为用户创建API Key"""
|
|
|
|
|
|
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),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""删除用户的API Key"""
|
|
|
|
|
|
adapter = AdminDeleteUserKeyAdapter(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): # type: ignore[override]
|
|
|
|
|
|
db = context.db
|
|
|
|
|
|
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:
|
|
|
|
|
|
user = UserService.create_user(
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
email=request.email,
|
|
|
|
|
|
username=request.username,
|
|
|
|
|
|
password=request.password,
|
|
|
|
|
|
role=role,
|
|
|
|
|
|
quota_usd=request.quota_usd,
|
|
|
|
|
|
)
|
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
|
raise InvalidRequestException(str(exc))
|
|
|
|
|
|
|
|
|
|
|
|
context.add_audit_metadata(
|
|
|
|
|
|
action="create_user",
|
|
|
|
|
|
target_user_id=user.id,
|
|
|
|
|
|
target_email=user.email,
|
|
|
|
|
|
target_username=user.username,
|
|
|
|
|
|
target_role=user.role.value,
|
|
|
|
|
|
quota_usd=user.quota_usd,
|
|
|
|
|
|
is_active=user.is_active,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": user.id,
|
|
|
|
|
|
"email": user.email,
|
|
|
|
|
|
"username": user.username,
|
|
|
|
|
|
"role": user.role.value,
|
|
|
|
|
|
"allowed_providers": user.allowed_providers,
|
|
|
|
|
|
"allowed_endpoints": user.allowed_endpoints,
|
|
|
|
|
|
"allowed_models": user.allowed_models,
|
|
|
|
|
|
"quota_usd": user.quota_usd,
|
|
|
|
|
|
"used_usd": user.used_usd,
|
|
|
|
|
|
"total_usd": getattr(user, "total_usd", 0),
|
|
|
|
|
|
"is_active": user.is_active,
|
|
|
|
|
|
"created_at": user.created_at.isoformat(),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminListUsersAdapter(AdminApiAdapter):
|
|
|
|
|
|
def __init__(self, skip: int, limit: int, role: Optional[str], is_active: Optional[bool]):
|
|
|
|
|
|
self.skip = skip
|
|
|
|
|
|
self.limit = limit
|
|
|
|
|
|
self.role = role
|
|
|
|
|
|
self.is_active = is_active
|
|
|
|
|
|
|
|
|
|
|
|
async def handle(self, context): # type: ignore[override]
|
|
|
|
|
|
db = context.db
|
|
|
|
|
|
role_enum = UserRole[self.role.upper()] if self.role else None
|
|
|
|
|
|
users = UserService.list_users(db, self.skip, self.limit, role_enum, self.is_active)
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": u.id,
|
|
|
|
|
|
"email": u.email,
|
|
|
|
|
|
"username": u.username,
|
|
|
|
|
|
"role": u.role.value,
|
|
|
|
|
|
"quota_usd": u.quota_usd,
|
|
|
|
|
|
"used_usd": u.used_usd,
|
|
|
|
|
|
"total_usd": getattr(u, "total_usd", 0),
|
|
|
|
|
|
"is_active": u.is_active,
|
|
|
|
|
|
"created_at": u.created_at.isoformat(),
|
|
|
|
|
|
}
|
|
|
|
|
|
for u in users
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminGetUserAdapter(AdminApiAdapter):
|
|
|
|
|
|
def __init__(self, user_id: str):
|
|
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
|
|
|
|
|
|
async def handle(self, context): # 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 {
|
|
|
|
|
|
"id": user.id,
|
|
|
|
|
|
"email": user.email,
|
|
|
|
|
|
"username": user.username,
|
|
|
|
|
|
"role": user.role.value,
|
|
|
|
|
|
"allowed_providers": user.allowed_providers,
|
|
|
|
|
|
"allowed_endpoints": user.allowed_endpoints,
|
|
|
|
|
|
"allowed_models": user.allowed_models,
|
|
|
|
|
|
"quota_usd": user.quota_usd,
|
|
|
|
|
|
"used_usd": user.used_usd,
|
|
|
|
|
|
"total_usd": getattr(user, "total_usd", 0),
|
|
|
|
|
|
"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,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminUpdateUserAdapter(AdminApiAdapter):
|
|
|
|
|
|
def __init__(self, user_id: str):
|
|
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
|
|
|
|
|
|
async def handle(self, context): # type: ignore[override]
|
|
|
|
|
|
db = context.db
|
|
|
|
|
|
existing_user = UserService.get_user(db, self.user_id)
|
|
|
|
|
|
if not existing_user:
|
|
|
|
|
|
raise NotFoundException("用户不存在", "user")
|
|
|
|
|
|
|
|
|
|
|
|
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("请求数据验证失败")
|
|
|
|
|
|
|
|
|
|
|
|
update_data = request.model_dump(exclude_unset=True)
|
2026-01-04 22:42:58 +08:00
|
|
|
|
old_role = existing_user.role
|
2025-12-10 20:52:44 +08:00
|
|
|
|
if "role" in update_data and update_data["role"]:
|
|
|
|
|
|
if hasattr(update_data["role"], "value"):
|
|
|
|
|
|
update_data["role"] = update_data["role"]
|
|
|
|
|
|
else:
|
|
|
|
|
|
update_data["role"] = UserRole[update_data["role"].upper()]
|
|
|
|
|
|
|
|
|
|
|
|
user = UserService.update_user(db, self.user_id, **update_data)
|
|
|
|
|
|
if not user:
|
|
|
|
|
|
raise NotFoundException("用户不存在", "user")
|
|
|
|
|
|
|
2026-01-04 22:42:58 +08:00
|
|
|
|
# 角色变更时清除热力图缓存(影响 include_actual_cost 权限)
|
|
|
|
|
|
if "role" in update_data and update_data["role"] != old_role:
|
|
|
|
|
|
from src.services.usage.service import UsageService
|
|
|
|
|
|
|
|
|
|
|
|
await UsageService.clear_user_heatmap_cache(self.user_id)
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
changed_fields = list(update_data.keys())
|
|
|
|
|
|
context.add_audit_metadata(
|
|
|
|
|
|
action="update_user",
|
|
|
|
|
|
target_user_id=user.id,
|
|
|
|
|
|
updated_fields=changed_fields,
|
|
|
|
|
|
role_before=existing_user.role.value if existing_user.role else None,
|
|
|
|
|
|
role_after=user.role.value,
|
|
|
|
|
|
quota_usd=user.quota_usd,
|
|
|
|
|
|
is_active=user.is_active,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": user.id,
|
|
|
|
|
|
"email": user.email,
|
|
|
|
|
|
"username": user.username,
|
|
|
|
|
|
"role": user.role.value,
|
|
|
|
|
|
"allowed_providers": user.allowed_providers,
|
|
|
|
|
|
"allowed_endpoints": user.allowed_endpoints,
|
|
|
|
|
|
"allowed_models": user.allowed_models,
|
|
|
|
|
|
"quota_usd": user.quota_usd,
|
|
|
|
|
|
"used_usd": user.used_usd,
|
|
|
|
|
|
"total_usd": getattr(user, "total_usd", 0),
|
|
|
|
|
|
"is_active": user.is_active,
|
|
|
|
|
|
"created_at": user.created_at.isoformat(),
|
|
|
|
|
|
"updated_at": user.updated_at.isoformat() if user.updated_at else None,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminDeleteUserAdapter(AdminApiAdapter):
|
|
|
|
|
|
def __init__(self, user_id: str):
|
|
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
|
|
|
|
|
|
async def handle(self, context): # type: ignore[override]
|
|
|
|
|
|
db = context.db
|
|
|
|
|
|
user = UserService.get_user(db, self.user_id)
|
|
|
|
|
|
if not user:
|
|
|
|
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
|
|
|
|
|
|
|
|
|
|
|
if user.role == UserRole.ADMIN:
|
|
|
|
|
|
admin_count = db.query(User).filter(User.role == UserRole.ADMIN).count()
|
|
|
|
|
|
if admin_count <= 1:
|
|
|
|
|
|
raise InvalidRequestException("不能删除最后一个管理员账户")
|
|
|
|
|
|
|
|
|
|
|
|
success = UserService.delete_user(db, self.user_id)
|
|
|
|
|
|
if not success:
|
|
|
|
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
|
|
|
|
|
|
|
|
|
|
|
context.add_audit_metadata(
|
|
|
|
|
|
action="delete_user",
|
|
|
|
|
|
target_user_id=user.id,
|
|
|
|
|
|
target_email=user.email,
|
|
|
|
|
|
target_role=user.role.value,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"message": "用户删除成功"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminResetUserQuotaAdapter(AdminApiAdapter):
|
|
|
|
|
|
def __init__(self, user_id: str):
|
|
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
|
|
|
|
|
|
async def handle(self, context): # type: ignore[override]
|
|
|
|
|
|
db = context.db
|
|
|
|
|
|
user = UserService.get_user(db, self.user_id)
|
|
|
|
|
|
if not user:
|
|
|
|
|
|
raise NotFoundException("用户不存在", "user")
|
|
|
|
|
|
|
|
|
|
|
|
user.used_usd = 0.0
|
|
|
|
|
|
user.total_usd = getattr(user, "total_usd", 0)
|
|
|
|
|
|
user.updated_at = datetime.now(timezone.utc)
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
context.add_audit_metadata(
|
|
|
|
|
|
action="reset_user_quota",
|
|
|
|
|
|
target_user_id=user.id,
|
|
|
|
|
|
quota_usd=user.quota_usd,
|
|
|
|
|
|
used_usd=user.used_usd,
|
|
|
|
|
|
total_usd=user.total_usd,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"message": "配额已重置",
|
|
|
|
|
|
"user_id": user.id,
|
|
|
|
|
|
"quota_usd": user.quota_usd,
|
|
|
|
|
|
"used_usd": user.used_usd,
|
|
|
|
|
|
"total_usd": user.total_usd,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdminGetUserKeysAdapter(AdminApiAdapter):
|
|
|
|
|
|
"""获取用户的API Keys"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, user_id: str, is_active: Optional[bool]):
|
|
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
self.is_active = is_active
|
|
|
|
|
|
|
|
|
|
|
|
async def handle(self, context): # 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,
|
|
|
|
|
|
"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): # type: ignore[override]
|
|
|
|
|
|
db = context.db
|
|
|
|
|
|
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("请求数据验证失败")
|
|
|
|
|
|
|
|
|
|
|
|
# 验证用户存在
|
|
|
|
|
|
user = db.query(User).filter(User.id == self.user_id).first()
|
|
|
|
|
|
if not user:
|
|
|
|
|
|
raise NotFoundException("用户不存在", "user")
|
|
|
|
|
|
|
|
|
|
|
|
# 为用户创建Key(不是独立Key)
|
|
|
|
|
|
api_key, plain_key = ApiKeyService.create_api_key(
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
user_id=self.user_id,
|
|
|
|
|
|
name=key_data.name,
|
|
|
|
|
|
allowed_providers=key_data.allowed_providers,
|
|
|
|
|
|
allowed_models=key_data.allowed_models,
|
2026-01-05 02:16:16 +08:00
|
|
|
|
rate_limit=key_data.rate_limit, # None = 无限制
|
2025-12-10 20:52:44 +08:00
|
|
|
|
expire_days=key_data.expire_days,
|
|
|
|
|
|
initial_balance_usd=None, # 普通Key不设置余额限制
|
|
|
|
|
|
is_standalone=False, # 不是独立Key
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"管理员为用户创建API Key: 用户 {user.email}, Key ID {api_key.id}")
|
|
|
|
|
|
|
|
|
|
|
|
context.add_audit_metadata(
|
|
|
|
|
|
action="create_user_api_key",
|
|
|
|
|
|
target_user_id=self.user_id,
|
|
|
|
|
|
key_id=api_key.id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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创建成功,请妥善保存完整密钥",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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): # type: ignore[override]
|
|
|
|
|
|
db = context.db
|
|
|
|
|
|
|
|
|
|
|
|
# 验证Key存在且属于该用户
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
|
|
db.delete(api_key)
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"管理员删除用户API Key: 用户ID {self.user_id}, Key ID {self.key_id}")
|
|
|
|
|
|
|
|
|
|
|
|
context.add_audit_metadata(
|
|
|
|
|
|
action="delete_user_api_key",
|
|
|
|
|
|
target_user_id=self.user_id,
|
|
|
|
|
|
key_id=self.key_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"message": "API Key已删除"}
|