"""``POST /uploads/presign`` — COS 直传预签名(FR-SYS-03 / T-P1-27)。 图片不经应用服务器:本接口只做场景/类型/大小校验并签发 PUT 预签名 URL, 字节流由客户端直传 COS。真实联调需在 `.env` 配 COS 四件套 (COS_REGION / COS_SECRET_ID / COS_SECRET_KEY + 场景桶配置 UPLOAD_SCENES_JSON);本地单测注入 FakeSigner,不依赖任何真实 COS。 ``POST /uploads/direct`` — 本地磁盘直传(无 COS 四件套的降级路线,docs 提交 9228f67 决策):body 即图片原始字节,Content-Type 头申报类型, scene 走 query(默认 report_photo)。校验口径与 presign 完全一致, 落盘 ``settings.upload_dir``,返回的 object_key 供建报告等接口回传。 """ from __future__ import annotations from fastapi import APIRouter, Query, Request from apps.api.deps import CurrentUserId, SettingsDep, StorageServiceDep from packages.shared.response import ok from packages.storage.local import save_direct_upload from packages.storage.schemas import PresignRequest router = APIRouter(prefix="/uploads", tags=["uploads"]) @router.post("/presign", summary="签发图片直传 COS 的 PUT 预签名 URL") async def presign_upload( user_id: CurrentUserId, storage: StorageServiceDep, request: PresignRequest, ) -> dict[str, object]: presigned = storage.build_presign(user_id=user_id, request=request) return ok(presigned.model_dump()) @router.post("/direct", summary="本地磁盘直传图片(无 COS 降级路线):body 为原始字节") async def direct_upload( user_id: CurrentUserId, settings: SettingsDep, request: Request, scene: str = Query(default="report_photo", min_length=1, max_length=64), ) -> dict[str, object]: data = await request.body() # Content-Type 头可能带参数(如 "image/jpeg; boundary=…"),只取主类型。 content_type = request.headers.get("content-type", "").split(";")[0].strip().lower() result = save_direct_upload( settings, user_id=user_id, scene=scene, content_type=content_type, data=data ) return ok(result.model_dump())