schema.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. # -*- coding: utf-8 -*-
  2. from datetime import datetime
  3. from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
  4. from urllib.parse import urlparse
  5. from fastapi import Query
  6. class ResourceItemSchema(BaseModel):
  7. """资源项目模型"""
  8. model_config = ConfigDict(from_attributes=True)
  9. name: str = Field(..., description="文件名")
  10. file_url: str = Field(..., description="文件URL路径")
  11. relative_path: str = Field(..., description="相对路径")
  12. is_file: bool = Field(..., description="是否为文件")
  13. is_dir: bool = Field(..., description="是否为目录")
  14. size: int | None = Field(None, description="文件大小(字节)")
  15. created_time: datetime | None = Field(None, description="创建时间")
  16. modified_time: datetime | None = Field(None, description="修改时间")
  17. is_hidden: bool = Field(False, description="是否为隐藏文件")
  18. @field_validator('file_url')
  19. @classmethod
  20. def _validate_file_url(cls, v: str) -> str:
  21. v = v.strip()
  22. parsed = urlparse(v)
  23. if parsed.scheme not in ('http', 'https'):
  24. raise ValueError('文件URL必须为 http/https')
  25. return v
  26. @field_validator('relative_path')
  27. @classmethod
  28. def _validate_relative_path(cls, v: str) -> str:
  29. v = v.strip()
  30. if '..' in v or v.startswith('\\'):
  31. raise ValueError('相对路径包含不安全字符')
  32. return v
  33. @model_validator(mode='after')
  34. def _validate_flags(self):
  35. if self.is_file and self.is_dir:
  36. raise ValueError('不能同时为文件和目录')
  37. if not self.is_file and not self.is_dir:
  38. raise ValueError('必须是文件或目录之一')
  39. # 根据名称自动修正隐藏标记
  40. self.is_hidden = self.name.startswith('.')
  41. return self
  42. class ResourceDirectorySchema(BaseModel):
  43. """资源目录模型"""
  44. model_config = ConfigDict(from_attributes=True)
  45. path: str = Field(..., description="目录路径")
  46. name: str = Field(..., description="目录名称")
  47. items: list[ResourceItemSchema] = Field(default_factory=list, description="目录项")
  48. total_files: int = Field(0, description="文件总数")
  49. total_dirs: int = Field(0, description="目录总数")
  50. total_size: int = Field(0, description="总大小")
  51. class ResourceUploadSchema(BaseModel):
  52. """资源上传响应模型"""
  53. model_config = ConfigDict(from_attributes=True)
  54. filename: str = Field(..., description="文件名")
  55. file_url: str = Field(..., description="访问URL")
  56. file_size: int = Field(..., description="文件大小")
  57. upload_time: datetime = Field(..., description="上传时间")
  58. class ResourceMoveSchema(BaseModel):
  59. """资源移动模型"""
  60. model_config = ConfigDict(from_attributes=True)
  61. source_path: str = Field(..., description="源路径")
  62. target_path: str = Field(..., description="目标路径")
  63. overwrite: bool = Field(False, description="是否覆盖")
  64. @field_validator('source_path', 'target_path')
  65. @classmethod
  66. def validate_paths(cls, value: str):
  67. if not value or len(value.strip()) == 0:
  68. raise ValueError("路径不能为空")
  69. return value.strip()
  70. class ResourceCopySchema(ResourceMoveSchema):
  71. """资源复制模型"""
  72. pass
  73. class ResourceRenameSchema(BaseModel):
  74. """资源重命名模型"""
  75. model_config = ConfigDict(from_attributes=True)
  76. old_path: str = Field(..., description="原路径")
  77. new_name: str = Field(..., description="新名称")
  78. @field_validator('old_path', 'new_name')
  79. @classmethod
  80. def validate_inputs(cls, value: str):
  81. if not value or len(value.strip()) == 0:
  82. raise ValueError("参数不能为空")
  83. return value.strip()
  84. @field_validator('new_name')
  85. @classmethod
  86. def _validate_new_name(cls, v: str) -> str:
  87. v = v.strip()
  88. if '..' in v or '/' in v or '\\' in v:
  89. raise ValueError('新名称包含不安全字符')
  90. return v
  91. class ResourceCreateDirSchema(BaseModel):
  92. """创建目录模型"""
  93. model_config = ConfigDict(from_attributes=True)
  94. parent_path: str = Field(..., description="父目录路径")
  95. dir_name: str = Field(..., description="目录名称", max_length=255)
  96. @field_validator('parent_path', 'dir_name')
  97. @classmethod
  98. def validate_inputs(cls, value: str, info):
  99. # 对于parent_path允许为空字符串(表示根目录)或 '/',其他情况必须非空
  100. if info.field_name == 'parent_path':
  101. # 允许空字符串或 '/' 表示根目录
  102. if value is None:
  103. raise ValueError("参数不能为空")
  104. # 对于parent_path仍然严格检查路径遍历
  105. if '..' in value or value.startswith('\\'):
  106. raise ValueError("参数包含不安全字符")
  107. else: # 对于dir_name仍然严格检查
  108. if not value or len(value.strip()) == 0:
  109. raise ValueError("参数不能为空")
  110. if '..' in value or value.startswith('/') or value.startswith('\\'):
  111. raise ValueError("参数包含不安全字符")
  112. return value.strip()
  113. class ResourceSearchQueryParam:
  114. """资源搜索查询参数"""
  115. def __init__(
  116. self,
  117. name: str | None = Query(None, description="搜索关键词"),
  118. path: str | None = Query(None, description="目录路径"),
  119. ) -> None:
  120. # 模糊查询字段
  121. self.name = ("like", name) if name else None
  122. # 精确查询字段
  123. self.path = path