feat(channels): QQ 账号 get_status 主动探活(复用既有 action 通道 / 状态变化才记日志 / AccountStatus 与 WS 连通分开)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

This commit is contained in:
AskaEth committed 2026-10-05 12:10:34 +08:00
1 parent f44114301e
commit a79fe9684e
6 files changed
+577 -18

No files matched your search

+32 -6
View File
@@ -121,6 +121,18 @@ type Adapter struct {
// reconnect / policy 只服务 ws+client 的首连与重连退避(见 reconnect.go)。
reconnect reconnectState
policy reconnectPolicy
// account 是**账号级**在线状态(get_status 探活所得,见 probe.go)。它与
// connected(Cyrene 与 OneBot 实现的 WS/会话是否通)是两件事——2026-10-05
// 事故正是把两者混为一谈:WS 通 ≠ QQ 账号在线。由 mu 保护。
account accountStatus
// probeMu / probeWait 是 WS 上「action 请求 → echo 回执」的等待表;probeEvery
// 是两次 get_status 探活之间的正常间隔(失败按 policy 退避)。见 probe.go。
probeMu sync.Mutex
probeWait map[string]chan *obAPIResponse
probeEvery time.Duration
probeTimeout time.Duration
}
// Compile-time conformance with the channel contract.
@@ -143,12 +155,15 @@ func NewAdapter(cfg Config) *Adapter {
base = logger.New(logger.Options{})
}
return &Adapter{
cfg: cfg,
log: logger.WithSource(base, "plugins/channels/qq_obv11"),
groups: map[string]string{},
newConn: make(chan struct{}, 1),
limiter: newRateLimiter(cfg.MaxProactivePerWindow, cfg.ProactiveWindow),
policy: defaultReconnectPolicy(),
cfg: cfg,
log: logger.WithSource(base, "plugins/channels/qq_obv11"),
groups: map[string]string{},
newConn: make(chan struct{}, 1),
limiter: newRateLimiter(cfg.MaxProactivePerWindow, cfg.ProactiveWindow),
policy: defaultReconnectPolicy(),
probeWait: map[string]chan *obAPIResponse{},
probeEvery: defaultProbeInterval,
probeTimeout: defaultProbeTimeout,
}
}
@@ -211,6 +226,10 @@ func (a *Adapter) Connect(ctx context.Context) error {
if !httpTransport {
go a.readLoop(a.lifeCtx)
}
// 账号在线探活(get_status)与连接生命周期同生共死:lifeCtx 取消即干净退出。
if a.probeEnabled() {
go a.runProbeLoop(a.lifeCtx)
}
return nil
}
@@ -231,6 +250,9 @@ func (a *Adapter) Disconnect(ctx context.Context) error {
a.ln = nil
a.srv = nil
a.httpAPI = nil
// 断开即丢弃账号在线状态:新会话的 known=false 由下一次探活重新确立,绝不沿用
// 上一轮的陈旧结论(AccountStatus 的诚实口径)。
a.account = accountStatus{}
a.mu.Unlock()
if !started && cancel == nil {
@@ -261,6 +283,10 @@ func (a *Adapter) Disconnect(ctx context.Context) error {
// IsConnected reports whether a live OneBot session exists. HTTP 传输没有长连接,
// 它反映的是「适配器已就绪」(监听已绑定 / 出站客户端已装配)。
//
// ⚠️ 它**只**回答「Cyrene 与 OneBot 实现(适配器服务器)通不通」——WS 两端是长连接
// 是否建立。它**不**回答「QQ 账号是否在线」:NapCat 等实现即使掉了账号,正向 WS 仍
// 可能保持连接(2026-10-05 事故现场)。账号在线状态见 AccountStatus(get_status 探活)。
func (a *Adapter) IsConnected() bool {
a.mu.RLock()
defer a.mu.RUnlock()