Files
Aether/_deprecated_py_src/models/api.py

903 lines
30 KiB
Python
Raw Normal View History

2025-12-10 20:52:44 +08:00
"""
API端点请求/响应模型定义
"""
from __future__ import annotations
2025-12-10 20:52:44 +08:00
import re
from datetime import datetime
from typing import Any, Literal
2025-12-10 20:52:44 +08:00
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
2025-12-10 20:52:44 +08:00
from ..core.enums import UserRole
from ..core.validators import PasswordValidator
2025-12-10 20:52:44 +08:00
# ========== 认证相关 ==========
class LoginRequest(BaseModel):
"""登录请求"""
email: str = Field(..., min_length=1, max_length=255, description="邮箱/用户名")
password: str = Field(..., description="密码")
2026-01-02 16:17:24 +08:00
auth_type: Literal["local", "ldap"] = Field(default="local", description="认证类型")
2025-12-10 20:52:44 +08:00
@field_validator("password")
@classmethod
def validate_password(cls, v: Any) -> Any:
"""验证密码输入合法,保留原始内容。"""
valid, error_msg = PasswordValidator.validate_login_input(v)
if not valid:
raise ValueError(error_msg or "密码格式无效")
2025-12-10 20:52:44 +08:00
return v
@model_validator(mode="after")
def validate_login(self) -> Any:
"""根据认证类型校验并规范化登录标识"""
identifier = self.email.strip()
if not identifier:
raise ValueError("用户名/邮箱不能为空")
# 本地和 LDAP 登录都支持用户名或邮箱
# 如果是邮箱格式,转换为小写
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if re.match(email_pattern, identifier):
self.email = identifier.lower()
else:
self.email = identifier
return self
2025-12-10 20:52:44 +08:00
class LoginResponse(BaseModel):
"""登录响应"""
access_token: str
token_type: str = "bearer"
expires_in: int = 86400 # Token有效期默认24小时
user_id: str
email: str | None = None
2025-12-10 20:52:44 +08:00
username: str
role: str
class RefreshTokenResponse(BaseModel):
"""刷新令牌响应"""
access_token: str
token_type: str = "bearer"
expires_in: int = 86400 # Token有效期默认24小时
class RegisterRequest(BaseModel):
"""注册请求"""
email: str | None = Field(None, max_length=255, description="邮箱地址(可选)")
username: str = Field(..., min_length=3, max_length=50, description="用户名")
password: str = Field(..., description="密码")
2025-12-10 20:52:44 +08:00
@field_validator("email")
@classmethod
def validate_email(cls, v: Any) -> Any:
"""验证邮箱格式(如果提供)"""
if v is None:
return None
v = v.strip()
if not v:
return None
2025-12-10 20:52:44 +08:00
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if not re.match(email_pattern, v):
raise ValueError("邮箱格式无效")
return v.lower()
@classmethod
@field_validator("username")
def validate_username(cls, v: Any) -> Any:
2025-12-10 20:52:44 +08:00
"""验证用户名格式"""
v = v.strip()
if not v:
raise ValueError("用户名不能为空")
if not re.match(r"^[a-zA-Z0-9_.\-]+$", v):
raise ValueError("用户名只能包含字母、数字、下划线、连字符和点号")
2025-12-10 20:52:44 +08:00
return v
@field_validator("password")
@classmethod
def validate_password(cls, v: Any) -> Any:
"""基础校验(非空和算法限制),策略级别校验在服务层按系统配置执行。"""
valid, error_msg = PasswordValidator.validate_basic_input(v)
if not valid:
raise ValueError(error_msg or "密码格式无效")
2025-12-10 20:52:44 +08:00
return v
class RegisterResponse(BaseModel):
"""注册响应"""
user_id: str
email: str | None = None
2025-12-10 20:52:44 +08:00
username: str
message: str
class LogoutResponse(BaseModel):
"""登出响应"""
message: str
success: bool
class SendVerificationCodeRequest(BaseModel):
"""发送验证码请求"""
email: str = Field(..., min_length=3, max_length=255, description="邮箱地址")
@field_validator("email")
@classmethod
def validate_email(cls, v: Any) -> Any:
"""验证邮箱格式"""
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if not re.match(email_pattern, v):
raise ValueError("邮箱格式无效")
return v.lower()
class SendVerificationCodeResponse(BaseModel):
"""发送验证码响应"""
message: str
success: bool
expire_minutes: int | None = None
class VerifyEmailRequest(BaseModel):
"""验证邮箱请求"""
email: str = Field(..., min_length=3, max_length=255, description="邮箱地址")
code: str = Field(..., min_length=6, max_length=6, description="6位验证码")
@field_validator("email")
@classmethod
def validate_email(cls, v: Any) -> Any:
"""验证邮箱格式"""
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if not re.match(email_pattern, v):
raise ValueError("邮箱格式无效")
return v.lower()
@field_validator("code")
@classmethod
def validate_code(cls, v: Any) -> Any:
"""验证验证码格式"""
v = v.strip()
if not v.isdigit():
raise ValueError("验证码必须是6位数字")
if len(v) != 6:
raise ValueError("验证码必须是6位数字")
return v
class VerifyEmailResponse(BaseModel):
"""验证邮箱响应"""
message: str
success: bool
class VerificationStatusRequest(BaseModel):
"""验证状态查询请求"""
email: str = Field(..., min_length=3, max_length=255, description="邮箱地址")
@field_validator("email")
@classmethod
def validate_email(cls, v: Any) -> Any:
"""验证邮箱格式"""
v = v.strip().lower()
if not v:
raise ValueError("邮箱不能为空")
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if not re.match(email_pattern, v):
raise ValueError("邮箱格式无效")
return v
class VerificationStatusResponse(BaseModel):
"""验证状态响应"""
email: str
has_pending_code: bool = Field(description="是否有待验证的验证码")
is_verified: bool = Field(description="邮箱是否已验证")
cooldown_remaining: int | None = Field(None, description="发送冷却剩余秒数")
code_expires_in: int | None = Field(None, description="验证码剩余有效秒数")
class RegistrationSettingsResponse(BaseModel):
"""注册设置响应(公开接口返回)"""
enable_registration: bool
require_email_verification: bool
email_configured: bool = Field(description="是否配置了邮箱服务")
password_policy_level: str = Field(description="密码策略等级weak/medium/strong")
2025-12-10 20:52:44 +08:00
# ========== 用户管理 ==========
class CreateUserRequest(BaseModel):
"""创建用户请求"""
username: str = Field(..., min_length=3, max_length=50, description="用户名")
password: str = Field(..., description="密码")
email: str | None = Field(None, max_length=255, description="邮箱地址(可选)")
role: UserRole | None = Field(UserRole.USER, description="用户角色")
initial_gift_usd: float | None = Field(
default=None, description="初始赠款USDnull 表示使用系统默认初始赠款"
)
unlimited: bool = Field(default=False, description="是否无限制")
# 访问限制字段
allowed_providers: list[str] | None = Field(
default=None, description="允许使用的提供商ID列表null表示无限制"
)
allowed_api_formats: list[str] | None = Field(
default=None, description="允许使用的API格式列表null表示无限制"
)
allowed_models: list[str] | None = Field(
default=None, description="允许使用的模型名称列表null表示无限制"
)
rate_limit: int | None = Field(
default=None,
ge=0,
description="每分钟请求限制null 表示继承系统默认0 表示不限制",
)
2025-12-10 20:52:44 +08:00
@field_validator("initial_gift_usd", mode="before")
2025-12-10 20:52:44 +08:00
@classmethod
def validate_initial_gift_usd(cls, v: Any) -> Any:
"""验证初始赠款金额null 表示使用系统默认初始赠款。"""
2025-12-10 20:52:44 +08:00
if v is None:
return None
if isinstance(v, (int, float)) and v >= 0 and v <= 10000:
return float(v)
if isinstance(v, (int, float)):
raise ValueError("初始赠款必须在 0-10000 范围内")
2025-12-10 20:52:44 +08:00
return v
@field_validator("email")
@classmethod
def validate_email(cls, v: str | None) -> str | None:
"""验证邮箱格式(如果提供)"""
if v is None:
return None
2025-12-10 20:52:44 +08:00
v = v.strip()
if not v:
return None
2025-12-10 20:52:44 +08:00
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if not re.match(email_pattern, v):
raise ValueError("邮箱格式无效")
return v.lower()
@field_validator("username")
@classmethod
def validate_username(cls, v: Any) -> Any:
2025-12-10 20:52:44 +08:00
"""验证用户名格式"""
v = v.strip()
if not v:
raise ValueError("用户名不能为空")
if not re.match(r"^[a-zA-Z0-9_.\-]+$", v):
raise ValueError("用户名只能包含字母、数字、下划线、连字符和点号")
2025-12-10 20:52:44 +08:00
return v
@field_validator("allowed_api_formats")
@classmethod
def validate_allowed_api_formats(cls, v: list[str] | None) -> list[str] | None:
"""校验并规范化 allowed_api_formatsendpoint signature: family:kind"""
if v is None:
return None
from src.core.api_format import list_endpoint_definitions, resolve_endpoint_definition
from src.core.api_format.signature import normalize_signature_key
allowed = [d.signature_key for d in list_endpoint_definitions()]
out: list[str] = []
seen: set[str] = set()
for fmt in v:
if not fmt:
continue
norm = normalize_signature_key(fmt)
if resolve_endpoint_definition(norm) is None:
raise ValueError(f"allowed_api_formats 必须是以下之一: {allowed},当前值: {fmt}")
if norm in seen:
continue
seen.add(norm)
out.append(norm)
return out
2025-12-10 20:52:44 +08:00
@field_validator("password")
@classmethod
def validate_password(cls, v: Any) -> Any:
"""基础校验(非空和算法限制),策略级别校验在服务层按系统配置执行。"""
valid, error_msg = PasswordValidator.validate_basic_input(v)
if not valid:
raise ValueError(error_msg or "密码格式无效")
2025-12-10 20:52:44 +08:00
return v
class UpdateUserRequest(BaseModel):
"""更新用户请求"""
email: str | None = None
username: str | None = None
password: str | None = Field(None, description="新密码(留空保持不变)")
role: UserRole | None = None
unlimited: bool | None = None
allowed_providers: list[str] | None = None # 允许使用的提供商 ID 列表
allowed_api_formats: list[str] | None = None # 允许使用的 API 格式列表
allowed_models: list[str] | None = None # 允许使用的模型名称列表
rate_limit: int | None = Field(
default=None,
ge=0,
description="每分钟请求限制null 表示继承系统默认0 表示不限制",
)
is_active: bool | None = None
2025-12-10 20:52:44 +08:00
@field_validator("allowed_api_formats")
@classmethod
def validate_allowed_api_formats(cls, v: list[str] | None) -> list[str] | None:
# 与 CreateUserRequest 保持一致
return CreateUserRequest.validate_allowed_api_formats(v)
@field_validator("password")
@classmethod
def validate_password(cls, v: str | None) -> str | None:
if v is None:
return None
return CreateUserRequest.validate_password(v)
2025-12-10 20:52:44 +08:00
class CreateApiKeyRequest(BaseModel):
"""创建API密钥请求"""
name: str | None = None
allowed_providers: list[str] | None = None # 允许使用的提供商 ID 列表
allowed_api_formats: list[str] | None = None # 允许使用的 API 格式列表
allowed_models: list[str] | None = None # 允许使用的模型名称列表
rate_limit: int | None = Field(
None,
ge=0,
description="每分钟请求限制独立Key: null=继承系统默认0=不限制普通Key: 0=不限制",
)
expire_days: int | None = None # None = 永不过期,数字 = 多少天后过期
expires_at: str | None = None # ISO 日期字符串,如 "2025-12-31",优先于 expire_days
initial_balance_usd: float | None = Field(
2025-12-10 20:52:44 +08:00
None, description="初始余额USD仅用于独立KeyNone = 无限制"
)
unlimited_balance: bool | None = Field(
None, description="是否无限余额编辑独立Key时用于切换额度模式"
)
2025-12-10 20:52:44 +08:00
is_standalone: bool = Field(False, description="是否为独立余额Key给非注册用户使用")
auto_delete_on_expiry: bool = Field(
False, description="过期后是否自动删除True=物理删除False=仅禁用)"
)
@field_validator("allowed_api_formats")
@classmethod
def validate_allowed_api_formats(cls, v: list[str] | None) -> list[str] | None:
# 与 CreateUserRequest 保持一致
return CreateUserRequest.validate_allowed_api_formats(v)
2025-12-10 20:52:44 +08:00
class UserResponse(BaseModel):
"""用户响应"""
id: str
email: str | None = None
2025-12-10 20:52:44 +08:00
username: str
role: UserRole
allowed_providers: list[str] | None = None # 允许使用的提供商 ID 列表
allowed_api_formats: list[str] | None = None # 允许使用的 API 格式列表
allowed_models: list[str] | None = None # 允许使用的模型名称列表
rate_limit: int | None = None
unlimited: bool = False
2025-12-10 20:52:44 +08:00
is_active: bool
created_at: datetime
updated_at: datetime
last_login_at: datetime | None
2025-12-10 20:52:44 +08:00
class ApiKeyResponse(BaseModel):
"""API密钥响应"""
id: str
user_id: str
key: str | None = None # 仅在创建时返回完整密钥
key_display: str | None = None # 脱敏后的密钥显示
name: str | None
2025-12-10 20:52:44 +08:00
total_requests: int
total_tokens: int
total_cost_usd: float
allowed_providers: list[str] | None
allowed_models: list[str] | None
rate_limit: int | None
2025-12-10 20:52:44 +08:00
is_active: bool
expires_at: datetime | None = None
2025-12-10 20:52:44 +08:00
is_standalone: bool = False
force_capabilities: dict[str, bool] | None = None # 强制开启的能力
2025-12-10 20:52:44 +08:00
created_at: datetime
last_used_at: datetime | None
2025-12-10 20:52:44 +08:00
# ========== 提供商管理 ==========
class ProviderCreate(BaseModel):
"""创建提供商请求
架构说明
2025-12-10 20:52:44 +08:00
- Provider 仅包含提供商的元数据和计费配置
- API格式URL认证等配置应在 ProviderEndpoint 中设置
- API密钥应在 ProviderAPIKey 中设置
"""
name: str = Field(..., min_length=1, max_length=100, description="提供商名称(唯一)")
description: str | None = Field(None, description="提供商描述")
website: str | None = Field(None, max_length=500, description="主站网站")
2025-12-10 20:52:44 +08:00
# Provider 级别的配置
rate_limit: int | None = Field(None, description="每分钟请求限制")
concurrent_limit: int | None = Field(None, description="并发请求限制")
config: dict | None = Field(None, description="额外配置")
2025-12-10 20:52:44 +08:00
is_active: bool = Field(False, description="是否启用默认false需要配置API密钥后才能启用")
# 超时配置(秒),为空时使用全局配置
stream_first_byte_timeout: float | None = Field(
None, ge=1, le=300, description="流式请求首字节超时(秒)"
)
request_timeout: float | None = Field(
None, ge=1, le=600, description="非流式请求整体超时(秒)"
)
2025-12-10 20:52:44 +08:00
class ProviderUpdate(BaseModel):
"""更新提供商请求"""
name: str | None = Field(None, min_length=1, max_length=100)
description: str | None = None
website: str | None = Field(None, max_length=500)
api_format: str | None = None
base_url: str | None = None
headers: dict | None = None
max_retries: int | None = Field(None, ge=0, le=10)
priority: int | None = None
weight: float | None = Field(None, gt=0)
rate_limit: int | None = None
concurrent_limit: int | None = None
config: dict | None = None
is_active: bool | None = None
2025-12-10 20:52:44 +08:00
# 超时配置(秒),为空时使用全局配置
stream_first_byte_timeout: float | None = Field(
None, ge=1, le=300, description="流式请求首字节超时(秒)"
)
request_timeout: float | None = Field(
None, ge=1, le=600, description="非流式请求整体超时(秒)"
)
2025-12-10 20:52:44 +08:00
class ProviderResponse(BaseModel):
"""提供商响应"""
id: str
name: str
description: str | None
website: str | None
2025-12-10 20:52:44 +08:00
api_format: str
base_url: str
headers: dict | None
2025-12-10 20:52:44 +08:00
max_retries: int
priority: int
weight: float
rate_limit: int | None
concurrent_limit: int | None
config: dict | None
2025-12-10 20:52:44 +08:00
is_active: bool
created_at: datetime
updated_at: datetime
models_count: int = 0
active_models_count: int = 0
api_keys_count: int = 0
# 超时配置
stream_first_byte_timeout: float | None = None
request_timeout: float | None = None
model_config = ConfigDict(from_attributes=True)
2025-12-10 20:52:44 +08:00
# ========== 模型管理 ==========
class ModelCreate(BaseModel):
"""创建模型请求 - 价格和能力字段可选,为空时使用 GlobalModel 默认值"""
provider_model_name: str = Field(
..., min_length=1, max_length=200, description="Provider 侧的主模型名称"
)
provider_model_mappings: list[dict] | None = Field(
None,
description="模型名称映射列表,格式: [{'name': 'alias1', 'priority': 1}, ...]",
2025-12-10 20:52:44 +08:00
)
global_model_id: str = Field(..., description="关联的 GlobalModel ID必填")
# 按次计费配置 - 可选,为空时使用 GlobalModel 默认值
price_per_request: float | None = Field(
2025-12-10 20:52:44 +08:00
None, ge=0, description="每次请求固定费用,为空使用默认值"
)
# 阶梯计费配置 - 可选,为空时使用 GlobalModel 默认值
tiered_pricing: dict | None = Field(
2025-12-10 20:52:44 +08:00
None, description="阶梯计费配置,为空使用 GlobalModel 默认值"
)
# 能力配置 - 可选,为空时使用 GlobalModel 默认值
supports_vision: bool | None = Field(None, description="是否支持图像输入,为空使用默认值")
supports_function_calling: bool | None = Field(
2025-12-10 20:52:44 +08:00
None, description="是否支持函数调用,为空使用默认值"
)
supports_streaming: bool | None = Field(None, description="是否支持流式输出,为空使用默认值")
supports_extended_thinking: bool | None = Field(
2025-12-10 20:52:44 +08:00
None, description="是否支持扩展思考,为空使用默认值"
)
is_active: bool = Field(True, description="是否启用")
config: dict | None = Field(None, description="额外配置")
2025-12-10 20:52:44 +08:00
class ModelUpdate(BaseModel):
"""更新模型请求"""
provider_model_name: str | None = Field(None, min_length=1, max_length=200)
provider_model_mappings: list[dict] | None = Field(
None,
description="模型名称映射列表,格式: [{'name': 'alias1', 'priority': 1}, ...]",
)
global_model_id: str | None = None
2025-12-10 20:52:44 +08:00
# 按次计费配置
price_per_request: float | None = Field(None, ge=0, description="每次请求固定费用")
2025-12-10 20:52:44 +08:00
# 阶梯计费配置
tiered_pricing: dict | None = Field(None, description="阶梯计费配置")
supports_vision: bool | None = None
supports_function_calling: bool | None = None
supports_streaming: bool | None = None
supports_extended_thinking: bool | None = None
is_active: bool | None = None
is_available: bool | None = None
config: dict | None = None
2025-12-10 20:52:44 +08:00
class ModelResponse(BaseModel):
"""模型响应 - 包含 Model 配置和关联的 GlobalModel 信息
注意价格和能力字段返回的是有效值优先使用 Model 配置否则使用 GlobalModel 默认值
"""
id: str
provider_id: str
global_model_id: str
2025-12-10 20:52:44 +08:00
provider_model_name: str
provider_model_mappings: list[dict] | None = None
2025-12-10 20:52:44 +08:00
# 按次计费配置
price_per_request: float | None = None
2025-12-10 20:52:44 +08:00
# 阶梯计费配置
tiered_pricing: dict | None = None
2025-12-10 20:52:44 +08:00
# Provider 能力配置 - 可选,为空表示使用 GlobalModel 默认值
supports_vision: bool | None
supports_function_calling: bool | None
supports_streaming: bool | None
supports_extended_thinking: bool | None
supports_image_generation: bool | None
2025-12-10 20:52:44 +08:00
# 有效值(合并 Model 配置和 GlobalModel 默认值后的结果)
effective_tiered_pricing: dict | None = None
effective_input_price: float | None = None
effective_output_price: float | None = None
effective_price_per_request: float | None = None
effective_supports_vision: bool | None = None
effective_supports_function_calling: bool | None = None
effective_supports_streaming: bool | None = None
effective_supports_extended_thinking: bool | None = None
effective_supports_image_generation: bool | None = None
2025-12-10 20:52:44 +08:00
# 状态
is_active: bool
is_available: bool
# 时间戳
created_at: datetime
updated_at: datetime
# 关联的 GlobalModel 信息
global_model_name: str | None = None
global_model_display_name: str | None = None
2025-12-10 20:52:44 +08:00
# 有效配置(合并 Model 和 GlobalModel 的 config
effective_config: dict | None = None
model_config = ConfigDict(from_attributes=True)
2025-12-10 20:52:44 +08:00
class ModelDetailResponse(BaseModel):
"""模型详细响应 - 包含所有字段(用于需要完整信息的场景)"""
id: str
provider_id: str
name: str
display_name: str
description: str | None
icon_url: str | None
tags: list[str] | None
2025-12-10 20:52:44 +08:00
input_price_per_1m: float
output_price_per_1m: float
cache_creation_price_per_1m: float | None
cache_read_price_per_1m: float | None
2025-12-10 20:52:44 +08:00
supports_vision: bool
supports_function_calling: bool
supports_streaming: bool
is_active: bool
is_available: bool
config: dict | None
2025-12-10 20:52:44 +08:00
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
2025-12-10 20:52:44 +08:00
# ========== 系统设置 ==========
class SystemSettingsRequest(BaseModel):
"""系统设置请求"""
default_provider: str | None = None
default_model: str | None = None
enable_usage_tracking: bool | None = None
password_policy_level: Literal["weak", "medium", "strong"] | None = None
2025-12-10 20:52:44 +08:00
class SystemSettingsResponse(BaseModel):
"""系统设置响应"""
default_provider: str | None
default_model: str | None
2025-12-10 20:52:44 +08:00
enable_usage_tracking: bool
password_policy_level: str
2025-12-10 20:52:44 +08:00
# ========== 使用统计 ==========
class UsageStatsResponse(BaseModel):
"""使用统计响应"""
total_requests: int
total_tokens: int
total_cost_usd: float
daily_requests: int
daily_tokens: int
daily_cost_usd: float
model_usage: dict[str, dict[str, Any]]
provider_usage: dict[str, dict[str, Any]]
2025-12-10 20:52:44 +08:00
# ========== 公开API响应模型 ==========
class PublicProviderResponse(BaseModel):
"""公开的提供商信息响应"""
id: str
name: str
description: str | None
website: str | None
2025-12-10 20:52:44 +08:00
is_active: bool
provider_priority: int # 提供商优先级(数字越小越优先)
# 统计信息
models_count: int
active_models_count: int
endpoints_count: int # 端点总数
active_endpoints_count: int # 活跃端点数
class PublicModelResponse(BaseModel):
"""公开的模型信息响应"""
id: str
provider_id: str
provider_name: str
name: str
display_name: str
description: str | None = None
tags: list[str] | None = None
icon_url: str | None = None
2025-12-10 20:52:44 +08:00
# 价格信息
input_price_per_1m: float | None = None
output_price_per_1m: float | None = None
cache_creation_price_per_1m: float | None = None
cache_read_price_per_1m: float | None = None
2025-12-10 20:52:44 +08:00
# 功能支持
supports_vision: bool | None = None
supports_function_calling: bool | None = None
supports_streaming: bool | None = None
2025-12-10 20:52:44 +08:00
is_active: bool = True
class ProviderStatsResponse(BaseModel):
"""提供商统计信息响应"""
total_providers: int
active_providers: int
total_models: int
active_models: int
supported_formats: list[str]
2025-12-10 20:52:44 +08:00
class PublicGlobalModelResponse(BaseModel):
"""公开的 GlobalModel 信息响应(用户可见)"""
id: str
name: str
display_name: str | None = None
2025-12-10 20:52:44 +08:00
is_active: bool = True
# 按次计费配置
default_price_per_request: float | None = None
2025-12-10 20:52:44 +08:00
# 阶梯计费配置
default_tiered_pricing: dict | None = None
2025-12-10 20:52:44 +08:00
# Key 能力配置
supported_capabilities: list[str] | None = None
# 模型配置JSON
config: dict | None = None
# 调用次数
usage_count: int = 0
2025-12-10 20:52:44 +08:00
class PublicGlobalModelListResponse(BaseModel):
"""公开的 GlobalModel 列表响应"""
models: list[PublicGlobalModelResponse]
2025-12-10 20:52:44 +08:00
total: int
# ========== 个人中心相关模型 ==========
class UpdateProfileRequest(BaseModel):
"""更新个人信息请求"""
email: str | None = None
username: str | None = None
2025-12-10 20:52:44 +08:00
class UpdatePreferencesRequest(BaseModel):
"""更新偏好设置请求"""
avatar_url: str | None = None
bio: str | None = None
default_provider_id: int | None = None
theme: str | None = None
language: str | None = None
timezone: str | None = None
email_notifications: bool | None = None
usage_alerts: bool | None = None
announcement_notifications: bool | None = None
2025-12-10 20:52:44 +08:00
class ChangePasswordRequest(BaseModel):
"""修改密码请求"""
old_password: str | None = None # 可选:首次设置密码时不需要
new_password: str = Field(..., description="新密码")
@field_validator("new_password")
@classmethod
def validate_new_password(cls, v: Any) -> Any:
"""基础校验(非空和算法限制),策略级别校验在服务层按系统配置执行。"""
valid, error_msg = PasswordValidator.validate_basic_input(v)
if not valid:
raise ValueError(error_msg or "密码格式无效")
return v
class UserSessionResponse(BaseModel):
"""用户会话响应"""
id: str
device_label: str
device_type: str
browser_name: str | None = None
browser_version: str | None = None
os_name: str | None = None
os_version: str | None = None
device_model: str | None = None
ip_address: str | None = None
last_seen_at: str | None = None
created_at: str
is_current: bool = False
revoked_at: str | None = None
revoke_reason: str | None = None
@classmethod
def from_db(cls, session: Any, *, current_session_id: str | None = None) -> dict[str, Any]:
return cls(
id=session.id,
device_label=session.device_label or "未知设备",
device_type=session.device_type or "unknown",
browser_name=session.browser_name,
browser_version=session.browser_version,
os_name=session.os_name,
os_version=session.os_version,
device_model=session.device_model,
ip_address=session.ip_address,
last_seen_at=session.last_seen_at.isoformat() if session.last_seen_at else None,
created_at=session.created_at.isoformat(),
is_current=bool(current_session_id and session.id == current_session_id),
revoked_at=session.revoked_at.isoformat() if session.revoked_at else None,
revoke_reason=session.revoke_reason,
).model_dump()
class UpdateSessionLabelRequest(BaseModel):
"""更新会话显示名称"""
device_label: str = Field(..., min_length=1, max_length=120, description="设备名称")
@field_validator("device_label")
@classmethod
def validate_device_label(cls, v: Any) -> Any:
normalized = str(v).strip()
if not normalized:
raise ValueError("设备名称不能为空")
return normalized
2025-12-10 20:52:44 +08:00
class CreateMyApiKeyRequest(BaseModel):
"""创建我的API密钥请求"""
name: str
rate_limit: int = Field(0, ge=0, description="该 Key 的每分钟请求限制0 表示不限制")
class UpdateMyApiKeyRequest(BaseModel):
"""更新我的 API 密钥请求"""
name: str | None = None
rate_limit: int | None = Field(
None,
ge=0,
description="该 Key 的每分钟请求限制0 表示不限制null 表示不修改",
)
2025-12-10 20:52:44 +08:00
class ProviderConfig(BaseModel):
"""提供商配置"""
provider_id: str = Field(..., description="提供商ID")
priority: int = Field(100, description="优先级(越高越优先)")
weight: float = Field(1.0, description="负载均衡权重")
enabled: bool = Field(True, description="是否启用")
class UpdateApiKeyProvidersRequest(BaseModel):
"""更新API密钥可用提供商请求"""
allowed_providers: list[ProviderConfig] | None = None # 提供商配置列表
2025-12-10 20:52:44 +08:00
# ========== 公告相关模型 ==========
class CreateAnnouncementRequest(BaseModel):
"""创建公告请求"""
title: str
content: str # 支持Markdown
type: str = "info" # info, warning, maintenance, important
priority: int = 0
is_pinned: bool = False
start_time: datetime | None = None
end_time: datetime | None = None
2025-12-10 20:52:44 +08:00
class UpdateAnnouncementRequest(BaseModel):
"""更新公告请求"""
title: str | None = None
content: str | None = None
type: str | None = None
priority: int | None = None
is_active: bool | None = None
is_pinned: bool | None = None
start_time: datetime | None = None
end_time: datetime | None = None