ForcePilot/backend/package/yuxi/external_systems/integrations/registry.py
Kris 74709ba2d3 feat: 完成外部系统限界上下文核心代码实现
新增六边形架构核心代码包,包含:
1. 协议适配器层:HTTP/SMTP/IMAP/SSH/GRPC等多协议适配器实现
2. 认证插件体系:基础认证、API密钥、HMAC等多类型认证插件
3. 执行编排框架:工具执行器、上下文构建、运行时治理组件
4. 用例端口与DTO:定义领域服务端口与数据传输对象
5. 厂商集成包框架:支持第三方系统集成扩展
6. 基础设施装配层:实现依赖注入与服务装配

所有代码遵循六边形架构设计原则,实现端口与适配器解耦,支持动态扩展与自动发现。
2026-06-20 22:12:42 +08:00

133 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""IntegrationRegistry厂商集成目录注册表。
实现 ``core/contracts.py::IntegrationRegistry`` 端口(结构化子类型,无需显式继承)。
维护 ``key -> IntegrationMetadata`` 映射,应用启动时由各集成包 ``__init__.py``
通过 ``register()`` 自注册,运行时只读。元数据为内存态,不持久化到数据库。
``clear()`` 仅供测试隔离使用。
键冲突策略:同一 ``key`` 重复注册且元数据实例不同时抛 ``DomainValidationError``
与 framework 层 ``register_protocol_auth_plugin`` / ``http_error_extractor_registry``
等注册表策略一致,避免静默覆盖。
"""
from __future__ import annotations
from yuxi.external_systems.exceptions import DomainValidationError
from yuxi.external_systems.integrations.schemas import IntegrationMetadata
class IntegrationRegistry:
"""厂商集成目录注册表,实现 core/contracts.py::IntegrationRegistry 端口。
实现 Protocol 的 3 个查询方法list_integrations / get_integration /
list_adapter_types并额外提供 register / clear 供框架内部使用
(与 framework/registry/adapter_registry.py::AdapterRegistry 模式一致)。
"""
_integrations: dict[str, IntegrationMetadata] = {}
@classmethod
def register(cls, metadata: IntegrationMetadata) -> None:
"""注册厂商集成元数据。键冲突抛 DomainValidationError。"""
existing = cls._integrations.get(metadata.key)
if existing is not None and existing is not metadata:
raise DomainValidationError(
f"厂商集成元数据冲突: key={metadata.key} "
f"已注册 {existing.display_name},尝试注册 {metadata.display_name}"
)
cls._integrations[metadata.key] = metadata
@classmethod
def filter_integrations(
cls,
*,
adapter_type: str | None = None,
tags: list[str] | None = None,
keyword: str | None = None,
) -> list[IntegrationMetadata]:
"""按条件过滤并稳定排序厂商集成(不分页)。
list_integrations 与 count_integrations 的共用底层方法,
保证过滤逻辑单一来源避免两处实现漂移。overview / categories
端点需要全量过滤结果时也直接调用本方法。
- adapter_type: 精确匹配 metadata.adapter_type
- tags: OR 关系(命中任一标签即返回)
- keyword: 模糊匹配 display_name / description大小写不敏感
- 按 key 升序稳定排序,保证分页结果可重现
"""
items = list(cls._integrations.values())
if adapter_type is not None:
items = [it for it in items if it.adapter_type == adapter_type]
if tags:
tag_set = set(tags)
items = [it for it in items if tag_set.intersection(it.tags)]
if keyword:
kw = keyword.lower()
items = [it for it in items if kw in it.display_name.lower() or kw in it.description.lower()]
items.sort(key=lambda it: it.key)
return items
@classmethod
def list_integrations(
cls,
*,
adapter_type: str | None = None,
tags: list[str] | None = None,
keyword: str | None = None,
limit: int = 50,
offset: int = 0,
) -> list[IntegrationMetadata]:
"""查询厂商集成目录,支持按适配器类型/标签/关键词过滤与分页。
分页基于 filter_integrations 的全量结果切片,全量总数请用
count_integrations 获取list 返回的 len(items) 仅为当前页条数)。
"""
filtered = cls.filter_integrations(
adapter_type=adapter_type, tags=tags, keyword=keyword
)
if offset < 0:
offset = 0
if limit < 0:
limit = 0
return filtered[offset : offset + limit]
@classmethod
def count_integrations(
cls,
*,
adapter_type: str | None = None,
tags: list[str] | None = None,
keyword: str | None = None,
) -> int:
"""统计满足筛选条件的厂商集成全量总数(不分页)。
与 list_integrations 共用 filter_integrations保证 total 与
分页结果一致。供 Router 层返回真实全量 total避免前端分页页数错误。
"""
return len(
cls.filter_integrations(
adapter_type=adapter_type, tags=tags, keyword=keyword
)
)
@classmethod
def get_integration(cls, key: str) -> IntegrationMetadata | None:
"""按 key 获取厂商集成元数据,未找到返回 None。"""
return cls._integrations.get(key)
@classmethod
def list_adapter_types(cls) -> list[str]:
"""列出所有已注册厂商集成涉及的适配器类型(去重)。"""
seen: dict[str, None] = {}
for it in cls._integrations.values():
if it.adapter_type not in seen:
seen[it.adapter_type] = None
return list(seen.keys())
@classmethod
def clear(cls) -> None:
"""清空注册表,仅供测试使用。"""
cls._integrations.clear()