""" 用户相关 Schema 定义用户数据的验证和序列化规则。 """ import re from datetime import datetime from typing import Annotated from pydantic import EmailStr, Field, field_validator from app.core.config import settings from app.schemas.base import BaseSchema # 用户名正则:字母开头,只允许字母、数字、下划线 USERNAME_PATTERN = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$") class UserBase(BaseSchema): """用户基础字段""" username: Annotated[ str, Field( min_length=settings.username_min_length, max_length=settings.username_max_length, description="用户名(字母开头,只允许字母、数字、下划线)", examples=["john_doe"], ), ] email: Annotated[ EmailStr | None, Field( default=None, description="邮箱地址", examples=["user@example.com"], ), ] nickname: Annotated[ str | None, Field( default=None, max_length=64, description="昵称", examples=["John"], ), ] @field_validator("username") @classmethod def validate_username(cls, v: str) -> str: """验证用户名格式""" if not USERNAME_PATTERN.match(v): raise ValueError("用户名必须以字母开头,只能包含字母、数字和下划线") return v.lower() # 统一转小写 class UserCreate(UserBase): """用户注册请求""" password: Annotated[ str, Field( min_length=settings.password_min_length, max_length=settings.password_max_length, description="密码", examples=["SecurePass123"], ), ] @field_validator("password") @classmethod def validate_password(cls, v: str) -> str: """验证密码强度""" errors: list[str] = [] if settings.password_require_uppercase and not re.search(r"[A-Z]", v): errors.append("至少包含一个大写字母") if settings.password_require_lowercase and not re.search(r"[a-z]", v): errors.append("至少包含一个小写字母") if settings.password_require_digit and not re.search(r"\d", v): errors.append("至少包含一个数字") if settings.password_require_special and not re.search(r"[!@#$%^&*(),.?\":{}|<>]", v): errors.append("至少包含一个特殊字符") if errors: raise ValueError("密码强度不足:" + ";".join(errors)) return v class UserUpdate(BaseSchema): """用户信息更新请求""" nickname: Annotated[ str | None, Field( default=None, max_length=64, description="昵称", ), ] email: Annotated[ EmailStr | None, Field( default=None, description="邮箱地址", ), ] avatar_url: Annotated[ str | None, Field( default=None, max_length=512, description="头像 URL", ), ] bio: Annotated[ str | None, Field( default=None, max_length=500, description="个人简介", ), ] class UserResponse(BaseSchema): """用户信息响应""" id: str username: str email: str | None nickname: str | None avatar_url: str | None bio: str | None is_active: bool created_at: datetime last_login_at: datetime | None class UserProfileResponse(BaseSchema): """用户公开资料响应(不包含敏感信息)""" id: str username: str nickname: str | None avatar_url: str | None bio: str | None created_at: datetime