import mimetypes from pathlib import Path from uuid import uuid4 from fastapi import APIRouter, HTTPException, Request, status from starlette.datastructures import UploadFile from app.config import oss_settings from app.schemas import UploadResponse router = APIRouter(prefix="/uploads", tags=["uploads"]) MAX_IMAGE_SIZE = 5 * 1024 * 1024 ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"} def get_public_url(object_key: str) -> str: if oss_settings.public_base_url: return f"{oss_settings.public_base_url.rstrip('/')}/{object_key}" endpoint = oss_settings.endpoint.replace("https://", "").replace("http://", "").rstrip("/") return f"https://{oss_settings.bucket_name}.{endpoint}/{object_key}" def build_object_key(filename: str | None, folder: str) -> str: suffix = Path(filename or "").suffix.lower() if not suffix: suffix = mimetypes.guess_extension("image/jpeg") or ".jpg" safe_folder = folder.strip("/ ") or oss_settings.upload_prefix return f"{oss_settings.upload_prefix.strip('/')}/{safe_folder}/{uuid4().hex}{suffix}" @router.post("/images", response_model=UploadResponse) async def upload_image(request: Request) -> UploadResponse: if not oss_settings.is_configured: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="OSS 配置未完成,请先填写 app/config.py 中的 OSS 配置。", ) form = await request.form() file = form.get("file") folder_value = form.get("folder") folder = str(folder_value) if folder_value else "products" if not isinstance(file, UploadFile): raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="请选择要上传的图片") content_type = file.content_type or "application/octet-stream" if content_type not in ALLOWED_IMAGE_TYPES: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="只支持上传图片文件") data = await file.read() if len(data) > MAX_IMAGE_SIZE: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="图片大小不能超过 5MB") try: import oss2 except ImportError as exc: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="缺少 oss2 依赖,请先安装 requirements.txt 中的依赖。", ) from exc object_key = build_object_key(file.filename, folder) auth = oss2.Auth(oss_settings.access_key_id, oss_settings.access_key_secret) bucket = oss2.Bucket(auth, oss_settings.upload_endpoint, oss_settings.bucket_name) bucket.put_object(object_key, data, headers={"Content-Type": content_type}) return UploadResponse(url=get_public_url(object_key), object_key=object_key)