Files
FishServer/fish_api/app/routers/biomass.py
zaiun xu c1aafc69bf 验收1
2026-04-13 17:13:02 +08:00

167 lines
5.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.
from __future__ import annotations
from typing import Optional
from fastapi import APIRouter, Depends, Header, Query
from starlette.responses import JSONResponse
from app.db import normalize_client_id, pop_next_health, pop_next_measure
from app.services.water_video import get_water_video_public_url
from app.settings import Settings, get_settings
router = APIRouter(prefix="/api/v1/biomass", tags=["biomass"])
# 是否有新快照被本次 GET 消费1/0body 保持与客户端约定字段一致,不写入 has_new。
HEADER_BIOMASS_NEW = "X-Fish-Biomass-New"
def _new_headers(has_new: bool) -> dict[str, str]:
return {HEADER_BIOMASS_NEW: "1" if has_new else "0"}
def _resolve_client_id(
x_fish_client_id: Optional[str] = Header(None, alias="X-Fish-Client-Id"),
client_id: Optional[str] = Query(
None,
description="客户端标识;与请求头 X-Fish-Client-Id 二选一(优先头)。未带时共用 default 游标",
),
) -> str:
if x_fish_client_id is not None and str(x_fish_client_id).strip():
return normalize_client_id(x_fish_client_id)
if client_id is not None and str(client_id).strip():
return normalize_client_id(client_id)
return normalize_client_id(None)
@router.get("/real/camera/")
async def get_real_camera(
settings: Settings = Depends(get_settings),
client_id: str = Depends(_resolve_client_id),
):
"""双目实时结果:每次 GET 投递该客户端下一条未消费的 FishMeasure 快照(按 client_id 独立游标)。"""
m, has_new, _ = pop_next_measure(settings, client_id)
if not has_new:
return JSONResponse(
content={
"code": 200,
"msg": "成功",
"data": {
"result": [],
"video_left": "",
"video_right": "",
},
},
headers=_new_headers(False),
)
if m.error:
return JSONResponse(
content={
"code": 500,
"msg": m.error,
"data": {
"result": [],
"video_left": "",
"video_right": "",
},
},
headers=_new_headers(True),
)
return JSONResponse(
content={
"code": 200,
"msg": "成功",
"data": {
"result": m.result,
"video_left": m.video_left,
"video_right": m.video_right,
},
},
headers=_new_headers(True),
)
@router.get("/health/result/")
async def get_health_result(
settings: Settings = Depends(get_settings),
client_id: str = Depends(_resolve_client_id),
):
"""行为 / 健康结果:每次 GET 投递该客户端下一条未消费的 FishAction 快照(按 client_id 独立游标)。
每个视频切片被视为独立的视频,会分别投递。
"""
h, has_new, _ = pop_next_health(settings, client_id)
if not has_new:
return JSONResponse(
content={
"code": 200,
"msg": "成功",
"data": {
"behavior_result": "",
"health_result": "",
},
},
headers=_new_headers(False),
)
if h.error:
return JSONResponse(
content={
"code": 500,
"msg": h.error,
"data": {
"behavior_result": "",
"health_result": "",
},
},
headers=_new_headers(True),
)
return JSONResponse(
content={
"code": 200,
"msg": "成功",
"data": {
"behavior_result": h.behavior_result,
"health_result": h.health_result,
},
},
headers=_new_headers(True),
)
@router.get("/water/video/")
async def get_water_video(
settings: Settings = Depends(get_settings),
client_id: str = Depends(_resolve_client_id),
):
"""水上视频FishAction 输入 mp4 经 H.264 转码后托管在 /media/,返回 `video_path` 绝对 URL。
如果视频较长超过15秒会自动切分为多个10秒的片段。
每个切片被视为独立的视频每次调用返回一个切片的URL按顺序轮流返回
对齐机制:使用 client_id 参数(请求头 X-Fish-Client-Id 或查询参数 client_id
确保与 /health/result/ 端点对齐返回同一切片。
"""
video_path = await get_water_video_public_url(settings, client_id)
return JSONResponse(
content={
"code": 200,
"msg": "成功",
"data": {
"video_path": video_path,
},
},
)
@router.get("/sonar/video/")
async def get_sonar_video():
"""声纳图像信息:当前返回空 `video_path`;后续将提供 H.264 编码 MP4 的绝对 URL。"""
return JSONResponse(
content={
"code": 200,
"msg": "成功",
"data": {
"video_path": "",
},
},
)