Files
nanocode/new-media-platform/website-new/backend/app/routers/upload.py
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

123 lines
4.0 KiB
Python

# app/routers/upload.py
import os
import uuid
from datetime import datetime
from pathlib import Path
from PIL import Image as PILImage
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, Form
from pydantic import BaseModel
from app.config import get_settings
from app.models.admin import Admin
from app.auth import get_current_admin
router = APIRouter()
settings = get_settings()
# Allowed file types (MIME)
ALLOWED_IMAGE_TYPES = {
"image/jpeg", "image/png", "image/gif", "image/webp",
"image/x-icon", "image/vnd.microsoft.icon", # .ico favicon
"image/svg+xml", # .svg
}
# Allowed extensions (fallback when browser reports wrong MIME)
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".ico", ".svg"}
MAX_FILE_SIZE = settings.MAX_UPLOAD_SIZE # 5MB
# Schemas
class UploadResponse(BaseModel):
code: int = 200
data: dict = None
message: str = "success"
def create_thumbnail(image_path: Path, thumb_path: Path, max_size: int = 300):
"""创建缩略图"""
try:
with PILImage.open(image_path) as img:
# 转换为 RGB(处理透明图片)
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
# 等比缩放到最大尺寸
img.thumbnail((max_size, max_size), PILImage.Resampling.LANCZOS)
# 保存
img.save(thumb_path, "JPEG", quality=85)
return True
except Exception as e:
print(f"创建缩略图失败: {e}")
return False
@router.post("/upload/image", response_model=UploadResponse)
async def upload_image(
file: UploadFile = File(...),
subdir: str = Form(default=""),
current_admin = Depends(get_current_admin)
):
"""Upload image file - 支持按子目录存储"""
# Check file type - allow by MIME or by extension (browsers may report wrong MIME for .ico)
file_ext = os.path.splitext(file.filename or "")[1].lower()
if file.content_type not in ALLOWED_IMAGE_TYPES and file_ext not in ALLOWED_EXTENSIONS:
raise HTTPException(
status_code=400,
detail=f"不支持的文件类型: {file.content_type} ({file_ext})"
)
# Check file size
content = await file.read()
if len(content) > MAX_FILE_SIZE:
raise HTTPException(
status_code=400,
detail=f"文件大小超过限制: {MAX_FILE_SIZE / 1024 / 1024}MB"
)
# Generate unique filename with timestamp
ext = os.path.splitext(file.filename)[1].lower()
if not ext:
ext = ".jpg" if file.content_type == "image/jpeg" else ".png"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{timestamp}_{uuid.uuid4().hex[:8]}{ext}"
# 根据子目录存储
upload_path = Path(settings.UPLOAD_DIR)
if subdir:
upload_path = upload_path / subdir
upload_path.mkdir(parents=True, exist_ok=True)
# 缩略图目录(始终放在 thumbnails 下)
thumbnail_dir = Path(settings.UPLOAD_DIR) / "thumbnails"
thumbnail_dir.mkdir(exist_ok=True)
# Save file
file_path = upload_path / filename
with open(file_path, "wb") as f:
f.write(content)
# 创建缩略图(.ico 和 .svg 跳过缩略图生成)
thumbnail_name = f"{Path(filename).stem}.jpg"
thumb_path = thumbnail_dir / thumbnail_name
thumb_relative_path = None
if ext not in ('.ico', '.svg') and create_thumbnail(file_path, thumb_path):
thumb_relative_path = f"/storage/admin/images/thumbnails/{thumbnail_name}"
# Return URL
if subdir:
url = f"/storage/admin/images/{subdir}/{filename}"
else:
url = f"/storage/admin/images/{filename}"
return UploadResponse(
message="上传成功",
data={
"url": url,
"filename": filename,
"size": len(content),
"thumb_url": thumb_relative_path
}
)