|
|
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="分页后的数据列表")
|