service.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import os
  2. from fastapi import UploadFile
  3. from app.config.setting import settings
  4. from app.core.base_schema import DownloadFileSchema, UploadResponseSchema
  5. from app.core.exceptions import CustomException
  6. from app.core.logger import log
  7. from app.utils.upload_util import UploadUtil
  8. class FileService:
  9. """
  10. 文件管理服务层
  11. """
  12. @classmethod
  13. async def upload_service(
  14. cls, base_url: str, file: UploadFile, upload_type: str = "local"
  15. ) -> dict:
  16. """
  17. 上传文件。
  18. 参数:
  19. - base_url (str): 基础访问 URL。
  20. - file (UploadFile): 上传文件对象。
  21. - upload_type (str): 上传类型,'local' 或 'oss',默认 'local'。
  22. 返回:
  23. - Dict: 上传响应字典。
  24. 异常:
  25. - CustomException: 当未选择文件或上传类型错误时抛出。
  26. """
  27. if upload_type == "local":
  28. filename, filepath, file_url = await UploadUtil.upload_file(
  29. file=file, base_url=base_url
  30. )
  31. else:
  32. raise CustomException(msg="上传类型错误")
  33. return UploadResponseSchema(
  34. file_path=f"{filepath}",
  35. file_name=filename,
  36. origin_name=file.filename,
  37. file_url=f"{file_url}",
  38. ).model_dump()
  39. @staticmethod
  40. def _validate_download_path(file_path: str) -> str:
  41. """
  42. 验证下载路径是否安全。
  43. 参数:
  44. - file_path (str): 文件路径。
  45. 返回:
  46. - str: 安全的绝对路径。
  47. 异常:
  48. - CustomException: 当路径不安全时抛出。
  49. """
  50. if not file_path:
  51. raise CustomException(msg="请选择要下载的文件")
  52. dangerous_patterns = ["../", "..\\", "\0"]
  53. for pattern in dangerous_patterns:
  54. if pattern in file_path:
  55. log.error(f"检测到路径穿越攻击: {file_path}")
  56. raise CustomException(msg="非法的文件路径")
  57. upload_root = settings.UPLOAD_FILE_PATH.resolve()
  58. abs_path = os.path.normpath(os.path.abspath(file_path))
  59. if not abs_path.startswith(str(upload_root)):
  60. log.error(f"路径不在上传目录内: {file_path}")
  61. raise CustomException(msg="非法的文件路径")
  62. return abs_path
  63. @classmethod
  64. async def download_service(cls, file_path: str) -> DownloadFileSchema:
  65. """
  66. 下载文件。
  67. 参数:
  68. - file_path (str): 文件路径。
  69. 返回:
  70. - DownloadFileSchema: 下载文件响应对象。
  71. 异常:
  72. - CustomException: 当未选择文件或文件不存在时抛出。
  73. """
  74. safe_path = cls._validate_download_path(file_path)
  75. if not UploadUtil.check_file_exists(safe_path):
  76. raise CustomException(msg="文件不存在")
  77. file_name = UploadUtil.download_file(safe_path)
  78. return DownloadFileSchema(
  79. file_path=safe_path,
  80. file_name=str(file_name),
  81. )