Files
Pine 985b85cb4e feat(oss): 统一OSS/CDN对象存储与直链出口体系
- infrastructure/oss.py 重写:服务端上传/预签名上下行/CDN鉴权直链(方式A)/resolve_url·to_object_path 出入口规范
- config.py 新增 OSS_REGION/OSS_CDN_BASE_URL/OSS_CDN_AUTH_KEY
- media_upload 统一走 oss 单例,返回 CDN 直链;dispatcher /oss 前缀路由
- 管理端用户列表头像出口 CDN 化
2026-08-28 17:17:38 +08:00

66 lines
2.9 KiB
Python
Raw Permalink 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 等)—— 统一走基础设施层 OSSinfrastructure.oss.oss)。
OSS 已配置时上传到桶并返回 /oss/<key> 的绝对 URL(下载经 /oss 路由跳预签名);
未配置时降级本地 serverdata/uploads。编辑器图片/视频/封面、C端资料上传共用。
"""
from __future__ import annotations
import secrets
import time
from pathlib import Path
from fastapi import HTTPException, UploadFile
from .. import config
# server-data 上传目录(本地降级用,与 infrastructure.oss 同一目录)
UPLOAD_DIR = config.SERVERDATA_DIR / "uploads"
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
# 站点公开基础地址(把 /oss、/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 或空则原样返回(委托基础设施层 resolve_url)。"""
from ..infrastructure.oss import resolve_url
return resolve_url(u)
async def save_media(file: UploadFile, dir: str = "misc") -> str:
"""保存上传文件(OSS 优先,本地降级);返回 CDN/OSS 直链绝对 URL;不合法抛 400/413。
OSS 路径规范:<业务目录>/<YYYYMM>/<时间戳>_<随机>.<ext>
业务目录:avatar 头像 / park-admission 入驻认证资料 / news 资讯 / event 活动封面 / park 大屏媒体 / misc 兜底
"""
from ..infrastructure.oss import oss
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 限制")
key = build_key(dir, ext)
await oss.upload(key, payload, content_type=_MIME.get(ext, "application/octet-stream"))
# 直接返回 CDN/OSS 直链,不经服务端代理
return abs_url(oss.direct_url(key))
def build_key(dir: str, ext: str) -> str:
"""按规范生成对象 key<业务目录>/<YYYYMM>/<时间戳>_<随机>.<ext>。"""
d = "".join(c for c in (dir or "misc").lower() if c.isalnum() or c in "-_") or "misc"
return f"{d}/{time.strftime('%Y%m')}/{int(time.time())}_{secrets.token_hex(4)}{ext}"