问题:
- 硬编码字段映射(gbName、name、app)
- 逻辑重复散落多处
- 格式写死无法配置
- 未基于数据库实际表结构
- 可扩展性差
重构方案:
1. 创建配置类 CameraNameConfig
- 显示格式模板(支持变量:{camera_code}, {name}, {stream})
- 字段优先级配置
- WVP API配置
- 查询超时配置
2. 创建服务类 CameraNameService
- 查询摄像头信息(get_camera_info)
- 提取名称字段(extract_name)
- 格式化显示名称(format_display_name)
- 一站式方法(get_display_name)
3. 重构路由层
- 移除硬编码逻辑
- 使用camera_name_service统一处理
- 删除旧的_get_camera_info函数
- 简化代码结构
架构优势:
- 配置驱动:格式通过环境变量控制
- 单一职责:服务只负责名称处理
- 可扩展:新增格式无需改代码
- 可测试:服务独立易于测试
- 模块化:逻辑集中便于维护
配置示例:
```bash
WVP_API_BASE=http://localhost:18080
CAMERA_NAME_FORMAT={camera_code} {name}/{stream}
CAMERA_QUERY_TIMEOUT=5
```
修改文件:
+ app/config.py - 添加CameraNameConfig配置
+ app/services/camera_name_service.py - 新建服务
+ docs/camera_name_config.md - 配置文档
~ app/routers/yudao_aiot_alarm.py - 使用新服务
测试结果:
- 告警列表: cam_1f0e3dad9990 → cam_1f0e3dad9990 大堂吧台3/012 ✓
- 设备汇总: cam_c51ce410c124 → cam_c51ce410c124 大堂吧台1/008 ✓
7.6 KiB
7.6 KiB
摄像头名称格式化配置
概述
摄像头名称格式化服务提供了灵活、可配置的方式来格式化摄像头显示名称。所有配置通过环境变量控制,无需修改代码即可调整显示格式。
架构设计
┌─────────────────────────────────────────────────────┐
│ config.py (CameraNameConfig) │
│ - 显示格式模板 │
│ - 字段优先级 │
│ - WVP API配置 │
└────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ camera_name_service.py (CameraNameService) │
│ - 查询摄像头信息 │
│ - 提取名称字段 │
│ - 格式化显示名称 │
└────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ yudao_aiot_alarm.py (路由层) │
│ - 告警列表 │
│ - 设备汇总 │
└─────────────────────────────────────────────────────┘
配置参数
环境变量
| 变量名 | 说明 | 默认值 | 示例 |
|---|---|---|---|
WVP_API_BASE |
WVP API基础URL | http://localhost:18080 |
http://192.168.1.100:18080 |
CAMERA_NAME_FORMAT |
显示格式模板 | {camera_code} {name}/{stream} |
{name} |
CAMERA_QUERY_TIMEOUT |
查询超时(秒) | 5 |
10 |
显示格式模板
支持以下变量:
| 变量 | 说明 | 示例值 |
|---|---|---|
{camera_code} |
摄像头编码 | cam_1f0e3dad9990 |
{name} |
摄像头名称(根据字段优先级提取) | 大堂吧台3 |
{stream} |
流ID | 012 |
常用格式示例
-
完整格式(默认):
CAMERA_NAME_FORMAT="{camera_code} {name}/{stream}" 结果:cam_1f0e3dad9990 大堂吧台3/012 -
仅名称+流ID:
CAMERA_NAME_FORMAT="{name}/{stream}" 结果:大堂吧台3/012 -
仅名称:
CAMERA_NAME_FORMAT="{name}" 结果:大堂吧台3 -
仅编码:
CAMERA_NAME_FORMAT="{camera_code}" 结果:cam_1f0e3dad9990 -
自定义分隔符:
CAMERA_NAME_FORMAT="{name} - {stream}" 结果:大堂吧台3 - 012
名称字段优先级
服务会按以下优先级从 StreamProxy 对象中提取名称:
- gbName(国标名称)- 自动去除 "/" 后缀
- app(应用名)
- stream(流ID)
此优先级在代码中硬编码,如需修改请编辑 app/services/camera_name_service.py:
self.name_field_priority = ["gbName", "app", "stream"]
使用方式
1. 在环境变量中配置
.env 文件:
WVP_API_BASE=http://192.168.0.104:18080
CAMERA_NAME_FORMAT={camera_code} {name}/{stream}
CAMERA_QUERY_TIMEOUT=5
2. 服务自动加载配置
from app.services.camera_name_service import get_camera_name_service
camera_service = get_camera_name_service()
display_name = await camera_service.get_display_name("cam_1f0e3dad9990")
# 返回: "cam_1f0e3dad9990 大堂吧台3/012"
3. 修改格式只需重启服务
# 修改环境变量
export CAMERA_NAME_FORMAT="{name}"
# 重启服务
systemctl restart service
API 示例
查询摄像头信息
camera_info = await camera_service.get_camera_info("cam_1f0e3dad9990")
# 返回: {
# "cameraCode": "cam_1f0e3dad9990",
# "app": "大堂吧台3",
# "stream": "012",
# "gbName": "大堂吧台3/",
# ...
# }
提取名称
name = camera_service.extract_name(camera_info)
# 返回: "大堂吧台3" (从 gbName 提取并去除 "/")
格式化显示名称
display_name = camera_service.format_display_name("cam_xxx", camera_info)
# 根据模板返回格式化结果
一站式查询
display_name = await camera_service.get_display_name("cam_1f0e3dad9990")
# 自动查询 + 格式化
扩展性
添加新的显示格式
只需修改环境变量即可:
# 添加前缀
CAMERA_NAME_FORMAT="[摄像头] {name}"
# 添加位置信息(需要在 WVP 中配置 gbAddress)
CAMERA_NAME_FORMAT="{name} ({gbAddress})"
如需使用新字段,需要修改 camera_name_service.py 中的 format_display_name 方法。
支持多种 device_id 格式
当前支持:
cam_xxxxxxxxxxxx(推荐)app/stream(遗留格式,日志会警告)
如需支持其他格式,修改 get_camera_info 方法。
缓存支持
如需添加缓存以减少 WVP 查询:
from functools import lru_cache
@lru_cache(maxsize=1000)
async def get_camera_info_cached(self, device_id: str):
return await self.get_camera_info(device_id)
或使用 Redis 缓存(需集成 Redis 服务)。
故障排查
1. 显示名称为 device_id(未格式化)
可能原因:
- WVP API 无法访问
- camera_code 不存在
- 摄像头信息字段缺失
检查方法:
# 测试 WVP API
curl "http://localhost:18080/api/ai/camera/get?cameraCode=cam_xxx"
# 查看服务日志
tail -f logs/app.log | grep "camera"
2. 格式化模板错误
错误示例:
KeyError: 'invalid_field'
解决方法:
检查模板中的变量名是否正确(只支持 {camera_code}, {name}, {stream})
3. 查询超时
错误日志:
WVP查询异常: camera_code=cam_xxx, error=Timeout
解决方法: 增加超时时间:
CAMERA_QUERY_TIMEOUT=10
最佳实践
- 使用 camera_code 格式:推荐在数据库中存储
camera_code而不是app/stream - 配置监控:监控 WVP API 可用性,查询失败时告警
- 缓存策略:高并发场景下添加缓存减少 WVP 负载
- 日志级别:生产环境设置 WARNING 级别,开发环境使用 INFO
- 格式统一:所有页面使用相同的
get_display_name方法,保证一致性
性能优化
并发查询
告警列表使用 asyncio.gather 并发查询多个摄像头:
alarm_list = await asyncio.gather(*[
_alarm_to_camel(a.to_dict(), current_user) for a in alarms
])
批量查询优化
如需批量查询,可以添加 get_display_names_batch 方法:
async def get_display_names_batch(self, device_ids: List[str]) -> Dict[str, str]:
"""批量查询摄像头显示名称"""
tasks = [self.get_display_name(device_id) for device_id in device_ids]
results = await asyncio.gather(*tasks)
return dict(zip(device_ids, results))
版本历史
- v1.0.0 (2026-02-24): 初始版本,支持配置化格式、字段优先级、WVP集成