Files
dpb/backend/app/core/base_schema.py
T
34047007@qq.com b95053c52c init: 初始化 dpb 桃育种系统代码库
前后端 + 后端 FastAPI 全量源码、部署脚本与文档。
2026-08-06 00:17:49 +08:00

223 lines
9.2 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.
import json
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from app.core.validator import DateTimeStr
class CommonSchema(BaseModel):
"""通用信息模型"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(description="编号ID")
name: str = Field(description="名称")
status: int = Field(description="状态")
class BaseSchema(BaseModel):
"""通用输出模型,包含基础字段和审计字段"""
model_config = ConfigDict(from_attributes=True)
id: int | None = Field(default=None, description="主键ID")
uuid: str | None = Field(default=None, description="UUID")
created_time: DateTimeStr | None = Field(default=None, description="创建时间")
updated_time: DateTimeStr | None = Field(default=None, description="更新时间")
is_deleted: bool = Field(default=False, description="是否已删除")
deleted_time: DateTimeStr | None = Field(default=None, description="删除时间")
class UserBySchema(BaseModel):
"""通用创建模型,包含基础字段和审计字段"""
model_config = ConfigDict(from_attributes=True)
created_id: int | None = Field(default=None, description="创建人ID")
created_by: CommonSchema | None = Field(default=None, description="创建人信息")
updated_id: int | None = Field(default=None, description="更新人ID")
updated_by: CommonSchema | None = Field(default=None, description="更新人信息")
deleted_id: int | None = Field(default=None, description="删除人ID")
deleted_by: CommonSchema | None = Field(default=None, description="删除人信息")
class ImportResultSchema(BaseModel):
"""批量导入结果模型 —— 结构化返回成功/失败条数及逐行错误明细。
前端据此展示失败原因,替代原先拼接的纯字符串。
"""
valid_count: int = Field(default=0, ge=0, description="成功条数")
invalid_count: int = Field(default=0, ge=0, description="失败条数")
message_list: list[str] = Field(default_factory=list, description="逐行错误明细")
class BatchSetAvailable(BaseModel):
"""批量设置可用状态的请求模型"""
ids: list[int] = Field(default_factory=list, description="ID列表")
status: int = Field(default=0, ge=0, le=1, description="是否可用")
class UploadResponseSchema(BaseModel):
"""上传响应模型"""
model_config = ConfigDict(from_attributes=True)
file_path: str | None = Field(default=None, description="新文件映射路径")
file_name: str | None = Field(default=None, description="新文件名称")
origin_name: str | None = Field(default=None, description="原文件名称")
file_url: str | None = Field(default=None, description="新文件访问地址")
class DownloadFileSchema(BaseModel):
"""下载文件模型"""
file_path: str = Field(..., description="新文件映射路径")
file_name: str = Field(..., description="新文件名称")
class SessionInfoSchema(BaseModel):
"""Redis 中存储的会话信息结构
由 ``AuthService._assemble_session_dict`` 构造,存入 Redis 后被认证、
在线用户等模块读取。``OnlineOutSchema`` 为此结构的公开子集。
"""
session_id: str = Field(default="", description="会话IDRedis key 后缀)")
user_id: int | None = Field(default=None, description="用户ID")
is_superuser: bool = Field(default=False, description="是否为超级管理员")
user_status: int = Field(default=0, description="用户状态")
name: str | None = Field(default=None, description="用户名称")
user_name: str | None = Field(default=None, description="用户名")
dept_id: int | None = Field(default=None, description="部门ID")
mobile: str | None = Field(default=None, description="手机号")
email: str | None = Field(default=None, description="邮箱")
gender: str | None = Field(default=None, description="性别(0:男 1:女 2:未知)")
avatar: str | None = Field(default=None, description="头像")
permissions: list[str] = Field(default_factory=list, description="用户权限列表")
menu_ids: list[int] = Field(default_factory=list, description="菜单ID列表")
ipaddr: str | None = Field(default=None, description="登陆IP地址")
login_location: str | None = Field(default=None, description="登录所属地")
os: str | None = Field(default=None, description="操作系统")
browser: str | None = Field(default=None, description="浏览器")
login_time: DateTimeStr | None = Field(default=None, description="登录时间")
login_type: str | None = Field(default=None, description="登录类型")
class JWTPayloadSchema(BaseModel):
"""JWT载荷模型"""
sub: str = Field(..., description="用户登录信息")
is_refresh: bool = Field(default=False, description="是否刷新token")
exp: datetime | int = Field(..., description="过期时间")
@model_validator(mode="after")
def validate_fields(self):
if not self.sub or len(self.sub.strip()) == 0:
raise ValueError("会话编号不能为空")
return self
class JWTOutSchema(BaseModel):
"""JWT响应模型"""
model_config = ConfigDict(from_attributes=True)
access_token: str = Field(..., min_length=1, description="访问token")
refresh_token: str = Field(..., min_length=1, description="刷新token")
token_type: str = Field(default="Bearer", description="token类型")
expires_in: int = Field(..., gt=0, description="过期时间(秒)")
class PageResultSchema[T](BaseModel):
"""分页查询结果模型"""
model_config = ConfigDict(from_attributes=True)
page_no: int | None = Field(default=None, ge=1, description="页码,默认为1")
page_size: int | None = Field(default=None, ge=1, description="页面大小,默认为10")
total: int = Field(default=0, ge=0, description="总记录数")
has_next: bool | None = Field(default=False, description="是否有下一页")
items: list[T] = Field(default_factory=list, description="分页后的数据列表")
class PaginationQueryParam(BaseModel):
"""分页 —— order_by 以 JSON 字符串传递,避免 Depends() 模式下 list 字段被当 body 验证。"""
page_no: int = Field(default=1, description="当前页码", ge=1)
page_size: int = Field(default=10, description="每页数量", ge=1, le=100)
order_by: Any = Field(
default=None,
description="排序字段 JSON 字符串, 格式:[{'field1': 'asc'}, {'field2': 'desc'}]",
)
@field_validator("order_by")
@classmethod
def validate_order_by(cls, v: Any) -> Any:
"""校验 order_byNone→默认升序,str→json.loads 转 listlist→直接返回,其他→抛异常。"""
if v is None:
return [{"id": "asc"}]
if isinstance(v, str):
try:
result = json.loads(v)
if not isinstance(result, list):
raise ValueError("order_by 必须是 JSON 数组字符串,例如 [{\"id\":\"asc\"}]")
return result
except json.JSONDecodeError:
raise ValueError("order_by 字符串无法解析为 JSON,请传入有效的 JSON 数组字符串,例如 [{\"id\":\"asc\"}]")
if isinstance(v, list):
return v
raise ValueError(f"order_by 类型无效: {type(v).__name__},预期为 JSON 数组字符串或列表")
class BaseQueryParam(BaseModel):
"""created_time + updated_time —— 子类自动继承
前端传数组格式 ``["start", "end"]````search_to_dict`` 自动转为 ``("between", [start, end])``。
"""
created_time: list[DateTimeStr] | None = Field(None, description="创建时间范围")
updated_time: list[DateTimeStr] | None = Field(None, description="更新时间范围")
class UserByQueryParam(BaseModel):
"""created_id + updated_id —— 子类自动继承"""
created_id: int | None = Field(None, description="创建人", json_schema_extra={"q": "eq"})
updated_id: int | None = Field(None, description="更新人", json_schema_extra={"q": "eq"})
class OptionSchema(BaseModel):
"""通用下拉选项 Schema,返回 [{value, label}]"""
value: int
label: str
class CoreUserSchema(BaseModel):
"""核心层用户信息 — AuthSchema 使用,不依赖任何业务模块
业务模块的 UserOutSchema 应继承此类以确保类型兼容。
"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(default=0, description="用户ID")
username: str | None = Field(default=None, description="用户名")
name: str | None = Field(default=None, description="名称")
dept_id: int | None = Field(default=None, description="部门ID")
is_superuser: bool = Field(default=False, description="是否超管")
class AuthSchema(BaseModel):
"""权限认证模型"""
model_config = ConfigDict(arbitrary_types_allowed=True)
user: CoreUserSchema = Field(default_factory=CoreUserSchema, description="用户信息", exclude=True)
permissions: list[str] = Field(default_factory=list, description="用户权限标识列表")
menu_ids: list[int] = Field(default_factory=list, description="角色授权的菜单ID列表")