Files
iot-device-management-service/app/routers/yudao_aiot_storage.py

126 lines
4.1 KiB
Python
Raw Normal View History

"""
AIoT 文件存储路由 - COS 对象存储接口
API 路径规范
- POST /admin-api/aiot/storage/upload - 后端中转上传
- GET /admin-api/aiot/storage/presign - 获取预签名下载 URL
- GET /admin-api/aiot/storage/sts - 获取 STS 临时凭证前端直传
- GET /admin-api/aiot/storage/upload-url - 获取预签名上传 URL前端直传
"""
import os
from fastapi import APIRouter, Query, Depends, HTTPException, UploadFile, File
from app.yudao_compat import YudaoResponse, get_current_user
from app.services.oss_storage import get_oss_storage, COSStorage, _generate_object_key
router = APIRouter(prefix="/admin-api/aiot/storage", tags=["AIoT-文件存储"])
@router.post("/upload")
async def upload_file(
file: UploadFile = File(..., description="上传文件"),
prefix: str = Query("alerts", description="存储路径前缀"),
current_user: dict = Depends(get_current_user),
):
"""
后端中转上传
文件经过后端写入 COS / 本地返回 object_key
适用于服务端需要对文件做校验或处理的场景
"""
storage = get_oss_storage()
# 文件大小检查(限制 20MB
content = await file.read()
if len(content) > 20 * 1024 * 1024:
raise HTTPException(status_code=400, detail="文件大小超过 20MB 限制")
# 文件类型检查
allowed_types = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".mp4", ".mov", ".pdf"}
_, ext = os.path.splitext(file.filename or "unknown.jpg")
ext = ext.lower()
if ext not in allowed_types:
raise HTTPException(status_code=400, detail=f"不支持的文件类型: {ext}")
# 确定 content_type
content_type = file.content_type or "application/octet-stream"
# 生成 object_key
object_key = _generate_object_key(prefix=prefix, ext=ext)
# 上传
result_key = storage.upload_file(content, object_key, content_type)
return YudaoResponse.success({
"objectKey": result_key,
"filename": file.filename,
"size": len(content),
"contentType": content_type,
})
@router.get("/presign")
async def get_presigned_download_url(
objectKey: str = Query(..., description="对象 Key"),
expire: int = Query(1800, description="有效期(秒)"),
current_user: dict = Depends(get_current_user),
):
"""
获取预签名下载 URL
前端查看告警截图时调用此接口拿到临时 URL 后直接访问 COS
URL 过期后失效防止泄露
"""
storage = get_oss_storage()
url = storage.get_presigned_url(objectKey, expire)
return YudaoResponse.success({
"url": url,
"expire": expire,
})
@router.get("/upload-url")
async def get_presigned_upload_url(
prefix: str = Query("alerts", description="存储路径前缀"),
ext: str = Query(".jpg", description="文件扩展名"),
expire: int = Query(1800, description="有效期(秒)"),
current_user: dict = Depends(get_current_user),
):
"""
获取预签名上传 URL
前端拿到此 URL 直接 PUT 文件到 COS无需经过后端中转
适用于大文件或高频上传场景
"""
storage = get_oss_storage()
if not storage.is_cos_mode:
raise HTTPException(status_code=400, detail="COS 未启用,请使用 /upload 接口")
object_key = _generate_object_key(prefix=prefix, ext=ext)
url = storage.get_presigned_upload_url(object_key, expire)
if not url:
raise HTTPException(status_code=500, detail="生成上传 URL 失败")
return YudaoResponse.success({
"uploadUrl": url,
"objectKey": object_key,
"expire": expire,
})
@router.get("/sts")
async def get_sts_credential(
prefix: str = Query("alerts", description="允许上传的路径前缀"),
current_user: dict = Depends(get_current_user),
):
"""
STS 凭证接口CVM 角色模式下不可用请使用 /upload-url 预签名上传
"""
raise HTTPException(
status_code=400,
detail="当前使用 CVM 角色认证,不支持 STS 签发。请使用 /admin-api/aiot/storage/upload-url 获取预签名上传 URL"
)