165 lines
5.8 KiB
Python
165 lines
5.8 KiB
Python
from pydantic import BaseModel, EmailStr, Field, validator
|
|
from typing import Optional
|
|
from datetime import datetime
|
|
import re
|
|
|
|
class UserRegister(BaseModel):
|
|
"""用户注册请求模型"""
|
|
username: str = Field(..., min_length=3, max_length=50, description="用户名")
|
|
email: EmailStr = Field(..., description="邮箱地址")
|
|
password: str = Field(..., min_length=6, max_length=128, description="密码")
|
|
confirm_password: str = Field(..., min_length=6, max_length=128, description="确认密码")
|
|
|
|
@validator('username')
|
|
def validate_username(cls, v):
|
|
"""验证用户名格式"""
|
|
if not re.match(r'^[a-zA-Z0-9_]+$', v):
|
|
raise ValueError('用户名只能包含字母、数字和下划线')
|
|
if v.startswith('_') or v.endswith('_'):
|
|
raise ValueError('用户名不能以下划线开头或结尾')
|
|
return v
|
|
|
|
@validator('confirm_password')
|
|
def passwords_match(cls, v, values):
|
|
"""验证密码确认"""
|
|
if 'password' in values and v != values['password']:
|
|
raise ValueError('两次输入的密码不一致')
|
|
return v
|
|
|
|
@validator('password')
|
|
def validate_password_length(cls, v):
|
|
"""验证密码长度"""
|
|
if len(v) <= 5:
|
|
raise ValueError('密码长度必须大于5个字符')
|
|
return v
|
|
|
|
class UserLogin(BaseModel):
|
|
"""用户登录请求模型"""
|
|
username: str = Field(..., description="用户名或邮箱")
|
|
password: str = Field(..., min_length=1, description="密码")
|
|
|
|
class TokenResponse(BaseModel):
|
|
"""令牌响应模型"""
|
|
access_token: str = Field(..., description="访问令牌")
|
|
refresh_token: str = Field(..., description="刷新令牌")
|
|
token_type: str = Field(default="bearer", description="令牌类型")
|
|
expires_in: int = Field(..., description="访问令牌过期时间(秒)")
|
|
|
|
class UserResponse(BaseModel):
|
|
"""用户信息响应模型"""
|
|
id: int
|
|
username: str
|
|
email: str
|
|
avatar_url: Optional[str] = None
|
|
storage_quota: int
|
|
storage_used: int
|
|
is_active: bool
|
|
is_verified: bool
|
|
last_login_at: Optional[datetime] = None
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class LoginResponse(BaseModel):
|
|
"""登录响应模型"""
|
|
user: UserResponse = Field(..., description="用户信息")
|
|
tokens: TokenResponse = Field(..., description="令牌信息")
|
|
|
|
class TokenRefresh(BaseModel):
|
|
"""令牌刷新请求模型"""
|
|
refresh_token: str = Field(..., description="刷新令牌")
|
|
|
|
class PasswordChange(BaseModel):
|
|
"""修改密码请求模型"""
|
|
current_password: str = Field(..., description="当前密码")
|
|
new_password: str = Field(..., min_length=8, max_length=128, description="新密码")
|
|
confirm_password: str = Field(..., min_length=8, max_length=128, description="确认新密码")
|
|
|
|
@validator('confirm_password')
|
|
def passwords_match(cls, v, values):
|
|
"""验证密码确认"""
|
|
if 'new_password' in values and v != values['new_password']:
|
|
raise ValueError('密码确认不匹配')
|
|
return v
|
|
|
|
@validator('new_password')
|
|
def validate_password_strength(cls, v):
|
|
"""验证密码强度"""
|
|
errors = []
|
|
|
|
if len(v) < 8:
|
|
errors.append("密码长度至少8位")
|
|
|
|
if not any(c.islower() for c in v):
|
|
errors.append("密码必须包含至少一个小写字母")
|
|
|
|
if not any(c.isupper() for c in v):
|
|
errors.append("密码必须包含至少一个大写字母")
|
|
|
|
if not any(c.isdigit() for c in v):
|
|
errors.append("密码必须包含至少一个数字")
|
|
|
|
special_chars = "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
|
if not any(c in special_chars for c in v):
|
|
errors.append("密码必须包含至少一个特殊字符")
|
|
|
|
if errors:
|
|
raise ValueError('; '.join(errors))
|
|
|
|
return v
|
|
|
|
class PasswordReset(BaseModel):
|
|
"""密码重置请求模型"""
|
|
email: EmailStr = Field(..., description="邮箱地址")
|
|
|
|
class PasswordResetConfirm(BaseModel):
|
|
"""密码重置确认模型"""
|
|
token: str = Field(..., description="重置令牌")
|
|
new_password: str = Field(..., min_length=8, max_length=128, description="新密码")
|
|
confirm_password: str = Field(..., min_length=8, max_length=128, description="确认新密码")
|
|
|
|
@validator('confirm_password')
|
|
def passwords_match(cls, v, values):
|
|
"""验证密码确认"""
|
|
if 'new_password' in values and v != values['new_password']:
|
|
raise ValueError('密码确认不匹配')
|
|
return v
|
|
|
|
@validator('new_password')
|
|
def validate_password_strength(cls, v):
|
|
"""验证密码强度"""
|
|
errors = []
|
|
|
|
if len(v) < 8:
|
|
errors.append("密码长度至少8位")
|
|
|
|
if not any(c.islower() for c in v):
|
|
errors.append("密码必须包含至少一个小写字母")
|
|
|
|
if not any(c.isupper() for c in v):
|
|
errors.append("密码必须包含至少一个大写字母")
|
|
|
|
if not any(c.isdigit() for c in v):
|
|
errors.append("密码必须包含至少一个数字")
|
|
|
|
special_chars = "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
|
if not any(c in special_chars for c in v):
|
|
errors.append("密码必须包含至少一个特殊字符")
|
|
|
|
if errors:
|
|
raise ValueError('; '.join(errors))
|
|
|
|
return v
|
|
|
|
class ApiResponse(BaseModel):
|
|
"""标准API响应模型"""
|
|
success: bool = Field(..., description="操作是否成功")
|
|
message: str = Field(..., description="响应消息")
|
|
data: Optional[dict] = Field(None, description="响应数据")
|
|
error: Optional[dict] = Field(None, description="错误信息")
|
|
|
|
class Config:
|
|
json_encoders = {
|
|
datetime: lambda v: v.isoformat() if v else None
|
|
} |