fix: correlate downstream receipts with submit responses
This commit is contained in:
@@ -280,6 +280,8 @@
|
||||
- 下游投递重试已改为指数退避第一版:首次失败后按基础间隔重试,随后按 2 倍递增,并受最大退避上限约束,避免客户长时间离线时平台每分钟机械重试。
|
||||
- 下游状态必须以客户确认作为终态:Gateway `SendPkt` 成功后只能写 `awaiting_ack`,仅收到匹配连接、`Sequence_Id`、`Msg_Id` 且 `CMPP_DELIVER_RESP.Result=0` 后才能写 `delivered`;超时、非零 Result 和历史未留存 ACK 的记录分别按未确认、拒绝或历史未确认展示,不能再把 TCP 写出冒充客户已收到。
|
||||
- 企业应用必须分别提供“回执自动重试投递”和“上行短信自动重试投递”开关,默认开启。开关按投递创建时快照保存;关闭只阻止已经写出但未获 ACK/被拒绝后的自动重发,不阻止离线队列在客户首次上线时完成首次投递。手工重投不受开关限制,但必须提示重复业务处理风险并二次确认。
|
||||
- 客户 Submit 的失败状态回执必须严格晚于对应 `CMPP_SUBMIT_RESP` 写出,且 Deliver 中的业务 `Msg_Id` 必须非 0、与该 SubmitResp 返回的 `Msg_Id` 完全一致;`Result=0` 但 `Msg_Id=0` 只能表示客户端协议栈收包,不能标记业务回执已确认。平台必须持久化原 Submit Sequence_Id,使 Gateway 重启或客户重连后的补投仍可重建相同业务 `Msg_Id`。
|
||||
- 运营端允许对 `delivered`(客户端已确认)记录再次手工重投,但单条和批量入口都必须明确提示可能造成下游重复处理;`awaiting_ack` 状态在确认窗口内不得并发重投。
|
||||
- 已实现客户侧最终 Deliver 推送的第一版能力:Gateway 在下游 Submit 被接受后记录 messageId 到客户连接的内存映射;NestJS 收到最终 receipt/uplink 并入库后调用 Gateway `/downstream/receipt`、`/downstream/uplink`,Gateway 向仍在线的客户 CMPP 连接下发 Deliver Receipt 或普通 Deliver。
|
||||
- 已实现客户侧 Deliver 持久化第一版能力:NestJS 收到最终 receipt/uplink 后写入 `CmppDownstreamDelivery` 待投递记录;在线推送成功标记 delivered,客户断线或 Gateway 不可达时保留 pending 并记录 retry 信息;客户重新 bind 后 Gateway 按账号拉取 pending 记录补发。
|
||||
- 已实现普通上行匹配与人工认领第一版:优先按 messageId 精确匹配;无 messageId 时按接入号匹配应用路由;仍无唯一应用时按手机号和最近下发时间窗口匹配;多候选标记 ambiguous 并写入 `SmsUplinkMatchCandidate` 候选,运营端可人工认领候选应用/下发记录;认领后更新上行记录、保留候选审计,并创建真实客户侧上行 Deliver 投递记录。
|
||||
|
||||
@@ -3290,4 +3290,5 @@ npm run verify:phase8
|
||||
| TC-GW-ACK-001 | 客户在线时分别触发一条状态回执和一条上行短信,Gateway `SendPkt` 成功后延迟返回 `CMPP_DELIVER_RESP`。 | 写出后 `CmppDownstreamDelivery.status=awaiting_ack`;只有匹配连接、Sequence_Id、Msg_Id 且 Result=0 后才变为 `delivered`,并保存写出时间、确认时间、ACK 字段和连接 ID。 |
|
||||
| TC-GW-ACK-002 | 分别返回非零 Result、不返回响应直到超时、重启 Gateway 后让旧 `awaiting_ack` 超时。 | 非零 Result 和超时不会误记为 delivered;Gateway 重启后 NestJS 能恢复过期 ACK,按策略进入退避重试或最终 `rejected/unconfirmed`。 |
|
||||
| TC-GW-ACK-003 | 在企业应用中分别关闭“回执自动重试”和“上行短信自动重试”,各制造一次 ACK 超时,再重新开启并创建新投递。 | 关闭只影响对应类型的新投递策略快照;离线后的首次投递仍会在重连时执行;已写出未确认的记录不自动重发;重新开启后新记录按退避策略重试。 |
|
||||
| TC-GW-ACK-004 | 对 `unconfirmed/rejected/failed` 记录执行单条和批量手工重投。 | 页面提示重复处理风险并二次确认;真实调用 Gateway;`awaiting_ack/delivered` 不允许重投;重发复用同一业务 Msg_Id。 |
|
||||
| TC-GW-ACK-004 | 对 `unconfirmed/rejected/failed/delivered` 记录执行单条和批量手工重投。 | 页面提示重复处理风险并二次确认;真实调用 Gateway;`awaiting_ack` 不允许并发重投;重发复用同一业务 Msg_Id。 |
|
||||
| TC-GW-ACK-005 | 客户 Submit 后由业务校验立即生成失败回执,并覆盖在线即时投递、Gateway 重启后恢复投递;另模拟客户端对 `Msg_Id=0` 返回 Result=0。 | 客户收到的第一个响应包必须是对应 `CMPP_SUBMIT_RESP`,之后 Deliver 的 `Msg_Id` 非 0 且与 SubmitResp 完全一致;重启后根据持久化 Submit Sequence_Id 重建同一 Msg_Id;Result=0/Msg_Id=0 不得写为 delivered。 |
|
||||
|
||||
@@ -1727,3 +1727,12 @@ git diff --check
|
||||
- 功能提交 `8c03663f` 已 push 并部署生产。部署前 PostgreSQL 备份为 `/opt/cmpp-platform/backups/cmpp-20260714-142059.sql`(约 65MB),运行源码备份为 `/opt/cmpp-platform/backups/source-20260714-142059.tar.gz`(约 21MB);发布包本地和服务器 SHA-256 均为 `3e969aaf00e828da4167018bc349795413743414bc827d452859f0fd45776cd1`。
|
||||
- 生产 migration `20260714130000_add_downstream_delivery_ack_tracking` 成功应用,37 条 migration 全部齐全;历史 7 条 `delivered` 已按新口径迁移为 `unconfirmed`,204 个企业应用的回执和上行自动重试开关均为默认开启。生产 `.deployed-commit=8c03663f245c12cd5e6aab28ad9eeb68ea60ed57`,`cmpp-api`、`cmpp-gateway`、Nginx、MinIO 均 active,`12026/17890/8090/3000` 监听,API/Gateway health、Redis、外部首页和运营端登录页均正常,部署后近期日志无新增 error;前端产物已确认包含“回执自动重试”和“客户端已确认”。未擅自发送或重投客户短信。
|
||||
- 生产验证站点当前为 HTTP,已按发布约束显式设置 `SESSION_COOKIE_SECURE=false`,ACK 超时设置为 30 秒;无 Cookie 和无效 Cookie 访问受保护/会话接口均返回预期 401。服务器凭据文件对存量管理员只记录 `password=unchanged`,不是可用明文密码,因此未继续进行真实登录和开关点击,且已清除本次诊断产生的一次失败计数;登录后的页面交互由用户使用现有账号验收。
|
||||
|
||||
## 2026-07-14 下游回执 Msg_Id=0 与 SubmitResp 时序修复
|
||||
|
||||
- 生产核查 15:07、15:10 两条余额失败短信:`CmppDownstreamDelivery` 均记录 Result=0,但 ACK Msg_Id 都是 0;Gateway 日志中的原 SubmitResp Msg_Id 分别为 `9677414932210841325`、`9687782471788644609`。这只能证明下游协议栈收到了 Deliver,不能证明下游平台已将状态回执关联到原短信,与下游页面仍显示“未回执”一致。
|
||||
- 根因为 NestJS 在处理入站 Submit 时同步生成失败回执,而 Gateway 尚未建立 messageId 映射;原查找逻辑在精确消息未命中时回退账号会话,使用 bind 会话的零值 Msg_Id 提前写出 Deliver。上午 11:40 的回执手工重投后才显示,是因为重投时对应 Submit 映射已经存在,能携带正确 Msg_Id。
|
||||
- 修复为:精确消息未登记时禁止回退账号会话;Gateway 仅在 ConnectResp/SubmitResp 成功写出后冲刷 pending;新建短信记录持久化原 CMPP Submit Sequence_Id,重启恢复时结合平台 MessageId 重建相同 Msg_Id;发送层拒绝任何 Msg_Id=0 的 Deliver,NestJS 也不再把 Result=0/Msg_Id=0 标记为 delivered。
|
||||
- migration `20260714153000_fix_downstream_receipt_message_id` 新增 `SmsMessageRecord.cmppSubmitSequenceId`,并把存量 `deliveryType=receipt/status=delivered/ackMessageId=0` 纠正为 `unconfirmed`,保留 ACK 证据但不冒充业务确认。
|
||||
- 下游投递记录列表改为固定信息分组:投递类型/时间、企业/应用、消息 ID、状态、重试、最后错误和操作各自保持可读宽度,最后错误不再被挤成竖排。`delivered` 记录开放单条和批量手工重投,继续保留重复业务处理二次确认;`awaiting_ack` 仍禁止并发重投。
|
||||
- 已新增 TC-GW-ACK-005 和 Gateway 回归:首个返回包必须是 SubmitResp,随后失败回执 Deliver Msg_Id 与 SubmitResp 完全相同;另覆盖精确映射不回退、持久化 Sequence_Id 恢复和 Msg_Id=0 拒绝。API 全量 15 suites、154 项、Gateway 全量 Go 测试、Prisma validate、真实本地 PostgreSQL migration、API build、前端 build 和 `git diff --check` 均通过;前端仅有既有 Vite chunk size warning。经用户授权在应用内浏览器完成一次本地图形验证码登录,使用真实 NestJS API、PostgreSQL 和临时投递记录在 1440×1000 视口验证列表无横向挤压、无 `NaN`、console 无 error/warn,`delivered` 行的重投按钮可用且点击确实进入真实后端重投链路;因本地未运行 Gateway,请求按预期变为待重试而非伪造成功。临时投递记录和验收账号已清理。当前未提交、未推送、未部署生产。
|
||||
|
||||
Reference in New Issue
Block a user