mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-22 13:05:18 +00:00
本次提交包含多项优化和修复: 1. 修复CRUD初始化参数传递、搜索参数处理逻辑 2. 更新环境配置中的大模型相关参数 3. 重构部分服务方法命名,统一代码风格 4. 新增多个枚举类型,补充模型关联关系和加载选项 5. 优化查询参数类实现,完善字段校验逻辑 6. 调整Pydantic模型字段注释和类型定义 7. 简化并移除冗余的CRUD方法实现 8. 新增超级管理员权限装饰器 9. 修复邮件日志模型的租户关联和字段定义
203 lines
6.5 KiB
Python
203 lines
6.5 KiB
Python
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from urllib.parse import urlparse
|
|
|
|
from fastapi import Query
|
|
from pydantic import (
|
|
BaseModel,
|
|
ConfigDict,
|
|
Field,
|
|
field_validator,
|
|
model_validator,
|
|
)
|
|
|
|
from app.common.enums import QueueEnum
|
|
|
|
|
|
class ResourceItemSchema(BaseModel):
|
|
"""资源项目模型"""
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
name: str = Field(..., description="文件名")
|
|
file_url: str = Field(..., description="文件URL路径")
|
|
relative_path: str = Field(..., description="相对路径")
|
|
is_file: bool = Field(..., description="是否为文件")
|
|
is_dir: bool = Field(..., description="是否为目录")
|
|
size: int | None = Field(None, description="文件大小(字节)")
|
|
created_time: datetime | None = Field(None, description="创建时间")
|
|
modified_time: datetime | None = Field(None, description="修改时间")
|
|
is_hidden: bool = Field(False, description="是否为隐藏文件")
|
|
|
|
@field_validator("file_url")
|
|
@classmethod
|
|
def _validate_file_url(cls, v: str) -> str:
|
|
v = v.strip()
|
|
parsed = urlparse(v)
|
|
# 允许相对路径(以 / 开头)和完整的 http/https URL
|
|
if parsed.scheme and parsed.scheme not in ("http", "https"):
|
|
raise ValueError("文件URL必须为 http/https 或相对路径")
|
|
return v
|
|
|
|
@field_validator("relative_path")
|
|
@classmethod
|
|
def _validate_relative_path(cls, v: str) -> str:
|
|
v = v.strip()
|
|
if ".." in v or v.startswith("\\"):
|
|
raise ValueError("相对路径包含不安全字符")
|
|
return v
|
|
|
|
@model_validator(mode="after")
|
|
def _validate_flags(self):
|
|
if self.is_file and self.is_dir:
|
|
raise ValueError("不能同时为文件和目录")
|
|
if not self.is_file and not self.is_dir:
|
|
raise ValueError("必须是文件或目录之一")
|
|
# 根据名称自动修正隐藏标记
|
|
self.is_hidden = self.name.startswith(".")
|
|
return self
|
|
|
|
|
|
class ResourceDirectorySchema(BaseModel):
|
|
"""资源目录模型"""
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
path: str = Field(..., description="目录路径")
|
|
name: str = Field(..., description="目录名称")
|
|
items: list[ResourceItemSchema] = Field(default_factory=list, description="目录项")
|
|
total_files: int = Field(0, description="文件总数")
|
|
total_dirs: int = Field(0, description="目录总数")
|
|
total_size: int = Field(0, description="总大小")
|
|
|
|
|
|
class ResourceUploadSchema(BaseModel):
|
|
"""资源上传响应模型"""
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
filename: str = Field(..., description="文件名")
|
|
file_url: str = Field(..., description="访问URL")
|
|
file_size: int = Field(..., description="文件大小")
|
|
upload_time: datetime = Field(..., description="上传时间")
|
|
|
|
|
|
class ResourceMoveSchema(BaseModel):
|
|
"""资源移动模型"""
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
source_path: str = Field(..., description="源路径")
|
|
target_path: str = Field(..., description="目标路径")
|
|
overwrite: bool = Field(False, description="是否覆盖")
|
|
|
|
@field_validator("source_path", "target_path")
|
|
@classmethod
|
|
def validate_paths(cls, value: str):
|
|
"""
|
|
校验移动/复制涉及的源路径与目标路径非空并去首尾空格。
|
|
|
|
参数:
|
|
- value (str): 路径字段当前值。
|
|
|
|
返回:
|
|
- str: 去空格后的路径。
|
|
|
|
异常:
|
|
- ValueError: 路径为空时抛出。
|
|
"""
|
|
if not value or len(value.strip()) == 0:
|
|
raise ValueError("路径不能为空")
|
|
return value.strip()
|
|
|
|
|
|
class ResourceCopySchema(ResourceMoveSchema):
|
|
"""资源复制模型"""
|
|
|
|
|
|
class ResourceRenameSchema(BaseModel):
|
|
"""资源重命名模型"""
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
old_path: str = Field(..., description="原路径")
|
|
new_name: str = Field(..., max_length=255, description="新名称")
|
|
|
|
@field_validator("old_path", "new_name")
|
|
@classmethod
|
|
def validate_inputs(cls, value: str):
|
|
"""
|
|
校验重命名所需的原路径与新名称非空并去首尾空格。
|
|
|
|
参数:
|
|
- value (str): 字段当前值。
|
|
|
|
返回:
|
|
- str: 去空格后的值。
|
|
|
|
异常:
|
|
- ValueError: 值为空时抛出。
|
|
"""
|
|
if not value or len(value.strip()) == 0:
|
|
raise ValueError("参数不能为空")
|
|
return value.strip()
|
|
|
|
@field_validator("new_name")
|
|
@classmethod
|
|
def _validate_new_name(cls, v: str) -> str:
|
|
v = v.strip()
|
|
if ".." in v or "/" in v or "\\" in v:
|
|
raise ValueError("新名称包含不安全字符")
|
|
return v
|
|
|
|
|
|
class ResourceCreateDirSchema(BaseModel):
|
|
"""创建目录模型"""
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
parent_path: str = Field(..., description="父目录路径")
|
|
dir_name: str = Field(..., description="目录名称", max_length=255)
|
|
|
|
@field_validator("parent_path", "dir_name")
|
|
@classmethod
|
|
def validate_inputs(cls, value: str, info):
|
|
"""
|
|
校验创建目录的父路径与目录名,防止路径遍历等不安全输入。
|
|
|
|
参数:
|
|
- value (str): 当前字段值。
|
|
- info: Pydantic 校验上下文(含 `field_name`)。
|
|
|
|
返回:
|
|
- str: 规范化后的字段值。
|
|
|
|
异常:
|
|
- ValueError: 含不安全字符或目录名为空时抛出。
|
|
"""
|
|
# 对于parent_path允许为空字符串(表示根目录)或 '/',其他情况必须非空
|
|
if info.field_name == "parent_path":
|
|
# 对于parent_path仍然严格检查路径遍历
|
|
if ".." in value or value.startswith("\\"):
|
|
raise ValueError("参数包含不安全字符")
|
|
else: # 对于dir_name仍然严格检查
|
|
if not value or len(value.strip()) == 0:
|
|
raise ValueError("参数不能为空")
|
|
if ".." in value or value.startswith(("/", "\\")):
|
|
raise ValueError("参数包含不安全字符")
|
|
return value.strip()
|
|
|
|
|
|
@dataclass
|
|
class ResourceSearchQueryParam:
|
|
"""资源搜索查询参数"""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str | None = Query(None, description="搜索关键词"),
|
|
path: str | None = Query(None, description="目录路径"),
|
|
) -> None:
|
|
self.name = (QueueEnum.like.value, name) if name else None
|
|
|
|
self.path = path
|