import re from datetime import datetime from typing import Optional from pydantic import BaseModel, ConfigDict, Field, model_validator, field_validator from sqlalchemy.ext.asyncio import AsyncSession from app.api.v1.module_system.user.model import UserModel from app.utils.sms_util import SmsTemplateEnum class SmsCodeSchema(BaseModel): # 验证手机号格式 mobile: str = Field(..., description="手机号") # 短信模版 template_name: str = Field(..., description="短信模版") # # 验证码 code: Optional[str] = Field(default=None, description="验证码") @field_validator("mobile", mode="before") @classmethod def validate_mobile(cls, v: str) -> str: # 移除常见地格式化字符 cleaned = re.sub(r'[\s\-+()()]', '', str(v)) # 中国大陆手机号验证 china_pattern = r'^1[3-9]\d{9}$' # 带国际区号的手机号 # china_with_country_code = r'^(?:\+?86)?1[3-9]\d{9}$' # if not re.match(china_with_country_code, cleaned): # raise ValueError("手机号格式错误") if not re.match(china_pattern, cleaned): raise ValueError("手机号格式错误") # 返回清洗后的纯数字 return cleaned.lstrip('+86') class AuthSchema(BaseModel): """权限认证模型""" model_config = ConfigDict(arbitrary_types_allowed=True) user: UserModel | None = Field(default=None, description="用户信息") check_data_scope: bool = Field(default=True, description="是否检查数据权限") db: AsyncSession = Field(description="数据库会话") tenant_id: int = Field(default=-1, description="租户ID") class JWTPayloadSchema(BaseModel): """JWT载荷模型""" sub: str = Field(..., description="用户登录信息") is_refresh: bool = Field(default=False, description="是否刷新token") exp: datetime | int = Field(..., description="过期时间") @model_validator(mode="after") def validate_fields(self): """ 校验 JWT 载荷字段的基本合法性。 返回: - JWTPayloadSchema: 校验后的载荷实例。 异常: - ValueError: 必填字段为空或格式不正确时抛出。 """ if not self.sub or len(self.sub.strip()) == 0: raise ValueError("会话编号不能为空") return self class JWTOutSchema(BaseModel): """JWT响应模型""" model_config = ConfigDict(from_attributes=True) access_token: str = Field(..., min_length=1, description="访问token") refresh_token: str = Field(..., min_length=1, description="刷新token") token_type: str = Field(default="Bearer", description="token类型") expires_in: int = Field(..., gt=0, description="过期时间(秒)") class RefreshTokenPayloadSchema(BaseModel): """刷新Token载荷模型""" refresh_token: str = Field(..., min_length=1, description="刷新token") class LogoutPayloadSchema(BaseModel): """退出登录载荷模型""" token: str = Field(..., min_length=1, description="token") class CaptchaOutSchema(BaseModel): """验证码响应模型""" model_config = ConfigDict(from_attributes=True) enable: bool = Field(default=True, description="是否启用验证码") key: str = Field(..., min_length=1, description="验证码唯一标识") img_base: str = Field(..., min_length=1, description="Base64编码的验证码图片") class AutoLoginUserSchema(BaseModel): """免登录用户信息模型""" model_config = ConfigDict(from_attributes=True) id: int = Field(..., description="用户ID") username: str = Field(..., description="用户名") name: str = Field(..., description="用户姓名") avatar: str | None = Field(default=None, description="头像") class AutoLoginTokenSchema(BaseModel): """免登录Token响应模型""" model_config = ConfigDict(from_attributes=True) token: str = Field(..., description="免登录Token") user: AutoLoginUserSchema = Field(..., description="用户信息") class LoginMiniRequestSchema(BaseModel): """小程序登录请求模型""" model_config = ConfigDict(from_attributes=True) username: str = Field(..., min_length=1, description="用户名") password: str = Field(..., min_length=1, description="密码") login_type: Optional[str] = Field(default="mini", description="登录类型") class SmsLoginRequestSchema(SmsCodeSchema): code: str = Field(..., description="验证码")