Files
lislgosms/docs/testing-progress.md
T

1917 lines
201 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第一版系统化测试进度
## 2026-07-15 短信模板签名自动填充与真实校验(未提交、未部署)
- 客户端和运营端短信模板表单将签名改为必选;选择签名时自动在模板内容开头填入规范 `【签名】`,切换签名只替换原前缀并保留正文,清空选择时移除自动前缀。
- 两套内容输入框均明确提示“模板内容必须以所选签名开头”,字符数、变量识别和计费条数继续基于包含签名的完整内容计算。
- NestJS 在模板创建、编辑和提交审核时校验真实签名归属及完整内容前缀,阻止绕过页面提交缺失或不匹配签名的模板。SmsConfig 定向 1 suite/33 项、API 全量 18 suites/194 项、Prisma validate/generate/migrate status47 条 migration 已应用)、API build、前端 build、Gateway `go test ./...``git diff --check` 均通过;Jest 仍有既有 open-handle 提示,相同全量测试加 `--forceExit` 复核退出码为 0。浏览器交互结果待完成后回填。
- 应用内浏览器可正常加载最新本地构建,页面标题、登录表单和控制台均正常;本地会话已过期并跳转图形验证码登录页,未绕过验证码进入模板表单,因此没有把目标表单交互误记为浏览器通过。自动填入/替换逻辑由共享纯函数、前端生产构建和真实后端定向/全量测试覆盖,仍建议登录后补一次可见交互复测。
## 2026-07-15 运营列表排序、短信批量驳回与通道成本展示(已提交、已部署)
- 短信审核页增加“驳回已选”,统一填写非空原因后调用真实 `/admin/risk-review/tasks/batch/reject`;NestJS 对 id 去重、限制单批最多 100 条并逐项执行现有风控拒绝和发送链路拒绝处理,不使用前端本地状态冒充完成。
- 企业管理接口按 PostgreSQL 当日短信消费 `todaySpendCents` 降序返回;企业应用管理接口按 PostgreSQL 当日短信记录 `sentToday` 降序返回;两者相同指标均以名称和 id 稳定排序。
- 通道列表成本继续读取真实 `unitPrice`,仅将展示格式调整为“X.XX 分”,不修改计费或成本快照精度。
- 功能与接入号改动提交 `3e114ee7294e502ab8a65f84891fd2e1258a00c1` 已 push 并部署生产;发布包本地/服务器 SHA-256 均为 `8d84c15d2d95c79c38a57af8e74588f1ef52f5550e6eb54d881eb2cb74ac7d1d`
- 部署前生产 PostgreSQL、运行源码和环境文件备份目录为 `/opt/cmpp-platform/backups/releases/20260715-154713`;三份文件均非空并通过 `sha256sum -c`,数据库、源码、环境 SHA-256 分别为 `12c372c9ccc65a815444b59cd5856864128b25f137a5fc141bec91084b2f0921``8100e7f88275e87fe7ccca89ceeb76443488335972076a767c6c29d64b944712``87ab2efd509b39849b0ee22ead1b4cf0568fba4ab8525c59b5c78da51c73bc86`
- 本地定向 3 suites/46 项、API 全量 18 suites/192 项、API build、前端 build、Gateway 全量 Go 测试、Prisma validate/generate/migrate status 和 `git diff --check` 均通过。Jest 仍有既有 open-handle 退出提示,使用相同全量测试集加 `--forceExit` 复核退出码为 0。
- 生产第 47 条 migration 已成功应用且无待执行项;API、Gateway、MinIO、Nginx、PostgreSQL、Redis 均 active`12026/17890/8090/3000/9000` 监听,内外 health、首页、运营登录页、Redis 和 PostgreSQL 均通过。真实 PostgreSQL 当日企业消费和应用发送量查询按降序返回,产物包含批量拒绝路由、“驳回已选”和接入号填充交互,废弃设计文档已从运行源码删除,部署后 API/Gateway 无新增 error。
- 应用内浏览器确认生产运营登录页标题、表单和验证码正常渲染,无框架错误覆盖或控制台错误;点击验证码后题目由 `43 + 8` 更新为 `46 + 6`。因浏览器无现成运营登录态且图形验证码需要人工确认,未执行登录后批量拒绝的破坏性真实任务操作,相关闭环由真实 API/数据库/产物及后端测试验收。
## 2026-07-15 企业应用接入号填充与扩展码(已提交、已部署)
- 已删除未落地且容易被误作实现依据的 `docs/access-number-upstream-downstream-design.md`,正式口径改为企业应用级“应用扩展码 + 可选客户接入号填充前缀”。
- 企业应用新增真实 PostgreSQL 配置:应用扩展码、填充开关、填充前缀和全局唯一客户侧 `Src_Id`。管理端仅在开启填充时展示可编辑前缀,并实时只读预览客户侧接入号。
- 配置扩展码后,NestJS 在任务、计费和入队前严格校验客户 CMPP `Src_Id`;新短信保存客户侧号码与应用扩展码快照。上游 Submit 只使用“通道基础号 + 应用扩展码”,客户填充前缀不进入上游。未配置扩展码的历史应用保持原通道基础号行为。
- 本地 PostgreSQL 已应用第 47 条 migrationPrisma format/validate/generate/migrate status 通过。SmsConfig/SendChain 定向 2 suites/88 项、API 全量 18 suites/188 项、API build、前端 build、Gateway `go test ./...` 均通过;前端仅有既有 Vite chunk size warning。Jest 全量用例全部通过但存在既有 open handle,使用 `--forceExit` 取得明确退出码 0。应用内浏览器确认本地站点、运营登录页和控制台正常,但真实登录及图形验证码阻断表单页,未将条件显示交互误记为浏览器已验收。
## 2026-07-15 报表与通用报备字段生产发布
- 功能提交 `8c3336600e900667d8bb77fd6e0fb5fe40986c11` 已 push 到 `origin/main` 并部署生产,覆盖对账单、利润报表、发送质量报表和通用报备字段/签名资料动态化全部工作区改动。发布包本地与服务器 SHA-256 均为 `94f59561f1a5a2255bb188007a6ce73fd1549ba33ca9241d05ba3ac4118f2d89`
- 部署前备份 PostgreSQL 为 `/opt/cmpp-platform/backups/cmpp-20260715-142351.sql`(约 65MBSHA-256 `7cedd56e15c402c9e6cc03f0d7b03bc232206361704988e7bfd8a0c8c5ca4cf6`),运行源码为 `/opt/cmpp-platform/backups/cmpp-source-20260715-142351.tar.gz`(约 1.3MBSHA-256 `fb4e7a6e75a68496c4a4bee68db1760dbafdc53dac2357a965ded6d6843f463e`),环境文件备份为 `/opt/cmpp-platform/backups/cmpp-env-20260715-142351`(权限 600SHA-256 `87ab2efd509b39849b0ee22ead1b4cf0568fba4ab8525c59b5c78da51c73bc86`)。
- 生产成功应用第 4446 条 migrationPrisma 确认 46 条 migration 全部齐全。日报启动任务已按北京时间重算 T-4~T-1,生成对账 3 行、利润 4 行、质量 11 行,质量日期范围为 2026-07-11 至 2026-07-14;生产现有通用报备字段为 0,未为验收伪造配置。
- `cmpp-api``cmpp-gateway``cmpp-minio`、Nginx、PostgreSQL、Redis 均为 activeAPI/Gateway/MinIO health、Redis `PONG`、PostgreSQL readiness、`12026/17890/8090/3000/9000` 监听均通过。外部首页、运营端登录页和 API health 均为 HTTP 200CMPP 入站 `17890` 可连接,部署后 API/Gateway journal 无 error,生产根目录与 API `npm audit --omit=dev` 均为 0 vulnerabilities。未发送或重投客户短信。
## 2026-07-15 发送质量报表(已提交、已部署)
- “报表对账”新增“发送质量报表”,包含企业应用、通道、签名、引流信息四个真实 Tab;统一展示日发送条数、成功条数、成功率、平均到达时长、生成时间,并由 API 按发送条数降序分页。
- 新增 `DailyQualityReport` 和第 46 条 migration;质量日报与现有日报任务共同按北京时间 T+1 生成并每日重算 T-4 至 T-1。企业应用/签名/引流按短信最终状态聚合,通道按 accepted submit 与对应 Gateway delivered 回执聚合。
- 平均到达时长使用成功短信的 `deliveredAt - submittedAt`,按日期和维度计算 PostgreSQL `PERCENTILE_CONT(0.95)`,只对小于等于 P95 的样本求平均;无有效成功时间时保存 null。
- `SmsMessageRecord` 新增真实 `drainageInfoId`。新短信在同签名 approved 引流信息中按正文 URL 精确匹配并取唯一最长 URL;migration 对历史短信用相同规则回填,歧义或未命中统一进入“未关联引流信息”,不重复展开。
- 本地真实 PostgreSQL 已应用 46 条 migration,并实际执行四个质量维度的 T-4~T-1 生成 SQL;现有真实短信成功生成企业应用、未关联签名和未关联引流信息质量行,未关联行按企业应用隔离并可正常筛选。Prisma validate/generate/migrate status、报表与发送链路定向 2 suites/58 项、API 全量 18 suites/183 项、API build、前端 build、Gateway 全量 Go 测试、`npm audit` 0 漏洞和 `git diff --check` 通过;前端仅有既有 Vite chunk size warning。
## 2026-07-15 通用报备字段与签名资料动态化(已提交、已部署)
- 新增真实 PostgreSQL `CommonReportField` 配置表,字段库可将字段分别配置为通用签名报备资料或通用引流信息报备资料,并支持真实新增、删除;删除配置不删除字段定义或历史材料。字段被通道或通用配置引用时,字段库定义均不能直接删除。
- 应用报备资料字段统一合并“通用字段 + 当前应用生效通道组/通道字段”,按字段库 ID 去重并合并必填规则。未绑定应用的签名和引流信息仍返回、展示并由 NestJS 强制校验通用字段;通用字段仍保留在通道报备字段的字段库选择项中。
- 运营端企业签名添加/编辑弹窗移除固定签名依据、资质凭证、企业信息和责任人信息,所有资料改由动态字段生成。运营端和客户端的签名、引流信息添加/编辑均加载对应通用字段;客户端新增签名不再走旧固定材料上传,改为把动态值写入真实签名资料 payload,文件继续上传 MinIO/FileObject。
- migration `20260715170000_add_common_report_fields` 已在本地 PostgreSQL 成功应用,当前 45 条 migration 全部齐全。Prisma format/generate/validate、API 定向 2 suites/39 项、API 全量 18 suites/181 项、API build、前端 build 和 `git diff --check` 通过;前端仅有既有 Vite chunk size warning。该功能已随 `8c333660` 提交、push 并部署生产。
## 2026-07-15 报表对账与利润报表(已提交、已部署)
- 在“数据详单”后新增“报表对账”一级菜单及“对账单”“利润报表”二级菜单;两页调用真实 `/api/admin/reports/reconciliation``/api/admin/reports/profit`,提供日期、企业、企业应用、通道、统计维度和服务端分页。
- 新增 `DailyReconciliationReport``DailyProfitReport` 真实 PostgreSQL 表和第 44 条 migration。报表按北京时间 T+1 生成,API 启动后及每日任务重算 T-4 至 T-1;每个日期在独立事务内删除旧聚合并重建,覆盖 72 小时回执更新窗口。
- 发送量和成功量按 `billingUnits` 统计;成功取最终 delivered。企业应用利润的消费取当前 charged 账单,退款不计收入;成本累计所有 accepted 上游提交,因此补发成本不会漏算。通道利润按实际 accepted 尝试及对应 Gateway delivered 回执汇总,收入只归属最终提交。
- `SmsSubmitRecord` 新增成本单价和金额快照,创建提交记录时写入;migration 按当时现有通道价回填历史记录。SubmitResult 更新同时收窄为优先按 `submitId` 更新,避免一次补发结果覆盖同一短信的其他尝试并污染成本。
- 本地真实 PostgreSQL 已成功应用 migration 并实际执行 2026-07-11 至 2026-07-14 的 T-4T-1 生成 SQLPrisma schema validate/generate 和 migrate status 通过,44 条 migration 全部齐全。报表与发送链路定向 2 suites/56 项、API 全量 18 suites/176 项、API build、前端 build、Gateway 全量 Go 测试和 `git diff --check` 全部通过;前端仅有既有 Vite chunk size warning。本批未修改依赖,`npm audit` 延续上一批 0 漏洞锁文件;本轮在线复查因 npm registry TLS 建链连续两次失败,未将网络失败误记为 audit 成功。
## 2026-07-15 依赖安全与今日返还修复(已提交、已部署)
- 生产数据库只读核查确认目标企业当天共有两笔真实返还:提交前路由失败产生消息级 `released=5` 分,最终失败产生 `refunded=5` 分,正确合计为 10 分(页面应显示 `¥0.100`)。原 Dashboard 和企业列表只聚合 `refunded`,因此少算前一笔并显示 5 分。
- 客户端 Dashboard 与运营端企业列表统一按当天 `refunded``released + relatedType=sms_message_record` 汇总;排除任务冻结转扣费时的 `released + relatedType=sms_batch_task`,避免把内部账务转换误当成返还。
- API 依赖升级到 NestJS `11.1.28`、Multer `2.2.0`,并通过 override 将 `@hono/node-server` 固定为 `1.19.13``npm --prefix api audit --json` 已由 3 个 moderate、2 个 high 降为 0。
- multipart 文件上传补充单文件 20MB、字段数、part 数、字段名/值长度和 header pair 限制,继续使用认证后的真实 NestJS API、MinIO 与 `FileObject` 链路。定向回归 3 suites/21 项通过;完整 API 17 suites/173 项、API build、前端 build、Gateway 全量 Go 测试、Prisma validate/migrate status 和依赖 audit 均通过,43 条 migration 全部齐全。前端仅有既有 Vite chunk size warning。
- 功能提交 `a758672436b3ebcd78aab91ba4a8e3cf68cbdfb0` 已 push 并部署。生产两次 `npm ci` 均为 0 vulnerabilities;真实 PostgreSQL 按新口径聚合目标企业当天返还为 10 分,前端产物包含“今日返还金额”。
## 2026-07-15 下游人工重投无限排队修复(已提交、已部署)
- 根因是 Gateway 以空的 `DownstreamSendResult` 同时表达“客户暂时离线”和“原消息映射丢失且缺少 Submit Sequence_Id”,NestJS 收到 `sent=false` 后又不登记失败,导致历史状态回执永久保持 `pending/retryCount=0`
- Gateway 控制结果新增 `retryable/reasonCode/errorMessage`:缺少原 `submitSequenceId` 且无法命中内存映射时返回不可恢复的 `MISSING_SUBMIT_SEQUENCE_ID`NestJS 立即终结为 `failed`;字段完整但客户离线时返回可恢复的 `CLIENT_DISCONNECTED`,失败回调进入真实次数上限与指数退避。
- 新状态回执入队补存 `SmsMessageRecord.cmppSubmitSequenceId`,Gateway 控制面允许在当前连接存在时立即恢复原 `Msg_Id`;不生成伪造或为 0 的 Msg_Id。
- 自动扫描为 pending 增加默认 72 小时绝对终点(`CMPP_DOWNSTREAM_PENDING_TIMEOUT_HOURS` 可覆盖),从 `createdAt` 或最近 `lastRetriedAt` 计算,超时写 `failed/queue_timeout` 和失败操作日志。积压告警同样以最近人工重投时间重新计时,避免刚重投就因旧创建时间立即告警。
- 回归已通过:API 定向 `send-chain/operations` 2 suites、68 项,API 全量 17 suites、173 项,Gateway 全量 Go 测试、Prisma validate、API build、前端 build 和 `git diff --check`;前端仅有既有 Vite chunk size warning。部署后生产原 3 条历史 pending 均已真实终结为 `failed/manualRetryCount=1/retryCount=1`,失败原因明确为 `MISSING_SUBMIT_SEQUENCE_ID`,当前下游投递 pending 为 0。
## 2026-07-15 待审核通知移除下游投递告警(已提交、已部署)
- 生产只读核查确认当前“下游投递告警 3”全部为历史状态回执记录:三条均在人工重投后成为 `pending/manualRetryCount=1/retryCount=0`,因为告警仍按原 `createdAt` 超过 10 分钟判断,立即计入 `stalled_pending`;它们不是待审核任务。
- 右上角“待审核任务”移除下游投递告警菜单项及其数字,通知总数只汇总企业认证、短信、模板、签名和引流信息五类审核。运营看板和下游投递记录页面继续保留独立告警展示。
- “人工重投排队中”表示记录已由人工重投重置为 `pending` 并累计人工重投次数,正在等待 Gateway 对在线客户写出 Deliver 并取得 `CMPP_DELIVER_RESP`,不代表重投已经成功。
- 进一步核查三条历史记录的 `payload.submitSequenceId` 均为空;虽然账号 `910887` 当前 connected 且持续心跳,Gateway 重启后没有原 Submit 的消息映射,也无法安全重建非 0 Msg_Id,因此修复前人工重投没有写出 Deliver 并持续保持 pending。前端 build 与 `git diff --check` 通过;应用内 Browser 连接因插件运行时初始化失败,未使用独立 Playwright 或 mock 页面替代。
- 本批随 `a7586724` 部署,生产源码确认右上角待审核区域不再包含“下游投递告警”;运营看板与下游投递页面仍保留独立告警入口。
## 2026-07-15 `a7586724` 生产发布记录
- 发布前确认本地 `main` 与最新 `origin/main` 无分叉;PostgreSQL、运行源码、环境文件分别备份为 `/opt/cmpp-platform/backups/cmpp-20260715-120751.sql`65MB)、`/opt/cmpp-platform/backups/source-20260715-120751.tar.gz`104MB)、`/opt/cmpp-platform/backups/cmpp-platform-20260715-120751.env`,均非空、权限 `600` 并完成 SHA-256 校验。
- 发布包本地与服务器 SHA-256 均为 `e1f14706389e6c42e89161422b07e6a409213dd54b9ae39b6bcc50dbba53f61e`;生产 `.deployed-commit=a758672436b3ebcd78aab91ba4a8e3cf68cbdfb0`43 条 migration 全部齐全且无待执行项。
- `cmpp-api``cmpp-gateway`、MinIO、Nginx、PostgreSQL、Redis 均 active`12026/17890/8090/3000/9000` 正常监听;API、Gateway、MinIO、Redis、PostgreSQL、外部首页、运营登录页和外部 API health 均通过,HTTP 返回 200。真实 CMPP 账号 `910887` 保持 connected,发布后 API/Gateway journal 无 error 级日志;未主动发送或重投计费短信。
## 2026-07-15 当前工作区汇总发布
- 当前各对话产生的 34 个文件变更已统一提交为 `e47432bc631bf37f4c6a6dcb3576ce3c86370b45` 并 push 到 `origin/main`;提交明确排除 `api/tsconfig.build.tsbuildinfo``logs/`。发布前确认本地 `main` 与最新 `origin/main` 无分叉,API 全量 17 suites/169 项、API build、前端 build、Gateway 全量 Go 测试、Prisma validate/migrate status 和 `git diff --check` 全部通过,前端仅有既有 Vite chunk size warning。
- 部署前生产 PostgreSQL、运行源码和环境文件分别备份为 `/opt/cmpp-platform/backups/cmpp-20260715-112406.sql`65MB)、`/opt/cmpp-platform/backups/source-20260715-112406.tar.gz`20MB)和 `/opt/cmpp-platform/backups/cmpp-platform-20260715-112406.env`;三份文件均非空、权限 `600` 并完成 SHA-256 校验。
- 发布包本地与服务器 SHA-256 均为 `c26d2414df13e535f3ce69d838d299d80680f23576599f264b7043ad1eea713c`。生产成功应用 `20260715090000_track_downstream_manual_retries``20260715153000_remove_disconnected_downstream_sessions`,共 43 条 migration 全部齐全;非 connected 历史连接行已清为 0,下游人工重投字段已可查询。
- 生产 `.deployed-commit=e47432bc631bf37f4c6a6dcb3576ce3c86370b45``cmpp-api``cmpp-gateway`、MinIO、Nginx、PostgreSQL、Redis 均 active`12026/17890/8090/3000/9000` 监听,API/Gateway health、Redis、PostgreSQL、外部首页、运营登录页和外部 API health 均通过。真实 CMPP2.0 账号 `910887` 已重新登录并保持 connected,部署后 API/Gateway 新增 error 为 0。
- 部署未重投 10:52:58 的历史失败短信,也未主动发送新的计费短信。生产 `npm ci` 报告现有依赖 3 个 moderate、2 个 high audit 风险,未阻断本次构建和启动,后续需在独立依赖升级任务中评估处理。
## 2026-07-15 CMPP 变量模板与 direct_send 修复
- 生产只读诊断确认:10:52:58 账号 `910887``18821203795` 提交验证码短信,应用已于 10:52:36 保存 `templateMismatchMode=direct_send`,且存在审核通过的 `${code}` 变量模板;原实现用正文精确相等查询,实际验证码无法匹配占位符,同时模板为空时仅识别 `manual_review`,导致 `direct_send` 错误落入 `TEMPLATE/REJECTD`
- `resolveInboundTemplateCandidate` 先保留精确匹配,再对同应用变量模板执行固定正文全量匹配,提取非空变量值;同名变量重复出现必须取值一致。匹配成功后保存真实 `templateId`,并将变量值传给真实风控评估。
- 新增 `direct_send` 分支:模板不匹配时识别并保存已审核完整括号签名,继续执行风控、余额冻结、真实队列、通道组路由和具体通道签名报备校验;只跳过模板要求,不做无条件放行。`reject/manual_review` 行为保持不变。
- 新增变量模板验证码和 `direct_send` 两项回归;SendChainService 1 suite/49 项通过,API 全量 17 suites/169 项通过,Prisma validate、API build、前端 build 和 `git diff --check` 通过,前端仅有既有 Vite chunk size warning。本修复已随 `e47432bc` 部署,未重投 10:52:58 的短信。
## 2026-07-15 运营端细节修复批次
- 企业签名引流字段统一为“引流信息”并隐藏提交时间;人工充值弹窗移除操作人;待审核通知中的 0 使用黑字灰底。
- 企业应用表单补充每任务号码上限的整任务拒绝说明;企业代码控件不可编辑并跟随 CMPP 6 位账号,NestJS 创建/更新也强制持久化两者相等。
- 手机号段新增真实 DELETE;报备字段 API 返回真实通道引用数,未引用可删除,引用数大于 0 时前端禁用且后端返回 400。
- 客户 CMPP 连接详情只展示已连接会话;Gateway 上报断开时删除活跃连接行,心跳超时扫描也删除,新增 migration 清理旧非 connected 行,断开操作日志仍保留。
- 短信审核新增逐行/全选勾选,批量按钮只处理已选任务;下游投递列表与 Dashboard 增加统一创建日期范围参数并下推 Prisma/PostgreSQL。
- 运营端和客户端短信模板变量均插入当前光标/选区位置;短信记录前端改为自适应卡片和分组详情,失败原因独立警示,不改变短信记录后端接口与业务语义。
- 定向 API 测试 `dictionaries/sms-config/operations` 为 3 suites、49 项通过;API 全量 17 suites、167 项通过,Prisma validate、API build、前端 build、Gateway 全量 Go 测试和 `git diff --check` 均通过,前端仅有既有 chunk size warning。本地真实 PostgreSQL 已应用连接清理 migration43 条 migration 全部齐全。
- 应用内浏览器已验证本地构建的运营端登录路由标题为“聆界短信管理平台”、DOM 非空且无框架错误覆盖;受 HttpOnly 会话和图形验证码限制,未绕过认证进入受保护页面。后续浏览器连接因桌面插件版本热更新失败,未以独立 Playwright 或 mock 页面替代。本批已随 `e47432bc` 部署,外部首页和运营登录页均 HTTP 200。
## 2026-07-15 下游人工重投状态追踪修复
- 根因确认:人工重投会将 `CmppDownstreamDelivery` 重置为 `pending/retryCount=0`,前端又将所有 pending 固定翻译为“待首次投递”,导致已人工重投的记录被误展示为从未投递。
- 新增 `CmppDownstreamDelivery.manualRetryCount/lastRetriedAt` 及真实 Prisma migration;人工重投时递增人工次数、保存时间,并在 `OperationLog` 中记录重投前状态、原自动重试次数和新人工次数。自动重试次数仍可为新一轮重置为 0,但不再丢失人工重投轨迹。
- 列表和详情改为基于真实字段显示:初始 pending 为“待首次投递”,仅自动失败为“等待自动重试”,存在人工重投时为“人工重投排队中”;分开展示自动/人工次数和最近人工时间。
- 后端新增 `awaiting_ack` 并发重投拦截,避免绕过前端禁用直接调 API 造成重复投递。并发修改最终已合并且编译问题已修正;汇总回归 API 17 suites/169 项、API/前端 build、Prisma validate/status 和 Gateway 全量 Go 测试均通过。本修复已随 `e47432bc` 部署,生产人工重投 migration 已应用且 43 条 migration 全部齐全。
## 2026-07-15 下游投递告警口径统一
- 修复前存在三套口径:侧栏/首页只统计超阈值 pending 和最近 failed;详情页额外统计超时 awaiting_ack 与最近 unconfirmed/rejected;应用排行则将全部 pending 和所有历史失败累加为告警,造成同一时刻数量不一致。
- 统一为“超阈值 pending + 超过 `ackDeadlineAt` 的 awaiting_ack + 最近窗口内 failed/unconfirmed/rejected”;默认积压阈值 10 分钟、最近失败窗口 1 小时,继续支持环境变量覆盖。
- `OperationsService.dashboard()``downstreamDeliveryDashboard()` 复用同一时间窗生成逻辑,首页/侧栏补齐 `stalledAck` 与三种最终异常状态;应用告警排行改为单独按统一告警 where 聚合,不再将普通 pending 和历史失败永久累加。
- 已补实 OperationsService 定向单元测试,覆盖首页三类告警条件和应用排行统一条件。API 完整 17 suites、163 项通过,API build 和前端 build 通过;前端仅有既有 Vite chunk size warning。本批已随 `e47432bc` 部署。
## 2026-07-14 生产发送 Worker 配置缺失修复
- 生产号码 `18821203795` 的最新短信于 18:24:59 审核通过后恢复为 queuedBullMQ 已生成 job,但一直停留在 `bull:sms.send.queue:prioritized`,无通道、`submitId``SmsSubmitRecord`
- 生产 API 进程环境缺少 `API_ENABLE_SEND_WORKER=true`,而 `SendChainService.onModuleInit()` 只在该值严格为 `true` 时启动 BullMQ WorkerGateway 健康,但任务尚未进入 Gateway。
- 修复为生产初始化默认写入 `API_ENABLE_SEND_WORKER=true` 和并发数 50,发布脚本在构建、迁移和重启前强制校验开关与正整数并发数,避免再次带病发布。
- 与本批工作区改动合并回归:Prisma validate/generate 通过,API 完整 17 suites、163 项通过,API build、前端 build 和 Gateway 全量 Go 测试通过;前端仅有既有 Vite chunk size warning。
- 功能提交 `c1a17699` 已 push 并部署生产。部署前 PostgreSQL 备份为 `/opt/cmpp-platform/backups/cmpp-20260714-184827.sql`(约 65 MB),运行源码备份为 `/opt/cmpp-platform/backups/source-20260714-184827.tar.gz`(约 23 MB),环境文件备份为 `/opt/cmpp-platform/backups/cmpp-platform-20260714-184827.env`;发布包本地和服务器 SHA-256 均为 `920e01406560214ad5e2ce9c4cbef6c4d627590a7da135be6416ed5f555f4ea5`
- 生产 API 进程已实际加载 `API_ENABLE_SEND_WORKER=true``API_SEND_WORKER_CONCURRENCY=50`Redis `prioritized/active` 均为 0。原排队短信于 18:51:11 经“赛邮行业-王斯评中转”通道获得 `SUBMIT_RESP accepted`,证明 Worker、路由、Gateway 和上游提交链路已恢复。18:57:00 上游回执原始状态 `DB:9032`,平台按 `undelivered` 最终失败处理,5 分扣费已全额退回,账户余额从 95 分恢复为 100 分。
- 生产迁移 `20260714190000_restore_negative_credit_limit` 成功应用,41 条 migration 全部齐全;`.deployed-commit=c1a17699db29b7fcbde63c0b716cbf1abceb0cdf`。API、Gateway、Nginx、PostgreSQL、Redis、MinIO 均为 active`12026/17890/8090/3000` 监听,API/Gateway health、Redis 和外部首页/运营登录页 HTTP 200,部署后 API/Gateway 无新增 error。
## 2026-07-14 恢复授信额度并补齐 72 小时无回执退款
- 产品口径修正:套餐和短信余量继续移除,但企业账户保留授信额度;授信额度可为正数、负数或 0。
- 发送校验公式调整为 `balanceCents + creditCents > 0`,和大于 0 才允许发送,和小于等于 0 时禁止发送;不扣除本次预估费用后再判断。
- 新增授信调整真实 API、操作日志、企业新建/编辑页输入和运营端企业列表展示,并新增后续迁移恢复 `TenantAccount.creditCents`;客户端账户页不展示授信额度字段,只展示现金余额和合并后的可用发送额度。
- 退款时点审查:明确失败回执在补发不可用或耗尽后,于同一次回执处理内同步退款;Submit 拒绝/超时发生在扣费前,只释放冻结而非退款。已修复原 72 小时接口无自动调度且漏掉 `submitted` 的缺口:API 启动 60 秒后首次扫描,之后默认每 5 分钟扫描 `submitted/unknown`,超过 72 小时即转 timeout 并退款,单条条件更新避免并发扫描重复处理。
- CMPP 2.0/3.0 的 `Stat` 定义包含标准值 `UNKNOWN`,不是平台自造状态;当前 Gateway 对 `DELIVRD` 以外的非空最终状态(包括 `UNKNOWN`)统一映射为 `undelivered`,因此会进入失败补发,无法补发时直接失败退款。只有空 `Stat` 才映射为平台 `unknown`,并由 72 小时扫描兜底。
- 本地验证通过:新增迁移已应用于本地 PostgreSQLPrisma validate/generate、完整 API 17 suites/163 项、API build、前端 build 和 Go Gateway 全量测试通过;前端仅保留既有 Vite chunk size warning。
## 2026-07-14 计费收敛为现金余额、失败退款展示
- 生产只读核查确认 17:01 的人工充值已将目标企业现金余额从 0 增加到 100 分;17:04 发送预估费用仅 5 分却被拒绝,根因是旧逻辑同时要求 `TenantAccount.smsUnits >= billingUnits`,充值只增加现金时短信余量仍为 0。
- 发送前账户检查已收敛为只判断 `TenantAccount.balanceCents >= amountCents`,错误文案统一为“企业账户余额不足”;冻结、提交成功扣费、最终失败退款均只变更现金余额。短信 `billingUnits` 继续作为 70/67 拆分和费用计算字段,不再充当套餐额度。
- 删除 `BillingPlan`、账户短信余量、授信额度及充值订单套餐字段,并增加 Prisma 迁移;移除管理端套餐接口和客户端套餐购买入口,客户端改为展示真实现金余额与人工充值记录,运营端人工充值只录入金额。
- 企业管理列表隐藏企业 ID 和透支限额,增加“今日返还”;后端只聚合当日 `AccountTransaction.transactionType=refunded` 的实际退款,普通冻结释放 `released` 不计入返还。
- 失败退款链路复核:提交拒绝或超时且未实际扣费时只释放冻结;已提交扣费短信收到最终失败回执后才退款;已有 `SmsBillingRecord.billingStatus=refunded` 的消息不会重复退款。新增 `TC-BILLING-011/012` 覆盖余额唯一判断和退款口径。
- 同步更新第一版需求和系统功能测试用例;Prisma validate/generate、本地 PostgreSQL migration deploy、完整 API 17 suites/162 项、API build、前端 build 和 Go Gateway 全量测试均通过。前端仅保留既有 Vite chunk size warning。
- 功能与日志治理提交 `28dad93e` 已 push 并部署生产。部署前 PostgreSQL 备份为 `/opt/cmpp-platform/backups/pre-billing-20260714-173949.sql.gz`(约 4.1 MB),运行源码备份为 `/opt/cmpp-platform/deploy-backups/pre-billing-20260714-173949.tar.gz`(约 26 MB),发布包 SHA-256 为 `6253aa50bdea9d5bd35786b7414286b72b56737df3fa533b3555544983537e19`
- 生产两条迁移成功应用,40 条 migration 无待执行项;`BillingPlan` 表和 `TenantAccount.smsUnits/creditCents` 字段已移除,目标企业 `CMPP生产对接测试企业A` 的现金余额保持 100 分。生产 `.deployed-commit=28dad93e3ed7adcc7791a92723200ffe19509999`API、Gateway、Nginx、PostgreSQL、Redis、MinIO 均 activeAPI/Gateway health、`12026/17890/8090/3000` 监听和外部首页/运营入口 HTTP 200;前端产物包含“今日返还”“账户余额”且不含“充值套餐”“透支限额”。未擅自重发计费短信。
## 2026-07-14 系统日志增长治理、分页索引与归档
- 生产只读评估确认 `OperationLog` 已有约 3 万条、总占用约 17 MB,当前查询尚未形成性能事故;其中 `cmpp_downstream_connection.heartbeat` 约 2.4 万条、`gateway.downstream_recovery_status_sync` 约 5700 条,两类周期事件约占全部日志 99.5%。
- `SmsConfigService.recordDownstreamConnectionEvent` 调整为 heartbeat/Submit/Deliver 只更新 `CmppDownstreamConnection` 当前状态和时间,只有 connected/disconnected 写系统日志。
- `SendChainService.recordGatewayDownstreamRecoveryStatus` 在 upsert 前读取审计状态,只在 state、gatewayInstanceId、lockOwner、failureCategory、lastError 或 lastSkipReason 真实变化时写 `gateway.downstream_recovery_status_changed`;仅尝试次数、重试时间和锁过期时间变化不再重复写日志。
- Prisma 为 `OperationLog` 新增 `createdAt``resource+createdAt` 索引;系统日志 level 条件移入 PostgreSQL 查询后再 count/分页,排序增加 id 稳定次序。`/admin/operations/audit-logs``/admin/operation-logs` 均改为分页响应并限制 pageSize 最大 100。
- 新增 `OperationLogArchive` 和定时归档服务:在线日志默认保留 180 天,每日最多 20 批、每批 1000 条,使用单条 PostgreSQL CTE、`FOR UPDATE SKIP LOCKED` 和“归档存在后才删除源记录”保证并发与失败安全;归档记录按 `archiveMonth=YYYY-MM` 标记且不自动删除。
- 同步需求与用例:`TC-LOG-010` 覆盖高频运行事件不写永久审计,`TC-LOG-011` 覆盖数据库侧分页、接口上限、归档完整性和失败不丢数据。
- 验证通过:Prisma schema validate、Prisma Client 生成、本地 PostgreSQL migration deploy;本地真实 PostgreSQL 归档 smoke 验证过期日志进入 `archiveMonth=2000-01` 且仅在归档成功后删除在线源记录;数据库级 error 筛选真实查询通过。相关 5 suites/87 项和完整 API 17 suites/162 项测试全部通过;API build、前端 build 通过。前端仅保留既有 Vite chunk size warning。
## 2026-07-09 运营端通道测试短信闭环修复
- 生产验证发现运营端通道“短信测试”弹窗仅关闭页面,未调用后端;`POST /api/admin/channels/:id/test` 仍返回 phase-4 placeholder,不创建 `SmsMessageRecord/SmsSubmitRecord`,也不写入 Gateway SubmitCommand,因此短信记录页面无记录。
- 已修复为真实链路:前端提交手机号、内容和可选接入号;NestJS 校验通道 active 且存在在线 CMPP 连接后,创建独立 `SmsMessageRecord``SmsSubmitRecord` 和操作日志,并向 BullMQ `gateway.submit.queue` 与 Redis Stream `gateway.submit.commands` 写入真实 `SubmitCommand`。通道测试不绑定企业、企业应用或 `SmsBatchTask` 发送任务。
- 测试口径同步:`TC-ADMIN-003` 增加通道测试短信闭环要求,必须能从页面/API 发起真实测试短信,短信记录页面可查询到对应记录,Gateway submit worker 按通道真实 CMPP 配置消费发送。
- 已执行:`npm --prefix api test -- channels.service.spec.ts --runInBand``npm --prefix api run build``npm run build`。待生产部署后用指定号码做一次真实发送验证,并回查 DB/短信记录。
- 2026-07-09 追加:按产品边界收窄通道测试短信,`SmsMessageRecord/SmsSubmitRecord/SmsReceiptRecord/SmsMessageSegmentAudit` 支持 `tenantId/batchTaskId` 为空;通道测试只记录短信、提交结果和回执,不进入企业账务、发送任务进度、客户侧 Deliver 推送或业务补发。
- 2026-07-09 追加:生产验证短信进入 Gateway 后出现 `SUBMIT_TIMEOUT/context deadline exceeded`,上游平台疑似内容乱码;排查确认 API、数据库和 Redis Stream 中中文内容正常,`SubmitCommand.cmpp.msgFmt=8`,根因为 Gateway 在 CMPP 2.0 通道上仍固定构造 `Cmpp3SubmitReqPkt`。已修复为按通道 `cmppVersion` 分别构造 `Cmpp2SubmitReqPkt`/`Cmpp3SubmitReqPkt`,并同时处理 CMPP 2.0/3.0 SubmitResp。新增 `submit_packet_test.go` 覆盖 CMPP 2.0 中文 UCS2 Submit 包类型和长度。
- 2026-07-09 追加:生产验证 Submit 已 accepted 后未见 `SmsReceiptRecord`,结合上游为 CMPP 2.0 排查 Gateway readLoop,确认只处理 `Cmpp3DeliverReqPkt`CMPP 2.0 回执 Deliver 即使到达连接也不会 ACK 或进入 receipt 事件链路。已修复为同时处理 `Cmpp2DeliverReqPkt`/`Cmpp3DeliverReqPkt`,分别返回 `Cmpp2DeliverRspPkt`/`Cmpp3DeliverRspPkt`,并统一 Deliver 解码、回执和上行处理。新增 `deliver_test.go` 覆盖 CMPP 2.0 `DELIVRD` 回执写入事件链路。
- 2026-07-09 追加:运营端短信记录“发送详情 - 通道发送与回执”的“回执码”必须展示通道原始回执码 `SmsReceiptRecord.rawStatus`,用于和上游平台核对;该字段不再回退展示平台映射状态 `receiptStatus` 或提交状态。
- 2026-07-09 追加:生产验证 `17317959177` 在 DB 中已有 `rawStatus=DELIVRD`,但页面仍显示空。排查确认页面调用 `/admin/send/messages` 时生产返回缺少 `submitRecords/receiptRecords` 明细,而 `/admin/operations/messages` 返回完整通道发送与回执记录;短信记录页已切换到完整明细接口,并将 UTC ISO 时间按 `Asia/Shanghai` 展示,避免 16 点多页面显示 8 点多。
## 2026-07-09 企业应用短信接口开关和接口类型
- 按设计基线恢复企业应用添加/编辑页“短信接口 开通/关闭”和“接口类型 CMPP/HTTP”;当前第一版只允许 CMPP2.0,HTTP 在页面中禁用,后端拒绝未实现的 `interfaceType=http`
- Prisma `SmsApplication` 新增 `interfaceEnabled``interfaceType` 持久化字段;企业应用创建、编辑、列表和 CMPP 参数接口均返回真实数据库值。
- 发送链路新增硬校验:`interfaceEnabled=false` 时客户端/API 发送、Gateway bind/login 鉴权和 Gateway submit 入站都会拒绝,不进入任务、计费或 Gateway 上游提交。
- 测试口径同步:`TC-ADMIN-018A` 覆盖表单保存、CMPP2.0 only、HTTP 禁用和接口关闭后的发送/Gateway 拒绝;`TC-GW-006` 覆盖 Gateway 鉴权与 submit 对接口开关的校验。
- 已执行:`npm --prefix api run prisma:generate``npm --prefix api test -- sms-config.service.spec.ts send-chain.service.spec.ts``npm --prefix api run build``npm run build``git diff --check``npm --prefix api run prisma:migrate:deploy`
## 2026-07-09 通道复制默认停用与真实连接池状态回写修复
- 生产验证发现当前 3 个赛邮行业通道中有 2 个由首个 active 通道复制而来;数据库 `CmppConnectionState` 曾显示 3 条通道都为 `connected/currentConnections=1`,但生产机 `ss/netstat` 与上游平台都只能看到 1 条真实 TCP 长连接。
- 根因一:`POST /api/admin/channels/:id/copy` 会继承源通道 `status=active`,复制后的通道创建完成后立即参与 Gateway 连接流程,和“复制只是拷贝配置,不应自动上线”的产品预期不符。
- 根因二:Gateway `/connections/connect` 之前只做一次性 `DialCMPP` 探测,探测成功后就把 `connected/currentConnections=desiredConnections` 回写给 NestJS;该探测连接随后立即断开,导致页面状态与真实上游连接池不一致。
- 已修复:
- 通道复制后统一保存为 `disabled`,复制日志补充 `sourceStatus``copiedStatus`,避免 active 通道副本自动占用上游连接。
- Gateway `ConnectChannel` 改为直接建立/复用真实上游连接池,并由连接池回写 `connected/failed/disconnected``currentConnections`、最近错误和断开时间;连接丢失时状态随真实连接数变化更新。
- 测试口径同步:
- `TC-ADMIN-003` 增加“连接状态必须来自真实上游连接池回写”的要求。
- `TC-ADMIN-016` 明确复制 active 通道后副本默认 `disabled`,且不会立即触发上游真实连接。
- 已执行:`npm --prefix api test -- channels.service.spec.ts --runInBand``npm --prefix api run build``go test ./internal/control ./internal/upstream``go build -o ..\\dist\\cmpp-gateway .\\cmd\\gateway`
## 2026-07-09 线上通道 CMPP 版本修复
- 线上生产验证发现 3 个赛邮行业通道配置均指向 `121.40.172.212:7890`,其中 2 个已触发 Gateway 真实连接并失败,`CmppConnectionState.lastError``packetWriter.ReadBytes error: ReadBytes reads 14 bytes, not equal to 16 we expected`
- 生产机到上游 `121.40.172.212:7890` TCP 可连接,失败不是 API/Gateway 服务不可用,也不是网络完全不通;结合上游确认参数为 CMPP 2.0,根因定位为通道创建默认 CMPP 3.0 且运营端没有版本选择入口。
- 已修复通道创建/编辑:运营端新增 CMPP 2.0/3.0 版本选择,默认 2.0NestJS 通道 API 默认 `cmppVersion=2.0`,并只允许 2.0 或 3.0Prisma `SmsChannel.cmppVersion` 默认值同步改为 2.0。
- 测试口径同步:`TC-ADMIN-003` 要求通道协议默认 CMPP,CMPP 版本默认 2.0,且可选择 2.0 或 3.0;Gateway 连接命令必须携带真实通道版本。
- 待复测:部署迁移后将线上赛邮通道 `cmppVersion` 调整为 2.0,并重新触发 Gateway 连接验证。
## 2026-07-07 企业应用通用下拉与优先队列需求补充
- 已补充需求文档,明确运营端企业应用新增时选择企业必须使用通用 Select/下拉控件,企业选项来自真实企业 API,支持加载、空态和错误态,不允许静态数组或 localStorage 兜底。
- 已补充应用发送队列等级需求:短信应用必须保存普通队列/优先队列配置,默认普通队列;优先队列用于验证码、登录确认、交易通知等高时效短信。
- 已补充发送链路要求:发送入队必须按应用队列等级分流,优先队列在同等业务校验、通道组路由、通道限速和 Gateway 连接条件下插队消费;普通队列不能永久饿死。
- 已补充测试计划和系统功能用例:
- 企业应用新增表单企业选择与队列等级真实保存。
- 优先队列插队发送。
- SubmitCommand 队列等级契约校验。
- 混合优先级队列性能 smoke。
- 当前状态:仅完成需求和测试口径补充,前后端、Prisma、BullMQ/Send Worker、Gateway 队列契约尚未实现,不能记为功能验收通过。
- 2026-07-07 追加:Prisma 与 NestJS 企业应用 API 已增加 `queuePriority` 持久化字段,创建/编辑支持 normal/priority 校验,列表/详情随真实应用数据返回;前端表单、发送入队、Send Worker 调度和 Gateway 队列契约仍待后续步骤实现。
- 2026-07-07 追加:运营端企业应用新增第一步企业选择已改为通用 `Select`;企业应用新增/编辑表单已按设计锚点恢复“发送队列”单选项,并在保存时真实提交 `queuePriority`。发送入队、Send Worker 调度和 Gateway 队列契约仍待后续步骤实现。
- 2026-07-07 追加:发送链路已将应用 `queuePriority` 固化到 `SmsMessageRecord`BullMQ 入队按 priority/normal 写入不同 job prioritySubmitCommand 契约、示例和 Go Gateway 队列结构已增加 `queuePriority`;当前实现覆盖优先队列插队的基础能力,持续高优先级流量下普通队列防饥饿策略仍需后续压测和调度增强。
- 2026-07-07 追加:Gateway 队列契约第 8 步已独立校验,`SubmitCommand` schema/example 要求 `queuePriority`Go Gateway `SubmitCommand` 结构可反序列化该字段,并通过 `npm run spike:contracts``npm run spike:gateway`
- 2026-07-07 追加:第 9 步收口验证通过:`npm --prefix api run prisma:generate``npm --prefix api test -- sms-config.service.spec.ts send-chain.service.spec.ts --runInBand``npm run spike:contracts``npm run spike:gateway``npm --prefix api run build``npm run build` 均通过;前端 build 仅保留既有 Vite chunk size warning。
## 2026-07-06 企业管理列表字段回归
- 按设计锚点 `131f344a^` 恢复运营端企业管理列表字段:企业 ID、企业名称、当前余额、透支限额、今日消费、企业状态、操作。
- 新增真实后端接口 `GET /api/admin/tenants/management-list`,由 NestJS/Prisma 聚合租户、企业账户和当天短信消息金额;前端不再用静态字段或本地假数拼出今日消费。
- 当前余额来自 `TenantAccount.balanceCents`,透支限额来自 `TenantAccount.creditCents`,今日消费来自当天 `SmsMessageRecord.amountCents` 汇总。
- 已执行:
- `npm --prefix api test -- tenants.service.spec.ts --runInBand`
- `npm --prefix api run build`
- `npm run build`
- 验证结果:API 单测、API build、前端 build 均通过;前端 build 仅保留既有 Vite chunk size warning。
## 2026-07-06 企业编辑页和上传链路修复
- 按设计锚点 `131f344a^` 恢复运营端企业新建/编辑页字段:企业照片、企业名称、统一社会信用代码、省/直辖市、市/区、通讯地址、联系人姓名、身份证号、手机号、电子邮箱。
- 运营端企业新建/编辑页移除偏离锚点的企业编码、企业状态字段;后端 `POST /api/admin/tenants` 支持不传企业编码,并按信用代码/企业名生成真实唯一企业编码。
- 修复营业执照/企业照片上传 500:
- 启动脚本在 MinIO 不可用时启用本地对象存储 `.local-data/object-storage`,文件仍通过真实 NestJS 上传接口写入对象存储目录并创建 `FileObject` 数据库记录。
- 文件上传接口缺少 multipart 文件时返回 400。
- `FileObject.sizeBytes` 返回前转换为字符串,避免 Prisma `BigInt` JSON 序列化 500。
- 已执行:
- `npm --prefix api test -- tenants.service.spec.ts files.service.spec.ts --runInBand`
- `npm --prefix api run build`
- `npm run build`
- `POST http://localhost:3000/api/admin/files/upload` multipart smoke
- 验证结果:API 单测、API build、前端 build 和真实上传 smoke 均通过;前端 build 仅保留既有 Vite chunk size warning。
## 2026-07-06 本地 MinIO 启动脚本补充
- `tools/start-local.ps1` 补充本地 MinIO 启动流程:Docker Compose 优先;无 Docker 时查找 `C:\cmpp-platform-local\minio.exe``C:\cmpp-platform-local\minio\minio.exe` 或 PATH 中的 `minio.exe`,使用 `C:\cmpp-platform-local\minio-data` 作为数据目录,监听 `9000/9001`
- `package.json` 新增 `npm run start:local:minio`,用于单独启动本地 MinIO。
- MinIO 不可用时,脚本仍会明确启用 `.local-data/object-storage` fallback;启动完成提示会区分 MinIO 是否真实运行。
- MinIO 模式下对象存储服务会在上传/预签名前自动确认并创建 `cmpp-platform` bucket。
- 已执行:
- `npm --prefix api run build`
- `npm run start:local -- -SkipApi -SkipWeb -SkipMigrate`
- 验证结果:API build 和启动脚本 smoke 通过;当前机器未发现 `minio.exe`,脚本按预期提示并启用本地对象存储 fallback。
## 2026-07-06 应用级 CMPP 连接和签名/引流表单基线
- CMPP 连接状态从企业/租户级聚合改为应用级独立连接:
- `CmppConnectionState` 新增 `applicationId` 并关联 `SmsApplication`
- 企业应用列表和连接详情只读取当前应用的 `CmppConnectionState`
- 运营端断开连接只操作当前应用下的连接。
- Gateway 连接上报 `POST /api/admin/gateway/connections` 支持 `applicationId`,新连接可按应用独立记录。
- 运营端添加/编辑短信签名页面按设计锚点 `131f344a^` 补齐字段:签名依据、短信签名、资质凭证、公司名称、统一社会信用代码、法人姓名、法人身份证号、法人身份证照片、责任人姓名、责任人手机号、责任人身份证号、责任人身份证照片、三网报备状态。
- 运营端添加/编辑引流信息页面按设计锚点 `131f344a^` 补齐字段:引流信息、字段名称 1-10、文件上传、三网报备状态、提交时间、备注。
- 签名和引流表单仍使用真实 `enterprise-signatures` 后端接口保存;扩展字段写入 `SmsSignature.drainageInfo` JSON,文件上传走真实 `admin/files/upload` 并保存 `FileObject` 引用。
- 已执行:
- `npm --prefix api run prisma:generate`
- `npm --prefix api test -- sms-config.service.spec.ts channels.service.spec.ts --runInBand`
- `npm run build`
- `npm --prefix api run build`
- `npm --prefix api run prisma:migrate:deploy`
- 验证结果:Prisma Client 生成、API 针对测试、API build、前端 build 和本地 PostgreSQL migration deploy 均通过;前端 build 仅保留既有 Vite chunk size warning。
## 2026-07-01
### 新增测试基础
- API 引入 Jest + ts-jest。
- API 新增脚本:
- `npm --prefix api test`
- `npm --prefix api test -- <spec>`
- 根目录新增脚本:
- `npm run test:api`
- `npm run test:gateway`
### 新增 API 测试
| 测试文件 | 覆盖范围 |
| --- | --- |
| `api/src/risk-review/risk-review.service.spec.ts` | 最大号码数、重复率、非法号码率、黑名单率、模板变量异常、非工作时间营销大批量、短时间频控、直接拒绝、人工审核。 |
| `api/src/billing/billing.service.spec.ts` | 70/67 费用预估、余额/授信/套餐检查、充值、冻结、扣费、释放、退款、调整、短信计费记录。 |
| `api/src/send-chain/send-chain.service.spec.ts` | 批量任务创建、手机号去重拆分、发送入队、Gateway SubmitCommand 投递、submit result、receipt、uplink、72 小时未知转超时。 |
| `api/src/channels/channels.service.spec.ts` | 通道创建、路由规则、报备材料 upsert、报备任务创建、导出、回执导入、签名报备状态同步。 |
| `api/src/operations/operations.service.spec.ts` | 发送记录查询过滤、dashboard、statistics、trace、reconciliation。 |
### 新增 Gateway 测试
- `gateway/internal/tracker/tracker_test.go` 增加并发 SEQID/MSGID/GatewayMessageID 映射测试。
- 既有 Gateway 测试继续覆盖:
- tracker 基础映射和未知 submit resp。
- reconnector 重连和最终错误返回。
- health handler。
- gocmpp adapter。
- spike simulator。
### 已执行命令
```bash
npm --prefix api test -- risk-review.service.spec.ts
npm --prefix api test -- billing.service.spec.ts
npm --prefix api test -- send-chain.service.spec.ts
npm --prefix api test -- channels.service.spec.ts
npm --prefix api test -- operations.service.spec.ts
npm run test:api
npm run verify:phase8
npm run build
npm run spike:gateway
npm --prefix api test
npm run test:gateway
node tools/smoke/real-env-smoke.mjs
$env:API_PORT='3101'; npm --prefix api run start:dev
node <inline step4 precheck smoke>
$env:API_PORT='3101'; $env:API_ENABLE_SEND_WORKER='true'; npm --prefix api run start:dev
node <inline step5 send-chain smoke>
node <inline step5 schedule probe>
$env:API_PORT='3101'; npm --prefix api run start:dev
node <inline step6 billing/reconciliation smoke>
node <inline step6 auto-billing probe>
npm run spike:contracts
npm run test:gateway
$env:API_PORT='3101'; $env:API_ENABLE_SEND_WORKER='true'; npm --prefix api run start:dev
node <inline step7 channel/cmpp status smoke>
npm run verify:phase8
npm run test:api
npm run test:gateway
```
### 当前结果
- API Jest5 个 test suite 通过,22 个测试通过。
- Gateway`npm run spike:gateway` 通过。
- 阶段 8 完整验证:`npm run verify:phase8` 通过,其中 BullMQ spike 15000 条消息、并发 500、端到端 705.65 TPS,满足 500 TPS。
- 前端 build 通过,仍存在既有 Vite chunk size warning。
- API Jest 使用 mock 依赖的结果仅代表单元/轻集成测试通过;系统功能验收仍要求 PostgreSQL/Redis/MinIO 和真实 API smoke 通过。
- 真实 PostgreSQL/Redis/MinIO smoke 通过:
- PostgreSQL `localhost:5432`、Redis `localhost:6379`、MinIO `localhost:9000/9001` 端口均连通。
- `npm --prefix api run prisma:migrate:deploy` 通过,无待应用迁移。
- API 以真实 `.env` 启动,`GET http://127.0.0.1:3101/api/health` 返回 `ok`
- `tools/smoke/real-env-smoke.mjs` 已补充最小幂等 seed,并验证登录、企业/客户、人工充值、余额检查、发送任务、MinIO 预签名上传、运营日志、发送链路追踪和账务对账聚合。
- 第 4 步发送前置校验 smoke 通过:
- `TC-TEMPLATE-001``TC-TEMPLATE-002``TC-TEMPLATE-003 / TC-RISK-005``TC-RISK-001``TC-RISK-002``TC-RISK-003``TC-RISK-004``TC-BILLING-001 / TC-TEMPLATE-005``TC-BILLING-PRECHECK-001` 均通过。
- 记录两个接口健壮性问题:不存在的 `reviewerId`、不存在的 `createdById` 会触发数据库外键 500。
- 客户侧导入发送、非法字符展示、敏感词接入发送前风控当前缺少完整入口,已在 `docs/testing-execution-step-4.md` 记录为阻塞缺口。
- 第 5 步发送链路 smoke 通过:
- `TC-SEND-001 / TC-SEND-002`、旧版 `TC-SEND-003 / TC-SEND-004``TC-SEND-005``TC-SEND-006``TC-SEND-007``TC-SEND-008`、旧版 `TC-SEND-009 / TC-SEND-010` 均通过。
- 2026-07-03 新增的通道组真实路由用例 `TC-SEND-010``TC-SEND-018` 尚未开发和执行,不能沿用旧 smoke 通过结论。
- 使用真实 Redis/BullMQ 和 `API_ENABLE_SEND_WORKER=true` 验证了任务创建、号码去重拆分、自动入队、Worker 消费、submit record、submit result、receipt、uplink、72 小时 unknown 转 timeout、客户端/运营端任务查看。
- 定时发送探测发现 `scheduledAt/sendMode=scheduled` 会被接口忽略,任务直接变为 `queued`,已在 `docs/testing-execution-step-5.md` 记录为功能缺口。
- 第 6 步计费和对账 smoke 通过:
- `TC-BILLING-001``TC-BILLING-002 / TC-RECHARGE-001``TC-BILLING-003``TC-BILLING-004 / TC-BILLING-005 / TC-BILLING-006 / TC-BILLING-007``TC-BILLING-008 / TC-RECON-001 seed``TC-RECON-001``TC-DASHBOARD-001 / TC-STAT-001``TC-TRACE-001``TC-BILLING-009` 均通过。
- 验证了费用预估、人工充值、余额检查、冻结、扣费、释放、退款、短信计费记录、账务流水、dashboard/statistics、trace 和 reconciliation。
- 自动计费探测发现发送任务不会自动生成短信计费记录、账户交易流水,消息金额默认为 0,已在 `docs/testing-execution-step-6.md` 记录为发送计费集成缺口。
- 第 7 步客户/通道 CMPP 连接状态和 Gateway smoke 通过:
- `TC-GW-CONTRACT-001``TC-GW-001``TC-GW-002``TC-GW-003``TC-GW-004``TC-CMPP-STATUS-001 / TC-CHANNEL-001``TC-CHANNEL-ROUTE-001``TC-CHANNEL-METRIC-001``TC-CMPP-SESSION-001 / TC-CONNECTION-COUNT-001``TC-CMPP-STATUS-002 / TC-OPERATIONS-MONITOR-001``TC-GATEWAY-EVENT-001` 均通过。
- 验证了 Gateway 队列契约、Go Gateway health/重连/tracker/gocmpp 模拟器、通道创建、路由、metrics、submit session、通道维度监控和 Gateway submit event trace。
- 客户级 CMPP 连接状态、客户连接数量、通道连接列表和 Gateway health 聚合到运营 dashboard 尚缺少一等 API,已在 `docs/testing-execution-step-7.md` 记录。
- 第 8 步最终回归通过:
- `npm run verify:phase8` 通过,BullMQ 15000 条消息、并发 500、端到端 TPS 668.71,满足 500 TPSPrisma generate、API build、前端 build 均通过。
- `npm run test:api` 通过,5 个 test suite、22 个 tests 全部通过。
- `npm run test:gateway` 通过,Go Gateway health、connection、tracker、cmpp、spike 测试全部通过。
- 最终归档见 `docs/testing-execution-step-8.md`
### 已知缺口
- 暂未新增前端测试框架,前端仍以 `npm run build` 作为 smoke。
- BullMQ 真实链路仍由 `npm run spike:bullmq` 覆盖,不在 Jest 内启动 Redis。
- Gateway 未接真实运营商 SMSC;真实 CMPP 互通需要运营商测试环境后补充联调记录。
- 客户侧导入发送、短信内容非法字符展示和敏感词发送前拦截尚未形成完整业务入口。
- 定时短信发送缺少计划发送时间字段、scheduled 状态、到点触发和取消接口。
- 发送链路尚未自动接入计费闭环,发送任务不会自动冻结/扣费/生成短信计费记录。
- 客户/通道 CMPP 连接状态和连接数量尚未形成运营端/客户端一等接口,Gateway health 未聚合到 NestJS dashboard。
## 2026-07-01 缺口修复复测
### 本轮修复范围
- P0 定时短信发送闭环:
- `SmsBatchTask` 增加 `scheduledAt``canceledAt` 字段和 `status/scheduledAt` 索引。
- `CreateBatchTaskDto` 支持 `sendMode=scheduled``scheduledAt`
- 新增定时任务取消和到点触发入口:`POST /api/client/send/batch-tasks/:id/cancel``POST /api/admin/send/scheduled/dispatch-due`
- 到点触发前重新校验企业状态、认证状态、应用状态、模板审核状态、签名审核/报备状态和账户余额。
- P0 发送链路自动计费闭环:
- 发送创建时按通道单价写入消息计费条数、单价和金额。
- 立即发送创建时执行账户检查和冻结;定时发送创建时检查余额,到点再冻结。
- submit accepted 后生成/更新 `SmsBillingRecord`、写入 charged 流水;submit rejected/timeout 释放冻结;失败回执和 72 小时超时执行退款。
- P0 发送前内容校验:
- 风控评估接入敏感词字典和控制字符扫描。
- `variableIssues` 扩展为 `{ variables, content }`,同时保留变量异常和内容异常证据。
- emoji/UCS2、多空格和换行不直接拒绝,继续通过 70/67 字符长度影响计费。
- P1/P2 补齐:
- 新增企业认证模型/API,提交、审核通过、驳回会同步 `Tenant.certificationStatus`,发送前强制认证通过。
- 新增客户侧导入预览/确认入口,覆盖 CSV/TXT 文本解析、20MB 限制、重复/非法/黑名单/变量缺失提示。
- 新增客户/通道 CMPP 连接状态模型/APIGateway 或本地 Gateway 模拟器可通过真实 API 回写连接状态,运营 dashboard 聚合连接状态。
- 新增应用密钥重置、应用/签名/模板状态变化、通道启停接口,并写入系统日志。
- 无效 `createdById``reviewerId` 改为明确 400,不再冒泡数据库外键 500。
### 新增/更新测试
| 测试文件 | 新增覆盖 |
| --- | --- |
| `api/src/send-chain/send-chain.service.spec.ts` | TC-SCHEDULE-001 到 006、TC-BILLING-AUTO-001 到 004、TC-IMPORT-001 到 006 子集、企业认证/状态阻断。 |
| `api/src/risk-review/risk-review.service.spec.ts` | TC-CONTENT-001 到 004 子集、敏感词和控制字符拦截、无效 createdById。 |
| `api/src/certification/certification.service.spec.ts` | TC-CERT-001 到 004 子集,认证提交、审核、驳回和租户状态同步。 |
| `api/src/channels/channels.service.spec.ts` | 通道启停日志、通道连接状态 upsert/list、客户连接状态 list。 |
| `api/src/sms-config/sms-config.service.spec.ts` | 无效 reviewerId 400,避免审核外键 500。 |
| `api/src/operations/operations.service.spec.ts` | Dashboard Gateway 连接状态聚合。 |
### 已执行命令
```bash
npm run prisma:generate
npm --prefix api test -- send-chain.service.spec.ts risk-review.service.spec.ts sms-config.service.spec.ts
npm --prefix api test
npm --prefix api run build
npm --prefix api run prisma:migrate:deploy
npm run verify:phase8
npm run test:api
npm run test:gateway
```
### 当前结果
- Prisma Client 生成通过。
- 新增迁移已应用到真实 PostgreSQL
- `20260701103000_add_scheduled_sms_fields`
- `20260701110000_add_certification_and_connection_state`
- API build 通过。
- API Jest7 个 test suite 通过,34 个测试通过。
- 最终回归通过:
- `npm run verify:phase8` 通过,BullMQ 15000 条消息、并发 500、端到端 TPS 608.93,满足 500 TPSPrisma generate、API build、前端 build 均通过。
- `npm run test:api` 通过,7 个 test suite、34 个 tests 全部通过。
- `npm run test:gateway` 通过,Go Gateway health、connection、tracker、cmpp、spike 测试全部通过。
### 剩余说明
- 客户侧导入当前提供 API 级文本预览/确认闭环;浏览器端真实文件选择、GBK 二进制转码和错误文件下载仍需前端/E2E 后续覆盖。
- Gateway 连接状态通过 NestJS API 支持 Go Gateway 或本地模拟器回写;真实运营商 SMSC 联调仍需运营商测试环境。
## 2026-07-02 运营端优化转真实后端补齐
### 本轮修复范围
- 修正测试策略说明:mock 仅作为单元/轻集成测试替身,不能作为业务完成标准;新增页面能力必须接 NestJS API、Prisma/PostgreSQL 和必要操作日志。
- 通道管理补齐真实 API
- `POST /api/admin/channels/:id/copy`:复制通道配置、通道报备字段和该通道签名报备材料,写入操作日志。
- `DELETE /api/admin/channels/:id`:软删除通道,避免破坏历史发送/报备外键。
- `GET /api/admin/channels/:id/connection-logs`:基于 `OperationLog``CmppConnectionState` 查询连接日志;保留 `/link-logs` 兼容旧前端。
- 安全控制补齐真实 API:敏感词、全局黑名单、企业黑名单支持 keyword/status 查询、创建、启停/软删除,并写操作日志。
- 企业黑名单修正为企业应用级黑名单:Prisma `EnterpriseBlacklist` 新增 `applicationId` 并改为 `applicationId + phoneNumber` 唯一;运营端页面按企业和短信应用新增/搜索;发送预览和风控只命中当前应用的 active 黑名单。
- 模板审核补齐真实查询:运营端模板列表支持 keyword/status,并返回企业、应用、签名信息;前端模板审核页已改为调用真实 API。
- 运营端企业模板管理新增短信模板时,后端直接写入 `auditStatus=approved`;短信模板审核页展示为“已通过”,客户端自行提交模板仍保留审核流。
- 企业认证审核补齐真实查询:列表支持 keyword/status,详情返回企业信息和认证 materials;前端企业认证审核页已改为调用真实 API。
- 前端新增 `/api` Vite 代理和 `src/api/adminApi.ts`,通道管理、模板审核、企业认证审核应调用真实 API;API 不可用时页面应展示错误态或空态,静态兜底不能作为验收通过依据。
### 新增/更新测试
| 测试文件 | 新增覆盖 |
| --- | --- |
| `api/src/channels/channels.service.spec.ts` | 通道复制、软删除、连接状态日志写入、连接日志查询。 |
| `api/src/dictionaries/dictionaries.service.spec.ts` | 敏感词、全局黑名单、企业黑名单查询、创建、软删除和操作日志。 |
### 已执行命令
```bash
npm --prefix api run build
npm --prefix api test
npm run build
```
### 当前结果
- API build 通过。
- API Jest8 个 test suite 通过,38 个测试通过。
- 前端 build 通过,仍存在既有 Vite chunk size warning。
### 文档同步
- 已将今天的客户端和运营端优化要求补入 `docs/first-version-development-requirements.md`
- 去除客户端独立账号设置菜单,改为头像下拉承载退出登录和修改密码。
- 明确模板审核搜索、企业认证详情审核、企业应用 CMPP 连接数/连接详情/参数复制、通道复制、通道软删除、通道连接日志、安全控制 CRUD、系统日志分页等均需要真实后端 API 支撑。
- 补充客户端用户、运营端企业认证、通道、连接、字典、安全控制、系统日志等接口范围。
- 修正 Codex 执行模板,明确 mock、localStorage 或静态数据不得作为真实开发完成标准。
- 已将今天的验收点补入 `docs/system-functional-test-cases.md`
- 新增 TC-CLIENT-010 到 TC-CLIENT-011。
- 新增 TC-ADMIN-014 到 TC-ADMIN-022。
## 2026-07-02 新增浏览器和业务闭环用例执行
### 执行环境
- API`npm --prefix api run start:dev`,监听 `http://localhost:3000/api`
- 前端:`npm run build` 后使用 `npm run preview -- --port 4173`,访问 `http://localhost:4173`
- Browser 插件:可连接本地 tab,但对 Vite dev 页 `Page.navigate` 超时;改用临时目录 Playwright 包加本机 Chrome 执行浏览器 smoke,未修改项目依赖。
- PostgreSQL:本地 `localhost:5432` 可用,API smoke 使用真实数据库。
### 已执行命令
```bash
npm run build
# 临时目录 C:\Users\hectorzhao\AppData\Local\Temp\cmpp-pw-smoke
npm init -y
npm install playwright --no-save
node <browser-and-api-smoke>
```
### 通过用例
| 用例 | 结果 | 覆盖点 |
| --- | --- | --- |
| TC-DASHBOARD-CLIENT-UI | UI-SMOKE PASS / BACKEND GAP | 客户端 Dashboard 可渲染账户余额、今日发送、账户状态,但页面数据仍需确认全部来自真实 API。 |
| TC-DASHBOARD-ADMIN-UI | UI-SMOKE PASS / BACKEND GAP | 运营端 Dashboard 可渲染今日发送总量、总体成功率、企业消费排行、通道运行,但当前源码仍存在 mock 数据路径。 |
| TC-BILLING-MANUAL-UI | UI-SMOKE PASS / BACKEND GAP | 运营端人工充值弹窗填写后,前端表格新增企业、金额、操作人和备注;该页面当前未调用真实充值 API。 |
| TC-LOG-ADMIN-UI | UI-SMOKE PASS / BACKEND GAP | 运营端系统日志页面可展示人工充值、账户计费等记录;页面数据仍需接真实日志 API。 |
| TC-LOG-CLIENT-UI | UI-SMOKE PASS / BACKEND GAP | 客户端系统日志页面可渲染并展示客户侧日志记录;页面数据仍需接真实日志 API。 |
| TC-CMPP-STATUS-UI | UI-SMOKE PASS / BACKEND GAP | 企业应用管理可展示 CMPP 状态和连接数量并打开连接详情;页面当前仍有本地初始数据路径。 |
| TC-FRONTEND-CONSOLE | PASS | 关键页面无相关 console error/pageerror;仅忽略 favicon 404。 |
| TC-BILLING-MANUAL-API | PASS | 人工充值无需审批:确认后账户余额、短信条数、充值单、账户流水和 Dashboard transactions 聚合同步更新。 |
| TC-CMPP-STATUS-API | PASS | 通道创建、Gateway 连接状态回写、按通道/客户查询、连接日志和 Dashboard gatewayConnections 聚合通过。 |
### 发现和说明
- `npm run dev` 在本机 5173 被占用后切换到 5174Vite 首次依赖 bundling 长时间未完成,浏览器看到白屏;生产构建和 preview 渲染正常。
- 运营端人工充值页面当前是前端本地状态 smoke,不能作为系统功能通过;真实入账闭环通过 `POST /api/admin/billing/manual-recharges` 验证。
- 人工充值不需要审批,测试口径已同步修正为“有权限确认即入账,不产生 pending 审批态”。
### 真实后端缺口和 Bug 清单
| 编号 | 严重级别 | 问题 | 证据 | 期望修复 |
| --- | --- | --- | --- | --- |
| BUG-FE-001 | P0 | 运营端人工充值页面未调用真实后端,提交后只更新前端本地表格状态。 | `src/apps/admin/AdminRechargeRecordsPage.tsx` 使用 `rechargeRecordsSeed``useState``submitManualRecharge``setRecords`。 | 页面提交调用 `POST /api/admin/billing/manual-recharges`,成功后刷新真实充值记录、账户余额、流水和日志。 |
| BUG-FE-002 | P0 | 运营端 Dashboard 仍使用 mock service 和静态排行,不能证明真实统计准确。 | `src/apps/admin/AdminHome.tsx` 引用 `adminService``hourlySendTrend``auditTrend`,指标从前端数组计算。 | 接入 `GET /api/admin/operations/dashboard/statistics` 或拆分真实统计接口,所有卡片和排行从 API 返回。 |
| BUG-FE-003 | P0 | 客户端 Dashboard 仍使用 mock service,余额、发送量、最近充值等不是实时后端数据。 | `src/apps/client/ClientHome.tsx` 使用 `clientService.getOverview()` 和客户端 mock 数据。 | 接入客户端真实 dashboard、账户、任务、充值流水 API,点击明细继承真实筛选条件。 |
| BUG-FE-004 | P0 | 客户端和运营端系统日志页面仍有静态数据路径,无法验证真实日志、分页、筛选和租户隔离。 | `src/apps/admin/AdminSystemLogsPage.tsx``src/apps/client/ClientSystemLogsPage.tsx` 页面 smoke 可展示,但未证明调用真实日志 API。 | 接入真实日志 API,支持分页、筛选、详情、租户隔离,失败动作也可查。 |
| BUG-FE-005 | P0 | 企业应用 CMPP 状态和连接详情页面仍使用本地初始数据,未读取真实连接状态 API。 | `src/apps/admin/AdminEnterpriseApplicationsPage.tsx` 使用 `initialSmsApps``setSmsApps`,连接删除也是本地状态变更。 | 接入企业应用、连接状态、连接详情、连接删除/断开真实 API 或 Gateway 回写接口。 |
| BUG-API-001 | P1 | 通道创建参数缺失时返回 Prisma 500,而不是业务 400。 | 浏览器 smoke 第一轮 `POST /api/admin/channels` 缺少 `code/gatewayHost/gatewayPort/account/passwordCipher/srcId`API 返回 Internal server error。 | 为通道创建 DTO 增加校验,缺失必填字段返回 400 和可读错误,并写失败日志。 |
| BUG-DEV-001 | P1 | `npm run dev` 在 5173 被占用后切到 5174Vite 依赖 bundling 长时间未完成,浏览器看到白屏。 | 本轮浏览器测试中 5174 HTTP 后续可达,但首次打开截图为空白;生产 build/preview 正常。 | 检查 Vite dev 依赖预构建和端口占用问题,确保开发模式可稳定渲染。 |
| BUG-SEND-001 | P0 | 发送路由规则允许直接绑定单个通道,违反“规则只能绑定通道组”的业务约束。 | `SendChainService.selectChannel()` 当前存在 `route?.channel ?? route?.group...` 路径;`ChannelRouteRule` 模型也保留 `channelId` 字段。 | 路由规则只能表达应用到通道组的绑定关系;发送链路必须从企业应用绑定的运营商通道组内选路,不允许规则直接指定单个通道。 |
| BUG-SEND-002 | P0 | 未命中路由规则时会 fallback 到全局第一个 `active` 通道,可能把短信发到未配置给该企业/应用的通道。 | `SendChainService.selectChannel()` 未找到 route 后执行 `smsChannel.findFirst({ where: { status: 'active' } })`。 | 企业应用没有配置对应运营商通道组或无可用通道时,短信直接 failed;不得进入 pending/delayed,不得 fallback 到其他 active 通道,需记录 trace/日志。 |
| BUG-SEND-003 | P0 | 发送选路只判断通道业务状态 `active`,不判断 CMPP 真实连接状态。 | `selectChannel()` 只检查 `SmsChannel.status`,未查询 `CmppConnectionState.status/currentConnections/lastHeartbeatAt/lastError`。 | 选路必须跳过离线、认证失败、心跳超时、重连中或 `currentConnections=0` 的通道;至少 1 条连接 connected 且心跳正常才可发送,连续 3 次心跳失败进入重连且不可选。 |
| BUG-SEND-004 | P0 | 通道组主通道提交失败、超时或回执失败后不会切换到下一个通道补发。 | `handleSubmitResult()``handleReceipt()` 只更新状态、释放/退款和刷新进度,没有重新选路或创建补发记录;`retry.maxAttempts` 目前未形成业务补发闭环。 | 除 unknown、超过 72 小时、超过通道组补发时间上限或通道组关闭补发外,submit rejected/timeout、连接断开、未提交成功、receipt failed 均需补发;省网失败后立即走全国通道,全国通道按优先级继续补发,最终成功只按企业应用客户费率扣一次。 |
| BUG-SEND-005 | P0 | 通道组省网/全国路由没有接入真实发送链路,手机号段库也未参与归属地识别。 | `SmsChannelGroupItem``ChannelRouteRule` 虽有 `carrier/province` 字段,`PhoneSegment``prefix/carrier/province/city`,但 `SendChainService.selectChannel()` 未读取 message.phoneNumber、未查询 `phoneSegment`,只按优先级取第一个 active 通道;前端 `AdminChannelGroupFormPage` 的省网/全国配置仍为本地 `useState`。 | 发送前按可配置号码前缀正则识别运营商,识别失败走移动通道组;按手机号段库识别省份和城市,省份识别失败走对应运营商全国通道;通道需支持移动/联通/电信/三网和全国/单省发送地区,三网作为通配。 |
| BUG-SEND-006 | P0 | 企业应用缺少按运营商绑定多个通道组和保存校验的真实闭环。 | 当前发送链路只按 `tenantId/applicationId` 查询单一路由规则;未体现一个应用分别绑定移动、联通、电信通道组,也未强制至少绑定一个通道组后才能保存。 | 企业应用可分别绑定移动、联通、电信通道组;一个都不绑定时 UI 不允许保存,发送时直接 failed;移动、联通、电信短信按识别结果进入对应通道组。 |
## 2026-07-03 通道组真实路由和补发修复
### 本轮修复范围
- BUG-SEND-001:后端 `createRouteRule` 禁止直接绑定单通道,路由规则只能绑定应用、运营商和通道组;发送链路不再读取 `route.channel`
- BUG-SEND-002:发送链路未找到企业应用对应运营商通道组或无可用在线通道时,短信直接标记 `failed`,不再 fallback 到全局第一个 active 通道。
- BUG-SEND-003:发送选路加入 CMPP 连接状态过滤,通道必须业务 `active`、连接 `connected``desiredConnections > 0``currentConnections > 0` 才可选;`online/open` 仅作为旧 Gateway 回写兼容词入库归一化。
- BUG-CMPP-STATUS-001:新建/启用通道后若 Gateway 连接请求长时间无回写,API 后台兜底任务会将超过 30 秒的 `connecting` 连接标记为 `failed`,写入超时原因和连接日志,避免页面长期停留“连接中”。
- BUG-SEND-004submit rejected、submit timeout、回执 failed 等失败场景会在补发开启且未超过时间限制时,排除已尝试通道并切换到同一通道组全国通道继续提交;unknown、超过 72 小时、超过通道组补发上限或关闭补发时不补发。
- BUG-SEND-005:新增 `PhoneCarrierRule` 运营商前缀正则配置,发送前先识别运营商,识别失败默认移动;手机号段库用于识别省份,省份识别失败走对应运营商全国通道;通道新增 `sendRegion`,支持全国或单省。
- BUG-SEND-006:企业应用创建页面可分别选择移动、联通、电信通道组,一个都不选时 UI 阻止保存;创建应用成功后写入真实通道组路由规则。
- 前端配置补充:通道创建支持移动、联通、电信、三网和发送地区;手机号段库新增“运营商区分规则”tab。
### 新增/更新测试
| 测试文件 | 新增覆盖 |
| --- | --- |
| `api/src/send-chain/send-chain.service.spec.ts` | 应用运营商通道组路由、在线连接过滤、失败不 fallback、补发关闭时释放/退款。 |
| `api/src/channels/channels.service.spec.ts` | 通道发送地区默认值、通道组补发配置、禁止单通道路由规则。 |
### 已执行命令
```bash
npm --prefix api run prisma:generate
npm --prefix api test -- send-chain.service.spec.ts channels.service.spec.ts --runInBand
npm --prefix api test
npm --prefix api run build
npm run build
npm run verify:phase8 # 阻塞:BullMQ spike endToEndTps 未达到 500
npm run spike:bullmq # 复跑仍未达到 500
```
### 当前结果
- Prisma Client generate:通过。
- API Jest10 个 test suite 通过,50 个测试通过。
- API build:通过。
- 前端 build:通过,仍存在既有大 chunk warning。
- `npm run verify:phase8`:未通过,阻塞在 `spike:bullmq` 性能阈值;第一次 endToEndTps=464.58,复跑 `npm run spike:bullmq` endToEndTps=495.97,第三次 endToEndTps=477.17,均低于 500 TPS 阈值。
- 尚未执行真实 PostgreSQL/Redis/Gateway 端到端 smoke;需在生产验证或本地真实服务环境中覆盖 `TC-SEND-010``TC-SEND-018``TC-CMPP-STATUS-008A`
## 2026-07-02 真实后端缺口修复
### 本轮修复范围
- BUG-FE-001:运营端充值记录页移除 `rechargeRecordsSeed` 验收路径,加载真实租户、人工充值记录、账户余额和账户流水;确认人工充值调用 `POST /api/admin/billing/manual-recharges`,成功后刷新真实记录、账户、流水,并由后端写 `billing.manual_recharge` 操作日志,不产生 pending 审批态。
- BUG-FE-002:运营端 Dashboard 移除 `adminService`、静态趋势和前端排行计算,改为调用 `GET /api/admin/operations/dashboard/statistics`、真实通道 API 和真实账户聚合。
- BUG-FE-003:客户端 Dashboard 移除 `clientService`、静态趋势和本地 mock,改为调用 `GET /api/client/operations/dashboard`、客户端账务/任务聚合,并通过 `x-tenant-id` 限定当前租户。
- BUG-FE-004:运营端和客户端系统日志页移除静态 `logsSeed`,接入真实日志 API,支持分页、关键字、级别、模块和时间范围;长详情使用详情卡展示 JSON 摘要。
- BUG-FE-005:企业应用管理短信应用 tab 接入真实企业应用、租户连接状态、连接详情和 CMPP 参数 API;断开连接调用真实后端并写系统日志,变更后刷新列表。彩信 tab 仍为第一版待开发路径,不作为短信验收依据。
- BUG-API-001:通道创建在 Service 层校验 `code/name/gatewayHost/gatewayPort/account/passwordCipher/srcId`,缺失或端口非法返回 400,不再让 Prisma validation error 冒泡成 500。
- BUG-DEV-001:复现 Vite 8 dev server 在端口切换后依赖/模块转换请求超时,导致白屏;根 `npm run dev` 改为先 `npm run build``vite preview --host 0.0.0.0`,确保本地打开稳定。`vite.config.ts` 保留 `optimizeDeps.noDiscovery`,避免自动扫描引发的预构建卡住。
### 新增/更新测试
| 测试文件 | 新增覆盖 |
| --- | --- |
| `api/src/channels/channels.service.spec.ts` | 通道创建缺少必填字段时返回可读 400。 |
| `api/src/billing/billing.service.spec.ts` | 人工充值写入 `billing.manual_recharge` 操作日志。 |
| `api/src/operations/operations.service.spec.ts` | Dashboard 新增今日统计、账户/充值/待审核聚合和系统日志分页详情。 |
| `api/src/sms-config/sms-config.service.spec.ts` | 企业应用列表聚合真实 CMPP 连接状态、CMPP 参数读取、断开连接写日志。 |
### 已执行命令和 Smoke
```bash
npm --prefix api test
npm --prefix api run build
npm run build
npm run dev
# API HTTP smoke on API_PORT=3101
GET /api/health
POST /api/admin/channels # 缺必填字段返回 400
GET /api/admin/operations/dashboard/statistics
GET /api/admin/system-logs?page=1&pageSize=2
```
### 当前结果
- API Jest8 个 test suite 通过,43 个测试通过。
- API build:通过。
- 前端 build:通过,仍存在既有大 chunk warning。
- `npm run dev`:通过,当前会 build 后启动 Vite preview,实际可访问 `http://localhost:4173/`,避免 Vite 8 dev optimizer/transform 白屏。
- 浏览器 smoke 通过:
- 运营端 Dashboard 渲染真实聚合指标,无相关 console error。
- 运营端人工充值页渲染真实记录,人工充值弹窗展示真实企业下拉和确认入口。
- 运营端系统日志页渲染真实日志,长详情以卡片展示。
- 企业应用管理页渲染真实应用和 CMPP 状态,连接详情弹窗和 CMPP 参数弹窗可打开。
- 客户端 Dashboard 和客户端系统日志页按当前租户渲染,无相关 console error。
- API HTTP smoke`/api/health` 返回 ok;通道缺参返回 400 和可读错误;dashboard/statistics、system-logs 返回真实数据。
### 剩余说明
-`npm run dev` 为稳定预览模式,不提供 Vite HMR;保留原因是 Vite 8/Rolldown dev transform 在当前 Windows + 中文路径工作区下会阻塞模块请求并造成白屏。开发时如需热更新,可另行评估降级 Vite 或迁移工作区路径后恢复原生 dev server。
## 2026-07-02 登录与用户管理闭环补充
### 本轮修复范围
- 新增用户登录字段和 fail2ban 持久化字段:`email``phone``failedLoginCount``lockedUntil``lastLoginAt``deletedAt`
- 登录入口拆分为 `/client/login``/admin/login`,两端均调用真实验证码和登录 API。
- 用户登录入口和用户表单统一文案为“用户名/登录账号”,提示可用用户名、邮箱或手机号登录。
- 运营端登录仅允许 `platform_admin`;客户端登录仅允许已关联企业的 `enterprise_admin`
- 运营端用户管理接入真实 `/api/admin/users`,支持平台管理员和企业管理员的新增、编辑、启用/禁用、删除、改密;企业管理员必须关联企业。
- 客户端用户管理接入真实 `/api/client/users`,所有操作继承当前登录企业 `tenantId`
- 启用/禁用、删除均通过确认弹窗执行;用户删除采用软删除,不破坏历史日志和业务记录。
- 连续 5 次登录失败后锁定 24 小时;登录成功清空失败次数和锁定状态。
### 已执行测试
```bash
npm --prefix api test -- users.service.spec.ts auth.service.spec.ts
npm --prefix api run prisma:generate
npm --prefix api run build
npm run build
```
### 当前结果
- `users.service.spec.ts``auth.service.spec.ts`:通过,覆盖用户类型约束、企业关联约束、操作日志、端登录隔离和失败次数累计。
- API build:通过。
- 前端 build:通过,仍有既有大 chunk warning。
- `tools/smoke/real-env-smoke.mjs` 已同步企业管理员邮箱/手机号、角色 seed、验证码登录和 CMPP 端口 `17890`
- 真实数据库迁移、浏览器端登录 smoke 需要在生产验证环境执行 `prisma migrate deploy` 后补充记录。
## 2026-07-02 非彩信纯 mock 菜单真实化
### 本轮修复范围
- 客户端:充值套餐、账单流水、批量任务、短信发送、短信签名、短信模板改为调用真实 API;签名材料使用真实文件元数据和材料关联接口;发送任务调用真实批量任务接口。
- 运营端:数据统计、账务账户、发送监控、敏感词、全局黑名单、企业黑名单、手机号段库、报备字段库、通道组、通道报备字段、报备任务、报备记录、短信审核、短信记录改为真实 API。
- 企业管理:客户列表、客户表单、客户详情由 `adminEnterpriseMock`/localStorage 改为真实租户、账户、应用、签名、模板接口;后端补充租户编辑、状态变更和删除接口。
- 通道管理:删除静态通道兜底,API 失败展示错误态。
- 企业认证审核:删除静态认证兜底,API 失败展示错误态。
- 企业签名/企业模板运营端列表只展示真实短信配置数据;彩信相关菜单继续作为待开发边界,不计入第一版短信验收。
### 已执行命令
```bash
npm --prefix api test
npm --prefix api run build
npm run build
npm run verify:phase8
```
### 当前结果
- API Jest10 个 test suite 通过,49 个测试通过。
- API build 通过。
- 前端 build 通过,仍存在既有大 chunk warning。
- `npm run verify:phase8` 通过:
- Gateway 队列契约 4 个示例通过。
- Go Gateway 测试通过。
- BullMQ 15000 条消息、并发 500、端到端 TPS 681.47,满足 500 TPS。
- Prisma generate、API build、前端 build 均通过。
- 源码搜索剩余静态业务数据集中在彩信待开发页面、彩信审核页面、企业应用彩信 tab,以及 `src/api/session.ts` 的登录 session 持久化;非彩信主菜单的 `clientService`/`adminService` 业务路径已清理。
### 待复测
- 浏览器 smoke 和真实文件上传 smoke 需要在生产验证环境补跑,重点复测客户端发送、签名材料上传、短信审核、短信记录、客户管理和报备任务。
## 2026-07-03 单运营商通道组、应用级费率和回执幂等
### 本轮修复范围
- 通道组规则:
- `SmsChannelGroup.carrier` 固化为移动、联通、电信三选一,禁止三网通道组。
- `SmsChannelGroupItem.carrier` 保留并参与发送,必须等于通道组运营商。
- 三网只作为通道本体能力 `SmsChannel.carrier=all`,放入某个通道组后只服务该组运营商。
- 同一通道组内同一省份只能配置一个通道;省份 item 必须引用发送地区一致的通道。
- 全国通道允许多个,但同一通道组内全国通道优先级禁止重复;本期不做权重分流。
- 路由规则必须绑定应用、运营商、通道组,且 route carrier 必须等于 group carrier。
- 发送和计费规则:
- 运营商以号码前缀正则为准;手机号段库只提供省份/城市,carrier 仅作后台提示或校验。
- 发送前校验最终选中通道的签名报备任务为 approved,补发切换通道时重新校验。
- 企业应用新增 `customerUnitPrice`,客户扣费按应用级客户费率;通道成本只作内部成本。
- 迟到旧通道 failed receipt 不覆盖新通道 delivered 最终状态;历史回执仍入库可查。
- 重复 submit/receipt 回调不得重复扣费、释放冻结或退款。
- 补发使用触发时当前通道组配置;本期不考虑人工重发。
- 前端和真实 smoke
- 企业应用表单增加客户单价输入,保存时写入真实应用 API。
- 企业应用按移动、联通、电信分别选择通道组,选项按通道组 carrier 过滤。
- 真实 smoke seed 补充应用客户单价、三网通道放入移动组、签名-通道 approved 报备。
### 新增/更新测试
| 测试文件 | 新增覆盖 |
| --- | --- |
| `api/src/channels/channels.service.spec.ts` | 单运营商通道组、通道组 item carrier 校验、通道 carrier 兼容、省份与发送地区一致、同省唯一、全国优先级唯一、route carrier 与 group carrier 一致。 |
| `api/src/send-chain/send-chain.service.spec.ts` | 通道组 item carrier 参与发送、最终通道签名报备校验、应用级客户费率、迟到旧 failed receipt 不覆盖 delivered、重复账务动作幂等。 |
| `api/src/sms-config/sms-config.service.spec.ts` | 应用配置与列表在新增客户费率字段后继续通过。 |
### 已执行命令
```bash
npm --prefix api run prisma:generate
npm --prefix api test -- channels.service.spec.ts send-chain.service.spec.ts sms-config.service.spec.ts --runInBand
npm --prefix api test
npm --prefix api run build
npm run build
npm --prefix api run prisma:migrate:deploy
$env:API_PORT='3101'; $env:API_ENABLE_SEND_WORKER='true'; npm --prefix api run start:dev
node tools/smoke/real-env-smoke.mjs
node <inline channel-group rule HTTP smoke>
npm run spike:contracts
npm run test:gateway
npm run verify:phase8
```
### 当前结果
- Prisma Client 生成通过。
- 新增迁移已应用到真实 PostgreSQL
- `20260703143000_add_channel_group_carrier`
- `20260703152000_add_application_customer_rate`
- API Jest10 个 test suite 通过,56 个测试通过。
- API build 通过。
- 前端 build 通过,仍存在既有 Vite chunk size warning。
- 真实 API smoke 通过:
- `tools/smoke/real-env-smoke.mjs` 通过,验证真实 API、Prisma/PostgreSQL、Redis/BullMQ、登录、充值、发送任务、worker 入队、文件上传元数据和操作日志。
- inline 通道组规则 HTTP smoke 通过,覆盖三网组拒绝、item carrier 不匹配拒绝、通道 carrier 不兼容拒绝、省份/发送地区不匹配拒绝、同省重复拒绝、全国优先级重复拒绝、route carrier/group carrier 不匹配拒绝。
- Gateway 队列契约通过,4 个示例均验证通过。
- `npm run test:gateway` 通过,Go Gateway health、connection、tracker、cmpp、spike 测试全部通过。
- `npm run verify:phase8` 未通过,仍阻塞在已知 BullMQ spike 性能阈值:
- 15000 条消息、并发 500。
- enqueue TPS 3139.43。
- end-to-end TPS 479.02,低于 500 TPS。
### 剩余说明
- `verify:phase8` 当前失败点是独立 BullMQ 性能阈值,不是本轮通道组、计费、报备、回执业务逻辑测试失败。
- 浏览器端完整手工回归仍建议补跑企业应用创建、通道组配置、短信记录详情弹窗中的历史回执展示。
## 2026-07-07 Gateway 下游 CMPP 入站第一阶段补齐
### 本轮修复
- Go Gateway 启动时同时监听 `GATEWAY_CMPP_ADDR`,默认生产端口 `0.0.0.0:17890`,不再只是 HTTP `/health` 控制服务。
- 新增企业应用独立 6 位 `cmppAccount`Prisma 迁移 `20260707162000_add_application_cmpp_account` 会为存量应用生成账号;客户端/运营端 CMPP 参数接口返回该应用独立账号。
- Gateway 下游 CMPP bind 使用真实 gocmpp 协议解析 `Source_Addr/AuthSource/Timestamp`,调用 NestJS `/api/gateway/events/inbound/authenticate`,由真实数据库校验应用账号、应用 CMPP 密码、企业状态、企业认证状态、应用状态和 IP 白名单。
- Gateway 下游 CMPP submit 解码 CMPP 3.0 `SubmitReq`,调用 NestJS `/api/gateway/events/inbound/submit`NestJS 按 `sourceType=cmpp` 创建发送记录并复用模板/签名/风控/余额/运营商识别/通道组路由/队列优先级链路。
- Go Gateway 新增入站集成测试,覆盖本地 CMPP 客户端 connect/login、UCS2 submit 和 API 回调。
### 验证状态
- `npm --prefix api run prisma:generate`:通过。
- `npm --prefix api test -- sms-config.service.spec.ts send-chain.service.spec.ts --runInBand`:通过。
- `npm --prefix api run build`:通过。
- `go test ./...`Gateway):通过。
### 剩余缺口
- 下游连接状态回写、连接数上限、断开/心跳历史日志仍需继续产品化。
- 下游 submit 当前通过 `sourceType=cmpp` 的系统批次兼容承载,尚未完全拆成独立单条发送模型。
- 客户侧最终 Deliver Receipt 投递、客户侧上行 Deliver 推送、上游真实 SMSC submit worker、上游 receipt/uplink 生产解析仍未完成。
## 2026-07-11 Gateway 客户侧 Submit 日志完善
### 本轮修复
- Gateway 入站 Submit 日志增加 `submit_received``submit_accepted``submit_rejected` 结构化事件,同时记录 CONNECT 声明的客户协议版本与 Go 实际解包类型,并记录账号、客户 IP、sequenceId、号码、srcId、编码、分片、CMPP result、平台 messageId、CMPP Msg_Id 和处理耗时。
- Gateway HTTP 回调在 NestJS 返回非 2xx 时保留最多 64KB 响应体,客户 Submit 失败日志可直接显示模板不匹配、IP 白名单、余额或路由等真实业务原因,不再只显示 HTTP 状态码。
- 日志不记录明文短信正文,仅记录字符数和 MD5 哈希,便于比对同一内容且避免日志泄露。
- 将当前 gocmpp 版本固定为仓库内小型 fork,仅在 server 循环补充底层诊断:包在进入业务 handler 之前发生长度、命令字、包体读取或 Unpack 失败时,记录 `read/unpack packet failed`、远端地址、库解析协议模式、Go 错误类型和原始错误;正常 EOF 断开不记为解包失败。
- 生产复现确认 CMPP2.0 客户 Submit 被固定 CMPP3.0 解包导致 `MsgSrc/手机号/内容` 错位为空。现在 gocmpp server 按 CONNECT `Version` 将每条连接切换到 CMPP2.0/2.1/3.0 解包模式,并返回同版本 ConnectResp、SubmitResp 和 Deliver。
- Gateway 使用 bind 时已鉴权会话账号调用 NestJS 入站接口,Submit `MsgSrc` 改为与鉴权返回的应用级 `cmppEnterpriseCode` 独立比对,不再将企业代码误当登录账号。
### 验证状态
- `go test ./internal/inbound -count=1`:通过。
- `go test ./... -count=1`:通过。
- `go build ./cmd/gateway`:通过。
- 真实 TCP 非法包用例:向入站端口写入非法 `total_length`,确认业务 handler 未执行时仍产生 `read/unpack packet failed` 日志。
- CMPP2.0 真实集成用例:客户使用与登录账号不同的 `MsgSrc=SP0001`,完成 V20 ConnectResp、Cmpp2SubmitReq/Resp 和 Cmpp2Deliver ReceiptNestJS 收到的 account 仍为 bind 账号:通过。
- 已将合并后提交 `bb4992f0` 部署到生产验证环境;Prisma 无待执行迁移,前端/API/Gateway 构建和标准健康检查通过,12026/3000/8090/17890 监听正常。生产账号 `910887` 重连日志确认 `requested_version=0x20 response_version=0x20`;该测试应用的 `cmppEnterpriseCode` 已通过真实运营 API 同步为 `910887`
## 2026-07-07 Gateway 上游提交与下游 Deliver 闭环补齐
### 本轮修复
- `SubmitCommand` 契约、示例和 Go 结构增加 `upstream.gatewayHost/gatewayPort/account/passwordCipher/cmppVersion`,API 发送链路在真实业务校验通过后保留 BullMQ 审计投递,同时写入 Redis Stream `gateway.submit.commands` 主命令流。
- Go Gateway 新增上游提交管理器,按通道建立/复用 gocmpp 客户端连接,发送真实 CMPP Submit,接收 SubmitResp,并回调 NestJS `SubmitResult`
- Go Gateway 上游读循环开始处理 deliver receipt 和普通 deliver 上行:receipt 解析后回调 NestJS `/gateway/events/receipt`,普通上行解码后回调 `/gateway/events/uplink`
- Gateway 下游入站服务记录客户 Submit 对应的 messageId 到在线客户连接映射;NestJS 收到最终 receipt/uplink 并入库后调用 Gateway `/downstream/receipt``/downstream/uplink`Gateway 向在线客户下发 CMPP Deliver Receipt 或普通 Deliver。
- `api/src/send-chain/send-chain.service.spec.ts` 覆盖 SubmitCommand 上游配置和 Redis Stream 发布;`gateway/internal/inbound/server_test.go` 覆盖客户 submit 后平台下发 Deliver ReceiptGateway 契约示例覆盖新 upstream 字段。
### 验证状态
- `npm --prefix api test -- send-chain.service.spec.ts`:通过。
- `npm --prefix api test`:通过,12 个 suites、82 个 tests。
- `npm --prefix api run build`:通过。
- `go test ./...`Gateway):通过。
- `npm run spike:contracts`:通过,4 个 Gateway 队列契约示例通过。
### 剩余缺口
- Gateway 控制面 `/upstream/submit` 仅保留为调试/补偿入口;生产主链路由 Go Gateway submit worker 消费 Redis Stream `gateway.submit.commands` 触发。worker 当前覆盖新消息 `>` 消费和 ack,pending 历史消息扫描与精细重试治理放入后续在途恢复阶段。
- 客户侧 Deliver Receipt/上行 Deliver 当前依赖 Gateway 内存在线连接映射;客户断线、Gateway 重启或映射丢失时尚未实现持久化缓存、重试和投递失败审计。
- 普通上行只有能关联 messageId 的事件可推送给客户;仅按接入号、手机号、应用和时间窗口匹配客户连接仍待产品化。
- 长短信拆分/重组、多连接窗口、窗口满、在途消息恢复、断线重连后的状态补偿仍待后续实现和压测。
## 2026-07-07 阶段 1Gateway SubmitCommand 独立消费
### 本轮修复
- NestJS SendChain 取消主链路同步调用 Gateway `/upstream/submit`;真实业务校验通过后创建 SmsSubmitRecord、保留 BullMQ `gateway.submit.queue` 审计/兼容投递,并向 Redis Stream `gateway.submit.commands` 写入 `SubmitCommand`
- Go Gateway 新增 `submitworker`,启动时默认创建/复用 consumer group `cmpp-gateway`,独立消费 Redis Stream 中的 `SubmitCommand`,调用同一个上游提交管理器真实 submit 到上游 SMSC。
- Gateway `/upstream/submit` 保留为调试/运维补偿接口,不作为 API 主发送路径。
- Gateway worker 支持环境变量:`REDIS_URL``GATEWAY_SUBMIT_STREAM``GATEWAY_SUBMIT_GROUP``GATEWAY_SUBMIT_CONSUMER``GATEWAY_SUBMIT_WORKER_DISABLED=true`
### 验收口径
- API 入队后不再因为 Gateway 控制面短暂不可达而自己生成 timeoutSubmitResult 必须由 Gateway worker 真实消费和提交后回调。
- Gateway 停止时,SubmitCommand 留在 Redis StreamGateway 恢复后由 consumer group 继续消费新消息。
- BullMQ `gateway.submit.queue` 仅作为审计/兼容,不再是唯一主提交通道。
### 剩余边界
- 当前 worker 先覆盖新消息 `>` 消费和 ackpending 历史消息扫描、claim、重试退避和死信审计放到在途恢复阶段继续做。
## 2026-07-07 阶段 2/3:客户侧 Deliver 持久化重投与普通上行匹配
### 本轮修复
- Prisma 新增 `CmppDownstreamDelivery`,用于保存客户侧待投递 Deliver Receipt 和普通 Deliver 上行;状态覆盖 pending/delivered,记录 retryCount、nextRetryAt、lastError、payload、message/application 关联。
- `SmsUplinkMessage` 增加 `applicationId``messageRecordId``matchStatus``matchReason`,并建立应用和匹配下发记录关系。
- NestJS 收到最终 receipt 后,先写平台回执和消息状态,再创建客户侧待投递记录,尝试调用 Gateway `/downstream/receipt`;成功标记 delivered,客户不在线或 Gateway 不可达时保留 pending 并记录失败原因。
- NestJS 收到普通上行后执行匹配:messageId 精确匹配优先;无 messageId 时按接入号匹配应用路由;仍无唯一应用时按手机号和最近下发时间窗口匹配;多候选标记 ambiguous,未匹配标记 unmatched,但均真实入库。
- Gateway 下游客户 bind/login 成功后保存账号级在线连接,并调用 NestJS `/gateway/events/downstream/pending` 拉取 pending 投递;补发成功后回调 `/gateway/events/downstream/delivered`,失败回调 `/gateway/events/downstream/failed`
- 运营/客户端上行查询 include 应用和匹配下发记录,便于页面展示 matchStatus/matchReason。
### 验证状态
- `npm --prefix api run prisma:generate`:通过。
- `npm --prefix api test -- send-chain.service.spec.ts`:通过。
- `npm --prefix api test`:通过,12 个 suites、82 个 tests。
- `npm --prefix api run build`:通过。
- `go test ./...`Gateway):通过。
- `npm run spike:contracts`:通过。
- `npm run build`:通过,仅既有 Vite chunk size warning。
### 剩余边界
- 待投递 pending 目前在客户 bind/login 时拉取补发;后台周期扫描、指数退避、过期策略、死信队列和运营端失败审计页面仍待后续实现。
- 上行匹配已覆盖 messageId、接入号和手机号时间窗口;共享接入号、多应用多候选时不会误推,但人工认领/改派流程尚未实现。
- 客户连接断开检测和应用级连接数状态回写仍需继续产品化。
## 2026-07-08 阶段 4Gateway 长短信拆分与长上行重组
### 本轮修复
- Go Gateway 上游 Submit 支持长短信第一版拆分:超过 140 字节的短信按 CMPP 标准 6 字节 UDH 生成分片,每片总长度不超过 140 字节,并设置 `PkTotal/PkNumber/TpUdhi` 后逐包发送到上游 SMSC。
- 同一平台 `SubmitCommand` 的多个 accepted 分片 `MsgId` 均登记到 Gateway 映射表,后续任一分片 receipt 可回溯到原 `messageId/submitId/channelId`
- Go Gateway 上游普通 Deliver 支持长上行第一版重组:收到 `TpUdhi=1` 且携带标准 UDH 的分片时,按通道、主叫、被叫、引用号和总片数缓存;分片齐全后只回传一条完整 `UplinkEvent` 给 NestJS。
- 新增 `gateway/internal/upstream/long_message_test.go`,覆盖 UCS2 长短信拆分、短短信不分片、长上行乱序重组。
### 验证状态
- `go test ./...`Gateway):通过。
### 剩余边界
- 阶段 4 后长短信仍按单条平台消息记录展示,尚未提供运营端分片级提交明细、分片级补发审计和部分分片失败后的精细补偿;该审计缺口已在阶段 23 补齐第一版。
- 长上行分片缓存当前为 Gateway 进程内内存;Gateway 重启、跨连接分片漂移或超过缓存 TTL 的残片不会恢复,后续在“在途消息恢复/状态补偿”阶段继续做。
## 2026-07-08 阶段 5Gateway 多连接窗口与窗口满控制
### 本轮修复
- `SubmitCommand.upstream` 契约、示例、Go 结构和 NestJS 生产者增加 `desiredConnections/windowSize`,字段来自通道真实配置;未配置时默认 `desiredConnections=1``windowSize=16`
- Go Gateway 上游提交管理器从单连接升级为通道级连接池:同一通道按 `desiredConnections` 建立多条 CMPP 客户端连接,每条连接独立维护 submit pending、receipt/uplink 映射和长上行分片缓存。
- 每条上游连接增加窗口令牌;提交前必须获得窗口,SubmitResp、reject 或 timeout 后释放窗口;所有连接窗口均满时等待可用窗口,超过提交超时时返回 `WINDOW_TIMEOUT`
- 长短信分片也复用连接池窗口调度,同一条平台消息的多个 accepted 分片仍映射回原 `messageId/submitId/channelId`
- 新增 `gateway/internal/upstream/pool_test.go`,覆盖连接池跨连接获取窗口、窗口满拒绝继续占用、释放后可重新获取。
### 验证状态
- `go test ./...`Gateway):通过。
- `npm --prefix api test -- send-chain.service.spec.ts`:通过。
### 剩余边界
- 当前窗口状态为 Gateway 进程内控制,尚未把连接级窗口占用、等待队列长度、submit latency 等指标回写到 NestJS 或运营端页面。
- 当前阶段只处理窗口容量和多连接发送;Gateway 重启、上游连接断开时的在途 submit 恢复、pending claim、状态补偿和死信审计仍在下一阶段处理。
## 2026-07-08 阶段 6CMPP 配置入口补齐
### 本轮修复
- 运营端通道创建/编辑表单新增上游 `desiredConnections``windowSize` 输入,真实提交到 NestJS 通道 API,并规范化写入 `SmsChannel.config`
- NestJS `ChannelsService``desiredConnections/windowSize` 增加正整数校验;通道激活后的 `ConnectChannel` 请求和发送链路 `SubmitCommand.upstream` 均复用该真实配置。
- Prisma 为 `SmsApplication` 新增 `cmppMaxConnections``cmppWindowSize` 字段;运营端短信应用创建/编辑表单新增 `cmppAccount` 和客户最大连接数输入,客户提交窗口暂不展示给运营配置,保留后端默认值。
- 企业应用 `cmppAccount` 现在支持两种真实路径:显式填写 6 位数字账号,或留空由后端自动生成唯一账号;重复账号和非法格式会被后端拒绝。
- 企业应用 CMPP 参数接口改为从应用真实字段返回 `enterpriseCode/account/passwordCipher/maxConnections/windowSize`,不再借用任意通道企业代码或默认值拼装客户参数。
- 应用级 `cmppEnterpriseCode` 新建/编辑可自定义;接口密码新建默认随机 16 位 UUID 片段,编辑留空不覆盖、填写 16 位后更新。`AppID` 仅作为平台应用标识展示,不作为 CMPP 协议认证参数。
### 验证状态
- `npm --prefix api run prisma:generate`:通过。
- `npm --prefix api test -- sms-config.service.spec.ts channels.service.spec.ts`:通过,2 个 suites、34 个 tests。
- `npm --prefix api run build`:通过。
- `npm run build`:通过,仅既有 Vite chunk size warning。
### 说明
- `desiredConnections/windowSize` 不是 CMPP 协议标准字段,也不是 gocmpp 的原生配置项;它们是本平台对上游通道连接池和提交窗口的运行参数。
- `cmppAccount` 是客户侧应用接入账号;当前已支持真实生成、真实保存和显式配置。
## 2026-07-08 阶段 7SubmitCommand 在途恢复第一步
### 本轮修复
- Go Gateway `submitworker` 在正常消费新消息前新增 pending 恢复流程:对 Redis Stream consumer group 中空闲超过阈值的消息执行 `XAUTOCLAIM`,将滞留在 PEL 的 `SubmitCommand` 认领到当前 consumer。
- 被认领的 pending 命令复用现有 `handleMessage -> Upstream.Submit -> XAck` 成功路径处理;成功后 ack,失败时保留在 PEL,留给后续重试/死信治理。
- `submitworker` 增加可注入 `Submit` 函数,便于单测覆盖消息处理路径;新增单测覆盖 injected submit 和默认 `minIdle` 阈值。
### 验证状态
- `go test ./...`Gateway):通过。
### 剩余边界
- 当前恢复能力只覆盖 Redis Stream PEL 中“已被读走但未 ack”的 pending 命令;尚未实现恢复次数上限、死信队列、失败审计页面和人工补偿入口。
- Gateway 重启时上游连接内已经发出但尚未收到 submit resp 的 in-flight CMPP 请求,仍未完成状态补偿;这部分继续放在后续“断线重连后的消息状态处理”阶段。
## 2026-07-08 阶段 8:上游连接断开时 pending submit 补偿
### 本轮修复
- Go Gateway 上游连接读循环开始区分“空读超时”和“真实连接断开”;空读超时继续等待,真实断开则进入连接丢失处理。
- 某条上游连接断开时,Gateway 会把该连接上所有等待 submit resp 的 pending submit 立即唤醒,返回 `timeout` + `CONNECTION_LOST`,不再机械等待固定 `SUBMIT_TIMEOUT`
- 连接池在再次分配连接前会重新执行 `ensureConnected()`;旧连接断开后,后续新消息可重新建立物理连接继续提交。
- 新增 `gateway/internal/upstream/connection_loss_test.go`,覆盖 pending submit 被唤醒和临时读超时识别。
### 验证状态
- `go test ./...`Gateway):通过。
- `npm --prefix api test -- send-chain.service.spec.ts`:通过。
### 剩余边界
- 当前补偿只覆盖“连接断开且 submit resp 尚未返回”的场景;尚未覆盖“上游其实已受理,但 submit resp 在断线前后丢失”的二次确认和幂等回查。
- submit 结果死信队列、失败审计、恢复次数上限和人工补偿入口仍在后续阶段。
## 2026-07-08 阶段 9receipt 驱动的保守二次归因
### 本轮修复
- Gateway 上游 receipt 事件补充 `phoneNumber`,即使无法从内存 tracker 中精确恢复平台 `messageId`,也会把运营商回执手机号带回 NestJS。
- NestJS `handleReceipt` 新增保守归因:如果 receipt 无法按平台 `messageId/gatewayMessageId` 精确命中,只在“同通道、同手机号、72 小时窗口内、且仅存在 1 条 `timeout + gatewayMessageId=null` 的 submit 记录”时才接收该回执。
- 归因成功后会先回填该次 `sms_submit_record.gatewayMessageId/sequenceId`,再写入真实 `sms_receipt_record` 并按既有逻辑更新 `sms_message_record`、下游客户回执推送和幂等保护。
- 新增 SendChainService 单测,覆盖唯一候选归因成功和多候选拒绝归因两种场景。
### 验证状态
- `go test ./...`Gateway):待本轮统一回归。
- `npm --prefix api test -- send-chain.service.spec.ts`:待本轮统一回归。
### 剩余边界
- 当前只做“唯一候选才归因”的保守版本,仍未实现面向运营商或供应商的 submit 结果主动回查。
- 如果同通道同手机号在窗口内存在多条 timeout 候选,系统会拒绝归因,后续仍需人工补偿或更强的协议级关联键。
## 2026-07-08 阶段 10SubmitCommand 死信治理第一版
### 本轮修复
- Prisma 新增真实表 `GatewaySubmitDeadLetter`,保存 Gateway SubmitCommand 死信的消息 ID、租户/应用/通道、失败原因、尝试次数、原始命令载荷、人工重入队状态和解决状态。
- Go Gateway `submitworker` 新增失败次数治理:同一条 Stream 消息处理失败达到阈值后,调用 NestJS `/gateway/events/dead-letter` 入库死信,并对原消息执行 ack,避免它无限滞留在 PEL。
- Gateway 对非法 `SubmitCommand` 载荷也会直接转死信,防止 poison message 持续阻塞消费。
- NestJS 新增真实死信接口:Gateway 可上报死信;运营端后端可分页查询 `/api/admin/operations/gateway-submit-dead-letters`;可通过 `/api/admin/operations/gateway-submit-dead-letters/:id/requeue` 将原始 `SubmitCommand` 重新写回 Redis Stream。
- NestJS 在收到同一 `submitId/messageId` 的后续真实 `SubmitResult` 时,会把对应死信自动标记为 `resolved`
### 验证状态
- `npm --prefix api run prisma:generate`:通过。
- `npm --prefix api test -- send-chain.service.spec.ts operations.service.spec.ts`:通过,2 个 suites、26 个测试通过。
- `npm --prefix api run build`:通过。
- `go test ./...`Gateway):通过。
### 剩余边界
- 当前死信治理只提供“达到阈值后入库 + 人工重入队”的第一版,尚未实现后台自动重放、重放节流、过期清理和专门的前端运营页面。
- 非法载荷死信如果缺少完整 `SubmitCommand`,当前不可人工重放,只能用于审计和人工排查。
## 2026-07-08 阶段 11:下游客户在线时周期补投与失败封顶
### 本轮修复
- 明确责任边界:客户系统负责断线后的重新 bind;平台负责客户不在线或临时投递失败时的消息不丢、待投递保存和补投。
- Go Gateway 下游入站服务新增在线账号周期补投:除客户 bind 成功后立即拉取 pending 外,Gateway 还会按周期为当前在线账号再次调用 `/gateway/events/downstream/pending`,继续补发未投递成功的 Deliver Receipt/上行 Deliver。
- Gateway 向下游发送 Deliver 失败时会清理失效的内存会话映射,避免对已失效连接无休止重复尝试。
- NestJS `markDownstreamDeliveryFailed` 新增失败上限:未超过阈值时继续 `pending` 并推进 `retryCount/nextRetryAt`;达到阈值后转为 `failed`,停止无限重试,并写 `gateway.downstream_delivery_failed` 系统日志。
### 验证状态
- `npm --prefix api test -- send-chain.service.spec.ts`:通过,1 个 suite、21 个测试通过。
- `go test ./internal/inbound ./internal/control ./...`Gateway):通过。
### 剩余边界
- 当前周期补投只针对“Gateway 认为客户在线”的账号;尚未实现下游投递失败专门列表、人工重投页面和跨 Gateway 实例共享的客户在线状态。
- `CmppDownstreamDelivery` 目前仍使用固定重试间隔,尚未实现指数退避、不同消息类型差异化策略和过期归档。
## 2026-07-08 阶段 12:下游投递失败审计与人工重投
### 本轮修复
- 运营端新增真实下游投递查询接口 `/api/admin/operations/downstream-deliveries`,支持按 `tenantId/applicationId/deliveryType/status/keyword` 筛选并分页返回真实 `CmppDownstreamDelivery` 数据。
- NestJS 新增 `/api/admin/operations/downstream-deliveries/:id/requeue`,可对单条下游投递记录执行人工重投,真实调用 Gateway `/downstream/receipt``/downstream/uplink`,并写 `gateway.downstream_delivery_requeue` 系统日志。
- 运营端新增“下游投递记录”页面,列表、详情、筛选和重投均接真实后端,不使用 mock、本地状态或静态数组。
### 验证状态
- `npm --prefix api test -- send-chain.service.spec.ts operations.service.spec.ts`:通过,2 个 suites、29 个测试通过。
- `npm --prefix api run build`:通过。
- `npm run build`:通过,仅有既有 Vite chunk size warning。
### 剩余边界
- 当前人工重投仍是单条操作,尚未提供批量重投、失败聚合告警和专门的下游投递 Dashboard。
- 页面侧暂未做自动轮询刷新,需要手动查询或重进页面观察状态变化。
## 2026-07-08 阶段 13:下游投递自动退避第一版
### 本轮修复
- `CmppDownstreamDelivery` 的失败重试从固定 60 秒改为指数退避:基础间隔来自 `CMPP_DOWNSTREAM_RETRY_DELAY_MS`,每次失败按 2 倍递增,并受 `CMPP_DOWNSTREAM_RETRY_MAX_DELAY_MS` 上限约束。
- 这样在客户长时间离线或网络持续抖动时,平台不会每分钟机械重试同一条下游投递,能更温和地消耗 API、Gateway 和连接资源。
- 总重试次数上限逻辑保持不变,超过 `CMPP_DOWNSTREAM_MAX_RETRIES` 后仍转 `failed` 并写失败审计。
### 验证状态
- `npm --prefix api test -- send-chain.service.spec.ts`:通过,新增指数退避单测。
### 剩余边界
- 当前退避策略还没有加入随机抖动,多个记录在同一时间失败时,后续重试时刻仍可能比较集中。
- 退避参数当前是全局环境变量,尚未细分到 receipt/uplink 或不同客户应用级别。
## 2026-07-08 阶段 14:下游投递批量重投
### 本轮修复
- 运营端下游投递记录页新增勾选和“批量重投”操作,仅允许对当前页的 `pending/failed` 记录执行批量重投。
- NestJS 新增真实批量接口 `/api/admin/operations/downstream-deliveries/requeue`,逐条调用既有单条重投逻辑,返回成功/失败汇总,不用前端自行拼结果。
- SendChainService 新增批量重投结果汇总与空选择拦截单测。
### 验证状态
- `npm --prefix api test -- send-chain.service.spec.ts`:通过,新增批量重投单测。
- `npm --prefix api run build`:通过。
- `npm run build`:待本轮统一回归。
### 剩余边界
- 当前批量重投只支持“勾选当前页记录”,还不支持“按筛选条件全量重投”或后台异步大批量任务。
- 批量结果当前以内联提示为主,尚未做专门的批量执行历史与导出。
## 2026-07-08 阶段 15:下游投递告警第一版
### 本轮修复
- `OperationsService.dashboard()` 新增真实下游投递告警聚合 `downstreamDeliverySummary`,统计 `pending/failed/delivered` 总量,以及“积压过久的 pending”和“最近失败”两类告警计数。
- 运营端右上角通知新增“下游投递告警”,数量直接来自真实 Dashboard 聚合。
- 运营看板新增下游投递告警摘要卡片,帮助运营从总览页直接感知当前下游投递异常。
### 验证状态
- `npm --prefix api test -- operations.service.spec.ts`:随定向测试通过。
- `npm --prefix api run build`:通过。
- `npm run build`:通过,仅有既有 Vite chunk size warning。
### 剩余边界
- 当前告警仍是站内聚合提醒,尚未接短信、邮件、企业微信等外部告警通道。
- 告警口径当前采用全局阈值环境变量,尚未按客户应用、消息类型或时间段细分。
## 2026-07-08 阶段 16:下游投递 Dashboard 第一版
### 本轮修复
- 新增真实接口 `/api/admin/operations/downstream-deliveries/dashboard`,直接按 `CmppDownstreamDelivery` 聚合返回 `summary/typeBreakdown/retryBuckets/topApplications`
- 运营端“下游投递记录”页面顶部补上真实 Dashboard 区域,展示投递总量、待投递、已投递、告警、类型分布、重试压力和应用告警排行。
- Dashboard 筛选范围与页面应用/类型筛选保持一致,不允许由前端只根据当前页列表数据临时拼装。
### 验证状态
- `npm --prefix api test -- operations.service.spec.ts`:通过。
- `npm --prefix api run build`:通过。
- `npm run build`:通过,仅有既有 Vite chunk size warning。
- `git diff --check`:无空白错误,仅 Windows LF/CRLF 提示。
### 剩余边界
- 当前 Dashboard 仍偏运营处置视角,尚未补时间趋势、按客户/账号维度的更细颗粒聚合。
- 应用告警排行当前以 `pending + failed` 为主排序,尚未加入更复杂的权重和 SLA 指标。
## 2026-07-08 阶段 17:下游在线账号 Presence 持久化底座
### 本轮修复
- Gateway inbound 新增 Redis presence store,客户 `cmppAccount` 在 bind 成功、submit 建链和下游回执/上行投递时,会把在线账号状态写入 Redis。
- presence 数据至少包含 `account/srcId/remoteIp/gatewayInstanceId/state/connectedAt/updatedAt`,并按 TTL 自动过期,避免该状态只存在单进程内存中。
- Gateway 发送失败触发连接清理时,会同步移除该账号的 Redis presence 记录。
- 该阶段先完成“在线状态外部化”,尚未宣称“Gateway 重启后 pending 投递自动恢复”已完成;恢复逻辑在后续阶段继续补。
### 验证状态
- `go test ./internal/inbound/...`:通过。
- `go test ./cmd/gateway/...`:通过。
### 剩余边界
- 当前 presence 主要服务于后续恢复能力,Gateway 还未在启动时主动根据 Redis presence 扫描并恢复 pending 投递。
- 连接断开当前主要依赖发送失败清理和 TTL 过期兜底,尚未建立更完整的显式断线回收机制。
## 2026-07-08 阶段 18Gateway 恢复候选视图
### 本轮修复
- Gateway 启动时会读取 Redis presence,并输出恢复候选账号加载日志。
- 新增控制面接口 `GET /downstream/recovery-candidates`,返回 Redis presence 与当前内存在线账号合并后的恢复候选视图。
- 候选视图当前用于后续恢复逻辑和运维排查,不直接触发 pending 下游投递补发。
### 验证状态
- `go test ./internal/inbound/...`:通过。
- `go test ./internal/control/...`:通过。
- `go test ./cmd/gateway/...`:通过。
### 剩余边界
- 当前只是“识别谁值得恢复”,还没有执行“把这些账号的 pending 回执/上行自动继续补投”。
- 候选视图默认按 Redis TTL 和最近活跃时间保留,尚未叠加更复杂的健康判定和跨实例去重策略。
## 2026-07-08 阶段 19Gateway pending 恢复执行第一版
### 本轮修复
- Gateway 启动时会立即按恢复候选账号执行一次 pending 下游投递恢复扫描。
- 后续每轮补投周期除扫描当前内存在线账号外,也会继续扫描恢复候选账号,尝试恢复 `CmppDownstreamDelivery.pending`
- 当前恢复策略是“能投就投,投不了继续 pending”:若账号尚无可用下游连接,Gateway 不会把记录误标成失败,而是等待客户重连后的后续恢复机会。
### 验证状态
- `go test ./internal/inbound/...`:通过。
- `go test ./internal/control/...`:通过。
- `go test ./cmd/gateway/...`:通过。
### 剩余边界
- 当前恢复仍按固定扫描周期触发,尚未做更细的按账号退避、恢复批次追踪和恢复告警。
- 仍未覆盖更复杂的长短信分片恢复、跨实例抢占协调和恢复中的重复投递防抖。
## 2026-07-08 阶段 20Gateway 恢复退避、锁与状态审计
### 本轮修复
- Gateway 新增账号级恢复锁,避免同一 `cmppAccount` 被并发重复恢复。
- 恢复失败、等待连接和部分成功场景会写入真实恢复状态,并按指数退避计算下一次可恢复时间,减少无意义高频重试。
- 控制面新增 `GET /downstream/recovery-statuses`,可查看账号最近恢复状态、尝试次数、下一次重试时间和错误原因。
### 验证状态
- `go test ./internal/inbound/...`:通过。
- `go test ./internal/control/...`:通过。
- `go test ./cmd/gateway/...`:通过。
### 剩余边界
- 当前恢复状态审计仍停留在 Gateway 控制面和 Redis,尚未同步到运营端页面或 NestJS 持久化审计表。
- 恢复退避当前按账号统一处理,尚未细分到回执/上行类型、失败类别或跨实例抢占优先级。
## 2026-07-08 阶段 21Gateway 恢复总览与链路缺口收口
### 本轮修复
- 控制面新增 `GET /downstream/recovery-overview`,一次性返回恢复候选账号和恢复状态,便于生产联调与排查。
- 需求文档已按当前真实代码重新梳理 CMPP 端到端链路剩余缺口,明确区分“已能验收的真实链路能力”和“尚未产品化完成的恢复审计/指标/复杂补偿能力”。
- 系统测试用例新增恢复总览接口口径,便于后续生产验证直接对照。
### 验证状态
- `go test ./internal/control/...`:通过。
- `go test ./internal/inbound/...`:通过(延续前一阶段验证结果,本轮未改动 inbound 核心分支逻辑)。
- `go test ./cmd/gateway/...`:通过。
### 阶段 21 后剩余真实缺口
- 恢复状态仍未写回 NestJS/Prisma/PostgreSQL,运营端暂无真实恢复状态页面。
- 多 Gateway 实例下更强的恢复抢占协调、分片级补偿审计、共享接入号上行人工认领仍未完成。
- 连接级窗口利用率、恢复吞吐、恢复失败分布等运营指标仍未进入真实后台页面。
## 2026-07-08 阶段 22:恢复状态回流 NestJS 与运营端展示
### 本轮修复
- NestJS 新增真实恢复状态接收接口 `/api/gateway/events/downstream/recovery-status`
- Prisma/PostgreSQL 新增 `GatewayDownstreamRecoveryStatus` 表,按 `cmppAccount` 持久化恢复状态、尝试次数、下一次恢复时间、错误原因及应用/企业关联。
- 运营端新增独立“恢复状态管理”页面,支持真实恢复状态列表、分页、详情接口和当前筛选结果 CSV 导出。
- 原“下游投递记录”页面仅保留投递记录与重投能力,不再混放恢复状态列表。
- 恢复状态新增 `failureCategory` 失败分类字段,Gateway 回传、NestJS 兜底归类并落库,运营端支持分类筛选、分布统计、详情展示和导出。
- 多 Gateway 恢复抢占协调补强:恢复锁升级为 Redis token 租约,恢复完成时通过 Lua 原子校验 token 后才写状态和释放锁;迟到旧实例不能误删新实例锁。
- `GatewayDownstreamRecoveryStatus` 新增 `lockOwner/lockExpiresAt`Gateway 回传并由 NestJS 入库,运营端恢复状态列表和详情可查看锁持有实例。
- 本地启动脚本补充 `.local-tools\minio.exe` 查找路径,并已验证本机 MinIO 可通过 `npm run start:local:minio` 启动。
### 验证状态
- `npm --prefix api run prisma:generate`:通过。
- `npm --prefix api test -- operations.service.spec.ts send-chain.service.spec.ts`:通过。
- `npm --prefix api run build`:通过。
- `go test ./internal/inbound/... ./internal/control/... ./cmd/gateway/...`:通过。
- `npm run build`:通过,仅有既有 Vite chunk size warning。
- `npm --prefix api run prisma:migrate:deploy`:通过,已应用 `20260708213000_add_recovery_lock_observability`
- `npm run start:local:minio`:通过,MinIO API `http://localhost:9000`、Console `http://localhost:9001` 已监听。
### 阶段 22 后剩余真实缺口
- 恢复状态已回流 NestJS,并已具备独立运营页、详情、导出和第一版失败分类分布;后续仍缺少恢复吞吐、耗时趋势、连续失败账号等更细指标。
- 多 Gateway 账号级恢复抢占协调已具备 token 租约和完成校验;分片级补偿审计、共享接入号上行人工认领仍未完成。
- 连接级窗口利用率、连接级心跳、恢复吞吐和恢复耗时等运营指标仍未进入真实后台页面。
## 2026-07-08 阶段 23:长短信分片级补偿审计
### 本轮修复
- Prisma/PostgreSQL 新增 `SmsMessageSegmentAudit`,按短信记录、submitId、分片序号保存真实分片提交、回执和补偿归因。
- Go Gateway 上游提交结果 `SubmitResult` 增加 `segments[]`,逐片回传 `segmentTotal/segmentIndex/sequenceId/gatewayMessageId/submitStatus/submittedAt`,长短信不再只暴露首个分片结果。
- NestJS `handleSubmitResult` 写入分片提交审计,`handleReceipt` 按上游 `gatewayMessageId` 回填分片回执状态;重投或补偿产生的新 submitId 与历史 submitId 可并存追踪。
- 运营端短信记录详情新增“分片补偿审计”列表,从真实 API 查询 `SmsMessageSegmentAudit`,展示分片、submitId、通道、Sequence、MsgId、提交状态、回执状态、补偿类型和错误信息。
- 契约文档和示例补充 `SubmitResult.segments[]`,系统测试用例新增 `TC-GW-026 长短信分片补偿审计`
### 验证状态
- `npm --prefix api run prisma:generate`:通过。
- `go test ./internal/upstream/... ./internal/queue/... ./internal/submitworker/...`:通过。
- `npm --prefix api test -- send-chain.service.spec.ts operations.service.spec.ts`:通过。
- `npm --prefix api run build`:通过。
- `npm run build`:通过,仅有既有 Vite chunk size warning。
### 阶段 23 后剩余真实缺口
- 长短信分片级提交、回执和补偿归因已具备真实审计;后续仍需补按单个分片自动重投、分片级人工重投和更细的补偿指标。
- 共享接入号、多候选普通上行的人工认领流程仍未完成。
- 连接级窗口利用率、连接级心跳、恢复吞吐、恢复耗时趋势和连续失败账号等运营指标仍未进入真实后台页面。
## 2026-07-08 阶段 24:共享接入号上行人工认领
### 本轮修复
- Prisma/PostgreSQL 新增 `SmsUplinkMatchCandidate`,用于保存普通上行 ambiguous 场景下的候选企业、应用、下发短信、候选来源、置信度、认领状态和认领时间。
- NestJS 上行匹配逻辑增强:接入号匹配多个应用、或手机号时间窗口匹配多条下发时,不误推客户;上行记录标记 `ambiguous`,并真实写入候选表。
- 运营端“短信上行记录”详情新增候选认领区,展示候选企业、候选应用、候选来源、置信度、候选下发短信和候选原因,支持“认领并推送”。
- 新增 `POST /admin/operations/uplink-messages/:id/claim`:认领后更新 `SmsUplinkMessage``matched`,选中候选置为 `claimed`,其他候选置为 `rejected`,写入操作日志,并创建真实 `CmppDownstreamDelivery(deliveryType=uplink)` 走客户侧下游投递链路。
- 系统测试用例新增 `TC-GW-027 共享接入号上行人工认领`
### 验证状态
- `npm --prefix api run prisma:generate`:通过。
- `npm --prefix api run prisma:migrate:deploy`:通过,已应用 `20260708233000_add_uplink_match_candidates`
- `npm --prefix api test -- send-chain.service.spec.ts operations.service.spec.ts`:通过,40 个测试。
- `npm --prefix api run build`:通过。
- `npm run build`:通过,仅有既有 Vite chunk size warning。
### 当前剩余真实缺口
- 共享接入号上行已具备候选记录、人工认领和认领后下游投递第一版;后续仍需补批量认领、认领复核和认领准确率/积压指标。
- 长短信分片级提交、回执和补偿归因已具备真实审计;后续仍需补按单个分片自动重投、分片级人工重投和更细的补偿指标。
- 连接级窗口利用率、连接级心跳、恢复吞吐、恢复耗时趋势和连续失败账号等运营指标仍未进入真实后台页面。
## 2026-07-03 阶段 9:运营端报备回执导入真实上传/解析
### 本轮修复
- 运营端报备任务导入弹窗改为真实选择 CSV/TSV/TXT 文件。
- 前端先调用 `/api/admin/files/upload` 保存文件对象,再提交 `fileObjectId`、文件名和文本内容到 `/api/admin/report-tasks/{id}/receipt-import`
- 后端导入接口解析文本回执,识别 `status/result/状态/结果` 列,统计成功行、失败行,并保存行级解析结果。
- 报备任务状态由后端按解析结果派生:全成功为 `completed`,有成功有失败为 `partial`,全失败或空文件为 `failed`
- 未识别的运营商状态按失败处理,避免把未知回执误判为通过。
### 已执行命令
```bash
npm --prefix api test -- channels.service.spec.ts
npm --prefix api test
npm --prefix api run build
npm run build
git diff --check
```
### 当前结果
- `api/src/channels/channels.service.spec.ts` 新增文本回执解析和任务状态派生覆盖。
- API Jest12 个 test suite 通过,73 个测试通过。
- API build 通过。
- 前端 build 通过,仍存在既有 Vite chunk size warning。
- `git diff --check` 无空白错误,仅 Windows 工作区 LF/CRLF 提示。
- 本地服务已重启:`http://localhost:3000/``http://localhost:5173/` 均监听,`/api/admin/report-tasks``/admin/report-tasks` HTTP smoke 返回 200。
## 2026-07-03 全菜单真实后端、上传和列宽回归
### 本轮修复
- 客户端企业认证从纯前端状态机改为真实 `GET/POST /api/client/enterprise-certification` 驱动。
- 客户端企业认证营业执照上传接入 `/api/admin/files/upload`,提交时保存 `licenseFileObjectId` 等材料字段。
- 运营端企业表单“企业照片”从禁用占位按钮改为真实上传,保存时写入 `photoFileObjectId`
- 客户端账号设置、运营端系统配置无真实保存接口,已移除路由并删除纯前端页面。
- 彩信待开发菜单路由统一指向占位页,不再进入静态 mock 演示页面。
- 客户端短信发送详情、批量任务表格中明显偏窄的中文字段列已加宽。
### 已执行命令
```bash
npm --prefix api test
npm --prefix api run build
npm run build
npm run spike:contracts
npm run test:gateway
$env:API_BASE_URL='http://127.0.0.1:3000/api'; node tools/smoke/real-env-smoke.mjs
npm run verify:phase8
git diff --check
```
### 当前结果
- API Jest12 个 test suite 通过,73 个测试通过。
- API build 通过。
- 前端 build 通过,仍存在既有 Vite chunk size warning。
- Gateway 队列契约通过,4 个示例均验证通过。
- `npm run test:gateway` 通过。
- 真实 API smoke 通过,覆盖真实 PostgreSQL/Redis/MinIO/API 主链路和文件上传对象写入。
- 浏览器抽检通过:客户端真实登录后,企业认证页面无“纯前端原型”文案,资料页出现真实上传入口;彩信待开发入口显示占位页而非静态表单。
- `npm run verify:phase8` 仍未通过,失败点仍是已知 BullMQ spike 性能阈值:15000 条消息、并发 500、end-to-end TPS 469.76,低于 500。
## 2026-07-03 企业列表列宽和新建应用交互回归
### 本轮修复
- 通用 `Table` 组件增加 `colgroup`、列最小宽度和表格最小宽度计算,显式配置的业务列不再被容器强行压窄,超出区域横向滚动。
- 企业管理列表加宽企业 ID、企业名称、企业编码、统一社会信用代码、联系人、联系电话、余额、短信余量、状态和操作列。
- 企业模板管理列表加宽企业、应用、签名、模板内容、审核状态、更新时间和操作列,模板内容列保留两行展示。
- 运营端短信任务进度、短信审核、短信记录、报备任务、用户、系统日志、安全控制、充值记录等列表中的状态/操作/数量等易挤压列统一加宽。
- 新建企业应用入口弹窗改为先选择真实企业,再进入应用参数、客户单价、IP 白名单和三网通道组配置;未选择企业时“下一步”禁用。
- 新建短信应用表单把移动、联通、电信通道组配置改为独立卡片区,显示已配置数量和无可用通道组提示;未填写应用名称或未选择任一运营商通道组时禁止保存。
- 补齐基础弹窗居中、遮罩、最大宽度和正文滚动样式,避免 1280px 视口下弹窗偏移或被截断。
### 已执行命令和浏览器验证
```bash
npm run build
git diff --check
```
- 前端 build 通过,仍存在既有 Vite chunk size warning。
- `git diff --check` 无空白错误,仅 Windows 工作区 LF/CRLF 提示。
- 窄列扫描仅剩短字段列:报备字段“必填”90px、运营看板排名72px、短信上行选择框72px。
- 浏览器使用真实运营端登录 `admin@example.com` 抽检通过:
- 企业管理表格最小宽度 1920px,统一社会信用代码列 220px,联系人列 160px,联系电话列 150px,横向滚动生效。
- 企业模板管理表格最小宽度 1820px,模板内容列 420px,横向滚动生效。
- 新建企业应用弹窗在 1280px 视口下未截断,未选择企业时“下一步”禁用。
- 新建短信应用页显示三网通道组卡片、已配置数量和无可用通道组提示,初始状态“创建应用”禁用。
## 2026-07-06 文件上传预览和下载回归
### 本轮修复
- 文件服务新增真实下载接口 `GET /api/admin/files/:id/download`,从 MinIO 或本地对象存储读取真实文件对象,支持 `inline` 预览和 `attachment` 下载。
- 运营端企业照片、企业签名材料、引流材料、报备回执导入均在真实上传成功后显示下载入口;图片类型文件显示点击预览入口。
- 客户端企业认证营业执照上传成功后显示下载入口,图片类型文件显示点击预览入口;提交认证时保存文件类型信息。
- 客户端签名列表对已保存签名材料显示下载入口,图片材料按文件名或类型显示预览入口。
- 客户端短信发送导入号码文件为前端解析文件,未生成后端文件对象;页面仅提供本地原始文件下载,不标记为真实后端归档。
### 已执行命令
```bash
npm --prefix api test -- files.service.spec.ts
npm --prefix api run build
npm run build
git diff --check
```
### 当前结果
- 文件服务单测通过:1 个 test suite、2 个测试通过。
- API build 通过。
- 前端 build 通过,仍存在既有 Vite chunk size warning。
- `git diff --check` 无空白错误,仅 Windows 工作区 LF/CRLF 提示。
## 2026-07-06 企业列表人工充值入口
### 本轮修复
- 运营端企业管理列表新增“充值”按钮。
- 点击“充值”打开企业人工充值弹窗,展示企业名称、当前余额,并支持录入充值金额、操作人和备注;充值金额允许负数冲正,0 金额不允许提交;企业列表入口不要求填写短信条数。
- 提交后调用现有真实接口 `POST /api/admin/billing/manual-recharges`,成功后重新拉取企业管理列表,余额来自真实账户接口聚合结果。
- 该入口不使用前端本地状态模拟充值入账;充值订单、账户余额、账户流水和操作日志仍由后端 `BillingService.createManualRecharge` 负责。
### 已执行命令
```bash
npm run build
git diff --check
```
### 当前结果
- 前端 build 通过,仍存在既有 Vite chunk size warning。
- `git diff --check` 无空白错误,仅 Windows 工作区 LF/CRLF 提示。
## 2026-07-07 手机号段 Tab 和通道组补发上限
### 本轮修复
- 运营端手机号段库页面移除自定义卡片式 Tab,改用通用 `Tabs` 控件,与企业应用管理页面“短信应用/彩信应用”交互一致。
- 通道组添加/编辑页面新增“补发时间上限(小时)”输入控件,编辑时回填 `retryTimeLimitHours`,保存时写入真实通道组接口。
- 补发时间上限按后端现有校验限制为 1 到 72 小时。
### 已执行命令
```bash
npm run build
git diff --check
```
### 当前结果
- 前端 build 通过,仍存在既有 Vite chunk size warning。
- `git diff --check` 无空白错误,仅 Windows 工作区 LF/CRLF 提示。
## 2026-07-07 账单流水页面移除和列表分页
### 本轮修复
- 删除客户端账单流水页面和运营端账单流水页面,移除对应路由、菜单、占位映射和首页跳转入口。
- 移除公开交易查询/创建接口:`GET/POST /api/admin/billing/transactions``GET /api/client/billing/transactions`
- 保留内部 `AccountTransaction` 写入能力,人工充值、扣费、释放、退款等真实计费动作仍可写入内部账务记录;本期不作为独立账单流水页面验收。
- 通用 `Table` 组件新增内置分页,默认每页 10 条;服务端分页页面关闭内置分页,避免双分页。
- 补齐手写列表和卡片列表分页:通道管理、通道组、充值记录、客户端应用、客户端充值套餐、客户端签名、客户端模板、客户端批量任务、客户端发送详情、运营端短信任务进度、运营端企业签名。
### 已执行命令
```bash
npm run build
npm --prefix api run build
npm --prefix api test -- billing.service.spec.ts --runInBand
git diff --check
```
### 当前结果
- 前端 build 通过,仍存在既有 Vite chunk size warning。
- API build 通过。
- BillingService 单测通过:1 个 test suite、6 个测试通过。
- `git diff --check` 无空白错误,仅 Windows 工作区 LF/CRLF 提示。
## 2026-07-07 通道组表格和分钟级补发上限
### 本轮修复
- 通道组添加/编辑页的省网分流、全国通道配置从卡片改为通用表格展示,行内保留编辑、删除操作。
- 通道状态文案改为设计锚点口径“链接正常/通道停用”;“链接正常”必须来自真实 CMPP 连接状态 connected 且当前连接数大于 0,新建但未连接的 active 通道不再显示为链接正常。
- 通道组补发时间上限从整小时升级为分钟级配置,页面交互为“小时 + 分钟”,默认 12 小时 0 分钟;后端新增 `retryTimeLimitMinutes` 持久化字段,并保留 `retryTimeLimitHours` 兼容旧调用。
- 发送链路按分钟级上限判断是否继续补发,超过配置分钟数、超过 72 小时或关闭补发时均不再补发。
### 已执行命令
```bash
npm --prefix api run prisma:generate
npm --prefix api test -- channels.service.spec.ts send-chain.service.spec.ts --runInBand
npm --prefix api run build
npm run build
git diff --check
```
### 当前结果
- Prisma Client 已根据新 schema 生成。
- ChannelsService 和 SendChainService 定向单测通过:2 个 test suites、35 个测试通过。
- API build 通过。
- 前端 build 通过,仍存在既有 Vite chunk size warning。
- `git diff --check` 无空白错误,仅 Windows 工作区 LF/CRLF 提示。
## 2026-07-10 手机号段库大数据分页
### 本轮修复
- `GET /api/admin/dictionaries/phone-segments` 从固定返回前 200 条改为按唯一 `prefix` 游标分页,支持服务端按号段、运营商、省份和城市搜索。
- API 每页多读取 1 条计算 `hasMore/nextCursor`,不执行 50 万级号段表的 `COUNT(*)`
- 运营端手机号段页面使用真实服务端分页,移除号段总数卡片、Tab 数字和分页总数,只显示当前页码及上一页/下一页。
- 生产手机号段数据已从 `dannyhu926/phone_location` 2026 年 4 月数据导入;源数据 516470 条,过滤 253 条非 7 位异常记录,最终有效 7 位号段 516217 条。
### 验证口径
- API 定向单测覆盖游标、搜索、每页多取 1 条和不查询总数。
- 前端 build 和 API build 必须通过。
- 生产验证应覆盖首尾翻页、关键词搜索、API/Gateway/PostgreSQL 健康状态和典型号段归属地查询。
### 已执行命令与结果
```bash
npm --prefix api test -- --runTestsByPath src/dictionaries/dictionaries.service.spec.ts
npm --prefix api run build
npm run build
git diff --check
```
- DictionariesService 定向单测通过:1 个 test suite、3 个测试通过。
- API build 和前端 build 通过;前端仍有既有 chunk size warning。
- 生产 API 实测 `pageSize=2`:第一页返回 `1300000/1300001``nextCursor=1300001`,下一页返回 `1300002/1300003`,响应无 `total` 字段。
- 生产 API 搜索 `1882120` 返回“中国移动/上海/上海”;搜索“上海”首屏响应约 80ms。
- 生产 `cmpp-api``cmpp-gateway`、PostgreSQL、Nginx 均为 activeAPI health 正常。
- 隔离部署后曾因 `dist/assets` 被保留为 `700 root:root` 导致 Nginx 无权读取 JS/CSS、admin 页面空白;线上已修正为目录 `755`、文件 `644`,正式生产部署脚本同步固化权限。
- 正式部署发现已有生产管理员且未配置 `PROD_ADMIN_PASSWORD` 时,`upsert.create` 仍会对空密码执行哈希;已拆分 create/update 密码变量,已有账号不改密码,新建账号才生成临时密码。
## 2026-07-10 Batch 0 飞书瑕疵台账与分批策略
来源:飞书《短信平台第一版瑕疵》。本表仅记录问题路由和验收边界;除 Batch 1 外,其他项目仍须先在生产验证环境只读复现并核对真实代码、API、PostgreSQL、Redis、MinIO 或 Gateway 状态,不能根据页面现象直接修改。
| 飞书项 | 初步分类 | 真实链路/风险 | 计划批次 | 当前状态 |
| --- | --- | --- | --- | --- |
| 1.1-1.5 企业-充值流程 | UI + API/DB | 对象存储预览、人工充值、充值订单、账户流水 | Batch 1 | 已完成并部署;历史余额取 AccountTransaction 快照 |
| 2.1 通道密码展示/修改 | UI + API/DB + 安全 | 密码密文、权限、审计、上游连接配置 | Batch 2 | 已完成:密码不回显,留空不覆盖,填写新值才更新 |
| 2.2 扩展位数和通道流速 | UI + API/DB + Gateway | 通道配置持久化、Gateway submit 限速 | Batch 2 | 已完成:真实持久化并下发 Gateway SubmitCommand |
| 2.3 通道组名称为空提示 | UI 校验 | 服务端字段校验与前端错误提示一致 | Batch 2 | 已验证:既有前端提示会在真实 API 调用前中断保存 |
| 2.4 发送记录详情弹窗 | UI + API | 详情、回执、提交记录必须来自真实接口 | Batch 2 | 已完成:真实状态、提交和回执信息分层展示 |
| 2.5 连接日志优化 | UI + API + Gateway | 连接状态回写、操作日志、分页筛选 | Batch 2 | 已完成:展示真实连接状态摘要并支持日志关键词筛选 |
| 2.6 通道测试 | API/DB + Gateway/CMPP | 测试 submit、Redis Stream、上游响应、审计 | Batch 2 | 已完成:提交结果展示真实测试流水和提交记录 |
| 2.7 短信记录页面 | UI + API/DB + Gateway | 短信、submit、回执、分片审计真实查询 | Batch 2 | 已完成:筛选下推 PostgreSQL,详情/审计为真实接口 |
| 3.1 报备配置无返回 | UI 导航 | 返回后筛选/表单状态不丢失 | Batch 3 | 已完成:返回通道列表 |
| 3.2 通道组添加通道弹窗 | UI + API | 通道组成员真实保存和回填 | Batch 3 | 已完成:真实候选、状态展示、重复项限制和错误提示 |
| 4.1 创建用户 | UI + API/DB | 用户、角色、企业关联、审计 | Batch 4 | 已完成:真实表单校验、提交状态和错误提示 |
| 4.2 禁用/删除/改密后踢下线 | API/DB + 会话 | Token/session 失效、跨浏览器验证、审计 | Batch 4 | 已完成:数据库会话版本使旧 token 失效 |
| 4.3 禁用按钮颜色 | UI | 仅样式,保持通用危险操作语义 | Batch 4 | 已完成:使用 warning 语义色 |
| 4.4 个人改密缺失 | UI + API/会话 | 当前用户校验、密码更新、旧会话失效 | Batch 4 | 已完成:右上角真实当前密码校验与改密 |
| 5.1 待审核任务数不准 | API/DB 聚合 | 审核状态口径与任务明细一致 | Batch 5 | 已完成:风险审核改按 SmsSendTask.pending_review 统计 |
| 5.2 任务进度 | UI + API/DB + Gateway | 状态机、发送/回执计数、分页 | Batch 5 | 已完成:未知/超时不重复累计,已处理数不超过总号码数 |
| 5.3 企业应用 | UI + API/DB | 短信应用真实 CRUD/审核;彩信仅占位 | Batch 5 | 已完成:停用使用 warning 色,启用使用 success 色,搜索区宽度协调 |
| 5.4 企业模板 | UI + API/DB | 模板材料、审核状态、真实筛选 | Batch 5 | 已完成:审核状态以中文展示,draft 显示为草稿 |
| 5.5 企业签名 | UI + API/DB + MinIO | 资质文件、签名审核、对象存储预览 | Batch 5 | 已完成:左边框按三网真实报备结果展示,编辑页不允许手工改报备状态 |
| 5.6 引流信息 | UI + API/DB | 字典字段、签名/模板关联、审核口径 | Batch 5 | 已完成:列表改为引流信息、长链接不跳转且省略展示、操作列可见,编辑页不允许手工改报备状态 |
| 6.1 手机号段库 Tab | UI | 使用通用 Tabs,不改变真实号段数据路径 | Batch 6 | 已完成:Tab 按内容宽度展示 |
| 6.2 运营商区分规则分页 | UI + API/DB | 服务端分页、筛选与总数口径 | Batch 6 | 已完成:PostgreSQL 分页、总数、25 条每页 |
| 7.1 敏感词页 | UI + API/DB | 敏感词 CRUD、生效范围、发送校验 | Batch 6 | 已完成:状态 Tag 清晰展示,添加弹窗扩展,保留真实 CRUD |
| 8.1 系统日志 IP 为空 | API/DB + Nginx | 转发头、请求上下文、OperationLog 落库、历史数据边界 | Batch 6 | 已完成:真实 HTTP 操作日志记录 Nginx 转发的客户端 IP;后台任务保持空值 |
| 8.2 客户端标题 | UI | 客户端产品名称与运营端区分 | Batch 6 | 已完成:短信平台客户端 |
| 8.3 通用输入框/文本框样式 | UI | 去除内层填充色,保留边框和焦点状态 | 回归复查 | 已完成:含 Chromium 自动填充背景 |
| 8.4 精确时间格式 | UI | 所有精确时间统一 `YYYY-MM-DD HH:mm:ss` | Batch 6 | 已完成:统一 helper 覆盖日志、用户、充值、任务与配置展示 |
| 8.5 中文图片文件名乱码 | API/DB + MinIO + UI | multipart 编码、对象存储文件名、历史展示兼容 | 回归复查 | 已完成:新上传正确入库,历史展示兼容解码 |
| 8.6 全局分页控件 | UI + API/DB | 总页数、首页/末页、指定页跳转与服务端分页口径 | Batch 6 | 已完成:统一控件支持首页、末页、页码跳转;真实服务端分页页传入总页数 |
| 9.1 客户端菜单顺序 | UI | 签名与引流信息菜单位于模板管理之前 | Batch 6 | 已完成 |
### 执行约束
- 每个 Batch 先只读复现并记录页面、API、DB、Gateway 分类,再做最小真实修复。
- 纯 UI 项也必须确认页面数据源不是 mock、localStorage 或静态数组;未实现后端的彩信仅保留待开发占位。
- 每批结束执行相关 API 测试、API build、前端 build;涉及 Gateway 时追加 Go 测试和生产 Gateway health/CMPP 验证。
- 完成后更新本文件;未经明确要求不提交或推送代码。
## 2026-07-10 Batch 1 企业充值流程瑕疵
### 本轮修复
- 企业新建/编辑页的图片“预览”改为站内弹窗展示,不再跳转或新开页面;下载仍走真实对象存储文件接口。
- 通用 `Input``Select``Textarea` 根据 `required` 属性显示必填标识;企业资料和人工充值弹窗不再依赖页面散落的文案约定。
- 运营端充值记录列表的“充值后余额”改为真实订单关联 `AccountTransaction.balanceAfter`;不再用当前 `TenantAccount` 余额冒充历史快照。没有可追溯流水的历史记录显示 `-`
- 人工充值弹窗补齐非零校验、提交中禁用和 API 失败提示;提交仍调用 `POST /api/admin/billing/manual-recharges`,成功后刷新真实记录。
- 企业名称与统一社会信用代码已经使用同一双列栅格,本轮复现未见对齐问题,不做无效样式改动。
### 验证口径
- `GET /api/admin/billing/manual-recharges` 必须基于 Prisma/PostgreSQL 的 `RechargeOrder` 和关联 `AccountTransaction` 返回余额快照。
- `TC-BILLING-006` 增加断言:充值记录“充值后余额”等于关联账务流水的 `balanceAfter`,与后续充值或消费后的当前余额无关。
### 已执行命令与结果
```bash
npm --prefix api test
npm --prefix api run build
npm run build
git diff --check
```
- API 全量单测通过:12 个 test suites、113 个测试通过;新增 BillingService 覆盖两笔人工充值分别返回其历史余额。
- API build 和前端 build 通过;前端仍有既有 Vite chunk size warning。
- `git diff --check` 无空白错误,仅 Windows 工作区 LF/CRLF 提示。
- 已按生产标准脚本部署到 `8.160.169.106`Prisma migration deploy 无待执行迁移,`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 activeAPI/Gateway health、Redis 均通过。
- 生产管理员真实登录后只读调用 `GET /api/admin/billing/manual-recharges` 成功返回 2 条记录,响应包含真实 `balanceAfterCents`10000、1000)。
## 2026-07-10 Batch 2 通道配置真实链路
### 本轮修复
- 运营端通道编辑/新建页的“通道流速”不再固定提交 `100`;输入值按 `1-2000 TPS` 校验后写入 `SmsChannel.rateLimitPerSecond`,发送链路和通道测试继续从该真实字段生成 Gateway `SubmitCommand.route.rateLimitPerSecond`
- “扩展位数”仅允许 `0/2/4/6`,持久化到 `SmsChannel.config.extensionDigits`;编辑页回填该值,普通发送和通道测试均将其放入 Gateway `SubmitCommand.cmpp.extensionDigits`
- NestJS 更新通道时修正 `config` 合并行为:传入的配置会与既有 JSON 配置合并,不会再被 `desiredConnections/windowSize` 规范化过程静默丢弃。
- 网关密码保持安全策略:编辑时不回显已配置密码,留空不覆盖;输入新密码才更新真实通道配置。
### 已执行命令与结果
```bash
npm --prefix api test -- channels.service.spec.ts --runInBand
npm --prefix api run build
npm run build
go test ./internal/queue ./internal/upstream
git diff --check
```
- ChannelsService 和 SendChainService 定向测试通过:2 个 test suites、55 个测试通过;ChannelsService 单独测试 23 项,覆盖流速、扩展位数持久化和非法配置拒绝。
- API build、前端 build、Gateway queue/upstream 测试通过;前端仍有既有 Vite chunk size warning。
- 已重新部署生产验证环境;Prisma migration deploy 无待执行迁移,`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 activeAPI/Gateway health 正常。生产运行源码已确认包含流速校验、扩展位数持久化及 Gateway 队列字段。
- 通道组名称为空时已有前端提示“请输入通道组名称”,保存会在调用真实创建/更新 API 前中断;本轮复核后不重复改动。
- 通道编辑密码保持掩码且不回显:编辑时明确提示“留空保持不变,填写新密码才更新”;新建通道仍要求填写密码。
- 上述密码交互调整已于 2026-07-10 生产验证部署后再次核验:`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 active,内外部 health/HTTP 检查通过。
- 短信记录列表修复:企业、应用、手机号、状态之外的提交日期、短信内容、通道名称筛选改为传给 `GET /api/admin/operations/messages`NestJS 通过 Prisma/PostgreSQL 执行内容、关联通道名和上海自然日范围查询,页面不再仅筛选已加载的前 500 条记录。
- 生产只读复现确认:短信记录 9 条均有真实 `SmsSubmitRecord`,其中 4 条已有真实 `SmsReceiptRecord`3 个通道均有 `CmppConnectionState``OperationLog` 连接日志。按一条生产记录的日期、内容、通道关键词组合查询,9 条中仅返回 1 条且条件均匹配。
- `OperationsService` 定向测试 12 项、API build、前端 build 均通过;已部署生产验证,`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 activeAPI/Gateway health 正常。
- 发送详情弹窗重组为真实状态摘要、短信内容、通道提交/回执轨迹、状态信息和分片补偿审计;提交轨迹新增真实 `submitStatus`,不再只展示时间和回执码。
- 连接日志弹窗新增 `CmppConnectionState` 摘要(连接 ID、状态、当前/期望连接数、最近心跳、最近错误),日志内容以真实 `OperationLog.detail` 可读格式呈现,并仅对已返回日志做关键词筛选。
- 通道测试成功后展示 API 返回的真实 `testNo`、提交数量、手机号与 `SmsSubmitRecord.submitId`,禁用重复提交,并提供跳转至短信记录入口;没有虚构“发送成功”或模拟回执。
## 2026-07-10 Batch 3 通道报备与通道组配置
- 通道报备配置页新增返回通道列表入口,沿用现有 `/admin/channels/:channelId/reports` 路由的来源页面,避免运营人员进入配置页后没有回退路径。
- 通道组“添加通道”弹窗不再使用固定省份数组:省份和候选通道均来自 `GET /api/admin/channels`,按真实运营商、地区和已绑定通道过滤;选中后展示通道代码、地区和真实连接状态。
- 弹窗在省份、优先级或通道未选择时提供表单错误提示;没有符合条件的候选时显示可读空态。前端仅做交互约束,最终仍由 NestJS `ChannelsService` 校验运营商/地区兼容性、重复通道和优先级规则,并持久化到 `SmsChannelGroupItem`
- 已执行 `npm --prefix api test -- channels.service.spec.ts --runInBand`23 项通过)、`npm run build``git diff --check`;前端保留既有 Vite chunk size warning。
- 已部署生产验证:Prisma migration deploy 无待执行迁移,`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 activeAPI/Gateway health 正常;生产只读接口返回 3 个真实通道(其中 2 个启用)、1 个真实通道组和 2 个组成员。
## 2026-07-10 Batch 4 用户管理与会话失效
- `User.sessionVersion` 真实持久化到 PostgreSQL;登录 token 携带该版本。浏览器携带 token 请求时,NestJS 会话中间件校验用户状态、删除状态和版本;禁用、删除、管理员改密和个人改密都会递增版本,使原会话在下一次请求被 401 拒绝,前端清理本地会话并跳回对应登录页。
- 为避免破坏 Gateway 与现有服务间无浏览器会话链路,中间件仅校验带 `Authorization` 的浏览器 token;未携带该 header 的既有内部请求保持原行为。
- 右上角“修改密码”补齐真实 `POST /api/auth/password`:要求当前密码、新密码(至少 6 位)和确认密码一致;成功后当前会话立即失效并回到登录页,写入操作日志。
- 运营端和客户端用户新建补齐姓名、至少一个联系方式、初始密码/企业关联等前端校验,提交中禁用按钮并展示 API 错误;用户禁用操作改用通用 warning 语义色。
- 已执行 `npm --prefix api run prisma:generate``npm --prefix api test -- auth.service.spec.ts session-validation.middleware.spec.ts users.service.spec.ts --runInBand`3 suites、8 项通过)、`npm --prefix api run build``npm run build``git diff --check`;前端保留既有 Vite chunk size warning。
- 已部署生产验证:第 25 条 Prisma migration `20260710153000_add_user_session_version` 成功应用;`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 activeAPI/Gateway health 正常。生产管理员新登录 token 为版本格式且可读取真实用户列表;伪造旧版本 token 被 `401` 拒绝,验证会话版本失效生效。
## 2026-07-10 Batch 5 审核与企业配置
- 修复 Dashboard 待审核聚合:短信审核的真实状态存储在 `SmsSendTask.status=pending_review`,原逻辑错误统计 `SmsBatchTask.auditStatus=pending`。聚合现统一模板、签名、企业认证和风险审核的真实待审状态。
- 生产 PostgreSQL 与对应 API 在部署后均显示四类待审为 0,Dashboard 也为 0,当前数据口径一致;无非零待审样本,未将该 0 值当作非零场景的充分验收。
- 任务进度、企业应用、企业模板、企业签名与引流字段页面均使用真实 NestJS API;生产只读接口成功返回任务、应用、模板、签名和引流字段数据,不存在 mock/localStorage 回退。
- 已根据下载的瑕疵文档修复 5.2-5.6:任务进度不重复累计未知/超时,企业应用启停语义色与搜索区,模板中文审核状态,签名三网状态驱动边框且移除人工状态选择,引流信息标题、链接展示、操作列和人工状态选择。
- 已执行 `npm --prefix api test -- operations.service.spec.ts --runInBand`(12 项通过)、前端 build 和 `git diff --check`;已部署生产验证,`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 activeAPI/Gateway health 正常。
## 2026-07-10 瑕疵回归复查
- 逐图复查下载的《短信平台第一版瑕疵》后,确认中文图片文件名乱码仍真实存在:生产 `FileObject.fileName` 中可见 UTF-8 被按 Latin-1 解释后的值。上传链路现先恢复 multipart 文件名编码;历史记录由前端展示层兼容解码,避免签名材料和企业认证页继续显示乱码。
- 通用输入框、文本框去除内部填充色;同时覆盖 Chromium 自动填充产生的蓝色内层背景。企业认证页此前额外写死的灰色输入背景已移除。
- 2026-07-11 回归发现此前金额展示验收不充分:充值记录和多处金额页面仍混用整数、两位或四位小数。现统一金额展示为人民币元三位小数,并新增输入框聚焦底色与文本选中高亮;需求和 `TC-BILLING-006` 已同步。
- 已执行 FilesService 定向单测(3 项通过)、API build、前端 build 和 `git diff --check`;生产部署后四个服务均为 activeAPI/Gateway health 正常。通过真实 `POST /api/admin/files/upload` 上传 `营业执照-编码回归.png`,响应和 `FileObject` 持久化文件名均为正常中文。
- Batch 6 的 6.1、6.2、7.1、8.1 仍为待修,不得因之前的前端构建通过而标记完成;其余 8.x 与客户端菜单顺序将继续按原始文档逐项复核。
## 2026-07-11 文档瑕疵二次闭环
- 基于本地《短信平台第一版瑕疵.docx》重新逐项复查,撤销“代码已改即已验收”的旧口径;本轮必须以源码、真实 NestJS API 返回、测试和生产页面复核共同作为完成条件。
- 通道扩展位数改为真实 API 与页面共同约束 `0-20` 的整数;流速仍由 NestJS 约束为 `1-2000 TPS` 并继续下发 Gateway。
- Dashboard API 新增四类真实待审明细:企业认证、短信审核、模板、签名;运营首页和右上角通知分别展示并跳转到各自真实审核入口。
- 手机号段接口从 cursor-only 响应升级为带 `total/page/pageSize` 的 PostgreSQL 分页,页面获得总页数、首页、末页和跳转能力;短信记录与通用 Table 同步补齐完整分页控制。
- 统一修复充值记录不展示操作人、客户端标题、引流详情名称、用户初始密码显示/隐藏与随机生成、时间秒级格式、输入框无填充焦点状态,以及发送页真实应用单价三位小数显示。
- 已执行:`channels.service.spec.ts`24 项通过)、`dictionaries.service.spec.ts`4 项通过)、`operations.service.spec.ts`12 项通过)、API build、前端 build 与 `git diff --check` 均通过;前端仍仅有既有 chunk size warning。
- 已部署生产验证:`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 activeAPI/Gateway health 均通过。真实认证 API 返回四类待审明细并与总数一致;手机号段第 2 页返回 25 条、总数 516217、`page=2/pageSize=25`,证明页面分页不再依赖 cursor 猜测总页数。
- 浏览器自动化在登录页连接阶段超时,未使用 CAPTCHA 绕过或修改生产数据;登录后页面视觉验收需在下一轮以人工登录或可用浏览器会话补充截图。其余项目以源码、真实 API 和构建结果验收,不能将该未完成的视觉截图记录成已完成。
## 2026-07-11 运营端查询控件与手机号段库重做
- 企业应用、企业签名、企业模板管理页的查询与重置统一为通用 `Button` 操作组:查询提交当前条件到真实 NestJS 列表 API,重置清空条件后重新加载真实列表,不再依赖输入即筛选或状态更新竞态。
- 系统管理的用户管理、手机号段库、报备字段库、系统日志均增加查询和重置;用户与报备字段使用已加载真实数据的显式筛选,系统日志使用已提交筛选条件请求真实日志 API。
- 手机号段库重做为概览、关键词筛选、号段/运营商规则双视图和统一分页工作台。号段和规则均使用 PostgreSQL 返回的 `total/page/pageSize`,不使用静态数组或 cursor 猜测总页数。
- 已执行前端 `npm run build``git diff --check`;已部署生产验证,`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 activeAPI/Gateway health 通过。生产源码和已构建静态资源均包含新的查询操作组与手机号段工作台样式;手机号段 API 继续返回真实 `total/page/pageSize`
## 2026-07-11 CMPP 业务失败回执闭环
- 修复下游 CMPP 入站的审计缺口:客户已完成 bind、账号可识别且手机号参数合法后,NestJS 会先创建真实 `SmsBatchTask``SmsApiRequest``SmsMessageRecord`,再执行模板、签名/报备、风控和余额校验;不再因模板未报备等业务失败而直接丢弃客户 Submit。
- 协议、鉴权、源 IP 和手机号参数错误仍由 Gateway/NestJS 返回非零 SubmitResp,且不创建短信记录。其余业务失败返回成功 SubmitResp 与平台 Msg_Id,并创建真实 `SmsReceiptRecord(rawStatus=REJECTD)``CmppDownstreamDelivery`,客户通过 Deliver Receipt 获得 `undelivered` 结果。
- 同一回执策略覆盖最终通道签名报备失败、无可用路由,以及上游 Submit rejected/timeout 在补发耗尽后的终态失败;失败记录、错误码和错误原因均可在运营端真实短信记录链路查询。
- Gateway 在客户 Submit 成功并建立 messageId-连接映射后立即冲刷该账号 pending 下游投递,避免 API 先创建失败回执时只能等待周期补投。
- 已执行 `npm --prefix api test -- --runInBand send-chain.service.spec.ts`34 项通过)、`npm --prefix api run build``go test ./...`(Gateway 全量通过)。待本轮全量 API/前端构建及生产验证完成后补充最终部署结果。
## 2026-07-11 企业应用下游 CMPP 连接状态修复
- 修复企业应用列表误用上游 `CmppConnectionState` 的问题。新增 PostgreSQL `CmppDownstreamConnection`,一条记录对应一个已鉴权的客户 CMPP TCP bind 会话,按应用保存账号、企业代码、客户端 IP、协议版本、建立时间、最近心跳、最近 Submit、最近 Deliver、断开时间和错误原因。
- Gateway 在客户 bind 成功、`ACTIVE_TEST`、Submit 和下游 Deliver 写入成功/失败时通过真实 NestJS API 回写连接事件;运营端只以该表的 `connected` 会话统计当前连接数,不再把上游通道连接数显示为企业客户连接数。
- API 列表/详情查询会将最近心跳超过 `CMPP_DOWNSTREAM_HEARTBEAT_TIMEOUT_MS`(默认 90 秒)的会话标记为 `heartbeat_timeout`;运营端展示真实客户端 IP、企业代码与最近心跳。移除了不能真正关闭 TCP 连接的运营端“删除连接”伪操作。
- 已执行 `sms-config.service.spec.ts`(17 项通过)、API 全量测试(13 suites、121 项)、Gateway 全量 `go test ./...`、API build 和前端 build;前端仅有既有 chunk size warning。
- 已部署生产验证:第 26 条 Prisma migration `20260711193000_add_cmpp_downstream_connections` 已成功应用,`CmppDownstreamConnection` 表存在。`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 active`12026/17890/8090` 监听及 API/Gateway health 均通过。部署时没有保持在线的客户 bind 会话,故新表初始为 0 条;下一次真实 CMPP bind 将作为生产数据验收样本写入该表。
## 2026-07-11 短信号码运营商与省份持久化
- 确认发送链路在进入应用通道组路由后,会先按真实 `PhoneCarrierRule` 正则识别号码运营商,再按 `PhoneSegment` 号段库识别省份。本轮不扩展地市字段。
- `SmsMessageRecord` 新增 `carrier/province`,路由阶段完成号码识别后立即持久化;即使后续缺少通道组或无在线通道而失败,短信记录仍保留识别结果。选中通道时与 `channelId/submitId` 再次同步写入;Prisma migration 使用真实号段库和生效运营商规则回填已有短信记录。
- 运营端短信记录列表、发送详情和 CSV 导出展示记录上的号码省份/运营商,不再以通道发送地区或通道本体 carrier 冒充号码归属。
- 短信任务进度详情中的“号码运营商分布”和“号码省份分布”均直接聚合任务内真实短信记录的 `carrier/province`
- 已执行 `npm --prefix api test -- --runInBand send-chain.service.spec.ts`35 项通过)、`npm --prefix api run build``npm run build``git diff --check`;前端仅有既有 chunk size warning。
- 已将 `7b8424d9` 部署生产,migration `20260711210000_add_message_route_identity` 成功应用。`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 active`12026/17890/8090/3000` 监听、API/Gateway health 及外部 `12026` HTTP 均通过。生产 11 条已有短信已全部回填运营商和省份:移动 9 条、电信 1 条、联通 1 条,省份均为上海。
## 2026-07-12 全页面固定条数截断与总数口径整改
- 生产核查确认企业管理只返回前 100 条,但 PostgreSQL 实际有 106 个企业(未删除 105 个);企业应用只返回前 100 条,实际有 204 条。页面将截断后的数组长度展示为“企业总数/共找到”,属于真实数据口径 Bug。
- 已系统审计页面主列表 API,移除会把前 `100/200/500` 条直接作为页面全量的固定截断。覆盖企业、企业应用、企业认证、用户/角色/权限、签名、模板、通道/通道组/路由规则、报备字段/材料/任务/记录、账务、文件、审计、风控、敏感词/黑名单/引流字段、短信任务/记录/提交/回执/上行等真实列表。
- 企业应用的“今日发送/到达率”不再每应用加载最多 1000 条 `SmsMessageRecord` 后用数组长度计算,改为 PostgreSQL 按 `applicationId/status``groupBy` 全量聚合。
- 短信任务进度不再为每个任务最多加载 100000 条短信后统计,改为 PostgreSQL 按 `batchTaskId/carrier/province/status` 聚合总数、成功数和计费条数,任务运营商/省份分布不再受 10 万条截断影响。
- 保留的固定条数均属于明确的近期日志/监控窗口、超时扫描单批、导出保护、单消息重试尝试或唯一候选判定,这些响应不被页面展示为业务总数。
- 已执行 API 全量测试(13 suites、122 项通过),及受影响服务定向测试(5 suites、91 项通过)、API build、前端 build 与 `git diff --check`;前端仅有既有 chunk size warning。
- 已将 `390b9700` 部署生产,部署前完成 PostgreSQL 和当前发布源码备份,Prisma 确认 27 条 migration 均已应用。`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 active`12026/17890/8090/3000` 监听、API/Gateway health 和外部 `12026` HTTP 均通过。使用生产平台管理员会话调用真实受保护 API:企业管理返回 105 条未删除企业,企业应用返回 204 条,不再封顶 100。
## 2026-07-12 客户批量任务与 CMPP 内部批次隔离
- 短信任务进度的产品口径回归为“客户在客户端提交的批量发送任务”。运营端和客户端任务列表统一强制 `SmsBatchTask.sourceType=client`,不再展示每条 CMPP Submit 创建的 `sourceType=cmpp` 内部批次或通道测试批次。
- CMPP 内部批次仍保留在 PostgreSQL,继续承载模板/签名/报备校验、风控、冻结/计费、队列、补发和 Deliver Receipt 关联;所有 CMPP 号码仍进入短信记录。
- 客户端任务详情、任务短信明细和取消接口同时校验当前 `tenantId``sourceType=client`,不能通过内部批次 ID 读取或操作 CMPP 内部任务。
- 生产现状只读核对:`sourceType=cmpp` 2 个、`sourceType=admin_channel_test` 1 个、`sourceType=client` 0 个。部署后任务进度应显示 0 个客户批量任务,但不删除现有内部批次数据。
- 已执行 `send-chain.service.spec.ts + operations.service.spec.ts`2 suites、50 项通过)、API 全量测试(13 suites、125 项通过)、API build 和前端 build;前端仅有既有 chunk size warning。已随 `8b6ec92f` 部署生产,受保护任务进度 API 返回 0 个客户批量任务,未将现有 2 个 CMPP 内部批次和 1 个通道测试批次误展示。
## 2026-07-12 CMPP 模板不匹配短窗口聚合审核
- 只有企业应用配置 `templateMismatchMode=manual_review` 时,CMPP 模板不匹配短信才进入人工审核。`reject` 继续逐条失败并下发 `REJECTD`;其他模式不被聚合逻辑接管。
- 新增 `SmsSendTask.sourceType/aggregationKey/contentHash/windowStartedAt/windowEndsAt`,以应用、CMPP 账号、规范化内容 SHA-256 和默认 10 秒窗口生成唯一聚合键。`SmsMessageRecord.reviewTaskId/signatureId` 保留每条成员与审核任务、真实签名的关联。
- 聚合前仍校验签名审核/报备、风控直接拒绝和账户余额,并按每条短信独立冻结。审核通过后逐条恢复到各自 `sourceType=cmpp` 内部批次并入队;驳回后逐条释放冻结、写入失败记录并生成客户侧 Deliver Receipt。
- 运营端短信审核页新增“审核来源”和“聚合号码数”,区分 CMPP 模板不匹配聚合与普通风控审核。
- 聚合窗口关闭前,任务不返回到待审列表且审核接口拒绝提前操作,避免窗口内后到短信加入已完成任务。
- Prisma migration`20260712113000_add_cmpp_review_aggregation`。已执行 API 全量测试(13 suites、129 项通过)、API build、前端 build、Prisma validate 和 `git diff --check`
- 已将 `8b6ec92f` 部署生产,部署前完成 PostgreSQL 和发布源码备份,migration 已成功应用。`SmsSendTask` 5 个聚合字段、`SmsMessageRecord.reviewTaskId/signatureId` 均已存在;生产应用中 `manual_review` 3 个、`reject` 201 个。审核 API 返回 200,当前无待审样本,未为验收人工注入短信或修改业务数据。`cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 active`12026/17890/8090/3000` 监听、API/Gateway health 和外部 `12026` HTTP 均通过。
## 2026-07-12 报备字段库到企业签名/引流资料完整链路
- `ChannelReportField` 通过 `drainageFieldId` 真实关联报备字段库,并增加 `reportType=signature/drainage/both`;通道报备配置页改为选择字段库字段和报备用途,不再在通道内手工复制字段编码、名称和类型。
- 新增企业应用报备字段解析 API,按生效的应用路由规则遍历通道组及组内通道,以字段库 ID 求合集;同字段任一通道必填即整体必填,并返回全部来源通道。
- 企业签名与引流信息弹窗根据所选企业应用动态加载字段合集。签名只展示签名/共用字段,引流项只展示引流/共用字段;文件字段继续走真实 MinIO/对象存储上传,文本值和文件对象 ID 均提交 NestJS API。
- API 在写签名前执行必填校验,防止绕过前端;保存后同步写入各目标通道的 `SignatureReportMaterial` 和新增的 `DrainageReportMaterial`,删除引流项时同步清理旧材料。
- 为避免运营人员面对动态资料时无法理解来源,签名和引流编辑页增加“通道组数/通道数/字段数/必填数”摘要、字段级来源说明和“为什么需要这些资料”解释弹窗;弹窗按企业应用、通道组、通道逐级展示字段用途及必填口径。每次保存同时在签名 JSON 中固化 `reportRequirementSnapshot`,记录当时的字段与来源通道,供配置变化后的历史追溯。
- Prisma migration`20260712150000_link_report_field_library`。已执行 Prisma generate/validate、`channels.service.spec.ts + sms-config.service.spec.ts`2 suites、44 项通过)、API build 和前端 build;前端仅有既有 chunk size warning。
- 已提交并 push `87ae4a20`,随后以该提交生成发布快照并部署生产;部署前备份 PostgreSQL 和运行源码,migration `20260712150000_link_report_field_library` 已成功应用。生产 `.deployed-commit=87ae4a20``cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 active`12026/17890/8090/3000` 监听,API/Gateway health 和外部 HTTP 均通过。
- 生产数据库已确认 `ChannelReportField.drainageFieldId/reportType``DrainageReportMaterial` 存在。当前生产 `DrainageField=0``ChannelReportField=0`,因此不会凭空展示动态资料区;需要先按真实业务配置创建字段库和通道字段后再做页面来源弹窗的有数据验收。
- 浏览器可打开生产登录路由并识别页面标题“CMPP 短信平台”,但读取 DOM/控制台时浏览器连接连续超时,未将解释弹窗点击交互标记为已通过;待生产产生真实字段配置后补测。
- 2026-07-12 追加:报备字段库字段类型收窄为字符串、图片、文件三种;前端筛选和新增弹窗移除整数、网址、电话、日期,API 严格拒绝三种之外的类型。migration `20260712170000_normalize_report_field_types` 将历史其他类型及其通道字段副本统一归并为字符串。
- 2026-07-12 追加:按设计基线 `131f344a^` 恢复“通道列表 → 报备详情”页面结构,不再把报备详情错误简化为字段配置表。页面按当前通道查询真实 `ChannelSignatureReportTask/ChannelSignatureReportRecord/SmsSignature.drainageInfo`,展示签名任务、报备状态和时间,签名下引流信息默认收起并可展开;查看详情使用真实企业、应用和动态资料。发送统计没有数据库事实时明确显示“暂无统计”,不复用基线演示百分比。签名报备字段和引流信息字段配置保留为页面顶部两个入口,均从真实报备字段库选择。
- 本地验证:通道/字典定向测试 2 suites、31 项通过,API 全量 13 suites、134 项通过,API build、前端 build、`git diff --check` 通过。浏览器确认本地构建可加载且无框架错误覆盖,但本地未启动真实 API,认证验证码请求返回 502 并停留登录页,因此未把目标报备页面的登录后视觉交互标记为通过;没有绕过认证或注入 mock 数据。
- 2026-07-12 追加:报备状态改为通道任务唯一事实来源。新增统一批量状态变更 API,企业签名按应用当前通道组展示具体通道矩阵,通道详情修改当前任务,报备任务页人工修正任务;三个入口统一更新/创建 `ChannelSignatureReportTask`、写 `ChannelSignatureReportRecord`,并重算三网汇总和 `SmsSignature.reportStatus`。回执导入不再直接覆盖全局状态,同样调用汇总算法;新增但无任务的目标通道按未报备计入汇总分母。
- 已执行相关定向测试 2 suites、46 项及 API 全量测试 13 suites、135 项,API build、前端 build、`git diff --check` 均通过;前端仅有既有 Vite chunk size warning。
- 已提交并 push `eab05958` 后部署生产,部署前完成 PostgreSQL 与运行源码备份;migration `20260712170000_normalize_report_field_types` 成功应用。生产 `.deployed-commit=eab05958``cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 active`12026/17890/8090/3000` 监听,API/Gateway health 和外部 HTTP 200。新统一状态接口对空 items 返回预期 400,证明路由已注册。生产当前 `DrainageField/ChannelReportField/ChannelSignatureReportTask/ChannelSignatureReportRecord` 均为 0,未为验收注入虚假字段、任务或人工状态;运营人员可从企业签名通道矩阵对真实目标通道首次设置状态并创建任务。
## 2026-07-13 部分通道报备通过的发送路由
- 修复发送链路先以 `SmsSignature.reportStatus=approved` 一票否决、导致部分通道通过仍无法发送的问题。全局状态改为运营汇总展示;签名审核仍必须通过。
- 首次发送和失败补发均在路由阶段解析真实签名,只保留对应 `ChannelSignatureReportTask.status=approved` 的候选通道,再结合运营商、省份、优先级、通道状态和实时连接选择通道。主通道未报备而备用通道已报备时允许走备用通道;没有已报备通过且在线的候选通道时明确失败。
- Gateway 提交前继续保留最终通道报备任务二次校验,覆盖路由完成后任务状态发生变化的竞态。
- 已执行 `send-chain.service.spec.ts` 40 项通过,覆盖全局 reporting 可发、主通道未通过而备用通道通过时选备用,以及所有候选均未通过时拒绝;API build 和前端 build 通过。
- 已提交并 push `ade06058` 后部署生产;部署前完成 PostgreSQL 与 `eab05958` 运行源码备份,无待执行 migration。生产 `.deployed-commit=ade06058``cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 active`12026/17890/8090/3000` 监听,API/Gateway health 与外部 HTTP 200。生产源码已确认包含 approved 通道候选过滤;【安徽航天信息】当前仍为全局 reporting、一个通道 approved、一个通道 pending,未主动发送计费短信,交由用户使用真实业务流量验证只走 approved 通道。
## 2026-07-13 企业签名审核与通道报备状态展示分离
- 生产核查【安徽航天信息】确认状态保存成功:两个 `carrier=all` 通道分别为 approved、reporting,三网真实汇总均为 reporting 且 1/2 通过;Bug 在于前端将 reporting 转换成旧 pending,再显示成“审核中”,并丢失通过数/总数。
- 企业签名列表和报备详情改为直接使用 API `carrierReportSummary.status/approved/total`:展示未报备、报备中、部分通过(x/y)、全部通过(x/y)、资料待补充、报备失败和不适用;签名 `auditStatus` 另列显示草稿、待审核、已通过、已驳回。详情同时列出每个目标通道的真实状态,不再读取 `drainageInfo.carrierStatus` 冒充当前汇总。
- 按用户授权,本次将工作区其他会话的字段代码校验、Select 搜索、应用列表及样式等改动一并测试和发布;API 全量 13 suites、137 项通过,API build、前端 build、`git diff --check` 通过。
- 已将完整工作区提交 `344b2afe` 部署生产,部署前完成 PostgreSQL 与 `ade06058` 运行源码备份,无待执行 migration。生产 `.deployed-commit=344b2afe`,四项服务 active`12026/17890/8090/3000` 监听,API/Gateway health 与外部 HTTP 200;发布产物已确认包含“部分通过(x/y)”“全部通过(x/y)”“签名审核”。生产【安徽航天信息】仍为 auditStatus=draft、reportStatus=reporting、1/2 通道 approved,刷新后应展示签名审核“草稿”和三网“部分通过(1/2)”。
## 2026-07-13 企业页面可用性与报备展示修正
- 企业短信模板卡片改为自适应列宽,卡片和操作区允许换行,避免中等视口下固定三列造成水平滚动。
- 企业应用管理列表隐藏 AppID 列;共用 `Select` 对标签中包含“企业”或“应用”的下拉自动提供名称搜索,选项仍来自各页已接入的真实 API。
- 报备字段代码在前端和 NestJS API 双层限制为 `A-Z/a-z/0-9`;API 会拒绝下划线、中文、空白等非法代码,防止绕过页面写入 PostgreSQL。
- 运营端浏览器标题更新为“聆界短信管理平台”。通道报备详情和签名详情弹窗在展示前剔除数据中已存在的外层中/英文括号,统一只渲染一层中括号。
- 本地验证:API 全量 13 suites、137 项通过,API build、前端 build 和 `git diff --check` 通过;前端仅有既有 chunk size warning。应用内浏览器确认本地运营端登录路由、非空 DOM、无框架错误覆盖,且标题为“聆界短信管理平台”;因本地未启动真实 API,验证码请求返回 502,无法进入登录后页面完成模板响应式、下拉搜索和签名实数据的视觉点击验收,未注入 mock 或绕过认证。截图阶段应用内浏览器页面挂载超时,未将截图标记为通过。
## 2026-07-13 运营端企业模板与企业签名布局优化
- 确认上一次自适应修正落在客户端 `/client/templates`,而运营端 `/admin/enterprise-templates` 仍使用累计最小宽度约 1820px 的表格,操作列必须水平滚动才能看到。
- 企业模板管理改为响应式列表行:将模板名称/审核状态、企业/应用/签名归属、两行内容摘要/变量数、更新时间和操作分区展示。在 1180px 以下重排为三列,860px 以下变为单列,预览/编辑/删除始终保留在当前视口。分页仍基于真实 API 结果,每页 10 条。
- 企业签名管理将三网报备标签与 `approved/total` 数量拆开,容器可换行但文字和数量各自保持完整;报备详情/报备状态/编辑/删除固定为两列两行。同时修正签名摘要网格少一列导致“引流信息”和操作区被挤到下一行的布局问题。
- 本地验证:API 全量 13 suites、137 项通过,API build、前端 build、`git diff --check` 通过;前端仅有既有 chunk size warning。Chrome 当前无已登录生产标签,部署前未绕过图形验证码或注入 mock 数据。
- 已将功能提交 `19d47b45` push 到 `origin/main` 并部署生产。部署前备份 PostgreSQL 为 `/opt/cmpp-platform/backups/cmpp-20260713-103205.sql`(约 61MB),备份运行源码为 `/opt/cmpp-platform/backups/source-20260713-103205.tar.gz`(约 63MB);发布快照本地/服务器 SHA-256 一致。生产无待执行 migration,`.deployed-commit=19d47b45``cmpp-api``cmpp-gateway`、Nginx、MinIO 均为 active`12026/17890/8090/3000` 监听,API/Gateway health 和外部 HTTP 200。生产 `index.html` 已引用新资源 `index-HtuLnrXL.js/index-C5Tf8YiT.css`,源码已确认包含企业模板响应式行和签名操作区两列布局。
- 生产浏览器确认路由可加载、标题为“聆界短信管理平台”、DOM 非空、无 console error/warn 和框架错误覆盖。由于浏览器没有已登录运营端会话,访问受保护页面按预期落到登录界面;未经用户确认不代解图形验证码,因此登录后真实数据截图和点击交互留待用户刷新生产页面验收。
## 2026-07-13 签名审核与引流信息通道报备闭环
- 生产只读核查确认:运营端新建的【安徽航天信息】仍为 `auditStatus=draft`;引流资料保存在 `SmsSignature.drainageInfo.links`,动态字段可写 `DrainageReportMaterial`,但缺少引流项维度的真实通道报备任务和记录。
-`/admin/signatures` 从占位页替换为真实签名审核页,支持状态查询、资质/动态资料详情、通过和带原因驳回,调用现有 NestJS 审核 API 并写 `AuditRecord`。运营端新建签名直接写 `auditStatus=approved`,同时写 `admin_create_approved` 审核记录;客户端提交审核流不变。
- `ChannelSignatureReportTask` 增加 `reportType=signature/drainage``drainageItemId`。保存引流资料时,根据企业应用的真实路由通道及其 `drainage/both` 字段自动生成 pending 任务和 create 记录;删除引流项时同步清理对应材料和任务。
- 企业签名引流列表、通道报备详情、报备任务页均按引流项和通道读取/修改同一任务,共享报备记录、导出和回执链路。企业签名三网状态改为引流任务真实汇总,不再使用 `drainageInfo.links.mobile/unicom/telecom` 静态值。
- 短信发送选路和最终通道二次校验明确增加 `reportType=signature`,引流通过任务不会误放行未报备签名。Prisma migrations`20260713153000_add_drainage_report_tasks``20260713154000_backfill_drainage_report_tasks``20260713155000_unique_report_task_scope`;后两者分别回填已有引流材料的 pending 任务/记录,并保证同一报备对象和通道仅一个任务。
- 本地真实 PostgreSQL 已成功应用 3 条新 migrationAPI 全量 13 suites、139 项通过,Gateway 全量 Go 测试、Prisma validate、API build、前端 build 和 `git diff --check` 通过,前端仅有既有 chunk size warning。应用内浏览器确认本地运营端登录路由标题正确、DOM 非空、无框架错误覆盖且 console 无 error/warn;真实图形验证码阻止进入受保护页,未代解验证码、未绕过认证、未注入 mock。
- 功能提交 `551b99cb` 已 push 并部署生产。部署前 PostgreSQL 备份为 `/opt/cmpp-platform/backups/cmpp-20260713-174148.sql`,运行源码备份为 `/opt/cmpp-platform/deploy-backups/full-19d47b45-20260713-174148`;三条 migration 成功应用。生产 `.deployed-commit=551b99cb`,四项服务 active`12026/17890/8090/3000` 监听,API/Gateway health 和外部 HTTP 200;前端产物已包含“短信签名审核”“按通道修改引流信息报备状态”“签名与引流信息报备任务”。生产现有 4 条任务均已回填为 `reportType=signature``DrainageReportMaterial=0`,因此没有伪造引流任务,待真实引流字段资料保存时自动生成。
- 部署后交付复核发现历史运营端新建的 draft 签名虽可在审核页筛选,但操作按钮只对 pending 开放。已改为 draft/pending 都可由运营直接通过或驳回,用于处理【安徽航天信息】等存量草稿;新增签名仍按新规则自动通过。修正提交 `ff3e5607` 已部署,二次备份时间戳 `20260713-174512`;生产 `.deployed-commit=ff3e5607`,四项服务、端口、API/Gateway health 和外部 HTTP 200 再次验证通过,部署后 API stderr 无新错误。
## 2026-07-13 引流信息独立审核与报备任务门禁
- 生产只读核查确认当前 `DrainageReportMaterial=0``reportType=drainage` 任务为 0、包含引流数组的签名为 0,因此本轮可安全引入规范化模型,不需要改写活跃引流业务数据。生产运行代码仍为 `ff3e5607`,本轮暂未部署。
- 新增独立 PostgreSQL 实体 `SmsDrainageInfo`,客户端在已审核签名下新建或修改引流信息均进入 pending,并写 `AuditRecord(targetType=sms_drainage_info)`;运营端新增/修改自动通过。运营端审核中心新增“引流信息审核”页,支持真实 API 查询、详情、通过和带原因驳回,首页和侧栏待审数量同步纳入引流信息。
- 通道报备增加审核门禁:审核通过前不创建新任务或可导出材料;已通过引流信息再次修改时删除旧材料并将已有任务冻结为 waiting_review。审核通过后按应用当前生效路由及通道引流字段重建材料,创建或重置 `ChannelSignatureReportTask(reportType=drainage)`,并写 `audit_approved_create/reset` 报备记录。
- 企业端签名页面补充引流信息新建、修改、删除、动态字段和真实对象存储上传;运营端企业签名页改为调用独立引流 API,并显示引流审核状态。报备任务和报备记录页增加类型筛选,直接关联真实引流实体显示站点、地址和所属签名。
- 本地真实 PostgreSQL 已成功应用 migration `20260713190000_add_drainage_audit_workflow`Prisma validate/migrate status 通过。真实 NestJS API 验收使用现有已审核签名、应用路由和通道临时增加报备字段:客户端创建后为 pending 且任务数 0;运营审核通过后生成 1 条 drainage 任务和报备记录;客户端修改后任务冻结为 waiting_review;驳回原因可从 API 返回。验收数据、临时报备字段、材料、任务、审核及报备记录已在本地数据库清理,不污染业务样本。
- API 全量测试 13 suites、141 项通过,API build、前端 build、`git diff --check` 通过;前端仅有既有 Vite chunk size warning。应用内浏览器确认本地构建标题、非空登录 DOM、无框架错误覆盖且 console 无 error/warn;访问 `/admin/drainage-audits` 按真实权限跳转登录页,因图形验证码未获授权代解,登录后的新增审核页点击和视觉验收未执行。该批改动纳入 2026-07-14 发布批次统一提交和部署。
## 2026-07-14 运营列表组合搜索与报备记录可追溯性
- 企业模板管理将企业名称、企业应用、模板名称、模板内容拆成独立查询参数;企业签名管理拆分企业名称、企业应用、签名名称/用途和引流信息。引流信息查询命中后按签名父级分组,并只展开显示命中的站点、URL 或备注。
- 企业应用管理新增应用名称和状态条件;企业黑名单搜索区重做为企业名称、企业应用、手机号码、入库原因、状态五个独立条件。上述条件均由 NestJS 接收并通过 Prisma AND 组合查询真实 PostgreSQL,不再拼接为单个模糊关键字。
- `ChannelSignatureReportRecord` 新增 `sourceEntry`,企业签名、报备任务、通道报备详情三个入口分别持久化 `enterprise_signature/report_task/channel_report`。报备记录列表展示真实通道名称、签名或引流信息主体、完整主体内容、中文动作和状态变化,并将入口翻译为“企业签名修改 / 报备任务修改 / 通道信息修改”。
- 历史 `manual_status_change` 记录无法从旧数据可靠反推出入口,迁移统一标记为 `legacy`,页面显示“历史记录(入口未记录)”,不得误标为系统自动处理或猜测入口。
- 充值记录、短信记录表头统一左对齐;搜索区使用自适应网格,增加条件后不挤压操作按钮。
- 本地真实 PostgreSQL 已应用 `20260714100000_add_report_record_source_entry`,并通过编译后的 NestJS 服务对现有应用、签名、模板执行真实组合查询。API 全量测试 13 suites、141 项通过;Prisma validate、API build、前端 build、Gateway 测试和 `git diff --check` 通过。应用内浏览器确认真实鉴权跳转、页面标题、非空 DOM、无框架错误覆盖及 console 无 error/warn;因图形验证码未获授权代解,登录后页面点击验收未执行。
- 功能提交 `64216712` 与历史入口修正提交 `70991478` 均已 push,生产最终运行代码为 `709914787196072b66d293ee9290d7cd41b46990`。首次备份为 `/opt/cmpp-platform/backups/cmpp-20260714-095958.sql``source-20260714-095958.tar.gz`,最终修正部署前备份为 `/opt/cmpp-platform/backups/cmpp-20260714-100658.sql``source-20260714-100658.tar.gz`;发布包本地与服务器 SHA-256 一致。
- 生产 36 条 migration 全部应用,四项服务 active,`12026/17890/8090/3000` 监听,API/Gateway health 和外部 HTTP 200,部署后 journal 无新 error。真实报备记录 API 返回 16 条且通道名称缺失数为 0;7 条系统记录为 `system`9 条旧人工记录为 `legacy`。用生产现有数据调用独立组合查询,企业应用、模板、签名和引流信息分组均各返回 1 个精确匹配结果;企业黑名单当前为 0 条,未注入演示数据。
## 2026-07-14 CMPP 入站完整括号签名与部分通道放行修复
- 生产只读核查手机号 `18821203795` 的最近一次提交:Gateway 已接收入站,但 NestJS 在路由前以 `SIGNATURE / 短信内容未识别到已审核且已报备的签名` 拒绝,未生成通道提交记录。实际短信前缀和签名库名称均为完整的 `【航天信息信诺网】`;签名审核已通过、全局报备状态为 reporting,主通道任务 approved、备用通道任务 pending。
- 根因是入站签名解析正则取捕获组后剥离了中括号,却用无括号名称查询保存完整括号的签名库;同时查询错误要求全局 `SmsSignature.reportStatus=approved`,与“部分通道通过即可发送、路由只选通过通道”的既定规则冲突。
- 修复为从短信开头提取完整 `【签名】` 并原样查询,仅在入站候选阶段校验 `auditStatus=approved`。全局 `reportStatus` 不再作为入口门禁,具体通道的 `ChannelSignatureReportTask(reportType=signature).status=approved` 仍由路由和最终提交二次校验。
- 新增真实故障形态回归用例:完整括号签名、审核通过、全局 reporting 时可进入模板不匹配人工审核聚合,并验证查询不再携带全局报备条件、不产生签名失败回执。定向 API 测试 1 suite、41 项通过;API 全量 13 suites、142 项通过,API build、前端 build、Gateway 全量 Go 测试和 `git diff --check` 通过,前端仅有既有 Vite chunk size warning。
- 功能提交 `f08a73e1` 已 push 并部署生产。部署前 PostgreSQL 备份为 `/opt/cmpp-platform/backups/cmpp-20260714-112012.sql`(约 64MB),运行源码备份为 `/opt/cmpp-platform/backups/source-20260714-112012.tar.gz`(约 15MB);发布包本地与服务器 SHA-256 均为 `38b3fa6282a06648dabc048fbebca7a6579a6a7ef1f8eee514ecf14fb9d69790`
- 生产 36 条 migration 无待执行项,`.deployed-commit=f08a73e19129f5249a5a9e0035c7ab63b80422d4``cmpp-api``cmpp-gateway`、Nginx、MinIO 均 active`12026/17890/8090/3000` 监听,API/Gateway health 和外部首页、运营端登录页 HTTP 200。线上源码确认按完整括号 `match[0]` 查询且不带全局 `reportStatus` 条件,部署后 API/Gateway 近期日志无 error。未擅自向客户号码重发短信;新提交将按应用现有模板不匹配人工审核策略进入真实审核与后续通道路由。
## 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`,多标签页同步状态。
- API 自动化测试当前 15 suites、151 项通过;新增 Redis 会话空闲锁定、客户端独立阈值、4 小时恢复期限、解锁轮换旧标识失效、`sessionVersion`/角色权限变更撤销和敏感操作 Guard 用例。真实本地 PostgreSQL + Redis + NestJS 短阈值验收通过:登录响应无访问令牌且收到 Cookie、无 Cookie 访问返回 `401/SESSION_INVALID`、空闲后返回 `401/SESSION_LOCKED`、密码解锁后恢复查询、认证窗口过期后敏感操作返回 `403/RECENT_AUTHENTICATION_REQUIRED`、重新认证和登出成功;临时用户与会话已清理。该功能已随下游 ACK 批次提交并部署,生产 Redis `PONG`,携带无效 HttpOnly 会话 Cookie 访问真实会话接口返回预期 `401/SESSION_INVALID`
## 2026-07-14 下游 CMPP_DELIVER_RESP 精确确认与双重试开关
- 生产只读排查 11:40 两条余额失败短信确认:NestJS 已生成 `REJECTD` 回执,Gateway 对同一 CMPP2.0 会话写出后分别收到 Sequence_Id 37/38 的 `CMPP_DELIVER_RESP`。原 `CmppDownstreamDelivery.status=delivered` 仅代表 `SendPkt` 成功,无法证明客户确认,属于观测口径缺陷。
- Gateway 新增按连接和 Sequence_Id 跟踪投递,记录实际下发 Msg_Id;写出后回调 `downstream/sent`,收到 CMPP2.0/2.1/3.0 `DELIVER_RESP` 后校验 Sequence_Id、Msg_Id 和 Result,再回调 `downstream/acknowledged`。ACK 超时回调真实失败类型;重启后 NestJS 在客户恢复拉取 pending 时补偿处理过期 `awaiting_ack`
- Prisma `SmsApplication` 新增回执、上行两个自动重试开关,默认开启;`CmppDownstreamDelivery` 保存策略快照、写出/确认/截止时间、ACK Result/Sequence_Id/Msg_Id 和连接 ID。关闭开关后首次投递仍保留,已写出未确认或被拒绝的对应类型不自动重发;手工重投保留并增加重复处理二次确认。
- 运营端企业应用编辑页增加两个独立开关;下游投递页区分待首次投递、等待客户端确认、客户端已确认、未确认、拒绝和最终失败,Dashboard 和告警改为真实 ACK 口径。历史 7 条仅确认写出的记录迁移为 `unconfirmed`,不伪造 ACK。
- 新增 API ACK 状态与关闭重试策略单测、Gateway 真实 CMPP 会话 ACK 回调/超时单测;同步更新需求和 TC-GW-ACK-001004。API 全量 15 suites、153 项通过,Gateway 全量 Go 测试、Prisma validate 与本地真实 PostgreSQL migration、API build、前端 build 和 `git diff --check` 通过;前端仅有既有 Vite chunk size warning。应用内浏览器确认本地构建标题、登录 DOM、无框架覆盖及 console 无 error/warn;因真实图形验证码未获授权代解,受保护页面内的两个开关点击验收未执行。
- 功能提交 `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 都是 0Gateway 日志中的原 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 的 DeliverNestJS 也不再把 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,请求按预期变为待重试而非伪造成功。临时投递记录和验收账号已清理。
- 功能提交 `1ce02ef2` 已 push 并部署生产。部署前 PostgreSQL 备份为 `/opt/cmpp-platform/backups/cmpp-20260714-163007.sql`(约 65MB),运行源码备份为 `/opt/cmpp-platform/backups/source-20260714-163007.tar.gz`(约 21MB);发布包本地与服务器 SHA-256 均为 `67a79f17bd2f61b5df3057ded500009d5d3ef95bf656728299f3ead0011a763a`
- 生产 migration `20260714153000_fix_downstream_receipt_message_id` 成功应用,38 条 migration 全部完成;错误的 `receipt/status=delivered/ackMessageId=0` 已降为 0,共 9 条历史记录按真实口径纠正为 `unconfirmed`。生产 `.deployed-commit=1ce02ef2066aa1ecd995b0a3b884304218adc008``cmpp-api``cmpp-gateway`、Nginx、MinIO 均 active`12026/17890/8090/3000` 监听,API/Gateway health、Redis、PostgreSQL 和外部首页/运营入口 HTTP 200;部署后 journal 无 error,真实 CMPP2.0 账号 `910887` 已重新连接并持续心跳。未擅自发送或重投客户短信,后续真实新提交用于验证 SubmitResp 与 Deliver 的 Msg_Id 关联。
## 2026-07-15 报表导出、模板规则与通用 Select 优化
- 运营端对账单、利润报表、发送质量报表增加真实服务端 CSV 导出,复用页面日期、企业、应用、通道和统计维度筛选,导出全部筛选结果且不受当前分页影响。
- 报备字段库重做为统计概览、签名/引流信息通用配置双栏和自适应字段卡片;继续使用真实字段库及通用字段 API,保留引用锁定删除规则。
- 运营端、客户端用户管理新增按钮统一调整为标准小尺寸。
- 本地真实 NestJS API 与 PostgreSQL 登录验收发现并修复两处仅构建无法暴露的布局问题:运营端新增用户按钮曾被 Grid 拉伸至 731px,现为 98×32px;报备字段库在 800px 视口的筛选区曾出现内部横向滚动,现已切换单列且页面与工具栏 `scrollWidth=clientWidth`。1280px 桌面下三类报表均加载真实聚合数据并显示唯一“导出报表”入口,页面无横向溢出、无 console error/warn。
- 将下拉裁剪修复收敛到项目通用 `Select`:所有下拉默认使用 `document.body` Portal 和 fixed 定位,最高展示 320px 选项,并随视口、页面滚动实时重定位,页面不再逐个配置专用下拉。应用内浏览器以企业签名真实本地数据验证企业搜索、企业选择及应用联动;831px 高视口下列表完整显示在弹窗上方层级,600px 高视口自动向上展开且 `top >= 0``bottom <= viewport`,控制台无 error/warn。
- 提交前整批验证:API 全量 18 suites、194 项通过,API build、前端 build、Gateway 全量 Go 测试、Prisma validate 和本地 47 条 migration status 均通过;通用 Select 另在利润报表普通筛选区确认 listbox 直接挂载于 `BODY`、使用 fixed 定位且完整处于视口内。前端仅保留既有 Vite chunk size warningJest 仍需 `--forceExit` 退出既有异步句柄。
- 工作区完整改动已提交并 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 或静态数组。