feat: add report material workflows and gateway safeguards

This commit is contained in:
hectorzhao
2026-07-15 18:23:48 +08:00
parent cf9f4ce4cd
commit 7091a8bed4
41 changed files with 3606 additions and 71 deletions
+16 -3
View File
@@ -234,7 +234,8 @@
7. Gateway 必须实现真实 CMPP Submit,包括短信内容编码、长短信拆分、RegisteredDelivery、serviceId、srcId、destTerminalId、msgFmt、feeType/feeCode 等字段映射。
8. Gateway 必须消费 NestJS 投递的 `SubmitCommand` 队列或等价内部接口;提交成功、提交失败、超时均必须回传 `SubmitResult`,不得只停留在 API 侧入队。
9. Gateway 必须按通道连接和窗口容量控制并发,处理窗口满、SMSC 慢响应、sequence 回绕、连接断开时的在途消息状态。
10. Gateway 不承担业务审核、计费、签名报备、通道组路由、黑名单或敏感词判断;这些由 NestJS 完成,Gateway 只执行已授权通道提交与协议事件回传
10. Gateway 必须对每个物理通道执行 Redis 分布式 TPS 限速。`SmsChannel.rateLimitPerSecond` 是单通道上限,不是平台总上限;同一通道被多个通道组或多个 Gateway 实例使用时共享同一额度,不同通道独立计数。通道连接命令下发的配置值是最终上限,提交命令携带的值只能进一步降低、不能放大该上限。超流速消息必须继续保留在 Redis Stream pending 中等待可用时隙,不能因等待直接标记发送失败;Gateway 重启后仍可由 consumer group 恢复。worker 必须并发处理一个读取批次,让不同通道独立等待,不能因低 TPS 通道造成其他通道队头阻塞;单通道实际并发仍由 Redis 限速和 CMPP 窗口共同约束
11. Gateway 不承担业务审核、计费、签名报备、通道组路由、黑名单或敏感词判断;这些由 NestJS 完成,Gateway 只执行已授权通道提交与协议事件回传。
#### 4.8.2 下游客户 CMPP 接入能力
@@ -278,7 +279,8 @@
- 已实现 SubmitCommand 在途恢复第一版:Go Gateway submit worker 在消费新消息前会对 Redis Stream consumer group 中空闲超过阈值的 pending 命令执行 `XAUTOCLAIM`,重新提交并按正常成功路径 ack,避免 Gateway 重启后命令永久滞留在 PEL。
- 已实现上游连接断开时的 pending submit 状态补偿第一版:如果某条上游 CMPP 连接在收到 submit resp 前断开,Gateway 会立即唤醒该连接上等待中的 pending submit,请求返回 `timeout/CONNECTION_LOST`,由 NestJS 进入既有补发或释放冻结逻辑,不再只依赖固定超时。
- 已实现“上游可能已受理但 submit resp 丢失”场景的保守补偿第一版:Gateway 在 receipt 事件中补充手机号;NestJS 对无法按 `messageId/gatewayMessageId` 精确命中的回执,只在“同通道、同手机号、72 小时窗口内、且仅存在 1 条 `timeout + gatewayMessageId=null` 的 submit 记录”时才回填并接收该回执,避免误绑到其他短信。
- 已实现 SubmitCommand 死信治理第一版:Go Gateway 对多次处理仍失败的 `SubmitCommand` 不再无限滞留在 PEL,而是按阈值写入 NestJS 真实 `GatewaySubmitDeadLetter`;运营端后端接口可分页查询死信,并支持人工将原始 `SubmitCommand` 重新写回 Redis Stream
- 已实现 Gateway 提交异常治理:Go Gateway 对多次处理仍失败的 `SubmitCommand` 不再无限滞留在 PEL,而是按阈值写入 NestJS 真实 `GatewaySubmitDeadLetter`(数据库表名和内部接口保留技术兼容名,页面统一称“Gateway提交异常”)。运营端 `/admin/gateway-submit-exceptions` 提供真实分页、筛选、汇总、脱敏详情和单条重新入队;原始 payload、密码、密钥不得返回浏览器。重新入队必须要求近期认证、填写原因、勾选“已确认上游未受理”,并校验短信尚未 accepted/submitted/delivered/unknown、通道 active 且 connected、人工次数小于 3;服务端以 pending 到 requeueing 的原子状态抢占防止重复点击,成功写回 Redis Stream 后记录操作人、原因、Stream ID 和时间。收到后续 SubmitResult 时必须将对应异常记录闭环为 resolved
- 已实现 Gateway 通道级 Redis 限速:NestJS 入队前保留业务层通道限速,Go Gateway 在真正调用上游 Submit 前再次按通道 ID 预约发送时隙;连接命令把权威 TPS 写入 Redis,提交按权威值与消息值的较小者执行。普通 Stream 消息在等待期间不 ACK、不转失败,多实例共同使用同一限速状态;worker 对同批消息并发调度,低 TPS 通道等待不阻塞其他通道。
- 已实现客户侧下游投递重试第二版:客户系统负责断线后重连;平台在客户离线或投递失败时把 Deliver Receipt/上行 Deliver 保留在 `CmppDownstreamDelivery`,客户 bind 成功后立即拉取 pending,且 Gateway 会对当前在线账号周期补投;超过重试上限后转 `failed` 并写失败审计。
- 已实现下游投递失败审计与人工重投第一版:运营端后端与页面可分页查看 `CmppDownstreamDelivery` 的 pending/awaiting_ack/failed/unconfirmed/rejected/delivered 记录,支持按状态、类型、应用和关键字筛选,并可对非 `awaiting_ack` 记录执行人工重投,真实调用 Gateway `/downstream/receipt``/downstream/uplink`。主记录必须分开保存自动重试次数 `retryCount`、人工重投次数 `manualRetryCount` 和最近人工重投时间 `lastRetriedAt`,操作日志保留重投前状态与自动重试次数。
- 已实现下游投递批量重投第一版:运营端可在当前页勾选多条 `pending/failed` 下游投递记录,调用真实批量接口逐条重投并返回成功/失败汇总,不允许用前端循环假装成功。
@@ -315,7 +317,7 @@
- 客户侧 Deliver 重投当前已经具备账号级周期恢复、退避、Redis token 租约锁和控制面状态观测;恢复状态已同步到 NestJS 持久化审计表并进入运营端独立页面,且具备第一版失败分类分析和多 Gateway 抢占协调。
- 普通上行匹配已覆盖 messageId、接入号、手机号时间窗口和共享接入号多候选人工认领;后续仍需补更复杂的批量认领、认领规则推荐和认领准确率指标。
- 长短信分片当前已具备真实分片提交/回执/补偿审计,运营端短信详情可查看 `SmsMessageSegmentAudit`;后续仍需补“按单个分片自动重投”和分片级人工补偿操作。
- 多连接窗口当前覆盖单进程内连接池和窗口满等待;在途恢复当前覆盖 Redis Stream pending claim、连接断开时的 pending submit 唤醒、receipt 驱动的保守唯一候选补偿、SubmitCommand 死信入库/人工重入队第一版,以及下游客户在线时的周期补投、Gateway 重启后的恢复候选扫描、账号级 Redis token 租约锁/退避/状态观测、恢复状态入库/运营端可视化;尚未实现连接级状态持久化、窗口指标回写、死信后台自动重试策略、长恢复任务锁续租,以及“上游已受理但 submit resp 丢失”场景的强确认或完整幂等补偿。
- 多连接窗口当前覆盖单进程内连接池和窗口满等待;在途恢复当前覆盖 Redis Stream pending claim、连接断开时的 pending submit 唤醒、receipt 驱动的保守唯一候选补偿、Gateway 提交异常转存/安全人工重入队,以及下游客户在线时的周期补投、Gateway 重启后的恢复候选扫描、账号级 Redis token 租约锁/退避/状态观测、恢复状态入库/运营端可视化;尚未实现连接级状态持久化、窗口指标回写、提交异常后台自动重试策略、长恢复任务锁续租,以及“上游已受理但 submit resp 丢失”场景的强确认或完整幂等补偿。
基于当前真实代码,CMPP 端到端链路剩余缺口可以明确收敛为以下几类:
@@ -698,6 +700,8 @@
9. 按企业应用绑定的对应运营商通道组执行路由:通道组只能是移动、联通、电信之一,发送时必须同时满足路由规则运营商、通道组运营商、通道组明细 carrier 与号码识别运营商一致;再校验通道本体 carrier 为对应运营商或三网;最后先匹配省网通道,再匹配全国通道,不得直接绑定或 fallback 到非授权单通道。
10. 过滤业务 disabled、连接离线、认证失败、心跳超时或无可用连接数的通道。
11. 通过通道限速器控制 TPS;优先队列不得突破通道配置的供应商 TPS 和连接窗口上限。
- TPS 按物理通道独立计数,不是整个平台共享一个总额度。例如通道 A、B 均配置 100 TPS 时,各自最多 100 TPS,平台理论合计为 200 TPS。
- 同一物理通道被多个通道组使用时,共享 `SmsChannel.rateLimitPerSecond` 的同一额度;通道组成员不提供单独流速配置,避免多个组并发使用同一供应商账号时重复计算额度。
12. 调用 Gateway Adapter 提交短信。
13. 写入 submit 状态。
14. Submit rejected、submit timeout、Gateway 连接断开或未提交成功、receipt failed 等场景按通道组策略补发到下一可用全国通道;unknown、超过 72 小时、超过通道组补发时间上限或关闭补发时不再补发。
@@ -1473,3 +1477,12 @@
然后按计划逐步实现。每完成一步都要运行构建或测试,并更新文档。
```
### 2026-07-15 签名与引流资料批量导入、通道映射及统一报备
1. 运营端在“报备任务”下提供“待报备资料”工作台。WPS 在线表格须先由用户另存为 `.xlsx`,系统读取真实工作簿、工作表、表头、单元格和内嵌图片;原始文件及拆出的图片写入 MinIO,导入批次、映射和业务资料写入 PostgreSQL,不支持用 CSV、前端静态数组或浏览器本地存储冒充图片导入。
2. 导入分为“解析预览”和“确认入库”两步。用户可指定企业、企业应用、资料类型、表头行数、数据起始行并复用映射方案;每个源列可映射到签名名称、用途、所属签名、站点名称、URL、备注或报备字段库中的动态字段,同时配置文本/图片/文件、必填和转换规则。源文件字段名称和顺序不固定,映射方案必须可持久化复用。
3. 导入和业务页面的新建/修改只将已审核签名或引流信息标记为待报备,并递增材料版本;不得在每次导入后自动创建通道报备任务。运营人员可跨签名、跨引流信息勾选资料,一次创建统一报备批次。
4. 创建批次时按每条资料所属企业应用的当前生效路由规则展开所有通道;一个签名走多个通道时,必须为每个通道创建或重置独立报备任务并生成一份该通道的 `.xlsx`。无生效路由、通道未配置字段或缺少通道必填资料时,该资料继续保留在待报备池,任务进入“资料待补充”,不得伪装为已完成。
5. 通道“配置签名报备字段”和“配置引流信息字段”弹窗使用字段池,按资料类型分别配置。每列包含标准字段、通道导出表头、列顺序、必填、说明、列宽、文本转换、缺省值以及图片宽高;导出表头和列顺序必须严格使用通道配置,不受导入表格原始名称和顺序影响。
6. 通道导出文件必须为 WPS/Excel 可打开的 `.xlsx`,图片直接内嵌到对应单元格区域,而不是仅写 MinIO URL 或本地路径。批次保留所选材料版本快照、通道文件、行号和通道任务关联,可从最近批次直接下载每个通道文件。
+4
View File
@@ -64,6 +64,8 @@ PROD_ADMIN_PASSWORD='change-me'
`API_ENABLE_SEND_WORKER=true` 是生产发送链路必填项。后续发布脚本会在构建和迁移前校验该开关以及正整数 `API_SEND_WORKER_CONCURRENCY`;缺失时直接终止发布,防止 API/Gateway 健康但 BullMQ 短信队列无人消费。
Gateway 的最终 TPS 防线依赖与 API 相同的 Redis。通道连接时会写入 `rate:gateway:channel:config:<channelId>` 权威上限,实际预约使用 `rate:gateway:channel:<channelId>`;这些 key 不应在正常发布时清理。多 Gateway 实例必须指向同一 Redis,才能共享单通道额度。超速的 `gateway.submit.commands` 消息会保持在 consumer group pending 中等待,不应通过手工 `XACK` 或删除 Stream 处理积压;先检查通道配置、Redis key、consumer group 和 Gateway 日志。
日报任务默认启用,并由 `REPORT_REFRESH_INTERVAL_MS` 每小时检查一次北京时间业务日是否变化;每个业务日只执行一次 T-4 至 T-1 重算。服务重启后也会自动补跑最近四个完整自然日,确保 72 小时回执更新反映到对账和利润报表。
如生产验证服务器临时无法稳定下载 MinIO,可显式传入 `OBJECT_STORAGE_DRIVER=local`,文件会通过真实 API 保存到服务器本地目录 `OBJECT_STORAGE_LOCAL_ROOT``cmpp-minio` 服务会跳过安装和启动。该模式只建议用于验证环境;正式生产建议恢复 `OBJECT_STORAGE_DRIVER=minio`
@@ -103,6 +105,8 @@ curl http://127.0.0.1:12026/
redis-cli -h 127.0.0.1 -p 6379 ping
pg_isready -d "$(grep '^DATABASE_URL=' /etc/cmpp-platform/cmpp-platform.env | cut -d= -f2-)"
grep -E '^(API_ENABLE_SEND_WORKER|API_SEND_WORKER_CONCURRENCY)=' /etc/cmpp-platform/cmpp-platform.env
redis-cli --scan --pattern 'rate:gateway:channel:*'
redis-cli XINFO GROUPS gateway.submit.commands
```
## 回滚
+42 -9
View File
@@ -1306,21 +1306,21 @@
- 如果存在多条候选或无候选,则拒绝归因,不得误绑到其他短信。
- 已经由新通道成功送达的短信,旧尝试迟到回执仍只记历史,不覆盖最终送达状态。
### TC-GW-012 SubmitCommand 死信入库与人工重入队
### TC-GW-012 Gateway 提交异常入库与人工重入队
- 优先级:P0
- 前置条件:Gateway submit worker 已连接 Redis Stream `gateway.submit.commands`;准备一条会持续触发处理错误的 `SubmitCommand`NestJS `/gateway/events/dead-letter` 和运营端 `/api/admin/operations/gateway-submit-dead-letters` 真实可用。
- 前置条件:Gateway submit worker 已连接 Redis Stream `gateway.submit.commands`;准备一条会持续触发处理错误的 `SubmitCommand`NestJS 内部异常上报接口和运营端 Gateway 提交异常 API 真实可用。
- 步骤:
1. 让同一条 `SubmitCommand` 连续处理失败,达到 Gateway 配置的死信阈值。
1. 让同一条 `SubmitCommand` 连续处理失败,达到 Gateway 配置的异常转存阈值。
2. 检查 Redis PEL 中该消息是否被 ack,不再无限 pending。
3. 检查 NestJS 是否在真实数据库写入一条 `GatewaySubmitDeadLetter`,保存失败原因、尝试次数和原始命令载荷。
4. 调用运营端真实接口查询死信列表
5. 调用人工重入队接口,将该死信重新写回 `gateway.submit.commands`
4. 运营端“Gateway提交异常”页面查询异常列表并打开脱敏详情
5. 完成风险确认、原因和状态校验后,将该异常命令重新写回 `gateway.submit.commands`
- 预期结果:
- 达到阈值后,Gateway 会把该消息转为死信,而不是永久卡在 PEL。
- 死信记录来自真实数据库,包含 `streamMessageId``messageId/submitId`、失败原因尝试次数和原始 `SubmitCommand`
- 人工重入队成功后,死信状态更新为 `requeued`,记录新的 Redis Stream 消息 ID,并写系统日志。
- 重入队后如后续收到真实 `SubmitResult`,对应死信记录应自动转为 `resolved`
- 达到阈值后,Gateway 会把该消息转存为提交异常,而不是永久卡在 PEL。
- 异常记录来自真实数据库,包含 `streamMessageId``messageId/submitId`、失败原因尝试次数;浏览器端只收到脱敏命令,不收到原始 payload 或密码密钥
- 人工重入队成功后,异常状态更新为 `requeued`,记录新的 Redis Stream 消息 ID、操作人和原因,并写系统日志。
-入队后如后续收到真实 `SubmitResult`,对应异常记录应自动转为 `resolved`
### TC-GW-013 下游客户在线时周期补投与失败封顶
@@ -3213,6 +3213,15 @@ npm run test:gateway
npm run verify:phase8
```
## 2026-07-15 通道 TPS 归属与通道组成员字段清理
| 用例编号 | 场景 | 操作 | 预期结果 |
| --- | --- | --- | --- |
| TC-CHANNEL-TPS-001 | 单通道限速 | 通道 A 配置 100 TPS,持续提交超过 100 条/秒 | PostgreSQL 保存 `SmsChannel.rateLimitPerSecond=100`NestJS 以通道 A 的 ID 建立 Redis 限速桶;单个自然秒放行不超过 100 条。 |
| TC-CHANNEL-TPS-002 | 多通道独立限速 | 通道 A、B 均配置 100 TPS,并发向两个通道提交 | A、B 使用不同通道 ID 的 Redis 限速桶,各自最多 100 TPS;平台不存在共享的 100 TPS 总额度。 |
| TC-CHANNEL-TPS-003 | 多通道组共享同一物理通道 | 两个通道组同时选中通道 A 并产生发送 | 两组发送共享通道 A 的同一个 100 TPS 限速桶,合计不超过通道 A 配置;通道组成员接口和页面均无单独流速字段。 |
| TC-CHANNEL-TPS-004 | 数据库结构清理 | 执行 Prisma migration 并读取 `SmsChannelGroupItem` 结构 | `SmsChannelGroupItem.rateLimitPerSecond` 已删除,通道自身的 `SmsChannel.rateLimitPerSecond` 保留。 |
测试环境必须具备 PostgreSQL、Redis、MinIO 或等价本地服务后,才能将 E2E smoke 和真实 API HTTP 测试记为系统功能通过;缺失时对应用例标记为阻塞或未执行。
## 17. 新增和更新用例细化执行清单
@@ -3419,3 +3428,27 @@ npm run verify:phase8
| TC-GW-ACK-005 | 客户 Submit 后由业务校验立即生成失败回执,并覆盖在线即时投递、Gateway 重启后恢复投递;另模拟客户端对 `Msg_Id=0` 返回 Result=0。 | 客户收到的第一个响应包必须是对应 `CMPP_SUBMIT_RESP`,之后 Deliver 的 `Msg_Id` 非 0 且与 SubmitResp 完全一致;重启后根据持久化 Submit Sequence_Id 重建同一 Msg_IdResult=0/Msg_Id=0 不得写为 delivered。 |
| TC-GW-ACK-006 | 对一条 Gateway 已丢失原消息映射且 payload 缺少 `submitSequenceId` 的历史状态回执执行人工重投;另对字段完整但客户离线的记录重投。 | 缺少序列号的记录由 Gateway 返回 `retryable=false/MISSING_SUBMIT_SEQUENCE_ID`NestJS 立即终结为 `failed` 并保存明确原因;客户暂时离线的记录返回 `retryable=true/CLIENT_DISCONNECTED`,按次数上限和指数退避继续处理,不得无限保持 `retryCount=0`。 |
| TC-GW-ACK-007 | 构造一条从未重投且 pending 超过 72 小时的记录,以及一条人工重投后尚未满 72 小时但原 `createdAt` 很早的记录,执行自动扫描并查看告警。 | 第一条自动转为 `failed/queue_timeout`;第二条仍保持 pending,终结时间和 10 分钟积压告警均从 `lastRetriedAt` 重新计算,刚重投后不立即告警。 |
### 17.12 签名与引流资料导入及统一通道报备
| 用例编号 | 操作 | 预期结果 |
| --- | --- | --- |
| TC-REPORT-MATERIAL-IMPORT-001 | 将含两行表头、文本列和营业执照/身份证等内嵌图片的 WPS 在线表格另存为 `.xlsx`,选择企业、应用和签名资料后解析。 | NestJS 读取真实工作表及图片锚点,返回列、组合表头、前十行和图片数预览;原文件写 MinIO,导入批次写 PostgreSQL;未确认前不改签名、不建通道任务。 |
| TC-REPORT-MATERIAL-IMPORT-002 | 将源列分别映射到短信签名、签名用途和动态报备字段,调整数据类型/必填/转换规则,保存映射方案后确认导入;再用列顺序不同但表头相同的文件复用方案。 | 新签名或已存在签名的资料真实入库,内嵌图片拆出并写 MinIO 引用,材料版本递增且进入待报备池;映射方案持久化并可再次选择,源列顺序不影响目标字段。 |
| TC-REPORT-MATERIAL-IMPORT-003 | 导入引流资料,将所属签名、站点、URL、备注和动态图片映射后确认;其中一行引用不存在或未审核签名。 | 合法行创建/更新真实 `SmsDrainageInfo` 并进入待报备池;非法行记录行号和原因,批次为部分失败,不因单行错误回滚其他合法行,也不自动创建报备任务。 |
| TC-REPORT-CHANNEL-FIELD-001 | 在同一通道分别打开签名和引流字段配置,添加字段、修改通道表头、上下排序、设置必填/列宽/图片宽高后保存并刷新。 | 两类配置相互独立且完整持久化;刷新后字段池、映射表头和顺序一致;重复字段、停用字段和非法尺寸由 API 拒绝或归一化。 |
| TC-REPORT-BATCH-001 | 一个应用配置两个生效通道,选择一个待报备签名创建统一批次。 | 系统从真实应用路由展开两个通道,生成两个独立通道任务和两个 `.xlsx`;每个文件表头名称、列顺序和列宽均来自对应通道配置,批次可下载两份文件。 |
| TC-REPORT-BATCH-002 | 两个通道对同一标准字段配置不同表头和顺序,并包含图片列,生成批次后分别用 WPS 打开。 | 两份工作簿各自使用对应通道映射,图片直接显示在数据行内且尺寸按通道配置;文件不是 URL 清单,文本与图片属于同一材料快照。 |
| TC-REPORT-BATCH-003 | 分别制造无生效路由、通道未配置字段、缺少通道必填图片,再创建批次。 | 对应资料不会清除待报备标记;有通道但资料不全时任务为 `waiting_material` 并记录原因;批次为部分失败,无任何假成功任务。 |
| TC-REPORT-BATCH-004 | 同一签名修改资料后再次选择生成批次。 | 材料版本递增;复用同一签名/通道任务并重置到新一轮状态,批次项目保留当次版本和快照,历史导出文件仍可追溯。 |
### 17.13 Gateway 提交异常与通道级 TPS 限速
| 用例编号 | 操作 | 预期结果 |
| --- | --- | --- |
| TC-GW-SUBMIT-EXCEPTION-001 | 制造一条超过 Gateway 最大处理次数的真实 `SubmitCommand`,打开运营端“Gateway提交异常”,按状态、应用、通道和关键字筛选并查看详情。 | 记录写入 PostgreSQL,页面汇总、分页和详情来自 NestJS API;手机号脱敏,命令中的密码、密钥和原始 payload 不返回浏览器,页面不使用“死信”作为业务名称。 |
| TC-GW-SUBMIT-EXCEPTION-002 | 对短信仍处于 pending/failed、通道 active 且 connected 的异常记录,输入 5~500 字原因,勾选“已确认上游未受理”并重新入队。 | 近期认证通过后服务端原子抢占记录、真实写入 Redis Stream;记录变为 requeued,人工次数、操作人、原因、Stream ID 和时间完整留痕,收到 SubmitResult 后变为 resolved。 |
| TC-GW-SUBMIT-EXCEPTION-003 | 不勾选确认、原因过短、重复点击同一记录,或分别把短信置为 accepted/submitted/delivered/unknown、把通道置为停用/断开、人工重试达到 3 次后尝试重新入队。 | API 拒绝危险或重复操作,不产生额外 Stream 命令;页面显示可读原因,操作日志不伪造成功。 |
| TC-GW-RATE-001 | 给通道 A 配置 10 TPS,连续投递 20 条;通道 B 同时配置 20 TPS 并投递,另让提交命令携带高于通道配置的数值。 | Gateway A 实际提交节奏不超过 10 TPS,B 独立按自身额度执行;消息值不能放大 A 的权威上限,同一通道跨通道组共享额度。 |
| TC-GW-RATE-002 | 超过通道 TPS 后观察 Redis Stream consumer group,并在存在等待消息时重启 Gateway。 | 超流速消息保留在 Stream pending,不直接失败;重启后通过 PEL/XAUTOCLAIM 恢复并继续按通道 TPS 排队提交,不丢失、不重复 ACK。 |
| TC-GW-RATE-003 | 启动两个共享同一 Redis 的 Gateway 消费实例,同时向同一通道发送,再向两个不同通道发送。 | 同一通道的两个实例共享 Redis 限速额度,总 TPS 不叠加;不同通道使用独立 key,不被合并成平台总 TPS。 |
+23
View File
@@ -1877,6 +1877,7 @@ git diff --check
## 2026-07-14 服务端安全会话与自动锁定
- 将可预测的 `dev-token:userId:sessionVersion` 和 localStorage 访问令牌替换为 256 位随机会话标识;浏览器只通过 HttpOnly、SameSite Cookie 携带,Redis 使用会话标识 SHA-256 键保存真实状态。生产模式 Cookie 默认 `Secure`;本次按用户要求部署到现有 HTTP 生产验证环境时显式配置 `SESSION_COOKIE_SECURE=false`,正式生产切换 HTTPS 后必须恢复为 `true`
- 运营端/客户端无操作阈值分别为 60/120 分钟,提前 5 分钟提醒;超时进入密码锁屏,4 小时内可用当前密码解锁并轮换会话标识,超过后完整登录。绝对会话时长 12 小时不可滑动续期;敏感操作最近密码认证窗口为 30 分钟。
- NestJS 中间件对运营端和客户端受保护 API 强制要求 Redis 会话,逐次校验用户状态和 `sessionVersion`Gateway 回调和 health 保持原内部链路,不被浏览器会话门禁拦截。自动轮询只有检测到近期真实浏览器操作时才携带活动标识,不能长期保活无人值守会话。
- 用户/权限、企业状态、应用密钥、通道和路由、报备状态、手工充值/退款/调整已接后端最近认证 Guard。前端收到 `RECENT_AUTHENTICATION_REQUIRED` 后要求当前密码,成功后自动重试;普通 JSON、Blob 和文件上传统一处理会话 401。会话创建、锁定、解锁、再认证和退出写 `OperationLog`,多标签页同步状态。
@@ -1914,3 +1915,25 @@ git diff --check
- 工作区完整改动已提交并 push`a7a4e8d9f6aba00b8137e5b70c59bdef67ed3b57``feat: polish reporting templates and shared controls`)。部署前确认本地 `main``origin/main` 一致,并备份生产 PostgreSQL、运行源码和环境配置至 `/opt/cmpp-platform/backups/releases/20260715-163418`;三份备份均通过 SHA-256 复核和压缩包完整性检查,其中数据库备份 SHA-256 为 `a59d3b7f4538f99cdc73a1092ac31628efa45bc38d4adf20a05d6a1f075ec5da`,运行源码备份为 `305c529fb94c71fd6e49f6228717b2ce93f14fff84a84e477d1f202f3192ef58`
- 发布快照本地与服务器 SHA-256 均为 `489d6c969252403689dfdcca15b87e4f5a93b65187551fa6650451527366942e`。生产 `.deployed-commit=a7a4e8d9f6aba00b8137e5b70c59bdef67ed3b57`47 条 migration 全部齐全;`cmpp-api``cmpp-gateway`、MinIO、Nginx、PostgreSQL、Redis 均为 active`12026/17890/8090/3000/9000` 正常监听,API/Gateway health、Redis PONG、PostgreSQL readiness 和外部首页/运营端/API HTTP 均通过,部署后 API/Gateway 无 error 级日志。
- 生产浏览器确认登录页加载成功且标题为“聆界短信管理平台”。服务器凭据文件对存量管理员仅记录 `password=unchanged`,不是可用明文密码,因此未擅自重置生产密码;通用 Select 的企业搜索、企业与应用联动、弹窗越界和普通筛选区 Portal 交互已在部署前通过本地真实 NestJS API、PostgreSQL 数据和生产同构建验证,未使用 mock、localStorage 或静态数组。
## 2026-07-15 签名与引流资料批量导入及统一通道报备(未提交)
- 新增真实待报备资料工作台:WPS 表格另存 `.xlsx` 后由 NestJS + ExcelJS 解析多行表头、文本和内嵌图片,原文件及图片走 MinIO,导入批次、可复用映射方案、材料版本和待报备状态走 Prisma/PostgreSQL;导入只更新资料池,不自动生成通道任务。
- 新增统一报备批次:运营勾选新建/修改的签名与引流信息后,按企业应用当前生效路由展开全部通道,每通道生成一份内嵌图片的 `.xlsx`,并关联批次材料版本快照、文件行号和真实通道报备任务。无路由、未配置通道字段或缺必填资料不会清除待报备标记。
- 通道签名/引流字段配置按设计基线恢复为字段池和已选字段双栏,支持通道导出表头、顺序、必填、列宽、图片尺寸、缺省值和文本转换;导出严格使用各通道自己的映射名称与顺序。
- Prisma migrations `20260715190000_add_report_material_import_export_workflow``20260715193000_scope_channel_report_fields_by_type``20260715194000_initialize_existing_report_material_pending` 已在本地真实 PostgreSQL 成功应用,50 条 migration status 齐全;字段范围迁移将旧 `both` 配置拆成签名/引流两份,并允许同一标准字段在两类中使用不同表头和顺序;初始化迁移不把上线前所有历史签名误认成本次新建/修改资料。Prisma validate/generate、API 全量 19 suites/198 项、API build、前端 build、Gateway 全量 Go 测试、根目录与 API 生产依赖 audit、`git diff --check` 均通过;audit 为 0 漏洞,前端仅有既有 Vite chunk size warningJest 仍需 `--forceExit` 退出既有异步句柄。
- 应用内浏览器使用本地真实 NestJS API/PostgreSQL 会话验证 `/admin/report-materials`:真实待报备签名加载成功,勾选后“统一生成通道报备”由禁用变为可用;导入弹窗展示企业/应用、映射方案、表头行和 XLSX 文件控件。进入真实通道报备详情后,签名字段配置弹窗按字段池/导出字段双栏渲染,页面和两次交互均无 console error/warn、无框架错误覆盖。未点击统一生成、未上传客户文件、未写入烟测业务数据。
## 2026-07-15 通道 TPS 配置口径清理(未提交)
- 明确 `SmsChannel.rateLimitPerSecond` 是单个物理通道的 TPS 上限,不是平台总流速;Redis 限速键包含通道 ID,不同通道独立计数。
- 同一物理通道即使被多个通道组引用,也共享通道自身的同一限速桶,不按通道组重复获得额度。
- 删除未参与发送链路的 `SmsChannelGroupItem.rateLimitPerSecond`:Prisma 模型、通道组成员 DTO、真实 API 持久化和前端类型均已清理,并新增数据库迁移删除对应列;通道管理中的 `SmsChannel.rateLimitPerSecond` 和现有 NestJS Redis 限速链路保留。
- 本地真实 PostgreSQL 已应用 `20260715200000_drop_channel_group_item_rate_limit`Prisma validate/generate/migrate status 通过;通道服务定向测试 29 项、API 全量 19 suites/198 项、API build、前端 build、Gateway `go test ./...` 均通过。Jest 仍存在测试完成后异步句柄不自动退出的既有提示,前端仍只有既有 chunk size warning。
## 2026-07-15 Gateway 提交异常处理与最终流速限制(待部署)
- 运营端新增“Gateway提交异常”页面和真实 NestJS API:支持状态/应用/通道/关键字筛选、服务端汇总、脱敏详情及单条重新入队;浏览器响应不包含原始 payload、通道密码、密钥或鉴权字段,数据库和内部兼容接口暂保留 `GatewaySubmitDeadLetter` 技术命名。
- 重新入队增加近期认证、人工原因、明确确认上游未受理、短信状态、通道状态/真实连接状态、最多 3 次以及 pending→requeueing 原子抢占校验;写入 Redis Stream 失败会恢复 pending,成功记录操作人、原因、Stream ID 和时间,后续 SubmitResult 自动闭环 resolved。
- Go Gateway 新增 Redis 分布式单通道限速。连接命令保存权威通道 TPS,提交取权威值与消息值的较小者;同一通道在多实例和多个通道组间共享额度,不同通道独立。Stream 消息超速时保持未 ACK 并等待,不作为发送失败,重启后继续使用既有 pending 恢复机制;worker 对同批消息并发处理,避免低 TPS 通道等待阻塞其他通道。
- 定向验证已通过:Gateway `internal/ratelimit``internal/control``internal/submitworker`API `operations.service.spec.ts``send-chain.service.spec.ts` 共 73 项。最终本地门禁通过:Prisma validate/generate、51 条 migration status、API 19 suites/199 项、API build、前端 build、Gateway `go test ./...``git diff --check`;前端仅有既有 chunk size warningJest 仍需 `--forceExit` 退出既有异步句柄。应用内浏览器确认受保护新路由真实跳转运营端登录、标题和 DOM 正常、console 无 error/warn;因真实图形验证码未获授权代解,未绕过认证或注入会话。生产备份、提交、push 与部署结果在发布后补录。