153 KiB
CMPP 短信平台第一版开发需求文档
0. 文档前提
本文基于当前前端设计原型整理,用于交给 Codex 或开发团队执行第一版落地开发。
当前确认:第一版保留短信业务,排除彩信功能;账户按现金余额和授信额度计费,人工充值和充值记录进入第一版开发范围,套餐、短信余量、账单流水页面和公开交易查询 API 不进入第一版。彩信服务、彩信应用/签名/模板 Tab,以及运营端彩信相关菜单标记为“待开发”;业务性能指标为“平台可稳定入队并调度 500 条短信/秒,实际向通道 submit 受通道限速配置控制”。
0.1 运营端细节要求(2026-07-15)
- 企业签名的站点字段统一显示为“引流信息”,列表、表单和详情不展示引流信息提交时间。
- 企业应用的企业代码必须始终等于 CMPP 6 位账号,由系统同步且不可单独编辑;账号留空时,创建应用时自动生成二者。每任务号码数超过应用上限时拒绝整个任务并提示拆分,不允许静默截断。
- 企业应用支持配置纯数字“应用扩展码”。开启“客户接入号填充”后才显示并允许编辑纯数字填充前缀,客户侧
Src_Id等于“填充前缀 + 应用扩展码”;关闭时前缀必须清空且客户侧Src_Id等于应用扩展码。计算结果全局唯一且不超过 CMPP 的 21 位边界。 - 客户接入号填充只解决客户系统的最小长度兼容,NestJS 必须在创建任务、冻结余额和入队前精确校验客户
Src_Id。上游实际Src_Id始终为“通道基础接入号 + 应用扩展码”,不得把客户填充前缀带入上游;新短信保存客户侧接入号和应用扩展码快照,补发继续使用原快照。未配置扩展码的历史应用保持通道基础接入号行为。 - 手机号段支持真实删除;报备字段库展示被通道报备配置引用的通道数,引用数大于 0 时前后端均禁止删除,未引用字段才允许真实删除。
- 企业应用连接详情只展示当前已连接会话;断开或心跳超时会话从活跃连接表删除,不保留为连接历史。
- 短信审核批量通过必须基于明确勾选,只处理已选待审核任务;不得默认通过当前筛选结果或全部数据。
- 下游投递记录支持创建日期范围筛选,并将日期条件下推 PostgreSQL 列表与 Dashboard 聚合。
- 短信模板变量插入到文本框当前选区或光标位置,插入后光标移动到变量之后。
- 运营端短信记录使用自适应信息卡片,发送详情按概览、短信内容、通道回执、状态和分片审计分组,失败原因使用独立警示区域突出;该项只调整前端展示,不改变短信记录后端语义。
- 人工充值弹窗不要求填写操作人,操作身份从当前登录会话和后端操作日志取得。
1. 项目目标
建设一个短信平台第一版,支持企业客户在客户端完成短信应用、模板、签名、号码导入、短信发送、批量任务查询、发送明细查询、上行短信查询;支持运营端完成企业管理、企业应用/签名/模板管理、审核、通道配置、通道签名报备、报备任务导出/回执导入、发送监控、任务进度、短信记录、上行记录、安全控制和系统管理。
第一版必须满足:
- 支持最高 500 条短信/秒的提交吞吐。
- 支持批量任务异步发送,前端提交后可查询任务进度。
- 支持通道级限速、失败重试、回执同步和发送记录追踪。
- 支持企业、应用、签名、模板、通道、通道组、报备任务等核心配置数据的后台维护。
- 彩信功能仅保留菜单占位或隐藏,不进入第一版开发范围。
- 账户现金余额、授信额度、人工充值和充值记录进入第一版范围,并与发送记录形成可对账闭环;不提供套餐或短信余量。
2. 角色与权限
2.1 客户端企业管理员
可管理本企业短信应用、短信模板、短信签名、发送任务、上行短信、企业认证、企业用户和系统日志。
2.2 客户端普通用户
可按授权查看应用、模板、签名、发送任务和发送明细;是否允许创建和发送需由企业管理员配置。
2.3 运营端管理员
可管理全平台企业、应用、签名、模板、审核、通道、通道组、报备任务、发送监控、短信记录、上行记录、黑名单、敏感词、用户、手机号段库和引流字段库。
2.4 运营审核员
可处理企业认证审核、短信审核、短信模板审核、签名/模板/发送内容相关审核。
3. 第一版范围
3.1 客户端范围
保留并开发:
- 工作台
- 短信发送
- 查看批量任务
- 短信发送详情
- 查看上行短信
- 短信应用
- 模板管理
- 签名与引流信息
- 账户余额与充值记录
- 账单流水
- 企业认证
- 用户管理
- 系统日志
第一版不展示独立“账号设置”菜单,退出登录、修改密码统一放在右上角用户头像下拉菜单。
暂不开发:
- 彩信服务分组下全部菜单
3.2 运营端范围
保留并开发:
- 运营看板、发送监控、数据统计、客户管理
- 企业管理、企业应用管理、企业签名管理、企业模板管理
- 企业认证审核、短信审核、短信模板审核
- 短信通道管理、短信通道组管理
- 报备任务、报备记录
- 短信任务进度、短信记录、短信上行记录
- 充值记录、账单流水、计费规则
- 企业黑名单、全局黑名单、敏感词管理
- 用户管理、手机号段库、引流信息字段库
暂不开发:
- 彩信模板审核
- 彩信通道管理
- 彩信任务进度
- 彩信记录
- 企业应用管理的彩信应用 Tab
- 企业签名管理的彩信签名 Tab
- 企业模板管理的彩信模板 Tab
4. 核心业务流程
4.1 企业入驻与认证
- 客户端企业管理员提交企业认证资料。
- 运营端在企业认证审核中查看资料并审核通过或驳回。
- 审核通过后企业可创建短信应用、签名、模板并发起短信发送。
- 驳回时必须记录驳回原因,客户端可查看并重新提交。
4.2 短信应用管理
- 客户端创建短信应用,填写应用名称、场景、回调地址、限额等信息。
- 运营端可查看企业应用,并支持按企业名称、应用名称、状态搜索。
- 运营端可启用、停用、编辑应用。
- 应用必须归属企业,并关联后续签名、模板和发送任务。
- 短信应用必须配置发送队列等级:普通队列或优先队列。未配置时默认普通队列;优先队列用于验证码、登录确认、交易通知等高时效短信,普通队列用于营销、通知等常规短信。
- 发送队列等级属于真实业务配置,必须保存到后端数据库,并在客户端、运营端创建/编辑应用时展示和可修改;不得只作为前端展示字段。
- 运营端代企业新增短信应用时,第一步选择企业必须使用项目通用 Select/下拉控件,选项来自真实企业 API,支持加载中、空数据、错误态,不允许写死企业列表。
- 短信应用必须有独立 6 位数字 CMPP 接入账号
cmppAccount;运营端添加/编辑应用时可显式配置,留空时后端自动生成且全局唯一。 - 短信应用必须有应用级客户侧企业代码
cmppEnterpriseCode,运营端添加/编辑应用时可自定义;不得从上游通道SmsChannel.enterpriseCode透传。 - 短信应用接口密码
passwordCipher新建时默认随机生成 16 位 UUID 片段,运营端可手工修改;编辑时留空不覆盖原密码。 - 应用
AppID是平台内部应用标识,用于页面展示、复制参数和工单定位,不作为 CMPP bind/login 认证参数。 - 短信应用必须可配置客户侧 CMPP 最大连接数
cmppMaxConnections;客户侧提交窗口cmppWindowSize后端保留默认值,当前第一版不在运营端展示或要求运营配置,待 Gateway 入站侧按应用窗口真正限流后再开放为高级配置。 - 短信应用必须恢复设计基线中的“短信接口”开关,字段为
interfaceEnabled,默认开通;关闭后客户端/API 发送链路、客户侧 CMPP Gateway bind/login 和 submit 都必须被真实后端拒绝,不允许只在前端隐藏入口。 - CMPP 协议类型当前第一版仅允许
CMPP2.0,字段保持interfaceType=cmpp20;HTTP 不写入该字段,而是通过独立的SmsApplicationHttpConfig总开关和子能力配置开通。前后端仍必须拒绝把interfaceType直接改成http等无效协议值。
4.3 签名与引流信息
- 客户端和运营端新增、编辑短信签名时,签名名称必须填写完整中文黑括号格式,例如
【某某科技】;缺少括号、英文方括号、重复括号、空括号或括号外附加文本均不得提交。NestJS API 必须执行同样校验并以完整格式写入 PostgreSQL,不能只依赖前端按钮状态。 - 运营端企业签名管理查看签名资料。
- 签名需完成企业内部审核和通道报备,状态包括草稿、待审核、已通过、已驳回、报备中、报备通过、报备失败。
- 已通过且报备通过的签名才允许发送。
- 签名列表、详情、审核、报备、导入导出、模板选择和短信预览统一展示恰好一层完整黑括号;历史非规范签名通过 migration 规范化,任何页面不得再次拼成
【【签名】】。
4.4 模板管理与审核
- 客户端创建短信模板,填写模板名称、短信内容、变量、应用、签名。
- 短信模板必须选择签名,模板内容必须以完整中文括号签名
【签名】开头。客户端和运营端选择签名时自动把完整签名填入内容开头,切换签名时替换原前缀而不是重复追加;内容输入框明确提示该规则,字符数和计费条数按“签名 + 正文”完整内容计算。 - NestJS 创建、编辑和提交审核时必须校验所选签名属于模板企业/应用,且模板内容以该签名开头;不得只依赖前端自动填充。
- 系统校验敏感词、字数、变量格式和签名匹配。
- 运营端短信模板审核可通过或驳回模板。
- 运营端在企业模板管理中代企业添加的短信模板,保存后应直接置为已通过
approved;客户端自行创建并提交的模板仍按审核流程处理。 - 审核通过的模板才允许在短信发送中选择。
4.5 短信发送
- 客户端选择短信应用、签名、模板。
- 输入或导入接收手机号,支持手动输入和文件导入。
- 系统校验号码格式、黑名单、手机号段、企业余额/额度、模板变量。
- 客户端选择立即发送或定时发送。
- 提交后生成批量任务,任务进入待审核或待发送状态。
- 系统执行风控规则,命中拒绝规则则直接拒绝并记录原因,命中人工审核规则则进入运营端短信审核。
- 若命中审核策略,运营端短信审核通过后进入发送队列,审核页面必须展示进入审核的原因。
- 发送服务按通道组路由、通道限速、企业限速执行提交。
- 平台批量任务只记录客户端创建的发送任务;API 调用和 CMPP 对接发送不进入批量任务。
- 运营端和客户端“短信任务进度”只查询
SmsBatchTask.sourceType=client。 - CMPP/API/通道测试可使用内部批次承载风控、计费、队列、重试和回执关联,但不得出现在客户批量任务列表。
- 客户端批量任务列表、详情、短信明细和取消操作必须同时校验当前企业和
sourceType=client。
- 运营端和客户端“短信任务进度”只查询
- 所有来源的短信,包括平台批量任务、API 调用、CMPP 对接发送,全部按手机号维度进入短信记录。
- 任务进度、发送详情和短信记录实时或准实时更新。
- 企业应用“不符合模板的短信”配置为
manual_review时,合法的 CMPP Submit 在模板不匹配后进入人工审核;配置为reject时直接拒绝并返回REJECTDDeliver Receipt;配置为direct_send时必须识别并绑定已审核通过的完整括号签名,继续执行风控、余额、通道组路由、具体通道签名报备和 Gateway 真实提交,不得因模板未匹配落入reject,也不得绕过其他发送校验。- CMPP 入站模板匹配必须支持模板正文中的
${variable}占位符。固定文本需完整匹配,占位符至少匹配一个字符;同名占位符重复出现时取值必须一致。匹配成功后应绑定真实templateId,并将提取的变量值传入风控,不能只用整段正文数据库精确相等判断。
- CMPP 入站模板匹配必须支持模板正文中的
- CMPP 模板不匹配审核支持短窗口内容指纹聚合:只有同一企业应用、同一 CMPP 账号、规范化后内容 SHA-256 完全一致且位于同一时间窗口的短信才能合并为一个审核任务。默认窗口 10 秒,可通过
CMPP_TEMPLATE_REVIEW_WINDOW_MS调整。 - 聚合审核不合并短信记录、计费或回执:每个手机号仍有独立
SmsMessageRecord/messageId/sequenceId。审核通过后逐条进入真实路由和上游提交;审核驳回后逐条释放冻结并产生客户侧REJECTD回执。 - 人工审核只覆盖模板不匹配;签名必须以完整中文中括号前缀
【签名】识别,并使用包含中括号的完整名称匹配签名库。入站候选签名只要求auditStatus=approved,不得以全局reportStatus提前拒绝;报备通过状态必须在后续路由和最终提交前按具体通道校验。签名不合法、风控直接拒绝或余额不足不得因内容聚合而绕过。 - 发送入队必须按短信应用的队列等级分流到优先队列或普通队列;同等条件下优先队列消息必须先于普通队列消息被 Send Worker 消费并提交 Gateway。
- 优先队列只能改变待发送消息的调度顺序,不得绕过企业/应用状态、签名模板审核、通道报备、余额/授信、黑名单、风控、通道组路由、通道限速和 Gateway 连接可用性校验。
- 同一队列内部按创建时间、任务顺序和手机号拆分顺序保持 FIFO 或可解释的稳定排序;优先队列插队时必须可在 trace 或任务日志中追踪队列等级和入队时间。
4.6 通道配置与路由
- 运营端配置短信通道,包括通道名称、运营商、单价、网关地址、端口、企业代码、账号、密码、接入号、协议参数、启停状态。
- 通道运营商支持移动、联通、电信和三网;三网通道可作为移动、联通、电信的通配通道。
- 通道必须配置发送地区,发送地区为全国或 34 个省级地区之一;单个通道只能选择一个发送地区。
- 运营端配置短信通道组,通道组必须选择且只能选择一个运营商:移动、联通或电信;通道组不允许选择三网。通道组定义通道优先级、运营商分配、省网路由、全国路由、权重、失败补发开关和补发时间上限。
- 企业不配置默认通道组;企业应用必须单独配置至少一个运营商通道组,否则页面不可保存,发送时也必须直接失败。
- 一个企业应用可以分别绑定移动、联通、电信通道组;可以只绑定其中一类或两类,但不能一个都不绑定。
- 发送时先按运营商识别结果分流:移动短信走移动通道组,联通短信走联通通道组,电信短信走电信通道组;识别不出的号码走移动通道组。
- 运营商识别以可配置的号码前缀正则表达式为准,通常匹配手机号前 3 到 4 位;手机号段库只提供省份/城市识别,其 carrier 字段仅作后台校验或提示,不参与发送运营商判定。
- 预发布运营商规则按公开码号资料覆盖中国移动、中国联通、中国电信及其移动转售号段;中国广电
192号段按当前业务约定归入中国移动路由。 - 号码前缀表示原始码号分配关系;携号转网号码无法仅凭前缀识别当前签约运营商,如后续要求按实时在网运营商路由,必须接入可信的携号转网/HLR 查询能力。
- 预发布运营商规则按公开码号资料覆盖中国移动、中国联通、中国电信及其移动转售号段;中国广电
- 路由规则只能表达应用到通道组的绑定关系,不能直接绑定单个通道,也不在规则层配置省份;省份和全国路由在通道组内部处理。
- 发送服务必须使用手机号段库识别手机号省份和城市;无法识别省份时走对应运营商的全国通道组路由。
- 通道组内必须先匹配省网路由;省网未匹配时走同一运营商通道组内的全国通道。省网发送失败后,当前版本立即跳到该通道组第一个全国通道补发,不再尝试同省第二省网通道。
- 省网配置当前版本按通道组内省份唯一:同一个通道组内山东只能选择一个通道、河南只能选择一个通道,依此类推;该校验针对通道组明细里的省份配置,不是校验通道本体属性。省网明细的省份必须与引用通道的发送地区一致,例如山东省网不能引用发送地区为河南的通道。
- 全国通道可配置多个,并按优先级依次补发;同一通道组内全国通道优先级禁止重复,本期不支持权重分流。
- 通道组明细
carrier必须保留并参与发送逻辑,且必须等于通道组运营商。三网只允许作为通道本体能力SmsChannel.carrier=all,表示该通道可被运营分配到移动、联通或电信通道组;一旦放入某个通道组,只能服务该通道组所属运营商。 - 通道组明细引用通道时必须校验通道能力:移动组只能引用移动通道或三网通道,联通组只能引用联通通道或三网通道,电信组只能引用电信通道或三网通道。
- 未命中企业应用通道组或无可用通道时不得 fallback 到全局第一个 active 通道,应将该短信标记为 failed,并记录可读失败原因、trace 和系统日志;客户端本期不展示通道细节。
- 可发送通道必须同时满足:通道业务状态 active、CMPP 连接状态 connected、期望连接数大于 0、当前连接数大于 0、心跳未失败;auth_failed、heartbeat_timeout、reconnecting、disconnected、failed 或当前连接数为 0 的通道均不可被选中。Gateway 旧回写词 online/open 仅允许在 API 入库时兼容归一化为 connected,不作为发送判断状态。
- 多连接通道只要至少 1 条连接 connected 且可用即可参与路由;心跳失败应及时更新连接状态,连续 3 次心跳失败后进入重连,重连成功前不可发送。
- 新建或启用通道后,系统应立即创建/更新默认 CMPP 连接状态为 connecting 并通知 Go Gateway 发起连接;如果 connecting 超过 30 秒仍未收到 Gateway 回写 connected 或 failed,API 后台兜底任务必须将该连接标记为 failed,写入
lastError=Gateway connection request timed out after 30 seconds和连接日志。默认扫描间隔 5 秒,可通过环境变量调整。 - 通道异常时应支持熔断、降级、切换备用通道和失败重试;失败补发必须在同一企业应用授权的通道组范围内执行,并使用触发补发时的当前通道组配置,保证计费、退款、幂等和 trace 可追踪。
- 失败补发除以下情况外均应触发:短信状态为 unknown;距离客户提交时间超过 72 小时;距离客户提交时间超过通道组配置的补发时间上限;通道组关闭失败补发。
- 通道组补发时间上限由运营端配置,交互为“小时 + 分钟”,默认 12 小时 0 分钟,最小 1 分钟,最大不得超过 72 小时;真实发送链路按分钟级上限判断是否继续补发。本期不配置最大补发次数、补发间隔、失败类型白名单或人工重发能力,进入最终 failed/timeout 后不再人工重发。
- 提交 accepted 后立即按企业应用配置的客户费率扣费;补发过程中最终成功只扣一次,submit failed 未真正发出时释放冻结且不扣费;failed receipt 导致最终全失败时退款;本期客户计费不使用通道成本价。
- 签名全局
reportStatus仅用于运营汇总展示,不得作为发送的一票否决条件。签名部分通道报备通过时允许发送,但路由候选必须只包含该签名ChannelSignatureReportTask.status=approved的通道;主通道未通过而备用通道通过时允许选择备用通道。所有在线候选通道均未报备通过时拒绝发送,补发切换通道时必须重新执行相同校验。
4.7 通道签名报备
- 运营端先在“报备字段库”维护字段编码、名称、类型、是否必填等标准定义;字段代码只允许阿拉伯数字和英文大小写字母,字段类型只允许字符串、图片、文件三种。字段库支持将任意字段分别配置为“通用签名报备资料”或“通用引流信息报备资料”,通用配置可真实新增和删除。通道报备详情仍可选择包括通用字段在内的任意字段库字段,并指定用途为签名报备、引流信息报备或两者共用,不得在通道内另建同名孤立字段。
- 通道组配置通道,企业应用通过路由规则选择通道组。企业签名和引流信息添加/编辑时,系统必须合并“全局通用字段”和“企业应用 -> 生效路由规则 -> 通道组 -> 组内通道 -> 通道报备字段”;即使签名暂未绑定应用,也必须展示并校验对应类型的通用字段。
- 同一字段被多个通道引用时按字段库记录去重;任一通道将该字段配置为必填,则企业资料中按必填处理,并保留该字段来源的全部通道用于后续分别报备。
- 企业签名弹窗不再维护固定的签名依据、资质凭证、企业信息或责任人信息分组,所有签名报备资料均由通用字段和通道字段动态生成;每条引流信息同样只使用动态引流报备字段。文件字段走真实对象存储上传,其他字段保存真实值,必填校验同时在运营端、客户端和 NestJS API 执行。
- 签名动态资料保留在签名记录;每条引流信息必须保存为独立
SmsDrainageInfoPostgreSQL 实体,包含所属企业、签名、应用、站点、地址、动态字段、审核状态和驳回原因,不得再以签名 JSON 数组作为引流审核事实来源。 - 客户端上传签名资料后进入签名审核;签名审核通过后方可新增引流信息。客户端新建或修改引流信息均自动进入
pending,运营端必须在独立“引流信息审核”页面查看资料后通过或带原因驳回。运营端在企业签名管理中新增或修改引流信息视为运营操作,自动审核通过并写审核记录。 - 引流信息审核通过前不得创建新的通道报备任务、写入可导出的引流报备材料或人工修改通道报备状态;已报备引流信息再次修改时,原通道任务冻结为
waiting_review且旧材料停止使用。审核通过后系统按应用当前真实路由通道生成/重置reportType=drainage任务与材料,并写报备记录。 - 运营端在报备任务或通道报备详情页导入通道回执,系统根据回执同步签名在各通道的报备状态。
- 报备记录保留每次导出、导入、状态变更和操作人。
- 发送前必须校验最终选中通道上的签名报备任务为 approved;补发切换到新通道时必须重新按新通道校验报备状态,未通过则该次发送失败。
- 企业签名页、通道报备详情页和报备任务页均允许人工修正报备状态,但三个入口必须操作同一份
ChannelSignatureReportTask通道级事实并写ChannelSignatureReportRecord;企业签名页修改时必须展示应用当前通道组内的具体通道矩阵,不允许直接修改移动/联通/电信汇总标签。 - 每次人工状态变更或回执导入后,系统必须按应用当前生效路由规则重新汇总各运营商目标通道状态和签名全局
reportStatus。新增目标通道但尚无任务时按未报备计入分母;移出当前配置的历史通道不参与当前汇总,但任务和记录继续保留。 - 报备任务和报备记录页面必须可按签名/引流信息类型筛选,并显示引流信息的站名称、地址和所属签名。报备记录查询必须关联真实任务、通道和引流实体,完整展示审核通过后创建、重置、冻结、导出、回执和人工状态变化。
4.8 CMPP Gateway 与外部接入
第一版发送链路必须区分两类 CMPP 连接,不允许用页面状态、HTTP 占位或模拟器结果替代真实协议能力:
- 上游通道连接:平台作为 SP 客户端,连接运营商或供应商 SMSC,将平台已路由的短信提交到目标通道。
- 下游客户接入:企业客户作为外部 SP 客户端,连接平台暴露的 CMPP 监听端口(生产端口
17890),通过 CMPP 协议向平台提交短信。
4.8.1 上游通道连接能力
- Gateway 必须按运营端通道配置连接上游 SMSC,使用通道的
gatewayHost/gatewayPort/account/passwordCipher/srcId/cmppVersion完成 CMPP 2.0/3.0 connect/login。- 运营端通道创建/编辑必须提供 CMPP 2.0/3.0 版本选项,默认 CMPP 2.0;保存后 Gateway 连接与 SubmitCommand 均必须使用真实保存的
cmppVersion。 - 运营端通道“发送测试”必须走真实闭环:前端提交手机号和短信内容到 NestJS API,后端校验通道在线后创建独立
SmsMessageRecord、SmsSubmitRecord,并向 Redis Streamgateway.submit.commands写入真实SubmitCommand;短信记录页面必须能查询到测试短信,不允许只返回占位成功或只关闭弹窗。 - 通道测试短信是运营侧上游通道连通性验证,不绑定企业、企业应用或
SmsBatchTask发送任务;submit result 和 receipt 只记录在短信记录、提交记录、回执记录和分片审计中,不触发客户侧 Deliver 推送、企业账务、任务进度或业务补发。
- 运营端通道创建/编辑必须提供 CMPP 2.0/3.0 版本选项,默认 CMPP 2.0;保存后 Gateway 连接与 SubmitCommand 均必须使用真实保存的
- Gateway 必须校验上游 connect/login 返回码,区分 connected、auth_failed、connect_timeout、network_error、protocol_error 等状态,并回写 NestJS 真实连接状态。
- Gateway 必须支持每个通道配置期望连接数,建立多条长连接,并按连接维度维护 currentConnections、lastConnectedAt、lastHeartbeatAt、lastError、reconnectCount。
desiredConnections、windowSize是平台对上游通道连接池和提交窗口的运行配置,必须通过运营端通道配置页面保存到真实后端;它们不是 CMPP 标准 PDU 字段,也不是 gocmpp 的原生配置字段。
- Gateway 必须实现 ActiveTest 心跳与超时检测;连续心跳失败后连接进入 heartbeat_timeout/reconnecting,重连成功前该连接不可参与发送。
- Gateway 必须支持断线自动重连、指数退避或固定退避、最大重试间隔、重连日志和状态回写。
- Gateway 必须维护 CMPP sequenceId 与平台 messageId、submitId、channelId 的映射,submit resp 和 deliver 回执必须能追溯到原短信记录和提交尝试。
- Gateway 必须实现真实 CMPP Submit,包括短信内容编码、长短信拆分、RegisteredDelivery、serviceId、srcId、destTerminalId、msgFmt、feeType/feeCode 等字段映射。
- Gateway 必须消费 NestJS 投递的
SubmitCommand队列或等价内部接口;提交成功、提交失败、超时均必须回传SubmitResult,不得只停留在 API 侧入队。 - Gateway 必须按通道连接和窗口容量控制并发,处理窗口满、SMSC 慢响应、sequence 回绕、连接断开时的在途消息状态。
- Gateway 必须对每个物理通道执行 Redis 分布式 TPS 限速。
SmsChannel.rateLimitPerSecond是单通道上限,不是平台总上限;同一通道被多个通道组或多个 Gateway 实例使用时共享同一额度,不同通道独立计数。通道连接命令下发的配置值是最终上限,提交命令携带的值只能进一步降低、不能放大该上限。超流速消息必须继续保留在 Redis Stream pending 中等待可用时隙,不能因等待直接标记发送失败;Gateway 重启后仍可由 consumer group 恢复。worker 必须并发处理一个读取批次,让不同通道独立等待,不能因低 TPS 通道造成其他通道队头阻塞;单通道实际并发仍由 Redis 限速和 CMPP 窗口共同约束。 - Gateway 不承担业务审核、计费、签名报备、通道组路由、黑名单或敏感词判断;这些由 NestJS 完成,Gateway 只执行已授权通道提交与协议事件回传。
4.8.2 下游客户 CMPP 接入能力
- Gateway 必须监听生产 CMPP 端口
17890,作为平台侧 CMPP Server 接收企业客户系统连接;该端口不是 HTTP 健康检查或控制接口。 - Gateway 必须按企业应用生成的 CMPP 接入参数校验客户 connect/login,包括客户侧企业代码、账号、密码、CMPP 版本、源 IP 白名单、短信接口开关、应用状态、企业状态、连接数上限;客户侧企业代码必须来自
SmsApplication.cmppEnterpriseCode,且interfaceEnabled=false时必须拒绝新的鉴权。已完成 bind 的连接若随后被停用,其后续参数合法 Submit 必须按业务失败记录并通过 Deliver Receipt 回执。Gateway 必须根据 CONNECTVersion为每条 TCP 连接独立协商 CMPP2.0/2.1/3.0 解包与响应类型,不得用固定 CMPP3.0 结构解析 CMPP2.0 Submit。 - 企业应用列表中的 CMPP 连接数和连接状态必须表示客户连接到 Gateway
17890的下游 CMPP 会话,不得复用上游SmsChannel连接状态。Gateway 必须在 bind、CMPPACTIVE_TEST、Submit、Deliver 写入失败等事件时回写应用级连接记录;列表只将最近心跳未超时的会话计为正常连接。超过心跳阈值的会话必须标记为心跳超时/断开,并保留远端 IP、协议版本、建立时间、最后心跳、最后 Submit、最后 Deliver 与断开原因供运营查询。 - 客户端应用的 IP 白名单必须对 CMPP 下游连接生效;未命中白名单、应用停用、企业停用、密码错误、超过连接数上限时必须拒绝连接并记录系统日志。
- Gateway 必须维护应用级下游连接状态,回写 applicationId、tenantId、connectionId、currentConnections、desiredConnections、lastHeartbeatAt、lastError,运营端企业应用列表和连接详情必须来自这些真实状态。
- Gateway 必须实现下游 ActiveTest、Terminate、异常断开处理;断开后连接数和状态必须及时回写。
- Gateway 必须处理客户提交的 CMPP Submit,将手机号、内容、源地址、企业应用、客户消息序号等转换为平台发送请求。应用身份必须来自当前已鉴权 TCP 连接绑定的
cmppAccount;SubmitMsgSrc是独立的企业代码,必须与该应用cmppEnterpriseCode匹配,不得把MsgSrc当作登录账号查找应用。 - 下游 CMPP Submit 进入平台后,不创建客户可见的批量发送任务,但必须按手机号维度创建
sms_message_record,source 标记为cmpp,并保留客户侧 sequence/msgId 映射。系统可使用内部批次承载计费、风控和队列,不得把该内部批次误展示为客户端手工批量任务。 - 下游 CMPP Submit 必须复用 NestJS 发送前校验:企业/应用状态、IP 白名单、签名/模板报备、模板匹配策略、风控、黑名单、余额/授信、运营商识别、通道组路由。
- 对客户 Submit 的响应必须符合 CMPP 协议:鉴权失败、账号或源 IP 不合法、PDU/手机号等参数不合法时直接返回非零 SubmitResp,且不创建短信记录。账号已识别且参数合法后必须先创建可追踪平台 messageId 和短信记录,再返回成功 SubmitResp;模板/签名/报备、风控、余额、应用后续停用、无可用通道以及上游最终提交失败等业务失败必须保存真实失败记录,并通过客户侧 Deliver Receipt 返回失败,不得因业务校验失败丢失客户 Submit 审计。
- Gateway 必须支持平台最终回执向下游客户连接投递 Deliver Receipt;若客户连接已断开,应按策略缓存、重试或记录投递失败,不能丢失平台最终状态。
- Gateway 必须支持下游客户上行接入场景:收到运营商上行后,按接入号、手机号、应用、时间窗口匹配并向客户连接推送 Deliver,上行同时入库。
- 下游客户连接与上游通道连接必须隔离管理:客户侧账号密码不能用于连接上游通道,上游通道账号密码也不能作为客户接入凭据。
- Gateway 必须为客户侧 CMPP2.0/3.0 Submit 记录可检索日志:收包时记录协议版本、账号、客户 IP、sequenceId、号码、srcId、编码、分片序号和内容长度;响应时记录 result、平台 messageId、CMPP Msg_Id、耗时和失败阶段。NestJS 拒绝 Submit 时,Gateway 日志必须保留 API 返回的真实业务原因,不能只记录 HTTP 状态码;短信正文不得明文写入 Gateway 日志,仅记录字符数和哈希。
- 客户已按 CMPP 标准 UDH 拆分的下游长短信必须先重组再进入业务发送链。Gateway 应识别 8 位
05 00 03和 16 位06 08 04拼接头,去除 UDH 后按MsgFmt解码正文,并将引用号、总片数、片序号和编码传给 NestJS;NestJS 必须在 PostgreSQL 持久化分组和分片,支持乱序、同片幂等、冲突片拒绝、进程重启恢复和超时终止。分片未齐全前不得创建内部批次、SmsMessageRecord、计费或路由;齐全后只创建一条完整正文主记录,并使用第一片Sequence_Id建立客户消息映射。每个合法分片仍应分别取得一个CMPP_SUBMIT_RESP,但不能把 UDH 字节作为正文、不能按分片生成多条短信记录。
4.8.3 回执、上行与幂等
- Gateway 必须解析上游 deliver receipt,将 DELIVRD、UNDELIV、EXPIRED、REJECTD 等供应商状态归一化为平台 delivered、failed、unknown、timeout 等状态。
- Gateway 必须解析上游普通 deliver 上行短信,携带手机号、接入号、内容、接收时间、通道和原始报文摘要回传 NestJS。
- Gateway 回传
SubmitResult、ReceiptEvent、UplinkEvent时必须包含 traceId、messageId、submitId 或可映射字段、channelId、gatewayMessageId、sequenceId,保证短信详情能展示完整历史尝试。 - 重复 submit resp、重复 receipt、迟到旧通道 receipt 必须交由 NestJS 幂等处理;Gateway 不得因本地缓存丢失而生成无法追踪的重复业务事件。
- Gateway 需要保留最小运行日志和指标:连接数、登录失败次数、心跳失败次数、submit TPS、submit resp 延迟、receipt 延迟、队列积压、重连次数、协议错误。
- Gateway 控制服务
/health只能表示进程存活;真实验收必须检查上下游连接状态、队列消费、submit/receipt/uplink 事件闭环。
4.8.4 当前实现缺口标记
截至当前版本,Go Gateway 已有 HTTP 控制服务、健康检查、连接上游 SMSC 的 ConnectChannel 控制入口、gocmpp 协议 spike、队列消息结构,并已补齐第一阶段下游 CMPP 入站、上游提交和下游回执/上行推送能力:
- 已实现
17890入站 CMPP Server 监听,生产部署由GATEWAY_CMPP_ADDR=0.0.0.0:17890启动。 - 已实现下游客户 connect/login 鉴权:CMPP
Source_Addr使用企业应用独立 6 位cmppAccount,密码使用应用 CMPP 参数中的passwordCipher,Gateway 将 CMPPAuthSource/Timestamp交由 NestJS 根据真实数据库校验。 - 已实现客户端应用 IP 白名单、应用状态、企业状态和企业认证状态校验;校验失败返回 CMPP connect 失败。
- 已实现下游 CMPP Submit 到平台发送请求的转换:Gateway 解码 CMPP 3.0 submit 内容,调用 NestJS 真实入站接口,NestJS 复用模板/签名/风控/余额/路由/队列优先级发送链路,接受后返回 CMPP submit_resp。
- 已实现 NestJS 校验通过后的异步 Gateway 提交:
SubmitCommand保留 BullMQ 审计/兼容投递,同时写入 Redis Streamgateway.submit.commands主命令流;Go Gateway 以 consumer group 独立消费该命令流,携带通道gatewayHost/gatewayPort/account/passwordCipher/cmppVersion作为 SP 客户端连接上游 SMSC 并发送 CMPP Submit。 - 已实现上游 submit_resp 回传:Gateway 将 accepted/rejected/timeout 转换为
SubmitResult调用 NestJS,NestJS 继续执行 accepted 扣费、失败/超时补发或释放冻结等既有逻辑。 - 已实现上游 deliver receipt 和普通 deliver 上行解析:Gateway 在上游连接读循环中解析 receipt/uplink,调用 NestJS
/gateway/events/receipt、/gateway/events/uplink写入真实发送记录、回执和上行表。 - 已实现上游长短信第一版拆分和上行长短信重组:Gateway 按 CMPP 标准 6 字节 UDH 将超过 140 字节的 Submit 内容拆成多个分片,设置
PkTotal/PkNumber/TpUdhi后逐包提交;上游普通 Deliver 携带 UDH 分片时,Gateway 在同一连接内按通道、主叫、被叫、引用号和总片数缓存并重组后再回传 NestJS。 - 已实现上游多连接和窗口控制第一版:NestJS 将通道配置中的
desiredConnections/windowSize写入SubmitCommand.upstream,Go Gateway 按通道建立连接池,每条连接独立维护 submit pending 映射、receipt/uplink 处理和窗口令牌;窗口满时等待可用窗口或按提交超时返回。 - 已实现 SubmitCommand 在途恢复第一版:Go Gateway submit worker 在消费新消息前会对 Redis Stream consumer group 中空闲超过阈值的 pending 命令执行
XAUTOCLAIM,重新提交并按正常成功路径 ack,避免 Gateway 重启后命令永久滞留在 PEL。 - 已实现上游连接断开时的 pending submit 状态补偿第一版:如果某条上游 CMPP 连接在收到 submit resp 前断开,Gateway 会立即唤醒该连接上等待中的 pending submit,请求返回
timeout/CONNECTION_LOST,由 NestJS 进入既有补发或释放冻结逻辑,不再只依赖固定超时。 - 已实现“上游可能已受理但 submit resp 丢失”场景的保守补偿第一版:Gateway 在 receipt 事件中补充手机号;NestJS 对无法按
messageId/gatewayMessageId精确命中的回执,只在“同通道、同手机号、72 小时窗口内、且仅存在 1 条timeout + gatewayMessageId=null的 submit 记录”时才回填并接收该回执,避免误绑到其他短信。 - 已实现 Gateway 提交异常治理:Go Gateway 对多次处理仍失败的
SubmitCommand不再无限滞留在 PEL,而是按阈值写入 NestJS 真实GatewaySubmitDeadLetter表(数据库表名和内部接口保留技术兼容名,页面统一称“Gateway提交异常”)。运营端/admin/gateway-submit-exceptions提供真实分页、筛选、汇总、脱敏详情和单条重新入队;原始 payload、密码、密钥不得返回浏览器。重新入队必须要求近期认证、填写原因、勾选“已确认上游未受理”,并校验短信尚未 accepted/submitted/delivered/unknown、通道 active 且 connected、人工次数小于 3;服务端以 pending 到 requeueing 的原子状态抢占防止重复点击,成功写回 Redis Stream 后记录操作人、原因、Stream ID 和时间。收到后续 SubmitResult 时必须将对应异常记录闭环为 resolved。 - Gateway 提交异常重新入队必须使用“异常记录 ID + 下一次人工次数”的稳定幂等键,通过 Redis Lua 原子完成“检查幂等键、XADD、保存 Stream ID”;API 在 XADD 成功后宕机或数据库落账失败时,超时恢复扫描必须复用同一幂等键完成落账,不能再次产生 Stream 消息。Gateway 重复上报同一个原始 Stream 异常不得把
requeued/resolved回退成pending。 - 已实现 Gateway 通道级 Redis 限速:NestJS 入队前保留业务层通道限速,Go Gateway 在真正调用上游 Submit 前再次按通道 ID 预约发送时隙;连接命令把权威 TPS 写入 Redis,提交按权威值与消息值的较小者执行。普通 Stream 消息在等待期间不 ACK、不转失败,多实例共同使用同一限速状态;worker 对同批消息并发调度,低 TPS 通道等待不阻塞其他通道。
- 已实现 Gateway 重启后的 active 上游通道恢复:部署先重启 Gateway 再重启 API;API 启动后从 PostgreSQL 读取 active 通道,重新下发连接命令,恢复 Gateway 内存连接池、真实连接状态和 Redis 权威 TPS key,不得继续沿用重启前的 connected 状态。
- 已实现客户侧下游投递重试第二版:客户系统负责断线后重连;平台在客户离线或投递失败时把 Deliver Receipt/上行 Deliver 保留在
CmppDownstreamDelivery,客户 bind 成功后立即拉取 pending,且 Gateway 会对当前在线账号周期补投;超过重试上限后转failed并写失败审计。 - 已实现下游投递失败审计与人工重投第一版:运营端后端与页面可分页查看
CmppDownstreamDelivery的 pending/awaiting_ack/failed/unconfirmed/rejected/delivered 记录,支持按状态、类型、应用和关键字筛选,并可对非awaiting_ack记录执行人工重投,真实调用 Gateway/downstream/receipt或/downstream/uplink。主记录必须分开保存自动重试次数retryCount、人工重投次数manualRetryCount和最近人工重投时间lastRetriedAt,操作日志保留重投前状态与自动重试次数。 - 同一下游投递的人工重投必须使用
id + status + updatedAt条件更新原子认领;认领后先进入manual_requeueing,避免运营并发请求或 Gateway pending 恢复扫描同时双发。调用完成后进入awaiting_ack或失败状态;进程中断超过默认 2 分钟后自动转回pending,由 Gateway 单一路径恢复投递。阈值可通过CMPP_DOWNSTREAM_MANUAL_REQUEUE_STALE_MS调整。 - 已实现下游投递批量重投第一版:运营端可在当前页勾选多条
pending/failed下游投递记录,调用真实批量接口逐条重投并返回成功/失败汇总,不允许用前端循环假装成功。 - 下游投递的
pending展示必须结合真实尝试字段:自动与人工次数均为 0 时显示“待首次投递”,retryCount > 0时显示“等待自动重试”,manualRetryCount > 0时显示“人工重投排队中”。人工重投可重置新一轮自动重试预算,但不得把记录伪装成从未投递。 - 下游投递不得无限停留在
pending:Gateway 对未写出的结果必须返回明确的retryable/reasonCode/errorMessage。状态回执在原消息映射已经丢失且缺少submitSequenceId时属于不可恢复错误,立即转为failed并保留失败原因和操作日志;客户端暂时离线属于可恢复错误,按既有重试次数与指数退避处理。所有 pending 从创建时间或最近人工重投时间起最多保留 72 小时,可通过CMPP_DOWNSTREAM_PENDING_TIMEOUT_HOURS调整,超时后自动终结为failed。 - 新产生的客户侧状态回执投递 payload 必须保存短信原始
cmppSubmitSequenceId,使 Gateway 重启后仍能结合账号和平台消息 ID 重建与原CMPP_SUBMIT_RESP一致的非 0Msg_Id;不得用伪造或为 0 的Msg_Id冒充成功投递。 - 已实现下游投递告警统一口径:运营看板、下游投递 Dashboard 和应用告警排行必须基于同一组真实
CmppDownstreamDelivery条件统计:pending超过积压阈值、awaiting_ack超过ackDeadlineAt,以及最近失败窗口内的failed/unconfirmed/rejected。默认积压阈值为 10 分钟,最近失败窗口为 1 小时,可分别通过CMPP_DOWNSTREAM_ALERT_PENDING_MINUTES和CMPP_DOWNSTREAM_ALERT_RECENT_FAILED_HOURS覆盖。右上角“待审核任务”仅汇总审核业务,不显示下游投递告警菜单或数字。 - pending 积压告警必须从本轮排队开始时间计算:从未人工重投的记录使用
createdAt,人工重投记录使用lastRetriedAt;刚执行人工重投的记录不得因旧createdAt立即重新计入积压告警。 - 已实现下游投递 Dashboard 第一版:运营端“下游投递记录”页面顶部新增真实聚合总览,直接按
tenantId/applicationId/deliveryType统计投递总量、pending/awaiting_ack/delivered/failed/unconfirmed/rejected、积压告警、ACK 超时告警、按类型分布、重试压力分布和应用告警排行,数据源必须来自CmppDownstreamDelivery,不能靠前端本地汇总。应用告警排行只统计满足统一告警时间窗的记录,不得将新创建的pending或超出最近窗口的历史失败永久累加为告警。 - 已实现下游连接映射持久化第一步:Gateway 在客户 CMPP 账号 bind 成功、下游 submit 建链和回执/上行下发时,会把账号在线状态、实例标识、最近活跃时间写入 Redis presence;该状态不再只保留在 Gateway 进程内存中,为后续“Gateway 重启后的 pending 恢复”提供外部状态基础。
- 已实现下游连接映射持久化第二步:Gateway 启动时会读取 Redis presence 与当前内存在线账号,形成“恢复候选视图”,并通过控制面
GET /downstream/recovery-candidates暴露候选账号列表,供后续恢复逻辑与运维排查使用;本阶段仍不等同于自动恢复 pending 投递。 - 已实现下游 pending 恢复执行第一版:Gateway 启动后会立即按恢复候选账号拉取真实
CmppDownstreamDelivery.pending,后续每轮补投周期也会继续扫描恢复候选;若账号已有可用下游连接则继续推送回执/上行,若账号尚未重连则保持pending等待后续恢复,不能因为 Gateway 重启就把未投递记录误标成失败。 - 已实现下游恢复控制第一版:Gateway 对恢复候选账号增加账号级恢复锁、失败/等待连接退避和恢复状态持久化,避免同一账号被并发重复恢复或每轮高频空转;控制面新增
GET /downstream/recovery-statuses可查看最近一次恢复状态、重试次数、下一次可恢复时间和错误原因。 - 已实现下游恢复观测第一版:控制面新增
GET /downstream/recovery-overview,一次性返回恢复候选账号与恢复状态,便于联调和生产排查。 - 已实现下游恢复状态回流第一版:Gateway 在每次恢复状态变化后,调用 NestJS
/api/gateway/events/downstream/recovery-status真实回传账号恢复状态;NestJS 将状态写入 Prisma/PostgreSQLGatewayDownstreamRecoveryStatus。 - 已实现下游恢复状态运营化第一版:运营端新增独立“恢复状态管理”页面,支持真实列表、分页、详情查看和当前筛选结果 CSV 导出;原“下游投递记录”页面只保留投递记录本身,不再混放恢复状态区块。
- 已实现下游恢复失败分类第一版:Gateway/NestJS 共同维护
failureCategory,覆盖client_disconnected、backoff、lock_contended、lock_lost、flush_failed、partial_delivery_failed、unknown;运营端“恢复状态管理”页面支持失败分类筛选、分类分布统计、详情展示和导出字段。 - 已实现多 Gateway 恢复抢占协调第一版:恢复锁从单纯实例名升级为 Redis token 租约,状态记录
lockOwner/lockExpiresAt;恢复完成时必须通过 Lua 原子校验锁 token,只有持锁实例才能写入最终恢复状态并释放锁,避免旧实例超时后误删新实例锁或覆盖新实例恢复结果;运营端详情/列表可查看锁持有实例。 - 已实现长短信分片审计第一版:Gateway
SubmitResult回传真实segments[],包含segmentTotal/segmentIndex/sequenceId/gatewayMessageId/submitStatus/submittedAt;NestJS 写入 Prisma/PostgreSQLSmsMessageSegmentAudit,回执按gatewayMessageId回填分片回执状态,补偿归因可记录compensationType;运营端短信记录详情可查看真实分片提交、回执和补偿审计。 - 下游投递重试已改为指数退避第一版:首次失败后按基础间隔重试,随后按 2 倍递增,并受最大退避上限约束,避免客户长时间离线时平台每分钟机械重试。
- 下游状态必须以客户确认作为终态:Gateway
SendPkt成功后只能写awaiting_ack,仅收到匹配连接、Sequence_Id、Msg_Id且CMPP_DELIVER_RESP.Result=0后才能写delivered;超时、非零 Result 和历史未留存 ACK 的记录分别按未确认、拒绝或历史未确认展示,不能再把 TCP 写出冒充客户已收到。 - 企业应用必须分别提供“回执自动重试投递”和“上行短信自动重试投递”开关,默认开启。开关按投递创建时快照保存;关闭只阻止已经写出但未获 ACK/被拒绝后的自动重发,不阻止离线队列在客户首次上线时完成首次投递。手工重投不受开关限制,但必须提示重复业务处理风险并二次确认。
- 客户 Submit 的失败状态回执必须严格晚于对应
CMPP_SUBMIT_RESP写出,且 Deliver 中的业务Msg_Id必须非 0、与该 SubmitResp 返回的Msg_Id完全一致;Result=0但Msg_Id=0只能表示客户端协议栈收包,不能标记业务回执已确认。平台必须持久化原 Submit Sequence_Id,使 Gateway 重启或客户重连后的补投仍可重建相同业务Msg_Id。 - 每一次客户侧 Deliver 投递都必须单独持久化发送时间、物理连接 ID、
Sequence_Id、业务Msg_Id、ACK 截止时间和DELIVER_RESP结果;运营端详情可按尝试查看,不得只保留最后一次结果而覆盖历史。客户连接关闭时必须清理该连接的发送时序状态;无法匹配的DELIVER_RESP也必须写通讯日志。 - 及时失败回执必须使用“当前客户物理连接上的 SubmitResp 写出屏障”:从开始处理 Submit 到该包
CMPP_SUBMIT_RESP实际写出前,同一连接不得下发该 Submit 产生的失败 Deliver;长短信以最终分片 SubmitResp 写出为释放时点。屏障只控制协议写出顺序,不修改通道路由、连接配置或连接池。 - 供应商 Deliver Receipt 必须先持久化到幂等收件箱,再返回成功
CMPP_DELIVER_RESP;持久化失败时不得向供应商虚假确认成功。收件箱异步匹配内部短信,支持失败退避、最大尝试/存留时间和进程异常后的processing超时恢复。 - 上游长短信必须在每个分片收到
SubmitResp后立即保存该分片的Sequence_Id、供应商Msg_Id、提交状态和时间,不能等待全部分片完成才批量落库;后到的提交成功聚合结果不得覆盖已经由早到失败回执形成的终态。 - 运营端允许对
delivered(客户端已确认)记录再次手工重投,但单条和批量入口都必须明确提示可能造成下游重复处理;awaiting_ack状态在确认窗口内不得并发重投。 - 已实现客户侧最终 Deliver 推送的第一版能力:Gateway 在下游 Submit 被接受后记录 messageId 到客户连接的内存映射;NestJS 收到最终 receipt/uplink 并入库后调用 Gateway
/downstream/receipt、/downstream/uplink,Gateway 向仍在线的客户 CMPP 连接下发 Deliver Receipt 或普通 Deliver。 - 已实现客户侧 Deliver 持久化第一版能力:NestJS 收到最终 receipt/uplink 后写入
CmppDownstreamDelivery待投递记录;在线推送成功标记 delivered,客户断线或 Gateway 不可达时保留 pending 并记录 retry 信息;客户重新 bind 后 Gateway 按账号拉取 pending 记录补发。 - 已实现普通上行匹配与人工认领第一版:优先按 messageId 精确匹配;无 messageId 时按接入号匹配应用路由;仍无唯一应用时按手机号和最近下发时间窗口匹配;多候选标记 ambiguous 并写入
SmsUplinkMatchCandidate候选,运营端可人工认领候选应用/下发记录;认领后更新上行记录、保留候选审计,并创建真实客户侧上行 Deliver 投递记录。
仍缺少生产完整闭环能力:
- 应用级下游连接数限制和连接状态回写尚未完整产品化。
- 当前下游 CMPP Submit 通过
sourceType=cmpp的系统批次兼容承载,尚未拆成完全独立于批量任务模型的单条发送模型。 - Gateway 控制面
/upstream/submit仅保留为本地调试、人工补偿和运维验证入口;生产主链路应由 Gateway submit worker 消费 Redis Streamgateway.submit.commands触发。 - 客户侧 Deliver 重投当前已经具备账号级周期恢复、退避、Redis token 租约锁和控制面状态观测;恢复状态已同步到 NestJS 持久化审计表并进入运营端独立页面,且具备第一版失败分类分析和多 Gateway 抢占协调。
- 普通上行匹配已覆盖 messageId、接入号、手机号时间窗口和共享接入号多候选人工认领;后续仍需补更复杂的批量认领、认领规则推荐和认领准确率指标。
- 长短信分片当前已具备真实分片提交/回执/补偿审计,运营端短信详情可查看
SmsMessageSegmentAudit;后续仍需补“按单个分片自动重投”和分片级人工补偿操作。 - 多连接窗口当前覆盖单进程内连接池和窗口满等待;在途恢复当前覆盖 Redis Stream pending claim、连接断开时的 pending submit 唤醒、receipt 驱动的保守唯一候选补偿、Gateway 提交异常转存/安全人工重新入队,以及下游客户在线时的周期补投、Gateway 重启后的恢复候选扫描、账号级 Redis token 租约锁/退避/状态观测、恢复状态入库/运营端可视化;尚未实现连接级状态持久化、窗口指标回写、提交异常后台自动重试策略、长恢复任务锁续租,以及“上游已受理但 submit resp 丢失”场景的强确认或完整幂等补偿。
基于当前真实代码,CMPP 端到端链路剩余缺口可以明确收敛为以下几类:
- 下游恢复审计产品化:
- 恢复状态已能写回 NestJS/Prisma/PostgreSQL,并已在运营端“恢复状态管理”页面提供独立列表、详情、导出和失败分类分布。
- 后续仍需补恢复吞吐、趋势、耗时、连续失败账号等更细指标看板。
- 高阶幂等与跨实例协调:
- 多 Gateway 实例下的账号级恢复抢占协调已具备 token 租约和完成校验;后续仍需增强分片级/消息级更细粒度去重,以及长恢复任务中的锁续租。
- “上游已受理但 submit_resp 永久丢失”的强确认补偿还不完整。
- 分片和复杂场景补偿:
- 长短信分片级提交明细、回执和补偿归因已进入真实审计表和运营端短信详情;后续仍需补分片级自动重投/人工重投。
- 共享接入号、多候选普通上行已具备第一版人工认领和认领后下游投递;后续仍需补批量认领、人工认领复核和指标分析。
- 运营观测与指标:
- 窗口利用率、连接级心跳、恢复吞吐、恢复耗时趋势等指标尚未回写到运营端真实页面。
这些缺口未补齐前,可以把客户 connect/login、IP 白名单、客户 submit 入平台、NestJS 业务校验、Gateway 上游连接池 submit、窗口满等待、长短信基础拆分/重组、submit_resp、receipt/uplink 入库、分片级提交/回执/补偿审计、共享接入号上行人工认领,以及在线或重连客户 Deliver 推送、Gateway 重启后的 pending 恢复、恢复状态入库、运营端恢复状态独立页/详情/导出和失败分类分布作为第一版真实链路验收;不能把连接级指标回写、分片级自动重投、批量认领和认领指标分析作为“生产已验收通过”。
4.9 回执与上行
- 通道回执接入后更新发送记录状态。
- 回执状态至少包括提交成功、提交失败、发送成功、发送失败、未知、超时。
- 提交后 72 小时仍为未知的短信转超时,返回客户失败。
- 超时前允许人工同步、重新拉取回执或接收供应商二次回执;所有二次回执必须记录历史。
- 旧通道迟到的 failed receipt 如果对应短信已经由新通道 delivered,不得覆盖短信记录最终 delivered 状态;短信详情弹窗必须能看到该历史 failed receipt。
- 重复提交结果或重复回执必须幂等处理,不得重复扣费、重复释放冻结或重复退款。
- 上行短信接入后优先按手机号、接入号、企业/应用、时间窗口匹配下发记录。
- 接入号匹配不到企业/应用时,展示近期平台对此手机号下发的短信供人工判断。
- 完全匹配不到的上行短信仍需入库,并展示为未匹配。
- 客户端可查看本企业上行短信,运营端可查看全平台上行短信。
4.10 账户计费
- 客户端可查看现金余额和充值记录;充值由运营端人工入账。
- 发送创建时按短信内容计费条数和企业应用客户单价生成预估费用,计费条数只按 70/67 字规则拆分;不按移动、联通、电信配置不同客户价。
- 平台发送前只判断
现金余额 + 授信额度 > 0;授信额度可为正数、负数或 0,和小于等于 0 时禁止发送。 - 发送链路需记录计费条数、计费单价、计费金额、账务状态。
- 账单流水与短信记录可追溯关联,支持按企业、应用、任务、手机号、时间对账。
- 最终失败、超时失败需要退费。
- 三网通道成本只用于平台内部成本核算,不影响客户扣费金额。
- 当前版本计费口径固定为提交 accepted 扣费、最终 failed receipt/timeout 退款。
- 所有面向用户展示的金额、余额、充值金额和单价统一以人民币元展示并固定保留四位小数;内部使用
0.0001 元整数金额单位持久化,不以浮点数执行账务计算。 - API 必须定时扫描提交成功但超过 72 小时仍未收到明确最终回执的短信,转为 timeout 并退还已扣金额;扫描需覆盖
submitted和unknown,且用条件更新避免多实例重复退款。
5. 功能需求
5.1 客户端工作台
- 展示现金余额、今日发送量、成功率、待审核事项、快捷入口。
- 展示最近发送批次、发送趋势、签名/模板状态。
- 数据范围限定为当前企业。
5.2 客户端短信发送
- 支持选择短信应用、签名、模板。
- 支持模板变量填充。
- 支持手机号手动输入、粘贴、文件导入。
- 导入号码文件格式支持 CSV、TXT,最大文件大小 20 MB。
- 单任务最大号码数默认 100 万条。
- 支持去重、非法号码提示、黑名单拦截提示。
- 支持立即发送和定时发送。
- 不允许修改已审核模板主体,只允许填写模板变量;如需修改主体,必须新建模板重新审核。
- 不符合模板的短信按应用配置处理:拒绝发送、进入人工审核或直接发送。
- 敏感词命中第一期直接拒绝发送,并记录拒绝原因。
- 提交后展示任务编号和任务状态。
5.3 客户端批量任务
- 支持按任务编号、应用、发送时间、状态搜索。
- 展示任务总量、成功数、失败数、未知数、发送进度、创建人。
- 支持查看任务详情和导出明细;默认企业管理员可导出,普通用户需授权。
5.4 客户端发送详情
- 支持按手机号、内容、回执、时间搜索。
- 展示手机号、短信内容、计费条数、通道、提交时间、回执时间、状态。
- 支持查看单条发送链路。
5.5 客户端上行短信
- 支持按手机号、内容、接收时间搜索。
- 展示上行内容、接入号、匹配下发记录、接收时间。
5.6 客户端短信应用
- 支持新增、编辑、启用、停用应用。
- 支持配置回调地址、IP 白名单、应用密钥、日发送限额。
- 支持配置发送队列等级:普通队列、优先队列;默认普通队列,变更后只影响新创建或新入队的发送消息。
- 支持配置“不符合模板的短信”处理策略:拒绝发送、人工审核、直接发送。
- 支持配置应用级风控阈值:单任务最大号码数、重复号码比例、非法号码比例、黑名单命中比例、非工作时间大批量发送策略、短时间任务创建频控。
- 默认阈值:重复号码比例 30%、非法号码比例 10%、黑名单命中比例 5%、同一企业/应用 10 分钟内最多创建 10 次任务。
- 应用密钥需支持重置,并记录操作日志。
5.7 客户端模板管理
- 支持新增、编辑、提交审核、撤回、删除草稿。
- 支持变量识别和字数/计费条数预估。
- 支持查看审核记录和驳回原因。
5.8 客户端签名管理
- 支持新增、编辑、提交审核、上传证明材料。
- “签名与引流信息”采用签名父级、引流信息子级的可展开工作台,提供真实后端统计、签名/应用/状态筛选、审核状态、已交资料数、驳回修改说明及新增、修改、删除操作。
- 签名审核通过后支持维护短信中使用的网站、应用页面等引流信息;新增或修改后进入真实审核流程,客户端只展示“待提交、资料审核中、审核通过、需修改”等客户可理解的状态。
- 客户端页面和
/clientAPI 均不得暴露内部通道、通道组、路由规则、运营商报备汇总、报备任务及内部资料要求快照。动态审核字段接口只返回字段名称、类型、必填规则等客户填报所需信息;通道级报备状态仅限运营端查看。
5.9 客户端账户计费
- 账户余额:展示当前现金余额和人工充值记录,不提供客户端购买套餐或创建充值订单入口。
- 账单流水:展示充值、冻结、扣费、退费、调整等流水。
- 账单流水需关联短信任务、短信记录或人工调整单据。
- 客户端账号设置属于系统管理,不允许客户自行配置通道或通道组。
5.10 运营端看板与监控
- 展示总发送量、成功率、通道健康度、待处理审核数。
- 发送监控展示通道状态、发送趋势、失败率、积压队列。
- 数据统计支持按企业、应用、通道、日期统计。
- 运营概览一级菜单下只保留运营看板、发送监控、数据统计;客户管理独立作为一级业务域展示,避免重复菜单。
- 右上角消息铃铛展示所有待审核任务总数,并按企业认证、短信审核、短信模板审核、签名审核等分类展示;点击分类跳转到对应审核页面。
- 新审核任务进入时,运营端应触发浏览器通知或站内提醒;提醒数据必须来自真实待审核数量接口,不得只写死前端数字。
5.11 运营端客户与企业
- 支持企业列表、企业详情、新增、编辑、启用、停用。
- 支持企业认证资料审核。
- 支持查看企业下应用、签名、模板、发送记录、报备记录。
- 企业管理列表不提供无业务意义的“详情”按钮;需要查看明细时从企业应用、签名、模板、发送记录等业务入口进入。
- 企业列表按真实 PostgreSQL 当日短信消费金额从大到小排序;金额相同时按企业名称和 id 稳定排序。
- 企业应用管理编辑保存后返回企业应用管理列表。
- 企业应用列表展示 CMPP 连接数;点击连接数打开连接详情弹窗,内容包含连接 id、状态、连接建立时间、最近心跳时间、上次提交时间、窗口占用等。
- 企业应用连接详情支持删除连接;删除连接必须调用真实后端接口或 Gateway 回写接口,并写入系统日志。
- 企业应用列表提供 CMPP 连接参数查看与一键复制能力,参数来源于真实应用/通道配置,不允许只在前端拼接假数据。
- 企业应用新增/编辑必须展示发送队列等级,并真实保存普通队列或优先队列配置;列表或详情应能看到该配置,便于运营核对高优先级应用。
- 企业应用新增时选择企业必须使用通用下拉控件和真实企业接口;下拉控件的视觉、尺寸、禁用态、错误态应与系统内其他 Select 保持一致。
- 企业应用列表按真实 PostgreSQL 当日发送数量从大到小排序;数量相同时按应用名称和 id 稳定排序,不能只对前端当前结果临时排序。
- 所有企业和企业应用下拉控件必须支持按名称搜索;企业应用管理列表不展示 AppID。
- 企业短信模板列表必须采用自适应布局,在常用桌面及平板视口下无需水平滚动即可看到编辑、删除等操作。
- 运营端“企业模板管理”以响应式列表行展示模板、归属、内容、状态和更新时间;预览、编辑、删除操作在常用视口内始终可见,不得依赖水平滚动。
- 运营端“企业签名管理”的移动、联通、电信报备状态必须将状态文字与通过数/总数允许分行展示;报备详情、报备状态、编辑、删除四个操作按钮在空间不足时按每行两个排列,不得将按钮文字挤成单字换行。
- 运营端企业签名的状态颜色必须按统一业务语义展示:全部目标通道通过为绿色、部分通过为蓝色、审核中/报备中/资料待补充为橙色、审核或报备失败为红色、未提交/未报备/不适用为灰色;卡片总体色先遵循签名审核状态,再汇总真实通道报备状态,不能把“报备中”和“部分通过”混成同一种颜色。
5.12 运营端审核
- 企业认证审核:通过、驳回、查看材料。
- 企业认证详情必须展示客户提交的主体资料、统一社会信用代码、法定代表人、注册地址、营业执照附件、对公账户验证资料、联系人信息、提交时间、审核备注、驳回原因等;通过/驳回必须更新认证记录和租户认证状态,并写操作日志。
- 短信模板审核:通过、驳回、敏感词提示、变量检查。
- 短信模板审核必须支持按客户、应用、模板内容、审核编号和审核状态搜索;搜索应由真实后端 API 支持,前端只做展示和交互。
- 短信审核:查看短信内容、号码量、计费条数、进入审核原因、命中规则、风险原因;支持单条通过、单条驳回、批量通过和批量驳回。批量驳回必须选择待审核任务、填写统一驳回原因并调用真实批量 API,逐项驱动发送链路拒绝处理。
- 审核动作集中在审核中心;企业应用/签名/模板管理页面只做查询、维护、停用、备注和查看审核记录。
5.13 运营端通道管理
- 支持新增、编辑、删除、启用、停用短信通道。
- 删除通道采用软删除或停用归档,不能破坏历史发送、报备、日志外键;删除、启用、停用、复制等高影响操作必须二次确认并写系统日志。
- 支持复制通道:复制后新建一个通道,除 id/code 自动生成外,通道配置、CMPP 参数、通道报备字段、签名/引流报备材料和个性化字段配置均需从源通道复制;名称默认追加“副本”;若源通道为 active,新副本默认保存为 disabled,避免复制后立即占用上游连接。
- 支持发送测试短信。
- 支持查看通道成功率、未知率、失败率、累计发送量。
- 通道列表的通道成本以“分”为单位展示并保留两位小数,例如
3.00 分;展示格式不改变 PostgreSQL 中的真实成本单价及计费快照。 - 支持进入通道报备详情。
- 通道报备详情页中,签名下的引流信息默认收起,用户点击后展开;展开/收起只影响页面展示,不改变报备数据。
- 通道列表状态区域展示“连接日志”入口;点击后弹窗展示真实连接日志,包括连接请求、连接成功、断开、心跳、重连、异常等事件,日志来源于 Gateway 回写或 OperationLog。通道连接状态、连接数和最近错误必须来自 Gateway 真实上游连接池回写,不能以一次性探测拨号成功代替长连接在线状态。
- 通道操作按钮应保持一致的两列布局,报备详情、编辑、复制、发送测试、启停、删除等操作文案清晰。
5.14 运营端通道组管理
- 支持新增、编辑通道组。
- 支持配置运营商、区域、优先级、权重、备用通道、限速。
- 支持设置企业或应用绑定关系。
5.15 运营端报备任务
- 支持按企业、签名、通道、任务状态、时间搜索。
- 支持生成报备任务。
- 支持在报备任务中导出通道报备资料。
- 支持在报备任务中导入回执。
- 支持查看报备任务详情和处理历史。
- 企业签名、报备任务、通道报备详情三个状态修改入口必须向统一状态接口传递入口标识,并持久化到报备记录,不能只靠前端文案推断。
5.16 运营端报备记录
- 记录报备任务生成、导出、导入、状态同步、失败原因。
- 支持按企业、签名、通道、状态、操作时间搜索。
- 列表必须显示真实通道名称,不以通道 ID 代替;明确展示变更主体为签名或引流信息,并展示签名内容,或“所属签名 + 引流站点 + URL + 备注”。动作和前后状态统一显示中文。
- 人工状态变化必须显示实际修改入口:企业签名修改、报备任务修改或通道信息修改;系统自动审核、冻结、导入等动作显示为系统自动处理,历史无入口字段的数据明确标注为历史记录。
5.17 安全控制
- 企业黑名单:企业应用级号码拦截,同一企业不同短信应用的黑名单互不影响。
- 全局黑名单:平台维度号码拦截。
- 敏感词管理:发送前和审核时命中提示或拦截。
- 手机号段库:用于运营商识别和路由;号段与运营商区分规则使用真实服务端分页、搜索和总数。通用 Tab 位于页面标题下、搜索条件上;每个 Tab 只显示本类统计数字,不同时展示另一类统计。
- 引流信息字段库:用于签名/报备资料结构化采集。
- 企业应用级黑名单、全局黑名单、敏感词管理必须提供搜索、添加、启停/删除功能;所有操作调用真实后端 API,写入系统日志。
- 企业黑名单必须绑定到具体短信应用,支持按企业、应用、手机号、入库原因、状态搜索;发送预览、风控和发送链路只能拦截当前应用的 active 黑名单号码,不得把同企业其他应用的黑名单串用;全局黑名单支持按手机号、原因、状态搜索;敏感词支持按词、分类/级别、状态搜索。
- 企业模板管理的企业名称、企业应用、模板名称、模板内容必须是互相独立且可组合的服务端查询条件;企业签名管理的企业名称、企业应用、签名名称/用途和引流信息同样独立查询。按引流信息搜索时,以签名为父级、命中的引流信息为子级分组展开。
- 企业应用管理提供企业名称、企业应用名称和状态三个独立服务端查询条件。企业黑名单搜索区提供企业名称、企业应用、手机号码、入库原因和状态五个独立条件。
- 运营端充值记录和短信记录表头统一左对齐。
- “引流信息字段库”菜单命名为“报备字段库”,编辑、删除按钮使用通用操作按钮样式。
5.18 风控规则闭环
- 运营端提供风控规则配置页面,可按平台、企业、应用维度配置规则。
- 第一版支持规则:单任务最大号码数、重复号码比例、非法号码比例、黑名单命中比例、非工作时间大批量营销发送、短时间任务创建频控、模板变量异常。
- 默认阈值:重复号码比例 30%、非法号码比例 10%、黑名单命中比例 5%、同一企业/应用 10 分钟内最多创建 10 次任务。
- 每条规则需配置启停、阈值、处理动作、适用范围、优先级。
- 处理动作包括:拒绝发送、进入人工审核、仅记录预警。
- 命中规则后必须生成风控命中记录,包含规则编号、规则名称、阈值、实际值、处理动作、命中时间。
- 进入短信审核的任务必须展示进入审核原因和命中的风控规则。
- 直接拒绝的任务必须向客户端返回可读原因,并写入系统日志。
5.19 运营端账户计费
- 充值记录:支持运营人员人工充值和负数冲正。
- 账单流水:支持冻结、扣费、退费、解冻、人工调整、失败返还。
- 计费规则:支持按短信计费条数和企业应用单价计算费用;发送准入额度为现金余额加授信额度。
- 账务流水必须与短信记录形成可追溯关系,支持对账导出。
- 最终失败、超时失败需要退费。
- “今日返还金额”按当天实际恢复到企业可用余额的消息级流水汇总:包含已扣费后的
refunded,以及提交前失败后按sms_message_record释放的released;任务冻结转扣费过程中按sms_batch_task产生的内部释放不得计入返还。 - 计费口径可配置为按提交成功计费或按回执成功计费。
5.19.1 报表对账
- 在“数据详单”之后增加“报表对账”一级菜单,包含“对账单”和“利润报表”两个二级菜单;页面必须读取真实 NestJS API 与 PostgreSQL 报表表,不得在前端按明细临时拼接或使用静态数据。
- 对账单按发送日期、企业、企业应用汇总日发送条数和成功条数。发送条数、成功条数均按短信计费条数
billingUnits统计,成功以最终delivered状态为准。 - 利润报表按发送日期汇总日发送条数、成功条数、消费金额、成本金额、利润和利润率,支持在“企业应用”和“通道”两个统计维度间切换。
- 企业应用维度的消费金额只统计仍为
charged的客户账单,最终失败并退款的短信不再形成收入;成本金额统计该应用短信所有上游accepted提交的通道成本,包括补发产生的真实额外成本。 - 通道维度按实际上游
accepted提交统计发送量和成本,按同一 Gateway 消息回执统计成功量;客户收入只归属最终有效提交,避免补发时重复计算收入。通道成本单价和成本金额必须在提交记录创建时快照,后续修改通道单价不得改写历史成本。 - 利润等于消费金额减成本金额;利润率等于利润除以消费金额,消费金额为 0 时利润率按 0 展示。所有金额使用
0.0001 元整数金额单位持久化并按四位小数展示。 - 报表按北京时间 T+1 生成,不生成当天未完整数据;每日刷新时必须在同一事务内重新生成 T-4 至 T-1 四个完整自然日,使 72 小时内到达或变化的回执能够修正发送成功和利润结果。
- API 启动后自动补生成最近四个完整自然日,并按日执行滚动刷新;报表查询支持服务端日期、企业、应用、通道和维度过滤及分页。
- “报表对账”增加“发送质量报表”,包含企业应用、通道、签名、引流信息四个 Tab。每个 Tab 按发送日期和对应维度展示发送条数、成功条数、成功率、平均到达时长,并默认按发送条数从大到小排序。
- 发送质量的发送和成功条数均按
billingUnits统计,成功以 delivered 为准;企业应用、签名和引流信息按短信最终状态统计,通道按真实 accepted 提交及同一 Gateway 消息的 delivered 回执统计。 - 平均到达时长只使用成功且时间有效的短信,按
deliveredAt - submittedAt计算;每个日期、每个维度组先计算 P95,剔除大于 P95 的最慢 5% 样本后再求平均。无成功或无有效时间样本时展示为空,不以 0 冒充。 SmsMessageRecord必须固化实际使用的drainageInfoId。新短信在同签名已审核通过的引流信息中按正文精确包含 URL 匹配,优先最长 URL;最长 URL 出现多个同长度候选时视为歧义并不关联。历史短信使用同一规则回填,未命中或歧义统一归入“未关联引流信息”,不得把一条短信复制到签名下所有引流信息造成重复统计。- 发送质量报表与对账、利润报表共用 T+1 及 T-4 至 T-1 滚动重算任务,每次重算在同一日期事务内重建四个质量维度。
- 对账单、利润报表、发送质量报表均提供导出功能。导出必须由真实 API 按页面当前筛选条件查询完整结果并生成 CSV,不得只导出当前分页或在浏览器内拼接静态数据。
- 报备字段库采用自适应卡片布局,分开展示统计概览、签名/引流信息通用字段和字段定义;卡片明确展示通道引用数及通用配置数,已被引用的字段不可删除。
- 运营端和客户端用户管理页的新增用户按钮使用标准小尺寸操作按钮,不得占用大块页面空间。
- 项目通用
Select下拉面板默认通过页面级 Portal 渲染,不得被弹窗正文、底部操作栏、卡片或滚动容器裁剪;控件根据视口剩余空间自动向上或向下展开,跟随页面滚动和窗口尺寸变化重新定位,并继续支持名称搜索和滚动浏览全部真实 API 选项。企业签名的企业和企业应用选择等所有页面统一复用该控件,不得另写页面专用下拉实现。
5.20 数据保存与清理
- 发送记录保存 12 个月。
- 支持运营端手动按月清除发送记录。
- 冷热分层由技术方案自行设计,但不得影响最近 12 个月在线查询与对账。
5.21 系统管理与审计
- 用户管理支持角色、权限、启停、重置密码;客户端用户角色第一版限定一个企业管理员,避免多个企业管理员导致租户管理边界不清。
- 用户管理删除按钮使用统一危险操作样式;删除、启停、重置密码必须二次确认并写操作日志。
- 客户端和运营端右上角用户头像提供下拉菜单,支持退出登录、修改密码;账号设置独立菜单第一版不展示。
- 客户端和运营端系统日志均支持分页、搜索和详情展示;详情列内容较长时使用详情卡/弹窗展示,不能被表格窄列截断。
- 系统日志记录登录、退出、配置变更、审核、发送、导入导出、密钥重置、通道复制、通道启停、通道删除、连接状态变化、安全控制变更等操作。
- CMPP 心跳、Submit、Deliver 和 Gateway 周期状态同步属于运行指标或当前状态更新,不得每次追加永久系统日志;连接建立、断开、超时、认证失败,以及恢复状态、实例、锁持有者或失败原因发生真实变化时才写系统日志。
- 系统日志列表必须在数据库侧完成级别、租户、模块、时间和关键词筛选后再分页;所有新旧日志查询接口均限制每页最多 100 条,不允许无界返回全表。
OperationLog在线保留期默认 180 天;到期日志以小批量事务搬入OperationLogArchive,按archiveMonth=YYYY-MM形成逻辑月度归档。归档记录不自动删除,归档失败不得删除源记录;归档表达到千万级或维护窗口不满足要求时再评估 PostgreSQL 月度分区。
6. 非功能需求
6.1 性能
- 平台峰值提交能力:500 条短信/秒。
- 批量任务提交接口响应:创建任务后 2 秒内返回任务编号,不同步完成发送。
- 查询接口 P95 响应时间:列表查询小于 500 ms,详情查询小于 800 ms。
- 发送进度延迟:任务进度统计延迟不超过 5 秒。
- 回执处理:单通道回执接入后 10 秒内完成状态更新,异常积压需告警。
6.2 可用性
- 发送链路服务可横向扩容。
- 通道异常不影响其他通道发送。
- 队列消息必须可重试、可追踪、可补偿。
- 关键配置变更必须记录审计日志。
6.3 数据安全
- 企业数据必须按租户隔离。
- 手机号、通道密码、应用密钥等敏感字段加密或脱敏展示。
- 导入文件需做格式校验、大小限制、病毒/恶意内容防护。
- 导出文件需鉴权并记录日志。
6.4 合规与风控
- 支持敏感词拦截。
- 支持黑名单拦截。
- 支持按企业、应用、通道设置日限额、秒级限速。
- 营销类短信不单独硬编码强制审核,由应用配置和风控规则共同决定处理动作。
7. 推荐技术栈
7.1 前端
- React 19
- TypeScript
- Vite
- React Router
- Zustand 或 TanStack Query
- ECharts
- Lucide React
- CSS Modules 或现有全局 CSS 体系
说明:现有原型已使用 React、TypeScript、Vite、React Router、Zustand、ECharts、Lucide,可延续。
7.2 后端
采用 TypeScript 业务后台 + Go CMPP 网关:
- 后端 API:NestJS + TypeScript
- DB:PostgreSQL
- ORM:Prisma
- 队列:Redis + BullMQ
- 缓存/限速:Redis
- 文件:MinIO
- API 文档:OpenAPI/Swagger
- 监控:Prometheus + Grafana
- 日志:Loki 或 ELK
- 网关:Go 实现真实 CMPP Gateway,第一版必须接入真实 CMPP 长连接能力
7.3 短信网关
- Go CMPP Gateway 作为独立服务部署,不和 NestJS 业务后台耦合。
- 第一版支持真实 CMPP 2.0/3.0 长连接能力,后续可扩展 SGIP、SMGP、HTTP 通道。
- 网关负责通道连接、登录认证、长连接保活、submit、deliver、active test、重连、滑动窗口、sequence 管理、回执与上行接收。
- 网关必须同时覆盖上游通道客户端能力和下游客户服务端能力;两类连接的账号、密码、IP 白名单、连接数、状态回写和消息映射必须隔离管理。
- NestJS 业务后台负责企业、应用、模板、签名、报备、审核、计费、风控、路由决策和发送任务编排。
- NestJS 与 Go Gateway 之间通过 Redis/BullMQ 队列或内部 gRPC/HTTP 通信;第一版建议使用队列提交发送指令、队列回传提交结果和回执事件。
- Go Gateway 不直接承担业务审核、账务扣费、签名报备判断,只执行已被业务后台路由后的通道提交。
7.4 Go CMPP 开源项目评估结论
可参考开源项目,但不建议直接把完整开源网关作为核心生产服务不加改造使用。建议策略:
- 协议层优先评估并复用成熟 Go CMPP 协议库,降低从零实现 CMPP PDU、编解码、sequence、滑动窗口、deliver 解析的风险。
- 网关服务层由本项目自研,围绕本系统的通道配置、限速、发送队列、回执幂等、监控、日志、故障切换、账务关联来设计。
- 对开源网关项目可作为参考实现或测试工具,不直接绑定业务模型。
初步候选:
bigwhite/gocmpp:Go CMPP 协议库,适合作为协议层参考或依赖候选。JoeCao/cmpp-gateway:基于 gocmpp 的网关项目,包含 gateway、backend、simulator 等模块,适合作为架构参考和联调模拟参考。
落地前需要完成一次技术 Spike:
- 拉取候选项目,确认 License、维护活跃度、CMPP 2.0/3.0 支持情况。
- 用模拟 SMSC 完成 connect、submit、deliver、active test、disconnect 流程。
- 压测单连接和多连接 submit 能力,验证 500 条/秒调度下的网关稳定性。
- 检查异常场景:断线重连、sequence 回绕、窗口满、回执重复、SMSC 慢响应。
- 决定最终依赖方式:直接依赖协议库、fork 改造、或完全自研协议层。
8. 建议系统架构
8.1 服务拆分
第一版可采用模块化单体加独立发送 Worker,避免过早微服务化:
- Web/API 服务:认证、企业、应用、签名、模板、审核、通道、报备、查询。
- Send Worker:消费发送队列,执行路由、限速、提交通道。
- Receipt Worker:处理通道回执、更新发送状态。
- Report Worker:处理报备资料导出、回执导入、状态同步。
- Gateway Adapter:CMPP 通道连接与协议适配。
后续可拆为独立服务:
- tenant-service
- config-service
- audit-service
- send-service
- gateway-service
- report-service
- statistics-service
8.2 发送链路
- API 创建批量任务。
- 拆分号码和变量,生成 message_record。
- 写入数据库并准备投递发送队列。
- 根据短信应用的队列等级写入 priority_send.queue 或 normal_send.queue,或在同一 BullMQ 队列中写入可验证的 priority 值;数据库中的 message_record/submit trace 必须保留队列等级。
- Send Worker 优先消费优先队列,再消费普通队列;多实例部署时必须避免普通队列持续抢占导致优先队列失效。
- Send Worker 消费消息,校验黑名单、敏感词、限额。
- 使用可配置号码前缀正则识别运营商;识别不出时按移动处理。
- 使用手机号段库识别号码省份和城市;识别不出省份时按全国路由处理。
- 按企业应用绑定的对应运营商通道组执行路由:通道组只能是移动、联通、电信之一,发送时必须同时满足路由规则运营商、通道组运营商、通道组明细 carrier 与号码识别运营商一致;再校验通道本体 carrier 为对应运营商或三网;最后先匹配省网通道,再匹配全国通道,不得直接绑定或 fallback 到非授权单通道。
- 过滤业务 disabled、连接离线、认证失败、心跳超时或无可用连接数的通道。
- 通过通道限速器控制 TPS;优先队列不得突破通道配置的供应商 TPS 和连接窗口上限。
- TPS 按物理通道独立计数,不是整个平台共享一个总额度。例如通道 A、B 均配置 100 TPS 时,各自最多 100 TPS,平台理论合计为 200 TPS。
- 同一物理通道被多个通道组使用时,共享
SmsChannel.rateLimitPerSecond的同一额度;通道组成员不提供单独流速配置,避免多个组并发使用同一供应商账号时重复计算额度。
- 调用 Gateway Adapter 提交短信。
- 写入 submit 状态。
- Submit rejected、submit timeout、Gateway 连接断开或未提交成功、receipt failed 等场景按通道组策略补发到下一可用全国通道;unknown、超过 72 小时、超过通道组补发时间上限或关闭补发时不再补发。
- Receipt Worker 接收回执并更新最终状态。
- 补发过程必须保持幂等、计费一致和 trace 可查,最终成功只扣一次客户费率。
- 统计任务进度。
8.3 500 条/秒实现建议
- 创建任务与发送解耦,提交接口只负责入库和投递队列。
- Send Worker 多实例部署,每实例处理固定通道分片或队列分片。
- 发送调度必须支持优先队列和普通队列:优先队列消息在同等通道和限速条件下先被消费;普通队列不能被永久饿死,应通过批次配额、老化策略或可配置调度比例保证恢复。
- 使用 Redis Token Bucket 或本地令牌桶加 Redis 协调实现通道限速。
- 单条发送记录以 message_id 全局唯一,所有提交和回执处理幂等。
- 批量插入 message_record,避免逐条事务。
- 按任务、日期或企业对发送明细表做分区。
- 热查询统计写入 Redis 或统计表,避免实时 count 大表。
9. 核心数据模型
9.1 租户与用户
- tenant:企业主体。
- tenant_certification:企业认证资料。
- user:用户。
- role、permission、user_role:权限。
- operation_log:操作日志。
- operation_log_archive:超过在线保留期的操作日志归档,保留原始日志 id、租户、操作者、动作、资源、详情、发生时间和归档月份。
9.2 短信配置
- sms_application:短信应用。
- sms_application.queue_priority 或等价字段:应用发送队列等级,取值 normal/priority,默认 normal。
- sms_signature:短信签名。
- signature_material:签名证明材料。
- sms_template:短信模板。
- template_variable:模板变量。
- audit_record:审核记录。
9.3 通道与路由
- sms_channel:短信通道。
- sms_channel_group:通道组,必须配置单一运营商
mobile/unicom/telecom,不支持三网通道组。 - sms_channel_group_item:通道组通道明细,
carrier必须等于所属通道组运营商;省网明细按groupId + province唯一,全国明细可多条。 - channel_route_rule:路由规则。
- channel_health_metric:通道健康指标。
9.4 报备
- channel_report_field:通道报备字段配置。
- signature_report_material:签名报备资料。
- channel_signature_report_task:通道签名报备任务。
- channel_signature_report_record:报备记录。
- report_export_file:导出文件。
- report_receipt_import:回执导入批次。
9.5 发送
- sms_batch_task:批量任务。
- sms_message_record:单号码发送记录,所有平台任务、API 调用、CMPP 对接发送均进入此表。客户 CMPP Submit 的
DestUsrTl/DestTerminalId包含多个号码时,Gateway 和 NestJS 必须按目标号码拆分为多条独立记录,每个号码分别执行模板/签名、风控、余额、计费、路由、上游提交和回执处理;同一客户 Submit 仍只返回一个 CMPP SubmitResp/Msg_Id,后续 Deliver Receipt 以该 Msg_Id 与各自DestTerminalId区分。所有拆分记录必须持久化同一 Submit 分组消息 ID,使 Gateway 重启后仍能重建客户最初收到的 Msg_Id。任何目标号码格式非法时必须在落库前拒绝整包,禁止返回成功后只处理首号码。 - sms_api_request:API 调用批次记录。
- cmpp_submit_session:CMPP 对接提交会话或批次记录。
- sms_submit_record:通道提交记录。
- sms_receipt_record:短信回执。
- sms_uplink_message:上行短信。
- sms_send_audit:短信发送审核。
9.6 风控与基础数据
- enterprise_blacklist:企业黑名单。
- global_blacklist:全局黑名单。
- sensitive_word:敏感词。
- risk_rule:风控规则配置。
- risk_rule_hit:风控规则命中记录。
- phone_segment:手机号段库。
- drainage_field:引流字段库。
9.7 账户计费
- tenant_account:企业账户。
- account_balance:企业现金余额。
- account_transaction:账户流水。
- billing_rule:计费规则。
- sms_billing_record:短信计费记录。
- recharge_order:充值订单或人工充值记录。
10. 接口范围
10.1 客户端 API
POST /api/client/auth/loginGET /api/client/dashboardGET /api/client/applicationsPOST /api/client/applicationsPUT /api/client/applications/{id}POST /api/client/applications/{id}/secret/resetGET /api/client/signaturesPOST /api/client/signaturesPOST /api/client/signatures/{id}/submitGET /api/client/templatesPOST /api/client/templatesPOST /api/client/templates/{id}/submitPOST /api/client/sms/tasksGET /api/client/sms/tasksGET /api/client/sms/tasks/{id}GET /api/client/sms/messagesGET /api/client/sms/uplinksGET /api/client/billing/plansPOST /api/client/billing/ordersGET /api/client/billing/transactionsPOST /api/client/enterprise-certificationGET /api/client/usersPOST /api/client/usersPUT /api/client/users/{id}DELETE /api/client/users/{id}POST /api/client/users/{id}/statusPOST /api/client/users/{id}/password/resetPOST /api/client/auth/password/changeGET /api/client/system-logs
10.2 运营端 API
GET /api/admin/dashboardGET /api/admin/monitorGET /api/admin/statisticsGET /api/admin/enterprisesPOST /api/admin/enterprisesPUT /api/admin/enterprises/{id}GET /api/admin/enterprise-applicationsPUT /api/admin/enterprise-applications/{id}GET /api/admin/enterprise-applications/{id}/connectionsDELETE /api/admin/enterprise-applications/{id}/connections/{connectionId}GET /api/admin/enterprise-applications/{id}/cmpp-paramsGET /api/admin/enterprise-signaturesGET /api/admin/enterprise-templatesGET /api/admin/enterprise-certificationsGET /api/admin/enterprise-certifications/{id}POST /api/admin/enterprise-certifications/{id}/approvePOST /api/admin/enterprise-certifications/{id}/rejectGET /api/admin/audit-summaryPOST /api/admin/audits/{id}/approvePOST /api/admin/audits/{id}/rejectGET /api/admin/channelsPOST /api/admin/channelsPUT /api/admin/channels/{id}POST /api/admin/channels/{id}/statusDELETE /api/admin/channels/{id}POST /api/admin/channels/{id}/copyPOST /api/admin/channels/{id}/testGET /api/admin/channels/{id}/reportsGET /api/admin/channels/{id}/connectionsGET /api/admin/channels/{id}/link-logsPOST /api/admin/channel-groupsGET /api/admin/report-tasksPOST /api/admin/report-tasks/generatePOST /api/admin/report-tasks/{id}/exportPOST /api/admin/report-tasks/{id}/receipt-importGET /api/admin/report-recordsGET /api/admin/sms/tasksGET /api/admin/sms/recordsGET /api/admin/sms/uplinksGET /api/admin/dictionaries/blacklists/enterprisePOST /api/admin/dictionaries/blacklists/enterprisePOST /api/admin/dictionaries/blacklists/enterprise/{id}/statusDELETE /api/admin/dictionaries/blacklists/enterprise/{id}GET /api/admin/dictionaries/blacklists/globalPOST /api/admin/dictionaries/blacklists/globalPOST /api/admin/dictionaries/blacklists/global/{id}/statusDELETE /api/admin/dictionaries/blacklists/global/{id}GET /api/admin/dictionaries/sensitive-wordsPOST /api/admin/dictionaries/sensitive-wordsPOST /api/admin/dictionaries/sensitive-words/{id}/statusDELETE /api/admin/dictionaries/sensitive-words/{id}GET /api/admin/risk-rulesPOST /api/admin/risk-rulesPUT /api/admin/risk-rules/{id}GET /api/admin/risk-rule-hitsGET /api/admin/billing/plansPOST /api/admin/billing/plansPUT /api/admin/billing/plans/{id}GET /api/admin/billing/transactionsPOST /api/admin/billing/rechargesGET /api/admin/phone-segmentsGET /api/admin/drainage-fieldsPOST /api/admin/drainage-fieldsPUT /api/admin/drainage-fields/{id}DELETE /api/admin/drainage-fields/{id}GET /api/admin/usersPOST /api/admin/usersPUT /api/admin/users/{id}DELETE /api/admin/users/{id}POST /api/admin/users/{id}/statusPOST /api/admin/users/{id}/password/resetPOST /api/admin/auth/password/changeGET /api/admin/system-logs
10.3 通道回调 API
POST /api/gateway/receipts/{channelCode}POST /api/gateway/uplinks/{channelCode}
如使用 CMPP 长连接,回执和上行由 Gateway Adapter 直接消费协议消息,不一定暴露 HTTP 回调。
11. 状态定义
11.1 审核状态
- draft:草稿
- pending:待审核
- approved:已通过
- rejected:已驳回
11.2 报备状态
- not_required:无需报备
- waiting_material:待资料
- pending:待报备
- exporting:导出中
- submitted:已提交通道
- approved:报备通过
- rejected:报备失败
- partial_approved:部分通过
11.3 发送任务状态
- draft:草稿
- pending_audit:待审核
- rejected:审核驳回
- scheduled:待定时发送
- queued:排队中
- sending:发送中
- completed:已完成
- canceled:已取消
- failed:失败
11.4 单条短信状态
- created:已创建
- rejected:风控拒绝或规则拒绝
- filtered:已拦截
- queued:待发送
- submitted:已提交
- submit_failed:提交失败
- delivered:发送成功
- undelivered:发送失败
- unknown:未知
- timeout:超时
11.5 短信来源
- platform_task:客户端平台批量任务
- api:企业 API 调用
- cmpp: 企业 CMPP 对接发送
11.6 风控处理动作
- reject:拒绝发送
- manual_audit:进入人工审核
- warn_only:仅记录预警
11.7 账务状态
- estimated:已预估
- frozen:已冻结
- charged:已扣费
- refunded:已退费
- released:已解冻
- adjusted:人工调整
12. 实施步骤
阶段 1:工程初始化
- 确认第一版范围,关闭或隐藏彩信菜单,保留账户计费菜单。
- 建立前端、NestJS API、Go CMPP Gateway 的仓库结构或 monorepo 工作区。
- 配置代码规范、环境变量、构建、Docker Compose。
- 建立 Prisma schema 和数据库迁移流程。
- 建立 OpenAPI 文档和前端 API Client 生成流程。
- 建立 Go Gateway 的配置、日志、监控、健康检查和本地模拟 SMSC 联调环境。
阶段 2:基础能力
- 实现登录、用户、角色、权限。
- 实现租户隔离。
- 实现文件上传和对象存储。
- 实现操作日志。
- 实现基础字典:手机号段、敏感词、黑名单、引流字段。
- 实现现金余额账户和账务流水基础模型。
阶段 3:企业与配置
- 实现企业认证。
- 实现短信应用。
- 实现短信签名和材料。
- 实现短信模板和变量解析。
- 实现应用级不符合模板短信处理策略。
- 实现审核记录与审核工作台。
阶段 4:通道与报备
- 实现短信通道 CRUD。
- 实现通道组与路由规则。
- 实现通道签名报备字段配置。
- 实现报备任务生成。
- 实现报备资料导出。
- 实现回执导入和报备状态同步。
阶段 5:发送链路
- 实现批量任务创建。
- 实现号码导入、校验、去重、拆分。
- 实现风控规则配置、规则命中、审核原因展示。
- 实现发送审核策略。
- 实现发送前账户校验、预估、冻结或扣费。
- 实现发送队列。
- 实现 Send Worker。
- 实现 Go CMPP Gateway,支持真实 CMPP 长连接、submit、deliver、active test、重连。
- 实现回执处理、72 小时超时和上行处理。
- 实现任务进度统计。
阶段 6:查询与统计
- 实现客户端批量任务、发送详情、上行短信。
- 实现运营端任务进度、短信记录、上行记录。
- 实现客户端账户余额和充值记录。
- 实现运营端充值记录和账务流水。
- 实现运营看板、发送监控、数据统计。
- 实现导出权限和导出日志。
阶段 7:性能与稳定性
- 压测任务创建接口。
- 压测队列消费和发送 Worker。
- 压测回执处理。
- 验证 500 条/秒提交能力。
- 验证风控规则命中、审核原因、账务流水一致性。
- 验证通道限速、故障切换、重试、幂等。
- 加入监控告警和慢查询优化。
阶段 8:验收
- 按原型完成页面功能联调。
- 完成核心流程端到端测试。
- 完成权限测试。
- 完成数据隔离测试。
- 完成账务对账测试。
- 完成性能测试报告。
- 完成 Linux 部署方案。
- 完成上线回滚方案。
13. Codex 开发执行建议
13.1 推荐任务拆分
- 根据本文创建数据库迁移脚本和实体模型。
- 实现认证、租户、权限基础模块。
- 实现企业、应用、签名、模板 CRUD 与审核。
- 实现现金余额计费和账务流水。
- 实现通道、通道组、报备任务。
- 实现风控规则、审核原因、规则命中记录。
- 实现 NestJS Send Worker、BullMQ 队列和 Redis 限速。
- 实现 Go CMPP Gateway,并完成与 NestJS 队列事件的联调。
- 实现发送任务、发送明细、回执、上行。
- 前端将 mock service 替换为真实 API。
- 增加集成测试、性能测试和部署脚本。
13.2 给 Codex 的单任务提示模板
请基于 docs/first-version-development-requirements.md,实现【模块名】。
要求:
1. 遵循现有原型页面和字段命名。
2. NestJS 后端实现 Prisma schema、migration、Service、Controller、DTO、单元测试。
3. Go Gateway 实现配置、连接管理、CMPP submit/deliver/active test、回执事件发布、单元测试。
4. 前端将对应 mock 数据替换为 API 调用,保留现有视觉样式。
5. 原型阶段的 mock、localStorage 或静态数据只能用于早期界面占位;进入系统功能验收后不得继续作为兜底通过路径。审核、通道、连接、日志、黑名单、敏感词、报备字段、用户、账务等闭环必须接入真实 API、数据库或 Gateway 回写;API 不可用时应展示错误态或空态,并将用例标记为阻塞或未通过。
6. 补充错误处理、权限校验、操作日志。
7. 运行构建和相关测试,并更新测试进度文档。
14. 已确认关键决策
14.1 通道协议与报备
- 运营商通道协议参数和回执格式先按公开 CMPP 文档实现。
- 签名报备字段模板由运营端自行配置和手工维护。
- 第一期不考虑通过 API 向运营商传送签名报备资料。
14.2 发送能力
- 需要支持定时发送。
- API 实例启动后必须自动扫描并派发到期任务,管理端手工触发仅作为运维补偿入口,不能作为正常发送的前置操作。
- 多 API 实例只能有一个实例成功认领同一定时任务;认领后进程退出或 Redis 短暂不可用时,任务超时后必须可恢复,且不得重复冻结余额或重复创建队列作业。0 元短信同样适用恢复和幂等要求。
- 导入号码文件格式支持 CSV、TXT。
- 导入文件最大 20 MB。
- 单任务最大号码数默认 100 万条。
14.3 风控默认阈值
- 重复号码比例默认阈值:30%。
- 非法号码比例默认阈值:10%。
- 黑名单命中比例默认阈值:5%。
- 短时间任务创建频控默认阈值:同一企业/应用 10 分钟内 10 次。
14.4 计费与退费
- 最终失败、超时失败需要退费。
- 计费口径可配置为按提交成功计费或按回执成功计费。
- 短信计费规则只按 70/67 字拆分。
14.5 数据保存与更正
- 发送记录保存 12 个月。
- 支持手动按月清除发送记录。
- 冷热分层由技术方案自行设计。
- 超时后供应商又返回成功回执时,不允许自动覆盖业务失败状态。
- 系统需保留二次回执记录,并提供人工更正功能。
14.6 认证、部署与账号体系
- 第一版不考虑多租户独立域名和企业 SSO,只支持一个平台域名。
- 部署环境为 Linux。
- 最终需要给出 Linux 部署方案。
- 企业认证资料采用人工上传、人工审核,不接第三方实名认证或营业执照 OCR。
14.7 待确认问题
暂无阻塞性待确认问题。后续进入详细设计或开发时,如遇具体运营商协议参数、生产部署资源规格等执行细节,再按模块补充确认。
15. 第一版落地执行路线
阶段 0:技术 Spike
目标:优先验证最大技术风险,避免业务代码写完后发现网关链路不可用。
任务:
-
Go CMPP Gateway Spike
- 基于
gocmpp验证 CMPP connect、submit、deliver、active test、terminate。 - 参考
JoeCao/cmpp-gateway的连接、重连、SEQID/MSGID 追踪、模拟器设计。 - 搭建最小 Go Gateway,支持配置一个模拟或真实 CMPP 通道。
- 基于
-
BullMQ 通信 Spike
- NestJS 通过 Redis/BullMQ 下发发送指令。
- Go Gateway 消费发送指令或监听队列事件。
- Go Gateway 回传提交结果、回执、上行事件。
-
500 条/秒链路 Spike
- 不接完整业务,只压测 NestJS 入队、Go Gateway 消费、模拟通道响应。
- 验证平台可稳定入队并调度 500 条短信/秒。
- 输出压测结果、瓶颈和下一步优化建议。
验收标准:
- 能跑通一条模拟短信:NestJS 入队 -> Go Gateway 提交 -> submit resp -> deliver 回执 -> NestJS 更新状态。
- Go Gateway 断线后可重连,并能继续消费后续消息。
- 所有消息都有 messageId、channelId、sequenceId 或可追踪映射。
- Spike 文档记录是否采用
gocmpp、是否需要 fork、哪些能力需要自研。
阶段 1:工程骨架
目标:让前端、NestJS API、Go Gateway 和基础设施能稳定启动。
任务:
- 建立 monorepo 或多目录结构:
web:React + TypeScript + Viteapi:NestJS + TypeScriptgateway:Go CMPP Gatewaydocs:需求、设计、接口、部署文档
- 配置 PostgreSQL、Redis、MinIO、Docker Compose。
- 配置 Prisma、migration、seed。
- 配置 OpenAPI/Swagger。
- 配置日志、环境变量、健康检查。
- 配置基础 CI 命令:lint、test、build。
验收标准:
web、api、gateway均可本地启动。- Docker Compose 可启动 PostgreSQL、Redis、MinIO。
- NestJS 可连接 PostgreSQL 和 Redis。
- Go Gateway 可读取配置并启动健康检查接口。
阶段 2:基础后台
目标:完成业务底座。
任务:
- 登录认证。
- 企业/租户。
- 用户、角色、权限。
- 操作日志。
- 文件上传。
- 系统字典。
- 手机号段库。
- 敏感词。
- 黑名单。
- 引流字段库。
验收标准:
- 所有业务数据具备租户隔离。
- 关键操作写入操作日志。
- 文件上传可用于签名资料和认证资料。
阶段 3:短信配置
目标:客户和运营能配置发送短信所需资源。
任务:
- 短信应用。
- 应用密钥和 IP 白名单。
- 应用发送限额。
- 应用级“不符合模板短信”处理策略。
- 短信签名。
- 签名资料。
- 短信模板。
- 模板变量。
- 平台审核记录。
验收标准:
- 客户端可创建应用、签名、模板。
- 运营端可审核签名和模板。
- 发送时只能使用审核通过的签名和模板。
- 客户端不能修改已审核模板主体,只能填写变量。
阶段 4:通道与报备
目标:运营端能配置通道、通道组和签名报备流程。
任务:
- 短信通道管理。
- 通道组管理。
- 路由规则。
- 通道签名报备字段配置。
- 签名报备资料。
- 报备任务生成。
- 报备资料导出。
- 回执导入。
- 报备记录。
- 签名报备状态同步。
- 引流信息按通道报备任务、报备记录和回执。
- 企业签名、通道报备详情、报备任务三个入口共用同一报备状态事实源。
验收标准:
- 签名审核状态和通道报备状态分层。
- 运营端提供真实签名审核列表、资料详情和通过/驳回操作;运营端直接新建的签名自动审核通过并写审核记录。
- 引流信息按“报备字段库 → 通道配置引流字段 → 企业签名提交资料 → 通道报备任务”流转,不使用 JSON 中的静态三网状态代替任务状态。
- 发送前必须校验签名在目标通道报备通过。
- 报备任务导出、导入具备幂等和历史记录。
阶段 5:账户计费
目标:发送前能判断是否可发,发送后能对账。
任务:
- 企业现金账户。
- 人工充值记录。
- 现金余额。
- 发送费用预估。
- 充值记录。
- 账单流水。
- 发送预估费用。
- 冻结、扣费、退费、解冻。
- 短信记录与账务流水关联。
验收标准:
- 客户端账户余额和充值记录进入第一版。
- 发送前校验企业现金余额与授信额度之和必须大于 0。
- 每条短信记录可追溯到账务流水。
阶段 6:风控与审核
目标:发送前有规则、有原因、有闭环。
任务:
- 风控规则配置。
- 默认阈值。
- 单任务最大号码数。
- 重复号码比例。
- 非法号码比例。
- 黑名单命中比例。
- 非工作时间大批量营销发送。
- 短时间任务创建频控。
- 模板变量异常。
- 风控命中记录。
- 短信审核。
- 审核原因展示。
- 拒绝原因返回客户端。
验收标准:
- 命中风控规则时记录规则编号、规则名称、阈值、实际值、处理动作。
- 进入审核的任务必须展示审核原因。
- 直接拒绝的任务必须向客户端返回可读原因。
阶段 7:发送链路
目标:真实跑通短信发送。
任务:
- 客户端批量任务创建。
- 号码导入和拆分。
- 发送任务入队。
- Send Worker。
- 通道路由。
- Redis 限速。
- 上游通道 CMPP connect/login、ActiveTest、断线重连和连接状态回写。
- Gateway 消费 SubmitCommand 并真实 submit 到上游通道。
- Submit Resp 解析、sequence/messageId 映射和提交状态回传。
- Deliver Receipt 解析、重复/迟到回执幂等回传。
- 普通 Deliver 上行解析和上行入库。
- 下游客户 CMPP Server 监听
17890。 - 下游客户 connect/login 鉴权、IP 白名单、应用状态和连接数校验。
- 下游客户 CMPP Submit 转平台发送请求,source=cmpp,不创建客户端批量任务。
- 平台最终回执和上行向下游客户连接投递。
- 72 小时未知转超时。
- 任务进度统计。
验收标准:
- 平台创建的批量任务只记录客户端任务。
- 平台任务、API 调用、CMPP 对接发送全部按手机号维度进入短信记录。
- 支持 submit resp、deliver 回执、上行短信、超时补偿。
- Gateway
/health只表示进程存活,不等于 CMPP 发送链路验收通过。 17890必须是真实 CMPP Server 监听,能完成客户 connect/login 鉴权、IP 白名单校验和 submit 接入。- Gateway 必须真实消费 SubmitCommand 并向上游通道 submit,submit resp、receipt、uplink 事件必须进入 NestJS 后端闭环。
- 客户 CMPP 接入发送不创建客户端批量任务,但所有号码必须进入短信记录并可追踪客户侧 sequence/msgId 与平台 messageId。
阶段 8:查询、统计、验收
目标:系统可运营、可排查、可上线。
任务:
- 客户端批量任务。
- 客户端发送详情。
- 客户端上行短信。
- 运营端发送监控。
- 运营端任务进度。
- 运营端短信记录。
- 运营端上行记录。
- 运营看板。
- 数据统计。
- 操作日志审计。
- 性能压测。
- 500 条/秒报告。
- Linux 上线部署文档。
- 回滚方案。
验收标准:
- 可按企业、应用、通道、任务、手机号追踪发送链路。
- 可按账务流水和短信记录对账。
- 通道列表“今日发送质量”必须按北京时间当天的真实通道提交和回执,展示总数、成功、未知、失败及对应比例,不得使用前端固定零值。
- 运营看板不重复展示通道连接或通道运行数据;按设计基线恢复当天签名发送统计,并按是否关联引流信息分组展示签名、企业、发送总数、成功、未知、失败、成功率和平均到达时长。
- 数据统计的“通道占比”默认统计北京时间当天,可选择单个历史日期重新查询;图表使用真实通道名称和该日期的通道提交量。
- 输出 500 条/秒压测报告。
- 输出 Linux 部署方案,至少覆盖 Docker Compose 或 systemd 部署、环境变量、数据库迁移、日志目录、备份恢复、服务健康检查和回滚步骤。
16. 新 Codex 会话提示词
17. 第一版需求增补记录
2026-07-01 账户与日志展示调整
- 运营端增加企业人工充值入口。
- 运营端可针对指定企业录入人工充值金额、操作人和备注。
- 人工充值金额支持负数,用于余额冲正或调减;0 金额不得提交。
- 人工充值必须写入充值订单和账务流水。
- 运营端充值记录均为人工充值或负数冲正。
- 客户端概览指标调整。
- 原“剩余条数”改为“剩余余额”。
- 原“近24小时成功率”改为“今日发送条数和今日成功率”。
- 新增“今日消费金额”和“今日返还金额”展示。
- “今日返还金额”展示当天真实返还到可用余额的金额,包含消息级冻结释放和扣费后退款,排除任务冻结转扣费产生的内部释放流水,金额统一保留三位小数。
- 运营端增加系统日志。
- 运营端可查询全平台系统日志。
- 日志需支持按企业、模块、级别、操作人、资源 ID 或详情定位。
- 人工充值、审核、风控、发送链路、系统管理等关键动作需要可追溯。
建议新开 Codex 会话后直接发送以下提示词:
当前项目是 CMPP 短信平台第一版开发,工作区内已有前端设计原型和需求文档。
请先完整阅读:
1. docs/first-version-development-requirements.md
2. docs/ui-design-guidelines.md
3. package.json
4. src/routes/AppRoutes.tsx
5. src/layouts/ClientLayout.tsx
6. src/layouts/AdminLayout.tsx
重要前提:
- 第一版保留短信业务,排除彩信功能。
- 现金余额计费、人工充值、账单流水进入第一版。
- 前端使用 React + TypeScript + Vite。
- 后端 API 使用 NestJS + TypeScript。
- DB 使用 PostgreSQL。
- ORM 使用 Prisma。
- 队列使用 Redis + BullMQ。
- 缓存/限速使用 Redis。
- 文件使用 MinIO。
- 第一版必须实现真实 Go CMPP Gateway。
- Go Gateway 协议层优先评估 gocmpp,参考 JoeCao/cmpp-gateway 的连接、重连、SEQID/MSGID 追踪、模拟器设计。
- 不从零手写整个 CMPP 协议栈,也不直接照搬完整开源网关;协议层可复用,服务层按本项目自研。
- NestJS 负责业务审核、风控、计费、报备、路由和发送编排。
- Go Gateway 只负责 CMPP 连接、协议提交、submit resp、回执、上行事件回传。
- 当前项目已进入真实开发阶段;前端 mock、localStorage 或静态数据不得作为功能完成标准,也不得作为验收兜底通过路径。涉及审核、通道、连接、日志、安全控制、用户、账务等闭环时,必须接入真实 API、数据库或 Gateway 回写,并补真实后端 smoke 或 E2E 测试。
请从 docs/first-version-development-requirements.md 的“阶段 0:技术 Spike”开始执行。
## 追加需求:登录与用户管理闭环
### 1. 登录入口
- 客户端和运营端必须使用独立登录页面:客户端 `/client/login`,运营端 `/admin/login`。
- 两端登录均输入用户名、邮箱或手机号,外加密码和图形验证码。
- 登录接口必须调用真实 NestJS API,不允许前端静态用户、localStorage mock 或纯前端验证码作为验收依据。
- 运营端登录仅允许平台管理员;客户端登录仅允许已关联企业的企业管理员。
- 浏览器认证凭据必须改为服务端随机生成的不可预测会话标识,通过 `HttpOnly`、`SameSite=Lax` Cookie 传输;前端 `localStorage` 只可保留非敏感展示信息,不得保存访问令牌。生产 Cookie 必须启用 `Secure`,因此正式部署本功能前必须先为页面和 API 配置 HTTPS。
- 会话真实状态保存在 Redis,至少包含用户、入口、`sessionVersion`、创建时间、最后有效操作时间、最近密码认证时间、锁定时间和绝对到期时间。Redis 不可用或记录不存在时必须失败关闭,不得退回纯前端会话。
- 运营端连续 60 分钟、客户端连续 120 分钟无用户操作后进入安全锁定,提前 5 分钟提醒。锁定后只需验证当前密码即可生成新会话标识并继续使用,不重复输入账号和图形验证码;锁定超过 4 小时必须完整登录。
- 自动轮询、Dashboard 定时刷新、健康检查和后台标签页请求不得延长无操作期限。前端计时器只负责提醒和锁屏,NestJS/Redis 必须在每次受保护请求前独立执行空闲、锁定和绝对期限判断。
- 单次登录绝对时长为 12 小时,无论是否持续操作均不得自动续期;到期必须使用账号、密码和图形验证码完整登录。修改密码、禁用/删除用户、角色变化和管理员强制下线必须通过 `sessionVersion` 和 Redis 会话立即撤销现有会话。
- 用户、权限、企业状态、应用密钥、通道配置、路由、报备状态和资金调整等敏感操作要求最近 30 分钟内验证过当前密码。超时后由后端返回 `RECENT_AUTHENTICATION_REQUIRED`,前端验证当前密码后自动重试原操作;不能只依赖前端弹窗判断。
- 主动退出、空闲锁定、密码解锁、敏感操作再认证和会话创建均需写真实系统日志;多标签页使用浏览器消息同步锁定、解锁和退出。普通网络错误、400、403 业务拒绝或 5xx 不得被误判为自动退出。
- 在同一浏览器已有运营端或客户端会话时,另一个入口的账号、密码、角色或验证码登录失败只属于本次登录尝试,不得清理、广播退出或跳转已有会话;只有现有会话自身的 401 失效响应才触发退出处理。
### 2. 用户类型和企业关联
- 运营端用户管理支持创建两类用户:平台管理员、企业管理员。
- 平台管理员不得关联企业,只能登录运营端。
- 企业管理员必须关联一个企业,只能登录客户端。
- 企业管理员登录客户端后,所有客户端 API 必须继承当前企业租户范围,用户、Dashboard、账务、日志等数据不得越权访问其他企业。
### 3. 用户管理功能
- 运营端和客户端用户管理均需接入真实 API,支持添加、编辑、启用/禁用、删除、修改密码。
- 启用/禁用、删除必须弹窗确认。
- 删除采用软删除,历史系统日志、审核记录和业务记录仍可追溯;删除后用户不可登录且列表默认不展示。
- 客户端用户字段必须包含邮箱和手机号,并支持用户名、邮箱或手机号登录。
- 所有创建、编辑、启用、禁用、删除、修改密码动作必须写系统日志。
### 4. fail2ban
- 用户连续登录失败 5 次后,账号锁定 24 小时。
- 锁定状态必须写入后端持久化字段,后续登录直接拒绝。
- 登录成功后清空失败次数和锁定状态。
### 2026-07-02 纯 mock 菜单真实化补充
除文档或菜单明确标注为待开发的彩信能力外,第一版所有可进入菜单不得以 mock/static/localStorage 作为系统功能完成标准:
1. 客户端账户余额、充值记录、批量任务、短信发送、短信签名、短信模板必须调用真实 API;签名/模板新增后进入真实审核状态,发送任务调用真实发送链路。
2. 运营端数据统计、账务账户、发送监控、短信审核、短信记录、安全控制、手机号段库、报备字段库、通道组、通道报备字段、报备任务和报备记录必须调用真实 API。
3. 运营端企业管理使用真实租户、账户、应用、签名、模板接口;企业新增、编辑、启用/禁用、删除必须写真实租户表,删除采用软删除或归档,不允许纯前端删除。
4. 企业签名和企业模板运营端列表只展示真实短信配置数据;彩信签名、彩信模板、彩信通道、彩信记录、彩信任务进度等仍归入待开发,不得用静态样例作为第一版短信验收结果。
5. 后端接口暂缺编辑/删除能力时,前端不得用本地状态模拟成功;应只开放真实能力,缺失能力记录为待补接口。
6. API 不可用、数据库不可用或依赖服务不可用时,页面展示错误态或空态;测试记录标记阻塞或失败,不能用静态兜底数据假装通过。
第一步请先不要大规模写业务代码,先输出并创建阶段 0 Spike 的最小工程计划,包括:
1. 目录结构建议。
2. NestJS 与 Go Gateway 的队列消息格式。
3. Go Gateway 最小能力清单。
4. gocmpp 与 cmpp-gateway 的技术评估任务。
5. 500 条/秒 Spike 压测指标。
6. 阶段 0 的验收标准。
然后按计划逐步实现。每完成一步都要运行构建或测试,并更新文档。
2026-07-15 签名与引流资料批量导入、通道映射及统一报备
- 运营端在“报备任务”下提供“待报备资料”工作台。WPS 在线表格须先由用户另存为
.xlsx,系统读取真实工作簿、工作表、表头、单元格和内嵌图片;原始文件及拆出的图片写入 MinIO,导入批次、映射和业务资料写入 PostgreSQL,不支持用 CSV、前端静态数组或浏览器本地存储冒充图片导入。 - 导入分为“解析预览”和“确认入库”两步。用户可指定企业、企业应用、资料类型、表头行数、数据起始行并复用映射方案;每个源列可映射到签名名称、用途、所属签名、站点名称、URL、备注或报备字段库中的动态字段,同时配置文本/图片/文件、必填和转换规则。源文件中的签名名称同样必须使用完整中文黑括号格式
【签名】,不得通过导入绕过页面/API签名校验;源文件字段名称和顺序不固定,映射方案必须可持久化复用。 - 导入和业务页面的新建/修改只将已审核签名或引流信息标记为待报备,并递增材料版本;不得在每次导入后自动创建通道报备任务。运营人员可跨签名、跨引流信息勾选资料,一次创建统一报备批次。
- 创建批次时按每条资料所属企业应用的当前生效路由规则展开所有通道;一个签名走多个通道时,必须为每个通道创建或重置独立报备任务并生成一份该通道的
.xlsx。无生效路由、通道未配置字段或缺少通道必填资料时,该资料继续保留在待报备池,任务进入“资料待补充”,不得伪装为已完成。 - 通道“配置签名报备字段”和“配置引流信息字段”弹窗使用字段池,按资料类型分别配置。每列包含标准字段、通道导出表头、列顺序、必填、说明、列宽、文本转换、缺省值以及图片宽高;导出表头和列顺序必须严格使用通道配置,不受导入表格原始名称和顺序影响。
- 通道导出文件必须为 WPS/Excel 可打开的
.xlsx,图片直接内嵌到对应单元格区域,而不是仅写 MinIO URL 或本地路径。批次保留所选材料版本快照、通道文件、行号和通道任务关联,可从最近批次直接下载每个通道文件。
HTTP 客户接口第一版
管理端企业应用配置
- CMPP 与 HTTP 是两套可独立开通的接入能力,不再把 HTTP 作为
interfaceType的互斥选项。运营端在企业应用“接口配置”中维护 HTTP 总开关,以及单条发送、短信状态查询、回执 Webhook、上行 Webhook、上行查询、客户端凭据自助管理等子能力。 - 企业应用新增/编辑页必须将 CMPP 与 HTTP 配置拆成两个视觉和语义独立的区域:CMPP 区只放协议、账号、扩展码、客户接入号、接口密码、连接数、CMPP 白名单及下游重试;HTTP 区只放 HTTP 子能力、HTTP 白名单、QPS、凭据限制、投递模式和 Webhook 策略。协议关闭时收起该协议参数,只保留独立开关和关闭说明,不得再把两套字段混排在同一表单网格中。
- HTTP 配置独立维护 IP/CIDR 白名单、应用级 QPS、签名时间容差、最多有效凭据数、上行保留/查询范围/分页上限、Webhook 超时和最多尝试次数、生产 HTTPS 约束、客户手工重投权限。
- 回执和上行分别配置
cmpp/http/both/none投递模式。Gateway 产生的回执或上行必须先写入现有真实短信记录,再按模式投递;HTTP 回调不得取代或伪造 Gateway、回执匹配和上行认领链路。 - HTTP 访问密钥和 Webhook 签名密钥使用
HTTP_API_MASTER_KEY派生的 AES-256-GCM 密钥加密保存。Secret 只在创建或轮换当次返回,后续运营端和客户端仅显示末四位;允许同时保留多个有效凭据以完成无停机轮换。
客户接口与安全约束
- 第一版提供
POST /api/openapi/v1/sms/messages单条发送、GET /api/openapi/v1/sms/messages/{messageId}状态查询、GET /api/openapi/v1/sms/uplinks上行游标查询和GET /api/openapi/v1/sms/uplinks/{uplinkId}上行详情。 - 身份只由
X-App-Key对应凭据确定,不接受请求体中的企业或应用身份。签名原文为METHOD + "\n" + PATH + "\n" + X-Timestamp + "\n" + X-Nonce + "\n" + SHA256(rawBody),使用 Secret 执行 HMAC-SHA256。 - 时间戳默认允许正负 5 分钟;签名成功后使用 Redis
SET NX EX防 nonce 重放,并按应用在 Redis 执行秒级 QPS 限制。IP 白名单与 CMPP 白名单相互独立。 - 单发必须提供 8~128 位
Idempotency-Key。PostgreSQL 对(applicationId, idempotencyKey)建唯一约束并保存请求体哈希和响应快照:相同内容重放原响应,不同内容返回 409;clientMessageId在应用内唯一。 - 单发复用现有
SendChainService,必须经过真实企业/应用、签名、模板、风控、余额、计费、通道路由和 Redis 队列链路;接收成功返回 202,不代表运营商提交或终端到达成功。 - 上行查询只返回已匹配或人工认领到当前应用的记录,默认最近 24 小时,单次范围和分页上限由应用配置控制;未匹配和歧义上行不得泄露给任一客户。
- 客户错误使用
application/problem+json和稳定业务码。客户 Swagger 只包含四个/openapi/v1接口,不得包含 admin、client 管理或 gateway 内部接口。
HTTP Webhook 与客户端页面
- 状态回执和上行回调分别配置 HTTPS URL 与独立事件类型,共享该端点的签名密钥;每个事件生成唯一
eventId。回调请求用TIMESTAMP + "\n" + rawBody执行 HMAC-SHA256,客户必须按eventId幂等。 - Webhook 禁止重定向,并在保存和每次投递前解析域名,拒绝环回、私网、链路本地、共享地址和元数据地址。2xx 成功;网络错误、408、429、5xx 可按立即、1 分钟、5 分钟、15 分钟、1 小时、6 小时、24 小时重试;其他 4xx 直接终结。
- PostgreSQL 分别保存 Webhook 事件、投递状态和每次尝试摘要;客户和运营人员可查询,授权后可手工重投。首次投递与重试均由 BullMQ 执行,不得使用浏览器定时器或 localStorage 冒充。
- 客户端“短信基础配置”新增“接口对接”,包含接口概览、访问凭据、回调配置、接口文档、调用与回调记录五个页签;企业应用卡片显示 HTTP 开通状态并跳转。客户端上行列表改为真实服务端条件查询,不再先拉全量数据后仅在浏览器过滤。
2026-07-16 运营端与客户端移动端适配要求
- 运营端和客户端在宽度不大于 780px 的小屏设备上统一使用顶部栏加左侧抽屉导航。抽屉默认关闭,由顶部菜单按钮打开,支持遮罩、关闭按钮、Esc 和选择菜单后关闭;菜单内容在抽屉内部独立滚动,业务内容不得被完整侧栏挤到页面下方。
- 320px、360px、375px、390px 和 768px 常见视口不得出现页面级横向滚动。登录面板、筛选条件、表单、统计卡、操作区和弹窗必须限制在可用宽度内,桌面端既有可折叠侧栏行为保持不变。
- 通用数据表格在小屏下改为带字段名称的纵向记录卡片,操作按钮允许换行;不得要求用户横向滚动才能看到状态、失败原因或操作。业务专用的签名、引流、通道报备列表也必须按同一原则重排。
- 多列查询条件和报表筛选在小屏下收敛为单列;相关查询、重置和导出按钮保持可见并可换行。通道组配置、手机号段库、HTTP 接口凭据、企业签名报备目标等固定宽度区域必须取消页面级最小宽度。
- 移动端顶部栏至少保留导航入口、平台标识、通知和用户菜单;交互控件应具备可读的无障碍名称,抽屉打开状态使用
aria-expanded表达,并尊重系统“减少动态效果”设置。 - 客户端彩信签名、彩信模板、彩信发送、彩信任务、彩信详情和上行彩信均未完成真实后端闭环,在功能完成前不得展示“彩信服务”菜单或其子菜单;保留内部路由不代表可向客户开放。
- 运营端手机号段库使用平台通用 Breadcrumb、Button、Input、Tabs、Table、Tag、Pagination 和 Modal 实现。Tab 位于标题下方和筛选条件上方;当前 Tab 仅显示自身的真实总数。统计使用紧凑信息带,手机号段突出显示、运营商使用语义标签、删除使用克制的危险操作样式,不得另造一套组件或用大面积统计卡挤压表格。
2026-07-16 全平台金额精度要求
- 企业应用客户单价、通道成本单价、账户余额、授信额度、充值、消费、返还、短信计费金额以及对账和利润报表中的全部金额,统一精确到人民币小数点后 4 位;输入最多允许 4 位小数,页面及导出文件统一展示 4 位小数。
- 数据库和计费链路继续使用整数运算,最小金额单位统一为
0.0001 元,即1 元 = 10000 金额单位。历史字段名中的Cents为兼容既有 API 暂不改名,但其数值语义同步调整为金额单位,不再表示人民币“分”。 - PostgreSQL 金额列统一升级为
BIGINT。上线迁移时既有按分保存的数据乘以 100,应用换算除数由 100 改为 10000,确保迁移前后实际人民币金额完全一致。 - 企业应用单价修改必须写入真实
SmsApplication.customerUnitPrice,例如0.0325 元/条保存为325;后续预估、冻结、扣费、返还和利润统计均使用该整数值,不得在前端或后端再次四舍五入到分。 - API 返回
BIGINT金额时仅在 JavaScript 安全整数范围内转换为 JSON number;超过安全整数范围必须显式报错,避免静默丢失金额精度。
2026-07-16 企业应用接口参数复制与下游接入约束
- 运营端企业应用列表同时提供 CMPP 参数和 HTTP 参数复制;客户端应用列表提供 CMPP 参数复制,客户端“接口对接”页提供 HTTP 参数复制。复制内容必须来自真实应用和 HTTP 配置 API,不得用静态数组、localStorage 或页面默认值冒充。
- 客户端仅在应用已开通对应协议时允许复制参数。未开通 CMPP 时按钮不可操作,且客户端直接请求 CMPP 参数 API 必须返回 403;未开通 HTTP 时同样不得复制 HTTP 参数。
- 客户侧 CMPP 网关地址和端口是平台对外公布的下游接入地址,分别由
CMPP_PUBLIC_HOST、CMPP_PUBLIC_PORT配置,不得读取任一上游短信通道的网关地址。当前预发布环境默认值为8.160.169.106:17890;正式生产必须使用独立正式地址和配置。 - 参数复制必须兼容平台当前 HTTP 页面:优先使用 Clipboard API;浏览器因非安全上下文或权限拒绝时,使用受控 textarea 复制降级,并向用户明确反馈成功或失败,不得无提示失败。
cmppMaxConnections必须在 Gateway 登录时按应用和活动 TCP 会话真实计数并限制,同时由 API 的连接事件校验兜底。连接关闭或异常断开后必须及时释放连接名额并回写断开事件。- CMPP IP/CIDR 白名单必须在登录和连接事件中校验;运营端修改白名单、关闭接口、停用应用或降低最大连接数后,Gateway 应在下一次心跳校验时关闭不再符合条件的存量连接,不能只限制后续 Submit。
2026-07-18 手工验收瑕疵收口要求
- 图片上传单文件不得超过 2MB,其他文件不得超过 10MB;前端选择文件时即时提示,NestJS 接口与 MinIO 入库前必须再次校验,不得只依赖页面限制。
- 企业统一社会信用代码仅允许英文字母和数字,前后端同时校验;上传文件名需换行,营业执照预览和下载使用一致的操作样式。
- HTTP 由未开通切换为开通时,默认开启发送、状态查询、回执 Webhook、上行查询、上行 Webhook 和客户自助凭据全部能力,回执与上行默认使用 HTTP Webhook;参数复制页须展示业务化投递方式,不直接暴露
cmpp/http/both/none原始值。 - 企业应用、短信记录的“查询”和“重置”即使条件未变也必须重新请求真实 API;CMPP 连接详情不得依赖水平滚动,长 AppID/连接 ID 必须可换行。
- 短信审核列表时间统一为
YYYY-MM-DD HH:mm:ss;审核人和审核时间由当前 HttpOnly 会话在服务端写入,列表用“更多信息”展示;操作成功后待审角标立即重新请求真实仪表盘 API。 - 短信详情同时展示客户提交时收到的接入号和平台送往上游的接入号;运营商区分规则显示中文名称并支持真实 DELETE API。
- 所有登录用户发起且成功的 POST/PUT/PATCH/DELETE 操作必须写入 PostgreSQL
OperationLog,至少保存操作人、方法、路径、资源 ID、状态码、IP 和 User-Agent;不得记录密码、密钥或请求体。
2026-07-20 回执、报表、安全与公开 HTTP 接口收口要求
- 供应商回执优先按内部消息 ID,或
channelId + gatewayMessageId + DestTerminalId对提交记录/长短信分片审计作唯一匹配。若同一供应商账号配置了多个物理通道连接,回执可能从非原提交连接返回,此时仅允许在“账号、Gateway 主机、端口、协议及 CMPP 版本全部一致,且gatewayMessageId + DestTerminalId只有一个候选提交/分片”时跨连接认领,并仍归属原提交的逻辑通道;任一字段不同或候选不唯一必须拒绝自动匹配。每个回执事件必须以逻辑通道生成数据库唯一键,并发或重复事件不得重复生成记录、退款、计费或企业应用投递。 - 成功回执统一更新短信主记录状态、回执状态、到达时间、真实通道、通道消息号、原始回执码和文本;失败、超时和未知回执保留可解释状态,最终失败只退款一次。
- 对账、利润和质量报表统一以短信记录计费条数为发送量,以唯一主记录终态统计成功/失败;收入取扣费流水,返还取退款流水,成本取真实提交通道单价,利润等于收入减成本。到达时长按提交至成功回执计算,延迟回执由 T+1 和 T-4 至 T-1 重算覆盖;查询、页面和 CSV 共用同一聚合表。
- 运营端与客户端用户接口使用各自安全 DTO,禁止输出密码散列、会话版本、登录失败内部计数和密钥字段。后端禁止自删除/自停用、禁止删除或降权最后一个平台管理员及企业管理员,并强制校验跨租户操作;唯一冲突返回 HTTP 409 和明确字段。
- HTTP 单发公开契约使用
mobile、content和可选clientMessageId,不要求内部签名或模板 ID;服务端按 CMPP 同一规则识别已审核签名、模板及变量,复用风控、余额、计费、路由和队列。成功返回可查询 messageId,业务拒绝返回对应 4xx,不得在已创建记录后返回“批次不存在”。 - 客户发送候选只返回 approved 签名和模板,管理视图可查看历史状态。模板变量必须拒绝空变量、未闭合、中文或非法名称、重复名称及超长名称;客户端签名视图仅返回必要报备汇总状态。
- 报备资料提供官方 XLSX 模板和按当前筛选导出;导入拒绝空文件、错误扩展名、超限文件以及公式/脚本单元格。分析、提交和导出日志记录操作人、文件名、筛选条件、成功数、失败数和 IP,不保存密钥或完整请求体。
- 下游客户连接超时清理必须同时覆盖“最后心跳早于阈值”和“最后心跳为空但连接建立时间早于阈值”;刚建立且尚未超过阈值的空心跳连接不得误删。历史共享供应商账号导致的错归回执,只能在同一主记录、同一通道消息号、同一目的号码且唯一提交记录可证明时改绑并聚合;存在歧义时必须保留原数据供人工核查。
2026-07-20 客户端用户管理移动操作可达性要求
- 客户端用户管理在
≤780px使用键值卡片时,编辑、改密、禁用/启用、删除四项操作不得继续沿用不可换行的桌面横排;采用2×2网格或可访问的“更多操作”菜单,任何允许动作都不得因容器裁切而消失。 - 390×844和375×667下四项操作必须全部可见、可聚焦、可命中,触控热区高度至少44px;操作组应具有包含目标用户名称的可访问名称。
- 删除仍必须走真实客户端用户API与确认流程,不得通过前端隐藏或静态数据冒充;页面验收只打开并取消确认时,不得产生DELETE请求或数据库状态变化。
- 1440×900、1366×768和768×1024必须同步回归。1366桌面宽表若仍需内部横向滚动,滚动条必须可发现且操作可到达;固定操作列和邮箱列宽另按全站Table整改治理。
2026-07-21 双门户会话隔离与深链恢复要求
- 运营端和客户端必须分别使用独立的浏览器存储键、跨标签广播频道和 HttpOnly Cookie;后端只允许目标门户的 Cookie 认证对应路由,不能依赖前端隐藏或跳转实现隔离。
- 受保护路由必须先向真实会话接口完成初始化,再决定渲染或跳转;刷新和直接打开深链时不得先跳登录页,失效后重新登录必须回到同站点、同门户白名单内的原目标。
- 同一浏览器可以同时保持运营端和客户端登录。锁定、解锁、会话过期和主动退出事件仅作用于当前门户;退出一端不得删除、广播或撤销另一端会话。
- 会话锁定必须保留目标页面,显示锁定原因、恢复说明和剩余策略;锁定状态下暂停首次业务路由请求,解锁后在原 URL 重新挂载并从真实 API 加载数据。当前标签页内已加载的非敏感草稿不得因跨门户事件被清除。
- 旧共享 Cookie 和旧共享 localStorage 只允许做一次同门户迁移或清理,不得继续作为双门户认证来源。生产发布会使旧共享 Cookie 失效时,必须在发布说明中明确需要重新登录。
2026-07-21 UI/UX A2安全上传与日志导出补充
- 客户端文件上传和下载必须使用
/api/client/files专用接口;后端从当前会话用户反查企业,不接受请求头指定文件归属。客户端仅允许企业认证、签名报备、引流报备三类用途及对应安全目录,跨企业文件统一不可见。 - 运营端和客户端系统日志导出必须读取PostgreSQL真实筛选结果,具备提交中防重复、结构化成功结果、操作单号、文件下载、失败原地重试和会话恢复后的筛选保留。客户端导出不得包含详情JSON、IP、User-Agent等内部字段。
- CSV导出最多10000条并明确截断状态;对
= + - @开头单元格做公式注入防护。瞬时恢复状态可使用按门户隔离的sessionStorage,不得作为业务数据源。
2026-07-21 UI/UX A3审核风险治理补充
- 运营端签名、模板“通过”必须先调用后端资格预检,确认层展示对象名称、唯一ID、企业、应用、资料完整度、阻断原因和后续影响;阻断项存在时后端和前端均不得批准。
- 审核决定必须使用当前登录会话审核人、客户端生成并重试复用的幂等键,以及对象
updatedAt状态版本。后端仅允许pending对象在Serializable事务中原子变更,版本不一致返回409且不得覆盖其他审核员结果。 - 审核成功必须返回持久化
AuditRecord.id作为操作单号;同对象同幂等键重试返回原结果且不得重复更新或重复审计。旧的签名/模板批准接口也必须进入同一治理服务,不得保留绕过入口。
2026-07-21 UI/UX A4报备生成风险治理补充
- 待报备资料必须由后端预检应用启用状态、资料审核与待报备状态、资料版本、启用路由/通道、通道字段配置和必填资料;不合格资料在列表中不可选择,并返回可执行的阻断原因。
- 生成接口提交时必须重新预检,不能信任列表时的前端状态。零个可生成通道组合必须返回业务4xx,不得先创建空批次或显示成功。
- 业务去重键必须覆盖资料类型、资料ID、资料版本、应用、通道及适用运营商;历史已生成组合返回跳过及既有批次,不得无提示覆盖旧任务或重复导出。
- 每次生成要求8至128位客户端幂等键。后端通过PostgreSQL事务锁认领操作,相同键同一请求返回原操作单及
replayed=true,相同键用于不同范围返回409;生成结果按成功、跳过、失败分项返回并提供操作单号。
2026-07-21 UI/UX A5删除治理补充
- 通道、签名和模板删除前必须由后端返回对象身份、活动依赖数量与对象摘要、影响范围、
allowedActions、blockedReasons、状态版本和可恢复说明;前端不得自行推断或只显示通用风险文案。 - 删除提交必须包含预检版本、8位以上幂等键和至少4字符原因。后端在Serializable事务中重新以
updatedAt和未删除状态做条件更新,并写入包含原因、依赖、影响及幂等键的OperationLog,返回操作单号和重放标识。 - 客户端只能预检和删除当前会话企业的签名/模板,不得删除运营通道;通道被活动通道组/路由/连接/未结束报备引用,签名被模板/引流/未结束报备引用,模板被未结束发送/批量任务引用时,后端必须阻断。
- 删除采用逻辑删除,历史发送、回执、计费、审核和审计数据继续保留;恢复需有审计依据。所有旧删除入口必须委托同一治理服务,禁止保留绕过路径。
2026-07-21 UI/UX A6人工充值治理补充
- 充值记录页和企业管理页必须共用同一人工充值组件。取消、右上角关闭或完成后必须销毁未提交金额、备注、预检结果和幂等键;重新打开必须是新草稿,不得自动恢复资金操作输入。
- 最终入账前必须调用真实后端预检并重复展示企业名称、编码、唯一ID、操作方向、当前现金余额、本次变动、预计现金余额和备注;余额以PostgreSQL账户读取结果为准,不能用前端静态计算冒充资格检查。
- 人工充值请求必须使用当前会话操作者、8至128位幂等键和账户
updatedAt版本。相同幂等键同一请求返回原订单/操作单和replayed=true;不同范围复用键或账户版本变化必须返回冲突并要求重新核对。 - RechargeOrder、TenantAccount余额增量、AccountTransaction和OperationLog必须在同一Serializable事务内原子完成;审计记录需包含前余额、变动金额、后余额、订单号、原因和幂等键。正数为充值,负数为冲正,金额精确到小数点后4位且不得为0。
- 运营端充值记录必须提供可截图的账户充值回执。回执只能使用真实充值订单、企业和关联账务流水数据,展示系统真实Logo、入账状态、企业名称与编码、订单号、入账时间、前后余额、入账方式和备注;不得使用前端临时数据补齐缺失字段。
- 回执的“本次充值金额”按实际精度显示:整数金额不显示小数部分,存在小数时仅保留有效小数位;前后余额继续遵循平台统一的四位金额精度。
2026-07-22 UI/UX A7公共Dialog契约
- 所有公共Dialog打开后必须把焦点送入弹窗,并将Tab/Shift+Tab约束在当前顶层弹窗;背景内容必须同时不可聚焦、不可被辅助技术读取,页面滚动必须锁定。
- Dialog必须通过
aria-labelledby关联可见标题;遮罩不得伪装成可聚焦关闭按钮。右上角、取消、Escape和遮罩关闭必须使用同一关闭协议,关闭后焦点返回触发控件。 - 可编辑弹窗必须声明dirty状态。存在未保存内容时,任何关闭入口都必须先显示具名确认层;继续编辑保留草稿并恢复原焦点,只有明确放弃后才能销毁草稿。
- 确认层作为顶层
alertdialog管理焦点并隔离父弹窗;手机端按钮应安全堆叠,320—1440px内不得产生页面级横向溢出。
2026-07-22 UI/UX A2/A3收口补充
- 客户端上传必须由可见页面操作触发客户端专用接口;租户只能从当前会话用户解析,上传用途、对象目录和租户内下载均由后端校验,真实对象必须写入配置的MinIO并可回读。
- 企业认证上传区在手机端必须完整显示长文件名;步骤条允许安全横向浏览且默认展示第一步,省市选择不得因固定宽度被裁切。
- 客户端日志导出必须显示提交中、完成数量、操作单号、下载和失败重试;客户端CSV仅允许时间、级别、模块、操作人、动作、资源ID六列,不得包含详情、IP或嵌套内部字段。
- 签名和模板审核通过/驳回必须共用资格预检、状态版本、幂等键、事务审计和结构化结果协议。页面必须在最终决定前显示对象唯一标识、资格和影响范围;取消确认不得改变状态或写审计。
2026-07-22 应用日发送上限与HTTP参数默认值补充
- 每个短信应用的日发送上限默认为100000条,按北京时间自然日和去重后目标号码数计数。客户端、公开HTTP和下游CMPP入站必须共用PostgreSQL原子配额计数,多API实例并发不得突破上限。
- 客户端/HTTP整批超限时不创建任务、短信记录或账务冻结,HTTP返回429及
DAILY_SEND_LIMIT_EXCEEDED。CMPP合法Submit整包超限时返回唯一一个非0SUBMIT_RESP(日限额映射result=8),每个目的号码仍保留rejected/DAILY_LIMIT审计主记录;不冻结、不扣费、不占用额度,也不再生成或投递异步DELIVER回执。 - 日额度在任务正式受理时按北京时间占用;待审核和定时任务占用受理日额度,后续审核拒绝、取消或发送失败均不返还。发送校验固定遵循身份/应用权限、请求结构和全部号码基础格式、签名/模板/引流、禁发时段及风控、路由通道、余额原子校验与冻结、日额度原子占用、任务落库入队的业务优先级。余额早期读取只能用于提示,最终资格判断与冻结必须紧邻任务受理执行。
- 号码基础校验只判断空值、字符、长度、数量上限、重复及多号码完整性;号段识别用于运营商和地区快照及路由提示,未知号段不得据此拒绝,必须继续按全国或三网兼容通道处理。
- 预发布历史数据库仍为
SQL_ASCII时,数据迁移不得使用可能把多字节UTF-8字符拆成单字节处理的正则字符类。中文签名修复必须同时校验主键和原始字节序列,并从迁移前备份恢复完整UTF-8;长期生产数据库必须规划迁移到UTF8编码。 - 首次开通HTTP接口时,后端默认开启单条发送、状态查询、回执回调、上行查询、上行回调和客户端自助密钥六项能力,回执/上行投递默认为HTTP Webhook。参数复制必须包含应用名称、AppID、六项能力、基础地址、文档、QPS、白名单和真实投递方式。
2026-07-23 供应商连接主动心跳与自动重连要求
- 供应商出站CMPP通道只要状态为
active,首次连接失败、运行中断链、心跳超时或Gateway重启后都必须持续自动恢复到desiredConnections;不得依赖下一条短信触发惰性重连。 - Gateway必须主动向供应商发送
CMPP_ACTIVE_TEST并按Sequence_Id确认CMPP_ACTIVE_TEST_RESP。默认30秒一次,连续3次未响应判定断链;间隔和阈值允许按通道配置。短信提交、回执响应和心跳写包必须串行,断链后旧读写循环必须退出。 - 网络类失败按5秒、15秒、30秒、60秒、2分钟、5分钟逐级退避并增加抖动,达到上限后持续重试;鉴权或凭据类失败仍持续重试,但默认降为5分钟一次。重连成功后清零失败次数和下次重连时间。
- API每30秒协调数据库期望状态与Gateway真实状态,以Redis租约避免多实例重复下发;连接数不足、心跳陈旧、失败且到期或状态缺失的活动通道需要重新下发连接意图。
- 手动停用或逻辑删除通道必须向Gateway发送
DisconnectChannel,关闭连接池、主动心跳和重连监督器;协调任务还必须清理数据库状态与通道状态不一致的存量连接。重新启用或修改地址、端口、账号、密码、版本、窗口、连接数、心跳配置时立即使用新配置连接。 CmppConnectionState必须记录重连次数、最近重连尝试、下次重连时间、错误分类和最近心跳。运营端允许配置心跳参数并展示重连次数、下次时间和最近错误。- 多API实例并发恢复同一供应商通道时,数据库必须以
channelId + connectionId的部分唯一索引约束applicationId IS NULL状态行;唯一冲突复用并更新既有状态,不得生成重复连接状态。
2026-07-23 通道、应用、详单与报表口径补充
- 新建上游短信通道默认端口为
7890;当前仅支持 CMPP,管理端和 API 均不得接受 HTTP/SGIP 作为通道协议。 - 通道业务代码写入真实通道运行配置
config.serviceId,默认SMS,限制为 1~10 个 ASCII 字符,并用于 Gateway CMPPService_Id。 - 新建企业应用的“每任务最大号码数”默认
10000,超过上限仍由后端整任务拒绝。 - 安全控制的企业黑名单、全局黑名单和敏感词接口不得返回逻辑删除记录。
- 所有日报表按
SmsMessageRecord.billingUnits统计长短信分片条数,输出提交、发送、未知、成功、失败五项;平台拦截(status=rejected)计入提交但不计入发送,并保证发送=未知+成功+失败。 - 通道维度只存在已经路由到通道的记录,因此该维度的提交数等于发送数;应用、签名、引流信息和对账维度的提交数包含平台拦截。
- 二级添加、编辑页面必须继续高亮其所属侧边菜单。
2026-07-24 CMPP/HTTP 通讯交互日志要求
- 运营端“系统日志”必须将人员操作审计与协议通讯日志分成两个独立页签。通讯日志至少支持协议、交互方向、事件类型、结果、关键字和时间范围过滤,并展示平台消息号、上游消息号或HTTP请求号、脱敏对象、结果码、耗时和安全详情。
- CMPP应覆盖客户登录/Submit、供应商SubmitResp、状态报告Deliver、上行Deliver及平台下游投递;HTTP应覆盖客户发送请求和平台回执/上行Webhook。数据库中一条记录必须对应一个真实业务报文,不得把同一报文的“入口收到”和“处理成功”拆成两条记录;处理结果、结果码和耗时写在该报文同一条记录中,失败、重试等后续真实交互另行记录。
- Gateway收到状态报告或上行后,必须对解包/解码失败及转发NestJS失败输出结构化安全日志;NestJS入口把业务处理结果合并回同一报文记录,以便区分“上游未发”“Gateway未收到”“Gateway转发失败”和“API落库失败”。一条正常短短信的供应商侧完整成功闭环应依次展示四个真实报文:平台→通道
CMPP_SUBMIT、通道→平台CMPP_SUBMIT_RESP、通道→平台CMPP_DELIVER、平台→通道CMPP_DELIVER_RESP;箭头只表达报文实际传输方向。
2026-07-26 依赖安全治理补充
- 依赖安全整改不得直接执行
npm audit fix --force;必须逐条核对公告的真实利用条件、当前代码调用路径、目标版本兼容性和生产依赖/构建工具边界,并通过干净安装、全量测试和生产构建。 - 前端不得引入 React Router 实验性 RSC 服务端 API;在官方发布兼容修复版前,必须由自动门禁扫描源码并固定已验证的客户端 SPA 版本。仅因审计建议降级到包含已知 XSS、RCE 或 DoS 公告的旧版本属于禁止操作。
- PostCSS 必须固定到
8.5.18或更高修复版本。平台不得接受用户 CSS 后交由构建链处理;如未来新增此能力,必须显式禁用不可信 previous source map 自动加载并重新开展威胁建模。 - ExcelJS 的旧版
minimatch调用接口与安全修复版brace-expansion5.x 不兼容时,允许使用受测试的本地 CommonJS 兼容适配层;适配层只能转发到官方有长度上限的实现,必须同时验证旧版 minimatch 花括号匹配、Excel 读写和干净npm ci。 - Prisma CLI 只用于生成、迁移和构建,不属于 API 请求运行路径。其无上游修复版本的中危工具链公告需记录接受条件并持续跟踪,不得为审计数字清零而把 Prisma 7 数据访问栈盲目降级到 6.x。
- 通讯日志不得保存短信正文、密码、密钥、Token、签名鉴权值或完整HTTP请求体;手机号只保存脱敏值。CMPP心跳不得逐包写入数据库,连接健康仍使用连接状态和聚合指标。
- 通讯日志写入不能阻塞短信主链路,默认批量异步写入,缓冲区应有上限和溢出告警;热数据默认保留30天,保留期允许通过环境变量配置。
- 通讯日志方向固定使用“企业应用 → 平台、平台 → 供应商通道、供应商通道 → 平台、平台 → 企业应用”。供应商长短信每个真实
SUBMIT和SUBMIT_RESP分片各记一条,企业应用每个真实SUBMIT_RESP也必须记录;内部submit-result聚合回调不是协议报文,不得重复生成通讯日志。 - 供应商长短信回执必须先写入对应
SmsMessageSegmentAudit。仅当同一提交尝试的全部分片均为delivered时,主记录才转delivered并向企业应用投递一次最终回执;任一分片明确失败可进入最终失败/补发状态,分片尚未齐全时主记录保持submitted,不得由首片成功提前聚合。 - 长短信任一分片返回非成功终态时,系统必须通过该分片审计关联的提交记录识别当前发送尝试,不得仅以主记录保存的首片上游消息号判断;确认属于当前尝试后,整条短信立即进入失败/补发或退款终态,无需等待其余分片回执。
- 回执和上行投递方式不得由运营人员选择。企业应用开通CMPP接口即按CMPP投递,开通HTTP接口且对应Webhook地址非空即按HTTP投递,两者同时满足时双投;任一地址为空时只跳过该类HTTP事件。运营端企业应用HTTP参数页必须始终可编辑回执和上行Webhook地址,不因HTTP接口开关关闭而隐藏。
- Gateway向企业应用发送真实
CMPP_DELIVER以及收到企业应用真实CMPP_DELIVER_RESP时,都必须各写一条通讯交互日志,分别使用“平台→企业应用”和“企业应用→平台”方向;下游投递记录继续承担排队、重试和ACK业务状态,不得以通讯日志替代。