适用版本:ChengOS v0.1.0+ | 最后核对:2026-08-13 | 来源:
crates/cheng-adapters、crates/cheng-api/src/middleware/channel_auth.rs、crates/cheng-api/src/services/adapter_health.rs、crates/cheng-core/src/domain/channel
渠道问题按消息卡在哪一环能干净地区分开,所以请先定位断点,而不是靠猜:
平台 → Webhook/工作者 → 鉴权 → 归一化 → 会话 → 工作流 → 回复 → 平台
① ② ③ ④ ⑤ ⑥ ⑦
| 卡在 | 现象 |
|---|---|
| ① 平台根本没投递 | 日志里什么都没有 |
| ② 传输 | 什么都没到,但渠道显示 active |
| ③ 鉴权 | 返回 401 |
| ④ 归一化 | 有投递日志,但没有消息落库 |
| ⑤ 会话 | 消息落库了,但没有运行 |
| ⑥ 工作流 | 运行起来了但失败 |
| ⑦ 回复 | 运行成功,但什么也没发回去 |
连接状态
unconfigured → configuring → active
│
健康检查失败 → degraded → (自动重连)→ active
│
disconnected(手动) error { message }(需人工介入)
| 状态 | 含义 |
|---|---|
unconfigured |
还没有凭证或配置 |
configuring |
建立中——正在注册 Webhook、等待 OAuth 跳转或扫码 |
active |
已连接、可工作 |
degraded |
健康检查失败或令牌过期;正在自动恢复 |
disconnected |
手动断开或已吊销 |
error |
不可恢复;需要人工介入 |
卡在 configuring 的渠道几乎总是因为多步流程没走完——OAuth 跳转没跟进,或者二维码没扫。去看 setup_data。
什么都收不到
首先:它是不是依赖工作者的平台? Telegram 轮询、Slack Socket Mode 和钉钉 Stream 都依赖后台工作者。健康服务把管理器的 is_running() 当作权威存活信号,正是因为凭证有效不等于消息在进来——工作者可能已经退出了。
工作者一旦死掉,渠道会在一个健康周期内(ADAPTER_HEALTH_INTERVAL_SECS,默认 300 秒)变为 degraded。恢复流程会调用管理器的 restart()——只调 connect() 仅仅校验凭证,会造成一种假恢复:数据库写着 active,实际什么都收不到。看到「active 但一片寂静」,就该怀疑这一条。
对于 Webhook 类平台(WhatsApp、企业微信、Webhook 模式的 Slack):
| 检查 | 原因 |
|---|---|
PUBLIC_BASE_URL 是你真实的公网 HTTPS 域名 |
Webhook URL 是运行时由它计算出来的 |
| 该 URL 公网可达且不是 localhost | Telegram 的 Webhook 模式会校验这两点,并带明确提示让连接失败 |
| 平台控制台里显示 Webhook 已注册 | 有些平台会默默丢弃失败的注册 |
| 验证挑战通过了 | WhatsApp 发一次性的 GET 带 hub.* 参数;Slack POST url_verification |
在 NAT 后面或笔记本上跑? 选一种不需要入站连接的传输:Telegram 轮询(默认)、Slack Socket Mode 或钉钉 Stream。见即时通讯渠道。
鉴权失败
渠道密钥的形式是 chk_{credential_id_hex}_{secret_hex}——chk_ 前缀、32 位十六进制的凭证 id,以及 32 位十六进制的密钥。它们作为 provider = "channel_adapter" 的凭证存储。
错误码,全部返回 401:
| 错误码 | 含义 |
|---|---|
MISSING_TOKEN |
没有 Authorization 请求头 |
INVALID_KEY_FORMAT |
前缀不对、长度不对,或凭证 id 格式错误 |
KEY_NOT_FOUND |
该凭证 id 不存在 |
WRONG_PROVIDER |
凭证存在,但不是 channel_adapter 类凭证 |
SECRET_MISMATCH |
密钥那一半校验不通过 |
INVALID_KEY |
通用的密钥拒绝 |
除此之外都是 500——那是服务端问题,不是你的密钥问题。
密钥还可能是受限的:限定到某个 app_id(平台)以及某个精确的 channel_id。一把在某个渠道能用、在另一个渠道 401 的密钥,是受限,不是坏了。
浏览器前端绝不能使用 chk_ 密钥。 它们只用 JWT;适配器密钥属于服务端的平台服务。见渠道路由。
另外,/webhooks/:channel_uuid 接口是刻意不带认证的——真实性由适配器的签名校验建立,而不是 Bearer 令牌。那里出现 401 说明是别的地方出了问题。
签名与挑战失败
各平台的校验方式不同,Normalization 或鉴权错误通常指向配错了密钥:
| 平台 | 机制 | 常见原因 |
|---|---|---|
| Telegram | X-Telegram-Bot-Api-Secret-Token 相等比较 |
配置里的 webhook_secret 与注册时不一致 |
X-Hub-Signature-256 上的 HMAC-SHA256 |
signing_secret 填错了(应是 app secret,不是 access token) |
|
| Slack | 带时间戳的 X-Slack-Signature HMAC-SHA256 |
signing_secret 错,或主机时钟偏移 |
| 企业微信 | SHA1 签名 + AES-CBC 加密 XML | token 或 encoding_aes_key 错(后者必须是 43 位 base64url) |
消息到了,却没有运行
| 检查 | 说明 |
|---|---|
| 有没有绑定工作流? | 渠道记录上的 bound_workflow_id。工作流绝不从 channelId 推断 |
渠道是否 enabled? |
|
| 这条消息是可执行的吗? | 空文本消息会被静默跳过;只含投递状态的更新会归一化成空数组 |
| 它是不是一条命令? | 以 ~ 开头的消息是渠道命令,会被消费而不是转发 |
| 工作流本身合法吗? | 见执行失败排查 |
重复与上下文丢失
重复回复通常意味着去重没生效。入站消息带有 external_message_id 正是为此;只要该 id 稳定,平台重复投递就是正常且无害的。
会话解析在 (workspace_id, app_id, external_chat_id) 上是幂等的,因此反复调用会返回同一个会话。如果某个用户总是落进新会话,说明 external_chat_id 不稳定——去看适配器在群聊与单聊时分别往那里放了什么。
会话轮换(~new 或切换工作流)会在创建新会话的同一个事务里,把旧会话的 external_chat_id 加上 __retired_<ts> 后缀使其退役。在数据库里看到 retired 后缀是正常的,不是数据损坏。
回复没有送达
出站命令是 send_text、send_media、reply、react、typing 之一。检查适配器声明的能力:Telegram 支持「正在输入」但不支持表情回应和编辑;Slack 支持回应、编辑和删除,但没有「正在输入」。执行不支持的动作会返回 NotSupported。
错误类别
适配器错误是稳定的语义类别,类别会告诉你该去哪儿看:
| 类别 | 去看 |
|---|---|
Config |
connection_config 缺字段或填错 |
Auth |
令牌无效、凭证过期、权限被拒 |
PlatformApi |
平台返回了错误——常见于配额或应用被吊销 |
Normalization |
载荷结构出乎意料;查已存消息里的 raw |
NotSupported |
该适配器不提供这个操作 |
Internal |
ChengOS 的 bug——带上轨迹提报 |
诊断清单
1. GET /workspaces/:ws/channels/:id/status → 连接状态
2. GET /workspaces/:ws/channels/:id/capabilities → 该动作到底支不支持
3. 确认自上次变更以来已经过了一个健康检查周期
4. 工作者类平台:确认工作者在跑,而不只是凭证有效
5. 检查 bound_workflow_id
6. 查会话里有没有落库的消息 → 区分 ④ 和 ⑤
7. 查执行轨迹 → 区分 ⑥ 和 ⑦

暂无评论内容