本次提交包含多项代码改进与功能增强: 1. 新增服务账号禁用错误类型与状态映射 2. 优化出站管道阶段顺序与上下文字段 3. 完善会话与事务管理逻辑,修复并发冲突处理 4. 调整钉钉与微信插件的适配逻辑 5. 优化内容校验与限流降级策略 6. 新增渠道降级投递事件与围栏回滚机制 7. 修正配置schema与权限校验逻辑 8. 完善审计与监控相关的日志与钩子处理
203 lines
8.4 KiB
Python
203 lines
8.4 KiB
Python
"""钉钉渠道插件入口。
|
||
|
||
实现 ``CHANNEL_ENTRY`` 契约,注册 17 个适配器与生命周期钩子,返回
|
||
``PluginManifest``。由宿主在 loaded 阶段通过 ``importlib`` 导入并调用。
|
||
|
||
调用顺序(严格):
|
||
1. ``getXxxPort`` 获取被驱动端口实例(宿主已从 manifest.accessible_ports
|
||
加载权限,无需运行时 declareXxx)。
|
||
2. 实例化 ``DingTalkLifecycleHandler`` 与 ``DingTalkClient``。
|
||
3. 实例化并注册 17 个适配器(16 个列表追加 + 1 个 identity_resolver 单实例)。
|
||
4. ``registerLifecycleHandler`` 注册生命周期钩子(鸭子类型调用)。
|
||
5. 返回 ``PluginManifest``。
|
||
|
||
F-03 单真相源:本入口仅承接 discover 阶段解析的 ``ChannelManifest``,
|
||
不再重复构造声明性字段(capabilities / config_schema / accessible_ports /
|
||
injectable_pipelines / resource_quota / env_vars / credential_strategy 等),
|
||
所有声明性字段以 ``manifest.json`` 为唯一真相源。本函数仅负责适配器实例化、
|
||
client 实例化、LifecycleHandler 构造与 ``registerAdapter`` 调用。
|
||
|
||
per-account 适配器处理:``WizardAdapter`` 的 ``__init__`` 需
|
||
``account_id``,但 ``CHANNEL_ENTRY`` 在插件加载时不知具体账户,传入空字符串
|
||
占位(``_PLACEHOLDER_ACCOUNT_ID``)。``WhitelistAdapter`` 由协议方法传入
|
||
``account_id``,无需构造时绑定。
|
||
|
||
Stream WS 长连接管理:钉钉 Stream 连接由 ``StreamWorker`` 通过
|
||
``DingTalkStreamConnectorAdapter`` 统一管理(账号启用/禁用由
|
||
``ChannelAccountOnline``/``ChannelAccountOffline`` 领域事件触发),
|
||
``LifecycleAdapter`` 与 ``LifecycleHandler`` 不自管理 WS 连接(设计方案 §2.4.4)。
|
||
|
||
依赖方向:仅 import ``yuxi.channels.contract.*`` + 标准库 + 同插件内部模块,
|
||
不污染框架层。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from yuxi.channels.contract.plugin.entry import PluginHost
|
||
from yuxi.channels.contract.plugin.manifest import (
|
||
ChannelManifest,
|
||
PluginManifest,
|
||
)
|
||
|
||
from .adapters.command_adapter import DingTalkCommandAdapter
|
||
from .adapters.directory_adapter import DingTalkDirectoryAdapter
|
||
from .adapters.doctor_adapter import DingTalkDoctorAdapter
|
||
from .adapters.identity_resolver_adapter import DingTalkIdentityResolverAdapter
|
||
from .adapters.inbound_adapter import DingTalkInboundAdapter
|
||
from .adapters.lifecycle_adapter import DingTalkLifecycleAdapter
|
||
from .adapters.mention_adapter import DingTalkMentionAdapter
|
||
from .adapters.message_ops_adapter import DingTalkMessageOpsAdapter
|
||
from .adapters.outbound_adapter import DingTalkOutboundAdapter
|
||
from .adapters.probeable_adapter import DingTalkProbeableAdapter
|
||
from .adapters.rich_message_adapter import DingTalkRichMessageAdapter
|
||
from .adapters.session_adapter import DingTalkSessionAdapter
|
||
from .adapters.status_adapter import DingTalkStatusAdapter
|
||
from .adapters.stream_connector_adapter import DingTalkStreamConnectorAdapter
|
||
from .adapters.streaming_adapter import DingTalkStreamingAdapter
|
||
from .adapters.whitelist_adapter import DingTalkWhitelistAdapter
|
||
from .adapters.wizard_adapter import DingTalkWizardAdapter
|
||
from .dingtalk_client import DingTalkClient
|
||
from .lifecycle import DingTalkLifecycleHandler
|
||
|
||
# per-account 适配器占位 account_id:CHANNEL_ENTRY 在插件加载时不知具体账户,
|
||
# WizardAdapter 传入空字符串占位。WhitelistAdapter 由协议方法传入 account_id,
|
||
# 无需构造时绑定。
|
||
_PLACEHOLDER_ACCOUNT_ID = ""
|
||
|
||
# 已注册适配器类型列表(与 registerAdapter 调用顺序一致)。
|
||
# 此处为运行时适配器清单,非声明性字段,需与下方 registerAdapter 调用保持一致。
|
||
# 不注册 login_adapter(supports_qr_login=false,设计方案 §2.5.3)。
|
||
_ADAPTER_TYPES: tuple[str, ...] = (
|
||
"inbound",
|
||
"outbound",
|
||
"session",
|
||
"status",
|
||
"rich_message",
|
||
"streaming",
|
||
"mention",
|
||
"message_ops",
|
||
"directory",
|
||
"command",
|
||
"whitelist",
|
||
"wizard",
|
||
"doctor",
|
||
"lifecycle",
|
||
"probeable",
|
||
"identity_resolver",
|
||
"stream_connector",
|
||
)
|
||
|
||
|
||
def channel_entry(host: PluginHost, manifest: ChannelManifest) -> PluginManifest:
|
||
"""钉钉渠道插件入口。
|
||
|
||
由宿主在 loaded 阶段调用,完成适配器注册、生命周期钩子注册,
|
||
返回 ``PluginManifest``。
|
||
|
||
能力边界(accessible_ports / injectable_pipelines / resource_quota /
|
||
config_schema / env_vars / credential_strategy 等)由 discover 阶段
|
||
解析的 ``manifest`` 静态字段承载,宿主在加载期通过
|
||
``PluginCapabilityChecker.check`` 校验,运行时通过
|
||
``PluginHostImpl._checkPortAccess`` 从 manifest 读取并强制约束。本函数
|
||
不重复构造声明性字段(F-03 单真相源)。
|
||
|
||
参数:
|
||
host: 插件宿主,提供端口获取与扩展点注册 API。
|
||
manifest: 渠道清单,由 discover 阶段从 ``manifest.json`` 解析得到,
|
||
承载所有声明性字段。本函数复用其 ``config_schema`` 注入
|
||
``LifecycleHandler``,并直接作为返回 ``PluginManifest.manifest``。
|
||
"""
|
||
|
||
# 1. 获取端口实例(宿主已从 manifest.accessible_ports 加载权限)
|
||
config_port = host.getConfigPort()
|
||
logger_port = host.getLoggerPort()
|
||
cache_port = host.getCachePort()
|
||
persistence_port = host.getPersistencePort()
|
||
|
||
# 2. 实例化 DingTalkClient 与 LifecycleHandler
|
||
# 依赖关系为单向:handler 在 onInit 调 client.attach_http_client 注入
|
||
# 连接池,client 不引用 handler。先创建 client,再创建 handler 并通过
|
||
# 构造器注入。config_schema 取自 manifest(F-03 单真相源)。
|
||
client = DingTalkClient(config_port, cache_port, logger_port)
|
||
handler = DingTalkLifecycleHandler(
|
||
config_port,
|
||
logger_port,
|
||
cache_port,
|
||
manifest.config_schema,
|
||
client,
|
||
)
|
||
|
||
# 3. 实例化并注册 17 个适配器
|
||
# 16 个列表追加 + 1 个 identity_resolver 单实例赋值
|
||
host.registerAdapter(
|
||
"inbound",
|
||
DingTalkInboundAdapter(client, config_port, logger_port),
|
||
)
|
||
host.registerAdapter(
|
||
"outbound",
|
||
DingTalkOutboundAdapter(client, logger_port, cache_port, config_port),
|
||
)
|
||
host.registerAdapter("session", DingTalkSessionAdapter())
|
||
host.registerAdapter("status", DingTalkStatusAdapter(logger_port))
|
||
host.registerAdapter("rich_message", DingTalkRichMessageAdapter(logger_port))
|
||
host.registerAdapter(
|
||
"streaming",
|
||
DingTalkStreamingAdapter(client, logger_port, cache_port),
|
||
)
|
||
host.registerAdapter("mention", DingTalkMentionAdapter(config_port))
|
||
host.registerAdapter(
|
||
"message_ops",
|
||
DingTalkMessageOpsAdapter(client, logger_port, cache_port, config_port),
|
||
)
|
||
host.registerAdapter(
|
||
"directory",
|
||
DingTalkDirectoryAdapter(client, cache_port, config_port),
|
||
)
|
||
host.registerAdapter("command", DingTalkCommandAdapter(config_port))
|
||
host.registerAdapter(
|
||
"whitelist",
|
||
DingTalkWhitelistAdapter(
|
||
persistence_port,
|
||
logger_port,
|
||
),
|
||
)
|
||
host.registerAdapter(
|
||
"wizard",
|
||
DingTalkWizardAdapter(config_port, client, _PLACEHOLDER_ACCOUNT_ID),
|
||
)
|
||
host.registerAdapter("doctor", DingTalkDoctorAdapter(client, config_port))
|
||
host.registerAdapter(
|
||
"lifecycle",
|
||
DingTalkLifecycleAdapter(client, config_port, logger_port),
|
||
)
|
||
host.registerAdapter(
|
||
"probeable",
|
||
DingTalkProbeableAdapter(client, config_port, persistence_port),
|
||
)
|
||
# identity_resolver 为单实例注册(非列表追加)
|
||
host.registerAdapter(
|
||
"identity_resolver",
|
||
DingTalkIdentityResolverAdapter(client, cache_port, config_port),
|
||
)
|
||
# stream_connector:钉钉 Stream WS 长连接适配器,由 StreamWorker 管理连接生命周期。
|
||
# 复用 DingTalkClient.get_stream_endpoint 获取 WS 端点,避免重复的 HTTP 调用
|
||
# 与错误翻译逻辑。
|
||
host.registerAdapter(
|
||
"stream_connector",
|
||
DingTalkStreamConnectorAdapter(client, persistence_port, logger_port),
|
||
)
|
||
|
||
# 4. 注册生命周期钩子
|
||
# PluginHost Protocol 未声明 registerLifecycleHandler,但 PluginHostImpl
|
||
# 已实现,运行时通过鸭子类型调用。
|
||
host.registerLifecycleHandler(handler)
|
||
|
||
# 5. 返回 PluginManifest(manifest 直接复用入参,adapters 为运行时清单)
|
||
return PluginManifest(
|
||
manifest=manifest,
|
||
adapters=_ADAPTER_TYPES,
|
||
)
|
||
|
||
|
||
CHANNEL_ENTRY = channel_entry
|