wendajia/backend/app/screenshot_storage.py
2026-10-17 18:22:39 +08:00

83 lines
2.7 KiB
Python

import hashlib
import mimetypes
import uuid
from pathlib import Path
from fastapi import HTTPException, Response
from .config import settings
def _safe_extension(filename: str, content_type: str) -> str:
extension = Path(filename or "").suffix.lower()
if extension in {".jpg", ".jpeg", ".png", ".webp"}:
return extension
guessed = mimetypes.guess_extension(content_type or "") or ".bin"
return ".jpg" if guessed == ".jpe" else guessed
def _absolute_path(relative_path: str) -> Path:
root = settings.screenshot_storage_dir.resolve()
candidate = (root / relative_path).resolve()
if root != candidate and root not in candidate.parents:
raise ValueError("非法截图文件路径")
return candidate
def store_screenshot(data: bytes, filename: str, content_type: str, category: str) -> str:
if not data:
raise ValueError("截图内容不能为空")
now_path = Path(category) / uuid.uuid4().hex[:2]
relative_path = now_path / f"{uuid.uuid4().hex}{_safe_extension(filename, content_type)}"
target = _absolute_path(relative_path.as_posix())
target.parent.mkdir(parents=True, exist_ok=True)
temporary = target.with_suffix(target.suffix + ".tmp")
temporary.write_bytes(data)
if temporary.stat().st_size != len(data):
temporary.unlink(missing_ok=True)
raise OSError("截图写入不完整")
temporary.replace(target)
return relative_path.as_posix()
def screenshot_fields(data: bytes, filename: str, content_type: str, category: str) -> dict:
return {
"filename": filename,
"content_type": content_type,
"file_path": store_screenshot(data, filename, content_type, category),
"image_data": None,
}
def screenshot_bytes(image) -> bytes:
if image.file_path:
try:
path = _absolute_path(image.file_path)
if path.is_file():
return path.read_bytes()
except (OSError, ValueError):
pass
if image.image_data:
return bytes(image.image_data)
raise HTTPException(status_code=404, detail="截图文件不存在")
def screenshot_response(image) -> Response:
return Response(
content=screenshot_bytes(image),
media_type=image.content_type or "application/octet-stream",
headers={"Cache-Control": "private, max-age=600"},
)
def screenshot_data_url(image) -> str:
import base64
encoded = base64.b64encode(screenshot_bytes(image)).decode()
return f"data:{image.content_type};base64,{encoded}"
def file_digest(relative_path: str) -> tuple[int, str]:
data = _absolute_path(relative_path).read_bytes()
return len(data), hashlib.sha256(data).hexdigest()