
简介面向通信网络初学者的短信发送流程学习教案基于8页PPT系统梳理短信从发送方到接收方的完整信令链路。内容涵盖漫游用户MO流程、省内互通短信、省内/漫游/省外用户MT流程等典型场景清晰展现BSC、MSC、LSTP、HSTP、SMSC、HLR、ISMG等网元在短信收发中的协作关系适合通信工程、网络优化及运营商技术人员学习参考。资源为单份PPT课件压缩包共1个文件类型为pptx格式大小158KB便于直接打开浏览或作为培训讲解底稿。目前已有66人学习。通过图示与步骤分解可帮助理解短信中心鉴权、路由转发、应答机制及计费话单生成等细节同时掌握不同场景下信令路径的差异提升对移动通信网短信业务端到端流程的认知与排障能力。1. 短信发送流程一份PPTX教案值得先把它当成技术方案来拆把“短信发送流程学习教案.pptx”做成一门课最难的不是画流程图而是把短信发送流程里的“真失败”和“假失败”讲明白。业务系统提交内容到短信平台平台做模板和内容校验再交给运营商SMSC下发最后状态报告沿着反方向回来。任何一个环节超时、重发、鉴权失败都会让最终状态变成一堆看起来差不多的错误码。这篇内容就是站在做教案的角度把短信发送流程的分段链路、状态机、HTTP提交、回调验签、重试退避和并发控制过一遍中间会给出能在本地跑通的最小命令和参数适合后端开发、SRE、测试以及需要给客户讲方案的技术售前。2. 短信发送流程的两张核心图分段链路与状态机2.1 从应用到运营商短信发送流程至少三层一份面向工程团队的PPTX教案开篇不该直接放网上的完整拓扑而是先把三层链路写清楚业务系统、短信平台、运营商SMSC。业务系统只负责把手机号、模板ID、变量参数和幂等键打包发给短信平台短信平台负责签名校验、模板匹配、内容审核还有供应商路由运营商SMSC负责把消息推到基站最终进入用户手机并产生回执。这三个角色对“成功”的定义完全不一样。业务系统看到200响应只代表短信平台受理了不代表用户已经收到。短信平台看到ACCEPTED只代表SMSC接受了也不代表最终送达。运营商回执里的DELIVERED才算真正到达终端。教案里如果把这三层画成一条直线后面的状态不一致问题就没法解释。我一般会在教案里放一张三层责任表表格比流程图更容易让听众记住边界层主要责任最终状态业务系统组装模板参数、签名、提交、接收回调request_id提交成功或超时短信平台模板校验、内容审核、路由、重试、计费PENDING、ACCEPTED、FAILED运营商SMSC路由到基站、下发、回执上报DELIVERED、FAILED、UNKNOWN建议用三种颜色对应三层状态名放在层里而不是单独占用一页。这样学生看到状态时能直接想起是哪一层产生的。2.2 状态机不要把所有非成功状态都当成失败短信发送流程的状态机比大多数消息队列复杂因为消息提交后永远不会收到一个“最终成功”的确认除非SMSC主动上报。常见状态包括PENDING、ACCEPTED、DELIVERED、FAILED、UNKNOWN。UNKNOWN最容易被误读它指的是平台发送了短信但超过了预设时间窗口还没有收到状态报告。这类超时可能来自运营商回执通道拥堵也可能是用户手机长时间关机。业务上不能直接把它和DELIVERED等同也不能直接判死。正确做法是根据业务的容忍时间设置一个查询节点超过该节点后调用平台的查询接口二次确认后再决定是标记为失败还是继续等待。生产环境里经常出现一个典型问题开发把FAILED当成唯一失败UNKNOWN根本不处理。结果运营统计数据里送达率虚高用户投诉收不到验证码时只能靠截图。教案里要把UNKNOWN单独拎出来明确它是“等待确认”而不是“最终状态”。2.3 用python-pptx把状态表写进学习课件因为标题是PPTX我通常会在准备教案时直接写一个小脚本把状态表固化成幻灯片而不是手工复制表格。这样改状态定义时只改一处数据源重新跑一次就能更新所有页面。from pptx import Presentation from pptx.util import Inches prs Presentation() slide prs.slides.add_slide(prs.slide_layouts[5]) # 第一行是表头所以总行数是状态数量加1 table slide.shapes.add_table(6, 3, Inches(0.5), Inches(1.0), Inches(9), Inches(4)).table headers [状态, 产生位置, 处理策略] rows [ [PENDING, 平台接收后排队, 等待下发不重复提交], [ACCEPTED, 已转发SMSC, 等待回执N秒后可查询], [DELIVERED, SMSC回执, 通知业务方归档], [FAILED, SMSC回执或平台拦截, 按失败码决定是否重试], [UNKNOWN, 回执超时, 发起主动查询避免误判], ] for col, text in enumerate(headers): table.cell(0, col).text text for row_idx, row in enumerate(rows, start1): for col_idx, text in enumerate(row): table.cell(row_idx, col_idx).text text prs.save(sms_flow_state.pptx)这段代码里最关键的两个参数是add_table(6, 3, ...)和slide_layouts[5]。第一个参数表示整个表格的行数比状态行多一行留给表头列数固定为3对应状态名、产生位置、处理策略。第二个参数5是python-pptx里的空白版式避免页面自带标题占位符干扰。后续想调整表格宽度可以用table.columns[0].width Inches(2)设置。用这个方式做教案的好处是状态词典一旦更新幻灯片内容就会同步不会出现文档和代码里状态口径不一致的情况。3. 用最小示例跑通短信发送流程HTTP提交与回调解析3.1 发送参数怎么给看最小HTTP提交命令在讲短信发送流程时我不会先上SMPP而是从HTTP接口开始因为绝大多数业务团队接触到的都是HTTP API。一个最小发送请求通常只需要手机号、模板ID、模板参数和请求唯一键。curl -X POST https://sms-gateway.example.com/v3/messages \ -H Authorization: Bearer ${SMS_TOKEN} \ -H Content-Type: application/json \ -d { mobile: 8613800138000, template_id: TMP_LOGIN, params: [421936, 5], request_id: req-20250501-001 }这里的mobile必须带国家码否则国际短信流程会走错路由template_id对应平台已审核通过的模板不能用明文消息替代否则平台会直接拦截params数组的顺序和长度必须与模板占位符完全一致比如模板内容是“验证码{1}{2}分钟内有效”那么params[0]是验证码params[1]是分钟数request_id是这时候最容易被忽略的字段它承担幂等键职责同一个request_id在平台侧重复提交时平台只会生成一条消息记录。从这个请求能看到一条关键原则短信发送流程的入口不是内容拼接而是参数映射。谁在业务代码里拼接好完整短信内容再传过来谁就会在签名或模板审核环节失败。参数是否必填说明mobile是手机号含国家码长度不超过20template_id是对应审核通过的模板params是模板变量顺序对应占位符request_id是全局唯一重试时保持同一值3.2 状态报告回调是短信发送流程里最关键的一步请求发送成功后不能靠轮询去拿最终结果正规的短信发送流程是服务端主动回调业务方。回调地址上必须做验签否则攻击者只要伪造一个DELIVERED就能把订单标记成成功。我一般会在教案里展示这个最小的Flask回调服务。import hashlib import hmac from flask import Flask, request, jsonify app Flask(__name__) WEBHOOK_SECRET your-webhook-secret app.route(/sms/callback, methods[POST]) def sms_callback(): payload request.get_json() signature request.headers.get(X-Signature, ) real hmac.new( WEBHOOK_SECRET.encode(), request.data, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(real, signature): return jsonify({code: SIGN_INVALID}), 401 message_id payload[message_id] event payload[event] # event 可能是 delivered/failed/unknown update_local_status(message_id, event) return jsonify({code: OK})这段代码需要重点理解两个地方。第一验签用的request.data必须是原始请求体不能是get_json()之后的字典因为JSON序列化会把键值顺序打乱导致签名不一致。第二hmac.compare_digest而不是直接比较字符串是为了防止时间侧信道攻击。收到回调后业务方应该用message_id作为主键更新本地状态表而不是用手机号因为同一手机号会收到多条短信。回调返回{code: OK}是实践里容易犯错的点。返回200不代表短信平台会停止重试只有返回特定成功标识后平台才会停止。如果业务方逻辑处理失败应该返回非200或抛出异常这样平台会在短暂间隔后再次推送。教案里我会建议学员把回调接口做成天然幂等的同一个message_id重复推送时第二次更新不改变结果同时返回成功标识。3.3 把教案里的时间线对应到实际日志PPTX教案里的时间线经常把“提交”和“回执”画成两个相邻节点但在系统里它们可能相隔几分钟。排查问题时我会按message_id把日志串起来看grep req-20250501-001 /var/log/sms/send.log | awk {print $1, $3, $7}这条命令的关键是用request_id或message_id作为关联键。发送请求日志、状态修改日志、回调推送日志都会带上这个ID按它过滤就能还原完整链路。如果只有手机号没有request_id日志会混入同号码的历史记录排查故障的效率会低很多。教案里要反复强调所有打印都必须带message_id这是短信发送流程可观测性的基础。4. 制作短信发送流程教案时的三个坑模板、重试、并发4.1 模板参数拼接错误是最常见的演示反例做PPTX教案时讲师很喜欢现场写代码拼接短信内容这是给学员挖坑。生产短信发送流程强制走模板是因为运营商审核不通过的内容会被整条链路拒绝。模板里的变量在使用前必须经过编码处理特别是用户输入的口令、链接参数里可能带、、{}等特殊字符。常见做法是平台模板占位符用{1}、{2}业务方把参数按位置传过去由平台统一做编码。不要在业务代码里做URL编码后再传入否则模板渲染时会看到%7B%7D这类二次转义。教案示例里最好同时放错误和正确代码错误示例让学员看出问题正确示例才不会被拿去线上抄错。4.2 重试策略不能一刀切要按错误码退避短信发送流程中的失败码不是“可重试”和“不可重试”两个桶至少分成三类瞬时错误、永久错误、限流错误。瞬时错误比如运营商网关超时重试能成功永久错误比如签名未审核重试一万次也没有意义限流错误则需要等比退避否则会加剧网关压力。错误场景是否重试建议策略网关连接超时是重试2-3次指数退避空号/停机否走号码状态清理流程模板审核不通过否发到告警群处理限流/系统繁忙是退避后再试加抖动我一般会给学员一个简单的退避算法让他们看到时间间隔不是固定写死的。import random def next_retry_delay(attempt, base1.0, cap60.0): exponential base * (2 ** attempt) jitter random.uniform(0, exponential / 2) return max(0, min(exponential jitter, cap))这里attempt从0开始第一次重试大约等待1秒第二次2秒第三次4秒最快不超过60秒。jitter是流量调度里常见的防抖技巧避免同一批消息在同一个时间点同时重试。把这段代码放进教案后还要演示一下不做退避的后果大批量发送失败后同时重试网关被自己打挂。4.3 并发窗口与幂等键决定发送流程的吞吐上限短信发送流程的瓶颈通常在供应商并发配额而不是业务服务器。如果把100万条消息一次性丢进线程池每个线程都卡在等待状态报告上内存和数据库连接会先耗尽。正确做法是用一个带最大并发数的队列控制同时在途的消息数量。教案里至少要给一张发送流水表的结构设计让学员理解为什么message_id必须作为主键。CREATE TABLE sms_outbox ( message_id VARCHAR(64) PRIMARY KEY, request_id VARCHAR(64) NOT NULL, mobile VARCHAR(20) NOT NULL, status VARCHAR(20) NOT NULL, task_time DATETIME NOT NULL, callback_time DATETIME NULL, KEY idx_request_id (request_id) ) ENGINEInnoDB;message_id由短信平台生成并返回业务方用它处理回执request_id是业务方自己生成的幂等键重复提交时会命中唯一键直接返回已有状态。status字段建议加上索引因为统计失败率和重发时都要按状态过滤。如果还想加成本可以再加一个provider_status字段保存平台原始错误码方便后排查沟通。很多项目会在表里存手机号明文后续做监控统计时直接按手机号分组但这个字段应该脱敏后再落到日志否则教案又要多讲一章数据安全。5. 用PPTX教案验收短信发送流程现场演练建议5.1 演练一断掉网关看消息怎么排队教案讲到最后一页时不需要再放架构图直接让听众做三个小实验。第一个实验是拔掉短信网关域名解析或直接关掉供应商测试环境然后提交一条消息。重点观察平台侧是否进入PENDING重试日志是否按退避间隔出现以及恢复网关后消息是否补发。这个实验能检验三个知识点状态是否准确进入等待队列、重试时间是否符合教案里的策略、恢复后是否有补偿任务。5.2 演练二重复提交同一个request_id第二个实验是使用同一个request_id连续提交两次第一次故意模拟超时第二次正常返回。重点让学员看短信平台是否只生成一条message_id业务侧是否通过幂等键拦截了重复发送。这个实验在教案里比任何原理都有效因为很多人以为幂等是数据库唯一索引的事忽略了应该在上游就拦截。5.3 演练三用message_id串日志第三个实验是把步骤一和步骤二的所有日志按message_id排序确认提交日志、回调日志、重试日志三者的时间戳顺序一致。我一般会给出一个空表格只留message_id、status、timestamp三列让学员自己填。这个表格填满后短信发送流程的完整链路就自然讲完了。最后把这三项演练并排放进教案的总结页删掉我准备好的预期状态要求学员独立写每个实验的期望输出和失败判断标准。下面这个对比能很快暴露问题不设置幂等键的人会在实验二里产出两条真实短信不配置重试的人会在实验一里丢失消息。我建议你在自己的教案里保留这个空白检查区让参与者现场把推测状态写进去比直接给标准答案更能验证是否真的理解了整个短信发送流程。本文还有配套的精品资源点击获取