Your Name 3a444864ac feat: 腾讯云 COS 迁移至火山引擎 TOS 对象存储
签名算法从 COS HMAC-SHA1 改为 TOS V4 HMAC-SHA256,
更新前后端上传凭证字段、配置项、备份脚本和文档。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 11:02:15 +08:00

126 lines
3.3 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.

"""
文件上传 API
"""
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
from app.services.oss import generate_upload_policy, get_file_url
from app.config import settings
from app.models.user import User
from app.api.deps import get_current_user
router = APIRouter(prefix="/upload", tags=["文件上传"])
class UploadPolicyRequest(BaseModel):
"""获取上传凭证请求"""
file_type: str = "general" # script, video, image, general
file_name: Optional[str] = None
class UploadPolicyResponse(BaseModel):
"""TOS 直传凭证响应"""
x_tos_algorithm: str
x_tos_credential: str
x_tos_date: str
x_tos_signature: str
policy: str
host: str
dir: str
expire: int
max_size_mb: int
class FileUploadedRequest(BaseModel):
"""文件上传完成回调"""
file_key: str
file_name: str
file_size: int
file_type: str
class FileUploadedResponse(BaseModel):
"""文件上传完成响应"""
url: str
file_key: str
file_name: str
file_size: int
file_type: str
@router.post("/policy", response_model=UploadPolicyResponse)
async def get_upload_policy(
request: UploadPolicyRequest,
current_user: User = Depends(get_current_user),
):
"""
获取 TOS 直传凭证
前端使用此凭证直接上传文件到火山引擎 TOS无需经过后端。
文件类型说明:
- script: 脚本文档 (docx, pdf, xlsx, txt, pptx)
- video: 视频文件 (mp4, mov, webm)
- image: 图片文件 (jpg, png, gif)
- general: 通用文件
"""
# 根据文件类型设置上传目录
now = datetime.now()
base_dir = f"uploads/{now.year}/{now.month:02d}"
if request.file_type == "script":
upload_dir = f"{base_dir}/scripts/"
elif request.file_type == "video":
upload_dir = f"{base_dir}/videos/"
elif request.file_type == "image":
upload_dir = f"{base_dir}/images/"
else:
upload_dir = f"{base_dir}/files/"
try:
policy = generate_upload_policy(
max_size_mb=settings.MAX_FILE_SIZE_MB,
expire_seconds=3600,
upload_dir=upload_dir,
)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
return UploadPolicyResponse(
x_tos_algorithm=policy["x_tos_algorithm"],
x_tos_credential=policy["x_tos_credential"],
x_tos_date=policy["x_tos_date"],
x_tos_signature=policy["x_tos_signature"],
policy=policy["policy"],
host=policy["host"],
dir=policy["dir"],
expire=policy["expire"],
max_size_mb=settings.MAX_FILE_SIZE_MB,
)
@router.post("/complete", response_model=FileUploadedResponse)
async def file_uploaded(
request: FileUploadedRequest,
current_user: User = Depends(get_current_user),
):
"""
文件上传完成回调
前端上传完成后调用此接口,获取文件的完整 URL。
"""
url = get_file_url(request.file_key)
return FileUploadedResponse(
url=url,
file_key=request.file_key,
file_name=request.file_name,
file_size=request.file_size,
file_type=request.file_type,
)