package adapter // 连接**就绪三态**的取值与判定:一次 Connect 返回之后,适配器到底处于哪一种状态。 // // 为什么必须三态而不是一个 bool:`Connect` 返回 nil 只说明「这次调用没有致命失败」, // 它至少覆盖三件互不相同的事—— // // ConnectStateConnected —— 真连上了(client 拨号成功 / HTTP 传输已装配就绪); // ConnectStatePending —— 已进入后台重试、当前还没连上(OneBot 实现比 Cyrene 后 // 启动是常态,首连失败刻意非致命); // ConnectStateReady —— 已绑定监听、就绪等待对端接入(ws+server 模式)。 // // pending 与 ready 的 IsConnected() 都是 false,但它们**不是**「未连接」这样一个静态 // 结论:前者是「正在连接」,后者是「就绪等接入」。用一个布尔去覆盖三态,管理面板就只能 // 编一个说法——编出来的正是 2026-10-07 那个「点了连接就弹已连接,而卡片一直显示未连接」 // 的谎。口径属于适配器(它才知道自己的 transport × mode 意味着什么),网关只做映射 // (internal/services/gateway 的 connectOutcomeAware,经类型断言探测,核心契约不扩展)。 // // 设计依据:DevDocs/design/08-渠道接入.md §1(PlatformAdapter 契约)与 §4.1(四端 // transport × mode);立场与 design/26 §2.6「状态面必须区分 WS 连着与账号在线」一致 —— // 一个布尔说不了两件事,更说不了三件。 // // 与 SessionState* / AccountStatus 同一口径:核心 PlatformAdapter 契约**刻意不扩展**, // 需要这条状态面的适配器自行实现该方法,网关用类型断言探测;没实现的适配器行为逐字不变。 const ( // ConnectStateConnected 表示真连上:会话已建立(client 拨号成功),或传输已装配 // 就绪而无可观察的长连接(HTTP)。此时 IsConnected() 应为 true。 ConnectStateConnected = "connected" // ConnectStatePending 表示已进入后台重试、当前尚未连上。它既不是配置错误,也不是 // 「未连接」,而是「连接中」,Reason 应说明原因(脱敏,绝不含凭据)。 ConnectStatePending = "pending" // ConnectStateReady 表示已就绪、等待对端接入:server 模式已绑定监听,但还没有 // 对端连上来。IsConnected() 此时为 false,却**不是失败**,因此没有 Reason。 ConnectStateReady = "ready" ) // ConnectSignals 是判定三态所需的**全部**输入。用结构体而不是几个相邻的布尔参数, // 一是让调用点自解释,二是避免两个 bool 被接反(那会静默地骗过所有测试)。 type ConnectSignals struct { // Started 报告 Connect 是否已成功启动该适配器;false 表示尚未启动,无从判断。 Started bool // Connected 报告当前是否有一条活的会话。 Connected bool // Retrying 报告未连上时后台是否有重试循环在跑(ws+client 有一条;HTTP 没有会话 // 可重连,不算)。 Retrying bool // Listening 报告适配器是否已绑定监听、在等对端接入(server 模式)。 Listening bool // Reason 是最近一次失败的原因(**已脱敏**);没有失败或已连上时为空串。 Reason string } // ConnectStateOf 是**纯函数**:由信号判定三态与原因。它不读时钟、不触网、不加锁, // 因此三态可以逐条单测(见 connect_outcome_test.go);适配器侧只负责在同一把锁下把 // 信号读齐。 // // 判定顺序(自上而下,先匹配者胜): // // !Started → ("", "") —— 未启动的适配器无从判断:回「无意见」而不是 // 随便认一个状态,调用方据此让可选键整键缺席。 // Connected → ConnectStateConnected —— 有活会话就是真连上,先于其它分支。 // Retrying → ConnectStatePending —— 尚未连上但重试在跑,这才是「连接中」。 // Listening → ConnectStateReady —— 监听已就绪、等对端接入,不是失败。 // 否则 → ("", "") —— 已启动、未连上、也没有可解释的就绪面:**不编** // 状态,让调用方退回只看 IsConnected()。 // // 只有 pending 会带 Reason:connected 没有任何坏消息要说,ready 本来就不是坏消息。 func ConnectStateOf(sig ConnectSignals) (state, reason string) { switch { case !sig.Started: return "", "" case sig.Connected: return ConnectStateConnected, "" case sig.Retrying: return ConnectStatePending, sig.Reason case sig.Listening: return ConnectStateReady, "" default: return "", "" } }