fix: 修复多个关键问题

- 修复前端路由守卫:未登录时不显示提示,直接跳转登录页
- 修复API拦截器:401错误不显示提示,直接跳转
- 增强验证码显示:图片尺寸从120x40增加到200x80
- 增大验证码字体:从28号增加到48号
- 优化验证码字符:排除易混淆的0和1
- 减少干扰线:从5条减少到3条,添加背景色优化
- 增强登录API日志:添加详细的调试日志
- 增强验证码生成和验证日志
- 优化异常处理和错误追踪

影响文件:
- src/router/index.ts
- src/api/request.ts
- app/services/auth_service.py
- app/api/v1/auth.py
- app/schemas/user.py

测试状态:
- 前端构建通过
- 后端语法检查通过
- 验证码显示效果优化完成

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-01-25 00:26:21 +08:00
commit e71181f0a3
150 changed files with 39549 additions and 0 deletions

231
app/schemas/user.py Normal file
View File

@@ -0,0 +1,231 @@
"""
用户相关的Pydantic Schema
"""
from typing import Optional, List
from datetime import datetime
from pydantic import BaseModel, Field, EmailStr, field_validator
# ===== 用户Schema =====
class UserBase(BaseModel):
"""用户基础Schema"""
real_name: str = Field(..., min_length=1, max_length=100, description="真实姓名")
email: Optional[EmailStr] = Field(None, description="邮箱")
phone: Optional[str] = Field(None, max_length=20, description="手机号")
class UserCreate(UserBase):
"""创建用户Schema"""
username: str = Field(..., min_length=4, max_length=50, description="用户名")
password: str = Field(..., min_length=8, max_length=100, description="密码")
role_ids: List[int] = Field(..., min_items=1, description="角色ID列表")
@field_validator("username")
@classmethod
def validate_username(cls, v: str) -> str:
"""验证用户名格式"""
if not v.replace("_", "").isalnum():
raise ValueError("用户名只能包含字母、数字和下划线")
return v
@field_validator("password")
@classmethod
def validate_password(cls, v: str) -> str:
"""验证密码强度"""
if not any(c.isupper() for c in v):
raise ValueError("密码必须包含至少一个大写字母")
if not any(c.islower() for c in v):
raise ValueError("密码必须包含至少一个小写字母")
if not any(c.isdigit() for c in v):
raise ValueError("密码必须包含至少一个数字")
return v
class UserUpdate(BaseModel):
"""更新用户Schema"""
real_name: Optional[str] = Field(None, min_length=1, max_length=100)
email: Optional[EmailStr] = None
phone: Optional[str] = Field(None, max_length=20)
status: Optional[str] = Field(None, pattern="^(active|disabled|locked)$")
role_ids: Optional[List[int]] = None
class UserInDB(BaseModel):
"""数据库中的用户Schema"""
id: int
username: str
real_name: str
email: Optional[str]
phone: Optional[str]
avatar_url: Optional[str]
status: str
is_admin: bool
last_login_at: Optional[datetime]
created_at: datetime
class Config:
from_attributes = True
class UserResponse(UserInDB):
"""用户响应Schema"""
roles: List["RoleResponse"] = []
class Config:
from_attributes = True
class UserInfo(BaseModel):
"""用户信息Schema不含敏感信息"""
id: int
username: str
real_name: str
email: Optional[str]
avatar_url: Optional[str]
is_admin: bool
status: str
class Config:
from_attributes = True
# ===== 登录认证Schema =====
class LoginRequest(BaseModel):
"""登录请求Schema"""
username: str = Field(..., min_length=1, description="用户名")
password: str = Field(..., min_length=1, description="密码")
captcha: str = Field(..., min_length=4, description="验证码")
captcha_key: str = Field(..., description="验证码UUID")
class LoginResponse(BaseModel):
"""登录响应Schema"""
access_token: str = Field(..., description="访问令牌")
refresh_token: str = Field(..., description="刷新令牌")
token_type: str = Field(default="Bearer", description="令牌类型")
expires_in: int = Field(..., description="过期时间(秒)")
user: UserInfo = Field(..., description="用户信息")
class RefreshTokenRequest(BaseModel):
"""刷新令牌请求Schema"""
refresh_token: str = Field(..., description="刷新令牌")
class RefreshTokenResponse(BaseModel):
"""刷新令牌响应Schema"""
access_token: str = Field(..., description="新的访问令牌")
expires_in: int = Field(..., description="过期时间(秒)")
class ChangePasswordRequest(BaseModel):
"""修改密码请求Schema"""
old_password: str = Field(..., min_length=1, description="旧密码")
new_password: str = Field(..., min_length=8, max_length=100, description="新密码")
confirm_password: str = Field(..., min_length=8, max_length=100, description="确认密码")
@field_validator("confirm_password")
@classmethod
def validate_passwords_match(cls, v: str, info) -> str:
"""验证两次密码是否一致"""
if "new_password" in info.data and v != info.data["new_password"]:
raise ValueError("两次输入的密码不一致")
return v
class ResetPasswordRequest(BaseModel):
"""重置密码请求Schema"""
new_password: str = Field(..., min_length=8, max_length=100, description="新密码")
# ===== 角色Schema =====
class RoleBase(BaseModel):
"""角色基础Schema"""
role_name: str = Field(..., min_length=1, max_length=50, description="角色名称")
role_code: str = Field(..., min_length=1, max_length=50, description="角色代码")
description: Optional[str] = Field(None, description="角色描述")
class RoleCreate(RoleBase):
"""创建角色Schema"""
permission_ids: List[int] = Field(default_factory=list, description="权限ID列表")
class RoleUpdate(BaseModel):
"""更新角色Schema"""
role_name: Optional[str] = Field(None, min_length=1, max_length=50)
description: Optional[str] = None
permission_ids: Optional[List[int]] = None
class RoleInDB(BaseModel):
"""数据库中的角色Schema"""
id: int
role_name: str
role_code: str
description: Optional[str]
status: str
sort_order: int
created_at: datetime
class Config:
from_attributes = True
class RoleResponse(RoleInDB):
"""角色响应Schema"""
permissions: List["PermissionResponse"] = []
class Config:
from_attributes = True
class RoleWithUserCount(RoleResponse):
"""带用户数量的角色响应Schema"""
user_count: int = Field(..., description="用户数量")
# ===== 权限Schema =====
class PermissionBase(BaseModel):
"""权限基础Schema"""
permission_name: str = Field(..., min_length=1, max_length=100)
permission_code: str = Field(..., min_length=1, max_length=100)
module: str = Field(..., min_length=1, max_length=50)
resource: Optional[str] = Field(None, max_length=50)
action: Optional[str] = Field(None, max_length=50)
description: Optional[str] = None
class PermissionCreate(PermissionBase):
"""创建权限Schema"""
pass
class PermissionUpdate(BaseModel):
"""更新权限Schema"""
permission_name: Optional[str] = Field(None, min_length=1, max_length=100)
description: Optional[str] = None
class PermissionResponse(PermissionBase):
"""权限响应Schema"""
id: int
created_at: datetime
class Config:
from_attributes = True
class PermissionTreeNode(PermissionResponse):
"""权限树节点Schema"""
children: List["PermissionTreeNode"] = []
# 更新前向引用
UserResponse.model_rebuild()
RoleResponse.model_rebuild()
PermissionTreeNode.model_rebuild()