移动MAS短信发送HTTP对接实战一次把接口方案、参数细节和排障经验讲透做了这么多年后端集成要说哪个通道看起来简单、实际坑最多短信发送绝对排得上号。尤其是通过移动MAS接入的HTTP短信发送接口表面上就是一个URL加几个参数真上手对接的时候编码格式、签名方式、状态报告、超时重试这些问题一个接一个。我之前在一个项目里接MAS短信通道前后联调加灰度花了快两周大部分时间不是在等人工开通就是在跟各种莫名其妙的返回码较劲。这篇就把整个对接过程、参数设计、避坑经验完整梳理一遍给准备接移动MAS HTTP短信接口的团队一个可以直接抄的作业。这篇文章适合后端开发、系统集成工程师以及要在OA、ERP、告警平台、运营通知这类系统里快速接入短信能力的技术负责人。不管你是第一次接触MAS还是已经接入但被各种异常码折磨过里面的排查思路和实操代码都能直接用。1. MAS通道选型与接入思路1.1 什么是MAS为什么团队选择它MAS的全称是移动代理服务器是面向集团客户提供融合通信能力的一套接入平台。简单说它就是架在企业内部系统和移动网络之间的一道桥让企业内部应用可以通过统一接口把短信发出去不需要自己搭建短信中心也不需要关心底层运营商网络的复杂协议。我们当时评估过三条路短信猫、CMPP直连、MAS HTTP接口。短信猫就是一台插SIM卡的硬件设备便宜但并发低、稳定性随信号波动设备维护也麻烦。CMPP是移动短信网关的标准协议功能全但对接成本高需要处理长连接、心跳、重连、消息编解码对技术团队要求比较高。最后选了MAS HTTP短信发送接口原因很实在第一对接成本低一个HTTP请求就能发短信现有系统零改造接入第二MAS平台本身已经帮我们处理了与移动核心网的协议转换、路由选择、状态回执等底层逻辑第三后续要加彩信、视频短信之类的能力走同一个平台就能扩展。1.2 直连、前置机与云MAS三种模式怎么选MAS接入模式各厂家版本略有差异但大致分三种本地前置机模式、云MAS模式、以及通过专线直连的集团客户网关模式。本地前置机模式是在企业机房内部署一台MAS服务器企业应用先请求前置机前置机再通过专线或者互联网送到移动侧。好处是数据在内网走了一程安全性好延迟低坏处是要多管一台服务器而且前置机版本不同接口字段会有差异。云MAS模式是移动侧直接提供HTTP API企业应用通过公网或专线调用。省掉了本地设备运维但要特别注意公网调用时的安全策略IP白名单、Token有效期这些都要设计好。我们最终用的是云MAS加专线方式既省运维链路也稳。选型的时候不要只看宣传页上的功能列表一定要让移动侧的人明确告诉你两件事接口是HTTP还是HTTPS、返回字段用的是JSON还是XML。这两个问题一旦开通后发现不符扯皮周期会让你怀疑人生。1.3 账号开通前的准备清单接入MAS不是下载个文档就能开干前置条件比一般开放平台多。先找客户经理或解决方案经理提交接入申请。申请材料主要包含企业资质、业务用途说明、预计日发送量、发送场景描述。这里有个容易被忽略的点发送场景描述最好写清楚是验证码、通知、告警还是营销因为后续签名审核和频控策略都跟场景挂钩。我们当时写的是“系统告警通知与业务验证码”审核就很顺利。开通完成后你会拿到几样东西一个用户账号通常叫用户代码、一个密码或密钥、一个接口地址、若干IP白名单配额。注意MAS的账号密码一般不是给你明文存到代码里的很多版本的接口都要求做MD5摘要或动态Token一定要把开通单里的鉴权说明完整保留。IP白名单尤其重要。云MAS平台通常要求把服务器出口IP加进去否则调用直接失败。我见过一个团队联调环境一切正常上生产后全部鉴权失败排查了半天才想起来生产出口IP没加白名单。2. HTTP短信发送核心参数与细节设计2.1 接口形态与参数表拆解不同版本的MAS HTTP接口在URL路径上略有区别常见的形态是一个POST或GET请求指向网关地址前缀有sms、ums、msg等我们项目用到的伪地址是这样http://mas.example.com/sms/send.do这不是某个特定厂商的真实地址实际环境里以MAS开通单提供的为准。核心参数大同小异常见字段如下参数名含义示例备注userCode企业账号100123456开通单分配password密码/签名串见鉴权说明有时是明文有时需MD5mobile手机号13800001234群发时逗号分隔msg短信内容您的验证码是123456需URL编码msgFmt内容编码格式88通常代表UTF-8ctrlId业务流水号20240512001用于幂等和排查timestamp时间戳20240512103000部分版本要求sign签名串见鉴权说明防篡改用有些版本还会要求appId、extCode扩展码之类的字段。扩展码的作用是让企业区分不同业务来源比如同一个账号既发验证码又发通知通过扩展码在状态报告里区分回调来源。开通时多问一句支不支持扩展码后面做业务隔离会舒服很多。2.2 编码与URL转义最容易翻车的点HTTP短信接口最典型的问题就是乱码。明明代码里字符串是正常的到手机上一看全是问号或乱码十有八九是编码处理出了问题。MAS网关收到的参数本质上是URL上的文本中文内容必须按指定字符集做百分号编码。msgFmt字段如果传8代表UTF-8那么msg里的中文要以UTF-8为基准做URLEncoder如果网关要求GBK编码基准不同同样一段文字编出来的转义序列完全不一样。我见过一个团队把msgFmt固定传8但代码里字符集写的是GBK网关按UTF-8解码自然全乱。另一个细节是POST和GET的处理差异。用POST时建议把参数放进请求体但有些MAS网关实现得很随意GET和POST都能用却对参数位置有要求。稳妥做法是先用网关文档确认请求方法再拿工具直接构造一个裸请求验证。不要小看这一步我曾经因为文档写着GET实际网关只认POST而浪费一整天。2.3 流水号、时间戳与签名三重防重放设计短信接口最怕两件事重复发送和请求被伪造。MAS平台侧通常有基本的校验但我们自己在接口封装时也要做足功课。第一业务流水号必须唯一。这个ctrlId不能是简单的UUID最好带上业务类型和日期比如NOTIFY20240512001这样在排查问题时一眼就能看出是哪类业务在什么时候发的。在数据库里建唯一索引重复请求直接拒绝这是防止用户重复点击导致连发短信的关键。第二时间戳配合有效期。如果MAS版本支持timestamp参数建议加上并做3到5分钟的有效期校验。网络抖动时重发请求时间戳过期就直接丢弃避免重放攻击。第三签名串的算法看开通单。有的版本直接用passwordMD5(userCodepasswordtimestamp)有的版本要求把参数按字典序拼接后再做摘要。这类设计要严格按文档来不能想当然。签名串不要打印到日志里否则等于把钥匙贴在门上。3. 从零到一的完整对接实操3.1 联调环境准备与自测工具拿到MAS账号后第一步不是在代码里调接口而是先在本地把接口调通确定请求格式、返回结构、编码要求。这个阶段的核心目标是排除所有外部变量。我习惯用命令行的方式做第一轮验证因为curl裸请求能看到最原始的效果不受任何框架封装的影响。下面是一个简单的探测请求curl -X POST http://mas.example.com/sms/send.do \ -d userCode100123456 \ -d passwordyour_password_here \ -d mobile13800001234 \ -d msg%E6%82%A8%E7%9A%84%E9%AA%8C%E8%AF%81%E7%A0%81%E6%98%AF123456 \ -d msgFmt8 \ -d ctrlIdTEST001注意msg字段已经做了URL编码内容是“您的验证码是123456”的UTF-8转义。如果平台返回成功再逐个参数去掉测试确认哪些参数是必填的哪些可以不传。这个“减参测试”很有效能快速摸清网关的容忍度后续代码里就知道哪些字段必须补默认值。3.2 Python对接实现与要点Python是很多自动化系统首选的后端语言这里给一套简洁可用的发送函数。import hashlib import requests import time import uuid def format_time(): return time.strftime(%Y%m%d%H%M%S, time.localtime()) def sign(user_code, password, timestamp): md5 hashlib.md5() md5.update(f{user_code}{password}{timestamp}.encode(utf-8)) return md5.hexdigest() def send_sms(user_code, password, mobile, msg, ctrl_idNone): timestamp format_time() sign_str sign(user_code, password, timestamp) payload { userCode: user_code, mobile: mobile, msg: msg, msgFmt: 8, ctrlId: ctrl_id or str(uuid.uuid4()), timestamp: timestamp, sign: sign_str, } resp requests.post( http://mas.example.com/sms/send.do, datapayload, timeout(3, 5) ) return resp.json()这里有几个关键点。data参数交给requests库处理时库内部会按application/x-www-form-urlencoded格式转义中文字符会被正确编码不需要自己再调一次urlencode否则会出现双重编码。timeout(3,5)分别指定连接超时和读取超时短信接口最怕无限等宁可快速失败做重试也不要阻塞业务线程。签名算法的sign()函数要跟开通单一致有些版本可能用SHA1把它替换成对应算法即可。为了演示方便这里签名字段用了sign如果你拿到的文档里没有签名要求那password字段直接用明文也行但建议至少走HTTPS通道。3.3 Java对接实现与线程池设计Java后端接短信接口时除了发送逻辑本身还要考虑线程模型。短信发送是IO操作如果直接写在业务线程里同步调用高峰期很容易把业务线程池打满。我们的做法是封装一个异步发送器。import java.security.MessageDigest; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.concurrent.*; public class MasSmsSender { private static final ExecutorService SMS_POOL new ThreadPoolExecutor(4, 8, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(1000), new ThreadPoolExecutor.CallerRunsPolicy()); public static void asyncSend(String userCode, String password, String mobile, String msg, String ctrlId) { SMS_POOL.execute(() - doSend(userCode, password, mobile, msg, ctrlId)); } private static void doSend(String userCode, String password, String mobile, String msg, String ctrlId) { MapString, String params new HashMap(); String timestamp LocalDateTime.now() .format(DateTimeFormatter.ofPattern(yyyyMMddHHmmss)); params.put(userCode, userCode); params.put(password, md5(userCode password timestamp)); params.put(mobile, mobile); params.put(msg, msg); params.put(msgFmt, 8); params.put(ctrlId, ctrlId); params.put(timestamp, timestamp); // 使用HttpClient发送略 } private static String md5(String input) { // 标准MD5实现 return input; } }线程池参数很讲究。4到8个核心线程足够大多数企业的通知类短信量级队列1000是为了削峰CallerRunsPolicy保证队列满了之后不会丢弃消息而是让调用线程自己执行起到天然背压效果。如果你们的日发送量在百万级这个参数需要重新评估但中小规模系统这个配置很稳。3.4 状态报告与上行消息处理发出去只是第一步短信真正到达用户手机才算完事。MAS平台会对每条短信产生状态报告也就是我们常说的回执。回执里会带着原始的ctrlId和最终状态成功到达、失败、超时等。如果是同步查询模式的接口发送后可以轮询查询结果如果是回调模式MAS平台会把状态报告通过回调地址推给我们。推荐回调模式但回调地址必须是公网可达的HTTPS地址并且要做好验签。上行消息是指用户回复的短信。比如用户回复“T”退订这个内容会通过MAS上行接口推送到企业应用。处理上行消息时要注意用户可能会回复任意内容数据清洗后再入库不能直接拼接到业务逻辑里当指令执行。我们上线第一周就漏处理了上行退订结果用户发来T被当成普通回复短信系统又自动回了一条差点把用户惹毛。后来加了关键词过滤和退订名单同步才把这个洞补上。4. 上线前必须确认的运维与性能细节4.1 频控与并发设计MAS平台侧通常有频控策略相同的手机号一分钟内只能发几条、一天最多发几条这个阈值在开通时就要确认清楚。很多团队只测了测试号上线后大批量发才发现被限流。应用侧也要做自己的频控。最简单的是内存计数器每个手机号一个滑动窗口复杂一点用Redis做分布式计数。这里给一个朴素但有效的方案发送前查一下Redis中该手机号的发送计数超过阈值直接丢弃并记录日志。频控的目的不只是保护网关更是保护用户感知。同一个用户一分钟收到8条相同验证码基本会认为账号被盗投诉率飙升。4.2 长短信拆分规则与字数计算MAS网关对单条短信的长度限制不是你想的140字节而是跟编码方式相关。纯英文内容一条短信160个字符中文按UTF-8是3字节一个字符通常一条限定70个中文字符。超过长度就要拆分成多条但拆分后计费也会变多状态报告也会分成多条。拆分规则各厂商有差异但主流做法是长短信按照67字一段拆分每条加上6字节的UDHI头用于标识长短信序号这样用户手机会自动把多条拼成一条完整消息。注意拆分的短信是按两条计费的你看到的总字数可能远远大于实际发送条数成本核算时别算错。我在项目里写过一个工具类专门计算短信条数和预估费用。上线后运营同学定期核对账单发现实际费用比预估高就是因为没算长短信拆分。这个教训让整个团队记忆深刻。4.3 超时重试与幂等设计HTTP调用一定会超时短信网关也不例外。MAS接口调用超时后不能简单重发因为可能请求已经到了网关只是响应在传输过程中丢失。如果直接重发用户会收到两条一样的短信。解决方式是发送前生成唯一ctrlId并落库超时后查状态报告判断上一条到底成功没有只有确认没有成功才重发。如果状态报告也查不到就进入人工介入队列。重试次数不要超过3次并且要用指数退避第一次等待1秒第二次2秒第三次4秒。短信是低时延场景用户等待超过10秒体验就很差了。4.4 日志留痕与监控报警短信接口一定要留完整的日志。发送请求、返回结果、状态报告回调、异常堆栈一条都不能少。日志至少保留90天因为用户投诉“我没收到短信”时能快速查到真相。每个消息的ctrlId就是串起所有日志的线索。我建议日志格式固定为MAS_SMS | ctrlIdNOTIFY20240512001 | mobile138**** | actionsend | resultsuccess这样后续用日志平台查询时只要输入一个ctrlId就能看到完整生命周期。监控报警也要配套发送失败率超过1%、回调延迟超过30秒、队列积压超过阈值都要触发告警。短信通道本身是给别人发告警的如果自己通道挂了还没人知道就滑稽了。5. 常见问题与排障实录5.1 返回鉴权失败的排查路径接到“鉴权失败”“密码错误”这类错误码时先不要怀疑密码输入错误。按这个顺序排查查IP白名单是否包含当前出口IP。云MAS平台对IP的校验常放在鉴权最前面IP不对就直接报密码错误。测试环境和生产环境出口IP不同最容易在这个环节翻车。查时间戳是否使用了服务器本地时间。如果服务器时间偏差超过允许范围签名自然会失败。做一次NTP同步排除时钟漂移。查签名字符串拼接顺序。字典序和固定顺序是两种完全不同的算法看文档里的示例别自己猜。查字符集。签名串里如果有中文MD5前的编码方式也会影响结果统一用UTF-8。我之前遇到一次诡异问题服务器返回鉴权失败后来发现是运维在网关设备上做了请求改写把POST请求体里的参数顺序给改了签名校验直接被打乱。排查网络链路设备也是重要环节。5.2 手机收到乱码的根因分析乱码问题十有八九是编码基础不统一。讲过msgFmt为8表示UTF-8但还有一种情况是网关端配置的字符集和你在联调时用的不一样。生产环境与测试环境中间经过了一套流量网关如果流量网关默认按GBK解码内容就到这一步就坏了。定位方法很直接用curl裸请求直接打生产网关地址看返回状态和内容。如果裸请求正常就是代码或中间链路的问题如果裸请求也乱码就是网关侧配置问题把报文截图发给MAS技术支持处理。另外提醒一点短信内容的模板化要提前做好。固定模板部分用ASCII字符只有用户名的部分允许中文这样即便编码有问题模板部分仍然是可读的方便判断问题环节。5.3 发送成功但手机收不到MAS接口返回成功不代表用户收到了。这个状态差异最让人头大。返回成功只代表MAS网关受理了这条短信后面还涉及路由、运营商网关、手机终端等多个环节。优先查状态报告。如果状态报告显示成功基本可以判断是手机终端的问题比如用户手机号停机、号码携转状态异常、手机短信被拦截软件吞掉。如果状态报告显示失败或长时间没有报告可能是路由不通或者短信内容被审核拦截。短信内容被拦截是个容易被忽略的点。带营销特征的词、异常链接、特殊符号都容易触发运营商内容审核。我们遇到过内容里带了“点击链接领取”字样虽然业务其实是普通通知但审核系统直接拦了。经验是通知类短信的文案要尽量朴素去掉营销词汇。5.4 延迟严重与状态报告缺失正常情况下验证码类短信应该在5秒内到达。如果出现明显的分钟级延迟先看发送量是否超出账号的通道优先级。MAS通道一般有普通和加急之分加急通道费用更高但时延有保障。状态报告缺失经常是因为回调地址配置错误。确认开通单上的回调地址和实际部署地址一致并且回调接口不依赖复杂的鉴权逻辑——MAS侧回调如果收到非200响应会重试几次但不会无限重试。另外回调地址不能用动态IP必须固定公网地址或域名。5.5 网关报错但业务日志显示正常网关报错却查不到日志大概率是日志打了但是格式不规范又或者被全局异常处理器吞了。我给团队定的规矩是所有外部接口调用必须单独打一个接入日志文件不能用业务日志混着。排查时直接看这个文件一目了然。另外有的框架会对HTTP响应码做统一处理把非200响应直接转换成了自定义异常但异常信息里没有透传响应体。这时候要在拦截器里把原始Response Body打出来才能看到网关返回的真实错误码。6. 写在最后的经验短信通道做了几年最大的体会是技术对接本身不难难的是把边界情况想清楚。编码、超时、重试、幂等、频控、日志这六件事每个都值得专门花时间设计。MAS平台的HTTP接口看着简单但生产环境里真正跑得稳的系统都是在这些细节上做了充足准备的。提醒一句短信虽然方便但一定要克制。天天给用户发消息最终一定逃不过被投诉被拉黑的下场。把通道留给真正重要的验证码和告警这比任何技术优化都重要。我后面还打算把状态报告统计、失败重发策略、多通道自动切换这几个模块单独拿出来写因为实际运维中这些比单纯发短信重要得多。如果在座有人正在接MAS或者已经接了但遇到问题欢迎按上面的思路去排查大概率能省下不少时间和精力。