
简介这份压缩包定位为阿里云短信服务在PHP环境中的集成示例面向需要快速接入短信验证码、系统通知或营销消息的网站开发者。压缩包整体大小约三点三五兆字节内含阿里云官方短信服务的PHP开发包调用示例完整演示了从配置访问密钥、初始化客户端、调用发送短信接口到解析返回结果的流程同时梳理了短信模板变量替换、签名审核规则、同步与异步调用差异、异常与错误码排查以及使用加密传输和妥善保管密钥等安全注意事项知识点覆盖全面。此外该开发包还支持查询发送状态、接收短信验证码等扩展操作便于开发者按业务需求二次开发。已有二百七十四人浏览学习借助该示例可以直观理解接口参数含义与调试技巧在实际项目中快速完成短信功能对接有效减少踩坑与返工。1. 阿里云短信接口demo.zip一个压缩包里藏着的完整调用链路做过对接短信功能的后端都懂第一次拿到阿里云短信接口demo.zip以为解压就能跑通结果被AccessKey、签名、模板、endpoint四个概念轮番劝退。这个压缩包不大通常就是pom.xml、一个配置文件、一个发送示例类但它背后对应的是完整的链路开通短信服务、申请签名、申请模板、创建RAM子账号、引入SDK、调SendSms、读懂错误码。它能解决“如何在十分钟内把第一条短信发到手机”的问题适合刚接手短信需求的后端开发也适合被产品催着“今天就上验证码”的工程同学。这里把demo值得复用的部分拆开讲透哪些要改、哪些别动、发不出去先查哪里。2. 先看懂短信接口的调用模型再动demo代码很多人解压demo就急着跑main这是新手阶段最常见的动作。我一般劝他们先花十分钟看明白这个接口是怎么工作的——demo能跑通不代表业务能扛真实流量短信这种接口一旦上线发不出去挨骂的是你不是demo。2.1 一次短信发送只是“受理”不是“到达”阿里云短信接口的调用模型并不复杂客户端拿着AccessKey构造请求把手机号、签名名、模板号、模板变量拼成参数调用SendSms这个Action请求发到dysmsapi.aliyuncs.com这个endpoint。阿里云后端校验请求合法性——这一步校验的是AccessKey签名也叫请求签名——再校验短信签名和模板的归属与状态都通过之后才把短信交给运营商下发。这里有个新手普遍会踩的认知坑SendSms返回的Code等于OK只代表阿里云受理了这条消息不代表手机已经收到。短信真正下发的状态要等运营商回执存储在阿里云侧需要用另一个接口QuerySendDetails按手机号和日期去查或者在控制台看发送记录。把“受理成功”当成“发送成功”对外承诺是很多线上事故的起点。错误码也要提前建立认知。阿里云短信返回的错误码分成几类前缀不同含义完全不同常见的类型如下错误码前缀/样式含义典型处理isv.*产品侧参数或业务规则错误检查参数、签名、模板、频控isp.*服务侧或运营商回执错误稍后重试或查具体子码SignatureDoesNotMatchAccessKey或签名算法错误检查密钥和本地时间Throttling接口调用请求过于频繁降低频率稍后重试收到isv开头的基本是代码或配置问题自己排查isp开头的多半是短期故障重试比改代码有效。这个分类能让排错少走弯路。另一个关键点是AccessKey的权限模型。demo里通常用的主账号AccessKey能跑通但风险极大。生产环境建议在RAM里开一个子账号只授予短信服务相关权限把AccessKeyId和AccessKeySecret放到环境变量或密钥管理服务不能让它们出现在代码仓库。这不是小题大做短信接口涉及资金和骚扰风险AccessKey一旦泄露后果比泄露数据库密码严重得多。短信签名和模板也不是凭空就能用的。新账号在控制台申请签名、申请模板后要等待审核通过才能发送。个人认证账号和企业认证账号可申请的签名类型、模板内容范围不同营销类短信基本和企业认证绑定。demo里自带的测试签名只能用于本地验证真实业务需要用自己的资质重新申请。2.2 demo.zip里的常见文件布局阿里云官方给出的短信demo在国内基本以zip形式分发解压之后通常长这样。这里说的是常见Java版本Python、PHP、Node.js的结构大同小异demo.zip ├── pom.xml // Maven 依赖声明 ├── src/main/resources/application.properties // 运行时配置 ├── src/main/java/com/aliyun/demo/SendSmsDemo.java // 发送示例 └── README.txt // 配置说明与申请入口pom.xml声明短信SDK依赖Java版核心是dysmsapi20170525配套teaopenapi这类基础库。application.properties里放的是accessKeyId、accessKeySecret、signName、templateCode几个运行时参数。SendSmsDemo.java是主流程初始化客户端、构造请求、发送、打印响应。为什么demo里用properties而不是yaml因为demo要跨框架复用Spring Boot工程里yaml需要特定解析器而properties是JDK原生支持的键值格式任何Java工程都能读。你在自己工程里换成yaml没问题但理解它用properties是为了最大兼容。把demo导入IDEA时记得在Maven面板勾选自动导入等依赖下载完成再打开SendSmsDemo.java否则会看到大量标红报错。Eclipse则是Import Existing Maven Project。依赖没拉完之前不要急着运行JDK版本也要确认新版SDK要求JDK 8以上。我特别提醒一点很多demo为了方便演示把AccessKey和签名模板直接写在源码里。你可以把demo当作学习材料但生产环境必须把这些配置挪到环境变量或配置中心。之前有个同事图省事把AccessKey提交到私有Git仓库后来仓库权限配置失误被外部扫描到一夜之间被刷几千条短信。扣费还在其次更麻烦的是签名被投诉短期封禁业务全部受影响。这条血泪经验记牢。2.3 为什么用demo而不是直接啃OpenAPI文档OpenAPI文档写得再全对第一次接入的人也不够友好。原因在于版本差异。阿里云短信接口的SDK有两代实现老一代用DefaultProfile初始化新一代以teaopenapi为基础用Config对象设置endpoint两代代码的包名、类名、调用方式完全不同。如果你搜到一个老博客照着写跟新SDK对不上编译都过不去。判断demo代码新旧有个简单办法看pom里依赖的artifactId是dysmsapi20170525还是aliyun-java-sdk-core前者是新版后者是老版。新版代码里设置endpoint用的是config.endpoint老版则是在request里setDomain。如果看的教程和你的demo不是同代直接放弃那篇教程以demo自带的pom为准。demo的价值在于它把“哪个版本配哪种初始化方式”这件事定死了。你解压出来的pom和代码是配套的先跑通再改业务逻辑比对着文档一遍遍试要快得多。但它也有“负面价值”demo为了说明问题通常把异常处理简化成打印堆栈把配置硬编码。直接拿demo上线是另一种翻车姿势。正确用法是用demo打通链路然后把骨架搬到自己的工程补上日志、连接池、异常分级、失败重试。实际上demo的定位更像地图——告诉你路怎么走实际开车的是你的代码。别把地图当车。看到这里模型已经立住了下面把demo跑起来。3. 把demo跑起来从Maven依赖到第一条短信理论模型看完了接下来动手。这里从解压demo开始把常见Java流程拆成三步配仓库、改配置、发短信。每一步我会标注哪些参数必须改哪些是demo里带过来但生产要格外小心的。3.1 用Maven配阿里云仓库把依赖拉到本地国内直接用Maven中央仓库拉阿里云SDK有时候会很慢特别是第一次拉teaopenapi那一组依赖容易卡住。常见做法是在Maven的settings.xml里配置阿里云公共仓库镜像地址是maven.aliyun.com/repository/public。这个镜像同时聚合了中央仓库和阿里云的制品仓库短信SDK的坐标能直接命中。mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror把这段加到$MAVEN_HOME/conf/settings.xml的 节点里。如果你只在自己用户目录配了~/.m2/settings.xml效果一样。mirrorOfcentral表示只镜像中央仓库不影响你自己定义的私有仓库这是最稳妥的写法不建议写成mirrorOf*把私有仓库也强制代理掉。配置完成后在pom.xml里声明依赖。demo自带的pom一般已经写好如果你自己新建工程坐标写法如下dependency groupIdcom.aliyun/groupId artifactIddysmsapi20170525/artifactId version以你拉取到的最新稳定版为准/version /dependency这里不写死具体版本号因为版本在持续发布。公司有统一BOM管理的话这个依赖的版本可以交给BOM统一约束业务pom里不用写version。拉取后用mvn dependency:tree看一眼确认teaopenapi相关传递依赖都已就位。老项目里如果还留着aliyun-java-sdk-core的旧依赖避免新旧混用新版SDK会和老包冲突建议统一到新SDK。Spring Boot工程导入demo时如果IDEA识别不到依赖先执行mvn clean install把依赖拉到本地再刷新。还有一类常见问题是本地Maven仓库损坏清理~/.m2/repository/com/aliyun目录后重新拉取即可不用折腾全局配置。3.2 配置AccessKey、签名名、模板号、手机号依赖拉下来下一步是填配置。demo里的application.properties给了四个核心配置项。其中AccessKeyId和AccessKeySecret最敏感不要写死在文件里启动时从环境变量读取更安全accessKeyId${ALIYUN_ACCESS_KEY_ID} accessKeySecret${ALIYUN_ACCESS_KEY_SECRET} signName你的短信签名 templateCodeSMS_000000000${}语法是Spring占位符方式demo如果不用Spring直接在代码里System.getenv()读取也一样。重点在于AccessKeyId和Secret必须来自安全环境不要在仓库出现真实值。signName是你在控制台申请通过后看到的签名名称比如“某某科技”templateCode是模板批准后的编号格式是SMS_加数字。这两个值在控制台菜单里都能找到。提示如果代码里直接写AccessKey并提交到Git仓库即使在私有仓库也存在泄露风险。建议用环境变量或配置中心管理并在启动时校验环境变量是否为空。AccessKey的获取路径需要说清楚。常见做法是在阿里云控制台进入RAM访问控制创建一个子用户勾选编程访问生成一对AccessKey。然后给子用户添加权限策略短信服务相关的系统策略名称一般是AliyunDysmsFullAccess。不建议直接拿主账号AccessKey因为主账号权限范围太大一旦泄露影响面不可控。手机号也可以放到配置里但生产环境手机号是动态入参demo里填一个自己的号码即可方便确认是否真的收到。我一般会把手机号和signName、templateCode分开处理签名和模板是相对固定的静态配置手机号和模板参数是每次请求的动态数据混在一起后面不好维护。3.3 发送第一条短信核心代码与参数说明配置就绪写发送逻辑。新版SDK的初始化方式和老版区别很大建议直接跟demo保持一致。新版一般写法如下import com.aliyun.dysmsapi20170525.Client; import com.aliyun.dysmsapi20170525.models.SendSmsRequest; import com.aliyun.dysmsapi20170525.models.SendSmsResponse; import com.aliyun.teaopenapi.models.Config; public class SendSmsDemo { public static void main(String[] args) throws Exception { // 1. 从环境变量读取AccessKey避免硬编码 String accessKeyId System.getenv(ALIYUN_ACCESS_KEY_ID); String accessKeySecret System.getenv(ALIYUN_ACCESS_KEY_SECRET); // 2. 初始化客户端短信服务endpoint全局唯一 Config config new Config() .setAccessKeyId(accessKeyId) .setAccessKeySecret(accessKeySecret); config.endpoint dysmsapi.aliyuncs.com; Client client new Client(config); // 3. 构造短信请求TemplateParam是JSON字符串 SendSmsRequest request new SendSmsRequest() .setPhoneNumbers(13800138000) .setSignName(你的短信签名) .setTemplateCode(SMS_000000000) .setTemplateParam({\code\:\1234\}); // 4. 同步发送并输出返回结果 SendSmsResponse response client.sendSms(request); System.out.println(response.getBody().getCode()); System.out.println(response.getBody().getMessage()); } }这段代码的逻辑拆开看第一步从环境变量取AccessKey避免硬编码第二步用Config对象设置endpoint短信接口所有region统一走dysmsapi.aliyuncs.com不像ECS那样需要分地域域名第三步构造SendSmsRequest四个入参是关键第四步同步调用sendSms拿到响应。四个参数的含义和边界我整理了一张表参数类型说明是否必须phoneNumbersString接收手机号只支持单个号码不用加86是signNameString短信签名需在控制台审核通过是templateCodeString短信模板编号格式为SMS_开头是templateParamStringJSON格式字符串key需与模板变量一致模板带变量时必须最容易被搞混的是TemplateParam。它看起来像对象实际上是一个JSON格式的字符串而且JSON里的key必须和模板里声明的变量名完全一致。模板内容如果写了“您的验证码为${code}${minute}分钟内有效”那么TemplateParam必须是{code:1234,minute:5}多一个、少一个、大小写不一样都会直接报变量相关错误。另一个容易踩的是引号转义Java字符串里表示JSON的double quote必须加反斜杠所以我一般不用手拼字符串而是用Jackson或Gson序列化Map代码更可读也更安全。响应体里的Code、Message、RequestId、BizId四个值都值得打日志。RequestId用于向阿里云提交工单时定位请求BizId是交易流水号查询明细时要带上。demo里只打印了Code和Message生产环境的日志要全量记录这四个字段。到这里main方法跑通手机上应该能收到测试短信。如果没收到别急着怀疑代码先看第五章的排错清单。4. 从demo到生产签名、模板、参数与异步改造demo能发出短信只是第一步离生产可用还有四个必须处理的点。这一章聊的是把demo代码拿去做真实业务时哪些参数要重新定义、哪些调用方式要换掉。4.1 region、endpoint、签名、模板四者的对应关系短信服务和ECS、OSS最大的差异在于短信接口的endpoint是全局唯一的dysmsapi.aliyuncs.com不分华东、华北、海外。但这不意味着没有地域概念——需要在控制台确认短信服务开通在哪个地域以及RAM权限策略里是否限制了地域。多数情况下主账号开通的短信服务可以在任意地域调用但如果用了RAM自定义策略可能被限定在cn-hangzhou这就是本地能发、线上403的原因之一。签名和模板是绑在账号上的资源不跟地域走但跟账号类型走。个人认证账号和企业认证账号可申请的签名类型、模板内容范围不同。个人账号只能发验证码和通知类营销类短信基本和企业认证绑定。这意味着如果业务方要求发营销推广短信demo里那套测试签名肯定不行必须用企业资质去申请。我一般会在工程里建一个短信配置常量类把签名和模板集中管理用枚举区分业务场景。验证码模板是一个枚举值通知模板是一个枚举值避免业务代码里到处裸写模板编号。散落的配置一多改一个签名名都要全局搜索实在痛苦。Spring Boot工程里还可以把这组枚举交给Spring管理缓存到本地Map一次性加载每次发送只查内存。4.2 TemplateParam的JSON转义与变量约束短信模板的变量不是随便传的。每个变量有长度限制验证码类变量默认不超过20个字符具体看模板审核结果。另外阿里云会对变量值做敏感词过滤你传了“免费”两个字很可能被系统拦截这类营销敏感词会直接导致发送失败。一个常见错误是企业内部发给会员的短信里带“免费领取”这类词模板审核时通常通不过。第二个常见错误是模板变量在代码里拼JSON时引号转义出错。我建议的做法是始终用Jackson序列化结构体不要手写字符串MapString, String paramMap new HashMap(); paramMap.put(code, randomCode); paramMap.put(minute, 5); String templateParam new ObjectMapper().writeValueAsString(paramMap);这段代码的价值在于Map的key决定变量名value就是变量值序列化后天然是合法JSON不需要关心字符串里有没有特殊符号。如果值本身是中文JSON库也会自动处理Unicode转义省去一行行找引号的体力活。另一个隐蔽的坑是模板变量在控制台审核时写的是中文变量名而代码里TemplateParam的key必须和模板变量完全一致。有些模板设置人员习惯在控制台用“验证码”作为变量名代码里却写了“code”发送时就报变量不匹配。所以建立模板的时候我一般直接在变量列表里定义英文字段名从源头避免编码混乱。4.3 线程池发送验证码异步与流控的平衡demo里用main方法同步调用sendSms一个请求几秒内返回看起来没问题。但放到Spring Boot里直接这么写就麻烦了接口内同步调短信用户的请求会一直挂着短信服务端偶尔耗时超过2秒整个HTTP链路就感觉卡顿。常见做法是把发送拆成异步接收请求时只做参数校验和快速校验然后丢线程池发送让接口立即返回。private final ExecutorService smsPool new ThreadPoolExecutor( 4, 8, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(1000)); public void sendCodeAsync(String phone, String code) { smsPool.execute(() - { // 组装请求并调用sendSms SendSmsResponse resp client.sendSms(req); if (!OK.equals(resp.getBody().getCode())) { // 记录错误码触发告警 } }); }异步线程池的核心数不需要很大因为短信接口本身有频控。阿里云对短信接口有默认的频率限制控制台或错误码说明里有明确值但有个经验性结论对同一个手机号发送验证码通常一分钟内不能超过一条同一个签名下的总量也有每日阈值。线程池开得再大也会被频控卡住所以异步的目的是提高接口响应速度不是为了无限并发。我习惯把频控设计在业务侧给同一手机号的验证码发送加一个Redis分布式锁或本地时间窗口判断距离上次发送不到60秒直接拒绝让频控错误不要打到阿里云上。这样既保护自己也避免把账号的发送额度烧光。队列满时要有策略一般是丢弃并提示稍后重试不能无限往阻塞队列里塞内存会爆。4.4 阿里云短信API与云MAS平台接口的选择并不是所有短信业务都必须走阿里云。很多企业和集团客户特别是运营商背景的甲方会要求使用中国移动的云MAS平台也就是常说的“云MAS平台http(java)接口文档短信”。它的对接方式不同云MAS对外暴露的是HTTP接口Java侧用HttpClient构造表单请求就能提交不需要引入重量级SDK但鉴权方式、加密规则、状态推送机制是另一套标准。什么时候选阿里云短信API什么时候选云MAS我从工程角度给判断标准如果只是产品里的验证码、通知追求接入速度和稳定性选阿里云demo和文档生态完整排错有据可循如果业务明确需要走移动通道才能有更好的到达率或者合同指定云MAS那按它的http接口文档实现也不复杂只是要做好通道切换的抽象。我见过不少项目在代码里写死厂商短信客户端后来要换通道时只能大改。所以我会在业务代码和厂商SDK之间加一层SmsSender接口阿里云和云MAS各自实现切换通道时只动配置。这个抽象听起来多写几个类但真的遇到通道故障要切换时能省一整夜的折腾。5. 阿里云短信api发不出去的排查清单五个高频坑我把多年踩坑的记录整理成排查清单。每一条都是“现象→原因→解决”三段写法遇到问题直接对照比翻文档快。5.1 报错isv.SMS_SIGNATURE_ILLEGAL签名不合法现象调用SendSms返回Codeisv.SMS_SIGNATURE_ILLEGALMessage提示签名不合法或未审核。原因三选一。一是signName写错了常见是把“某某科技”写成“某科技”二是签名还没审核通过新申请的签名有审核周期期间调用会被拒绝三是签名被停用通常是内容违规或投诉过多导致。解决先到控制台“签名管理”页面看签名状态状态必须为已审核通过。再看代码里signName参数是否和控制台完全一致包括括号、空格这类字符。在工程里执行grep -R signName src/能找到所有引用点逐一核对。如果签名被停用只能重新申请所有引用旧签名的代码得一并改掉。5.2 报错isv.MOBILE_NUMBER_ILLEGAL手机号不被认可现象参数里手机号是“13800138000”这种正常号段但接口返回手机号不合法。原因手机号字段传了带国家码、带空格、带横杠的格式或者变量在传输过程中被解析成数字导致精度丢失。我遇到过用Long类型传手机号前导0被截断的情况还有一次是业务方把多个手机号用逗号拼接传进来以为能群发其实SendSms一次只接受一个号码。解决发送前做严格清洗统一用字符串接收手机号去掉86、空格、横杠再用正则^1\d{10}$校验。打印入参的字节长度看号码里是否有肉眼看不见的零宽字符这类字符在复制粘贴时偶尔混进来。群发需求不要用SendSms循环要用批量发送能力这属于另一条产品线的能力。5.3 报错isv.TEMPLATE_MISSING_PARAMETER模板变量对不上现象模板审核内容里有${code}和${minute}但代码只传了code接口报缺少参数。原因TemplateParam里的key集合和模板变量集合不匹配。多传、少传、key拼写不一致都会触发这一类错误。有些模板变量被设置成中文代码里却是英文同样报错。解决最直接的排查是把TemplateParam打印出来和模板内容逐字对比。记住模板变量名是认证时定义的代码必须对齐认证值而不是“我觉得叫什么就叫什么”。用JSON库序列化参数Map减少转义问题的同时也方便打印日志核对。如果模板里变量很多可以写个单元测试把模板变量枚举和参数Map做差集校验漏传了在发短信之前就报错。5.4 报错isv.BUSINESS_LIMIT_CONTROL流控触发的玄学现象代码没变配置没换突然批量发送时大量报BUSINESS_LIMIT_CONTROL。刚发完一条紧接着发第二条也报这个错。原因阿里云对单手机号、单签名、单账号均有频率控制。验证码场景尤其严格同一号码在几秒内重复请求基本必然触发流控。还有一些限制是账户维度的比如每天总量、高峰并发量控制台不一定每个都能看到明确阈值。解决业务侧加发送间隔控制验证码场景通常对同一号码限60秒一条。补发按钮要有倒计时防止手抖点三次。真正常量发送的场景提前规划号码维度的时间窗分散提交。这个错误的玄学在于阈值可能随账号风控状态调整所以要保留完整日志出问题时方便申请解除限制。5.5 本地能发、线上挂环境与权限差异现象demo在本地用主账号AccessKey发送一切正常上到测试环境就开始报Forbidden或InvalidAccessKeyId或者一直超时。原因线上环境可能没加载环境变量AccessKey拉取为空也可能是运维只给测试环境配了某个RAM角色角色没有短信服务权限。还有一类是网络层问题测试环境没有放通到dysmsapi.aliyuncs.com的HTTPS出口。解决登录线上服务器执行echo $ALIYUN_ACCESS_KEY_ID检查环境变量确认值存在且与本地一致。权限方面到RAM控制台确认角色的授权策略里包含短信服务相关权限或者直接配置子账号AccessKey。网络方面用curl -I https://dysmsapi.aliyuncs.com探测连通性如果出口被防火墙限制加白名单或配置公司出口网关。6. 从demo到工程化验证码存储、落库与对账demo的问题是你发了第一条短信但不知道它后来怎么样了。生产系统中验证码要能校验、发送记录要能查、状态要能对账。这章讲三个工程化动作把demo代码变成可靠的短信子系统。6.1 验证码有效期与Redis存储验证码发出去5分钟有效这是业务常态。存储在Redis里key的格式我用sms:code:{phone}value存验证码过期时间300秒。校验时先取出来比对比对成功立即删除防止同一个验证码被重复使用。还需要记录一个每手机号的发送时间key用来做60秒的发送间隔限制两步锁串起来就同时解决有效期和频控。6.2 发送记录落库每次发送都要落库字段设计不需要复杂一个发送日志表就够了。核心字段包括手机号、模板号、参数JSON、请求返回的Code、Message、BizId、RequestId、发送时间。BizId是阿里云返回的业务IDQuerySendDetails的时候要用。落库的时间点要选在拿到响应之后避免把没受理成功的记录也写进去。状态字段标记为受理成功或受理失败后续对账时再更新为已到达或未到达。6.3 用QuerySendDetails做对账短信的最终状态以运营商回执为准QuerySendDetails接口能按手机号和发送日期查到明细。我一般做一个定时任务每小时扫描发送日志里状态仍是已受理的记录批量调QuerySendDetails更新终态。注意这个接口也有频率限制按批处理、按序号排队不要一张表全量扫一遍直接并发查询。对账能发现很多“假成功”用户投诉没收到时拿BizId去查往往发现短信被运营商拦截或手机号停机。我自己的习惯是所有AccessKey配置统一放环境变量并在启动时做一次显式校验配不上就FailFast进程不启动不让错误配置带着跑。有一次线上验证码大面积发不出去控制台看签名还在排查大半天结果是RAM子账号AccessKey过期轮换后线上配置文件没同步更新。从那以后凡是短信相关的密钥变更我都会在变更单里强制加一条“启动自检”步骤。这算是我在短信接口上最值得分享的一个习惯希望帮到你。本文还有配套的精品资源点击获取