1. 将transport_mode改为pull以通过manifest校验,实际仅使用Webhook接收消息 2. 统一处理XML payload兼容raw_body包装格式 3. 重构配置读取逻辑,使用CHANNEL_TARGET常量替换硬编码target 4. 移除冗余的webhook_url配置诊断项,更新文档说明 5. 优化代码格式与导入顺序
179 lines
9.0 KiB
Python
179 lines
9.0 KiB
Python
"""微信公众号(wechat_mp)渠道插件入口。
|
||
|
||
实现 ``CHANNEL_ENTRY`` 契约,注册 11 个适配器与生命周期钩子,返回
|
||
``PluginManifest``。由宿主在 loaded 阶段通过 ``importlib`` 导入并调用。
|
||
|
||
调用顺序(严格,违反顺序将触发 ``PermissionDeniedError``):
|
||
1. 获取端口实例(仅在 manifest ``accessible_ports`` 声明集合内取用,
|
||
宿主在 ``PluginHostImpl._checkPortAccess`` 中校验)。
|
||
2. 实例化 ``WeChatMpClient`` 与 ``WeChatMpLifecycleHandler``。
|
||
3. 注册 11 个适配器(inbound / outbound / rich_message / session /
|
||
status / lifecycle / probeable / doctor / wizard / whitelist /
|
||
directory)。
|
||
4. 注册生命周期钩子(鸭子类型调用)。
|
||
5. 返回 ``PluginManifest``(能力边界由 manifest 字段静态声明,宿主在加载
|
||
期通过 ``PluginCapabilityChecker.check`` 校验)。
|
||
|
||
F-03 单真相源:本入口仅承接 discover 阶段解析的 ``ChannelManifest``,
|
||
不再重复构造声明性字段(capabilities / config_schema / accessible_ports /
|
||
injectable_pipelines / resource_quota / env_vars / credential_strategy 等),
|
||
所有声明性字段以 ``manifest.json`` 为唯一真相源。本函数仅负责适配器实例化、
|
||
client 实例化、LifecycleHandler 构造与 ``registerAdapter`` 调用。
|
||
|
||
适配器 DI 说明:
|
||
- ``inbound`` 接收 ``(client, config_port, logger_port)``:需从 ConfigPort
|
||
读取 ``encrypt_mode`` / ``encoding_aes_key`` / ``token`` 用于入站消息
|
||
解密与签名校验。
|
||
- ``outbound`` 接收 ``(client, config_port, logger_port)``:需从 ConfigPort
|
||
CHANNEL 作用域读取 ``max_message_length`` 用于长文本分片。
|
||
- ``rich_message`` 接收 ``(logger_port)``:纯协议转换,将 ``RichMessage``
|
||
DTO 渲染为微信公众号 news 图文消息 JSON。
|
||
- ``session`` / ``status`` / ``wizard`` 接收 ``(logger_port)``:仅做协议
|
||
转换,所有数据从 raw_event.payload 或入参提取。
|
||
- ``lifecycle`` 接收 ``(client, config_port, cache_port, logger_port)``:
|
||
账户配置写入后触发 access_token 预取,删除/禁用时清理 CachePort 缓存。
|
||
- ``probeable`` 接收 ``(client, persistence_port, logger_port)``:需动态
|
||
发现 account_id 调用 ``get_callback_ip`` 探活(契约 ``probe()`` 无参)。
|
||
- ``doctor`` 接收 ``(client, logger_port)``:调用 ``get_callback_ip`` /
|
||
``get_user_list`` 执行诊断。
|
||
- ``whitelist`` 接收 ``(cache_port, logger_port)``:白名单数据本地存储于
|
||
CachePort,不调用微信 API。
|
||
- ``directory`` 接收 ``(client, cache_port, logger_port)``:调用
|
||
``get_user_list`` / ``get_user_info`` 查询关注者,用户信息缓存于 CachePort。
|
||
|
||
传输管理:wechat_mp 为纯 Webhook 接入渠道,不注册 ``PullerAdapter`` /
|
||
``StreamConnectorAdapter``(决策 3)。manifest 声明 ``transport_mode: "pull"``
|
||
以通过 ``manifest_loader`` 校验(仅接受 ``pull``/``stream``/``both``),
|
||
实际消息接收通过 Webhook 端点 ``POST /channels/wechat_mp/webhook`` 由
|
||
``webhook_router`` → ``InboundMessageService.receiveWebhook`` 进入入站
|
||
管道,端到端延迟 ≤5s(5 秒被动回复策略)。``TransportManager`` 找不到
|
||
puller/stream_connector 适配器时不启动传输任务,不影响 Webhook 接入。
|
||
|
||
依赖方向:仅 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 ._constants import DEFAULT_MAX_CONNECTIONS
|
||
from .adapters.directory_adapter import WeChatMpDirectoryAdapter
|
||
from .adapters.doctor_adapter import WeChatMpDoctorAdapter
|
||
from .adapters.inbound_adapter import WeChatMpInboundAdapter
|
||
from .adapters.lifecycle_adapter import WeChatMpLifecycleAdapter
|
||
from .adapters.outbound_adapter import WeChatMpOutboundAdapter
|
||
from .adapters.probeable_adapter import WeChatMpProbeableAdapter
|
||
from .adapters.rich_message_adapter import WeChatMpRichMessageAdapter
|
||
from .adapters.session_adapter import WeChatMpSessionAdapter
|
||
from .adapters.status_adapter import WeChatMpStatusAdapter
|
||
from .adapters.whitelist_adapter import WeChatMpWhitelistAdapter
|
||
from .adapters.wizard_adapter import WeChatMpWizardAdapter
|
||
from .lifecycle import WeChatMpLifecycleHandler
|
||
from .wechat_mp_client import WeChatMpClient
|
||
|
||
# 已注册适配器类型列表(与 registerAdapter 调用顺序一致)。
|
||
# 此处为运行时适配器清单,非声明性字段,需与下方 registerAdapter 调用保持一致。
|
||
_ADAPTER_TYPES: tuple[str, ...] = (
|
||
"inbound",
|
||
"outbound",
|
||
"rich_message",
|
||
"session",
|
||
"status",
|
||
"lifecycle",
|
||
"probeable",
|
||
"doctor",
|
||
"wizard",
|
||
"whitelist",
|
||
"directory",
|
||
)
|
||
|
||
|
||
def channel_entry(host: PluginHost, manifest: ChannelManifest) -> PluginManifest:
|
||
"""微信公众号 wechat_mp 渠道插件入口。
|
||
|
||
由宿主在 loaded 阶段调用,完成端口获取、客户端与生命周期钩子实例化、
|
||
适配器注册,返回 ``PluginManifest``。能力边界(accessible_ports /
|
||
injectable_pipelines / resource_quota / config_schema / env_vars /
|
||
credential_strategy 等)由 discover 阶段解析的 ``manifest`` 静态字段
|
||
承载,宿主在加载期通过 ``PluginCapabilityChecker.check`` 校验,本函数
|
||
不重复构造声明性字段(F-03 单真相源)。
|
||
|
||
参数:
|
||
host: 插件宿主,提供端口获取与扩展点注册 API。
|
||
manifest: 渠道清单,由 discover 阶段从 ``manifest.json`` 解析得到,
|
||
承载所有声明性字段。本函数复用其 ``config_schema`` 注入
|
||
``LifecycleHandler``,并直接作为返回 ``PluginManifest.manifest``。
|
||
"""
|
||
|
||
# 1. 获取端口实例(仅在 manifest accessible_ports 声明集合内取用)
|
||
# WeChatMpClient 依赖 ConfigPort/CachePort/LoggerPort(app_id/app_secret
|
||
# 由 CredentialService 通过 ConfigPort→CachePort 管理);
|
||
# PersistencePort 供 ProbeableAdapter 动态发现 account_id。
|
||
config_port = host.getConfigPort()
|
||
logger_port = host.getLoggerPort()
|
||
cache_port = host.getCachePort()
|
||
persistence_port = host.getPersistencePort()
|
||
|
||
# 2. 实例化 WeChatMpClient 与 LifecycleHandler
|
||
# 依赖关系为单向:handler 在 onInit 调 client.attach_http_client 注入
|
||
# HTTP 连接池,client 不引用 handler。先创建 client,再创建 handler
|
||
# 并通过构造器注入。config_schema 取自 manifest(F-03 单真相源)。
|
||
client = WeChatMpClient(
|
||
config_port,
|
||
cache_port,
|
||
logger_port,
|
||
plugin_version=manifest.version,
|
||
)
|
||
handler = WeChatMpLifecycleHandler(
|
||
config_port,
|
||
logger_port,
|
||
cache_port,
|
||
manifest.config_schema,
|
||
client,
|
||
max_connections=(
|
||
manifest.resource_quota.max_connections if manifest.resource_quota is not None else DEFAULT_MAX_CONNECTIONS
|
||
),
|
||
persistence_port=persistence_port,
|
||
)
|
||
|
||
# 3. 注册 11 个适配器
|
||
# inbound/outbound 接收 (client, config_port, logger_port);
|
||
# rich_message 接收 (logger_port);
|
||
# session/status 无构造参数(INV-5 无状态);
|
||
# lifecycle 接收 (client, config_port, cache_port, logger_port);
|
||
# probeable 接收 (client, persistence_port, logger_port);
|
||
# doctor 接收 (client, config_port, cache_port, logger_port);
|
||
# wizard 接收 (client, logger_port);
|
||
# whitelist 接收 (cache_port, logger_port);
|
||
# directory 接收 (client, cache_port, logger_port)。
|
||
host.registerAdapter("inbound", WeChatMpInboundAdapter(client, config_port, logger_port))
|
||
host.registerAdapter("outbound", WeChatMpOutboundAdapter(client, config_port, logger_port))
|
||
host.registerAdapter("rich_message", WeChatMpRichMessageAdapter(logger_port))
|
||
host.registerAdapter("session", WeChatMpSessionAdapter())
|
||
host.registerAdapter("status", WeChatMpStatusAdapter())
|
||
host.registerAdapter("lifecycle", WeChatMpLifecycleAdapter(client, config_port, cache_port, logger_port))
|
||
host.registerAdapter("probeable", WeChatMpProbeableAdapter(client, persistence_port, logger_port))
|
||
host.registerAdapter("doctor", WeChatMpDoctorAdapter(client, config_port, cache_port, logger_port))
|
||
host.registerAdapter("wizard", WeChatMpWizardAdapter(client, logger_port))
|
||
host.registerAdapter("whitelist", WeChatMpWhitelistAdapter(cache_port, logger_port))
|
||
host.registerAdapter("directory", WeChatMpDirectoryAdapter(client, cache_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
|