feat: 实现配置热更新机制
数据库扩展: - roi_configs 新增算法参数字段(working_hours, confirm_on_duty_sec等) - 新增 config_update_log 表记录配置变更日志 Redis缓存: - ROI/摄像头配置缓存到 Redis(TTL 1小时) - sync_all_to_redis() 批量同步配置 - notify_config_change() 发布配置变更通知 热更新: - AlgorithmManager 订阅 Redis config_update 频道 - load_from_redis() 从 Redis 加载算法参数 - reload_algorithm() 热更新单个算法 - reload_all_algorithms() 重新加载所有算法 配置模型: - ROIInfo 添加算法参数字段
This commit is contained in:
@@ -383,6 +383,168 @@ class ConfigSyncManager:
|
||||
),
|
||||
}
|
||||
|
||||
def _cache_roi_to_redis(self, roi_config: Dict[str, Any]) -> bool:
|
||||
"""将ROI配置缓存到Redis"""
|
||||
if not self._redis_client:
|
||||
return False
|
||||
|
||||
try:
|
||||
roi_id = roi_config.get("roi_id")
|
||||
key = f"config:roi:{roi_id}"
|
||||
|
||||
self._redis_client.hset(key, mapping={
|
||||
"roi_id": roi_id,
|
||||
"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)),
|
||||
})
|
||||
|
||||
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:
|
||||
return False
|
||||
|
||||
try:
|
||||
camera_id = camera_config.get("camera_id")
|
||||
key = f"config:camera:{camera_id}"
|
||||
|
||||
self._redis_client.hset(key, mapping={
|
||||
"camera_id": camera_id,
|
||||
"rtsp_url": camera_config.get("rtsp_url", ""),
|
||||
"camera_name": camera_config.get("camera_name", ""),
|
||||
"enabled": str(camera_config.get("enabled", True)),
|
||||
"location": camera_config.get("location", ""),
|
||||
})
|
||||
|
||||
self._redis_client.expire(key, 3600)
|
||||
logger.debug(f"摄像头配置已缓存到Redis: {key}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"缓存摄像头配置到Redis失败: {e}")
|
||||
return False
|
||||
|
||||
def sync_all_to_redis(self) -> int:
|
||||
"""同步所有配置到Redis缓存"""
|
||||
if not self._redis_client:
|
||||
return 0
|
||||
|
||||
count = 0
|
||||
try:
|
||||
cameras = self._db_manager.get_all_camera_configs()
|
||||
for camera in cameras:
|
||||
if self._cache_camera_to_redis(camera):
|
||||
count += 1
|
||||
|
||||
rois = self._db_manager.get_all_roi_configs()
|
||||
for roi in rois:
|
||||
if self._cache_roi_to_redis(roi):
|
||||
count += 1
|
||||
|
||||
logger.info(f"已同步 {count} 条配置到Redis缓存")
|
||||
return count
|
||||
except Exception as e:
|
||||
logger.error(f"同步配置到Redis失败: {e}")
|
||||
return count
|
||||
|
||||
def get_roi_from_redis(self, roi_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""从Redis获取ROI配置"""
|
||||
if not self._redis_client:
|
||||
return None
|
||||
|
||||
try:
|
||||
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['enabled'] = data.get('enabled', 'True') == 'True'
|
||||
return data
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"从Redis获取ROI配置失败: {e}")
|
||||
return None
|
||||
|
||||
def get_camera_from_redis(self, camera_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""从Redis获取摄像头配置"""
|
||||
if not self._redis_client:
|
||||
return None
|
||||
|
||||
try:
|
||||
key = f"config:camera:{camera_id}"
|
||||
data = self._redis_client.hgetall(key)
|
||||
if data:
|
||||
data['enabled'] = data.get('enabled', 'True') == 'True'
|
||||
return data
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"从Redis获取摄像头配置失败: {e}")
|
||||
return None
|
||||
|
||||
def notify_config_change(self, config_type: str, config_ids: List[str]):
|
||||
"""通知配置变更(发布到Redis频道)"""
|
||||
if not self._redis_client:
|
||||
return
|
||||
|
||||
try:
|
||||
message = {
|
||||
"type": config_type,
|
||||
"ids": config_ids,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
}
|
||||
self._redis_client.publish("config_update", json.dumps(message))
|
||||
logger.info(f"已发布配置变更通知: {config_type} - {config_ids}")
|
||||
except Exception as e:
|
||||
logger.error(f"发布配置变更通知失败: {e}")
|
||||
|
||||
def clear_redis_cache(self, config_type: Optional[str] = None):
|
||||
"""清理Redis缓存"""
|
||||
if not self._redis_client:
|
||||
return
|
||||
|
||||
try:
|
||||
if config_type == "roi":
|
||||
keys = self._redis_client.keys("config:roi:*")
|
||||
if keys:
|
||||
self._redis_client.delete(*keys)
|
||||
elif config_type == "camera":
|
||||
keys = self._redis_client.keys("config:camera:*")
|
||||
if keys:
|
||||
self._redis_client.delete(*keys)
|
||||
else:
|
||||
keys = self._redis_client.keys("config:*")
|
||||
if keys:
|
||||
self._redis_client.delete(*keys)
|
||||
logger.info(f"已清理Redis缓存: {config_type or 'all'}")
|
||||
except Exception as e:
|
||||
logger.error(f"清理Redis缓存失败: {e}")
|
||||
|
||||
def reload_algorithms(self):
|
||||
"""重新加载所有算法配置"""
|
||||
self.invalidate_all_cache()
|
||||
self.clear_redis_cache()
|
||||
count = self.sync_all_to_redis()
|
||||
self.notify_config_change("roi", [])
|
||||
logger.info(f"算法配置已重新加载,更新了 {count} 条缓存")
|
||||
|
||||
def close(self):
|
||||
"""关闭管理器"""
|
||||
self.stop_config_subscription()
|
||||
|
||||
Reference in New Issue
Block a user