用cURL调通短信接口最直接,需构造Authorization头、POST数据、检查HTTP状态码和业务码;验证码存Redis并设TTL,键名用"sms:verify:{phone}";加限流防刷码;验证时须校验存在性、时效性、一致性并立即删除。

用 cURL 调通短信接口是最直接的路径
绝大多数国内短信服务商(如阿里云、腾讯云、亿美软通)都提供 HTTP API,PHP 里用 cURL 发请求是最快落地的方式。别碰封装过重的 SDK,容易卡在认证或签名逻辑里出错。
关键点在于:构造好 Authorization 头(可能是 AccessKeyId + Signature,也可能是 Bearer Token),POST JSON 或表单数据,检查返回的 http_code 是否为 200,再解析响应体里的 Code 字段(注意不是 HTTP 状态码,是业务码,比如 "OK" 或 0)。
- 务必设置
CURLOPT_TIMEOUT≤ 10,避免用户等超时卡死 - 不要用
file_get_contents()代替cURL,它默认不支持 POST JSON,且无法细粒度控制超时和错误 - 测试时先用服务商控制台生成「测试签名」和「测试模板」,避免因资质审核未通过导致一直返回
"Template not approved"
验证码得存进 Redis,别用 $_SESSION 或文件
短信验证码必须可跨请求验证,且有严格过期时间(通常 5 分钟)。$_SESSION 依赖 Cookie 和 PHP 进程生命周期,分布式部署下会失效;写文件则并发冲突、无自动过期。
Redis 的 SET key value EX 300 是最匹配的方案——写入即设 300 秒 TTL,后续用 GET 拿值比对,命中即 DEL 掉,天然防重放。
立即学习“PHP免费学习笔记(深入)”;
- 键名建议用
"sms:verify:{phone}",避免不同手机号互相覆盖 - 存的值别只存纯数字验证码,至少拼上时间戳或随机盐,比如
json_encode(['code' => '123456', 'ts' => time()]),方便后续审计 - 如果没装 Redis 扩展,别硬上
phpredis,改用PredisComposer 包更稳妥,但要注意其setex()方法参数顺序和原生命令相反
防止恶意刷码得加基础限流
没防护的短信接口会被脚本扫号,一小时发几千条,钱烧得快,还可能被运营商拉黑。不需要上 Redis+Lua 复杂方案,用一个简单计数器就够用。
在发码前,先对手机号做频率检查:用 Redis 的 INCR + EXPIRE 组合,比如以 "sms:freq:{phone}" 为 key,每调一次 INCR,首次时顺带 EXPIRE 60(1 分钟窗口)。若返回值 > 5,就拒绝本次发送并返回 "Too many requests"。
- 注意
INCR返回的是字符串,得用(int)转一下再比较 - 这个限流和验证码存储用的是两个独立 key,别混在一起,否则用户输错几次就锁死发码功能
- 前端按钮点击后要禁用 60 秒,并显示倒计时,否则用户狂点照样触发后端多次请求
验证时别只比对数字,还要检查时效和是否已使用
用户提交验证码后,不能只查 Redis 里有没有这个值。必须同时确认三件事:key 是否存在、JSON 解析后 code 是否匹配、ts 是否在 5 分钟内。漏掉任一条件都会导致安全缺口。
更关键的是:验证成功后必须立刻 DEL 对应 key。否则同一验证码可重复使用,形同虚设。
- 别用
GET后再DEL—— 中间可能被并发请求抢走。改用GETDEL(Redis 6.2+)或事务MULTI/EXEC,兼容性起见推荐后者 - 如果解析 JSON 失败(比如存的时候格式错乱),直接当验证失败处理,别抛异常暴露内部信息
- 数据库里记录发送日志时,字段要包含手机号、模板 ID、返回的
MessageId(用于运营商侧溯源),别只记“发送成功”四个字
真正麻烦的从来不是调通接口,而是把时间窗口、并发竞争、错误分支、日志追踪这四件事串成一条不掉链子的线。少盯一个点,上线后就得半夜爬起来看监控。



















