feat: enforce signature-scoped drainage authorization before SMS submission

This commit is contained in:
hectorzhao
2026-09-10 13:29:04 +08:00
parent 5bcdbb2a03
commit 0c3f820cc9
35 changed files with 2769 additions and 791 deletions
+179
View File
@@ -0,0 +1,179 @@
# 引流信息拦截与通道报备匹配方案
日期:2026-09-10。状态:已按后续授权实施,本地代码与隔离数据库/API验证完成,提交及测试发布状态见测试进度最新记录。授权为修改、本地提交、测试环境部署;未授权推送、预生产部署或真实短信发送。第2节保留设计阶段基线,第10节为实施事实。
## 1. 结论、范围及设计关系
该需求可实施,属于发送链路与风控规则变更,不是增加一个页面开关。复用已有引流资料、检测规则、签名报备、客户回执和费用处理能力;补齐统一规范化匹配、多引流关联、通道资格交集和可靠拒绝闭环。必须同时覆盖普通与批量快速入口、长短信合并、定时发送、审核释放及最终通道路由,不能只修改 resolveDrainageInfoMatch。
本方案是 [风控审核方案](phase-6-risk-review-plan.md) 的专项补充,关联 [发送链路设计](phase-4-send-pipeline-redesign.md)、[通道报备方案](phase-4-channel-reporting-plan.md)、[计费方案](phase-5-billing-plan.md)。实施生效后,替代 [需求](first-version-development-requirements.md) 中现有“本期只识别、记录、查询和统计”“引流审核/报备不得拦截”的规则,以及对应旧用例中的允许发送断言;历史实施记录保留,检测、高亮、统计能力继续保留。旧规则目前仍是代码现状,写方案不等于已启用拦截。
不新增独立引流登记入口,不自动添加资料或审核通过,不修改短信内容,不改企业余额/路由配置,不恢复关闭账号,不执行发送/补发/重新入队。后续实施、本地提交和测试部署以用户明确指令为准;历史短信重发仍未授权。
## 2. 当前证据与缺口
核验基线:本地 main / HEAD 为 5bcdbb2a03637b1ab1aeda59a4e1db9cc9fc4243;实际 ls-remote origin main 为6d63eb5452ffc7c802960d044bf598cc8646564d,本地领先1提交。暂存区空;保护原有19个跟踪修改和全部未跟踪文件。
| 现有证据 | 代码现状 | 本次缺口 |
|---|---|---|
| api/src/send-chain/drainage-content-detection.ts | 检测副本做NFKC及分类清洗,保留原文位置;记录多个matches;规则缓存30秒;最多20000字符/50命中,有truncated标记 | 检测结果未统一成为发送资格;截断和规则故障不能作为无引流放行 |
| api/src/common/drainage-target.ts | 登记值做trim和格式校验,未形成与检测一致的类型化规范键 | 02177882277与021-77882277在旧匹配中不等价 |
| api/src/send-chain/send-inbound-entry.service.ts | 普通路径使用原文includes,取最长单条;微批路径另有同类实现 | 未命中不拒绝;多项匹配不完整;两套路径易出现差异 |
| api/src/send-chain/send-chain.helpers.ts | drainageRejectionReason明确废弃并返回undefined | 不能简单恢复旧函数就宣称全入口拦截完成 |
| api/prisma/schema.prisma | SmsDrainageInfo含tenantId/signatureId/applicationId/url/auditStatus/materialVersion;消息只有单个drainageInfoId,另有drainageDetection JSON | 单个外键不足以表达多目标、多个匹配资料和每通道资格证据 |
| api/src/send-chain/send-gateway-submit.service.ts | 批量路由读取reportType=signature的approved任务;最终签名检查也有独立方法 | 需接入引流报备资格,不能把存在方法当成所有执行路径均调用 |
| api/src/send-chain/send-downstream-delivery.service.ts | recordCmppFailureReceipt写failed/undelivered/REJECTD和平台回执,再调用queueFinalReceiptDeliveries | 按提交来源区分:CMPP沿用既有分发,非CMPP拦截不推送;需验证长短信及请求回执标记;当前先查再写与已有回执直接返回不能直接宣称并发/崩溃可靠 |
| api/src/sms-config/drainage.service.ts、admin-sms-config.controller.ts | 企业签名下新增引流;真实资料审核及通道报备目标API已存在 | 复用关联,不能使用页面三网汇总、材料齐全或跨签名同值代替发送授权 |
证据为当前源码、Prisma模型与真实远端Git读取。本轮未启动应用服务、连接业务数据库或查询远端API;不作当前数据规模、报备覆盖、运行版本或线上行为结论。PostgreSQL/Redis/MinIO/Gateway状态及数据库实际索引需在实施前只读复核,历史交接数据不代替此次实测。
## 3. 业务判定规则
### 3.1 两道资格检查
1. 先沿用现有认证、企业应用和签名解析,签名必须属于当前企业及应用的有效授权范围;客户端传入的signatureId/drainageInfoId不能作为放行依据。
2. 对最终完整短信内容执行检测,包含模板变量替换及CMPP长短信合并后的内容。未识别出引流且检测完整时,继续原发送流程。
3. 识别出引流后,每个不同目标均须在当前有效签名下找到匹配引流资料。仅其他签名、其他企业或其他应用登记的同值不能借用。
4. “已添加”解决关联存在;平台审核和通道报备是另外的资格。用户已确认沿用既有资料审核链:deleted/pending/rejected不能作为可发资料,只有auditStatus=approved且材料有效的资料可参与通道匹配。仅添加但未审核通过不能发送。
5. 最终通道须同时满足应用当前路由、号码运营商、签名报备及每一个引流目标的报备资格;再走既有连接、地区、优先级、限速、额度等判断。引流通过不豁免其他门禁。
6. 一条短信包含多个目标时全部检查;同一目标重复出现只判定一次,保留全部原文位置。任一目标无对应资料即拒绝整条业务消息,不允许只匹配第一项/最长项。批量发送按每条业务消息记录判定,不牵连其他合法消息。
### 3.2 规范化与原文保护
实现共享、带版本的纯函数,登记侧与检测侧使用同一规则,保留类型、原值、规范值、原文起止位置。NFKC及清洗仅作用于比较副本;SmsMessageRecord.content、模板渲染结果、编码、计费长度、CMPP提交字节均使用原文。高亮位置仍指向原文,覆盖全角和代理对字符。
- 电话类:NFKC后清洗空白、横线及现有电话检测定义的干扰标点,再按完整号码比较。021-77882277与02177882277等价;不使用号码子串contains,不把77882277与02177882277自动等价。国家码、区号和分机不擅自补齐/删除,首期只有明确干扰字符清洗,不做号码归属推断。
- URL类:NFKC及明确的全角标点映射,清理零宽干扰字符的具体白名单需固定测试;保留host中的横线、点和path/query的结构字符,不沿用电话清洗。不把跨空格的两个目标拼成一个URL。协议/host按解析结果处理大小写,path/query保留大小写;不默认解码百分号或把+当空格。
- 邮箱仍沿用现有排除规则,不能从邮箱中截出域名或数字冒充引流;其他检测分类未经定义规范化和匹配策略时视为“不支持判定”,不悄悄忽略。
- 内容超限、命中截断、规则不可用、无有效规则或正则执行异常:不得以空matches判为放行;记录技术校验失败、停止向供应商提交,走有界重试/失败告警,区别于“客户未报备”。不得新增无限扫描或不受约束正则。
### 3.3 URL域名层级匹配(用户已明确)
用户补充确认:报备父域名后,其下三级、四级及更深子域名可使用,后续路径和参数不受影响;lisglo.cn.evil.com不是lisglo.cn的子域名,不允许。此规则取代此前“任意字面包含”的初稿,不再将URL包含边界列为待确认项。
提取完整URL目标,使用URL解析器取得hostname,不对整条URL做includes。缺少协议的目标仅在解析副本中补协议;规范化NFKC、host小写、国际化域名统一转换及可选单个末尾根点后,以标签边界判定:
~~~text
candidateHost === registeredHost
|| candidateHost.endsWith('.' + registeredHost)
~~~
报备lisglo.cn,等于该域或任何层级子域均匹配;报备sms.lisglo.cn,则只匹配它自己及a.sms.lisglo.cn等更深子域,不反向授权lisglo.cn或其他兄弟子域。不要靠字符串中的点数推断“一、二级域名”,以实际登记的合法域名作为授权根;不允许登记cn、com、com.cn等公共后缀来授权无关企业域名,实施时使用可维护的公共后缀数据校验,不能仅维护这几个示例。
对于纯域名报备,协议、端口、路径、query参数及fragment不参与域名资格比较;这仅表示引流域名匹配,仍受已有发送规则约束。例如https://a.sms.lisglo.cn:8443/1yhf7e87?x=1#top可匹配lisglo.cn。userInfo必须由解析器与hostname区分,不能将@前的内容当主机名;畸形URL不通过解析,不使用正则截出其中合法片段放行。
| 登记域名 | 短信中的目标 | 域名匹配结果 |
|---|---|---|
| lisglo.cn | lisglo.cn | 通过 |
| lisglo.cn | sms.lisglo.cn/1yhf7e87 | 通过 |
| lisglo.cn | a.sms.lisglo.cn/path?x=1&y=2#top | 通过 |
| lisglo.cn | lisglo.cn.evil.com | 不通过 |
| lisglo.cn | evillisglo.cn | 不通过 |
| lisglo.cn | evil.com/?next=lisglo.cn | 不通过 |
| lisglo.cn | https://lisglo.cn@evil.com/path | 不通过 |
| sms.lisglo.cn | a.sms.lisglo.cn?x=1 | 通过 |
| sms.lisglo.cn | lisglo.cn或other.lisglo.cn | 不通过 |
检测必须保留完整目标边界,不能把lisglo.cn.evil.com识别成lisglo.cn后再比较;参数内域名也不能成为另一个可独立授权外层URL的匹配结果。检测器与解析器均需相应失败回归。任何“通过”仅指域名关联匹配,尚须审核有效及相应通道报备通过。
现有资料可登记带协议/路径的完整URL:不能在未说明的情况下把历史路径级报备自动扩大为整个host授权。建议新增/确认纯域名报备使用上述规则;历史带路径资料标注为兼容待处理,先盘点,再决定保留路径限定或经业务确认升级为域名级。路径级资料的迁移策略仍待明确,但不影响本次已确认的纯域名报备行为。IP地址不是域名,不适用子域后缀规则,若支持则按完整规范IP相等。不会访问URL、跟随重定向、解析短链或发起HTTP探测。
## 4. 多目标与通道选择
对目标t求出同一签名下全部匹配且有效的资料集合 M(t)。允许多个匹配项,不再用最长匹配及更新时间任选唯一项;一个目标可由其中任一有该通道有效报备的资料证明,但审计必须记录实际使用的资料ID和报备任务ID。
设R是当前应用/运营商的可路由通道集合,S是签名已报备通道集合,D(d)是资料d已报备的通道集合:
```text
C = R ∩ S ∩ 对每个目标t求交集(对d属于M(t)求并集D(d))
```
示例:目标A可走通道1/2,目标B可走通道2/3,签名可走1/2/3,则仅可走通道2。不能把A与B分开送往不同通道,也不能拼接不同企业/签名的授权。
报备事实使用ChannelSignatureReportTasksignatureId一致、channelId一致、reportType=drainage、drainageItemId指向匹配资料、status=approved;不能只看签名任务或前端三网绿色状态。报备批准必须对应当前有效材料,资料修改/删除及waiting_review、pending、rejected、failed、abandoned均不能继续借旧通过记录放行。
报备配置边界(用户已确认):旧通道报备配置由用户负责调整,本次不迁移、不补齐、不推断旧记录应覆盖哪些运营商,也不自动重置报备状态。开发保证现有报备配置页面/API的查看、修改、保存、刷新和发送资格读取正常;按用户当前有效配置及通道支持范围判断,无有效资格则不发送。旧记录保持可查看、可操作,不因字段为空导致页面崩溃或无法保存;旧配置盘点/兼容改造不作为本次实施前置条件。
C为空时区分“引流未报备到可路由通道”和“报备合格但通道离线/限速”等情况,保留不同原因。选中失败、切换通道、自动重试都不得扩展到C之外;从未通过的通道不能成为兜底。
## 5. 处理流程、状态与回执
1. 各入口完成协议/认证校验并按既有耐久受理契约保存消息。入口受理成功与供应商发送成功分开;不能为业务拒绝同时返回不受理又伪造已受理的终态回执。
2. 用统一服务提取全部目标、规范化、校验签名关联并保存决策;必须在首次供应商提交前执行。定时任务到期、审核释放及已有排队消息均按生效策略重新检查,不信任旧单个drainageInfoId。
3. 规划通道时按第4节求交集;最终生成Gateway提交意图前复核资格版本,批量和单条路径共用实现。入口预检用于提前反馈,最终门禁为权威判定。
4. 各入口业务拒绝均保存消息failed和可读原因;CMPP来源再复用平台未送达回执路径,记录receiptStatus=undelivered、receiptRawStatus=REJECTD。非CMPP来源保留失败记录和任务进度,不为本次拦截新增回执记录或推送。建议内部原因码为DRAINAGE_NOT_REGISTERED、DRAINAGE_NOT_APPROVED、DRAINAGE_CHANNEL_NOT_APPROVED;这些是待新增内部码,不能直接把长字符串塞入CMPP固定长度字段,协议短码须核对现有映射并补兼容测试。
5. 消息状态、拒绝证据、费用结算,以及CMPP来源适用的回执投递意图需具备事务/耐久幂等;建议以messageRecordId+终态业务拒绝建立唯一键。已存在回执但尚未创建/发送下游投递时必须能恢复,不能因早返回丢失回执,也不能并发重复退款。
6. 用户已确认按提交来源沿用签名未报备行为:CMPP提交沿用现有失败回执分发、原请求标记、长短信关联和接收配置;非CMPP提交本次不推送回执,即使应用配置了HTTP回调也不得因引流拦截新建投递。实现必须在来源分支处约束,不能对全部入口无条件调用会生成HTTP投递意图的统一回执函数。CMPP来源现有配置允许的分发行为不另行改动;接收方离线等情况留真实投递状态,不伪报成功。不修改其他正常送达回执或上行推送功能。
7. 无供应商Submit不得产生供应商成本。入口尚未扣费则不扣;已扣/冻结的消息按既有签名/路由失败计费策略幂等退回或解冻。上线前对不同入口当前扣费位置逐一对账,不新建独立余额调整捷径。
8. 拒绝后补登记/补报备不自动释放已失败短信。回执重试仅恢复回执投递,不重发短信。短信发送、补发和重新入队须另有专项授权。
## 6. 数据、API与页面
复用SmsDrainageInfo和ChannelSignatureReportTask作为授权事实,不另建重复登记库。建议增加规范化类型、规范值及normalizationVersion用于索引和诊断,初期可由共享函数实时计算;是否落列取决于只读规模与执行计划,不在方案阶段执行迁移。历史原始url字段保留,规范键冲突先报告,不能自动合并、覆盖或继承另一条资料的报备状态。
消息需要多目标关系与不可变判定快照。建议新增SmsMessageDrainageMatchmessageRecordId、targetKey、匹配资料ID、实际采用的报备任务ID、carrier、channelId、资料/规则版本、decisionId),按一次决策与目标/资料建立唯一约束;未登记目标也需由快照保留,不能因无外键而丢证据。快照含原文位置、规范值、全部候选资料、最终采用项、原因、时间和策略版本,独立于既有drainageDetection检测JSON。现有drainageInfoId只保留兼容单目标历史展示,不能作为新门禁真相来源。
多引流会影响报备今日发送、引流质量和统计SQL:按message/submit ID去重后关联相应引流,单条短信可以归属多个引流,但首页消息数/客户分片/计费不得因此倍增。明确交叉归属统计不可直接相加,更新说明与查询,不在旧最长外键上冒充完整多引流统计。
复用接口:POST /api/admin/enterprise-signatures/:id/drainage-infos、PUT /api/admin/drainage-infos/:id、GET /api/admin/drainage-infos/:id/report-targets及对应client入口;继续服务器端租户/应用/签名校验。新增规范字段由服务端计算,客户端不得提交已通过判定或通道白名单。发送API不要求客户新增放行参数;消息详情API增加只读判定结果,历史未判定明确显示“未执行引流资格校验”,不默认通过。
企业签名管理仍负责登记/审核/报备;短信记录保留发送状态、原文和既有详情入口,在失败原因及详情展示具体未登记目标、对应签名、未通过通道原因。客户端只能看本企业证据,跨租户对象按不可见处理。不得输出内部凭据、完整路由配置或不相关客户资料。复用公共Table/Tag/Modal,错误/空态/加载/权限分别呈现,关闭方式沿用显式关闭规范;不改CSS和页面布局作为前置条件。
## 7. 一致性、性能与兼容风险
- 统一判断入口,CMPP微批必须按企业/应用/签名批量读取资料和批准事实,禁止按每号码×每目标×每通道做N+1查询;缓存以版本为键,仅用于候选,不以30秒旧缓存授予最终发送资格。
- 资料审核/修改/删除、通道报备撤销与发送意图创建需共同的锁或版本并发协议;建议同签名授权版本作为一致性锚点,所有写入口包括导入/批量状态变更都递增并参与校验。固定锁顺序,测试多Worker并发。
- 生成意图后到物理发送存在分布式窗口:已在Gateway队列但未发出的旧意图必须复核版本或取消,不能只在API检查后宣称“撤销即刻阻断”。实施需盘点Gateway/Redis提交消费者;若现契约不足,增加版本验证及耐久拒绝返回。已实际发出的短信不能撤回,不能伪造为未发送。切换生效时先暂停领取并处理在途意图,定义明确生效边界。
- 规则异常、数据库/Redis不可用应阻止提交并可恢复,禁止吞错放行;校验技术错误与客户未报备分开计数,避免误记客户违规。重试次数、超时沿用现有调度上限并留告警。
- 旧已终态消息不补判不回写;待发送、定时和待审核消息在生效后执行新判定;已有已发部分分片/提交尝试的消息单独列为在途,不能当首次拒绝统一全额退款。
- 实施按需核对规范化与表/索引规模;不开展旧通道报备配置盘点、自动迁移或补齐,旧配置由用户调整。可用历史内容离线评估,不调用发送入口,不修改短信状态。无覆盖率证据不承诺无影响上线。
- 回退到只识别旧版会绕过新规则。策略启用后回退须停止发送Worker/提交消费,保留决策与失败回执事实;不能因回退恢复已拒绝任务。启用/回退另行遵循标准发布入口和授权。
## 8. 实施拆分与验证成本
| 阶段 | 交付及依赖 | 验证重点 |
|---|---|---|
| A | 落实已确认域名规则、审核和按提交来源的回执规则;核对配置入口 | 保证用户可正常配置,不处理旧报备配置,不发送 |
| B | 共享规范化/多目标匹配及消息证据模型,历史兼容迁移 | 号码、URL、全角干扰、冲突、多项及原文不变;迁移回退 |
| C | 所有入口/快速批量/最终路由/重试接入;并发授权版本 | 多目标通道交集、撤销窗口、定时及审核释放、Redis与Gateway契约 |
| D | 终态、费用、可靠回执及详情/统计展示 | 幂等、崩溃恢复、CMPP回执/非CMPP不推送、租户隔离、统计去重 |
| E | 隔离环境全链路与回归,取得专项许可后发布 | PostgreSQL/Redis/Gateway模拟器、MinIO相关资料验证、浏览器与容量对比 |
复杂度中高,主要成本在多入口一致性、多引流数据模型和拒绝回执/费用闭环,单一正则修改不足。暂不承诺工期/TPS;完成阶段A后按实际消费者数量、存量数据和迁移规模估算。未来实施按测试计划运行API/前端定向及全量、类型/构建、格式/样式/包体门禁;涉及Gateway执行Go测试和vet,涉及迁移/发布执行对应门禁。真实发送模拟验收也须先获得明确专项授权,不能以测试方案存在代替许可。
## 9. 已确认事项、实施核查与完成标准
已确认:纯域名授权自身及所有层级子域,路径/参数不限制;资料必须已经添加到对应签名且平台审核通过;引流拦截参考签名未报备的处理。
以下用业务语言说明,不把内部数据兼容问题转成用户必须理解的审批项:
- 旧资料如果存的是https://lisglo.cn/app,而不是lisglo.cn,问题只是“它是否也允许lisglo.cn/other”。这与新登记纯域名后的子域/参数规则不同。建议按域名使用的目标统一考虑;实施先盘点实际是否存在这种旧资料,再明确处理,不能把尚未证实的数据情况当成阻塞。
- 旧通道报备配置由用户调整,本次只保证配置入口和保存后读取的资格判断正常,不处理旧配置。
- CMPP提交沿用签名失败回执;非CMPP提交不推送回执,是用户确认的正常规则,不是缺陷。将来如需非CMPP拦截回执,由用户另提需求。
### 9.1 本轮补核的签名未报备失败路径(源码证据)
send-gateway-submit.service.ts的批量failRouteBatch及单条路由失败处理均写消息failed和失败原因、释放费用预留。对于sourceType=cmpp,调用recordCmppFailureReceipt,使用ROUTE错误码,记录undelivered/REJECTD平台失败回执,再交由统一回执分发。普通路由原因可能为“无已报备通过且在线的可用通道”,其中包含签名资格/在线状态,不能把所有ROUTE失败都说成签名未报备。
CMPP回执分发按客户原提交及长短信分片的registeredDelivery标记:请求回执才生成对应CMPP投递;历史null按既有兼容默认处理。HTTP分发仍需应用可投递、HTTP回执配置开启及有效地址,沿用现有配置,不自动开启接口或添加地址。
现有非CMPP路由失败分支只刷新任务进度,没有调用统一失败回执方法。用户已确认这是预期行为,撤销初稿将其列为“缺口/闭环修复”的判断;本次不修改该行为。引流拦截同样按提交来源分支:全部记录失败及原因,仅CMPP来源进入适用的既有失败回执流程。此处是源码核验,未执行真实发送复现。费用释放、记录、CMPP回执队列创建与成功送达分别验收,非CMPP验证无新增投递。
验收用例见 [系统功能用例](system-functional-test-cases.md) TC-DRAINAGE-GATE-0116,全部待实现/待执行。验收需要保留原文和发送字节对比、PostgreSQL拒绝/授权/费用事实、供应商Submit为0的证明、CMPP来源适用的下游回执ACK/接收及持久化状态,以及非CMPP来源无拦截回执投递的证据;仅HTTP200、mock或记录failed均不足以证明拦截闭环完成。
## 10. 2026-09-10 实施落地与验收边界
本节描述本地实现,取代上文的待实现状态;不把本地验证写成线上生效。统一服务 drainage-authorization.ts 对全部目标判断,微批一次读取签名资料并按企业/应用/签名隔离,普通路由及换通道共用门禁;旧最长匹配仅作兼容归属,不再决定是否放行。使用 tldts 公共后缀数据检查域名边界,域名按自身或点边界子域匹配;手机号清洗后完整比较。检测副本保留原文位置,不改消息原文、分片长度或计费。配置资料保留原值,不迁移旧通道批准、不扩大历史带路径资料授权范围。
数据实现选择独立的 SmsDrainageDecision 追加式 JSON 决策表,而非逐目标关系表;每份快照保存全部目标、资料版本、报备任务、候选通道、时间和原因,避免单外键表达不了多目标。SmsMessageRecord.drainageGate 保存最新判定供详情使用,SmsSubmitRecord.drainageGate 保存该尝试首次最终许可的快照,后续撤销不覆盖该尝试证据。旧终态消息不回填;历史未判定显示“未执行引流资格校验”。报备统计从尝试快照读取多目标,按运营商聚合;无实际写入的 DRN 拒绝不算通道发送尝试。质量统计只在引流维度展开,其他维度不倍增。
Gateway 每个分片等待可用连接后通过仅本机直连的 POST /api/gateway/events/authorize-drainage 复核真实消息、提交ID、通道及完整内容 SHA256。转发头请求拒绝,客户端不能提交白名单。校验读取新规则、平台审核、签名及引流批准,不使用30秒规则缓存授予最终许可。数据库触发器覆盖资料/报备的新增、修改、删除,与资格事务使用同签名 advisory lock;签名行及规则表有读锁。提交事务取得许可后对应的物理写入属于在途,不能承诺已经取得许可的网络写入被撤回。无引流的独立通道测试保留原路径,含引流但未关联签名仍拒绝。已终结提交不重新授权。
业务拦截内部原因使用 DRAINAGE_*CMPP沿用 REJECTD 与短码 DRN;技术不可用使用 DRNCHK,并在尚未写任何分片时交给现有Worker有限重试/死信策略,不当作客户未报备违规。已有部分分片的尝试保留分片事实,不自动另选通道重发。资料为空、规则为空/异常、检测截断均不能当成无引流放行。
本次修复的回执崩溃窗口:原方法遇已有平台回执直接返回,可能漏建投递。引流业务拒绝在终态写入时同时保存 drainageReceiptPending,沿用原幂等费用释放;回执按 receiptKey upsert,已有回执仍补齐幂等下游意图,持久化失败不清标记。Worker每10秒恢复最多50条本类CMPP拒绝的回执;仅补回执,不重新发送短信。HTTP/CMPP下游各沿用既有唯一键。非CMPP路由拦截不调用该方法,不新增回执或推送。
新增迁移 20260910130000_drainage_send_gate 只加列、索引、决策表及锁触发器,不改既有批准/客户/余额/短信记录。测试发布按标准工具执行,包含此前九项运营修复提交;回退旧程序会失去本门禁,不能未经评估恢复发送。原治理工具草稿和备份/候选均保留。
验证:独立本机 PostgreSQL 克隆库完成新迁移,真实规则/API验证 NFKC号码、全部目标交集、审核撤销、报备撤销、并发锁等待、URL三种伪装拒绝、决策持久化及报备SQL。真实浏览器连接该API验证拦截详情、刷新、路由切换和1600×1000、1366×768、390×844;无Browser插件,使用既有Playwright/Edge。发送Worker与Gateway传输未启动,不以这些证据替代供应商零Submit、客户回执ACK、长短信物理发送、费用对账或容量测试,以上须专项发送授权后验证。自动回归及发布结果以 testing-progress.md 最新记录为准。
@@ -2249,3 +2249,16 @@
## 2026-09-09 运营九项修正
用户确认需求及实现范围见 [九项修复设计](operations-fixes-20260909.md)。通道报备发送统计按实际运营商分开;创建/修改弹窗默认仅显式关闭;签名活跃度的企业、应用、签名、通道独立组合筛选;系统监控增加日期可选、默认近7日历史;首页客户分片按唯一业务消息汇总;发送质量告警已读计数与阅读筛选;详情行去通道组重复文案;HTTP地址随开关显示,保存校验错误居中;清退预警展示去“请通知 企业:”。本节客户分片口径替代旧供应商分片总数口径,到达率仍沿用供应商分片分子/分母。
## 2026-09-10 引流发送资格与通道匹配(待实施)
新增需求:完整短信经NFKC及分类干扰字符清洗后检测引流;每个目标须匹配当前企业应用/签名下的登记资料,号码规范值完整匹配,URL按hostname等于登记域名或为其任意层级子域名匹配,路径和参数不限制,排除lisglo.cn.evil.com等伪包含,短信原文不得修改。最终只能选同时满足签名及全部引流信息报备通过的通道;不满足时不向供应商提交,记录原因并走未送达客户回执闭环。方案、旧规则替代范围、URL包含边界与待明确事项见[专项方案](drainage-send-gating-plan-20260910.md)。本需求在实施启用后替代前文“引流只识别不得拦截”,不表示当前已上线;未授权本轮开发/提交/部署。
2026-09-10补充确认:引流资料仅添加不够,必须平台审核通过。回执参考现有签名未报备处理及客户原接收配置;源码核查发现非CMPP路由失败缺少统一失败回执调用,实施时纳入相关闭环修复和回归,不冒称当前HTTP一定推送。详见专项方案9.1。
2026-09-10最终澄清:旧通道报备配置由用户处理,本次不迁移/补齐/推断旧配置,保证配置页面/API可正常操作和资格读取。非CMPP提交遭拦截不推送回执,属于预期规则,撤销前条“纳入闭环修复”的要求;CMPP沿用签名未报备的既有失败回执行为。非CMPP回执需求以后另提。
## 2026-09-10 引流发送资格实施确认
后续已授权修改、本地提交及测试部署。以 drainage-send-gating-plan-20260910.md 第10节为实现说明,替代此前“未授权实施”的阶段性描述。每个引流目标须匹配本企业应用/签名审核通过的资料并满足最终通道报备;NFKC及干扰清洗只用于检测,域名自身与子域按点边界匹配,纯域名不限制路径参数。非CMPP拦截不推送回执;CMPP失败回执需幂等且可恢复。旧报备配置仍由用户处理。是否线上生效及未执行发送验收见 testing-progress.md。
+9
View File
@@ -69,3 +69,12 @@ api/src/risk-review/
- 阈值修改不清空计数,不自动释放待审消息;时间配置在当前夜间结束后生效,界面说明延迟生效。批量任务已有部分正常发送时,保留部分发送进度并标记存在待审核,不覆盖整批消息状态。审核与入队失败不得吞错,续发使用消息ID幂等队列任务。
- 权限沿用管理员风控配置/短信审核入口;应用必须从真实消息与企业关联取得,不能信任客户端自报企业、时间或分类;应用覆盖必须验证对象存在。历史审核记录不重写、不自动重投。回退须先停发送Worker并保留新待审及计数事实,旧版本不能继续绕过新夜间门禁。
- 验收覆盖阈值边界、多入口/多实例并发、跨午夜、应用隔离、幂等、重启、配置覆盖/变更、历史初始化、相同内容聚合、审核范围与并发、定时任务和数据库失败;使用隔离PostgreSQL/Redis证明持久化,不发送真实短信。前后端全量测试、类型/构建/质量门禁、两环境真实API与三尺寸页面验收分别留证。
## 2026-09-10 引流资格门禁(待实施专项)
详细规则、源码差异、多目标通道交集、终态回执/费用、并发与迁移见[引流信息拦截与通道报备匹配方案](drainage-send-gating-plan-20260910.md)。这是新增硬性发送资格,审核通过不得豁免引流检查;沿用既有夜间风控等规则,旧“只识别不拦截”在新功能生效后被替代。当前仅完成设计。
## 2026-09-10 引流发送门禁实施
后续用户已授权实施、本地提交及测试部署。具体数据模型、最终分片复核、并发锁、CMPP回执恢复和非CMPP无推送行为见 drainage-send-gating-plan-20260910.md 第10节;取代此前本主题仅处于设计阶段的状态。旧批准配置不迁移;线上状态与未执行项以测试进度为准。
+36
View File
@@ -5352,3 +5352,39 @@ OPS0908-01至07已按本轮范围验证;精确证据见testing-progress.md对
| TC-OPS0909-07 | 短信记录发送详情多个提交/回执 | 每行无通道组文案,顶部汇总及通道/回执数据仍存在 |
| TC-OPS0909-08 | HTTP关/开/关/开;本地及API校验失败 | 地址随HTTP参数隐藏展示且保值;居中错误弹窗保留表单,不伪报保存成功 |
| TC-OPS0909-09 | 历史/新清退预警多行、包含企业名称、非前缀正文 | 仅展示去行首请通知企业文案,保留签名/运营商/数量正文;不改数据库/外发消息 |
## 2026-09-10 引流发送资格验收(全部待实施/待执行)
权威方案见[引流门禁方案](drainage-send-gating-plan-20260910.md)。生效后替代旧引流未登记/未审核仍允许发送的断言;历史结果不改写。真实发送/入队验收需专项授权,本轮未执行。
| 用例 | 场景 | 预期 |
|---|---|---|
| TC-DRAINAGE-GATE-01 | 无引流、检测正常 | 继续原签名/风控/路由,不新增拒绝 |
| TC-DRAINAGE-GATE-02 | 02177882277登记,原文021-77882277及全角/干扰符 | 规范值匹配;数据库原文、计费长度和提交字节不变 |
| TC-DRAINAGE-GATE-03 | 号码子串、区号不同、跨签名/企业/应用同值 | 不得借用或子串放行 |
| TC-DRAINAGE-GATE-04 | 登记lisglo.cn,短信sms.lisglo.cn及带路径 | 两例通过目标关联;仍须合格通道 |
| TC-DRAINAGE-GATE-05 | lisglo.cn.evil.com、evillisglo.cn、query/userInfo伪包含;任意子域+路径参数 | 前四类不通过;hostname等于或点边界子域通过,深层子域不授权父/兄弟域;检测不得截短目标 |
| TC-DRAINAGE-GATE-06 | 一条短信两目标,仅一个登记 | 整条拒绝,另一个目标和原因可追溯;同批合法消息不受影响 |
| TC-DRAINAGE-GATE-07 | 一个目标匹配多条资料,多个目标通道1/2及2/3 | 单目标资料取并集,多目标取交集,仅通道2;记录授权任务 |
| TC-DRAINAGE-GATE-08 | 资料pending/rejected/deleted、修改冻结、旧版本报备 | 不借失效资料通过;兼容策略经确认后执行 |
| TC-DRAINAGE-GATE-09 | 同通道签名通过而引流未通过、运营商不匹配、历史通道级任务 | 严格区分签名/引流资格;旧配置不迁移补齐,用户修改/保存/刷新正常,按当前配置读取资格 |
| TC-DRAINAGE-GATE-10 | 普通HTTP/CMPP/客户端、微批、模板替换、长短信跨片URL | 统一结果,完整内容判定,入口受理不等于发送 |
| TC-DRAINAGE-GATE-11 | 定时到期、审核释放、换通道/重试、启用前排队 | 最终重新校验,不因旧快照/人工批准绕过 |
| TC-DRAINAGE-GATE-12 | 撤销报备与多Worker并发,Gateway意图已排队 | 授权版本/生效边界可证,未发送的失效意图不继续Submit |
| TC-DRAINAGE-GATE-13 | 拦截与回执并发、写状态后崩溃、回调失败/无目标 | 终态/费用幂等;CMPP沿用适用的既有回执且可恢复;非CMPP不创建拦截回执投递,即使配置了HTTP回调 |
| TC-DRAINAGE-GATE-14 | 已扣/未扣/冻结及部分已发历史记录 | 复用原策略不多扣多退,无供应商提交不产生供应商成本 |
| TC-DRAINAGE-GATE-15 | 50命中/20000字符上限、规则为空/故障、Redis/DB故障 | 不因不完整检测判无引流,不吞错放行;技术失败独立记录 |
| TC-DRAINAGE-GATE-16 | 详情/历史未校验/多引流统计/三尺寸与权限 | 保留原文和证据,历史不冒称通过,首页及费用不倍增,跨租户不可见 |
TC-DRAINAGE-GATE-05补充:覆盖host大小写、国际化域名、末尾根点、畸形URL、公共后缀登记拒绝和历史带路径资料迁移边界;纯域名报备不限制下级域名、URL路径或参数。此项已按用户澄清确定域名边界,不再使用字面includes。
TC-DRAINAGE-GATE-08审核要求已获用户确认:已添加但未审核通过不得发送。TC-DRAINAGE-GATE-13补充签名与引流在HTTP/CMPP路由失败的对照回归:当前非CMPP路由失败没有统一失败回执调用,需补闭环后验证,不能以CMPP通过代替HTTP通过;尊重原回执标记及接收配置。
2026-09-10最终澄清替代前条TC-DRAINAGE-GATE-13“补非CMPP回执闭环”要求:非CMPP不推送是预期行为,不作为Bug;CMPP继续沿用签名未报备的回执路径。TC-DRAINAGE-GATE-09不做旧报备配置迁移或补齐,增加旧记录可查看、用户修改保存刷新及当前资格读取回归;旧配置调整由用户负责。
## 2026-09-10 引流门禁实施及验证分层
TC-DRAINAGE-GATE-0116的实现范围以专项方案第10节为准,不再笼统标为全部待实现。新增真实数据库/API脚本 tools/testing/verify-drainage-gate.mjs 只接受本机 cmpp_qa_drainage_ 数字后缀克隆库,不调用发送入口或Gateway传输;覆盖02、04~09、12、16中的规范化、关联、通道交集、审核/报备撤销、并发锁、伪URL及SQL部分。Gateway隔离单元覆盖最终复核与故障拒绝;API单元覆盖CMPP已有回执恢复、持久化失败保留待恢复标记和非CMPP无推送。真实页面覆盖16的三尺寸、关闭、刷新和路由切换。新增用例:转发头不能调用最终资格接口;已终态提交不能重复授予许可;无实际wire的DRN拒绝不计入通道发送尝试。
保留待专项发送验收:10/11的各入口到供应商端到端、12的物理提交边界、13的客户回执ACK/离线重投、14的真实费用及部分分片对账、15的真实基础设施故障恢复及吞吐。单元测试、持久化夹具和浏览器不代替这些证据。旧配置不迁移、不自动补齐;配置入口沿用原API,测试环境只读确认,不保存客户配置。
+27
View File
@@ -4838,3 +4838,30 @@ git diff --check
- 遗留边界:创建通道390宽原有布局拥挤,关闭按钮可见可用,未扩大为视觉改版;历史告警受Prometheus保留和采样限制,含等待触发周期,最后采样不等于恢复时刻。预生产历史ERR_CONNECTION_CLOSED根因和已有管理员会话锁定未在本轮解决,未尝试恢复管理员。未推送、未测试部署、未预生产部署,线上真实业务验收未执行。
本地证据目录:C:/Users/hectorzhao/AppData/Local/Temp/cmpp-nine-fixes-20260909。自动日志api-full.log、frontend-final.log、build-last.log、api-build-final.log、lint-last.log、security.log等;浏览器成功记录browser-run7.log、browser-extra2.log、browser-retirement2.log、browser-counts2.log及各browser-*目录截图/结果,失败日志保留。敏感认证值不写入文档或Git。
## 2026-09-10 引流拦截需求评估与方案
仅文档授权;本地main为5bcdbb2a03637b1ab1aeda59a4e1db9cc9fc4243,实际ls-remote远端main为6d63eb5452ffc7c802960d044bf598cc8646564d,暂存区空,保护既有19个跟踪修改及全部未跟踪文件。核对检测器、两条原文最长匹配路径、报备路由、Prisma模型和平台失败回执后,新增[专项方案](drainage-send-gating-plan-20260910.md),同步需求/风控索引/16项待执行用例。明确旧只识别规则被新需求替代的生效关系、多目标交集、号码清洗且原文不变、URL字面包含边界、审核和legacy任务兼容、拒绝回执/费用幂等、队列与撤销并发、数据迁移及阶段实施。
证据仅当前源码/模型和真实Git远端读取;未启动服务、未连接业务数据库或远端API,未验证PostgreSQL/Redis/MinIO/Gateway当前状态,未执行业务测试、构建、浏览器或发送。文档完成不代表引流门禁已实现。业务边界待明确项列在方案第9节;本轮运行代码/配置/数据库无修改,未提交、未推送、未测试部署、未预生产部署。文档开工副本及保护核验记录位于%TEMP%/cmpp-drainage-design-20260910。
用户随后明确URL规则:登记父域名授权自身及任意层级子域,路径/参数不限制,排除lisglo.cn.evil.com等伪包含。已将方案3.3改为URL解析后的hostname相等或点边界后缀匹配,同步需求和TC-DRAINAGE-GATE-05;仅历史带路径资料的迁移策略等仍待明确,域名边界已确定。
文档核验完成:4份既有文档保持开工字节前缀不变,专项方案6个相对链接有效,16项验收用例均明确待执行;git diff --check通过,暂存区仍空。src无修改;api仅保留开工已有metrics两文件修改。本轮交付为新增1份方案与4份既有文档追加,无代码实现、业务测试或提交/发布。
2026-09-10解释与补核:用户确认引流资料须平台审核通过,已写入方案及用例。将旧带路径资料/通道级报备术语改为业务示例和实施核查项。只读核对单条/批量路由失败、recordCmppFailureReceipt、queueFinalReceiptDeliveries及HTTP投递配置:CMPP路径生成REJECTD并按请求标记投递;非CMPP路由失败只刷新任务进度,未调用统一失败回执,列为后续闭环修复点。无代码修改或真实发送复现。
2026-09-10用户最终确认:旧通道报备配置由用户调整,仅要求配置功能正常;非CMPP提交本来就不应推送回执,未来另提需求。已修正专项方案的现状评价、来源分支、实施范围和验收条件,同步需求与用例;撤销此前将非CMPP不推送判为缺口及纳入修复的判断。未修改代码/配置/数据,未迁移旧报备,未提交或部署。
## 2026-09-10 引流发送资格实施与本地验收(13:20 CST)
授权:修改、本地提交、测试环境部署;未授权推送、预生产部署、发送/补发/重投/重新入队短信或修改既有客户/通道/余额配置。实施见 drainage-send-gating-plan-20260910.md 第10节,用例 TC-DRAINAGE-GATE-0116 分层记录。
- Gitmain 开工 HEAD 5bcdbb2a03637b1ab1aeda59a4e1db9cc9fc4243,实际远端 main 6d63eb5452ffc7c802960d044bf598cc8646564d。本轮保护42个已有具体文件;发布工具、metrics、AGENTS及治理草稿不提交。本轮变更前副本、日志及浏览器证据位于 %TEMP%/cmpp-drainage-implementation-20260910。
- 实现:全目标规范化与同签名审核校验、通道交集、批量/普通路由、Gateway每分片最终复核、域名边界及伪URL排除、判定快照、CMPP拒绝回执耐久恢复、非CMPP不推送、多引流统计和短信详情。运行时仅新门禁产生拒绝,不处理旧批准或重发旧短信。治理清理未实施。
- 新增 schema 迁移在独立真实 PostgreSQL 克隆库 cmpp_qa_drainage_1789017023546 应用成功;另一个早期迁移试验库保留,均未改本机业务原库。真实规则+资格HTTP接口验证NFKC号码/原文不变、两个目标通道交集、平台审核、报备撤销、写事务与复核并发锁、伪后缀/query/userInfo、转发请求拒绝、持久化决策及报备SQL;结果 real-gate2.log。仅构造隔离数据库夹具,无发送Worker、Gateway transport或提交outbox。
- 自动检查:API全量67套723项通过;最终定向2套152项通过。前端27文件136项通过;TypeScript、API构建、前端production构建通过。Gateway全量Go测试及vet通过。lint(含类型/Stylelint/CSS治理15项)、format:check、security:verify、deploy:verify、bundle:verify通过。原有API metrics未提交测试包含在工作区总数中,精确提交的发布validate独立核验,不冒称所有测试文件均被提交。改动文件必要格式化和未使用导入清理用于满足当前门禁,未触及其他会话源码。
- 浏览器:Browser插件不可用,使用既有Playwright/Edge,真实本地API+独立PostgreSQL库;Redis5仅隔离本机队列,登录使用测试Redis7的DB15独立随机前缀,经SSH转发,结束删除仅本轮认证键和临时QA用户。1600×1000、1366×768、390×844通过,拦截原因、显式关闭、刷新与跨路由正常,无pageerror。首轮在详情请求返回前断言历史占位,已修正测试等待,并为加载中新增明确提示;最终production构建再次验收通过。证据 browser-final.log 及该目录最新 browser-* 截图。
- 环境:13:12重新核验测试SSH和health可达,线上版本仍809175b544f2526891ba6d2dece1a50eadaf57a0。期间两次SSH连接超时,Tailscale通过DERP后恢复;不记作认证失败或网络根因已永久解决。本次测试发布包同时包含此前九项运营修复提交与本功能;不推送远端。
- 未执行:真实短信提交、供应商零Submit/长短信跨分片对账、客户回执ACK/离线送达、费用和部分已发记录对账、TPS/故障注入容量测试;没有专项发送授权,以上不以单元、夹具或浏览器代替。既有通道配置不修改,MinIO材料无变更。提交及标准发布阶段、磁盘/恢复资产与目标页面结果在后续记录补齐。