feat(channel): 出站消息 id 采集(可选 seam + qq_obv11 两分支取回)
「接她的话」缺的 A 层:平台给她自己那条出站消息分配的 id,此前在解析的那一刻 就被丢掉(HTTP 只校 retcode、WS 写完不等回声、契约层没有 id 出口)。 - internal/channel/adapter/outbound.go(新):SendReceipt/SendOutcome/SendIDStatus + 可选接口 OutboundReceiptAdapter + 调用方入口 SendWithReceipt。**不加进 PlatformAdapter** —— 四个未接线适配器与全部桩实现一行不改(向后兼容硬指标); 也不用回调:id 是某一次投递的属性,必须随那次投递返回。 - qq_obv11:HTTP 从响应信封 data.message_id 取;WS 登记 send_msg 的 echo 等待者 (复用 probe.go 的 action 等待表与 routeActionResponse,不另开通道)。取不到 一律空 id + 三态可分辨(reported/absent/unreported),绝不编造。 - send_msg echo 键改用单调计数器(旧 UnixNano 在 Windows 时钟粒度下会撞键, 撞键即拿不到自己的回执)。 - 既有 SendMessage/SendProactive 路径行为不变(wantReceipt=false 不等待)。 验收:go build ./... + go vet ./... + go test ./... 全绿(cmd/cyrene、internal/webui 的 setup 失败是 worktree 缺 dist 的既有环境限制,base 同样失败)。
This commit is contained in:
1 parent
a6f3817061
commit
b504df01e0
8 files changed
+1053
-63
No files matched your search
@@ -135,15 +135,24 @@ type Adapter struct {
|
||||
// 事故正是把两者混为一谈:WS 通 ≠ QQ 账号在线。由 mu 保护。
|
||||
account accountStatus
|
||||
|
||||
// probeMu / probeWait 是 WS 上「action 请求 → echo 回执」的等待表;probeEvery
|
||||
// 是两次 get_status 探活之间的正常间隔(失败按 policy 退避)。见 probe.go。
|
||||
probeMu sync.Mutex
|
||||
probeWait map[string]chan *obAPIResponse
|
||||
// actionMu / actionWait 是 WS 上「出站 action 请求 → echo 回执」的等待表:探活
|
||||
// (get_status,probe.go)与出站回执采集(send_msg,outbound_id.go)共用同一张表、
|
||||
// 同一条 routeActionResponse —— echo 前缀不同(status_ / send_),永不撞键。
|
||||
// probeEvery 是两次 get_status 探活之间的正常间隔(失败按 policy 退避)。见 probe.go。
|
||||
actionMu sync.Mutex
|
||||
actionWait map[string]chan *obAPIResponse
|
||||
probeEvery time.Duration
|
||||
probeTimeout time.Duration
|
||||
// sendReceiptTimeout 是 SendMessageWithReceipt 在 WS 上等待 send_msg 回执的上限
|
||||
// (见 outbound_id.go;测试可收紧)。HTTP 端不涉及:响应本来就要等。
|
||||
sendReceiptTimeout time.Duration
|
||||
// probeSeq 是 WS 探活 echo 的单调序号(见 probe.go 的 nextProbeEcho):唯一性不依赖
|
||||
// 时钟精度,两次探活绝不会撞在同一个 echo 上。
|
||||
probeSeq atomic.Uint64
|
||||
// sendSeq 是 WS 出站 send_msg echo 的单调序号(见 outbound_id.go 的 nextSendEcho),
|
||||
// 与 probeSeq 同理:同一毫秒内的多次发送绝不能撞在同一个 echo 上,否则先登记的那次
|
||||
// 发送会拿不到自己的回执(旧实现用 UnixNano,Windows 时钟粒度下真会撞)。
|
||||
sendSeq atomic.Uint64
|
||||
}
|
||||
|
||||
// Compile-time conformance with the channel contract.
|
||||
@@ -178,14 +187,15 @@ func NewAdapter(cfg Config, opts ...Option) *Adapter {
|
||||
cfg.ProactiveWindow = defaultProactiveWindow
|
||||
}
|
||||
a := &Adapter{
|
||||
cfg: cfg,
|
||||
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,
|
||||
cfg: cfg,
|
||||
groups: map[string]string{},
|
||||
newConn: make(chan struct{}, 1),
|
||||
limiter: newRateLimiter(cfg.MaxProactivePerWindow, cfg.ProactiveWindow),
|
||||
policy: defaultReconnectPolicy(),
|
||||
actionWait: map[string]chan *obAPIResponse{},
|
||||
probeEvery: defaultProbeInterval,
|
||||
probeTimeout: defaultProbeTimeout,
|
||||
sendReceiptTimeout: defaultSendReceiptTimeout,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(a)
|
||||
|
||||
Reference in new issue
Block a user