Files
FastapiAdmin/backend/app/core/base_schema.py
T
zhangtao 211fddd6e0 refactor: 完成项目大规模代码重构与依赖清理
这是一次综合性的重构更新,包含以下主要变更:
1.  升级Python版本到3.12,更新依赖配置
2.  替换旧的.j2模板为.jinja2格式,新增代码生成模板
3.  重构权限过滤策略,更新权限枚举与模型配置
4.  移除Prefect依赖,替换为自研拓扑并行执行引擎
5.  重构认证与上下文管理,拆分租户/请求上下文
6.  简化响应模型、CRUD与服务层代码
7.  清理废弃的支付网关模块,重构订单定时任务
8.  更新在线用户、监控等模块的接口与路由
9.  优化邮件模板与工具类,新增邮件模板文件
10. 修复数据库会话配置与类型提示
2026-06-21 06:02:05 +08:00

161 lines
5.9 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 typing import TYPE_CHECKING, Any, TypeVar
from pydantic import BaseModel, ConfigDict, Field, model_validator
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.validator import DateTimeStr
if TYPE_CHECKING:
# 仅供 IDE 类型推断,运行时不会触发导入,避免 app.core ↔ app.api.v1 循环依赖
from app.api.v1.module_system.user.model import UserModel
UserT = TypeVar("UserT")
class CommonSchema(BaseModel):
"""通用信息模型"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(description="编号ID")
name: str = 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 TenantBySchema(BaseModel):
"""租户嵌套出参(不再使用扁平 tenant_id / tenant_name / tenant_code"""
model_config = ConfigDict(from_attributes=True)
tenant_id: int | None = Field(default=None, description="租户ID")
tenant_by: CommonSchema | None = Field(default=None, 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 BatchDelete(BaseModel):
"""批量删除请求模型"""
ids: list[int] = Field(..., min_length=1, description="ID列表")
class AuthSchema(BaseModel):
"""权限认证模型
``user`` 字段运行时为 ``Any``(避免与 SQLAlchemy 懒加载冲突,也避免
循环依赖)。通过 ``get_user()`` 方法获得 IDE 类型推断。
"""
model_config = ConfigDict(arbitrary_types_allowed=True)
user: Any = Field(default=None, description="用户信息(UserModel 实例)", exclude=True)
check_data_scope: bool = Field(default=True, description="是否检查数据权限")
db: AsyncSession | None = Field(default=None, description="数据库会话", exclude=True)
tenant_id: int | None = Field(default=None, description="租户ID,用于用户认证前查询")
def get_user(self) -> "UserModel | None":
"""类型化的用户访问方法。
业务方 ``auth.get_user()`` 由 IDE 推断为 ``UserModel | None``
享受自动补全。``auth.user`` 仍可用但无类型补全。
"""
return self.user # type: ignore[return-value]
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 RefreshTokenPayloadSchema(BaseModel):
"""刷新Token载荷模型"""
refresh_token: str = Field(..., min_length=1, description="刷新token")
class LogoutPayloadSchema(BaseModel):
"""退出登录载荷模型"""
token: str = Field(..., min_length=1, description="token")
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="分页后的数据列表")