""" 后台管理系统数据传输对象定义 定义管理员认证、用户管理、模型管理相关API请求和响应的数据结构 """ from datetime import datetime, date from decimal import Decimal from typing import Optional, List from pydantic import BaseModel, ConfigDict, Field # ==================== 认证相关 Schema ==================== class AdminLoginRequest(BaseModel): """管理员登录请求""" username: str = Field(..., min_length=1, max_length=50, description="用户名") password: str = Field(..., min_length=1, max_length=100, description="密码") class AdminLoginResponse(BaseModel): """管理员登录响应""" token: str = Field(..., description="JWT Token") admin_id: int = Field(..., description="管理员ID") username: str = Field(..., description="用户名") nickname: str = Field(..., description="昵称") expires_at: datetime = Field(..., description="Token过期时间") class AdminInfo(BaseModel): """管理员信息""" id: int username: str nickname: str status: str created_at: datetime model_config = ConfigDict(from_attributes=True) # ==================== 用户管理相关 Schema ==================== class UserListParams(BaseModel): """用户列表查询参数""" keyword: Optional[str] = Field(None, description="搜索关键词(用户ID/用户名/手机号/邮箱)") status: Optional[str] = Field(None, description="账户状态:active/disabled") register_start: Optional[date] = Field(None, description="注册开始日期") register_end: Optional[date] = Field(None, description="注册结束日期") sort_by: Optional[str] = Field("created_at", description="排序字段:created_at") sort_order: Optional[str] = Field("desc", description="排序方向:asc/desc") page: int = Field(1, ge=1, description="页码") size: int = Field(20, ge=1, le=100, description="每页数量") class UserListItem(BaseModel): """用户列表项""" id: str username: Optional[str] = None nickname: str phone: Optional[str] = None registration_date: Optional[date] = None status: str = "active" is_verified: Optional[str] = None real_name: Optional[str] = None model_config = ConfigDict(from_attributes=True) class UserDetailResponse(BaseModel): """用户详情响应""" id: str username: Optional[str] = None nickname: str phone: Optional[str] = None email: Optional[str] = None avatar: Optional[str] = None registration_date: Optional[date] = None status: str = "active" model_config = ConfigDict(from_attributes=True) class UserUsageStats(BaseModel): """用户使用统计""" conversation_count: int = 0 token_consumed: int = 0 image_count: int = 0 audio_count: int = 0 audio_duration: int = 0 video_count: int = 0 api_call_count: int = 0 api_input_tokens: int = 0 api_output_tokens: int = 0 class UserStatusRequest(BaseModel): """用户状态变更请求""" status: str = Field(..., pattern="^(active|disabled)$", description="目标状态:active/disabled") class ResetPasswordResponse(BaseModel): """密码重置响应""" user_id: str new_password: str # ==================== 模型管理相关 Schema ==================== class ModelListParams(BaseModel): """模型列表查询参数""" keyword: Optional[str] = Field(None, description="搜索关键词(模型标识/显示名称/供应商)") category: Optional[int] = Field(None, description="模型分类") supplier: Optional[str] = Field(None, description="供应商") is_show_enabled: Optional[bool] = Field(None, description="是否展示") is_api_enabled: Optional[bool] = Field(None, description="是否启用API") page: int = Field(1, ge=1, description="页码") size: int = Field(20, ge=1, le=100, description="每页数量") class ModelListItem(BaseModel): """模型列表项""" id: int model_code: str display_name: Optional[str] = None img: str = "" category: int categories: List[int] = [] supplier: str description: Optional[str] = None # 描述摘要(前80字) # 价格摘要:取第一条 is_active 价格记录 price_label: Optional[str] = None # 如 "input<=32k" input_price_original: Optional[Decimal] = None output_price_original: Optional[Decimal] = None input_price_discounted: Optional[Decimal] = None output_price_discounted: Optional[Decimal] = None discount_rate: Optional[Decimal] = None discount_label: Optional[str] = None price_unit: Optional[str] = None is_show_enabled: bool = True is_api_enabled: bool = False is_featured: bool = False is_search: bool = False is_thinking: bool = False model_config = ConfigDict(from_attributes=True) class PriceTierItem(BaseModel): """价格阶梯项""" tier_min: int tier_max: Optional[int] = None input_price: Decimal output_price: Decimal class ModelPriceDetail(BaseModel): """模型价格详情""" id: Optional[int] = None pricing_mode: str = "simple" input_price: Optional[Decimal] = None output_price: Optional[Decimal] = None unit: str = "tokens" currency: str = "CNY" tiers: Optional[List[PriceTierItem]] = None class ModelDetailResponse(BaseModel): """模型详情响应""" id: int model_code: str display_name: Optional[str] = None img: Optional[str] = None category: int supplier: str description: Optional[str] = None custom_description: Optional[str] = None tag1: Optional[str] = None tag2: Optional[str] = None keywords: Optional[str] = None is_featured: bool = False is_search: bool = False is_thinking: bool = False is_show_enabled: bool = True is_api_enabled: bool = False source_keys: Optional[List[str]] = None normalized_keys: Optional[List[str]] = None created_at: datetime updated_at: datetime model_config = ConfigDict(from_attributes=True) class ModelCreateRequest(BaseModel): """模型创建请求""" model_code: str = Field(..., min_length=1, max_length=200, description="模型唯一标识") display_name: Optional[str] = Field(None, max_length=255, description="显示名称") img: Optional[str] = Field(None, description="图标URL") category: int = Field(0, ge=0, le=8, description="模型分类") supplier: str = Field("Qwen", min_length=1, max_length=100, description="供应商") description: Optional[str] = Field(None, description="模型描述") custom_description: Optional[str] = Field(None, description="管理员自定义描述") tag1: Optional[str] = Field(None, max_length=100, description="主标签") tag2: Optional[str] = Field(None, max_length=100, description="次要标签") keywords: Optional[str] = Field(None, description="关键词") is_featured: bool = Field(False, description="是否精选") is_search: bool = Field(False, description="是否支持搜索") is_thinking: bool = Field(False, description="是否支持思考") is_show_enabled: bool = Field(True, description="是否展示") is_api_enabled: bool = Field(False, description="是否启用API") class ModelUpdateRequest(BaseModel): """模型更新请求""" display_name: Optional[str] = Field(None, max_length=255, description="显示名称") img: Optional[str] = Field(None, description="图标URL") category: Optional[int] = Field(None, ge=0, le=8, description="模型分类") supplier: Optional[str] = Field(None, min_length=1, max_length=100, description="供应商") description: Optional[str] = Field(None, description="模型描述") custom_description: Optional[str] = Field(None, description="管理员自定义描述") tag1: Optional[str] = Field(None, max_length=100, description="主标签") tag2: Optional[str] = Field(None, max_length=100, description="次要标签") keywords: Optional[str] = Field(None, description="关键词") is_featured: Optional[bool] = Field(None, description="是否精选") is_search: Optional[bool] = Field(None, description="是否支持搜索") is_thinking: Optional[bool] = Field(None, description="是否支持思考") class ModelPriceRequest(BaseModel): """模型价格设置请求""" label: str = Field("default", description="价格标签,如 input<=128k / 视频生成(720P)") tier_min: Optional[Decimal] = Field(None, description="阶梯下限") tier_max: Optional[Decimal] = Field(None, description="阶梯上限") tier_unit: Optional[str] = Field(None, description="阶梯单位:tokens/seconds/images") input_price: Optional[Decimal] = Field(None, description="输入原价") output_price: Optional[Decimal] = Field(None, description="输出原价") discount_rate: Optional[Decimal] = Field(Decimal("100"), description="折扣率 0-100,100=无折扣") unit: str = Field("元/每百万tokens", description="显示单位") display_multiplier: int = Field(1000000, description="展示倍数") currency: str = Field("CNY", description="货币单位") class ModelStatusRequest(BaseModel): """模型状态变更请求""" field: str = Field(..., pattern="^(is_show_enabled|is_api_enabled|is_featured)$", description="状态字段") value: bool = Field(..., description="目标值") # ==================== 本地模型管理相关 Schema ==================== class LocalModelListParams(BaseModel): """本地模型列表查询参数""" keyword: Optional[str] = Field(None, description="搜索关键词") user_id: Optional[str] = Field(None, description="用户ID筛选") visibility: Optional[str] = Field(None, description="可见性筛选: user/global") page: int = Field(1, ge=1, description="页码") size: int = Field(20, ge=1, le=100, description="每页数量") class LocalModelListItem(BaseModel): """本地模型列表项""" id: int title: str name: str supplier: Optional[str] = "Custom" user_id: str username: Optional[str] = None base_url: str visibility: str = "user" category: int = 0 created_at: datetime updated_at: datetime model_config = ConfigDict(from_attributes=True) class LocalModelVisibilityRequest(BaseModel): """本地模型可见性变更请求""" visibility: str = Field(..., pattern="^(user|global)$", description="可见性: user/global")