ThinkPHP发短信需对齐四要素:AK/SK来源、签名模板审核状态、TemplateParam字段名与模板占位符一致、host与regionId匹配;TP5调创世华信须转GB2312编码、MD5大写密码、urlencode参数并用cURL;容联云SDK需手动注册PSR-4命名空间;所有场景均须规避敏感信息硬编码、频次失控及响应体未记录三大风险。

ThinkPHP 发短信不是调一个函数就能成的事,得看用哪家服务商、用什么 SDK、怎么塞参数、怎么兜住错误——漏掉任意一环,线上就收不到验证码。
TP6 用阿里云 SDK 发短信,SendSms 请求老失败?
常见现象是返回 InvalidAccessKeyId.NotFound 或 SignatureDoesNotMatch,本质不是代码写错,而是四个地方没对齐:
-
access_key_id和access_key_secret必须从阿里云「访问控制 RAM」里拿,不是「用户中心」或旧版 AK 页面; - 签名(
SignName)和模板(TemplateCode)必须已在「短信服务控制台 → 国内消息 → SMS签名管理 / SMS模板管理」中通过审核,草稿/待审核状态会直接被拒绝; -
TemplateParam必须是 JSON 字符串,且字段名要和模板里定义的占位符完全一致,比如模板写的是{code},就得传{"code": "123456"},不能传{"verify_code": "123456"}; - 请求
host和regionId要匹配,例如用cn-hangzhou就必须配dysmsapi.aliyuncs.com,换cn-shanghai就得查对应 endpoint。
示例关键片段:
->options([
'query' => [
'PhoneNumbers' => $mobile,
'SignName' => $config['sign_name'],
'TemplateCode' => $config['template_code']['verify'],
'TemplateParam' => json_encode(['code' => $code], JSON_UNESCAPED_UNICODE),
]
])
TP5 用原生 cURL 调创世华信接口,file_get_contents 返回空?
这是最典型的编码+协议陷阱。创世华信的 http://sh2.cshxsp.com/sms.aspx 接口默认只认 GB2312 编码,而 TP5 默认 UTF-8,不转码就会被静默丢弃内容。
立即学习“PHP免费学习笔记(深入)”;
- 手机号、内容必须先用
iconv('UTF-8', 'GB2312//IGNORE', $content)转码; -
password要用 MD5 32位大写(strtoupper(md5($password))),小写或 base64 都不行; - URL 中含中文或特殊符号(如【】)时,必须用
urlencode()包一层,不能直接拼; - 别用
file_get_contents,它不支持 POST,得换curl_init()并显式设CURLOPT_POST和CURLOPT_POSTFIELDS。
容联云(云通讯)在 TP6 里报 Class 'OrgwlwRest' not found?
这个错误不是类没加载,是自动加载路径断了。容联云老 SDK 的 Rest.class.php 依赖 ThinkPHP 传统命名空间映射(Org → ThinkPHP/Library/Org/),但 TP6 已废弃该机制。
- 不能直接扔进
app/library或extend下就完事; - 得手动注册命名空间:在
composer.json的"autoload"→"psr-4"里加"Org\wlw\": "app/library/Org/wlw/",然后运行composer dump-autoload; - 或者更简单:把
Rest.class.php改名成Rest.php,类声明改成namespace applibraryOrgwlw;,再按标准 PSR-4 加载; - 注意
$serverPort = '8883'是 HTTPS 端口,但实际请求要用https://协议,不能写http://+ 8883。
所有短信接口都绕不开的三个硬伤
无论用腾讯云、阿里云还是第三方,只要没显式处理这三点,上线后必出问题:
- 敏感参数(
access_key_secret、accountToken)不能写死在控制器或配置文件里,必须走环境变量($_ENV)或加密配置中心; - 没做发送频次限制,同一个手机号 1 分钟内重复触发,轻则被服务商限流,重则触发风控封 IP;
- 没记录原始响应体(
$result->toArray()或curl_error()),出错时只能看到 “发送失败”,根本没法区分是网络超时、签名错误,还是运营商拦截。
真正难的从来不是“怎么发”,而是“发失败时你知道为什么失败”。



















