feat: densify sms records and improve uplink matching

This commit is contained in:
hectorzhao
2026-08-27 10:15:08 +08:00
parent 898471423f
commit a280b4bb22
25 changed files with 859 additions and 154 deletions
+6 -4
View File
@@ -110,8 +110,9 @@
修复:
- 已接入 `GET /api/client/operations/uplink-messages`
- 详情弹窗按上行 `messageId` `GET /api/client/operations/messages`,展示真实匹配的下发记录
- 原无 API 支撑的“添加到应用黑名单”按钮已移除,后续补真实企业黑名单或应用黑名单 API 后再恢复
- 详情优先展示上行列表响应中已匹配的真实 `messageRecord`;仅对历史兼容数据在缺少内嵌记录且存在平台 `messageId` 时回`GET /api/client/operations/messages`
- 页面分开展示供应商 MO 的上行网关消息 ID 与关联平台消息 ID,不再把缺少平台 `messageId` 误判为没有匹配记录
- 客户端保持只读,不提供越权的运营黑名单写操作。
### 运营端短信上行记录仍是静态表
@@ -133,8 +134,9 @@
修复:
- 已接入 `GET /api/admin/operations/uplink-messages`,返回真实上行记录、企业和通道信息。
- 详情弹窗按上行 `messageId` `GET /api/admin/operations/messages`,展示真实匹配的下发记录
- 原无 API 支撑的“添加到应用黑名单”按钮已移除,后续补真实企业黑名单或应用黑名单 API 后再恢复
- 详情优先展示上行列表响应中已匹配的真实 `messageRecord`;仅对历史兼容数据在缺少内嵌记录且存在平台 `messageId` 时回`GET /api/admin/operations/messages`
- 页面分开展示供应商 MO 的上行网关消息 ID 与关联平台消息 ID,并显示匹配状态和原因
- 已匹配或人工认领到企业应用后,恢复“加入应用黑名单”按钮并调用真实 `POST /api/admin/dictionaries/blacklists/enterprise`;未匹配应用时不允许写入。
### 运营端短信任务进度仍是静态任务
@@ -37,6 +37,8 @@
".admin-sms-record-table-card",
".admin-sms-record-toolbar",
".admin-sms-record-list",
".admin-sms-record-list__header",
".admin-sms-record-group-title",
".admin-sms-record-card",
".admin-sms-record-status",
".admin-sms-record-detail-link",
@@ -66,10 +68,12 @@
"短信内容",
"通道名称",
"发送状态",
"是否含引流信息",
"查询",
"重置",
"导出CSV",
"查看发送详情",
"分片",
"发送详情",
"通道发送与回执",
"分片补偿审计"
+1 -1
View File
@@ -2,10 +2,10 @@
"schemaVersion": "v1",
"messageType": "UplinkEvent",
"traceId": "trace-20260701-uplink-000001",
"messageId": "uplink-20260701-000001",
"channelId": "sms-channel-cmpp-001",
"createdAt": "2026-07-01T09:01:00.000Z",
"sequenceId": 4096,
"gatewayMessageId": "8412634832294102675",
"phoneNumber": "13800138000",
"destId": "106900000000",
"content": "TD",
@@ -24,6 +24,18 @@
"createdAt": { "type": "string", "format": "date-time" }
}
},
"UplinkEnvelope": {
"type": "object",
"required": ["schemaVersion", "messageType", "traceId", "channelId", "createdAt"],
"properties": {
"schemaVersion": { "const": "v1" },
"messageType": { "const": "UplinkEvent" },
"traceId": { "type": "string", "minLength": 8 },
"messageId": { "type": "string", "minLength": 8 },
"channelId": { "type": "string", "minLength": 1 },
"createdAt": { "type": "string", "format": "date-time" }
}
},
"SubmitCommand": {
"allOf": [
{ "$ref": "#/$defs/Envelope" },
@@ -180,13 +192,14 @@
},
"UplinkEvent": {
"allOf": [
{ "$ref": "#/$defs/Envelope" },
{ "$ref": "#/$defs/UplinkEnvelope" },
{
"type": "object",
"required": ["messageType", "sequenceId", "phoneNumber", "destId", "content", "receivedAt"],
"required": ["messageType", "sequenceId", "gatewayMessageId", "phoneNumber", "destId", "content", "receivedAt"],
"properties": {
"messageType": { "const": "UplinkEvent" },
"sequenceId": { "type": "integer", "minimum": 0 },
"gatewayMessageId": { "type": "string", "minLength": 1 },
"phoneNumber": { "type": "string", "pattern": "^1[3-9][0-9]{9}$" },
"destId": { "type": "string", "minLength": 1 },
"content": { "type": "string", "minLength": 1 },
+25 -6
View File
@@ -234,9 +234,10 @@
2. 按手机号和时间查询。
3. 查看上行关联下发记录。
- 预期结果:
- 展示上行内容、接入号、接收时间。
- 可展示匹配到的下发 messageId。
- 未匹配上行仍可查询,状态或关联为空
- 展示上行内容、接入号、接收时间、上行网关消息 ID、匹配状态和匹配说明
- 后端已通过接入号或手机号时间窗匹配时,即使上行事件没有关联平台 `messageId`,详情仍直接展示响应中嵌入的真实下发记录及其平台 `messageId`
- 上行网关消息 ID 与关联平台消息 ID 分栏展示,不把供应商 MO `Msg_Id` 误作历史 MT Submit 消息 ID
- 未匹配或多候选上行仍可查询,并显示真实状态;多候选提示联系运营人员认领。
### TC-CLIENT-010 用户管理与企业管理员唯一性
@@ -1139,11 +1140,16 @@
### TC-SEND-008 上行短信匹配
- 优先级:P1
- 步骤:模拟带 messageId 的上行事件。
- 步骤:
1. 模拟带平台 `messageId` 的兼容上行事件。
2. 模拟真实供应商 MO:仅带独立 `gatewayMessageId`,不带历史平台 `messageId`,并分别制造接入号唯一匹配、手机号 72 小时唯一匹配和多候选场景。
3. 打开客户端和运营端详情。
- 预期结果:
- 创建 SmsUplinkMessage。
- tenantId 可通过 messageId 关联。
- 客户端和运营端均可查询
- `gatewayMessageId` 原样持久化;兼容事件仍可通过平台 `messageId` 精确关联。
- 不带平台 `messageId` 时按接入号、手机号 72 小时窗口执行匹配;唯一结果写入 `tenantId/applicationId/messageRecordId`,多候选保留为 `ambiguous`
- 客户端和运营端均可查询;详情优先使用 API 响应内嵌 `messageRecord`,不因 `messageId` 为空误报“无法匹配”。
- 运营端对已匹配或已认领应用显示“加入应用黑名单”,确认后调用真实企业应用黑名单 API;客户端保持只读。
### TC-SEND-009 未匹配上行短信入库
@@ -1151,6 +1157,7 @@
- 步骤:模拟不带 messageId 或匹配不到下发记录的上行事件。
- 预期结果:
- 上行短信仍入库。
- 供应商 MO `gatewayMessageId` 与平台 `messageId` 分别保存,不能用前者伪造后者的关联。
- tenantId 可为空。
- 运营端可查询并人工判断。
@@ -4890,3 +4897,15 @@ npm run verify:phase8
| TC-CMPP-PHASE5-018 | 同连接并发状态回调 | 同一connectionId的connected与submit并发时幂等upsert且保持在线;不同connectionId超过cmppMaxConnections仍403,不能误断当前连接或漏SubmitResp |
执行记录:按企业微批发布后,单企业100 TPS为999/999响应、P95/P99=`243/466ms`、首次供应商Submit=`95.85 TPS`;单企业150 TPS冲击为1498/1498、`83/117ms`、首次Submit=`122.87 TPS`;双企业200 TPS冲击为1999/1999、`148/287ms`、首次Submit=`129.39 TPS`。三档最终有效运行均零拒绝、零节流、零连接错误,价格均325。双企业档账务1988 charged/646100、11 refunded/35752145个Submit/Outbox唯一,1950条终态回执投递1950次、重复0,973条离线pending通过零发送客户端排空。多Gateway P2未实施。
## TC-ADMIN-SMS-RECORD-DENSITY 运营端短信记录高密度列表(2026-08-27)
| 用例ID | 场景 | 预期 |
| --- | --- | --- |
| TC-ADMIN-SMS-RECORD-DENSITY-001 | 打开短信记录搜索区 | 企业、应用、提交日期、手机号码、运营商、短信内容、通道、发送状态、是否含引流信息9项条件均保留,不能新增、删减或合并 |
| TC-ADMIN-SMS-RECORD-DENSITY-002 | 在桌面与窄视口查看搜索区 | 仅控件宽度和布局响应式变化,9项条件、查询和重置行为不变,无控件覆盖或截断 |
| TC-ADMIN-SMS-RECORD-DENSITY-003 | 查看包含单分片和多分片的短信记录 | 列表按日期分组;提交与回执分别显示日期和时分秒;计费列同时显示真实金额、分片数和字数;列表不显示“已补发”标签 |
| TC-ADMIN-SMS-RECORD-DENSITY-004 | 点击任一行最右侧箭头 | 继续打开既有发送详情弹窗,原有短信内容、通道发送与回执、状态信息及分片补偿审计保持不变 |
| TC-ADMIN-SMS-RECORD-DENSITY-005 | 使用真实本地API数据加载、查询、翻页和打开详情 | 页面非空、无异常遮罩,控制台无新增错误;数据仍来自原有真实API,不引入mock或localStorage业务数据 |
执行记录:本地真实API/PostgreSQL渲染通过,9项条件全部存在,1/2分片均显示在计费列;右箭头成功打开原有详情弹窗,干净页面控制台日志为空。R11契约、前后端构建、159项API专项测试及Gateway全包测试/vet通过。
+16
View File
@@ -4021,3 +4021,19 @@ git diff --check
- 预生产最终监控标记和部署标记均为`523481299028d9ded470d7738add145ee2070bd1`。安装版本:Prometheus 3.14.0、Node Exporter 1.12.1、PostgreSQL Exporter 0.20.1、Redis Exporter 1.89.0、Nginx Exporter 1.5.3Prometheus加载9个规则组共83条规则。
- 发布后 Prometheus 9个 target 全部`up``pg_up=1``redis_up=1``nginx_up=1`9090/9100/9187/9121/9113及API/Worker metrics均只监听回环。Prometheus、5个 Exporter、MinIO、Nginx、API、Worker、Gateway、PostgreSQL和Redis均activeAPI/Gateway健康;供应商连接`desired=9/connected=9`,命令/结果 Stream 最终均`pending=0/lag=0`
- 浏览器只读验收已到达预生产运营端登录页,因当前浏览器没有运营端登录会话,没有输入凭据或验证码,故本轮未把登录后的页面截图作为验收证据;监控可用性以 Prometheus targets、PromQL和API进程实际环境变量为当前证据。未发送短信、未压测、未修改余额/应用/企业/白名单/临时号段/通道配置,未操作正式生产,多 Gateway P2 未实施。
## 2026-08-26 上行匹配展示与应用黑名单入口本地修复及提交前验证
- 只读回查预生产数据库时共有84条上行记录,其中61条已匹配、23条为多候选;82条没有平台`messageId`,但59条已通过接入号或手机号72小时窗口写入`messageRecordId`。因此“全部匹配不到”的直接原因是客户端和运营端详情在`messageId`为空时提前返回,忽略API已返回的`messageRecord`;这不代表后端未匹配。
- CMPP普通MO的Deliver `Msg_Id`是供应商为该上行分配的独立标识,不是历史MT Submit的消息ID。Gateway此前仅尝试用它查询本地Submit跟踪器,正常MO通常得不到平台`messageId`,且原始MO `Msg_Id`只写日志未持久化。本轮新增独立可空字段`SmsUplinkMessage.gatewayMessageId`及迁移,Gateway将MO `Msg_Id`原样随事件上送,API持久化并在两端详情与关联平台消息ID分栏展示;平台`messageId`继续只表示真实关联,不伪造关联。
- 客户端和运营端详情优先使用上行列表响应内嵌的真实`messageRecord`;只有历史兼容记录在缺少内嵌记录且存在平台`messageId`时才回查消息接口。多候选记录给出认领提示。运营端对已匹配或已认领到企业应用的上行恢复“加入应用黑名单”按钮,确认后调用现有真实企业应用黑名单API;客户端保持只读。
- 自动化门禁:Gateway/API队列5份契约样例通过;SendChain与Operations专项2套159项通过;API正式TypeScript构建、前端TypeScript检查、Vite生产构建、Prisma schema校验、Gateway全包`go test ./... -count=1``go vet ./...`均通过。Gateway首次全包测试仅既有限速时序用例偶发一次`delay=0`,该包连续5轮及随后全包复跑均通过,本轮未修改限速实现。
- 本轮功能改造仅发生在本地工作区,没有在远端环境执行数据库迁移,没有发送短信、压测、push或部署。新增迁移必须随未来授权发布执行后,新上行才会保存`gatewayMessageId`;历史记录不会反填供应商MO ID。预生产和正式生产均未改动,多Gateway P2未实施。
## 2026-08-27 运营端短信记录高密度列表本地调整
- 搜索区保留原有9项条件:企业、应用、提交日期、手机号码、运营商、短信内容、通道、发送状态、是否含引流信息;未新增、删除或合并条件,仅改为12列响应式布局并压缩控件宽度。
- 短信记录由大卡片改为按提交日期分组的紧凑行式列表;提交时间和回执时间均按日期、时分秒两行展示,无回执时明确显示“暂无回执”。计费列集中展示金额、分片数和字数,不展示“已补发”标签;最右侧箭头继续调用原有`SendDetailModal`,弹窗实现未修改。
- 本地真实API和PostgreSQL数据渲染验证通过:9项搜索条件全部存在,列表可见1/2分片计费记录,点击右箭头可打开原有发送详情、通道发送与回执、状态信息和分片补偿审计;另开干净页面控制台日志为空。
- 门禁通过:R11页面契约、前端TypeScript检查与Vite生产构建、API正式构建与Prisma校验、队列契约、SendChain/Operations 2套159项、Gateway全包`go test ./... -count=1``go vet ./...`。pnpm包装器因既有`msgpackr-extract`构建脚本未批准而中止,未放宽依赖策略,改用已安装的TypeScript/Vite入口完成等价构建。
- 本轮仅使用本地隔离环境,没有访问或修改预生产/生产,没有发送短信或压测。为渲染验证启动的PostgreSQL、API、前端预览和临时Redis均已停止;多Gateway P2未实施。