tp8必须用接口+容器绑定实现短信服务,禁用静态方法和全局函数;需定义smsserviceinterface接口、实现类构造注入sdk客户端、在provider.php中绑定,并通过类型提示注入使用。

TP8 里直接 new 阿里云 SDK 或抄 TP6 的短信类,99% 会报 Class "think\sms\Sms" not found 或 Call to undefined method think\facade\Cache::record() ——不是你配错了,是底层契约全变了。
TP8 必须用接口 + 容器绑定,不能硬写静态方法
TP8 彻底弃用全局函数和静态单例,Sms::send() 这种调用方式在自动加载规则下根本找不到类。老项目里常见的 extend/AliSmsService.php 放静态方法,控制器里 use extend\AliSmsService; AliSmsService::send(),在 TP8 中会直接抛出类未定义错误。
- 必须定义接口:
SmsServiceInterface,只声明send()和sendBatch()方法 - 实现类(如
AliyunSmsService)构造函数只能接收依赖(如\AlibabaCloud\Client\AlibabaCloud实例或配置数组),不能自己 new 客户端 - 在
app/provider.php里绑定:SmsServiceInterface::class => AliyunSmsService::class,否则app()->make(SmsServiceInterface::class)拿不到实例 - 控制器中用类型提示注入:
public function __construct(private SmsServiceInterface $smsService),别再app()->make()临时取
阿里云 SDK 初始化必须走容器,不能每次发都 new client
TP5/TP6 习惯在发送方法里调 AlibabaCloud::accessKeyClient()->asGlobalClient(),TP8 下这会触发 No client found 错误,因为全局 client 注册机制已移除。
- 客户端实例应由容器统一创建并注入:在
AliyunSmsService构造函数中接收\AlibabaCloud\Client\AlibabaCloud实例,而不是自己调静态方法 - 配置项(
region、timeout、access_key_id)从config('sms.aliyun')读取,别硬编码 -
TemplateParam必须用json_encode($data, JSON_UNESCAPED_UNICODE),否则中文签名或变量乱码,导致模板审核不通过 - 异常必须归一化:
ClientException和ServerException全部 catch 后转成自定义SmsSendException,上层只处理这一种异常
防刷限流的 Cache 操作必须原子,不能分两步
用 Cache::inc($key) 再 Cache::expire($key, 3600) 看似合理,但两个操作非原子——中间若超时或崩溃,$key 就永久存在,用户再也无法重发。
- 必须用
Cache::tag('sms')->set($key, $value, $ttl),Redis 驱动下支持按 tag 批量清理,避免残留 key - 本地
File缓存驱动在多机部署时完全失效,生产环境必须强制切到redis或memcached - 手机号校验必须前置:
filter_var($phone, FILTER_VALIDATE_INT)不行,得用正则/^1[3-9]\d{9}$/,SDK 不做输入过滤,错格式直接返回模糊的InvalidParameter.PhoneNumber - TTL 别设成 5 分钟(验证码有效期),防刷窗口应设为 60 秒 + 频次限制(如 1 小时最多 5 次),否则用户输错两次就被锁死一小时
回调路由必须走中间件生命周期,不能直接读 $_POST
旧方案在控制器里直接 $_POST['PhoneNumbers'] 解析阿里云回调,会绕过 TP8 的 CSRF 防护、请求验证和中间件链,导致安全漏洞或参数解析失败。
- 回调地址必须注册为标准路由,并绑定中间件(如
AllowCrossDomain、VerifyCsrfToken::except()) - 用
$this->request->post()或$this->request->param()读参,确保走完整生命周期 - 签名验证逻辑必须封装进独立服务类(如
AliyunCallbackValidator),别塞在控制器里 - 日志记录必须用
think\facade\Log::channel('sms')->info(),Log::record()已废弃,否则日志不落盘
最常被忽略的是:Service 默认是单例,但一旦构造函数里注入了带请求上下文的对象(比如 Request 或 session-aware 的 Cache),并发下会共享状态。这种场景下,光靠接口绑定不够,得配合工厂模式或容器作用域控制实例生命周期。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











