Files
lislgosms/docs/access-number-upstream-downstream-design.md
T

19 KiB
Raw Blame History

CMPP 接入号上下游配置与匹配流程设计

1. 文档目的

本文梳理当前 CMPP 短信平台接入号的真实代码、生产数据和协议字段使用情况,并给出客户侧下游 CMPP 接入号、运营商侧上游 CMPP 接入号、号码映射、普通上行匹配和历史追溯的目标设计。

本文仅描述设计结论,不代表相关改造已经完成。后续实现仍必须基于真实 NestJS API、Prisma/PostgreSQL、Go Gateway、Redis 和生产 CMPP 链路验证,不允许使用前端静态数据、localStorage 或 mock 代替业务完成。

2. 结论摘要

当前系统已经具备“通道接入号下发、上行接收、模糊匹配、人工认领”的第一版链路,但尚未形成真正的应用级接入号分配和上下游映射模型。

现状核心问题如下:

  1. 上游通道接入号和客户应用接入号混用。
  2. 客户侧展示的接入地址、端口和接入号来自任意一条上游通道,而不是平台客户接入端点和本应用号码。
  3. 客户 CMPP SUBMIT 中的 Src_Id 虽已传给 NestJS,但未校验是否属于当前应用,也未参与后续通道号码映射。
  4. 多个应用绑定同一通道组时,上行 Dest_Id 会匹配到该组全部应用,无法唯一定位。
  5. extensionDigits 目前只是配置和队列元数据,Go Gateway 没有根据它生成或匹配应用扩展号。
  6. 上游 CMPP SUBMIT 的 MsgSrc 当前使用通道登录账号,而不是通道企业代码。
  7. 普通上行没有完整保留 Service_IdLinkID、原始接入号和规范化接入号。
  8. 应用级 cmppEnterpriseCode 当前允许最长 32 字符,与 CMPP SUBMIT 中 MsgSrc 固定 6 字节的字段边界不一致。

生产只读检查显示:

  • 当前 3 条通道的 srcId 均为 1069999999
  • 其中 2 条通道为 active1 条为 disabled。
  • 生产通道未配置 serviceId 和有效扩展位数。
  • 4 个真实应用路由到同一个通道组和同一基础接入号。
  • 当前 2 条普通上行的 destId 都是 1069999999,匹配状态均为 ambiguous
  • 生产存在大量批量验证应用,不应自动分配正式生产接入号。

因此,当前共享基础接入号只能支撑模糊候选和人工认领,不能作为多应用上行自动路由的生产方案。

3. CMPP 字段边界

接入号设计必须区分以下概念:

概念 CMPP 字段 含义 当前状态
登录账号 CONNECT Source_Addr 建立 CMPP TCP 会话的账号,协议字段为 6 字节 上下游已有账号字段
企业代码 SUBMIT MsgSrc SP 身份、地址翻译、计费和结算标识,协议字段为 6 字节 下游应用允许过长;上游错误使用登录账号
业务代码 Service_Id 业务类型,最长 10 字节 藏在通道 JSON 中,生产未配置
下发接入号 SUBMIT Src_Id 服务代码或以服务代码为前缀的长号码,最长 21 字节 当前只有通道级基础号码
上行接入号 DELIVER Dest_Id 用户实际回复到的服务代码或长号码 当前仅按通道基础号反查路由组
上行手机号 DELIVER Src_Terminal_Id 发送普通上行的用户手机号 当前已处理
点播关联标识 LinkID 点播类业务的关联标识 当前未传入 NestJS

CMPP 2.0/3.0 协议要求:

  • MsgSrc 和 CONNECT 登录账号是两个不同概念,即使运营商配置值相同,也必须在数据模型中分开保存。
  • Src_Id 可以是基础服务代码,也可以是以服务代码为前缀的长号码。
  • 普通上行 DELIVER 中,Dest_Id 是用户回复的接入号,Src_Terminal_Id 是用户手机号。
  • 状态报告 DELIVER 和普通上行 DELIVER 必须通过 Registered_Delivery 严格分流。
  • 普通上行自身的 Msg_Id 不能默认当作原下发消息 ID。

协议参考:

行业监管要求端口类短信按照批准的码号结构、位长、用途、使用范围和期限使用,并保存发送时间、接收时间、发送端、接收端和内容等记录。因此系统不能因为协议字段最大长度为 21 字节,就允许任意拼接扩展码;扩展长度必须依据具体运营商合同和报备结果配置。

行业规则参考:

4. 当前真实实现

4.1 当前下发流程

  1. 应用通过页面、HTTP API 或客户侧 CMPP 提交短信。
  2. NestJS 根据应用、运营商、省份和通道组选择上游通道。
  3. SubmitCommand.cmpp.srcId 直接取 SmsChannel.srcId
  4. Go Gateway 构造运营商侧 CMPP SUBMIT。
  5. 上游 MsgSrc 当前取通道登录账号。
  6. 上游 Src_Id 取通道基础接入号。
  7. 所有走同一通道的应用对外使用同一基础号码。

相关代码:

  • api/src/send-chain/send-chain.service.ts:生成 SubmitCommand
  • gateway/internal/upstream/manager.go:构造 CMPP 2.0/3.0 SUBMIT 报文。

4.2 当前客户 CMPP 入站流程

  1. 客户通过 6 位账号 CONNECT。
  2. Gateway 根据会话账号定位应用,并校验应用企业代码。
  3. 客户 SUBMIT 的 Src_Id 被传给 NestJS。
  4. NestJS 未校验该号码是否分配给当前应用。
  5. NestJS 未把客户 Src_Id 保存到短信记录或提交记录。
  6. 后续上游发送重新取通道 srcId,客户提交号码被丢弃。

相关代码:

  • gateway/internal/inbound/server.go:客户 CMPP CONNECT/SUBMIT 解析。
  • api/src/send-chain/send-chain.service.tssubmitInboundMessage

4.3 当前普通上行流程

  1. 运营商通过 CMPP DELIVER 返回用户手机号和 Dest_Id
  2. Gateway 将手机号、接入号和内容回调 NestJS。
  3. NestJS 尝试按 messageId 匹配。
  4. 未匹配时,根据“上行通道所属通道组”查询所有绑定该组的应用。
  5. 多应用共用通道组时生成 ambiguous 候选。
  6. 无接入号候选时,再按手机号和最近 72 小时下发记录匹配。
  7. 最后进入人工认领。

当前流程能避免多候选时误推客户,但不能提供生产级接入号唯一路由。

相关代码:

  • gateway/internal/upstream/manager.go:解析普通上行 DELIVER。
  • api/src/send-chain/send-chain.service.tsresolveUplinkMatch

4.4 当前客户 CMPP 参数接口问题

SmsApplication 没有应用接入号字段。getApplicationCmppParams 会查询最新一条未删除的 SmsChannel,将该上游通道的地址、端口和 srcId 返回给客户。

这会产生以下错误:

  • 客户看到运营商侧上游网关地址,而不是平台客户接入地址。
  • 不同应用得到同一个任意通道接入号。
  • 上游通道新增、删除或排序变化可能改变客户展示参数。
  • 应用接入参数与实际客户连接到 8.160.169.106:17890 的生产事实不一致。

相关代码:api/src/sms-config/sms-config.service.tsgetApplicationCmppParams

5. 目标数据模型

目标模型分为平台接入端点、上游号码能力、应用虚拟接入号和上下游绑定四层。

5.1 CmppDownstreamEndpoint

保存客户连接本平台的公共端点:

  • id
  • name
  • gatewayHost
  • gatewayPort
  • supportedVersions
  • heartbeatSeconds
  • status
  • effectiveFrom
  • effectiveTo

生产客户参数应返回平台端点 8.160.169.106:17890 或后续正式域名/TLS 地址,不再读取任意上游通道。

5.2 UpstreamAccessNumber

表示运营商或上游供应商在某条通道上实际开通的号码能力:

  • id
  • channelId
  • enterpriseCode:上游 MsgSrc,严格 6 字节。
  • serviceId:最长 10 字节。
  • baseNumber
  • extensionModenone/fixed/allocated/shared
  • allowedExtensionLengths:由合同或报备决定。
  • maxTotalLength:不得超过 21。
  • uplinkReturnModefull/base_only/truncated/custom
  • replySupported
  • reportStatus
  • effectiveFrom
  • effectiveTo
  • status

不能把扩展位数设成全平台统一常量。不同运营商、供应商和通道允许的扩展位数可能不同,应由通道号码能力明确配置。

5.3 ApplicationAccessNumber

表示客户应用看到、提交和收到普通上行时使用的号码:

  • id
  • tenantId
  • applicationId
  • endpointId
  • accessNumber
  • serviceId
  • isDefault
  • directionmt/mo/both
  • replyEnabled
  • shareModeexclusive/shared
  • effectiveFrom
  • effectiveTo
  • status

约束:

  1. 同一活动号码原则上只能属于一个应用。
  2. 允许共享时必须显式标记 shared
  3. 共享号码不能仅按接入号直接自动投递。
  4. 应用开放 CMPP 提交前必须至少有一个默认活动号码。
  5. 客户 SUBMIT 中的 Src_Id 必须属于当前已鉴权应用。

5.4 AccessNumberBinding

表示客户应用号码与各上游通道号码之间的真实映射:

  • id
  • applicationAccessNumberId
  • channelId
  • upstreamAccessNumberId
  • upstreamFullNumber
  • upstreamExtension
  • serviceIdOverride
  • carrier
  • province
  • priority
  • matchMode
  • effectiveFrom
  • effectiveTo
  • status

关键约束:

  • 可自动回复的号码,活动状态下 (channelId, upstreamFullNumber) 必须唯一。
  • 多应用共享同一上游号码时,必须标记共享并禁止仅凭接入号自动投递。
  • 激活绑定前校验号码前缀、长度、运营商、报备状态和通道状态。
  • 每个需要发送的运营商/省份路由必须有可用号码绑定,否则该通道不是有效候选。

5.5 短信和提交快照

SmsMessageRecordSmsSubmitRecord 中增加不可变快照:

  • applicationAccessNumberId
  • accessNumberBindingId
  • clientSrcId
  • upstreamSrcId
  • upstreamMsgSrc
  • upstreamServiceId
  • upstreamChannelId

这样即使以后修改应用号码、通道或绑定,历史回执、上行和审计仍可按提交当时的号码关系追溯。

6. 目标下发流程

flowchart LR
    A["客户应用提交短信"] --> B["CONNECT账号定位应用"]
    B --> C["校验MsgSrc等于应用企业代码"]
    C --> D["校验或补全客户Src_Id"]
    D --> E["运营商、省份和通道组路由"]
    E --> F["查询应用号码到候选通道的有效绑定"]
    F --> G["生成上游MsgSrc、Service_Id、Src_Id"]
    G --> H["保存客户号和上游号快照"]
    H --> I["Gateway发送上游CMPP SUBMIT"]

详细规则:

  1. CONNECT 账号只用于定位应用。
  2. 客户 MsgSrc 必须与应用企业代码严格匹配;建议统一为 6 位 ASCII,现有超长数据需迁移或重新分配。
  3. 客户 Src_Id
    • 未填且应用只有一个默认号码时,自动补全默认号码。
    • 已填写时,必须精确属于当前应用。
    • 号码停用、过期、未分配或不允许下发时,返回明确的 Src_Id 非法错误。
    • 校验失败不得进入计费、任务和上游发送队列。
  4. 选出上游通道后,再查找应用号码在该通道上的有效绑定。
  5. 主通道没有有效号码绑定时,可按通道组规则选择下一候选,但不能退化使用通道基础号码。
  6. 上游 CMPP 报文必须使用:
    • MsgSrc = UpstreamAccessNumber.enterpriseCode
    • Service_Id = binding.serviceIdOverride 或上游号码默认值
    • Src_Id = AccessNumberBinding.upstreamFullNumber
    • Dest_Terminal_Id = 用户手机号
  7. 最终客户号、上游号、企业代码、业务代码和绑定 ID 必须持久化为提交快照。

7. 目标普通上行流程

flowchart TD
    A["收到上游普通DELIVER"] --> B["保存Channel、原始Dest_Id、Service_Id、LinkID"]
    B --> C["按通道规则规范化Dest_Id"]
    C --> D{"channelId和完整接入号唯一绑定?"}
    D -- "是" --> E["定位应用和客户接入号"]
    D -- "否" --> F{"通道明确配置截断或基础号回传?"}
    F -- "是" --> G["手机号和近期下发号码快照辅助匹配"]
    F -- "否" --> H["生成候选"]
    G --> I{"得到唯一高置信结果?"}
    I -- "是" --> E
    I -- "否" --> H
    H --> J["ambiguous或unmatched,进入人工认领"]
    E --> K["转换为客户侧CMPP DELIVER"]

匹配优先级:

  1. (channelId, rawDestId) 精确匹配活动绑定。
  2. 按该通道明确配置的规范化、截断或别名规则匹配。
  3. 手机号、通道和近期下发记录中的 upstreamSrcId 快照匹配。
  4. 使用 Service_IdLinkID、运营商、时间窗口作为辅助条件。
  5. 多候选进入人工认领。
  6. 完全无候选标记 unmatched

禁止:

  • 仅因为多个应用绑定同一通道组,就把全部应用视为接入号候选。
  • 选择任意 active 应用兜底。
  • 对所有通道统一执行前缀匹配或截断。
  • 把普通上行自身的 Msg_Id 当成原下发消息 ID。
  • 在没有其他证据时将共享接入号自动投递给某个客户。

匹配成功后,客户侧普通上行 DELIVER 应使用:

  • Dest_Id = ApplicationAccessNumber.accessNumber
  • Src_Terminal_Id = 用户手机号
  • Service_Id = 客户侧业务代码
  • LinkID = 可用时保留关联值

原始上游号码仍需保存在数据库和运营审计中,但不能直接作为转换后的客户号码。

8. 配置界面设计

8.1 平台客户接入端点

独立配置:

  • 公网域名/IP
  • 端口
  • CMPP 版本
  • TLS 状态
  • 心跳间隔
  • 启停状态

8.2 上游通道页面

通道连接配置和号码能力分区展示:

  • 登录账号 Source_Addr
  • 企业代码 MsgSrc
  • 默认 Service_Id
  • 基础接入号
  • 扩展模式
  • 允许扩展长度
  • 最大总长度
  • 是否支持上行
  • 上行回传模式
  • 报备状态和生效时间

8.3 应用页面

应用 CMPP 参数区展示:

  • 平台接入地址和端口
  • 账号
  • 密码
  • 企业代码
  • 协议版本
  • 连接数和窗口
  • 已分配客户接入号列表
  • 默认号码
  • 上行/下发能力
  • 生效状态

8.4 上下游号码绑定矩阵

按应用号码、运营商、通道组和通道展示:

  • 客户号码
  • 上游基础号
  • 上游扩展码
  • 上游完整号码
  • Service_Id
  • 报备状态
  • 是否支持上行
  • 主备优先级
  • 生效时间

绑定不完整时,不允许把对应通道标记为该应用的可用发送候选。

9. API 建议

建议新增或调整:

  • GET /api/admin/cmpp-downstream-endpoints
  • POST /api/admin/cmpp-downstream-endpoints
  • GET /api/admin/channels/:id/access-numbers
  • POST /api/admin/channels/:id/access-numbers
  • GET /api/admin/applications/:id/access-numbers
  • POST /api/admin/applications/:id/access-numbers
  • GET /api/admin/applications/:id/access-number-bindings
  • POST /api/admin/applications/:id/access-number-bindings
  • POST /api/admin/access-number-bindings/:id/activate
  • POST /api/admin/access-number-bindings/:id/disable
  • GET /api/client/applications/:id/cmpp-params

客户参数接口返回结构建议:

{
  "gatewayHost": "8.160.169.106",
  "gatewayPort": 17890,
  "account": "123456",
  "enterpriseCode": "900001",
  "protocolVersion": "CMPP2.0",
  "maxConnections": 1,
  "windowSize": 16,
  "accessNumbers": [
    {
      "accessNumber": "10699999990001",
      "serviceId": "NOTICE",
      "isDefault": true,
      "replyEnabled": true,
      "status": "active"
    }
  ]
}

示例号码只用于说明结构,不能在未获得上游合同和报备确认时直接用于生产配置。

10. 生产数据迁移原则

  1. 将现有 SmsChannel.srcId=1069999999 迁移为各通道的上游基础号码记录。
  2. 从上游供应商合同或后台确认:
    • 上游企业代码。
    • Service_Id
    • 允许扩展位数。
    • 是否完整回传扩展号。
    • 是否支持普通上行。
    • 多连接或多通道是否共享同一号段。
  3. 在未确认扩展规则前,不为多个应用自动生成扩展号码。
  4. 现有 2 条 ambiguous 上行继续保留人工认领,不自动修改历史归属。
  5. 大量批量验证应用不得自动分配正式号码。
  6. 修复或迁移超过 6 字节的 cmppEnterpriseCode
  7. 修复客户参数接口,不再返回任意上游通道地址和号码。
  8. 对现有两条 active、配置相同的复制通道确认是否属于真实独立上游连接,避免重复通道与号码能力重复生效。

11. 实施优先级

P0:数据正确性

  • 新增上游号码、应用号码和绑定表。
  • 修复客户 CMPP 参数接口。
  • 严格校验客户 MsgSrcSrc_Id
  • 上游 MsgSrc 改用通道企业代码。
  • 持久化客户号码和上游号码快照。
  • 上行事件补充 Service_IdLinkID 和原始号码。
  • 删除“通道组内所有应用都是接入号候选”的匹配路径。

P1:配置与运营

  • 增加通道号码能力配置。
  • 增加应用接入号分配。
  • 增加应用号码到三网通道的绑定矩阵。
  • 激活前检查报备状态和三网映射完整性。
  • 人工认领可生成规则建议,但不得自动修改正式绑定。

P2:治理与指标

  • 接入号精确匹配率、歧义率、未匹配率。
  • 每个号码的下发量、上行量和最后活跃时间。
  • 共享号码积压告警。
  • 非法 Src_Id、未报备号码和异常截断告警。
  • 接入号到期、报备失效和停用后的发送阻断。

当前数据规模不需要为接入号表提前做分区;优先保证唯一约束、有效期索引、通道和号码组合索引,以及历史快照完整性。

12. 验收清单

后续实现至少覆盖以下真实链路用例:

  1. 客户参数接口只返回平台 CMPP 端点和当前应用号码。
  2. 客户提交未分配 Src_Id 时被拒绝,且不计费、不入队。
  3. 客户不填 Src_Id 且应用只有一个默认号时自动补全。
  4. 同一客户号码在移动、联通、电信映射为不同上游号码。
  5. 主备通道切换后使用各自绑定的上游 Src_Id
  6. 上游 MsgSrc 与通道登录账号不同时仍能正常提交。
  7. 完整扩展号上行能唯一匹配应用。
  8. 运营商只返回基础号或截断号时,严格按该通道规则辅助匹配。
  9. 两个应用共享号码时不得自动误投。
  10. 普通上行和状态报告严格分流。
  11. 长上行完成重组后仍按完整接入号匹配。
  12. 修改号码配置后,历史消息仍按提交快照关联。
  13. 接入号停用、过期或报备失效后阻断新发送,但历史记录仍可查询。
  14. 上行匹配失败时真实写入 PostgreSQL 候选或未匹配记录,并支持人工认领。
  15. Gateway、NestJS、PostgreSQL 和客户 CMPP 连接上的号码值可以相互核对。

13. 后续落地建议

建议后续另开实现批次,按以下顺序完成:

  1. 先取得上游供应商的真实企业代码、业务代码、扩展位数和上行回传规则。
  2. 建立 Prisma 模型、迁移和唯一约束。
  3. 修复客户 CMPP 参数接口和应用企业代码校验。
  4. 改造下发路由,在选中通道后解析号码绑定并保存快照。
  5. 扩展 Gateway 上行事件字段。
  6. 重写普通上行匹配优先级。
  7. 补齐运营端配置页面和人工认领页面。
  8. 使用真实 PostgreSQL、Redis、Gateway 和生产同类 CMPP 测试对端执行完整验收。