feat: 重构数据库schema分离空间与业务配置 - 新增algorithm_registry和roi_algo_bind表 - roi_configs简化为纯空间配置 - 新增AlgorithmInfo/ROIAlgoBind数据模型

This commit is contained in:
2026-02-03 14:26:52 +08:00
parent 1caba41625
commit fa0304aa47
5 changed files with 942 additions and 185 deletions

View File

@@ -21,7 +21,7 @@ from redis.client import PubSub
from config.settings import get_settings, RedisConfig
from config.database import get_sqlite_manager, SQLiteManager
from config.config_models import CameraInfo as CameraInfoModel, ROIInfo, ConfigVersion
from config.config_models import CameraInfo as CameraInfoModel, ROIInfo, ConfigVersion, ROIInfoNew, ROIAlgoBind
from utils.version_control import get_version_control
logger = logging.getLogger(__name__)
@@ -282,7 +282,7 @@ class ConfigSyncManager:
def get_roi_configs(self, camera_id: Optional[str] = None,
force_refresh: bool = False) -> List[ROIInfo]:
"""获取ROI配置列表"""
"""获取ROI配置列表(兼容旧版本)"""
cache_key = f"rois_{camera_id}" if camera_id else "rois_all"
if not force_refresh:
@@ -312,6 +312,59 @@ class ConfigSyncManager:
cached = self._cache.get(cache_key)
return cached or []
def get_roi_configs_with_bindings(self, camera_id: Optional[str] = None,
force_refresh: bool = False) -> List[ROIInfoNew]:
"""获取ROI配置列表包含算法绑定信息"""
cache_key = f"rois_bindings_{camera_id}" if camera_id else "rois_bindings_all"
if not force_refresh:
cached = self._cache.get(cache_key)
if cached is not None:
return cached
self._init_database()
if self._db_manager is None:
logger.warning("数据库管理器不可用返回空ROI配置列表")
return []
try:
if camera_id:
roi_configs = self._db_manager.get_rois_by_camera(camera_id)
bindings_list = self._db_manager.get_bindings_by_camera(camera_id)
else:
roi_configs = self._db_manager.get_all_roi_configs()
bindings_list = []
for roi in roi_configs:
bindings = self._db_manager.get_bindings_by_roi(roi['roi_id'])
bindings_list.extend(bindings)
roi_dict = {r['roi_id']: r for r in roi_configs}
bindings_dict = {}
for b in bindings_list:
roi_id = b['roi_id']
if roi_id not in bindings_dict:
bindings_dict[roi_id] = []
bindings_dict[roi_id].append(b)
result = []
for roi_id, roi_data in roi_dict.items():
roi_info = ROIInfoNew.from_dict(roi_data)
if roi_id in bindings_dict:
roi_info.bindings = [ROIAlgoBind.from_dict(b) for b in bindings_dict[roi_id]]
result.append(roi_info)
result.sort(key=lambda x: x.priority, reverse=True)
self._cache.set(cache_key, result)
logger.info(f"已加载ROI配置含绑定: {len(result)}")
return result
except Exception as e:
logger.error(f"获取ROI配置含绑定失败: {e}")
cached = self._cache.get(cache_key)
return cached or []
def get_camera_rois(self, camera_id: str) -> List[ROIInfo]:
"""获取指定摄像头的ROI配置"""
return self.get_roi_configs(camera_id=camera_id)
@@ -397,13 +450,8 @@ class ConfigSyncManager:
"camera_id": roi_config.get("camera_id", ""),
"roi_type": roi_config.get("roi_type", ""),
"coordinates": str(roi_config.get("coordinates", [])),
"algorithm_type": roi_config.get("algorithm_type", ""),
"working_hours": str(roi_config.get("working_hours", [])),
"confirm_on_duty_sec": str(roi_config.get("confirm_on_duty_sec", 10)),
"confirm_leave_sec": str(roi_config.get("confirm_leave_sec", 10)),
"cooldown_sec": str(roi_config.get("cooldown_sec", 300)),
"target_class": roi_config.get("target_class", "person"),
"enabled": str(roi_config.get("enabled", True)),
"priority": str(roi_config.get("priority", 0)),
})
self._redis_client.expire(key, 3600)
@@ -413,6 +461,33 @@ class ConfigSyncManager:
logger.error(f"缓存ROI配置到Redis失败: {e}")
return False
def _cache_algo_bind_to_redis(self, bind_config: Dict[str, Any]) -> bool:
"""将ROI算法绑定配置缓存到Redis"""
if not self._redis_client:
return False
try:
bind_id = bind_config.get("bind_id")
key = f"config:bind:{bind_id}"
self._redis_client.hset(key, mapping={
"bind_id": bind_id,
"roi_id": bind_config.get("roi_id", ""),
"algo_code": bind_config.get("algo_code", ""),
"params": str(bind_config.get("params", {})),
"priority": str(bind_config.get("priority", 0)),
"enabled": str(bind_config.get("enabled", True)),
"algo_name": bind_config.get("algo_name", ""),
"target_class": bind_config.get("target_class", "person"),
})
self._redis_client.expire(key, 3600)
logger.debug(f"ROI算法绑定配置已缓存到Redis: {key}")
return True
except Exception as e:
logger.error(f"缓存ROI算法绑定配置到Redis失败: {e}")
return False
def _cache_camera_to_redis(self, camera_config: Dict[str, Any]) -> bool:
"""将摄像头配置缓存到Redis"""
if not self._redis_client:
@@ -454,6 +529,12 @@ class ConfigSyncManager:
if self._cache_roi_to_redis(roi):
count += 1
self.clear_redis_cache("bind")
bindings = self._db_manager.get_bindings_by_camera("")
for bind in bindings:
if self._cache_algo_bind_to_redis(bind):
count += 1
logger.info(f"已同步 {count} 条配置到Redis缓存")
return count
except Exception as e:
@@ -469,13 +550,8 @@ class ConfigSyncManager:
key = f"config:roi:{roi_id}"
data = self._redis_client.hgetall(key)
if data:
if data.get('coordinates'):
data['coordinates'] = eval(data['coordinates'])
if data.get('working_hours'):
data['working_hours'] = eval(data['working_hours'])
data['confirm_on_duty_sec'] = int(data.get('confirm_on_duty_sec', 10))
data['confirm_leave_sec'] = int(data.get('confirm_leave_sec', 10))
data['cooldown_sec'] = int(data.get('cooldown_sec', 300))
data['coordinates'] = eval(data['coordinates']) if data.get('coordinates') else []
data['priority'] = int(data.get('priority', 0))
data['enabled'] = data.get('enabled', 'True') == 'True'
return data
return None
@@ -483,6 +559,47 @@ class ConfigSyncManager:
logger.error(f"从Redis获取ROI配置失败: {e}")
return None
def get_algo_bind_from_redis(self, bind_id: str) -> Optional[Dict[str, Any]]:
"""从Redis获取ROI算法绑定配置"""
if not self._redis_client:
return None
try:
key = f"config:bind:{bind_id}"
data = self._redis_client.hgetall(key)
if data:
data['params'] = eval(data['params']) if data.get('params') else {}
data['priority'] = int(data.get('priority', 0))
data['enabled'] = data.get('enabled', 'True') == 'True'
data['target_class'] = data.get('target_class', 'person')
return data
return None
except Exception as e:
logger.error(f"从Redis获取ROI算法绑定配置失败: {e}")
return None
def get_bindings_from_redis(self, roi_id: str) -> List[Dict[str, Any]]:
"""从Redis获取ROI的所有算法绑定"""
if not self._redis_client:
return []
try:
pattern = "config:bind:*"
keys = self._redis_client.keys(pattern)
results = []
for key in keys:
data = self._redis_client.hgetall(key)
if data and data.get('roi_id') == roi_id:
data['params'] = eval(data['params']) if data.get('params') else {}
data['priority'] = int(data.get('priority', 0))
data['enabled'] = data.get('enabled', 'True') == 'True'
data['target_class'] = data.get('target_class', 'person')
results.append(data)
return sorted(results, key=lambda x: x['priority'], reverse=True)
except Exception as e:
logger.error(f"从Redis获取ROI算法绑定列表失败: {e}")
return []
def get_camera_from_redis(self, camera_id: str) -> Optional[Dict[str, Any]]:
"""从Redis获取摄像头配置"""
if not self._redis_client:
@@ -529,6 +646,10 @@ class ConfigSyncManager:
keys = self._redis_client.keys("config:camera:*")
if keys:
self._redis_client.delete(*keys)
elif config_type == "bind":
keys = self._redis_client.keys("config:bind:*")
if keys:
self._redis_client.delete(*keys)
else:
keys = self._redis_client.keys("config:*")
if keys:
@@ -543,6 +664,7 @@ class ConfigSyncManager:
self.clear_redis_cache()
count = self.sync_all_to_redis()
self.notify_config_change("roi", [])
self.notify_config_change("bind", [])
logger.info(f"算法配置已重新加载,更新了 {count} 条缓存")
def close(self):