2026-02-24 13:59:13 +08:00
|
|
|
|
"""
|
2026-03-24 13:38:45 +08:00
|
|
|
|
摄像头名称服务
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
|
|
|
|
|
功能:
|
2026-03-24 13:38:45 +08:00
|
|
|
|
1. 从 WVP 查询摄像头信息(按 camera_code)
|
|
|
|
|
|
2. 固定优先级提取名称:cameraName → gbName → device_id
|
|
|
|
|
|
3. 批量查询和缓存优化
|
2026-02-24 13:59:13 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
2026-02-24 14:08:36 +08:00
|
|
|
|
from typing import Optional, Dict, List
|
2026-03-24 13:38:45 +08:00
|
|
|
|
import asyncio
|
2026-03-18 17:31:18 +08:00
|
|
|
|
import time
|
2026-02-24 13:59:13 +08:00
|
|
|
|
import httpx
|
|
|
|
|
|
from app.config import CameraNameConfig
|
|
|
|
|
|
from app.utils.logger import logger
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CameraNameService:
|
2026-03-18 17:31:18 +08:00
|
|
|
|
"""摄像头名称服务(带内存缓存)"""
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
2026-03-18 17:31:18 +08:00
|
|
|
|
# 缓存 TTL(秒)
|
|
|
|
|
|
CACHE_TTL = 300 # 5 分钟
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
2026-03-18 17:31:18 +08:00
|
|
|
|
def __init__(self, config: CameraNameConfig):
|
2026-02-24 13:59:13 +08:00
|
|
|
|
self.config = config
|
2026-03-18 17:31:18 +08:00
|
|
|
|
self._cache: Dict[str, tuple] = {} # {device_id: (info, expire_time)}
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
|
|
|
|
|
async def get_camera_info(self, device_id: str) -> Optional[Dict]:
|
2026-03-18 17:31:18 +08:00
|
|
|
|
"""从 WVP 查询摄像头信息(带缓存)"""
|
2026-03-24 13:38:45 +08:00
|
|
|
|
if not device_id:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
2026-03-18 17:31:18 +08:00
|
|
|
|
cached = self._cache.get(device_id)
|
|
|
|
|
|
if cached and cached[1] > time.time():
|
|
|
|
|
|
return cached[0]
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
2026-03-24 13:38:45 +08:00
|
|
|
|
info = await self._query_by_camera_code(device_id)
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
2026-03-18 17:31:18 +08:00
|
|
|
|
# 写入缓存(包括 None 结果,避免反复查询不存在的设备)
|
|
|
|
|
|
self._cache[device_id] = (info, time.time() + self.CACHE_TTL)
|
|
|
|
|
|
return info
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
|
|
|
|
|
async def _query_by_camera_code(self, camera_code: str) -> Optional[Dict]:
|
2026-03-24 13:38:45 +08:00
|
|
|
|
"""通过 camera_code 查询摄像头信息"""
|
2026-02-24 13:59:13 +08:00
|
|
|
|
try:
|
|
|
|
|
|
async with httpx.AsyncClient(timeout=self.config.query_timeout) as client:
|
|
|
|
|
|
resp = await client.get(
|
|
|
|
|
|
f"{self.config.wvp_api_base}/api/ai/camera/get",
|
|
|
|
|
|
params={"cameraCode": camera_code},
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if resp.status_code == 200:
|
|
|
|
|
|
data = resp.json()
|
|
|
|
|
|
if data.get("code") == 0:
|
|
|
|
|
|
return data.get("data")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.warning(
|
|
|
|
|
|
f"WVP查询摄像头失败: camera_code={camera_code}, "
|
|
|
|
|
|
f"status={resp.status_code}"
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"WVP查询异常: camera_code={camera_code}, error={e}")
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
2026-03-24 13:38:45 +08:00
|
|
|
|
def format_display_name(self, device_id: str, camera_info: Optional[Dict] = None) -> str:
|
2026-02-24 13:59:13 +08:00
|
|
|
|
"""
|
|
|
|
|
|
格式化摄像头显示名称
|
|
|
|
|
|
|
2026-03-24 13:38:45 +08:00
|
|
|
|
固定优先级:cameraName → gbName → device_id
|
2026-02-24 13:59:13 +08:00
|
|
|
|
"""
|
|
|
|
|
|
if not camera_info:
|
|
|
|
|
|
return device_id
|
|
|
|
|
|
|
2026-03-24 13:38:45 +08:00
|
|
|
|
# 1. cameraName(用户自定义名称)
|
|
|
|
|
|
name = camera_info.get("cameraName") or camera_info.get("camera_name")
|
|
|
|
|
|
if name and name.lower() != "default":
|
2026-02-24 14:08:36 +08:00
|
|
|
|
return name
|
|
|
|
|
|
|
2026-03-24 13:38:45 +08:00
|
|
|
|
# 2. gbName(国标名称,去 "/" 后缀)
|
|
|
|
|
|
name = camera_info.get("gbName") or camera_info.get("gb_name")
|
|
|
|
|
|
if name:
|
|
|
|
|
|
if "/" in name:
|
|
|
|
|
|
name = name.split("/")[0]
|
|
|
|
|
|
if name:
|
|
|
|
|
|
return name
|
2026-02-24 14:26:44 +08:00
|
|
|
|
|
2026-03-24 13:38:45 +08:00
|
|
|
|
# 3. 兜底
|
|
|
|
|
|
return device_id
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
2026-03-24 13:38:45 +08:00
|
|
|
|
async def get_camera_infos_batch(self, device_ids: List[str]) -> Dict[str, Optional[Dict]]:
|
|
|
|
|
|
"""批量查询摄像头信息(并发+去重优化)"""
|
|
|
|
|
|
unique_ids = list(set(did for did in device_ids if did))
|
|
|
|
|
|
if not unique_ids:
|
|
|
|
|
|
return {}
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
2026-03-24 13:38:45 +08:00
|
|
|
|
tasks = [self.get_camera_info(did) for did in unique_ids]
|
|
|
|
|
|
results = await asyncio.gather(*tasks)
|
|
|
|
|
|
return dict(zip(unique_ids, results))
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
2026-03-24 13:38:45 +08:00
|
|
|
|
async def get_display_name(self, device_id: str) -> str:
|
|
|
|
|
|
"""获取摄像头显示名称(一站式方法)"""
|
2026-02-24 13:59:13 +08:00
|
|
|
|
camera_info = await self.get_camera_info(device_id)
|
|
|
|
|
|
return self.format_display_name(device_id, camera_info)
|
|
|
|
|
|
|
2026-03-24 13:38:45 +08:00
|
|
|
|
async def get_display_names_batch(self, device_ids: List[str]) -> Dict[str, str]:
|
|
|
|
|
|
"""批量获取摄像头显示名称"""
|
2026-02-24 14:08:36 +08:00
|
|
|
|
info_map = await self.get_camera_infos_batch(device_ids)
|
|
|
|
|
|
return {
|
|
|
|
|
|
did: self.format_display_name(did, info_map.get(did))
|
|
|
|
|
|
for did in device_ids
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 13:59:13 +08:00
|
|
|
|
|
|
|
|
|
|
# 全局单例(依赖注入)
|
|
|
|
|
|
_camera_name_service: Optional[CameraNameService] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_camera_name_service() -> CameraNameService:
|
2026-03-24 13:38:45 +08:00
|
|
|
|
"""获取摄像头名称服务单例"""
|
2026-02-24 13:59:13 +08:00
|
|
|
|
global _camera_name_service
|
|
|
|
|
|
if _camera_name_service is None:
|
|
|
|
|
|
from app.config import settings
|
|
|
|
|
|
_camera_name_service = CameraNameService(settings.camera_name)
|
|
|
|
|
|
return _camera_name_service
|