Files
server-core/app/services/media_upload.py
T

70 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""媒体上传(图片/pdf/doc 等)—— OSS 优先(OSS_ENDPOINT 配置后走桶),否则落 serverdata/uploads。
统一返回契约:绝对 URL。编辑器图片/视频/封面、C端资料上传共用。
"""
from __future__ import annotations
import secrets
import time
from pathlib import Path
from fastapi import HTTPException, UploadFile
from .. import config
# server-data 上传目录(server-core 根下 serverdata/uploads,本地降级用)
SERVER_CORE_DIR = Path(__file__).resolve().parents[2]
UPLOAD_DIR = SERVER_CORE_DIR / "serverdata" / "uploads"
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
# 站点公开基础地址(把 /uploads 相对路径补全为完整 HTTPS)
PUBLIC_BASE = __import__("os").environ.get("PUBLIC_BASE", "https://opc.pinesound.cn").rstrip("/")
ALLOWED_EXT = {".jpg", ".jpeg", ".png", ".webp", ".gif", ".pdf", ".doc", ".docx"}
MAX_SIZE = 20 * 1024 * 1024 # 20MB
# 扩展名 → content-type
_MIME = {
".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png",
".webp": "image/webp", ".gif": "image/gif",
".pdf": "application/pdf", ".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
}
def abs_url(u: str | None) -> str:
"""相对路径补全为绝对 URL;已是完整 URL 或空则原样返回。"""
if not u:
return u or ""
u = str(u).strip()
if u.startswith("http://") or u.startswith("https://"):
return u
return PUBLIC_BASE + u
def _oss_url(key: str) -> str:
"""OSS 对象 -> 可访问绝对 URL(公共读桶 endpoint/bucket/key)。"""
endpoint = (config.OSS_ENDPOINT or "").rstrip("/")
return f"{endpoint}/{config.OSS_BUCKET}/{key}"
async def save_media(file: UploadFile) -> str:
"""保存上传文件:OSS 优先,否则本地 uploads;返回绝对 URL;不合法抛 400/413。"""
name = (file.filename or "").rsplit("/", 1)[-1]
ext = ("." + name.split(".")[-1].lower()) if "." in name else ""
if ext not in ALLOWED_EXT:
raise HTTPException(status_code=400, detail=f"不支持的文件类型({ext or '无扩展名'}")
payload = await file.read()
if len(payload) > MAX_SIZE:
raise HTTPException(status_code=413, detail="文件超过 20MB 限制")
fname = f"up_{int(time.time())}_{secrets.token_hex(2)}{ext}"
if config.OSS_ENDPOINT and config.OSS_SECRET_KEY and config.OSS_ACCESS_KEY:
from ..infrastructure.oss import OSS
path = await OSS().upload(fname, payload, content_type=_MIME.get(ext, "application/octet-stream"))
# upload 返回 /oss/<key> 或 /uploads/<key>——OSS 桶走 endpoint/bucket/key
return _oss_url(fname)
with open(UPLOAD_DIR / fname, "wb") as f:
f.write(payload)
return abs_url(f"/uploads/{fname}")