PayrollManagementSystem/backend/app/schemas/auth.py
liuwenxin 108bf87225 feat(核算)!: 添加 T+ 正式核算与采集托管
补齐源快照、资料预演、正式导入及工资核算的依赖链路。
新增版本化输入、逐段 FIFO、生成锁和结构化当月调整,
并提供默认关闭的周期采集、有限重试及 Windows 托管。

原始客户资料、私有配置及首页改动不纳入本次提交。

BREAKING CHANGE: 升级前需应用新增迁移,并通过
PAYROLL_MAKER_USER_ID 绑定实际工资制作人,否则业务只读。
旧单批次核算默认关闭;历史兼容需显式启用
PAYROLL_ALLOW_LEGACY_CALCULATION。生产启用和真实金额待验收。
2026-11-02 09:42:53 +08:00

76 lines
2.4 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.

"""认证相关的请求与响应模型。"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.core.constants import CAPTCHA_LENGTH, PASSWORD_MAX_BYTES
class CaptchaOut(BaseModel):
"""验证码响应。
图片以 base64 内联返回而非单独的 URL:让 id 与图片在同一响应中成对产生,
避免前端分两次请求时拿到不匹配的组合。
"""
captcha_id: str = Field(description="验证码标识,登录时需回传")
image_base64: str = Field(description="PNG 图片的 base64,不含 data: 前缀")
expires_in: int = Field(description="有效期(秒)")
class LoginIn(BaseModel):
"""登录请求。"""
model_config = ConfigDict(str_strip_whitespace=True)
username: str = Field(min_length=1, max_length=64, description="登录用户名")
password: str = Field(min_length=1, description="密码")
captcha_id: str = Field(min_length=1, max_length=64, description="验证码标识")
captcha_code: str = Field(
min_length=CAPTCHA_LENGTH,
max_length=CAPTCHA_LENGTH,
description="用户填写的验证码",
)
@field_validator("password")
@classmethod
def _check_password_bytes(cls, value: str) -> str:
"""按**字节数**校验密码长度。
必须在这里拦下:bcrypt 5.0 起对超过 72 字节的密码直接抛 ValueError,
而中文一个字符占 3 字节——按字符数校验(如 max_length=20)根本拦不住,
24 个汉字就会让哈希函数抛异常、登录接口 500。
"""
if len(value.encode("utf-8")) > PASSWORD_MAX_BYTES:
raise ValueError(f"密码超过 {PASSWORD_MAX_BYTES} 字节上限")
return value
class UserBrief(BaseModel):
"""用户简要信息。"""
model_config = ConfigDict(from_attributes=True)
id: int
username: str
real_name: str | None = None
roles: list[str] = Field(default_factory=list, description="角色编码列表")
can_write_payroll: bool = False
payroll_approval_enabled: bool = False
class LoginOut(BaseModel):
"""登录成功响应。"""
access_token: str
token_type: str = "Bearer"
expires_in: int = Field(description="有效期(秒)")
user: UserBrief
class CurrentUserOut(UserBrief):
"""当前登录用户信息。"""
last_login_time: datetime | None = None