feat(qq_obv11): OneBot v11 四端齐备(裁决 1.4 / 12.4.1)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 四端=ws×client / ws×server(原有,补语义注释)+ **http×client**(新增 http_client.go:
  调 OneBot HTTP API、请求-响应、retcode 判成败、token 只走 Authorization 头)+
  **http×server**(新增 http_server.go:监听 listen_addr 收上报、fail-closed 鉴权、10MiB 体上限)
- 配置面:适配器新增 transport / api_base_url / report_path(json tag 沿用既有口径);
  env 兜底 CHANNEL_QQ_OBV11_{TRANSPORT,API_BASE_URL,REPORT_PATH};**默认值=现状**(ws+server)
- 出站分流:session.go 发送路径按 transport 走 WS 写帧或 HTTP POST(不改它则 HTTP 端无法出站)
- 端口豁免边界:http+server 沿用 AGENTS §9 已登记的同一豁免(design/13 §0 原文即含 HTTP server),
  且 listen_addr 为空时报错、**绝不开端口**(与 ws 侧同名校验成对)
- 关键取舍(已上报):OneBot 的 HTTP 是「动作 + 上报」两半,mode 决定**哪一半必需**、
  另一半给齐字段就一并启用 ⇒ 单个适配器即可「收上报 + 回消息」,四端仍各自可独立选择与测试
- 复核:build/vet 绿 + qq_obv11/cmd 包测试绿 + `-race` 干净;10 条新用例(HTTP 两端全部用本地
  httptest 假 OneBot,未连真实 QQ);变异 3 条(伪造 retcode 成功 / 去掉上报鉴权门 / 默认值翻 http)已还原
- 未接:get_group_list(后续)、无 token 时放行(与既有 ws_server 一致,如需 fail-closed 请裁决)
This commit is contained in:
AskaEth committed 2026-10-04 13:58:18 +08:00
1 parent 0e1f163ef9
commit 9253fef92a
9 files changed
+972 -45

No files matched your search

+10 -6
View File
@@ -1,9 +1,10 @@
package qq_obv11
// This file implements the server role of the OneBot v11 WebSocket channel:
// the adapter listens on a TCP port and accepts inbound OneBot (反向) client
// connections, validates any configured access token, and upgrades each HTTP
// request to a WebSocket session.
// 本文件实现四个端里的 **ws + server**(裁决 1.4):Cyrene 监听 `listen_addr`,等
// OneBot 实现(NapCat / go-cqhttp 的「反向 WS」)反连上来——连接由 OneBot 发起
// (OneBot→Cyrene),建立后收发都走这条长连接。端口必须由配置显式给出,默认不开
// (AGENTS §9 的登记豁免)。鉴权与 HTTP server 端一致:配置了 access_token 时,
// Authorization: Bearer 或 ?access_token= 必须匹配,否则 403。
import (
"context"
@@ -28,13 +29,16 @@ func (a *Adapter) connectServer(ctx context.Context) error {
return fmt.Errorf("qq-obv11: listen %s: %w", a.cfg.ListenAddr, err)
}
// 先把 *http.Server 落在局部变量上再交给 goroutine:Disconnect 会把 a.srv 置 nil,
// 直接读 a.srv 会与它构成数据竞争。
srv := &http.Server{Handler: mux}
a.mu.Lock()
a.ln = ln
a.srv = &http.Server{Handler: mux}
a.srv = srv
a.mu.Unlock()
go func() {
if serveErr := a.srv.Serve(ln); serveErr != nil && serveErr != http.ErrServerClosed {
if serveErr := srv.Serve(ln); serveErr != nil && serveErr != http.ErrServerClosed {
a.log.Warn("websocket server stopped", "err", serveErr)
}
}()