feat: 实现消息分发推送确认撤回与启动恢复

This commit is contained in:
Nixevol
2026-09-30 07:32:53 +08:00
parent 34e7c2827f
commit bdd1d9e9f4
12 changed files with 2395 additions and 119 deletions
+55 -12
View File
@@ -317,33 +317,76 @@
### M1 2026-09-30
1. **提交时分发做成最小正确版**
- 原条款:DEVELOPMENT 7.3 步骤 8 / 7.4:`send_at` 已到则同一写操作内完整分发(停用拒绝、`queue_full`、`expire_at`/宽限、无接收者 `completed`、回执等)。
- 实际做法:单聊只插一条 `pending`;群按当时 `group_members` 去掉发送者各插 `pending`;消息改为 `dispatched`。不设 `expire_at`,不检查接收端配额/在线/停用,不因无接收者改为 `completed`,不写回执,不唤醒推送循环。
- 原因:M1 范围是提交;完整分发与推送属 M2。
- 备选方案:M1 直接实现完整 7.4(抢 M2)。
- 影响:到点消息已有投递行,但停用成员仍会有 `pending`;无成员群仍为 `dispatched` 且无投递;推送需等 M2。
1. **提交时分发做成最小正确版**(已被 M2 取代)
- 原条款:DEVELOPMENT 7.3 步骤 8 / 7.4:`send_at` 已到则同一写操作内完整分发。
- 实际做法(M1):单聊/群插 `pending` 后改 `dispatched`,不做停用/配额/宽限。
- 现状(M2):`Submit` 到点与 `DispatchDue` 均走完整 `dispatchFullTx`(7.4)。
- 原因 / 备选 / 影响:见 M2。
2. **请求频率突发容量写死为 100**
- 原条款:DEVELOPMENT 6.10 每端每秒 50、突发 100;配置示例仅有 `requests_per_second`。
- 实际做法:`Limits.RequestBurst` 默认 100;`requests_per_second<=0` 时不限速(便于测试)。速率桶挂在 `message.App` 的 `Submit` 入口;`ack`/`receipt_ack` 尚未实现故未接桶。
- 实际做法:`Limits.RequestBurst` 默认 100;`requests_per_second<=0` 时不限速(便于测试)。速率桶挂在 `message.App` 的 `Submit` 入口;`ack`/`receipt_ack` 不计入桶(与 6.10 一致)。
- 原因:配置无独立 burst 字段。
- 备选方案:配置增加 `request_burst`;由连接线在上行统一限流。
- 影响:改 `requests_per_second` 不改突发;正式接线后若 N 线也限流可能双重计数。
3. **未接线 `cmd/nixmsg`**
- 原条款:可替换 T0.4 假实现。
- 实际做法:新增 `message.App` 实现 `Submit`;保留 `Stub`;按任务隔离要求未改 `cmd/nixmsg`/`wire.go`。
- 原因:本任务禁止改 `cmd/nixmsg`;总控接线或后续任务再换。
- 实际做法:`message.App` 实现 Service;保留 `Stub`;未改 `cmd/nixmsg`/`wire.go`。
- 原因:本任务隔离;总控接线。
- 备选方案:本任务直接改 `wire.go`。
- 影响:进程内仍用 Stub,需显式构造 `message.New` 才能用真实提交。
- 影响:进程内仍用 Stub,需显式 `message.New` 并注入 `Downlink`/`ConnRegistry`。
4. **防重键在、消息行已删时返回 `not_found`**
- 原条款:防重命中返回原消息当前状态;未写明消息行已被清理时的提交重试行为(状态查询为 `not_found`)。
- 原条款:防重命中返回原消息当前状态;未写明消息行已被清理时的提交重试行为。
- 实际做法:`send_keys` 指纹相同但 `messages` 无行时返回 `not_found`。
- 原因:无法构造 `send_at`/`state`。
- 备选方案:在 `send_keys` 冗余存结果快照。
- 影响:保留期过后的重试不再幂等成功。
- 影响:保留天数 0 完成后重试不再幂等成功(与 F18 防重「记录还在时」一致)。
### M2 / M3 / M4 2026-09-30
1. **下行与在线用可注入接口,测试用假实现**
- 原条款:推送经 broker `Downlink`;连接表在 N 线内存。
- 实际做法:`WithDownlink` / `WithConnRegistry`;测试用 `RecordingDownlink`、`MemoryConns`。状态机全在 `message` 包。未接真实 MQTT/mochi。
- 原因:N3 握手与 wire 本波未强制合入;任务允许假下行。
- 备选方案:直接依赖 `internal/broker.Broker`。
- 影响:接线方需在握手/断线时调用 `OnHandshakeComplete`/`OnDisconnect`,登记连接,并把 `OnPublishDropped` 转到 `App`。
2. **大帧并发名额在 message 包再管一份**
- 原条款:大于 64KiB 全局同时不超过 64(DEVELOPMENT 7.5);N2 broker 已有信号量。
- 实际做法:`App` 内另有容量 64 的 `largeSem`,发布前申请,确认/超时/清标记时释放。
- 原因:假 `Downlink` 不经 broker 时仍要满足上限。
- 备选方案:只依赖 broker,测试也走真实 PublishDown。
- 影响:接线真实 broker 后可能双重限流(更严,不破坏语义)。
3. **确认超时按库内 `pushed_at` 判定,不另开每连接计时器 goroutine**
- 原条款:推送循环在内存里计时。
- 实际做法:`PushPending` 开头扫描该连接已推且 `now - pushed_at >= ack_timeout` 的投递,再按 keep/expire 规则处理。
- 原因:与崩溃恢复一致、测试可拨钟;避免无调度器时泄漏计时器。
- 备选方案:每连接 `time.AfterFunc`。
- 影响:需周期性调用 `PushPending`(或 `WakePush`)才会触发超时。
4. **后台调度/清理循环未在 App 内自启**
- 原条款:调度按 `send_at` 唤醒;清理约每秒;推送每连接一循环。
- 实际做法:导出 `DispatchDue`、`PushPending`、`CleanupOnce`、`RecoverOnStart`、`WakePush`;由接线方起 goroutine。`WakePush` 在有连接时异步 `PushPending`。
- 原因:未改 `cmd/nixmsg`;避免无 context 的后台泄漏。
- 备选方案:`App.Start(ctx)` 内启三循环。
- 影响:未接线则定时消息不会自动到点,需外部调用 `DispatchDue`。
5. **回执推送窗口未单独记 inflight**
- 原条款:回执窗口默认 64,确认一笔再推下一笔。
- 实际做法:按 `acked=0` 取最多 `ReceiptWindow` 条尽力发布;不因未 `receipt_ack` 停推后续。
- 原因:简化;回执可重复、SDK 按 `receipt_id` 去重。
- 备选方案:内存记已推未确认回执数。
- 影响:发送方慢确认时可能多推几条回执(协议允许重复)。
6. **`Status` 返回自建 map,非独立协议类型**
- 原条款:6.4 状态响应字段。
- 实际做法:`map[string]any`(`state`/`reason`/`counts`/`deliveries`/`next_cursor`)。
- 原因:`protocol` 无 StatusData 结构且不可改共享协议包时取稳妥形状。
- 备选方案:总控在 `protocol` 增类型。
- 影响:接线编码 `resp.data` 时直接 Marshal 该 map 即可。
## 身份 I