""" 文件处理工具 """ import os import shutil from pathlib import Path from typing import Optional from fastapi import UploadFile from app.core.config import settings from app.core.exceptions import FileUploadException from app.utils.logger import logger def validate_file_extension(filename: str) -> bool: """验证文件扩展名""" ext = Path(filename).suffix.lower() return ext in settings.allowed_extensions def validate_file_size(file_size: int) -> bool: """验证文件大小""" return file_size <= settings.MAX_UPLOAD_SIZE async def save_upload_file(file: UploadFile, project_id: str, subdir: Optional[str] = None) -> str: """ 保存上传的文件 Args: file: 上传的文件 project_id: 项目ID subdir: 子目录(可选) Returns: 保存的文件路径 """ # 验证文件扩展名 if not validate_file_extension(file.filename): raise FileUploadException( f"不支持的文件类型。支持的类型: {', '.join(settings.allowed_extensions)}" ) # 创建保存目录 save_dir = Path(settings.UPLOAD_DIR) / project_id if subdir: save_dir = save_dir / subdir save_dir.mkdir(parents=True, exist_ok=True) # 保存文件 file_path = save_dir / file.filename try: with open(file_path, "wb") as f: content = await file.read() # 验证文件大小 if not validate_file_size(len(content)): raise FileUploadException( f"文件大小超过限制(最大 {settings.MAX_UPLOAD_SIZE / 1024 / 1024:.0f}MB)" ) f.write(content) logger.info(f"文件保存成功: {file_path}") return str(file_path) except Exception as e: logger.error(f"文件保存失败: {str(e)}") raise FileUploadException(f"文件保存失败: {str(e)}") def cleanup_temp_file(file_path: str) -> None: """清理临时文件""" try: if os.path.exists(file_path): os.remove(file_path) logger.info(f"临时文件已删除: {file_path}") except Exception as e: logger.warning(f"删除临时文件失败: {file_path}, 错误: {str(e)}") def cleanup_temp_directory(dir_path: str) -> None: """清理临时目录""" try: if os.path.exists(dir_path): shutil.rmtree(dir_path) logger.info(f"临时目录已删除: {dir_path}") except Exception as e: logger.warning(f"删除临时目录失败: {dir_path}, 错误: {str(e)}") def detect_file_type(filename: str) -> str: """根据文件扩展名检测文件类型""" ext = Path(filename).suffix.lower() if ext in [".xlsx", ".xls"]: return "excel" elif ext in [".docx", ".doc"]: return "word" elif ext == ".pdf": return "pdf" elif ext == ".csv": return "csv" else: raise FileUploadException(f"不支持的文件类型: {ext}")