我把我自己接入阿里云短信服务的过程完整写成了这篇东西,中间踩过的坑、最后调通的代码、还有上线之后的运维细节都在里面了。如果你正准备把短信服务接进自己的项目,或者已经在控制台里转了半天还不知道下一步干嘛,这篇文章应该能帮你把路理顺。
我记得第一次给业务接阿里云短信服务,差点栽在签名审核上——那会儿还不理解为什么短信签名必须提前申请、模板必须写好变量占位,结果接口调通了,却因为签名没审核通过一直被拒。后来给多个项目做过短信接入,才发现这类问题特别典型:大多数人会先去拿SDK写代码,等代码报错或者用户收不到短信时,才回头研究控制台里的各种配置,来回折腾时间全耗在“我以为”上面了。
这篇内容我会从产品边界、控制台配置、代码实战、故障排查、线上运维五个维度展开,既讲“怎么调通”,也讲“为什么这么配”,尽量把每个关键选择背后的原因说清楚。
1. 接入前先分清:阿里云短信服务到底做的是哪门生意
1.1 短信类型先行,别拿验证码模板去发营销短信
很多人第一次接触阿里云短信服务,脑子里只有“发短信”这一个模糊概念,申请起来才发现里面还分国内短信、国际短信、语音验证码、数字短信。就算只聊国内短信,也至少能拆成三块:验证码短信、通知短信、推广短信。
这三类业务场景是完全不一样的。
验证码短信通常用于用户注册、登录、找回密码、支付确认,特点是单条内容短、需要用户立刻阅读、时效性非常强。通知短信适合订单状态变化、物流更新、服务到期提醒这类信息性推送。推广短信则是促销活动、新品上架、会员营销,一般由商家主动发起,单次发送量大,而且必须包含“退订”关键词。
这三类的模板审核规则、发送频率限制和计费方式都有差异。验证码和通知类通常不需要用户主动订阅,而推广短信必须保证用户允许接收,一旦被投诉或者退订率高,账号可能会被限制。所以接入前先想清楚:你到底要发什么内容?选错了类型,后面申请模板的时候很容易被驳回。
1.2 签名、模板和参数:先理解再申请,审核更顺利
阿里云短信服务里有一个核心三角:签名、模板、参数。
签名是用户收到的短信前带入的名称,比如“【某某商城】”。它不是你想写什么就写什么,必须提供证明材料,证明你是这个名称的使用主体,比如企业营业执照、已上线的App名称、公众号主体信息等。签名可以理解为身份举证,平台需要确认“你确实能代表这个签名”。
模板是短信内容的固定格式,比如:
code复制您的验证码为${code},将在5分钟后过期,请勿泄露。
其中${code}是一个变量,发送的时候由业务方传入。模板必须提前审核,目的是防止垃圾短信和诈骗信息。参数是业务侧每次请求时动态传入的内容,比如具体的验证码值、用户名、订单号。
这三个概念看起来简单,但它们决定了你的SDK调用参数。SignName对应签名名称,TemplateCode对应模板编号,TemplateParam对应变量JSON。很多人发不出去短信,往往不是代码的问题,而是这三者没在控制台里对齐。
1.3 费用构成:别等到月底账单出来才吓一跳
短信的计费是按条数走的,国内验证码和通知短信一般是几分钱一条,具体价格取决于你选择的套餐包和购买量。小体量业务直接用按量付费就行,量大的话建议买套餐包,但需要注意套餐包通常有有效期和适用范围,并不是所有短信类型都能抵扣。
这里有一个大家容易忽略的点:一次发送失败也可能产生费用。如果用户手机号异常、运营商通道拦截,短信虽然没到用户手里,但发送请求已经进入平台处理流程,照样会扣费。所以费用控制不能只看“成功发送了多少钱”,还要观察“发送失败和接收失败”的比例。控制台里有发送记录,后面我会单独讲怎么把发送状态和接收状态分开看。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 控制台配置:从实名认证到签名审核,真正耗时的是这一步
2.1 开通服务与账户准备
使用阿里云短信服务前,阿里云账号需要先完成实名认证。个人实名认证做个人业务没问题,但大多数企业场景建议企业实名认证,因为签名审核时需要上传营业执照,认证主体不一致会直接被拒。
在控制台搜索“短信服务”,进入后按照指引开通即可。开通本身很快,难点在后面几个环节。如果你用的是国际短信,还需要单独申请资质;国内短信按流程走就行。开通后建议第一时间拿到自己的AccessKey ID和AccessKey Secret,但这一步千万不要在根账号下生成,见下一节。
2.2 用 RAM 创建独立密钥:生产环境不要用主账号 AccessKey
这是我在第一个项目里吃过亏的地方。当时图省事,直接用了主账号的AccessKey,后来运维同学说要轮换密钥,才发现这个密钥被多个项目共用,根本不敢随便换。
正确的做法是在 RAM 访问控制里创建一个子用户,只授予短信服务的相关权限。最小权限的实践非常关键:如果业务只需要发短信,就只给AliyunDysmsFullAccess或者更细粒度的自定义权限,不要顺手把OSS、ECS、RDS的权限也挂上去。
具体操作路径是:控制台搜索“RAM 访问控制”,创建用户时选择“编程访问”,系统会生成一组AccessKey。然后为用户添加权限策略,建议用“精确授权”而不是“AliyunSMFullAccess”这种超大权限。如果你需要同时操作短信和OSS,也建议分成两个RAM用户,分别管理两套密钥,方便单独轮换和审计。
这里有朋友会问,RAM的登录和访问控制底层到底是什么逻辑?简单说,RAM本质上是阿里云账号体系里的一个授权主体,它通过策略(Policy)绑定到用户或角色上,访问云产品API时,云平台会先校验调用方的身份凭证,再根据策略判断是否有权限。所以即便是同一个AccessKey,如果策略里没有dysms:SendSms权限,SDK调用也会报权限错误。我把RAM用户和主账号分别管理后,代码里的密钥初始化反而更清晰了。
2.3 签名和模板申请:常见驳回原因与应对
签名申请看似简单,但很多人被卡在这里。我印象最深的驳回原因是:签名的来源证明不足。
签名名称可以是公司全称、简称或者App名称。如果你申请的是“某某商城”,那就需要提供商标注册证书或者应用商店上架截图。如果你用企业全称,企业实名认证的营业执照就够了。小程序、公众号类签名需要提供对应的主体页面截图。个人用户能申请的签名类别很少,基本没有太多选择空间,这也是为什么企业接入更顺滑。
模板申请也有几个高频驳回理由:
- 模板内容里出现了“测试”“test”等不规范字样,平台会怀疑你在验证通道。
- 模板格式和实际发送内容不一致,比如变量名写成了
${验证码},而阿里云要求变量名使用字母、数字或下划线,例如${code}。 - 营销短信模板缺少退订关键词,通常需要在正文末尾写“回T退订”之类的说明。
- 模板文案涉嫌诱导、夸大或金融类敏感词,比如“立即提现”“贷款利率低至”这种话术,基本过不了审。
审核时间一般不长,但为了不卡上线,我现在的做法是:项目还没开始写代码,就先在控制台提交签名和模板审核,让审核和别人开发并行。审核通过后代码调通时,模板已经可用了。
3. Java 实战:在ECS上用阿里云SDK把短信发出去
3.1 用阿里云 Maven 镜像加速依赖下载
短信服务SDK相关依赖从Maven中央仓库下载速度一般,但在国内环境经常慢到怀疑人生。我之前的做法是在~/.m2/settings.xml里配置阿里云公共仓库镜像,效果非常明显。
xml复制<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
这样配置之后,不光短信SDK,其他所有中央仓库依赖都能享受加速。如果你用 Spring Initializr 生成项目,还可以把服务地址换成阿里云的 start 地址,生成的项目结构里直接带上阿里云的仓库配置,对国内开发来说更顺手。
3.2 Spring Boot 里的发送验证码核心代码
短信服务SDK有很多版本,新项目我建议直接用dysmsapi20170525这个独立SDK,依赖更轻,没有老SDK那种版本冲突的烦恼。
在pom.xml里加依赖:
xml复制<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.24</version>
</dependency>
写一个简单的发送函数:
java复制import com.aliyun.dysmsapi20170525.models.*;
import com.aliyun.teaopenapi.models.*;
public class SmsService {
private static com.aliyun.dysmsapi20170525.Client createClient() throws Exception {
Config config = new Config()
.setAccessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"))
.setAccessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"));
config.endpoint = "dysmsapi.aliyuncs.com";
return new com.aliyun.dysmsapi20170525.Client(config);
}
public static SendSmsResponse sendCode(String phone, String code) throws Exception {
com.aliyun.dysmsapi20170525.Client client = createClient();
SendSmsRequest request = new SendSmsRequest()
.setPhoneNumbers(phone)
.setSignName("你的签名")
.setTemplateCode("SMS_123456789")
.setTemplateParam("{\"code\":\"" + code + "\"}")
.setOutId(String.valueOf(System.currentTimeMillis()));
return client.sendSms(request);
}
}
这段代码里值得注意的几个点:
AccessKey不写死在代码中,而是从环境变量读取,避免密钥泄露进Git仓库。TemplateParam必须是合法的JSON字符串,而且字段名要和模板里的变量名一致。模板里是${code},这里就传code字段。OutId可以用来传业务侧的唯一标识,比如用户ID、订单ID,后续查发送记录时能对应上业务。endpoint固定写dysmsapi.aliyuncs.com,不要随意改。
如果你用的是旧版SDK,代码风格会不一样,但新SDK的类设计更自然,错误信息也更容易定位。
3.3 解析返回结果:别只盯着 HTTP 200
第一次调通SDK时,我看到HTTP 200就以为成功了,但实际不是。阿里云短信API的成功与失败要通过响应体里的Code字段判断。
接口返回会有这几种情况:
- 返回
Code=OK,说明接口调用成功,短信请求已进入处理流程。 - 返回
Code=isv.SMS_SIGNATURE_ILLEGAL,说明签名不存在或未通过审核。 - 返回
Code=isv.MOBILE_NUMBER_ILLEGAL,说明手机号格式不对。 - 返回
Code=isv.TEMPLATE_MISSING_PARAMETERS,说明模板变量和你传的JSON对不上。
所以拿到响应后,不要只打印response.body.message就完事,要把code、message、requestId完整记录到日志里。requestId在和阿里云技术支持沟通时极其重要,相当于这次请求的身份证。
4. “短信发了但用户没收到”这类问题,怎么一步步查到根因
4.1 先分清接口状态、发送状态、接收状态三层
这是我在生产环境被折磨过最久的一个问题。接口返回OK,用户却说没收到短信。排查时要先建立一个概念:短信链路有状态分层。
第一层是接口状态,指SDK调用是否成功,返回内容是否合法。第二层是发送状态,也就是阿里云是否接受了这条短信,并且把它转给了运营商。第三层是接收状态,即用户手机号是否真正收到了短信。这三层完全可能不一致。
控制台里的“发送记录”能看到第二层和部分第三层信息。如果记录里显示“发送成功”,但用户实际没收到,问题多半在运营商侧,比如手机号被用户设置了黑名单、运营商拦截规则、手机停机、信号问题等。如果记录里显示“发送失败”,那就要看失败原因,比如用户退订、发送频率超限、号码在黑名单中。
排查的第一动作不是改代码,而是去控制台查发送记录,把失败原因截图,再对应错误码表处理。我的习惯是每次发送都带着OutId,这样业务侧查到用户ID,控制台里也搜得到,两边能对上。
4.2 高频错误码速查与定位链路
下面这个表格是我整理出的高频错误码,做短信接入的团队可以收藏一份:
| 错误码 | 含义 | 处理建议 |
|---|---|---|
| isv.SMS_SIGNATURE_ILLEGAL | 签名不存在或未通过审核 | 检查控制台签名状态,确认申请时填写的SignName完全一致 |
| isv.TEMPLATE_MISSING_PARAMETERS | 模板变量缺失 | 检查TemplateParam是否包含模板中所有占位变量 |
| isv.MOBILE_NUMBER_ILLEGAL | 手机号格式错误 | 确认号码为11位国内手机号,国际短信要带国家码 |
| isv.INVALID_JSON_PARAMS | TemplateParam不是合法JSON | 在本地用JSON解析工具验证一下拼接结果 |
| isv.INVALID_PARAMETERS | 参数类型或格式错误 | 逐个检查PhoneNumbers、SignName、TemplateCode、TemplateParam |
| isv.BUSINESS_LIMIT_CONTROL | 触发发送频率限制 | 降低发送频率,检查同一号码的发送间隔和每日上限 |
| isv.BLACK_KEY_CUSTOMER_CONTROL | 号码或被拉黑对象命中黑名单 | 联系客服核实,确认号码是否有投诉或退订历史 |
| RequestTimeTooSkewed | 请求时间和服务器时间偏差过大 | 校准服务器NTP时间,检查ECS系统时间是否准确 |
这里要特别说下RequestTimeTooSkewed,这是很多自建服务器场景容易踩的坑。如果服务器时间没有同步,和阿里云服务器时间差超过一定范围,请求会被直接拒绝。解决办法很简单:在ECS或自建机器上配置NTP定时同步,比如chrony或者ntpd。
4.3 权限、网络、依赖三个隐藏雷区
除了错误码,还有三个不常见但一踩就懵的情况。
权限问题:我遇到过SDK明明配好了AccessKey,但还是报Forbidden,最后检查发现RAM用户被禁用或者AccessKey被删了。排查路径是:先确认密钥状态,再到权限策略内容里看是否包含短信服务权限。权限策略和用户绑定关系变更需要时间才能生效,刚调整完权限就立刻重试,偶尔也会出现缓存不一致。
网络问题:短信接口走HTTPS,需要服务器能访问dysmsapi.aliyuncs.com。有些服务器安全组规则过严,出方向443端口被限制,导致SDK连接超时。排查思路很简单,在服务器上执行:
bash复制curl -I https://dysmsapi.aliyuncs.com
如果能正常返回,网络基本没问题。如果返回Could not resolve host或连接超时,就要检查DNS和安全组。
依赖冲突:老版本SDK容易和OSS、RDS等其他云产品SDK打架,因为aliyun-java-sdk-core版本不一致。新项目我直接用独立命名的dysmsapi20170525,内部自带核心模块,不去依赖老core,能避开大部分冲突。
5. 上线后真正该做的事:限流、回执和成本预警
5.1 应用侧自己做限流,别依赖平台兜底
阿里云短信服务有平台侧的频率限制,但这只是兜底,业务侧如果不好好控流,很可能在关键活动时把短信配额打爆,甚至误伤正常用户。
短信轰炸是典型的例子。用户注册页面如果只做了前端的倒计时限制,攻击者可以直接模仿接口请求狂发短信,短时间内把一个手机号塞满验证码短信。这种投诉一旦多了,签名的信誉会受影响。
我在业务侧做了一个简单的按手机号限流设计,核心规则是:
- 同一手机号60秒内只能发1条验证码。
- 同一手机号24小时内最多发10条。
- 同一IP每30分钟最多发20条。
- 发送前先校验,发送后记录到Redis。
实现上可以用一个简单的Redis计数器。每次请求时判断当前窗口内的发送次数,超过阈值直接返回“操作过于频繁”,而不是发给阿里云。这个设计成本很低,但能避免大量恶意请求把短信通道拖垮。
5.2 回执消息配置:从“发出去了”到“送达了”
短信发出后,用户到底收到没有,最佳判断方式是开通“短信回执消息”。阿里云支持通过消息队列或HTTP推送的方式,把每条短信的最终接收状态推给你的后端。
控制台里找到“回执消息设置”,配置SmsReport消息类型,选择MNS队列或者HTTP地址。如果业务量不大,我建议直接配HTTP接收地址,后端暴露一个回调接口,把推送过来的状态落库。
回执消息的数据格式大致如下:
json复制{
"phone_number": "13800138000",
"send_time": "2025-01-01 10:00:00",
"report_time": "2025-01-01 10:00:05",
"success": true,
"err_code": "DELIVERED",
"err_msg": "用户接收成功",
"sms_size": "1",
"biz_id": "123456789",
"out_id": "order_20250101"
}
拿到回执后,可以用biz_id或out_id关联业务记录,把短信状态从“已提交”变成“已送达”或“失败”。这样客服后台能直接看到短信送达链路,排查用户投诉时效率高很多。
注意,回执推送存在一定延迟,有时候长达几十秒甚至几分钟,所以不要在前端同步等待回执结果。异步处理才是符合短信业务特性的做法。
5.3 成本与安全:预算预警和密钥轮换
短信这种服务,单条不贵,量大以后也是一大笔成本。控制台里可以设置消费预警阈值,比如短信月消费超过100元时提醒我。这个功能建议一开始就配好,不然活动运营一兴奋,发了大批量营销短信,月底账单会让财务找你聊人生。
成本控制上还有一个技巧:尽量把相同内容合并成一条发送,避免一条通知拆成多次API调用。比如订单取消通知和退款到账通知,如果时间相近,完全可以在一次通知里把两个信息都带上,从而省一条短信费用。
安全方面,除了RAM最小权限,还要保证密钥有轮换机制。我目前的做法是每90天轮换一次AccessKey,先创建新密钥,更新部署环境变量,确认业务稳定后再删除旧密钥。阿里云控制台支持创建多个密钥,所以这个过程可以平滑过渡。
另外,代码仓库里一定要做敏感信息扫描,防止有人把AccessKey提交到Git。一旦发现密钥泄露,不用犹豫,直接在控制台禁用并删除,再生成新的。
最后分享两个小习惯。一是把阿里云短信服务的RequestId和业务OutId打全在日志里,后面排查问题时你会感谢自己当初多写了这两行日志。二是每次新项目接入时,我都会先自己造一个测试手机号,在测试环境跑通“接口返回OK、发送记录成功、回执为DELIVERED”的完整链路,再给前端联调。这一步能提前把签名、模板、权限、网络这些杂七杂八的问题全过滤掉,留给联调的时间反而更充足。
