Files
SatoNano/app/schemas/user.py
2026-01-06 23:49:23 +08:00

157 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
用户相关 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