Files
FastapiAdmin/backend/app/core/base_schema.py
T
zhangtao 158c14a46d refactor: 项目代码整理与依赖优化
主要变更:
1.  替换默认部门名称为系统部门
2.  简化SQLAlchemy异步导入语句
3.  移除request_context.py与相关依赖,重构请求上下文逻辑
4.  替换旧的日志链路ID实现,将代码整合到logger.py
5.  简化WebSocket限流逻辑,移除冗余import
6.  删除多个模块的测试文件:common/ai/example/generator/monitor/task
7.  移除cron_util.py,改用croniter库实现Cron表达式校验
8.  优化系统参数Redis缓存键格式,移除多余的:1:前缀
9.  简化初始化脚本逻辑,移除日期自动转换逻辑
10. 简化动态路由发现代码,移除热重载相关逻辑
11. 简化认证逻辑,移除request.state.ctx相关操作
12. 修复定时任务调度器调用方式,移除await
13. 新增修改定时任务接口
14. 新增croniter依赖并更新相关配置
15. 优化数据库异常日志格式化方式
16. 简化base_schema.py空行格式
17. 移除initialize.py中的冗余依赖导入
2026-07-17 23:19:37 +08:00

217 lines
8.7 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 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="会话ID(Redis 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_by:None→默认升序,str→json.loads 转 list,list→直接返回,其他→抛异常。"""
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 —— 子类自动继承"""
created_time: list[DateTimeStr] | None = Field(
None,
description="创建时间范围",
examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"],
)
updated_time: list[DateTimeStr] | None = Field(
None,
description="更新时间范围",
examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"],
)
class UserByQueryParam(BaseModel):
"""created_id + updated_id —— 子类自动继承"""
created_id: int | None = Field(None, description="创建人")
updated_id: int | None = Field(None, description="更新人")
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列表")