# -*- coding: utf-8 -*- """ 通信中间件工厂 ───────────────────────────────────────────────────────────────── 支持通过 URL 自动路由到对应适配器: redis://[:password@]host[:port][/db][?options] kafka://[user:password@]host[:port][/group_id][?options] dds://[domain_id@]host[:port][/topic_prefix][?options] 扩展新协议只需: 1. 实现 CommBaseAdapter 子类 2. 调用 CommAdapterFactory.register("scheme", YourAdapter) ───────────────────────────────────────────────────────────────── """ import logging from typing import Type, Dict from urllib.parse import urlparse, parse_qs, unquote from uas.comm.base import CommBaseAdapter, ConnectionConfig logger = logging.getLogger(__name__) class CommAdapterFactory: """ 适配器工厂(注册表模式) 线程安全,支持运行时动态注册新协议 """ _registry: Dict[str, Type[CommBaseAdapter]] = {} # ── 注册 ────────────────────────────────────────────────────── @classmethod def register(cls, scheme: str, adapter_cls: Type[CommBaseAdapter]) -> None: """ 注册新的协议适配器 :param scheme: 协议名,如 "redis"、"kafka"、"mqtt" :param adapter_cls: CommBaseAdapter 子类 """ scheme = scheme.lower() if not issubclass(adapter_cls, CommBaseAdapter): raise TypeError(f"{adapter_cls} 必须继承自 CommBaseAdapter") cls._registry[scheme] = adapter_cls logger.info(f"✅ 已注册适配器: {scheme} -> {adapter_cls.__name__}") @classmethod def unregister(cls, scheme: str) -> None: cls._registry.pop(scheme.lower(), None) @classmethod def available_schemes(cls) -> list: return list(cls._registry.keys()) # ── 创建 ────────────────────────────────────────────────────── @classmethod def create(cls, url: str) -> CommBaseAdapter: """ 根据 URL 自动创建对应适配器实例 示例: redis://:mypassword@127.0.0.1:6379/0?socket_timeout=5 kafka://broker1:9092/my-group?auto_offset_reset=earliest dds://0@localhost:7400/rt?reliability=RELIABLE """ config = cls._parse_url(url) scheme = config.scheme if scheme not in cls._registry: raise ValueError( f"未知协议: '{scheme}',已注册: {cls.available_schemes()}" ) adapter_cls = cls._registry[scheme] logger.info(f"🔧 创建适配器: {adapter_cls.__name__} url={url!r}") return adapter_cls(config) # ── URL 解析 ────────────────────────────────────────────────── @classmethod def _parse_url(cls, url: str) -> ConnectionConfig: """ 解析通用 URL 为 ConnectionConfig 兼容以下格式: redis://[:password@]host[:port][/db] kafka://[user:pass@]host[:port][/group] dds://[domain@]host[:port][/prefix] """ parsed = urlparse(url) scheme = parsed.scheme.lower() # 默认端口映射 default_ports = { "redis": 6379, "kafka": 9092, "dds": 7400, "mqtt": 1883, "nats": 4222, "amqp": 5672, } host = parsed.hostname or "localhost" port = parsed.port or default_ports.get(scheme, 0) username = unquote(parsed.username or "") password = unquote(parsed.password or "") database = parsed.path.lstrip("/") if parsed.path else "" # 解析 query 参数(所有值展开为单值) raw_opts = parse_qs(parsed.query, keep_blank_values=True) options = {k: v[0] if len(v) == 1 else v for k, v in raw_opts.items()} config = ConnectionConfig( scheme = scheme, host = host, port = port, username = username, password = password, database = database, options = options, ) logger.debug(f"URL 解析结果: {config}") return config