61e3d71b72
there are 3 parts, from back to head, url inside the bucket -> url of backend media api -> backend domain, only the last part is spliced by frontend, the other two parts are spliced with sqlalchemy
45 lines
1.6 KiB
Python
45 lines
1.6 KiB
Python
from fastapi import APIRouter, HTTPException
|
|
from fastapi.responses import StreamingResponse
|
|
import oss2
|
|
from app.utils.s3 import bucket
|
|
|
|
router = APIRouter(prefix="/v1/media", tags=["Media"])
|
|
|
|
@router.get("/{file_path:path}")
|
|
async def get_avatar(file_path: str):
|
|
"""
|
|
通过流式传输代理阿里云 OSS 中的图片
|
|
file_path 接收 avatars/xxx/avatar.jpg 等任何相对路径
|
|
"""
|
|
s3_key = file_path.lstrip("/")
|
|
|
|
if not s3_key:
|
|
raise HTTPException(status_code=400, detail="Invalid file path")
|
|
|
|
try:
|
|
# 从阿里云 OSS 获取对象
|
|
# oss2 的 get_object 是同步阻塞的
|
|
# 初期可以直接用,后续高并发可以放入 run_in_executor
|
|
oss_object = bucket.get_object(s3_key)
|
|
|
|
# 获取文件的 Content-Type
|
|
content_type = oss_object.headers.get("Content-Type", "image/jpeg")
|
|
|
|
# 创建一个生成器来分块读取 OSS 数据,防止大文件时撑爆内存
|
|
def image_stream():
|
|
while True:
|
|
chunk = oss_object.read(1024 * 64) # 每次读 64KB
|
|
if not chunk:
|
|
break
|
|
yield chunk
|
|
|
|
# 流式返回给前端
|
|
return StreamingResponse(image_stream(), media_type=content_type)
|
|
|
|
except oss2.exceptions.NoSuchKey:
|
|
# 阿里云 OSS 专属的 404 异常
|
|
raise HTTPException(status_code=404, detail="Image not found in storage")
|
|
except Exception as e:
|
|
import traceback
|
|
traceback.print_exc()
|
|
raise HTTPException(status_code=500, detail="Internal storage service error") |