fix: 修复上行归属并实现签名质量日报优化

This commit is contained in:
hectorzhao
2026-09-17 11:12:58 +08:00
parent 4eb7b16d12
commit 572290308c
40 changed files with 2854 additions and 778 deletions
@@ -2339,3 +2339,18 @@ Webhook需在当前受支持Node运行时通过真实HTTPS投递;SSRF校验后
5. 监控刷新失败显示真实上一快照和过期提示,超时/切换请求取消,恢复后刷新;不同查询范围隔离。
授权交付:代码、定向/全量测试、精确提交、推送、测试环境标准发布及隔离模拟短信验收;不操作预生产,不向真实运营商发送。
## 2026-09-17 签名质量独立查询与日报优化(本地实现,待发布)
本节对应用户本轮方案要求,权威设计见[签名退网检测与四页查询优化方案](signature-quality-optimization-plan-20260917.md),本地实施状态见下方补充,线上尚未发布。
1. 四TAB的日期、筛选、分页、请求、错误及结果独立;企业/通道活跃度改为带dimensionType的独立服务端查询与分页,不再各自获取两类全量数据。保留默认25条和10/25/50/100档;同TAB顶部和卡片查询应用同一组草稿条件。
2. T为服务器北京时间今天,实际统计日为d:d=T查询时实时查业务库;T-1~T-3每天凌晨同时刷新、查询只读日报;T-4及以前只读冻结日报,普通查询、调度和重启不得重算。缺报表明确显示未生成,不回退明细或伪装零。
3. 用户确认热力图保持所选日D之前30天,即D-1~D-30,不包含当天列;每格按实际日期相对真实T判断数据来源,不按页面D重新开放历史刷新。
4. 日报历史名称、归属和报备适用性快照保留;冻结后迟到回执、补登记和状态变化不覆盖旧报表。预警判断/通知与可刷新的活动日报分开,刷新不得重发历史通知。
5. 退网检测采用批量聚合、提前跳过已完成、任务原子认领与故障接续,候选索引由真实执行计划决定。保留业务短信/提交尝试/计费片数区别、长短信完整分段判定和跨通道/跨日窗口去重;现有最长365天规则不缩短。
6. 原方案阶段为只读;本轮本地实现与隔离验收见下方补充,线上历史补建、推送、两环境部署与真实发送均未执行。本节不改变其他财务/质量报表T-4~T-1刷新规则。
### 2026-09-17 本轮本地实现
签名质量日报按专项优化方案实现:T实时、T-1~T-3每日刷新、T-4冻结;热力图D-1~D-30且企业/通道请求独立,失败保留本TAB上次真实数据并标明日期。退网窗口批量去重、规则快照/租约/fence/失败恢复与日报解耦。上行按应用唯一性而非短信条数归属,同通道接收前accepted作为证据;入库、候选、认领、通知意图保证事务一致。历史待认领不自动处理。本轮仅本地修改和提交,线上发布/补建及实际短信/通知验收另按授权执行。
@@ -0,0 +1,191 @@
# 签名退网检测与签名质量四页查询优化方案
维护日期:2026-09-17。状态:**本地实现及隔离验收完成,待发布**。用户于本轮授权修改并本地提交;没有授权本轮推送、部署、历史上行认领或真实短信/通知发送。第1节保留实施前取证;本轮实现与验收以第8节为准。
本文是本次三项需求的统一实施设计,补充[签名清退设计](signature-retirement-alert-design.md)。实施后替代该设计第6节、第7节第15步中“热力图直接读检测快照、按当前报备维度展示、前端筛选分页”的实现方式,以及[运营修复设计](operations-fixes-20260908.md)第4项中的客户端热力图分页;保留四TAB独立状态、默认25条、10/25/50/100档、D-1至D-30列序和原业务统计单位。现阶段旧代码仍在运行,不能把本方案描述为已上线功能。
本方案仅针对“签名质量检测”四TAB与签名退网检测,不改变“报表对账/发送质量报表”的既有T-4~T-1刷新任务,不改变短信门禁、补发、计费、余额或报备状态。
## 1. 核查结论与证据
### 1.1 基线
- 本地main与实际远端main均为`4eb7b16d122da14f921093716d4ca1ed390d9e4c`,暂存区空;已有版本、metrics、发布工具、部署脚本及文档草稿保留。
- 2026-09-17 09:37北京时间只读核验预生产运行`010ba3216889032a6160cdb14d8536b616ae7102`。上述两个提交间本方案涉及的页面、质量查询和退网检测源码没有差异。
- 证据:`src/apps/admin/AdminAnalyticsPage.tsx``src/api/admin/signature-retirement.api.ts``api/src/operations/queries/quality.queries.ts``api/src/signature-retirement/signature-retirement.service.ts`、对应controller及`api/prisma/schema.prisma`
- 本机原始取证在忽略目录`.local-data/cpu-20260917/``prom.json``detail.jsonl``design-audit.json`及只读脚本。线上DB使用只读事务与8秒语句超时,未启动应用调度器。此次未执行登录后的四TAB真实浏览器/HTTP复测;以下交互结论来自当前源码,不冒称已复现浏览器串数据。
### 1.2 凌晨CPU与检测成本
今天检测日2026-09-17的结果从04:00:01.620写到04:04:07.792,共5,831条(企业1,431、通道4,400)。前一天4,280条。Prometheus五分钟CPU均值峰值58.79%04:06:45为32.81%04:06最近一分钟忙碌率约2.54%,趋势包含此前负载。日报刷新日志完成于00:33:09。
同窗口`SmsMessageRecord``SmsSubmitRecord`顺序扫描行速率峰值分别约362万、536万行/秒;这是数据库重复访问行的计数,不是短信量或TPS。IO等待较低。检测时段、真实快照与数据库扫描高度吻合,是主要负载线索;缺少历史业务进程CPU与SQL耗时采样,不能量化各进程占比或断言唯一原因。
现有`runDetection`逐个维度调用`activityCounts`:至少一次单日查询,完成观察期后再查一次规则窗口;每次关联消息、提交、分段/回执。已有检测是否存在的判断在这些计算之后的`persistDetection`中,重启补偿仍可能先重复计算再退出;周期、抑制、检测记录还存在逐行读写。现有唯一键保证部分去重,不等于任务已具备全程原子认领和完整事务恢复。
### 1.3 四TAB是否关联
| TAB | 当前请求与数据来源 | 当前关联/独立性 | 与目标的差距 |
|---|---|---|---|
| 签名通道发送质量 | `GET /admin/operations/signature-quality`;所有日期实时聚合消息、提交、分段与回执 | 独立日期、关键字、分页、请求序号;详情使用本TAB结果 | 历史也查明细,无近三天报表/冻结分支 |
| 企业签名活跃度 | `GET /admin/signature-retirement/heatmap?date=D` | 独立组件状态,但接口返回企业和通道全部数据,再在前端选企业、过滤、排序分页 | 使用次日检测快照而非可刷新的活动日报;行集合依赖当前通过任务 |
| 通道签名活跃度 | 同上 | 独立发起请求,但再次拉取相同两类数据,再在前端选通道 | 同上,整月全量传输和处理重复 |
| 未报备签名 | `GET /admin/signature-retirement/unreported-signatures`;所有日期查消息正文及当前有效签名库 | 独立日期、关键字、分页与结果 | 历史实时重算;补登记/删除签名会改变旧日期结果 |
结论:没有发现四TAB共用筛选状态或一个TAB直接改写另一个结果的实现;存在两个活跃度TAB共用全量接口与数据源,且活跃度展示依赖退网检测任务。底层业务事实本来有关联,不能为“页面独立”复制出四套互相矛盾的短信事实;应解除请求、状态、失败和生成触发之间的耦合。
另需同步修复的本TAB日期歧义:未报备TAB编辑顶部日期后,卡片内“查询”仍按`appliedDate`请求,须先点顶部“查询统计”才能应用新日期。这不是跨TAB串数据,但会造成查询条件似乎未生效。
### 1.4 三档取数规则当前未实现
质量与未报备页面全部日期实时查库;活跃度使用`SignatureRetirementDetection`快照,`persistDetection`已存在即返回,不会连续刷新最近三天。历史展示又拼接当前签名名称和当前报备维度,因此既不是完整的实时统计,也不是稳定冻结日报。
已有`DailyQualityReport`服务另一个报表模块,按计费单位统计,维度缺少本页完整的运营商、通道、引流拆分、去重业务数与到达耗时分子分母,不能直接拿来替代本页。既有财务/质量报表T-4~T-1策略也不自动等同本次要求。
## 2. 已确认业务规则
### 2.1 日期定义和数据来源
T始终指服务器按`Asia/Shanghai`确定的真实今天;D指页面所选日期;d指实际统计自然日,均不依赖浏览器所在时区。用户已确认:**每天凌晨同时刷新T-1、T-2、T-3;热力图保留所选日之前30天。**
| 实际统计日d | 查询行为 | 后台生成/更新 |
|---|---|---|
| d=T | 本次查询访问真实业务库,在一致性读视图中聚合 | 不将昨日缓存冒充当天数据,不写退网检测或通知 |
| T-3≤d≤T-1 | 只读已完整发布的日报 | 每天凌晨刷新最近三个完整自然日,吸收迟到回执 |
| d≤T-4 | 只读冻结日报 | 普通调度、页面查询、进程重启均不得自动重算或覆盖 |
例:今天9月17日,9月17日实时,9月14~16日报表可刷新,9月13日及以前冻结。9月18日可刷新范围成为9月15~17日;9月14日报表保留9月17日最后成功发布的版本,不在成为T-4后再额外重算。刷新判断相对真实今天,不能相对用户选择的历史日期重新开放冻结区。
热力图选D=9月17日仍展示9月16日至8月18日(D-1至D-30),没有当天列;每一列按该格子的d判断可刷新/冻结。单日质量与未报备TAB选今天时采用实时路径。禁止选择未来D来变相让热力图出现今天列。
冻结意味着“固定统计截面”,不意味着所有短信此时都已有最终回执。冻结后迟到回执继续正常更新短信业务事实,不改已冻结报表,不把未知自动改成失败;报表与当前详单存在截面差异时明确显示截止时间。跨日补发也不得使已冻结日的历史报表被普通任务改写。
### 2.2 独立查询的边界
- 四TAB各有日期草稿/已应用日期、过滤草稿/已应用过滤、排序、分页、pageSize、请求取消/序号、loading/error和结果,任何查询只更新本TAB。
- 首次打开TAB只请求该TAB;切回保留自己的条件及已取得结果,用户查询/刷新只重查当前TAB。隐藏TAB不自动重查,不用另一TAB成功响应填补失败。
- 企业、通道活跃度请求明确携带不同`dimensionType`,服务端只处理所选维度,按过滤后的完整30日合计排序再分页,只返回当页维度及其30格。
- 日期与搜索“查询”统一应用本TAB当前全部草稿条件并回到第一页;翻页只使用已应用条件。特别修正未报备卡片查询沿用旧日期的问题。
- 底层只读聚合器、数据库连接池和已发布日报可以复用;页面查询不触发其他TAB请求、不触发报表重建、不运行退网检测、不创建预警。
### 2.3 指标和历史维度
- 质量列表/运营商概览以业务短信为单位;通道矩阵以真实发送尝试为单位;保留现有不同成功率分母并在契约逐字段登记,不把计费片数换成业务条数。
- 业务统计按`SmsMessageRecord.queuedAt`归日;通道尝试及活跃度按现有`COALESCE(submittedAt,createdAt)`归日。跨日补发可能使列表与矩阵日期归属不同,应分别生成,不能先用当天业务列表裁掉当天实际提交、但原消息属于其他日期的事实。
- 企业活跃度在统计范围内按messageRecordId去重,通道按messageRecordId+channelId去重;不直接累加通道值得到企业值。规则窗口跨日也须重新去重,不能把每日distinct数简单相加。热力图“30日合计”保持每日展示值之和并标注为日活跃量合计,区别于预警窗口去重人数/条数。
- 成功必须按发送尝试完整分段事实判断,缺片/缺回执仍为未知;长短信不能因已到的几个分段都成功而提前成功。旧记录无分段事实时才按既有明确回执兼容;不把上游接受当送达。
- 平均到达时长持久化成功耗时总和与样本数,展示时相除;不能平均各组平均值。列表、抽屉、运营商概览及引流三类拆分使用相同generation版本。
- 日报保存当次统计维度名称、企业应用归属、运营商、报备适用状态/通过时间与统计规则版本。历史筛选使用报表快照字段,不能inner join当前通过任务导致历史行消失。活动日报逐日记录适用性;尚未通过、不适用、已通过且零发送、未生成必须区分。
- 未报备仍指规范正文签名在该企业应用有效签名库中不存在,且原消息无signatureId,不等于缺少通道报备。实时用查询时签名库;近三日用本轮生成时签名库;冻结日保留最后发布判定及名称。此后补登记不得追溯抹除冻结历史。
## 3. 性能优化方案
### 3.1 优先消除重复计算
1. 将逐个签名×通道×运营商反复扫描,改为按统计日有界批次聚合;先限定日期与候选消息/提交,再对这些submitId批量归集分段/回执,避免每个维度重新读取相同事实。
2. 一次读取参与检测的规则、通过任务、既有检测键与抑制摘要。已经完成的检测批次直接退出;部分完成只恢复缺失维度,避免昂贵计算后才查存在性。
3. 单日活动日报与质量日报复用经核验的尝试分类逻辑,按各自归日维度生成结果。退网“是否活跃”的窗口只需要accepted去重,不为判断阈值重复关联所有回执;送达率由日报单独计算。
4. 预警窗口最长支持现有365天,按实际生效规则的最大窗口限制扫描,批量JOIN规则维度后按message/channel去重。企业跨通道、跨日不能直接SUM日报;在测试库比较批量COUNT DISTINCT与适量窗口分组方案后选定。禁止以改成30天窗口偷换规则。超大窗口必要时分阶段物化紧凑去重键,但须先盘点容量,不能本轮默认复制全量短信正文或全部历史明细。
5. 批次发布后退网任务引用已完成单日活动版本,并独立保存当时窗口判断及规则版本。刷新日报不更新既有检测决策、不推进旧周期、不重新生成通知。
### 3.2 索引和SQL计划
预生产真实索引已有message.queuedAt、submit.messageRecordId,但未见消息的signatureId+carrier组合索引,也未见submit的`COALESCE(submittedAt,createdAt)`表达式时间索引。不能仅因没有索引就断言某一SQL必然全表扫描。
候选包括消息(signatureId,carrier,id)、提交(COALESCE(submittedAt,createdAt),messageRecordId)、按实际计划需要的通道+有效提交时间,以及日报日期/维度/排序组合。保留表达式原语义,不能直接用createdAt替代submittedAt。候选须通过有代表性数据的`EXPLAIN (ANALYZE, BUFFERS)`验证选择性、loops、临时文件和写入成本后决定,不盲目全部加索引;本轮未执行重查询或建索引。
大表索引上线采用平台支持的在线建索引流程,明确非事务DDL、失败无效索引检查及重试;具体迁移方式在实施时结合Prisma发布门禁验证,禁止把不允许在事务中的DDL塞进普通事务而绕过错误。不全局扩大work_mem,不默认增加SQL/worker并发。
### 3.3 页面成本
- 活跃度改服务端过滤、排序、分页,只传单类维度的当页30格;四TAB仍各自独立,不将两个活跃度结果捆绑返回。
- 查询快照元数据和维度使用Map/数据库JOIN,不逐条Array.find造成大集合反复查找;保留已有模块级日期格式化器。
- 首屏与列表查询仅取所需字段;若详情改独立请求,必须携带列表generationId以保证详情一致,不能点击详情又扫原始历史短信。
- 当天实时查询采用有界日期、分页和超时;失败保留当前TAB最后真实结果并标注其日期/时间,不能把旧值作为新查询成功。跨午夜请求由服务端固定T并回传,前端不自行混合两个自然日结果。
## 4. 日报生成、冻结和恢复
建议签名日报任务北京时间03:00启动,依次处理T-3、T-2、T-1,与已存在财务日报错峰;04:00退网检测、08:00通知时点保留。固定时间是拟实施配置,尚未上线;也必须检查与其他后台任务是否重叠。
生成任务具备耐久状态`pending/running/succeeded/retry_wait/failed`、租约、递增fence和有限重试;按scope+businessDate唯一认领,旧worker过期后不能发布。每个日期在一致性数据库快照下生成候选版本,完成四类校验后短事务切换publishedGenerationId。部分失败保留完整旧版;其他日期可继续,但整轮不得虚报全成功。发布必须再次验证fence、日期是否已冻结和候选水位。
页面只读published版本;生成中有旧版则显示旧版及刷新中/上次成功时间,没有旧版显示“报表尚未生成”,失败显示失败,不能返回空数组冒充零数据或回退扫描明细。合法零数据日期也必须生成完成清单与0行标记。
日界线进入T-4后自动禁止覆盖;跨午夜仍在运行的旧任务在发布校验时被拒绝。若T-3最后刷新失败,冻结上一成功版本并显示未达到预期截止时间;若从未成功则保留缺口,不伪造冻结完整。错过整个三日窗口的缺口只能进入显式历史补建流程,不能借“启动补偿”无限回算。
04:00所需日报未完成时,检测等待该日完成且告警;08:00只发送已完整完成检测的结果,不把半批维度当完整预警。后续恢复沿用当日补偿与幂等,不补发旧日期通知。周期变更、检测行、通知意图在维度事务中一致提交;全批完成标记必须在全部维度校验后置位。通知仍按日期+企业应用聚合及现有抑制规则,重试不能重复创建消息/Webhook。
日报刷新三天不等于重新做三天的预警判断。`SignatureRetirementDetection`保留历史判断审计,页面活动日报是另一投影,两者截止时间可能不同,页面标明统计口径而不是互相覆盖。
## 5. 拟新增模型与接口
以下名称仅为设计,不表示Prisma已有模型:
| 模型 | 主键/唯一维度与作用 |
|---|---|
| SignatureAnalyticsDay | businessDate唯一;publishedGenerationId、state、generatedAt、sourceAsOf、frozenAt、schemaVersion、coverage/provenance、rowCounts/checksum;成功零行也记录 |
| SignatureAnalyticsRun | scope+businessDate+generationId唯一;owner、leaseUntil、fence、attempt、checkpoint、startedAt/finishedAt、error摘要;每scope/date最多一个有效运行者 |
| SignatureQualityDaily | generationId+metricKind+tenant/application/signature+carrier/channel/drainage规范化维度键唯一;区分business与attempt,计数、耗时分子分母、名称快照 |
| SignatureActivityDaily | generationId+dimensionType+tenant/application/signature/channelKey/carrier唯一;单日提交、accepted业务数、送达数、适用性及报备快照 |
| UnreportedSignatureDaily | generationId+tenantId+applicationId+规范正文签名键唯一;业务数、生成时判定及名称快照 |
空channel/application使用无碰撞规范键,不能依赖可空字段唯一约束防重复。行均有真实businessDate和generationId外键;普通查询仅取发布版本。预警任务可复用耐久Run机制但scope独立,不与日报共用完成状态。旧版本保留期限和空间预算须在实施/发布阶段核验,不自动清理现有业务记录。
API建议:
- 质量与未报备保留现有GET路径和date/keyword/page/pageSize,增加`dataSource=live|report``reportState=ready|refreshing|failed|missing``mutable/frozen`、businessDate、serverBusinessDate、generatedAt/sourceAsOf、generationId和schemaVersion。数据源由服务器决定,客户端不能要求重算冻结日。
- 热力图提供独立版本路径`GET /admin/signature-retirement/activity`,必填dimensionType=enterprise|channeldate为D;企业/应用/签名/通道过滤分别传入,pageSize只接受10/25/50/100。返回单类当页维度、total和D-1至D-30;逐日期带版本/完整性元数据。保留旧heatmap GET只作兼容,最新页面不再调用;记录旧调用后再决定移除,不让旧前端接收截断结果冒充完整数据。
- 活跃度筛选、30日排序、总数与当页结果在同一个一致性读事务获取;先在过滤全集排序再LIMIT。缺失日期不可当成0参与已完整合计,须展示“部分日期缺失”。冻日前名称和维度只来自报表。
- 日期严格验证真实日历、拒绝未来日;页码为正整数、pageSize有上限,排序白名单。请求取消和旧响应隔离按TAB独立实现。
- 沿用运营端鉴权及数据可见范围,在数据库查询前约束tenant/application;不新增匿名查询/生成接口,不以报表缓存绕过权限。真实HTTP401/403、越权筛选和缓存键隔离须专项验收。
## 6. 历史切换、发布和恢复
1. 在真实数据规模的隔离测试库建立旧口径对照,包括长短信、补发、跨日和未报备;冻结字段契约,修正差异而非迎合旧错误。
2. 增量新增模型/索引与后台生成器,旧接口继续运行;候选生成器先影子计算,不产生通知,完成准确性与负载对照后切换。
3. 盘点可支持历史日期的源数据覆盖、报备轨迹、消息/回执保留和磁盘空间。一次性按明确日期清单补建历史日报,先满足热力图至少30日与用户常用查询区间;预警规则窗口另覆盖实际最大365日需求。没有可信历史事实就标记缺口,不把当前报备状态当作过去状态。
4. 旧检测快照可作为活动量迁移证据,但不能冒充补齐迟到回执后的完整日报。用原始事实重建的历史报表须记录backfilledAt/sourceAsOf/provenance,不能伪造为当年T+3冻结结果。完整性不足的日期不宣称已完成,历史可查询范围明确展示。
5. 迁移期不复制历史周期、不重发历史消息。生成和读取按schemaVersion切换;切换后禁用旧逐维度检测调度入口,防止双跑。先测试环境验收,再另按授权发布预生产。
6. 应用回退保留新增表和已发布冻结版本;异常优先暂停新任务、保留只读已发布日报。旧应用会恢复历史实时查询行为,不能称之为满足新冻结规则的等价回退,须在恢复说明中明确影响。不自动恢复数据库或删除候选/旧报表。
## 7. 验收、观察指标与实施顺序
详见[功能用例](system-functional-test-cases.md)中`TC-SQA-20260917-0116`,本地执行覆盖及环境未执行项见第8节。
- 准确性:真实PostgreSQL逐字段对账;短/长短信、缺片、重复乱序、失败补发、跨通道/跨日去重、未知和迟到回执;旧历史无分段兼容;成功率分母及耗时加权正确。
- 冻结:T、T-1、T-3、T-4、跨午夜/跨月/闰日;刷新原子性、版本一致、补登记不改变冻结未报备、名称/通过状态变更不让历史行消失;失败与零数据可区分。
- 独立性:四TAB首次/切换/查询/分页/详情;一个TAB失败、慢响应或修改日期不影响另一个;企业请求不含通道维度;真实HTTP与三尺寸浏览器核验,不仅用mock证明功能。
- 可靠性:双worker抢占、租约超时接管、旧fence发布被拒、批次部分失败、任务重启、错过冻结窗口、08:00依赖未完成、重复通知去重。报表刷新不写短信/余额/报备/通知状态。
- 性能记录:整机及PG/API进程CPU、数据库扫描行/块、SQL耗时/p95、任务耗时、锁等待/临时文件、响应字节、API内存、短信队列延迟;分别标注冷/热缓存和相同业务规模,不把一次结果当容量承诺。
- 拟验收目标:相同数据规模,退网原始明细扫描行数下降至少80%,任务耗时下降至少50%;单类热力图每页最多100×30个格,响应规模不随未选维度总数增长。此为测试目标而非已实测收益;不能单凭CPU目标判定成功。若准确性/业务队列恶化立即停止候选压测,不在预生产重新跑整套重任务验证猜测。
实施分三批:①独立接口/页面查询及精确统计契约;②日报三日刷新/冻结/历史补建与版本化读取;③退网批量聚合/原子任务/依赖编排及性能对照。批次①不宣称已完成报表化,③完成前不宣称CPU问题已解决。
这是前端、查询后端、数据库模型和后台调度的跨模块改造,主要成本在历史回填、口径对账和故障恢复验收,不能按简单索引补丁处理。实施需API/前端定向与全量回归、类型/构建/质量门禁、真实PG/接口/浏览器;若不修改Gateway无需冒充重做Gateway发布。真实短信、外部通知及压测只在后续明确授权的环境和范围执行。
## 8. 2026-09-17 本地实现与验收
### 8.1 实现及设计落地
- `api/src/signature-analytics/`提供北京时间日期校验、日报聚合、版本读取、后台调度和独立退网批次。03:00后依次刷新T-3/T-2/T-160秒补偿检查;04:00检测与08:00通知保留。日报失败不降级实时查询,未生成与合法零结果分开;热力图继续D-1~D-30。
- 新增六表:Day发布清单、Generation不可变版本、Run耐久任务、QualityDaily、ActivityDaily、UnreportedDaily。日报行和发布指针以generationId+businessDate复合外键保证日期/版本一致。质量嵌套矩阵和概览保存在同一JSON快照,检索字段独立列化;引流分组保存耗时总和/有效样本数并加权合并,不平均组均值。
- 与原建议“候选后短事务切指针”相比,本期采用**每个自然日一个RepeatableRead事务**,聚合、分批250行写入、发布与fence验证一起提交。理由是保证同日各口径取自同一源快照,避免未持久化源水位的断点续算混入不同截面。单SQL90秒、事务120秒、租约300秒;失败重算该日,成功日期不重算,最多5次指数退避。事务上限短于租约,不需要保持长事务跨租约续期。超过此规模应先扩展分阶段水位设计,不能直接无限增大超时。
- 退网窗口只扫描accepted提交,批量按实际规则窗口(最长365天)去重,单日送达数复用活动日报。规则与所引用日报版本在首次认领后持久化为checkpoint,重试不改变;检测、周期及冻结通知正文同事务。完成检查前移;日报刷新不修改已有检测/消息。缺昨日当日刷新版本时等待,不消耗检测重试次数。通知沿用原耐久消息/Webhook去重,同进程完成后不每分钟重扫全部消息;重启可补偿,禁止自动补发历史检测。
- 两个活动TAB改用`/admin/signature-retirement/activity`,明确dimensionType;服务器过滤、排序、分页和完整性读取处于一致性事务。每页只返回所选维度,最多100×30格;空的越界页仍返回正确总数。旧heatmap接口保留兼容旧页面,当前页面不调用。四TAB查询/取消/错误/日期/筛选各自独立,未报备卡片查询应用新日期。
- 普通历史读取只访问日报快照,无当前报备/名称JOIN。报备轨迹可还原时取历史状态与通过时间,旧数据仅有明确approvedAt时保留兼容证据;缺完整历史轨迹不宣称重建了过去所有状态。无报备资格的格子N/A,缺日报的格子显示缺口。旧记录完全无分段审计时保留明确回执兼容;存在分段时按预期总片数判断,缺片不提前成功。
- 迁移`20260917030000_signature_analytics`新增表;`20260917031000_signature_submit_effective_at`单独以非事务`CREATE INDEX CONCURRENTLY`建立有效提交时间表达式索引,避免将所有历史源扫描混进三日任务。108项完整迁移已在独立PostgreSQL库执行。线上失败须检查`pg_index.indisvalid`及迁移状态,按发布恢复流程处理;不自动DROP/resolve失败迁移,不绕过门禁。
### 8.2 历史补建与发布边界
离线入口`tools/testing/backfill-signature-analytics.mjs`使用已构建API,要求环境变量DATABASE_URL和匹配目标主机的`--host`,逐个明确`--dates=YYYY-MM-DD,...`;默认只检查,明确`--execute=yes`才生成。禁止未来/当天、隐式范围和覆盖已有发布版本。最多366个显式日期,逐日顺序生成;不初始化应用调度、不运行检测/通知,结果标记`backfill-current-source`与实际sourceAsOf,页面标明“事后补建”。本轮只在隔离库验证,线上补建未执行。
发布时需先核对源数据覆盖、实际报备历史与存储空间,生成影子日报并对账,至少为常用D准备此前30日,再切换页面;缺失日期保持可见缺口。新版本/旧版本表行均保留,不自动清理。回退应用保留新增表与冻结版本,但旧应用将恢复旧历史查询行为,不是业务等价回退。本轮没有推送或部署,也没有声称线上CPU已下降。
### 8.3 已执行证据及限度
- 真实PostgreSQL16隔离集群127.0.0.1:16435,空库完整迁移;`verify-signature-analytics.mjs`覆盖日报三日、冻结、跨日去重、2/3/4片、缺片、旧无分段回执、耗时加权、超出页码、重复认领、旧fence拒绝、失败回滚/重试及规则快照。验收数据只在本机专用`cmpp_qa_signature_*`库,脚本拒绝其他主机/库名。
- `verify-uplink-matching.mjs`真实事务验证重复事件、应用归属、供应商MO ID保留、并发人工认领和通知存储故障回滚。故障注入仅模拟存储错误;实际数据/事务/通知意图均为PostgreSQL,未调用外部Gateway发送。
- 真实完整Nest应用、PostgreSQL和隔离Redis,生产构建页面在1600×1000、1366×768、390×844打开/切TAB/查询/刷新,页面异常0;HTTP正常查询200、匿名401、非法维度/分页400。浏览器认证会话仅内存传递,不保存密码、令牌或storageState。Redis本机为5.0.14.1,有BullMQ建议6.2警告;本轮未将其视为生产队列能力验收。
- 前端全量35文件/163项、API全量78套/842项;类型检查、构建、代码结构、格式、ESLint、CSS治理、样式和Bundle门禁记录在testing-progress。Gateway代码未改,不重复冒称实发/协议验收。
- `benchmark-signature-analytics.mjs`对照4eb7b16原活动聚合:30000条消息、600维度,本地热缓存旧逐维度10343.912ms,新批量624.940ms,减少93.96%accepted计数逐维度相等。EXPLAIN原单维度实际源表访问30113行,按600维度估算约1806.78万;新批量60039行。旧总行数是样本外推,不是全批逐SQL实测;均为热缓存,未产生临时磁盘块。这里只证明活动聚合环节,整个日报+365日检测批次的CPU、p95、内存、线上队列影响、真实高密度回执规模仍须部署前/后专项验收,不能宣称全部性能目标已达标。
- 本机原始日志、截图和故障证据在忽略目录`.local-data/signature-optimization-20260917/`。初次迁移目录未生成SQL、测试通道缺carriers、断言漏计样本、临时账号缺email等失败已修正并保留原日志,不用最后成功覆盖失败历史。
@@ -193,3 +193,11 @@
- 第4至10步可回滚到能够读取运营商集合和历史任务的兼容版本;出现双运营商通道后不得回滚到只识别旧单值的版本。
- 第11步严格门禁启用后,如出现真实发送资格异常,优先切回兼容双读版本,不回滚或覆盖已经产生的新业务数据。
- 任一阶段不得为验收发送、补发或重投真实短信,不修改真实通道账号、密码、启停状态、企业余额或客户连接,除非另获明确授权。
## 9. 2026-09-17 性能与签名质量日报改造(本地实现,待发布)
权威增量设计见[签名退网检测与四页查询优化方案](signature-quality-optimization-plan-20260917.md)。实施后替代本文第6节、第7节第15步中检测快照直接作为活动查询、当前通过任务决定历史行及前端全量筛选分页的方式;保留D-1~D-30日期列、原去重单位、规则/抑制和预警历史。四TAB独立查询,日报T实时/T-1~T-3凌晨刷新/T-4起冻结;活动日报与当时预警判断分离。原正文是历史实现描述,本节及专项方案的本地实施状态见下方补充,线上未发布。
### 2026-09-17 实施状态补充
上述专项方案已完成本地代码与隔离验收,具体表结构、03:00三日刷新、事务边界、恢复、离线补建及性能测量见专项方案第8节。历史正文保留为旧实现说明;尚未推送或部署,线上依然是旧行为。
+37
View File
@@ -5614,3 +5614,40 @@ RC-01/02/04/08/09/10/11目前仅部分本地证据:三段齐段、重复与双
### 2026-09-16 五项整改最终测试证据索引
TC-RC-20260916-0112按[测试交付记录](release-20260916-test-completion.md)逐项区分真实目标链路、隔离PG故障验证及未执行场景,不能整组标全部通过。两段/三段/四段、补发成功/失败退款、20HTTP通知及9CMPP目标、503/超时自动恢复、无客户回执目标和无租户通道测试均有真实证据;全部逐断点退出/72小时/历史组合/旧版恢复/等负载性能对照未完成。TC-RC-13修复前真实TCP复现,修复后5000次即时响应及测试环境新增6条18分段无补发通过。五项运营页面检查包含三尺寸、请求失败保留真实快照与恢复、恢复告警不自动消失及人工清除审计。详细计数与测试资源收尾以交付记录为准。
## 2026-09-17 签名质量日报与退网检测优化验收(本地覆盖,环境项待执行)
设计见[优化方案](signature-quality-optimization-plan-20260917.md)。以下全部为计划用例,本轮只读取证不等于用例通过;不沿用此前短信测试授权。既有TC-ANALYTICS、TC-SIGNATURE-RETIREMENT保留历史记录,与本轮替代口径冲突时以本节和方案为实施目标。
| 编号 | 场景 | 验收要求 |
|---|---|---|
| TC-SQA-20260917-01 | 四TAB切换、不同日期/过滤/分页、慢响应及失败 | 仅当前TAB查询;条件与结果互不覆盖;企业请求不返回通道集合,反之亦然;隐藏TAB不重查 |
| TC-SQA-20260917-02 | 未报备TAB编辑日期后分别点顶部/卡片查询,再翻页 | 两个查询入口均应用当前全部草稿条件;翻页只用已应用条件,不串旧日期 |
| TC-SQA-20260917-03 | 今天查询质量和未报备 | 真实业务库一致性聚合,响应标记live及截止时间;不触发生成/检测/通知;过午夜由服务端确定自然日 |
| TC-SQA-20260917-04 | T-1/T-2/T-3迟到回执后再次凌晨生成 | 三天均更新日报,页面只读发布版本;失败保留旧版并标记;同一版本列表、概览、抽屉一致 |
| TC-SQA-20260917-05 | T-4及更早日收到迟到回执/补登记/更名 | 原短信事实可按原业务规则更新,冻结报表内容摘要不变;历史行和名称不依赖当前报备状态 |
| TC-SQA-20260917-06 | 热力图D为今天/历史日,跨月年及闰日 | 严格显示D-1~D-30,不出现D当天;每格按真实T判断冻结;未来D拒绝 |
| TC-SQA-20260917-07 | 有效零数据、缺报表、无报备资格、部分日期失败 | 四种状态明确区分;缺口不能计作已确认零或触发回查明细;部分30日合计明确不完整 |
| TC-SQA-20260917-08 | 短信跨日/跨通道补发,同通道多次accepted | 企业窗口按业务消息去重、通道按消息+通道去重;窗口不简单累加每日distinct数;质量业务日与尝试日分别核对 |
| TC-SQA-20260917-09 | 2/3/4段、缺片、乱序重复、失败与历史无分段记录 | 完整成功才算送达;未知保持未知;去重和旧回执兼容正确,耗时分子分母及成功率口径不漂移 |
| TC-SQA-20260917-10 | 两worker、租约失效、旧worker恢复、重复执行 | 同scope/date原子认领,旧fence不能发布;批次断点恢复,已完成不重新扫描,周期/快照/意图事务一致 |
| TC-SQA-20260917-11 | 候选生成部分失败、发布时跨入T-4、漏跑三日 | 旧版原子保留,跨冻结边界拒绝覆盖;缺口进入明确历史补建流程,不自动无限回算 |
| TC-SQA-20260917-12 | 04:00日报未好、08:00检测未完成、历史日报刷新 | 检测/通知按依赖等待且告警,不用半批发通知;恢复幂等,不重发历史消息/Webhook,不改已发正文 |
| TC-SQA-20260917-13 | 热力图大数据过滤排序分页及一类TAB失败 | 数据库先过滤并按完整30日合计排序再分页;只返回当页单类维度;另一TAB可独立查询;响应最多100×30格 |
| TC-SQA-20260917-14 | 真实接口鉴权/越权、非法日期和分页参数 | 401/403正确,tenant/application可见范围生效;无匿名生成入口;缓存不越权;SQL参数化 |
| TC-SQA-20260917-15 | 同规模新旧方案性能与准确性对照 | 所有指标对账;记录扫描行/块、任务耗时、进程CPU、API内存/响应量与队列影响;目标扫描下降80%、耗时下降50%,未达到不得冒称达标 |
| TC-SQA-20260917-16 | 历史补建、切换/回退和真实浏览器 | 历史覆盖/来源标明,无伪造冻结时间;不重发预警;三尺寸1600×1000/1366×768/390×844覆盖进入、刷新、路由、筛选、分页、详情、失败与权限;发布按后续授权执行 |
### 2026-09-17 本地执行覆盖与线上未执行项
TC-SQA-0114:真实隔离PG覆盖核心日期/日报/长短信/事务/分页路径,真实HTTP覆盖200/401/400,组件回归与三尺寸浏览器覆盖独立TAB。详细断言见`tools/testing/verify-signature-analytics.mjs`,不能将各用例全部边界都标成通过:真实08:00外部Webhook、旧回退版本、跨午夜运行过程、线上权限配置与365日大批次仍未执行。TC-SQA-15只完成30000消息/600维度活动聚合对照,整机CPU/内存/队列未测;TC-SQA-16只完成隔离历史补建、三尺寸页面,真实环境切换/回退未执行。
| 用例 | 操作 | 验证结果 |
|---|---|---|
| TC-MO-20260917-01 | 同手机号同应用多条accepted、共享接入号多应用 | 按应用归并;有唯一发送证据可匹配应用,不强填原短信 |
| TC-MO-20260917-02 | 第三条消息属另一应用、其他通道、接收后发送 | 保留真实歧义;通道/时间边界参与过滤,messageId不能跳过证据 |
| TC-MO-20260917-03 | 重复事件并发入库 | 真实PG仅一个上行和一份通知意图,供应商ID保留 |
| TC-MO-20260917-04 | 两个候选同时认领 | 真实PG仅一个应用成功,另一个拒绝,通知仅一份 |
| TC-MO-20260917-05 | 通知存储失败后重试 | 真实PG整笔回滚,事件重试恢复;未执行外部发送 |
历史159条认领/重新投递、真实供应商MO扩展号码及最终客户收取未执行,不沿用此前测试发送授权。
+28
View File
@@ -5109,3 +5109,31 @@ CUA本轮可用,实际后端文档三尺寸1600×1000/1366×768/390×844无页
标准工具最终deployed-needs-review仅提示业务数量变化:119597/130835→119603/130841,精确对应补测6条/6次,已人工对账;版本/资源/服务及日志检查通过。工具businessAcceptance未自动更新,不改报告伪造完成。全部证据、原失败、耗时、恢复点、磁盘增量/治理未完成项及矩阵未覆盖项见[交付验收记录](release-20260916-test-completion.md)。预生产未操作,未做性能容量对照、真实72小时等待或全部逐断点/旧版恢复演练。
本轮专用应用/接口/凭据/Webhook和两个模拟通道已停用,模拟器/隧道/监听器/临时hosts/本地隔离PG停止或撤销;短信账务及审计数据保留。61项旧工作保护核对完成,共享文档/metrics只增本轮内容。最终收尾文档及测试日志标签修正单独提交推送,不重启应用。密码及会话不进入Git。
## 2026-09-17 签名退网与四TAB查询优化方案(未实施)
- 授权:只读核查并编写方案。main与实际远端4eb7b16、暂存区空;09:37预生产实际010ba32。原50项已有修改/草稿继续保护,不纳入本轮交付。
- 已核查代码、线上编译入口、PG检测结果/真实索引及本会话CPU取证:04:00:0104:04:07产生5831条检测,逐维度重复扫描与CPU窗口高度吻合,缺历史进程CPU/SQL采样不能唯一归因。当前TAB状态独立,但企业/通道各自调用同一全量heatmap;质量及未报备所有日期实时查库;活跃度检测快照不做近三日刷新,历史又受当前报备维度影响。
- 用户确认每天凌晨刷新T-1~T-3;热力图保留所选日D之前30天,不新增当天列。方案明确实际T和D、T-4冻结、迟到回执、历史维度/未报备判定冻结、生成失败/缺口、批量聚合/索引验证、任务租约/fence、跨日去重及预警通知分离。
- 新增[优化方案](signature-quality-optimization-plan-20260917.md),向既有签名清退设计追加替代关系,需求及TC-SQA-20260917-01~16同步,全部实现验收用例待执行。取证脚本与结果在忽略目录.local-data/cpu-20260917,不含认证秘密。
- 本轮仅执行文档路径/编号/规则一致性及diff检查;未执行业务回归、迁移、重算、登录后四TAB真实HTTP/浏览器验收或新旧SQL性能对照。没有把既有组件mock测试当本轮真实功能通过。
- 本地修改:五份文档;业务代码无改动。本地提交、推送、测试部署、预生产部署:均未执行;未发送/补发/重投短信、未改余额/通道/客户配置或触发外部通知。
## 2026-09-17 上行待认领只读诊断(未修复)
- 预生产010ba32共253条上行:159待认领、92已匹配、2未匹配。147条共享接入号多应用直接返回ambiguous,其中143条接收前72小时实际仅有一个候选应用的同通道accepted发送;另12条手机号多记录全部同应用。已用当前线上编译匹配函数与真实PG只读事务复现两个分支,未启动调度/写入/投递。
- 明确应用归属与原短信唯一关联不能混为一谈;155条可进一步收敛不等于可直接自动认领。另发现窗口按处理时刻、无上界/通道限定、路由take10先截断等风险;现有89条自动关联只读边界核对未见手机号不符/未来关联/缺同通道accepted证据。
- 详见[上行匹配诊断](uplink-matching-diagnosis-20260917.md),区分已证实根因和未发生证据的风险;取证在.local-data/cpu-20260917/uplink-*。未执行HTTP/浏览器验收、写入故障注入或代码修复,未认领/发送/重推上行。
- main/实际远端4eb7b16、暂存区空;已有保护项及前轮签名质量方案保留。新增诊断、追加进度,执行文档路径和diff检查;本地提交、推送、测试部署、预生产部署均未执行。
## 2026-09-17 上行归属修复与签名质量日报优化(本地实现)
- 授权:修复上行待认领问题、执行`signature-quality-optimization-plan-20260917.md`并本地提交;本轮不推送、不部署,不操作历史上行认领/重推,不执行真实短信和外部通知。开始及提交前main/实际远端均4eb7b16,暂存区原空;65项已有修改/草稿已做摘要及副本保护。
- 上行:共享接入号不再提前判歧义;receivedAt前72小时同通道accepted记录按应用归并,原短信不唯一则不填原短信编号。供应商MO编号独立保存;事件入库/候选/通知、人工认领/通知分别事务化,重复事件和并发认领串行核验,通知只创建耐久意图。既有159条待认领没有自动认领/重投。
- 日报:六个新增表和版本/日期复合外键;03:00三日刷新,T-4起冻结,历史页面不回查明细;缺口、刷新/失败、截止时间和事后补建来源可见。企业/通道新接口只返回各自分页维度;四TAB查询/日期/筛选/错误独立,保留D-1~D-30。退网批量accepted窗口去重,规则/日报版本首次认领冻结,失败重试沿用;检测与通知等待完整上游批次,不重复生成历史预警。
- 真实验收:本机独立PostgreSQL16端口16435,新库108项迁移通过(含单独非事务并发索引)。`verify-signature-analytics.mjs`12组验收通过:当天/历史数据源、跨日去重、2/3/4段、缺片、旧无分段明确回执、加权耗时、三日刷新/冻结、旧版本保留、页码越界总数、失败回滚/重试、双worker/fence与规则快照。`verify-uplink-matching.mjs`3组通过:重复事件一份CMPP及一份HTTP通知意图、并发认领、存储故障整笔回滚并恢复;没有启动外部通知消费者。
- 前端:生产构建,完整真实Nest应用+PG+隔离RedisEdge/Playwright在1600×1000、1366×768、390×844检查首次、四TAB查询、刷新,补充历史日报、抽屉、企业服务端筛选、TAB日期独立和请求失败仍显示上次真实结果;28个相关请求、页面异常0。匿名401、非法分页/维度400、正常查询200。Browser插件/技能未提供,沿用用户已授权的独立Playwright;会话只在内存传递。
- 自动门禁:API78套842项、前端35文件163项通过;API/前端类型与生产构建、结构检查、ESLint、Prettier、CSS治理/样式、Bundle预算通过。ESLint仍有发送链路既存测试27项any警告,无error;构建既有chunk提示在预算内。覆盖率门禁见下方补充;Gateway代码未变,无本轮Go/实发/性能容量结论。
- 性能:30000消息/600维度活动聚合,旧10343.912ms,新624.940ms,热缓存下降93.96%accepted逐维度一致。原EXPLAIN单维度源访问30113行×600外推18067800,新批量60039;样本外推不冒称全批实测。未测整机CPU、365日真实大批次、峰值内存、p95和短信队列;不能据此宣布预生产CPU问题已完全解决。
- 原始证据保留于忽略目录`.local-data/signature-optimization-20260917/`integration-6、uplink-real-3、browser-5、performance-1及最终门禁日志。前面失败(迁移SQL未生成、缺carriers、测试断言漏计新增样本、账号缺email、浏览器按钮名称/模糊标签定位)已修正后重跑;不删除原失败日志。Redis5.0.14.1的BullMQ版本建议及PG驱动在嵌套关系读取时的并发query弃用提示保留,未改依赖版本。
- 文档:优化方案第8节、上行诊断第6节、需求/测试用例/清退设计同步。本地提交仅纳入本轮代码、两项迁移、验收脚本及相关文档精确新增段落;已有metrics、版本、部署和发布工具修改保留。推送:未执行;测试部署:未执行;预生产部署:未执行。历史日报补建、真实外部MO投递、线上权限/供应商扩展码、午夜冻结过程、现场恢复/回退及完整容量指标未验证,按后续授权实施。
@@ -0,0 +1,76 @@
# 上行短信待认领只读诊断
核查日期:2026-09-17,北京时间。状态:诊断完成;2026-09-17本地修复与隔离验收见第6节,线上未认领、未投递。范围为预生产既存上行和当前匹配代码,不沿用测试环境发送/补发授权。
## 1. 基线与结论
本地main及实际远端均为`4eb7b16d122da14f921093716d4ca1ed390d9e4c`,暂存区为空。预生产实际运行`010ba3216889032a6160cdb14d8536b616ae7102`。已有版本、metrics、发布工具、部署及前轮优化方案等修改全部保留。
当前共253条上行:159条ambiguous(页面“待认领”)、92条matched、2条unmatched。“待认领”不是接收失败:上行已入库,但没有确定应用归属,当前不会按正常已匹配路径推送给客户。
| 待认领原因 | 数量 | 真实数据复核 |
|---|---:|---|
| 接入号匹配多个应用 | 147 | 143条在接收前72小时仅有一个发送应用,且属于原候选应用、具有同上行通道accepted提交;4条在此限定口径下无下发记录 |
| 手机号窗口有多条下发 | 12 | 保存的两个候选全属同一应用/企业;完整时间窗口复核也只有一个应用,且有同通道accepted提交 |
因此155/159条具有“应用归属可以进一步收敛”的真实证据,不代表155条均能唯一确定被回复的具体业务短信,也不构成直接历史认领/投递授权。4条无证据记录保持未决,不因为某条路由现在有效就推断历史归属。历史源记录可能变化或被治理,当前回查不能替代完整历史快照。
接入号多应用147条分别位于移动物业-富泷78、三网物业-百信互动52、三网物业-铁布衫15、赛邮行业-王斯评中转2;它们的destId均等于当前通道srcId。12条手机号多记录位于三网物业-百信互动10、联电物业-富泷2。9月16日有31条待认领,9月15日12条。
## 2. 已确认根因
匹配实现:`api/src/send-chain/send-downstream-delivery.service.ts``resolveUplinkMatch`。页面`src/apps/admin/AdminSmsUplinkRecordsPage.tsx`将ambiguous直接映射为待认领,不是前端计算错误。
### 2.1 共享接入号过早返回
当前顺序为messageId精确匹配→接入号路由查应用。接入号得到多个active应用后,立即返回ambiguous与应用候选,完全不执行后面的手机号时间窗查询。
这与需求中“仍无唯一应用时按手机号和最近下发时间窗口匹配”的描述存在差距。共享通道接入号只表示有多个可能客户,不足以否定“该手机近期仅被其中一个客户发送”的进一步证据。
真实只读复现记录`cmu48o30s00fo34nkyp3phwt9`:运行中的编译类只调用`channelRouteRule.findMany``smsApplication.findMany`,返回3个应用候选;没有查询短信记录。同手机号接收前72小时实际只有一个应用的同通道accepted发送。
### 2.2 将短信记录歧义等同应用归属歧义
手机号分支取最近两条消息,按记录数量判断唯一或歧义,没有先按tenantId/applicationId归并。一个应用给同一手机发送两次,也被阻断客户归属。
真实只读复现`cmtmq3vef0ioyeankps3d80yl`:运行类返回两条phone_window候选,但distinct application数为1。12条同类存量均符合这一情况。可以有唯一客户归属而没有唯一原短信,应分别表达,不能为了绑定客户随意填最近一条messageRecordId/messageId。
## 3. 代码边界风险(不等于已经发生错误投递)
1. 时间窗下限由`Date.now()`计算而非事件receivedAt,且没有`submittedAt <= receivedAt`上限。延迟消费/故障恢复可能错过真实历史下发,或把上行之后的发送纳入。
2. 手机号回查未限定实际发送通道/供应商边界,也未校验真实accepted提交;仅按message.submittedAt查。不能在修复时直接扩大自动匹配,必须先收紧证据范围。确需同供应商跨物理连接兼容时,另核对账号、主机、端口、协议等边界,不能跨任意通道匹配。
3. 路由查询`take:10`发生在应用去重之前、没有稳定排序:前10条若同属一个应用可能漏掉其他应用,造成假唯一或候选遗漏。手机号`take:2`也只适于证明多条记录,不能据此证明窗口内只有一个应用。
4. 接入号只与通道静态srcId精确比较,没有完整核对实际发送号码/应用扩展码。其差异会进入宽泛手机号兜底,不能用当前路由代替历史发送证据。
5. messageId精确分支未同时验证手机号/通道来源;Gateway普通MO通过packet Msg_Id尝试查本地command。供应商上行ID应继续独立保存为gatewayMessageId,不能把它当已证明的原下发ID。本轮未发现碰撞或错误关联实例。
6. 上行入库、候选写入与投递分别进行;eventId已存在即返回,部分失败恢复可能留缺候选/缺投递。人工认领是先读状态再更新,未见条件更新原子竞争保护。属于附带发现的可靠性风险,本轮未做故障注入或并发写入复现。
对现有85条“手机号窗口唯一匹配”和4条“messageId精确匹配”执行只读边界核对:关联消息均存在、手机号一致、关联提交时间不晚于上行,均存在此前同通道accepted提交。未在这89条中检出上述明显异常;此结果不证明所有潜在风险不存在。
## 4. 建议修复方向(未实施)
- 分开“应用归属”与“原短信关联”。限定证据范围后应用唯一即可确定客户;具体消息仍多条时messageRecordId/messageId留空,并保留真实候选及原因。
- 共享接入号多应用时继续在候选应用中,结合手机号、上行接收时刻之前的72小时以及真实发送通道/接入号收敛;候选间有冲突仍待认领,不按最后一条或最高分随意选。
- 查询完整的distinct应用集合或以安全的唯一性检测查询证明唯一;展示候选可以分页,唯一性判断不能先截断。时间窗配置须校验正数有限值,上行receivedAt须校验合法性。
- 原始供应商MO ID、平台业务ID保持不同语义;无可信原短信关联不得伪造业务messageId。
- 配套原子入库/认领、通知意图与故障接续;客户查询仍严格隔离应用,ambiguous不得泄露给候选客户。
- 历史159条先生成只读建议清单,核验保留数据、有效应用、完整号码/通道证据及投递历史,再在明确授权后决定是否认领及是否推送。修复代码不自动批量认领、不自动把旧上行重新入队或投递。
## 5. 证据、复现限制与后续验证
忽略目录`.local-data/cpu-20260917/`中的`uplink-audit.json``uplink-correlation.json``uplink-reproduce.json``uplink-boundaries.json`及同名mjs为本机证据;输出只含统计、内部记录ID和必要通道标签,不导出手机号、正文或凭据。
复现直接加载预生产当前编译的`SendDownstreamDeliveryService.resolveUplinkMatch`,使用真实PG只读事务(815秒statement_timeout),不启动Nest生命周期,不调用handleUplink/claim/投递方法。仅在该独立诊断进程将Date.now固定到被查上行的receivedAt以复现旧事件分支,配置仍是当前数据库路由,不能宣称重建了当时全部配置。未执行管理HTTP或浏览器交互验收。
后续修复验收至少覆盖共享接入号+唯一应用、同应用多消息、多应用真实冲突、无记录、超过10条路由/超过2条消息、延迟消费/跨日、扩展码、跨供应商隔离、MO ID不冒充MT ID、事务中断、重复事件、并发认领、无重复投递及租户隔离。真实发送/推送只能在后续明确授权的隔离范围执行。
本轮业务代码、数据库和线上配置均未修改;仅新增诊断及追加进度记录,未提交、推送或部署。
## 6. 2026-09-17 本地修复
用户授权修复并本地提交。新增`api/src/send-chain/uplink-matching.ts`,共享接入号继续核对手机号在事件receivedAt前72小时的同通道accepted记录;按tenant/application归并,不以消息条数制造歧义;无take10/take2提前截断。messageId分支同时验证手机号和通道发送事实;收到的供应商gatewayMessageId独立保存。只能确定应用时不填写原短信ID,多应用/配置与事实冲突继续待认领。独占接入号无发送证据的原兼容规则保留;供应商扩展号码改写与跨物理通道归并没有扩大自动归属规则,需有真实协议证据后另行处理。
上行记录、候选和CMPP/HTTP通知意图纳入同一事务,沿用发送链路事务适配器;按事件ID事务锁和唯一键去重。人工认领按上行ID串行核验,只有一个候选可成功,认领与通知意图同事务;不在事务里执行外部推送,后续沿用已有耐久队列消费。已有事件直接返回,不擅自修补或重推历史半完成事件。
真实隔离PG验证:同应用多消息、第三条不同应用、接收前窗口、其他通道拒绝、原短信为空、重复事件只入一次/通知一次、两候选并发仅一次成功、通知存储失败回滚并重试恢复。自动化入口`tools/testing/verify-uplink-matching.mjs`及签名验收脚本;API全量回归见testing-progress。
原159条待认领保持原状;155条可收敛是取证结果,不是已经认领/推送。本轮未连接线上执行认领、重投或修配置,未推送/部署。历史建议清单、实际供应商扩展码、客户端最终收到MO和线上故障恢复仍未验证。