本次提交包含了大量跨模块的功能增强、bug修复与代码优化: 1. 清理冗余空行与导入格式,统一代码风格 2. 新增集成超时异常类,替换原生TimeoutError避免穿透核心层 3. 扩展审计日志DTO与用例,新增变更字段追踪能力 4. 完善各类仓储接口,新增批量操作、时间范围过滤支持 5. 重构健康检查返回格式,统一使用status字段替代冗余的reachable/healthy 6. 扩展仪表盘与各类服务端口,新增待办统计、系统dashboard等能力 7. 优化批量操作与事务处理,新增保存点支持隔离失败操作 8. 修复配额管理阈值计算逻辑,支持自定义告警阈值 9. 统一认证类型错误提示,优化插件注册校验逻辑 10. 扩展SOAP适配器预览能力,兼容bytes类型输入 11. 新增批量恢复、批量硬删除回收站资源的接口与DTO 12. 优化测试用例断言逻辑,兼容前端操作符别名与字段名差异
147 lines
5.9 KiB
Python
147 lines
5.9 KiB
Python
"""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, EntityNotFoundError
|
||
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: 模糊匹配 key / 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 or ())]
|
||
if keyword:
|
||
kw = keyword.lower()
|
||
items = [
|
||
it
|
||
for it in items
|
||
if kw in it.key.lower() or kw in (it.display_name or "").lower() or kw in (it.description or "").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 get_integration_or_raise(cls, key: str) -> IntegrationMetadata:
|
||
"""按 key 获取厂商集成元数据,未找到抛 ``EntityNotFoundError`` (404)。
|
||
|
||
供 Router 的详情类端点使用,避免每个端点重复 None 判断与抛异常逻辑。
|
||
"""
|
||
metadata = cls._integrations.get(key)
|
||
if metadata is None:
|
||
raise EntityNotFoundError(f"厂商集成 {key} 不存在")
|
||
return metadata
|
||
|
||
@classmethod
|
||
def list_adapter_types(cls) -> list[str]:
|
||
"""列出所有已注册厂商集成涉及的适配器类型(去重,按字母升序排序)。
|
||
|
||
与 ``filter_integrations``(按 key 排序)和 ``list_operations``(按字母
|
||
排序)保持一致的排序契约:所有 list_* 方法均返回排序后的结果,
|
||
调用方无需重复排序。
|
||
"""
|
||
seen: dict[str, None] = {}
|
||
for it in cls._integrations.values():
|
||
if it.adapter_type not in seen:
|
||
seen[it.adapter_type] = None
|
||
return sorted(seen.keys())
|
||
|
||
@classmethod
|
||
def clear(cls) -> None:
|
||
"""清空注册表,仅供测试使用。"""
|
||
cls._integrations.clear()
|