122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
通信适配器抽象基类
|
|||
|
|
所有中间件适配器必须继承此类并实现全部抽象方法
|
|||
|
|
遵循 Factory + Strategy 模式
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import asyncio
|
|||
|
|
import logging
|
|||
|
|
from abc import ABC, abstractmethod
|
|||
|
|
from dataclasses import dataclass
|
|||
|
|
from typing import Callable, Awaitable, List, Optional
|
|||
|
|
|
|||
|
|
from uas.comm.message import Message
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
# 消息处理器类型:同步 or 异步均可
|
|||
|
|
MessageHandler = Callable[[Message], Optional[Awaitable[None]]]
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class ConnectionConfig:
|
|||
|
|
"""通用连接配置(由 URL 解析填充)"""
|
|||
|
|
scheme: str # redis / kafka / dds
|
|||
|
|
host: str = "localhost"
|
|||
|
|
port: int = 0
|
|||
|
|
username: str = ""
|
|||
|
|
password: str = ""
|
|||
|
|
database: str = "" # redis db index / kafka group id / dds domain
|
|||
|
|
options: dict = None # URL query 参数
|
|||
|
|
|
|||
|
|
def __post_init__(self):
|
|||
|
|
if self.options is None:
|
|||
|
|
self.options = {}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class CommBaseAdapter(ABC):
|
|||
|
|
"""
|
|||
|
|
所有通信适配器的抽象基类
|
|||
|
|
子类只需实现以下 5 个核心方法
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, config: ConnectionConfig):
|
|||
|
|
self.config = config
|
|||
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|||
|
|
self._running = False
|
|||
|
|
self._handlers: dict[str, List[MessageHandler]] = {}
|
|||
|
|
|
|||
|
|
# ── 生命周期 ──────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
async def connect(self) -> None:
|
|||
|
|
"""建立连接"""
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
async def disconnect(self) -> None:
|
|||
|
|
"""断开连接"""
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def is_connected(self) -> bool:
|
|||
|
|
return self._running
|
|||
|
|
|
|||
|
|
# ── 核心功能 ──────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
async def publish(self, message: Message) -> bool:
|
|||
|
|
"""
|
|||
|
|
发布消息到指定 topic
|
|||
|
|
:return: True 表示发送成功
|
|||
|
|
"""
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
async def subscribe(self, topic: str, handler: MessageHandler) -> None:
|
|||
|
|
"""
|
|||
|
|
订阅 topic,注册消息处理回调
|
|||
|
|
支持通配符(由各适配器自行实现)
|
|||
|
|
"""
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
async def unsubscribe(self, topic: str) -> None:
|
|||
|
|
"""取消订阅"""
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
# ── 可选扩展 ──────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
async def request(self, message: Message, timeout: float = 5.0) -> Optional[Message]:
|
|||
|
|
"""
|
|||
|
|
Request/Reply 模式(默认未实现,子类可覆盖)
|
|||
|
|
"""
|
|||
|
|
raise NotImplementedError(f"{self.__class__.__name__} 不支持 Request/Reply 模式")
|
|||
|
|
|
|||
|
|
async def health_check(self) -> bool:
|
|||
|
|
"""健康检查,默认返回连接状态"""
|
|||
|
|
return self._running
|
|||
|
|
|
|||
|
|
# ── 内部工具 ──────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def _register_handler(self, topic: str, handler: MessageHandler):
|
|||
|
|
self._handlers.setdefault(topic, []).append(handler)
|
|||
|
|
|
|||
|
|
async def _dispatch(self, message: Message):
|
|||
|
|
"""将消息分发给所有匹配的 handler"""
|
|||
|
|
handlers = self._handlers.get(message.topic, [])
|
|||
|
|
for handler in handlers:
|
|||
|
|
try:
|
|||
|
|
result = handler(message)
|
|||
|
|
if asyncio.iscoroutine(result):
|
|||
|
|
await result
|
|||
|
|
except Exception as e:
|
|||
|
|
self.logger.error(f"Handler 执行异常 topic={message.topic}: {e}", exc_info=True)
|
|||
|
|
|
|||
|
|
async def __aenter__(self):
|
|||
|
|
await self.connect()
|
|||
|
|
return self
|
|||
|
|
|
|||
|
|
async def __aexit__(self, *args):
|
|||
|
|
await self.disconnect()
|