refactor: update message gateway handling and remove adapter references
- Changed the message sending method to return DeliveryBatch instead of DeliveryReceipt in integration.py. - Removed AdapterDeclarationPayload and related references from envelope.py, replacing them with MessageGatewayStateUpdatePayload and MessageGatewayStateUpdateResultPayload. - Updated runner_main.py to remove adapter-related logic and methods, focusing on message gateway functionality. - Added tests for message gateway runtime state synchronization and action bridge functionality in test files.
This commit is contained in:
@@ -6,8 +6,9 @@
|
||||
|
||||
from .manager import PlatformIOManager, get_platform_io_manager
|
||||
from .route_key_factory import RouteKeyFactory
|
||||
from .routing import RouteBindingConflictError, RouteTable
|
||||
from .routing import RouteTable
|
||||
from .types import (
|
||||
DeliveryBatch,
|
||||
DeliveryReceipt,
|
||||
DeliveryStatus,
|
||||
DriverDescriptor,
|
||||
@@ -15,10 +16,10 @@ from .types import (
|
||||
InboundMessageEnvelope,
|
||||
RouteBinding,
|
||||
RouteKey,
|
||||
RouteMode,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"DeliveryBatch",
|
||||
"DeliveryReceipt",
|
||||
"DeliveryStatus",
|
||||
"DriverDescriptor",
|
||||
@@ -27,9 +28,7 @@ __all__ = [
|
||||
"PlatformIOManager",
|
||||
"RouteKeyFactory",
|
||||
"RouteBinding",
|
||||
"RouteBindingConflictError",
|
||||
"RouteKey",
|
||||
"RouteMode",
|
||||
"RouteTable",
|
||||
"get_platform_io_manager",
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""提供 Platform IO 的插件适配器驱动实现。"""
|
||||
"""提供 Platform IO 的插件消息网关驱动实现。"""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional, Protocol
|
||||
|
||||
@@ -9,45 +9,49 @@ if TYPE_CHECKING:
|
||||
from src.chat.message_receive.message import SessionMessage
|
||||
|
||||
|
||||
class _AdapterSupervisorProtocol(Protocol):
|
||||
"""适配器驱动依赖的 Supervisor 最小协议。"""
|
||||
class _GatewaySupervisorProtocol(Protocol):
|
||||
"""消息网关驱动依赖的 Supervisor 最小协议。"""
|
||||
|
||||
async def invoke_adapter(
|
||||
async def invoke_message_gateway(
|
||||
self,
|
||||
plugin_id: str,
|
||||
method_name: str,
|
||||
component_name: str,
|
||||
args: Optional[Dict[str, Any]] = None,
|
||||
timeout_ms: int = 30000,
|
||||
) -> Any:
|
||||
"""调用适配器插件专用方法。"""
|
||||
"""调用插件声明的消息网关方法。"""
|
||||
|
||||
|
||||
class PluginPlatformDriver(PlatformIODriver):
|
||||
"""面向适配器插件链路的 Platform IO 驱动。"""
|
||||
"""面向插件消息网关链路的 Platform IO 驱动。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
driver_id: str,
|
||||
platform: str,
|
||||
supervisor: _AdapterSupervisorProtocol,
|
||||
send_method: str = "send_to_platform",
|
||||
supervisor: _GatewaySupervisorProtocol,
|
||||
component_name: str,
|
||||
*,
|
||||
supports_send: bool,
|
||||
account_id: Optional[str] = None,
|
||||
scope: Optional[str] = None,
|
||||
plugin_id: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""初始化一个插件适配器驱动。
|
||||
"""初始化一个插件消息网关驱动。
|
||||
|
||||
Args:
|
||||
driver_id: Broker 内的唯一驱动 ID。
|
||||
platform: 该适配器负责的平台名称。
|
||||
supervisor: 持有该适配器插件的 Supervisor。
|
||||
send_method: 出站发送时要调用的插件方法名。
|
||||
platform: 该消息网关负责的平台名称。
|
||||
supervisor: 持有该插件的 Supervisor。
|
||||
component_name: 出站时要调用的网关组件名称。
|
||||
supports_send: 当前驱动是否具备出站能力。
|
||||
account_id: 可选的账号 ID 或 self ID。
|
||||
scope: 可选的额外路由作用域。
|
||||
plugin_id: 拥有该适配器实现的插件 ID。
|
||||
plugin_id: 拥有该实现的插件 ID。
|
||||
metadata: 可选的额外驱动元数据。
|
||||
"""
|
||||
|
||||
descriptor = DriverDescriptor(
|
||||
driver_id=driver_id,
|
||||
kind=DriverKind.PLUGIN,
|
||||
@@ -59,7 +63,8 @@ class PluginPlatformDriver(PlatformIODriver):
|
||||
)
|
||||
super().__init__(descriptor)
|
||||
self._supervisor = supervisor
|
||||
self._send_method = send_method
|
||||
self._component_name = component_name
|
||||
self._supports_send = supports_send
|
||||
|
||||
async def send_message(
|
||||
self,
|
||||
@@ -67,16 +72,27 @@ class PluginPlatformDriver(PlatformIODriver):
|
||||
route_key: RouteKey,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> DeliveryReceipt:
|
||||
"""通过适配器插件发送消息。
|
||||
"""通过插件消息网关发送消息。
|
||||
|
||||
Args:
|
||||
message: 要投递的内部会话消息。
|
||||
route_key: Broker 为本次投递选择的路由键。
|
||||
metadata: 本次出站投递可选的 Broker 侧元数据。
|
||||
metadata: 可选的发送元数据。
|
||||
|
||||
Returns:
|
||||
DeliveryReceipt: 由驱动返回的规范化回执。
|
||||
DeliveryReceipt: 规范化后的发送回执。
|
||||
"""
|
||||
|
||||
if not self._supports_send:
|
||||
return DeliveryReceipt(
|
||||
internal_message_id=message.message_id,
|
||||
route_key=route_key,
|
||||
status=DeliveryStatus.FAILED,
|
||||
driver_id=self.driver_id,
|
||||
driver_kind=self.descriptor.kind,
|
||||
error="当前消息网关仅支持接收,不支持发送",
|
||||
)
|
||||
|
||||
from src.plugin_runtime.host.message_utils import PluginMessageUtils
|
||||
|
||||
plugin_id = self.descriptor.plugin_id or ""
|
||||
@@ -87,14 +103,14 @@ class PluginPlatformDriver(PlatformIODriver):
|
||||
status=DeliveryStatus.FAILED,
|
||||
driver_id=self.driver_id,
|
||||
driver_kind=self.descriptor.kind,
|
||||
error="插件适配器驱动缺少 plugin_id",
|
||||
error="插件消息网关驱动缺少 plugin_id",
|
||||
)
|
||||
|
||||
try:
|
||||
message_dict = PluginMessageUtils._session_message_to_dict(message)
|
||||
response = await self._supervisor.invoke_adapter(
|
||||
response = await self._supervisor.invoke_message_gateway(
|
||||
plugin_id=plugin_id,
|
||||
method_name=self._send_method,
|
||||
component_name=self._component_name,
|
||||
args={
|
||||
"message": message_dict,
|
||||
"route": {
|
||||
@@ -119,7 +135,7 @@ class PluginPlatformDriver(PlatformIODriver):
|
||||
return self._build_receipt(message.message_id, route_key, response)
|
||||
|
||||
def _build_receipt(self, internal_message_id: str, route_key: RouteKey, response: Any) -> DeliveryReceipt:
|
||||
"""将适配器调用响应归一化为出站回执。
|
||||
"""将网关调用响应归一化为出站回执。
|
||||
|
||||
Args:
|
||||
internal_message_id: 内部消息 ID。
|
||||
@@ -129,8 +145,9 @@ class PluginPlatformDriver(PlatformIODriver):
|
||||
Returns:
|
||||
DeliveryReceipt: 标准化后的出站回执。
|
||||
"""
|
||||
|
||||
if getattr(response, "error", None):
|
||||
error = response.error.get("message", "适配器发送失败")
|
||||
error = response.error.get("message", "消息网关发送失败")
|
||||
return DeliveryReceipt(
|
||||
internal_message_id=internal_message_id,
|
||||
route_key=route_key,
|
||||
@@ -149,7 +166,7 @@ class PluginPlatformDriver(PlatformIODriver):
|
||||
status=DeliveryStatus.FAILED,
|
||||
driver_id=self.driver_id,
|
||||
driver_kind=self.descriptor.kind,
|
||||
error=str(payload.get("result", "适配器发送失败")) if isinstance(payload, dict) else "适配器发送失败",
|
||||
error=str(payload.get("result", "消息网关发送失败")) if isinstance(payload, dict) else "消息网关发送失败",
|
||||
)
|
||||
|
||||
result = payload.get("result") if isinstance(payload, dict) else None
|
||||
@@ -161,7 +178,7 @@ class PluginPlatformDriver(PlatformIODriver):
|
||||
status=DeliveryStatus.FAILED,
|
||||
driver_id=self.driver_id,
|
||||
driver_kind=self.descriptor.kind,
|
||||
error=str(result.get("error", "适配器发送失败")),
|
||||
error=str(result.get("error", "消息网关发送失败")),
|
||||
metadata=result.get("metadata", {}) if isinstance(result.get("metadata"), dict) else {},
|
||||
)
|
||||
external_message_id = str(result.get("external_message_id") or result.get("message_id") or "") or None
|
||||
|
||||
@@ -10,7 +10,7 @@ from .outbound_tracker import OutboundTracker
|
||||
from .route_key_factory import RouteKeyFactory
|
||||
from .registry import DriverRegistry
|
||||
from .routing import RouteTable
|
||||
from .types import DeliveryReceipt, DeliveryStatus, InboundMessageEnvelope, RouteBinding, RouteKey
|
||||
from .types import DeliveryBatch, DeliveryReceipt, DeliveryStatus, InboundMessageEnvelope, RouteBinding, RouteKey
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.chat.message_receive.message import SessionMessage
|
||||
@@ -21,17 +21,21 @@ InboundDispatcher = Callable[[InboundMessageEnvelope], Awaitable[None]]
|
||||
|
||||
|
||||
class PlatformIOManager:
|
||||
"""统一协调双路径平台消息 IO 的路由、去重与状态跟踪。
|
||||
"""统一协调平台消息 IO 的路由、去重与状态跟踪。
|
||||
|
||||
这个管理器预期会成为 legacy 适配器链路与 plugin 适配器链路之间的
|
||||
唯一裁决点。当前地基阶段,它只提供共享状态和 Broker 侧契约,还没有
|
||||
真正把生产流量切到新中间层。
|
||||
与旧实现不同,这个管理器不再负责“多条链路谁该接管平台”的裁决,
|
||||
只维护发送表和接收表两张轻量路由表:
|
||||
|
||||
- 发送时:解析所有命中的发送绑定并全部投递。
|
||||
- 接收时:只校验当前驱动是否已登记为可接收链路,然后全部放行给上层。
|
||||
- 去重时:仅对单条链路做技术性重放抑制,不做跨链路语义去重。
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""初始化 Broker 管理器及其内存状态。"""
|
||||
self._driver_registry = DriverRegistry()
|
||||
self._route_table = RouteTable()
|
||||
self._send_route_table = RouteTable()
|
||||
self._receive_route_table = RouteTable()
|
||||
self._deduplicator = MessageDeduplicator()
|
||||
self._outbound_tracker = OutboundTracker()
|
||||
self._inbound_dispatcher: Optional[InboundDispatcher] = None
|
||||
@@ -152,13 +156,22 @@ class PlatformIOManager:
|
||||
return self._driver_registry
|
||||
|
||||
@property
|
||||
def route_table(self) -> RouteTable:
|
||||
"""返回管理器持有的路由绑定表。
|
||||
def send_route_table(self) -> RouteTable:
|
||||
"""返回发送路由表。"""
|
||||
|
||||
Returns:
|
||||
RouteTable: 用于归属解析的路由绑定表。
|
||||
"""
|
||||
return self._route_table
|
||||
return self._send_route_table
|
||||
|
||||
@property
|
||||
def receive_route_table(self) -> RouteTable:
|
||||
"""返回接收路由表。"""
|
||||
|
||||
return self._receive_route_table
|
||||
|
||||
@property
|
||||
def route_table(self) -> RouteTable:
|
||||
"""兼容旧接口,返回发送路由表。"""
|
||||
|
||||
return self._send_route_table
|
||||
|
||||
@property
|
||||
def deduplicator(self) -> MessageDeduplicator:
|
||||
@@ -257,15 +270,15 @@ class PlatformIOManager:
|
||||
return None
|
||||
|
||||
removed_driver.clear_inbound_handler()
|
||||
self._route_table.remove_bindings_by_driver(driver_id)
|
||||
self._send_route_table.remove_bindings_by_driver(driver_id)
|
||||
self._receive_route_table.remove_bindings_by_driver(driver_id)
|
||||
return removed_driver
|
||||
|
||||
def bind_route(self, binding: RouteBinding, *, replace: bool = False) -> None:
|
||||
"""为某个路由键绑定驱动。
|
||||
def bind_send_route(self, binding: RouteBinding) -> None:
|
||||
"""为某个路由键绑定发送驱动。
|
||||
|
||||
Args:
|
||||
binding: 要保存的路由绑定。
|
||||
replace: 是否允许替换已有的精确 active owner。
|
||||
|
||||
Raises:
|
||||
ValueError: 当绑定引用了不存在的驱动,或者绑定与驱动描述不一致时抛出。
|
||||
@@ -275,30 +288,78 @@ class PlatformIOManager:
|
||||
raise ValueError(f"驱动 {binding.driver_id} 未注册,无法绑定路由")
|
||||
|
||||
self._validate_binding_against_driver(binding, driver)
|
||||
self._route_table.bind(binding, replace=replace)
|
||||
self._send_route_table.bind(binding)
|
||||
|
||||
def unbind_route(self, route_key: RouteKey, driver_id: Optional[str] = None) -> None:
|
||||
"""移除一个或多个路由绑定。
|
||||
def bind_receive_route(self, binding: RouteBinding) -> None:
|
||||
"""为某个路由键绑定接收驱动。
|
||||
|
||||
Args:
|
||||
binding: 要保存的路由绑定。
|
||||
|
||||
Raises:
|
||||
ValueError: 当绑定引用了不存在的驱动,或者绑定与驱动描述不一致时抛出。
|
||||
"""
|
||||
driver = self._driver_registry.get(binding.driver_id)
|
||||
if driver is None:
|
||||
raise ValueError(f"驱动 {binding.driver_id} 未注册,无法绑定路由")
|
||||
|
||||
self._validate_binding_against_driver(binding, driver)
|
||||
self._receive_route_table.bind(binding)
|
||||
|
||||
def bind_route(self, binding: RouteBinding) -> None:
|
||||
"""兼容旧接口,默认同时绑定发送表和接收表。"""
|
||||
|
||||
self.bind_send_route(binding)
|
||||
self.bind_receive_route(binding)
|
||||
|
||||
def unbind_send_route(self, route_key: RouteKey, driver_id: Optional[str] = None) -> None:
|
||||
"""移除发送路由绑定。
|
||||
|
||||
Args:
|
||||
route_key: 要移除绑定的路由键。
|
||||
driver_id: 可选的特定驱动 ID。
|
||||
"""
|
||||
self._route_table.unbind(route_key, driver_id)
|
||||
|
||||
def resolve_driver(self, route_key: RouteKey) -> Optional[PlatformIODriver]:
|
||||
"""解析某个路由键当前的 active 驱动。
|
||||
self._send_route_table.unbind(route_key, driver_id)
|
||||
|
||||
def unbind_receive_route(self, route_key: RouteKey, driver_id: Optional[str] = None) -> None:
|
||||
"""移除接收路由绑定。
|
||||
|
||||
Args:
|
||||
route_key: 要移除绑定的路由键。
|
||||
driver_id: 可选的特定驱动 ID。
|
||||
"""
|
||||
|
||||
self._receive_route_table.unbind(route_key, driver_id)
|
||||
|
||||
def unbind_route(self, route_key: RouteKey, driver_id: Optional[str] = None) -> None:
|
||||
"""兼容旧接口,默认同时从发送表和接收表解绑。"""
|
||||
|
||||
self.unbind_send_route(route_key, driver_id)
|
||||
self.unbind_receive_route(route_key, driver_id)
|
||||
|
||||
def resolve_drivers(self, route_key: RouteKey) -> List[PlatformIODriver]:
|
||||
"""解析某个路由键当前命中的全部发送驱动。
|
||||
|
||||
Args:
|
||||
route_key: 要解析的路由键。
|
||||
|
||||
Returns:
|
||||
Optional[PlatformIODriver]: 若存在 active 驱动,则返回该驱动实例。
|
||||
List[PlatformIODriver]: 当前命中的全部发送驱动。
|
||||
"""
|
||||
active_binding = self._route_table.get_active_binding(route_key)
|
||||
if active_binding is None:
|
||||
return None
|
||||
return self._driver_registry.get(active_binding.driver_id)
|
||||
|
||||
drivers: List[PlatformIODriver] = []
|
||||
for binding in self._send_route_table.resolve_bindings(route_key):
|
||||
driver = self._driver_registry.get(binding.driver_id)
|
||||
if driver is not None:
|
||||
drivers.append(driver)
|
||||
return drivers
|
||||
|
||||
def resolve_driver(self, route_key: RouteKey) -> Optional[PlatformIODriver]:
|
||||
"""兼容旧接口,返回首个命中的发送驱动。"""
|
||||
|
||||
drivers = self.resolve_drivers(route_key)
|
||||
return drivers[0] if drivers else None
|
||||
|
||||
@staticmethod
|
||||
def build_route_key_from_message(message: "SessionMessage") -> RouteKey:
|
||||
@@ -335,9 +396,9 @@ class PlatformIOManager:
|
||||
否则返回 ``False``。
|
||||
"""
|
||||
|
||||
if not self._route_table.accepts_inbound(envelope.route_key, envelope.driver_id):
|
||||
if not self._receive_route_table.has_binding_for_driver(envelope.route_key, envelope.driver_id):
|
||||
logger.info(
|
||||
"忽略非 active owner 的入站消息: route=%s driver=%s",
|
||||
"忽略未登记到接收路由表的入站消息: route=%s driver=%s",
|
||||
envelope.route_key,
|
||||
envelope.driver_id,
|
||||
)
|
||||
@@ -361,8 +422,8 @@ class PlatformIOManager:
|
||||
message: "SessionMessage",
|
||||
route_key: RouteKey,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> DeliveryReceipt:
|
||||
"""通过 Broker 选中的驱动发送一条消息。
|
||||
) -> DeliveryBatch:
|
||||
"""通过 Broker 选中的全部发送驱动广播一条消息。
|
||||
|
||||
Args:
|
||||
message: 要投递的内部会话消息。
|
||||
@@ -370,61 +431,54 @@ class PlatformIOManager:
|
||||
metadata: 可选的额外 Broker 侧元数据。
|
||||
|
||||
Returns:
|
||||
DeliveryReceipt: 规范化后的出站回执。若路由不存在、驱动缺失,
|
||||
或同一消息已存在未完成的出站跟踪,也会返回失败回执而不是抛异常。
|
||||
DeliveryBatch: 规范化后的批量出站回执。
|
||||
"""
|
||||
drivers = self.resolve_drivers(route_key)
|
||||
if not drivers:
|
||||
return DeliveryBatch(internal_message_id=message.message_id, route_key=route_key)
|
||||
|
||||
active_binding = self._route_table.get_active_binding(route_key)
|
||||
if active_binding is None:
|
||||
return DeliveryReceipt(
|
||||
internal_message_id=message.message_id,
|
||||
route_key=route_key,
|
||||
status=DeliveryStatus.FAILED,
|
||||
error="未找到 active 路由绑定",
|
||||
)
|
||||
receipts: List[DeliveryReceipt] = []
|
||||
for driver in drivers:
|
||||
try:
|
||||
self._outbound_tracker.begin_tracking(
|
||||
internal_message_id=message.message_id,
|
||||
route_key=route_key,
|
||||
driver_id=driver.driver_id,
|
||||
metadata=metadata,
|
||||
)
|
||||
except ValueError as exc:
|
||||
receipts.append(
|
||||
DeliveryReceipt(
|
||||
internal_message_id=message.message_id,
|
||||
route_key=route_key,
|
||||
status=DeliveryStatus.FAILED,
|
||||
driver_id=driver.driver_id,
|
||||
driver_kind=driver.descriptor.kind,
|
||||
error=str(exc),
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
driver = self._driver_registry.get(active_binding.driver_id)
|
||||
if driver is None:
|
||||
return DeliveryReceipt(
|
||||
internal_message_id=message.message_id,
|
||||
route_key=route_key,
|
||||
status=DeliveryStatus.FAILED,
|
||||
driver_id=active_binding.driver_id,
|
||||
driver_kind=active_binding.driver_kind,
|
||||
error="active 路由绑定对应的驱动不存在",
|
||||
)
|
||||
try:
|
||||
receipt = await driver.send_message(message=message, route_key=route_key, metadata=metadata)
|
||||
except Exception as exc:
|
||||
receipt = DeliveryReceipt(
|
||||
internal_message_id=message.message_id,
|
||||
route_key=route_key,
|
||||
status=DeliveryStatus.FAILED,
|
||||
driver_id=driver.driver_id,
|
||||
driver_kind=driver.descriptor.kind,
|
||||
error=str(exc),
|
||||
)
|
||||
|
||||
try:
|
||||
self._outbound_tracker.begin_tracking(
|
||||
internal_message_id=message.message_id,
|
||||
route_key=route_key,
|
||||
driver_id=driver.driver_id,
|
||||
metadata=metadata,
|
||||
)
|
||||
except ValueError as exc:
|
||||
return DeliveryReceipt(
|
||||
internal_message_id=message.message_id,
|
||||
route_key=route_key,
|
||||
status=DeliveryStatus.FAILED,
|
||||
driver_id=driver.driver_id,
|
||||
driver_kind=driver.descriptor.kind,
|
||||
error=str(exc),
|
||||
)
|
||||
self._outbound_tracker.finish_tracking(receipt)
|
||||
receipts.append(receipt)
|
||||
|
||||
try:
|
||||
receipt = await driver.send_message(message=message, route_key=route_key, metadata=metadata)
|
||||
except Exception as exc:
|
||||
receipt = DeliveryReceipt(
|
||||
internal_message_id=message.message_id,
|
||||
route_key=route_key,
|
||||
status=DeliveryStatus.FAILED,
|
||||
driver_id=driver.driver_id,
|
||||
driver_kind=driver.descriptor.kind,
|
||||
error=str(exc),
|
||||
)
|
||||
|
||||
self._outbound_tracker.finish_tracking(receipt)
|
||||
return receipt
|
||||
return DeliveryBatch(
|
||||
internal_message_id=message.message_id,
|
||||
route_key=route_key,
|
||||
receipts=receipts,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _build_inbound_dedupe_key(envelope: InboundMessageEnvelope) -> Optional[str]:
|
||||
@@ -453,7 +507,7 @@ class PlatformIOManager:
|
||||
if not normalized_dedupe_key:
|
||||
return None
|
||||
|
||||
return f"{envelope.route_key.to_dedupe_scope()}:{normalized_dedupe_key}"
|
||||
return f"{envelope.driver_id}:{normalized_dedupe_key}"
|
||||
|
||||
@staticmethod
|
||||
def _validate_binding_against_driver(binding: RouteBinding, driver: PlatformIODriver) -> None:
|
||||
|
||||
@@ -1,52 +1,29 @@
|
||||
"""提供 Platform IO 的路由绑定存储与归属解析能力。"""
|
||||
"""提供 Platform IO 的轻量路由绑定表。"""
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from .types import RouteBinding, RouteKey, RouteMode
|
||||
|
||||
|
||||
class RouteBindingConflictError(ValueError):
|
||||
"""当同一路由键出现多个 active owner 竞争时抛出。"""
|
||||
from .types import RouteBinding, RouteKey
|
||||
|
||||
|
||||
class RouteTable:
|
||||
"""维护路由绑定并解析路由归属。
|
||||
"""维护单张路由绑定表。
|
||||
|
||||
这个表刻意保持轻量,只负责归属规则本身,不掺杂具体发送或接收逻辑。
|
||||
它决定某个路由键当前由哪个驱动 active 接管,哪些驱动仅以 shadow
|
||||
方式旁路观测。
|
||||
该实现不负责裁决“唯一 owner”,只负责保存绑定,并按
|
||||
``RouteKey.resolution_order()`` 解析出候选绑定列表。
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""初始化一个空的路由绑定表。"""
|
||||
"""初始化空路由绑定表。"""
|
||||
|
||||
self._bindings: Dict[RouteKey, Dict[str, RouteBinding]] = {}
|
||||
|
||||
def bind(self, binding: RouteBinding, *, replace: bool = False) -> None:
|
||||
def bind(self, binding: RouteBinding) -> None:
|
||||
"""注册或更新一条路由绑定。
|
||||
|
||||
Args:
|
||||
binding: 要注册的绑定对象。
|
||||
replace: 当精确路由键上已经存在 active owner 时,是否允许替换。
|
||||
|
||||
Raises:
|
||||
RouteBindingConflictError: 当精确路由键上已存在其他 active owner,
|
||||
且 ``replace`` 为 ``False`` 时抛出。
|
||||
binding: 要保存的路由绑定。
|
||||
"""
|
||||
|
||||
if binding.mode == RouteMode.DISABLED:
|
||||
self.unbind(binding.route_key, binding.driver_id)
|
||||
return
|
||||
|
||||
if binding.mode == RouteMode.ACTIVE:
|
||||
active_binding = self.get_active_binding(binding.route_key, exact_only=True)
|
||||
if active_binding and active_binding.driver_id != binding.driver_id:
|
||||
if not replace:
|
||||
raise RouteBindingConflictError(
|
||||
f"RouteKey {binding.route_key} 已由 {active_binding.driver_id} 接管,"
|
||||
f"拒绝绑定到 {binding.driver_id}"
|
||||
)
|
||||
self.unbind(binding.route_key, active_binding.driver_id)
|
||||
|
||||
self._bindings.setdefault(binding.route_key, {})[binding.driver_id] = binding
|
||||
|
||||
def unbind(self, route_key: RouteKey, driver_id: Optional[str] = None) -> List[RouteBinding]:
|
||||
@@ -54,7 +31,7 @@ class RouteTable:
|
||||
|
||||
Args:
|
||||
route_key: 要移除绑定的路由键。
|
||||
driver_id: 可选的特定驱动 ID;若为空,则移除该路由键上的全部绑定。
|
||||
driver_id: 可选的驱动 ID;为空时移除该路由键下全部绑定。
|
||||
|
||||
Returns:
|
||||
List[RouteBinding]: 被移除的绑定列表。
|
||||
@@ -67,15 +44,15 @@ class RouteTable:
|
||||
if driver_id is None:
|
||||
removed = list(binding_map.values())
|
||||
self._bindings.pop(route_key, None)
|
||||
return removed
|
||||
return self._sort_bindings(removed)
|
||||
|
||||
removed_binding = binding_map.pop(driver_id, None)
|
||||
if not binding_map:
|
||||
self._bindings.pop(route_key, None)
|
||||
return [removed_binding] if removed_binding else []
|
||||
return [removed_binding] if removed_binding is not None else []
|
||||
|
||||
def remove_bindings_by_driver(self, driver_id: str) -> List[RouteBinding]:
|
||||
"""移除某个驱动在所有路由键上的绑定。
|
||||
"""移除某个驱动在整张表上的全部绑定。
|
||||
|
||||
Args:
|
||||
driver_id: 要移除绑定的驱动 ID。
|
||||
@@ -83,9 +60,9 @@ class RouteTable:
|
||||
Returns:
|
||||
List[RouteBinding]: 被移除的绑定列表。
|
||||
"""
|
||||
|
||||
removed_bindings: List[RouteBinding] = []
|
||||
empty_route_keys: List[RouteKey] = []
|
||||
|
||||
for route_key, binding_map in self._bindings.items():
|
||||
removed_binding = binding_map.pop(driver_id, None)
|
||||
if removed_binding is not None:
|
||||
@@ -99,13 +76,13 @@ class RouteTable:
|
||||
return self._sort_bindings(removed_bindings)
|
||||
|
||||
def list_bindings(self, route_key: Optional[RouteKey] = None) -> List[RouteBinding]:
|
||||
"""列出当前绑定。
|
||||
"""列出当前路由表中的绑定。
|
||||
|
||||
Args:
|
||||
route_key: 可选的路由键过滤条件;若为空,则返回全部路由键上的绑定。
|
||||
route_key: 可选的路由键过滤条件。
|
||||
|
||||
Returns:
|
||||
List[RouteBinding]: 按优先级降序排列的绑定列表。
|
||||
List[RouteBinding]: 当前绑定列表。
|
||||
"""
|
||||
|
||||
if route_key is None:
|
||||
@@ -117,51 +94,38 @@ class RouteTable:
|
||||
binding_map = self._bindings.get(route_key, {})
|
||||
return self._sort_bindings(list(binding_map.values()))
|
||||
|
||||
def get_active_binding(self, route_key: RouteKey, *, exact_only: bool = False) -> Optional[RouteBinding]:
|
||||
"""获取某个路由键当前生效的 active 绑定。
|
||||
def resolve_bindings(self, route_key: RouteKey) -> List[RouteBinding]:
|
||||
"""按从具体到宽泛的顺序解析路由候选绑定。
|
||||
|
||||
Args:
|
||||
route_key: 要解析的路由键。
|
||||
exact_only: 是否只检查精确路由键而不做回退解析。
|
||||
route_key: 待解析的路由键。
|
||||
|
||||
Returns:
|
||||
Optional[RouteBinding]: 若存在 active owner,则返回对应绑定。
|
||||
List[RouteBinding]: 去重后的候选绑定列表。
|
||||
"""
|
||||
|
||||
candidate_keys = [route_key] if exact_only else route_key.resolution_order()
|
||||
for candidate_key in candidate_keys:
|
||||
binding_map = self._bindings.get(candidate_key, {})
|
||||
active_binding = self._pick_best_binding(binding_map, RouteMode.ACTIVE)
|
||||
if active_binding is not None:
|
||||
return active_binding
|
||||
return None
|
||||
resolved_bindings: List[RouteBinding] = []
|
||||
seen_driver_ids: set[str] = set()
|
||||
for candidate_key in route_key.resolution_order():
|
||||
for binding in self.list_bindings(candidate_key):
|
||||
if binding.driver_id in seen_driver_ids:
|
||||
continue
|
||||
seen_driver_ids.add(binding.driver_id)
|
||||
resolved_bindings.append(binding)
|
||||
return resolved_bindings
|
||||
|
||||
def get_shadow_bindings(self, route_key: RouteKey) -> List[RouteBinding]:
|
||||
"""获取某个精确路由键上的 shadow 绑定。
|
||||
def has_binding_for_driver(self, route_key: RouteKey, driver_id: str) -> bool:
|
||||
"""判断指定驱动是否在当前路由键解析结果中。
|
||||
|
||||
Args:
|
||||
route_key: 要查看的路由键。
|
||||
route_key: 待解析的路由键。
|
||||
driver_id: 目标驱动 ID。
|
||||
|
||||
Returns:
|
||||
List[RouteBinding]: 按优先级降序排列的 shadow 绑定列表。
|
||||
"""
|
||||
binding_map = self._bindings.get(route_key, {})
|
||||
shadow_bindings = [binding for binding in binding_map.values() if binding.mode == RouteMode.SHADOW]
|
||||
return self._sort_bindings(shadow_bindings)
|
||||
|
||||
def accepts_inbound(self, route_key: RouteKey, driver_id: str) -> bool:
|
||||
"""判断某个驱动是否是当前允许入 Core 的 active owner。
|
||||
|
||||
Args:
|
||||
route_key: 入站消息对应的路由键。
|
||||
driver_id: 希望将消息送入 Core 的驱动 ID。
|
||||
|
||||
Returns:
|
||||
bool: 若该驱动是解析结果中的 active owner,则返回 ``True``。
|
||||
bool: 若驱动存在于解析结果中则返回 ``True``。
|
||||
"""
|
||||
|
||||
active_binding = self.get_active_binding(route_key)
|
||||
return active_binding is not None and active_binding.driver_id == driver_id
|
||||
return any(binding.driver_id == driver_id for binding in self.resolve_bindings(route_key))
|
||||
|
||||
@staticmethod
|
||||
def _sort_bindings(bindings: List[RouteBinding]) -> List[RouteBinding]:
|
||||
@@ -173,30 +137,5 @@ class RouteTable:
|
||||
Returns:
|
||||
List[RouteBinding]: 排序后的绑定列表。
|
||||
"""
|
||||
|
||||
return sorted(bindings, key=lambda item: item.priority, reverse=True)
|
||||
|
||||
@staticmethod
|
||||
def _pick_best_binding(
|
||||
binding_map: Dict[str, RouteBinding],
|
||||
mode: RouteMode,
|
||||
) -> Optional[RouteBinding]:
|
||||
"""从绑定映射中挑选指定模式下优先级最高的一条绑定。
|
||||
|
||||
Args:
|
||||
binding_map: 某个精确 ``RouteKey`` 对应的绑定映射。
|
||||
mode: 需要挑选的绑定模式。
|
||||
|
||||
Returns:
|
||||
Optional[RouteBinding]: 若存在匹配模式的绑定,则返回优先级最高的一条。
|
||||
|
||||
Notes:
|
||||
这里使用单次线性扫描代替“先过滤成列表再排序”的做法,以减少
|
||||
高频路由解析路径上的临时对象分配和排序开销。
|
||||
"""
|
||||
best_binding: Optional[RouteBinding] = None
|
||||
for binding in binding_map.values():
|
||||
if binding.mode != mode:
|
||||
continue
|
||||
if best_binding is None or binding.priority > best_binding.priority:
|
||||
best_binding = binding
|
||||
return best_binding
|
||||
|
||||
@@ -19,14 +19,6 @@ class DriverKind(str, Enum):
|
||||
PLUGIN = "plugin"
|
||||
|
||||
|
||||
class RouteMode(str, Enum):
|
||||
"""路由归属模式枚举。"""
|
||||
|
||||
ACTIVE = "active"
|
||||
SHADOW = "shadow"
|
||||
DISABLED = "disabled"
|
||||
|
||||
|
||||
class DeliveryStatus(str, Enum):
|
||||
"""统一出站回执状态枚举。"""
|
||||
|
||||
@@ -158,21 +150,19 @@ class DriverDescriptor:
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RouteBinding:
|
||||
"""表示一条从路由键到驱动的归属绑定关系。
|
||||
"""表示一条从路由键到驱动的绑定关系。
|
||||
|
||||
Attributes:
|
||||
route_key: 该绑定覆盖的路由键。
|
||||
driver_id: 拥有或旁路观察该路由的驱动 ID。
|
||||
driver_id: 拥有该路由的驱动 ID。
|
||||
driver_kind: 绑定驱动的类型。
|
||||
mode: 绑定模式,例如 active owner 或 shadow observer。
|
||||
priority: 当同模式下存在多条绑定时使用的相对优先级。
|
||||
priority: 当同一路由键存在多条绑定时使用的相对优先级。
|
||||
metadata: 预留给未来路由策略的额外绑定元数据。
|
||||
"""
|
||||
|
||||
route_key: RouteKey
|
||||
driver_id: str
|
||||
driver_kind: DriverKind
|
||||
mode: RouteMode = RouteMode.ACTIVE
|
||||
priority: int = 0
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@@ -239,3 +229,36 @@ class DeliveryReceipt:
|
||||
external_message_id: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class DeliveryBatch:
|
||||
"""表示一次广播式出站投递的批量结果。
|
||||
|
||||
Attributes:
|
||||
internal_message_id: 内部消息 ID。
|
||||
route_key: 本次投递使用的路由键。
|
||||
receipts: 各条路由的独立投递回执列表。
|
||||
"""
|
||||
|
||||
internal_message_id: str
|
||||
route_key: RouteKey
|
||||
receipts: List[DeliveryReceipt] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def sent_receipts(self) -> List[DeliveryReceipt]:
|
||||
"""返回全部发送成功的回执。"""
|
||||
|
||||
return [receipt for receipt in self.receipts if receipt.status == DeliveryStatus.SENT]
|
||||
|
||||
@property
|
||||
def failed_receipts(self) -> List[DeliveryReceipt]:
|
||||
"""返回全部发送失败的回执。"""
|
||||
|
||||
return [receipt for receipt in self.receipts if receipt.status != DeliveryStatus.SENT]
|
||||
|
||||
@property
|
||||
def has_success(self) -> bool:
|
||||
"""返回当前批量投递是否至少命中一条成功回执。"""
|
||||
|
||||
return bool(self.sent_receipts)
|
||||
|
||||
Reference in New Issue
Block a user