From 66c80398894fe9ae81336892be961f6ae3c8970f Mon Sep 17 00:00:00 2001 From: 16337 <1633794139@qq.com> Date: Sat, 14 Feb 2026 11:25:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(config-sync):=20=E4=BF=AE=E5=A4=8D=E5=85=A8?= =?UTF-8?q?=E9=87=8F=E5=90=8C=E6=AD=A5=E6=97=B6=E7=A9=BA=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=B8=8D=E6=B8=85=E7=90=86=E6=97=A7=E6=95=B0=E6=8D=AE=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题描述: - 当sync_mode=full且incoming_ids为空时,条件判断失败 - 导致旧的孤儿ROI配置残留在本地数据库 - 后续配置更新时尝试启动孤儿ROI对应的摄像头,产生警告 根本原因: - line 889: if self._db_manager and incoming_ids: - 当incoming_ids为空列表时,条件判断为False - 跳过了清理旧配置的逻辑 修复方案: - 移除incoming_ids的条件判断 - 全量同步时始终执行清理逻辑 - incoming_ids为空时,清除所有旧配置(符合全量同步语义) - incoming_ids不为空时,清除不在列表中的旧配置 附加工具: - cleanup_orphan_rois.py: 清理当前残留的孤儿ROI记录 影响: - 修复配置同步逻辑bug - 避免孤儿ROI警告 - 提高配置同步的可靠性 Co-Authored-By: Claude Opus 4.6 --- cleanup_orphan_rois.py | 121 +++++++++++++++++++++++++++++++++++++++++ core/config_sync.py | 6 +- 2 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 cleanup_orphan_rois.py diff --git a/cleanup_orphan_rois.py b/cleanup_orphan_rois.py new file mode 100644 index 0000000..c4e2ada --- /dev/null +++ b/cleanup_orphan_rois.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +""" +清理孤儿ROI配置 +- 删除没有对应摄像头配置的ROI记录 +- 删除关联的算法绑定记录 +""" +import sys +from pathlib import Path +sys.path.insert(0, str(Path(__file__).parent)) + +from core.config_sync import get_config_sync_manager + +def main(): + print("=== Orphan ROI Cleanup Tool ===\n") + + config_manager = get_config_sync_manager() + + # 1. Get all cameras and ROIs + cameras = config_manager.get_cameras() + camera_ids = {c.camera_id for c in cameras} + + rois = config_manager.get_roi_configs(force_refresh=True) + binds = config_manager.get_bindings() + + print(f"Current Status:") + print(f" - Total cameras: {len(cameras)}") + print(f" - Total ROIs: {len(rois)}") + print(f" - Total bindings: {len(binds)}") + + # 2. Find orphan ROIs + orphan_rois = [roi for roi in rois if roi.camera_id not in camera_ids] + + if not orphan_rois: + print("\n[OK] No orphan ROIs found. Database is clean!") + return + + print(f"\n[PROBLEM] Found {len(orphan_rois)} orphan ROIs:") + for roi in orphan_rois: + print(f" - ROI ID: {roi.roi_id}") + print(f" Camera ID: {roi.camera_id} (MISSING)") + print(f" ROI Type: {roi.roi_type if hasattr(roi, 'roi_type') else 'N/A'}") + print() + + # 3. Find orphan bindings + orphan_roi_ids = {roi.roi_id for roi in orphan_rois} + orphan_binds = [b for b in binds if b.roi_id in orphan_roi_ids] + + if orphan_binds: + print(f"Related orphan bindings: {len(orphan_binds)}") + for bind in orphan_binds: + print(f" - Bind ID: {bind.bind_id}") + print(f" ROI ID: {bind.roi_id}") + print(f" Algorithm: {bind.algo_code}") + print() + + # 4. Ask for confirmation + print("=" * 50) + print("Cleanup Plan:") + print(f" 1. Delete {len(orphan_rois)} orphan ROI records") + print(f" 2. Delete {len(orphan_binds)} orphan binding records") + print("=" * 50) + + response = input("\nProceed with cleanup? (yes/no): ").strip().lower() + + if response != 'yes': + print("\n[CANCELLED] Cleanup aborted by user") + return + + # 5. Perform cleanup + print("\n[EXECUTING] Cleanup in progress...") + + # Initialize database manager + config_manager._init_database() + db_manager = config_manager._db_manager + + if not db_manager: + print("[ERROR] Database manager not available") + return + + deleted_rois = 0 + deleted_binds = 0 + + # Delete orphan bindings first (foreign key constraint) + for bind in orphan_binds: + try: + db_manager.delete_binding(bind.bind_id) + deleted_binds += 1 + print(f" [OK] Deleted binding: {bind.bind_id}") + except Exception as e: + print(f" [FAIL] Failed to delete binding {bind.bind_id}: {e}") + + # Delete orphan ROIs + for roi in orphan_rois: + try: + db_manager.delete_roi(roi.roi_id) + deleted_rois += 1 + print(f" [OK] Deleted ROI: {roi.roi_id}") + except Exception as e: + print(f" [FAIL] Failed to delete ROI {roi.roi_id}: {e}") + + # 6. Invalidate cache + config_manager.invalidate_all_cache() + + # 7. Summary + print("\n" + "=" * 50) + print("Cleanup Summary") + print("=" * 50) + print(f"Deleted ROIs: {deleted_rois}/{len(orphan_rois)}") + print(f"Deleted bindings: {deleted_binds}/{len(orphan_binds)}") + + if deleted_rois == len(orphan_rois) and deleted_binds == len(orphan_binds): + print("\n[SUCCESS] All orphan records cleaned up!") + print("\nNext steps:") + print(" 1. Restart the Edge service to apply changes") + print(" 2. Warnings should disappear from logs") + else: + print("\n[WARNING] Some records failed to delete") + print(" Please check error messages above") + +if __name__ == "__main__": + main() diff --git a/core/config_sync.py b/core/config_sync.py index d2e4f0c..df37cec 100644 --- a/core/config_sync.py +++ b/core/config_sync.py @@ -886,15 +886,17 @@ class ConfigSyncManager: sync_mode = config_data.get("sync_mode", "partial") if sync_mode == "full": self._init_database() - if self._db_manager and incoming_ids: + if self._db_manager: try: existing = self._db_manager.get_all_camera_configs() for cam in existing: old_id = cam.get("camera_id") + # 如果incoming_ids为空,清除所有旧配置 + # 如果incoming_ids不为空,清除不在列表中的旧配置 if old_id and old_id not in incoming_ids: self._clear_rois_for_camera_ids([old_id]) self._db_manager.delete_camera_config(old_id) - logger.info(f"[EDGE] 清除不在推送列表中的旧摄像头: {old_id}") + logger.info(f"[EDGE] 全量同步清除旧摄像头: {old_id}") except Exception as e: logger.warning(f"[EDGE] 清理旧摄像头失败: {e}") else: