feat: harden CMPP delivery and platform workflows

This commit is contained in:
hectorzhao
2026-07-20 18:07:29 +08:00
parent 80fb5a8f53
commit f02c33cbb7
61 changed files with 1834 additions and 281 deletions
+38 -3
View File
@@ -1159,7 +1159,7 @@
- 步骤:
1. 启动 Go Gateway,确认 `GATEWAY_CMPP_ADDR=0.0.0.0:17890`
2. 使用 gocmpp 或真实 CMPP 客户端连接 `17890``Source_Addr` 填应用 `cmppAccount`,密码填应用 CMPP 参数 `passwordCipher`
3. 分别发送 CMPP 2.0 和 CMPP 3.0 SubmitReq,手机号和内容匹配已审核模板。
3. 分别发送 CMPP 2.0 和 CMPP 3.0 单号码 SubmitReq;再发送 `DestUsrTl=2`、两个合法 `DestTerminalId` 的多号码 SubmitReq,手机号和内容匹配已审核模板。
4. 再发送一条不匹配审核模板的 SubmitReq,检查客户收到的 SubmitResp 和 Gateway 日志。
5. 查询 NestJS 数据库和运营端短信记录。
- 预期结果:
@@ -1167,10 +1167,10 @@
- bind 阶段调用真实 NestJS API 校验账号、密码、企业状态、认证状态、应用状态、短信接口开关和 IP 白名单。
- CMPP2.0 和 CMPP3.0 连接分别返回对应版本 ConnectResp,后续 Submit/Deliver 按该 TCP 连接协商版本解包和组包,不发生字段错位。
- 密码错误、应用停用、企业停用、短信接口关闭、IP 不在白名单时 connect/login 被拒绝。
- submit 被接受后返回 CMPP SubmitResp 成功,并在真实数据库创建 `sourceType=cmpp` 的发送记录,进入真实发送链路。
- submit 被接受后返回 CMPP SubmitResp 成功,并在真实数据库创建 `sourceType=cmpp` 的发送记录,进入真实发送链路。多号码 Submit 仍只返回一个 SubmitResp/Msg_Id,但必须为全部目标号码分别创建 `SmsMessageRecord` 和内部批次,分别校验、计费、路由和提交;每个号码的 Deliver Receipt 使用同一原 Submit Msg_Id,并以各自 `DestTerminalId` 区分。Gateway 重启后从持久化分组消息 ID 和 Submit Sequence_Id 恢复时,所有号码的回执 Msg_Id 仍必须与原 SubmitResp 完全一致。
- `sourceType=cmpp` 的内部批次不出现在运营端或客户端“短信任务进度”;客户端不能通过任务 ID 读取该内部批次的详情、短信明细或执行取消。
- Submit 应用身份使用 bind 已鉴权账号;`MsgSrc` 使用应用级企业代码并独立校验。企业代码与登录账号不同时仍能正确定位应用,企业代码不匹配时返回失败。
- 鉴权失败、IP 白名单不符、手机号等协议参数不合法时返回非零 SubmitResp,且不创建短信记录。
- 鉴权失败、IP 白名单不符、任一目标手机号等协议参数不合法时返回非零 SubmitResp,且整包不创建短信记录;禁止多号码 Submit 返回成功后只保存或发送首号码
- 已鉴权且参数合法的 Submit 必须先返回成功 SubmitResp 和平台 Msg_Id;内容不匹配审核模板、签名/报备未通过、余额不足、应用在 bind 后停用、无可用通道、上游 Submit 最终失败时,均须真实创建短信记录、`SmsReceiptRecord``CmppDownstreamDelivery`,并向客户下发 `undelivered/REJECTD` Deliver Receipt,不得仅以 SubmitResp 失败替代回执。
- Gateway 对每次 submit 记录 `submit_received``submit_accepted`/`submit_rejected`;日志可按账号、IP、sequenceId、号码和 messageId 定位,拒绝时包含 NestJS 真实业务原因和 CMPP result,但不包含明文短信正文。
- CMPP 包在进入 handler 前因长度、命令字、读包或 Unpack 失败时,Gateway 记录 `read/unpack packet failed`、远端地址、协议模式、错误类型和原始错误,不得静默断开。
@@ -3518,3 +3518,38 @@ npm run verify:phase8
| TC-HTTP-WEBHOOK-002 | 回调依次返回 500、429、408、400、302 和 200,并模拟超时。 | 500/429/408/网络错误按既定退避重试,400 和重定向终结,2xx 成功;每次尝试、状态码、耗时和截断响应写 PostgreSQL,可授权手工重投。 |
| TC-HTTP-WEBHOOK-003 | 保存指向 localhost、RFC1918、链路本地、共享地址、云元数据 IP、会解析到私网的域名和发生 DNS 重绑定的 URL。 | 保存或投递前被 SSRF 校验拒绝;不跟随重定向;生产 HTTPS 约束开启时 HTTP URL 被拒绝。 |
| TC-HTTP-CLIENT-001 | 客户端打开“接口对接”五个页签,切换应用、创建凭据、配置回调、查看文档与日志;API 断开后重试。 | 所有状态来自真实 API/PostgreSQL/Redis;应用卡片显示 HTTP 状态;API 失败展示错误,不使用 localStorage 或前端静态数据伪造成功。 |
### 17.15 手工验收瑕疵回归
| 用例编号 | 操作 | 预期结果 |
| --- | --- | --- |
| TC-DEFECT-001 | 分别选择 2MB 与超过 2MB 的图片、10MB 与超过 10MB 的普通文件,并绕过前端直接请求上传 API。 | 边界值可上传至 MinIO,超限在前端即时拒绝且 API 再次返回 400,MinIO 无超限对象。 |
| TC-DEFECT-002 | 新建/编辑企业,输入中文、标点和英文数字信用代码,上传长文件名执照并预览/下载。 | 非英文数字被拒绝,合法值真实入库;文件名换行且两个操作样式一致。 |
| TC-DEFECT-003 | 编辑一个 HTTP 未开通的应用,切换为开通并保存,刷新后查看/复制 HTTP 参数。 | 六项能力全部开启,回执/上行为 HTTP Webhook,配置真实写入 PostgreSQL,复制内容不显示原始枚举值。 |
| TC-DEFECT-004 | 企业应用列表保持相同条件连续点击查询/重置,打开包含长 ID 的 CMPP 连接详情;短信记录同样操作。 | 每次均有真实 API 请求且数据更新;弹窗无水平滚动,长 ID 自动换行。 |
| TC-DEFECT-005 | 通过/驳回一条待审短信并查看列表、更多信息及导航角标。 | 审核人/时间由当前会话写库,时间格式正确,列表不额外占列,角标不等待 30 秒轮询即更新。 |
| TC-DEFECT-006 | 打开真实短信详情,再新增后删除一条运营商区分规则。 | 详情分别展示 `clientSrcId` 与通道 `srcId + applicationExtension`;运营商显示中文,DELETE API 真实删库并刷新。 |
| TC-DEFECT-007 | 用登录运营用户修改企业应用及其他任一写操作,再查看系统日志;另构造失败请求。 | 成功写操作均有操作人、路径、资源和结果日志;失败请求不写伪成功日志,日志不含请求体、密码或密钥。 |
### 17.16 2026-07-20 缺陷回归
| 用例编号 | 操作 | 预期结果 |
| --- | --- | --- |
| TC-RECEIPT-IDENTITY-001 | 两个通道使用同一下游账号及相同通道 Msg_Id,分别向两个号码提交,再乱序返回回执。 | 以通道、通道 Msg_Id 和号码唯一关联提交记录;主记录写入正确通道、到达时间、原始状态和文本,无跨通道抢占。 |
| TC-RECEIPT-IDEMPOTENT-001 | 顺序和并发重复发送相同 DELIVRD,随后重启服务并再次发送。 | `receiptKey` 唯一约束保证只保存一次、只下发一次客户回执,且不重复计费或退款;重启后行为一致。 |
| TC-REPORT-RECALC-001 | 插入一条成功、一条失败及扣费/退款流水,执行指定日期重算两次,再查询应用和通道报表及 CSV。 | 两次结果一致;发送 2、成功 1、失败 1;收入、退款、成本和利润使用相同金额单位且 `利润=收入-成本`;平均到达时长来自真实提交/回执时间。 |
| TC-USER-SAFE-001 | 分别查询运营端、客户端用户列表和详情,并尝试跨租户访问。 | 响应不含 `passwordHash`、`sessionVersion`、密钥或认证内部字段;跨租户查询、更新、改密、禁用和删除由 API 拒绝。 |
| TC-USER-CONTINUITY-001 | 当前用户删除/停用自己,删除或降权最后一个平台管理员、最后一个企业管理员,并创建重复用户名。 | 前三类操作返回 403;最后管理员保护生效;唯一冲突返回 409 和冲突字段,不出现 500。 |
| TC-HTTP-SEND-002 | 仅传 `mobile/content`,正文使用已审核签名及 `${code}` 模板,调用公开发送接口并重放同一幂等键。 | 自动识别签名/模板并提取合法变量,经真实发送链返回 202、稳定 messageId;相同请求返回同一结果,不出现批次 404。 |
| TC-TEMPLATE-VARIABLE-001 | 提交空变量、未闭合、中文、重复、超长和非法字符变量,再查看发送候选。 | 前后端均拒绝非法变量;候选只含 approved 签名/模板,disabled/rejected 仅在历史管理视图显示。 |
| TC-REPORT-MATERIAL-SAFE-001 | 下载官方 XLSX;按筛选导出;导入空文件、错误扩展名、超限文件、含公式/脚本单元格及部分错误行文件。 | 模板和导出为真实 XLSX;危险文件在入库前拒绝,部分失败保留行级原因;日志含操作人、文件名、筛选和计数,不含敏感请求体。 |
### 17.16 客户端用户管理移动操作可达性回归
| 用例编号 | 操作 | 预期结果 |
| --- | --- | --- |
| TC-UIUX-P0-001 | 使用真实企业管理员登录客户端并打开`/client/users`,依次设置390×844和375×667。 | 真实`/api/client/users`返回的用户以卡片显示;编辑、改密、禁用/启用、删除形成2×2操作网格,全部位于视口和卡片裁切范围内。 |
| TC-UIUX-P0-002 | 在390和375视口测量四个操作按钮并逐项执行命中测试。 | 每项高度至少44px,中心点命中自身按钮,页面根节点无横向溢出;操作组可访问名称包含目标用户。 |
| TC-UIUX-P0-003 | 在375视口点击删除,读取确认内容后点击取消。 | 确认层显示目标用户名;取消后用户仍在列表,PostgreSQL记录保持active且没有执行删除。 |
| TC-UIUX-P0-004 | 依次设置768×1024、1366×768、1440×900并复核同一用户。 | 平板四项操作全部可见且热区≥44px;1440首屏完整;1366即使存在内部横向滚动,滚动后删除必须完整可达,且页面级无横向溢出。 |
| TC-UIUX-P0-005 | 完成五视口操作后检查浏览器控制台并执行前端/API构建与用户服务回归。 | 无新增console error/warn;前端与API build通过,用户服务测试通过,`git diff --check`通过。 |