85 lines
2.4 KiB
Python
85 lines
2.4 KiB
Python
"""
|
|
文件服务 API 路由
|
|
"""
|
|
|
|
import aiofiles.os
|
|
from urllib.parse import unquote
|
|
from pathlib import Path
|
|
from fastapi import APIRouter, HTTPException
|
|
from fastapi.responses import FileResponse
|
|
|
|
from app.core.logger import logger
|
|
from app.core.storage import DATA_DIR
|
|
|
|
router = APIRouter(tags=["Files"])
|
|
|
|
# 缓存根目录
|
|
BASE_DIR = DATA_DIR / "tmp"
|
|
IMAGE_DIR = BASE_DIR / "image"
|
|
VIDEO_DIR = BASE_DIR / "video"
|
|
|
|
|
|
def _normalize_cached_filename(filename: str) -> str:
|
|
"""
|
|
规范化客户端传入文件名,兼容尾部误带反斜杠等情况。
|
|
"""
|
|
value = (filename or "").strip()
|
|
# 尝试解码一次,兼容 %5C 这类编码
|
|
try:
|
|
value = unquote(value)
|
|
except Exception:
|
|
pass
|
|
value = value.strip().strip('"').strip("'").rstrip("\\/")
|
|
# 将路径分隔符统一扁平化到缓存命名规则
|
|
value = value.replace("\\", "-").replace("/", "-")
|
|
return value
|
|
|
|
|
|
@router.get("/image/{filename:path}")
|
|
async def get_image(filename: str):
|
|
"""
|
|
获取图片文件
|
|
"""
|
|
filename = _normalize_cached_filename(filename)
|
|
|
|
file_path = IMAGE_DIR / filename
|
|
|
|
if await aiofiles.os.path.exists(file_path):
|
|
if await aiofiles.os.path.isfile(file_path):
|
|
content_type = "image/jpeg"
|
|
if file_path.suffix.lower() == ".png":
|
|
content_type = "image/png"
|
|
elif file_path.suffix.lower() == ".webp":
|
|
content_type = "image/webp"
|
|
|
|
# 增加缓存头,支持高并发场景下的浏览器/CDN缓存
|
|
return FileResponse(
|
|
file_path,
|
|
media_type=content_type,
|
|
headers={"Cache-Control": "public, max-age=31536000, immutable"},
|
|
)
|
|
|
|
logger.warning(f"Image not found: {filename}")
|
|
raise HTTPException(status_code=404, detail="Image not found")
|
|
|
|
|
|
@router.get("/video/{filename:path}")
|
|
async def get_video(filename: str):
|
|
"""
|
|
获取视频文件
|
|
"""
|
|
filename = _normalize_cached_filename(filename)
|
|
|
|
file_path = VIDEO_DIR / filename
|
|
|
|
if await aiofiles.os.path.exists(file_path):
|
|
if await aiofiles.os.path.isfile(file_path):
|
|
return FileResponse(
|
|
file_path,
|
|
media_type="video/mp4",
|
|
headers={"Cache-Control": "public, max-age=31536000, immutable"},
|
|
)
|
|
|
|
logger.warning(f"Video not found: {filename}")
|
|
raise HTTPException(status_code=404, detail="Video not found")
|