Files
iot-device-management-service/app/routers/yudao_aiot_storage.py
16337 51dbad6794 fix: 告警列表500错误修复 + 摄像头查询容错 + COS认证重构
- 添加缺失的 import httpx(修复 _notify_ops_platform NameError)
- 摄像头批量查询加 try/except 容错,失败时降级使用 device_id
- 摄像头查询超时从 5 秒提升到 15 秒
- COS 存储重构:支持 CVM 角色认证 + 密钥认证双模式
- STS 接口改为返回提示(CVM 模式不支持 STS)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:33:28 +08:00

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

"""
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"
)