Files
geMoldInsight/src/utils/file_handler.py
T
2026-05-14 16:56:18 +08:00

72 lines
2.4 KiB
Python

# utils/file_handler.py
import aiofiles
import hashlib
import re
import uuid
from pathlib import Path
from fastapi import UploadFile
from typing import Tuple, Dict, Any
class FileHandler:
def __init__(self, upload_dir: str = "uploads", max_file_size: int = 50 * 1024 * 1024):
self.upload_dir = Path(upload_dir)
self.upload_dir.mkdir(exist_ok=True)
self.max_file_size = max_file_size
def _sanitize_filename(self, filename: str) -> str:
original = Path(filename or "upload.step").name
suffix = Path(original).suffix.lower()
stem = Path(original).stem or "upload"
safe_stem = re.sub(r"[^A-Za-z0-9._-]+", "_", stem).strip("._-") or "upload"
if suffix not in {".stp", ".step"}:
suffix = ".step"
return f"{safe_stem}{suffix}"
@staticmethod
def _looks_like_step(content: bytes) -> bool:
if not content:
return False
head = content[:4096].decode("utf-8", errors="ignore").upper()
return (
"ISO-10303-21" in head
or "HEADER;" in head
or "FILE_SCHEMA" in head
or "DATA;" in head
)
async def save_uploaded_file(self, file: UploadFile) -> Tuple[Path, int, Dict[str, Any]]:
"""保存上传的文件并返回安全元数据"""
content = await file.read()
if not content:
raise ValueError("上传文件为空")
if len(content) > self.max_file_size:
raise ValueError(f"上传文件过大,限制 {self.max_file_size // (1024 * 1024)}MB")
if not self._looks_like_step(content):
raise ValueError("文件内容不是有效的 STP/STEP 数据")
safe_name = self._sanitize_filename(file.filename)
unique_name = f"{uuid.uuid4().hex}_{safe_name}"
file_path = self.upload_dir / unique_name
async with aiofiles.open(file_path, "wb") as f:
await f.write(content)
metadata = {
"original_filename": file.filename,
"safe_original_name": safe_name,
"stored_filename": unique_name,
"sha256": hashlib.sha256(content).hexdigest(),
}
return file_path, len(content), metadata
def cleanup_file(self, file_path: Path):
"""清理文件"""
try:
if file_path.exists():
file_path.unlink()
except Exception as e:
print(f"文件清理失败: {e}")