fix: reassemble inbound CMPP long messages
This commit is contained in:
@@ -180,6 +180,8 @@
|
||||
6. 一个企业应用可以分别绑定移动、联通、电信通道组;可以只绑定其中一类或两类,但不能一个都不绑定。
|
||||
7. 发送时先按运营商识别结果分流:移动短信走移动通道组,联通短信走联通通道组,电信短信走电信通道组;识别不出的号码走移动通道组。
|
||||
8. 运营商识别以可配置的号码前缀正则表达式为准,通常匹配手机号前 3 到 4 位;手机号段库只提供省份/城市识别,其 carrier 字段仅作后台校验或提示,不参与发送运营商判定。
|
||||
- 预发布运营商规则按公开码号资料覆盖中国移动、中国联通、中国电信及其移动转售号段;中国广电 `192` 号段按当前业务约定归入中国移动路由。
|
||||
- 号码前缀表示原始码号分配关系;携号转网号码无法仅凭前缀识别当前签约运营商,如后续要求按实时在网运营商路由,必须接入可信的携号转网/HLR 查询能力。
|
||||
9. 路由规则只能表达应用到通道组的绑定关系,不能直接绑定单个通道,也不在规则层配置省份;省份和全国路由在通道组内部处理。
|
||||
10. 发送服务必须使用手机号段库识别手机号省份和城市;无法识别省份时走对应运营商的全国通道组路由。
|
||||
11. 通道组内必须先匹配省网路由;省网未匹配时走同一运营商通道组内的全国通道。省网发送失败后,当前版本立即跳到该通道组第一个全国通道补发,不再尝试同省第二省网通道。
|
||||
@@ -254,6 +256,7 @@
|
||||
11. Gateway 必须支持下游客户上行接入场景:收到运营商上行后,按接入号、手机号、应用、时间窗口匹配并向客户连接推送 Deliver,上行同时入库。
|
||||
12. 下游客户连接与上游通道连接必须隔离管理:客户侧账号密码不能用于连接上游通道,上游通道账号密码也不能作为客户接入凭据。
|
||||
13. Gateway 必须为客户侧 CMPP2.0/3.0 Submit 记录可检索日志:收包时记录协议版本、账号、客户 IP、sequenceId、号码、srcId、编码、分片序号和内容长度;响应时记录 result、平台 messageId、CMPP Msg_Id、耗时和失败阶段。NestJS 拒绝 Submit 时,Gateway 日志必须保留 API 返回的真实业务原因,不能只记录 HTTP 状态码;短信正文不得明文写入 Gateway 日志,仅记录字符数和哈希。
|
||||
14. 客户已按 CMPP 标准 UDH 拆分的下游长短信必须先重组再进入业务发送链。Gateway 应识别 8 位 `05 00 03` 和 16 位 `06 08 04` 拼接头,去除 UDH 后按 `MsgFmt` 解码正文,并将引用号、总片数、片序号和编码传给 NestJS;NestJS 必须在 PostgreSQL 持久化分组和分片,支持乱序、同片幂等、冲突片拒绝、进程重启恢复和超时终止。分片未齐全前不得创建内部批次、`SmsMessageRecord`、计费或路由;齐全后只创建一条完整正文主记录,并使用第一片 `Sequence_Id` 建立客户消息映射。每个合法分片仍应分别取得一个 `CMPP_SUBMIT_RESP`,但不能把 UDH 字节作为正文、不能按分片生成多条短信记录。
|
||||
|
||||
#### 4.8.3 回执、上行与幂等
|
||||
|
||||
|
||||
@@ -1011,6 +1011,21 @@
|
||||
- 放入联通通道组的三网通道不能被移动号码选中。
|
||||
- route/trace 标明实际命中的三网通道。
|
||||
|
||||
### TC-SEND-016A 预发布运营商号段规则完整性
|
||||
|
||||
- 优先级:P0
|
||||
- 前置条件:运营商区分规则已按当前公开码号资料同步。
|
||||
- 步骤:
|
||||
1. 读取真实 `GET /api/admin/dictionaries/phone-carrier-rules?page=1&pageSize=100`。
|
||||
2. 使用移动、联通、电信基础号段及 `162/165/167/170/171` 等移动转售号段生成代表号码。
|
||||
3. 使用 `190/191/192/193/195/196/197/198/199` 生成代表号码。
|
||||
4. 对每个代表号码按发送服务相同的优先级和 JavaScript 正则逐条匹配。
|
||||
- 预期结果:
|
||||
- 每个代表号码唯一命中一条 active 规则,不得重叠或漏配。
|
||||
- `190/191/193/199` 归电信,`196` 归联通,`195/197/198` 归移动。
|
||||
- 中国广电 `192` 按当前业务约定唯一归入移动。
|
||||
- 测试结论仅代表原始号段分配规则,不把携号转网号码误宣称为实时运营商识别。
|
||||
|
||||
### TC-SEND-018 失败补发停止条件
|
||||
|
||||
- 优先级:P0
|
||||
@@ -1205,6 +1220,25 @@
|
||||
- CMPP 内部批次的详情、短信明细和取消请求均返回不可见/不存在,不泄露内部任务。
|
||||
- CMPP 短信仍完整出现在短信记录、提交、回执和账务链路。
|
||||
|
||||
### TC-GW-006A 下游 CMPP UDH 长短信持久化重组
|
||||
|
||||
- 优先级:P0
|
||||
- 前置条件:企业应用可正常 bind;已配置审核通过的完整签名和模板;NestJS、PostgreSQL、Redis 与 Go Gateway 使用真实本地或预发布链路。
|
||||
- 步骤:
|
||||
1. 使用 CMPP2.0 和 CMPP3.0 分别提交一条两片长短信,分片正文使用 UCS2,首片携带签名;覆盖 8 位 `05 00 03` 和 16 位 `06 08 04` UDH。
|
||||
2. 第一片提交后查询 `CmppInboundLongMessage`、`CmppInboundLongMessageSegment`、`SmsBatchTask` 和 `SmsMessageRecord`。
|
||||
3. 乱序提交第二片,再重复提交内容和 `Sequence_Id` 完全相同的分片。
|
||||
4. 使用相同引用号和片序号提交内容不同或 `Sequence_Id` 不同的冲突片。
|
||||
5. 在全部分片已持久化、业务处理尚未完成时重启 API,再重放任一已存分片。
|
||||
6. 只提交部分分片并等待超过 `CMPP_INBOUND_LONG_MESSAGE_TTL_SECONDS`,执行超时扫描。
|
||||
- 预期结果:
|
||||
- Gateway 去除 UDH 后才按 `MsgFmt` 解码,NestJS 收到的每片正文不含 `05 00 03`/`06 08 04` 控制字节;每个合法分片均返回 `SUBMIT_RESP status=0`。
|
||||
- 分片未齐全时只存在一条 collecting 分组和已收分片,API 返回稳定的分组 `messageId`,不得创建内部批次、短信主记录、计费或路由。
|
||||
- 分片齐全后按 `segmentIndex` 唯一排序拼接,只创建一条完整正文 `SmsMessageRecord` 和一个内部批次;正文签名/模板识别针对拼接后的完整内容执行,`cmppSubmitSequenceId` 使用第一片 `Sequence_Id`。
|
||||
- 乱序可完成;完全相同的重复片幂等复用原结果;冲突片返回明确 4xx/非零 SubmitResp,数据库唯一约束禁止同组同片序号出现两行。
|
||||
- API 重启后从 PostgreSQL 恢复分组、分片、原 `messageId` 和第一片 `Sequence_Id`,不得重复创建主记录。
|
||||
- 超时未齐分组转为 expired,保留分片审计但不创建短信主记录;后续相同引用号的新消息可建立新分组。
|
||||
|
||||
### TC-SEND-039 CMPP 模板不匹配短窗口聚合人工审核
|
||||
|
||||
- 优先级:P0
|
||||
|
||||
@@ -2244,3 +2244,21 @@ git diff --check
|
||||
- 发布后以字节级扫描复核49条签名,非法UTF-8为0;两个目标ID均精确恢复为hex `e38090e888aae5a4a9e4bfa1e681afe4bfa1e8afbae7bd91e38091`(`【航天信息信诺网】`)。生产Prisma真实执行签名、报备任务、报备记录、待报备资料四类关联查询均成功,分别返回49、34、82、4条,不再触发P2039/22021。
|
||||
- API/Gateway health、外部首页、运营登录页、客户端登录页和外部API health均返回HTTP 200;Redis PONG,`gateway.submit.commands`为`pending=0、lag=0`,7个通道TPS权威配置存在。两个active上游通道均为`connected/currentConnections=1`;disabled通道的一条`connected/1`仍是2026-07-09历史状态残留,本次启动没有把它作为活动通道恢复。
|
||||
- 部署后API日志新增P2039/22021为0,Nginx中企业签名及相关报备接口新增5xx为0。应用内Browser因Chrome标签被另一Codex会话占用且控制连接超时,未完成登录态页面交互验收;本次以真实NestJS所用Prisma关联查询、PostgreSQL字节扫描和Nginx/API日志作为后端修复证据,不虚报浏览器交互通过。未发送短信,未审核、删除、充值、改密或修改其他生产业务数据。
|
||||
|
||||
## 2026-07-23 预发布运营商区分规则同步(配置变更,未提交、未部署代码)
|
||||
|
||||
- 在线证据以华为云2026年2月《消息&短信》号码规则为主,并以工信部关于`190/197/196/192`公众移动通信网网号核发信息交叉核对。规则覆盖三大基础运营商、移动转售号段及必要的上网卡/物联网/卫星前缀;按用户明确要求将中国广电`192`归入中国移动路由。
|
||||
- 变更前预发布`PhoneCarrierRule`有30条,存在`190`重复、移动`195`仅覆盖`1951—1952`、电信`191/193`等缺失。完整PostgreSQL及原规则CSV已备份至`/opt/cmpp-platform/backups/config/20260723-164357-phone-carrier-rules`;数据库备份SHA-256为`239c4c669d55e8ccf6bfc5458e92fab507e1af8cb928e7f4ace5cd7f53c1e6f1`,规则CSV SHA-256为`8e437138f0e1a526519b01c6ddc4e95ce58e1a76fb623dc2a3ab0f6616630e86`,两者权限600且gzip校验通过。
|
||||
- 使用单个PostgreSQL事务锁定并原子替换规则,最终30条全部active:移动12条、联通9条、电信9条。`19200000000`唯一命中`mobile / ^19[2578]`,备注明确“含中国广电192,按业务要求归中国移动”。
|
||||
- 真实管理员验证码登录成功(201),`GET /api/admin/dictionaries/phone-carrier-rules?page=1&pageSize=100`返回200和30条;对68个基础、转售及新号段代表号码按发送服务同款JavaScript正则验证,全部唯一命中、0个错配,随后真实登出成功。首次验证误把凭据文件的`password=unchanged`当作密码产生一次401,未锁定账号、未修改规则,修正为已授权密码后通过。
|
||||
- 本次只修改预发布字典配置,不改代码、不重启服务、不发送短信,也不回写历史短信运营商。号段规则反映原始码号分配;携号转网后的当前签约运营商无法只靠前缀判断,若业务要求实时识别需另接MNP/HLR能力。
|
||||
|
||||
## 2026-07-23 下游 CMPP UDH 长短信持久化重组修复(发布前验证)
|
||||
|
||||
- 根因确认不是近期回归,而是既有下游入站链路从未实现重组:Gateway 将每个 CMPP Submit 的完整 `MsgContent`(包含 UDH)直接按 UCS2/GB18030 解码并逐片调用 NestJS,NestJS 因而把两片当成两条独立短信,控制字节污染首部签名匹配。此前台账通过的是“平台完整正文向上游拆分”和“上游 Deliver 长上行重组”,未覆盖“企业客户端已拆分的下游 Submit 重组”。
|
||||
- Gateway 新增标准 8 位 `05 00 03`、16 位 `06 08 04` UDH 解析,校验 `PkTotal/PkNumber` 与 UDH 总片数/片序号一致,先剥离 UDH 再按 `MsgFmt` 解码,并向 NestJS 传递引用号、总片数、片序号和编码。真实 CMPP2.0 TCP 回归确认两片均获得成功 SubmitResp,API 收到的片正文不含 UDH。
|
||||
- NestJS/Prisma 新增 `CmppInboundLongMessage`、`CmppInboundLongMessageSegment` 及 migration `20260723120000_add_cmpp_inbound_long_message_reassembly`。分组键包含应用、账号、Src_Id、目标号码、引用号、总片数和编码;使用 PostgreSQL advisory transaction lock 与同组同片唯一索引保证并发幂等。分片齐全前不创建批次/短信,齐全后按片序合并并只创建一条完整正文记录;持久化稳定 `messageId` 和第一片 `Sequence_Id`,支持乱序、重复片、冲突拒绝、进程重启恢复及超时转 expired。
|
||||
- 本地真实 PostgreSQL 16 已应用 62 条 migration,schema 最新。事务验证成功写入2片并按序拼成 `【测试】第一片第二片正文`,同组同片重复索引被唯一约束拒绝,验证事务最终回滚为0条残留。真实本地 NestJS API + PostgreSQL + Redis 调用两次入站接口后,分组为 completed、持久化2片、只创建1条主记录,完整正文和第一片 `Sequence_Id` 均正确;未启动 Go Gateway 上游连接,未发送真实短信。
|
||||
- 新增长短信相关 API 回归5项(合并、乱序/重复/冲突、处理中断恢复、主记录已落库后的幂等恢复、超时终止)和 Gateway 回归3项(8位UDH、16位UDH、真实CMPP2.0两片转发)。API发送链83/83、API全量24 suites/295项、Gateway `go test ./...`、API build、前端build、Prisma generate/validate/status均通过。`verify:phase8`首次与本地API并发时BullMQ为438.93 TPS而失败;关闭仅由本轮启动的API后单测为909.56 TPS,完整重跑为872.43 TPS并通过。前端仅保留既有约1.92MB单chunk告警。
|
||||
- 预发布两次测试正文使用的 `【深圳市合正物业服务有限公司】` 在该应用签名库中不存在;本次修复能消除UDH污染并完整重组,但不会绕过签名审核。部署后复测前需先按正常产品流程为应用配置并审核该签名/模板,或改用应用已有的审核通过签名。数据库升级只新增两张重组表和外键/索引;如必须回滚,应先停止新版本 API/Gateway,再删除子表和父表,未完成分片审计会丢失,既有短信主记录不受影响。
|
||||
- 发布前功能代码、迁移、回归测试和文档已完成并获用户授权提交、推送和部署;本节先保留发布前验证证据,实际提交、备份、migration、服务重启和发布后验收结果在部署完成后追加记录。
|
||||
|
||||
Reference in New Issue
Block a user