Files
ecm_sync_system/sync_state_machine/common/binding.py
T
strepsiades 4a9c444b10 first commit
2026-03-09 16:31:42 +08:00

162 lines
6.3 KiB
Python

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}]"
)