import json import logging from typing import Optional, Dict, Any, List from .persistence import PersistenceBackend from .types import BindingStatus, BindingRecord logger = logging.getLogger(__name__) class BindingManager: """ BindingManager 负责维护不同 DataCollection 间节点的绑定关系。 所有查询均在内存中进行,持久化仅作为备份。 """ def __init__(self, persistence: PersistenceBackend, auto_persist: bool = True): self.persistence = persistence self.auto_persist = auto_persist # { node_type: { local_id: remote_id } } self._bindings: Dict[str, Dict[str, Optional[str]]] = {} self._deleted: Dict[str, set[str]] = {} def _get_type_bindings(self, node_type: str) -> Dict[str, Optional[str]]: if node_type not in self._bindings: self._bindings[node_type] = {} return self._bindings[node_type] def _get_deleted(self, node_type: str) -> set[str]: if node_type not in self._deleted: self._deleted[node_type] = set() return self._deleted[node_type] async def bind(self, node_type: str, local_id: str, remote_id: Optional[str], manual: bool = False): """ 建立本地节点与远程节点间的映射(带中文日志) Args: node_type: 节点类型 local_id: 本地节点 ID remote_id: 远程节点 ID manual: 是否为手工绑定(用于日志区分) """ bindings = self._get_type_bindings(node_type) bindings[local_id] = remote_id self._get_deleted(node_type).discard(local_id) # 记录日志:手工绑定保留 info,自动绑定降为 debug 避免日志过量 op_type = "手工" if manual else "自动" log_line = ( f"[{op_type}绑定] 节点类型={node_type}, " f"本地节点={local_id} <-> 远程节点={remote_id}" ) if manual: logger.info(log_line) else: logger.debug(log_line) if self.auto_persist: await self.persistence.save_binding(node_type, local_id, {"remote_id": remote_id}) async def unbind(self, node_type: str, local_id: str, manual: bool = False): """ 解除映射关系并物理删除(带中文日志) Args: node_type: 节点类型 local_id: 本地节点 ID manual: 是否为手工解绑(用于日志区分) """ bindings = self._get_type_bindings(node_type) remote_id = bindings.get(local_id) if local_id in bindings: del bindings[local_id] # 记录日志(中文,区分手工/自动) op_type = "手工" if manual else "自动" logger.info( f"[{op_type}解绑] 节点类型={node_type}, " f"本地节点={local_id} (原远程节点={remote_id})" ) if self.auto_persist: await self.persistence.delete_binding(node_type, local_id) else: self._get_deleted(node_type).add(local_id) async def get_remote_id(self, node_type: str, local_id: str) -> Optional[str]: """查询本地节点对应的远程 ID""" bindings = self._get_type_bindings(node_type) return bindings.get(local_id) async def get_local_id(self, node_type: str, remote_id: str) -> Optional[str]: """查询远程节点对应的本地 ID (内存遍历)""" bindings = self._get_type_bindings(node_type) for lid, rid in bindings.items(): if rid == remote_id: return lid return None async def get_record(self, node_type: str, local_id: str) -> Optional[BindingRecord]: """获取结构化绑定记录""" remote_id = self._get_type_bindings(node_type).get(local_id) if remote_id is None: return None return BindingRecord( local_id=local_id, remote_id=remote_id ) async def get_all_records(self, node_type: str) -> List[BindingRecord]: """获取指定类型的所有完整绑定记录 (结构化对象)""" bindings = self._get_type_bindings(node_type) return [BindingRecord(local_id=lid, remote_id=rid) for lid, rid in bindings.items()] async def get_all_bindings(self, node_type: str) -> List[tuple[str, Optional[str]]]: """获取指定类型的所有 ID 映射关系 (local_id, remote_id)""" bindings = self._get_type_bindings(node_type) return [(lid, rid) for lid, rid in bindings.items()] async def load_from_persistence(self, node_type: str): """从持久化层加载特定类型的绑定关系到内存""" raw_bindings = await self.persistence.load_bindings(node_type) bindings = self._get_type_bindings(node_type) self._get_deleted(node_type).clear() for rb in raw_bindings: payload = rb.get("payload") or {} bindings[rb["local_id"]] = payload.get("remote_id") async def persist(self) -> None: """将所有绑定关系写回持久化层""" if not self._bindings and not self._deleted: logger.info("🔍 [BindingManager.persist] 跳过:无绑定变更") return type_count = 0 saved_total = 0 deleted_total = 0 touched_types: List[str] = [] for node_type, bindings in self._bindings.items(): deleted = list(self._get_deleted(node_type)) binding_count = len(bindings) deleted_count = len(deleted) type_count += 1 saved_total += binding_count deleted_total += deleted_count if binding_count > 0 or deleted_count > 0: touched_types.append(node_type) if deleted: await self.persistence.delete_bindings_bulk(node_type, deleted) self._get_deleted(node_type).clear() if bindings: await self.persistence.save_bindings_bulk(node_type, bindings) touched_text = ",".join(touched_types) if touched_types else "none" logger.info( f"🔍 [BindingManager.persist] 完成: types={type_count}, saved={saved_total}, deleted={deleted_total}, touched=[{touched_text}]" )