养生
easyphp下载(告别折腾!PHP短信发送神器EasySMS,一套接口搞定30+运营商)

引言:解决短信集成的核心痛点

在实际业务开发中,短信功能集成常面临多重挑战:多家服务商API差异、故障转移机制复杂、代码耦合度高等问题。overtrue/easy-sms 组件通过统一抽象层,为PHP开发者提供了一套优雅的解决方案。

环境要求与安装

# 要求 PHP >= 8.4composer require "overtrue/easy-sms"

核心配置详解

基础配置结构

<?php// config/sms.phpreturn [    // HTTP请求超时设置    'timeout' => 5.0,        // 默认发送策略    'default' => [        // 策略配置:顺序、随机、轮询等        'strategy' => \Overtrue\EasySms\Strategies\OrderStrategy::class,                // 网关执行顺序        'gateways' => [            'aliyun',    // 主用通道            'yunpian',   // 备用通道1            'qcloud',    // 备用通道2        ],    ],        // 网关配置    'gateways' => [        // 调试网关配置        'errorlog' => [            'file' => runtime_path('logs/sms.log'),        ],                // 阿里云配置        'aliyun' => [            'access_key_id' => env('SMS_ALIYUN_KEY_ID'),            'access_key_secret' => env('SMS_ALIYUN_KEY_SECRET'),            'sign_name' => env('SMS_ALIYUN_SIGN_NAME'),            'template_code' => 'SMS_001', // 默认模板        ],                // 云片网络配置        'yunpian' => [            'api_key' => env('SMS_YUNPIAN_API_KEY'),        ],                // 腾讯云配置        'qcloud' => [            'sdk_app_id' => env('SMS_QCLOUD_APP_ID'),            'secret_id' => env('SMS_QCLOUD_SECRET_ID'),            'secret_key' => env('SMS_QCLOUD_SECRET_KEY'),            'sign_name' => env('SMS_QCLOUD_SIGN_NAME'),        ],                // 华为云配置        'huawei' => [            'endpoint' => 'https://api.rtc.huaweicloud.com',            'app_key' => env('SMS_HUAWEI_APP_KEY'),            'app_secret' => env('SMS_HUAWEI_APP_SECRET'),            'from' => [                'channel' => '89******87',                'template_id' => '0000000'            ],        ],    ],];

环境变量配置

# .env 文件SMS_ALIYUN_KEY_ID=your_access_key_idSMS_ALIYUN_KEY_SECRET=your_access_key_secretSMS_ALIYUN_SIGN_NAME=您的签名SMS_YUNPIAN_API_KEY=your_yunpian_keySMS_QCLOUD_APP_ID=1400000000SMS_QCLOUD_SECRET_ID=your_secret_idSMS_QCLOUD_SECRET_KEY=your_secret_keySMS_QCLOUD_SIGN_NAME=腾讯云签名

实战应用示例

基础短信发送服务类

<?phpnamespace app\service;use Overtrue\EasySms\EasySms;use Overtrue\EasySms\Exceptions\NoGatewayAvailableException;class SmsService{    private EasySms $easySms;        public function __construct()    {        $config = config('sms');        $this->easySms = new EasySms($config);    }            public function sendVerificationCode(string $phone, string $code, string $template = null): array    {        $message = [            'template' => $template ?? config('sms.gateways.aliyun.template_code'),            'data' => [                'code' => $code,                'time' => '5分钟'            ],            'content' => sprintf('您的验证码是%s,有效期5分钟。', $code)        ];                return $this->send($phone, $message);    }            public function sendNotification(string $phone, string $content, array $data = []): array    {        $message = [            'content' => $content,            'data' => $data        ];                return $this->send($phone, $message);    }            private function send(string $phone, array $message): array    {        try {            $result = $this->easySms->send($phone, $message);                        // 记录发送日志            $this->logSendResult($phone, $message, $result);                        return [                'success' => true,                'data' => $result            ];                    } catch (NoGatewayAvailableException $e) {            $errorInfo = $this->parseGatewayErrors($e);                        // 记录错误日志            $this->logSendError($phone, $message, $errorInfo);                        return [                'success' => false,                'error' => '短信发送失败',                'details' => $errorInfo            ];        }    }            private function parseGatewayErrors(NoGatewayAvailableException $e): array    {        $errors = [];        foreach ($e->getResults() as $gateway => $result) {            $errors[$gateway] = [                'exception' => $result['exception']->getMessage(),                'code' => $result['exception']->getCode() ?? '未知错误'            ];        }                return $errors;    }            private function logSendResult(string $phone, array $message, array $result): void    {        logger()->info('短信发送成功', [            'phone' => $phone,            'message' => $message,            'result' => $result,            'time' => date('Y-m-d H:i:s')        ]);    }            private function logSendError(string $phone, array $message, array $errorInfo): void    {        logger()->error('短信发送失败', [            'phone' => $phone,            'message' => $message,            'errors' => $errorInfo,            'time' => date('Y-m-d H:i:s')        ]);    }}

控制器中使用示例

<?phpnamespace app\controller;use app\service\SmsService;use support\Request;use support\Response;class SmsController{        public function sendCode(Request $request): Response    {        $phone = $request->post('phone');        $code = mt_rand(100000, 999999);                // 验证手机号格式        if (!$this->validatePhone($phone)) {            return json(['success' => false, 'message' => '手机号格式错误']);        }                $smsService = new SmsService();        $result = $smsService->sendVerificationCode($phone, $code);                if ($result['success']) {            // 将验证码存入缓存,5分钟过期            cache('sms_code_' . $phone, $code, 300);                        return json(['success' => true, 'message' => '验证码发送成功']);        }                return json(['success' => false, 'message' => '验证码发送失败']);    }            public function batchSend(Request $request): Response    {        $phones = $request->post('phones', []);        $content = $request->post('content');                $results = [];        $smsService = new SmsService();                foreach ($phones as $phone) {            if ($this->validatePhone($phone)) {                $results[$phone] = $smsService->sendNotification($phone, $content);            }        }                return json([            'success' => true,            'data' => $results        ]);    }            private function validatePhone(string $phone): bool    {        return preg_match('/^1[3-9]\d{9}$/', $phone) === 1;    }}

高级特性与最佳实践

自定义发送策略

<?php// 自定义权重策略class WeightStrategy implements \Overtrue\EasySms\Contracts\StrategyInterface{    public function apply(array $gateways): array    {        $weights = [            'aliyun' => 60,  // 60%流量            'qcloud' => 30,   // 30%流量              'yunpian' => 10, // 10%流量        ];                $result = [];        foreach ($weights as $gateway => $weight) {            $result = array_merge($result, array_fill(0, $weight, $gateway));        }                shuffle($result);        return $result;    }}// 在配置中使用自定义策略'default' => [    'strategy' => \app\strategy\WeightStrategy::class,    'gateways' => ['aliyun', 'qcloud', 'yunpian'],];

网关性能监控

<?phpclass MonitorableSmsService extends SmsService{    private array $gatewayStats = [];        public function send(string $phone, array $message): array    {        $startTime = microtime(true);                try {            $result = parent::send($phone, $message);            $this->recordSuccess($startTime);                        return $result;        } catch (\Exception $e) {            $this->recordFailure($startTime, $e);            throw $e;        }    }        private function recordSuccess(float $startTime): void    {        $responseTime = microtime(true) - $startTime;        // 记录到监控系统        $this->updateGatewayStats('success', $responseTime);    }        private function recordFailure(float $startTime, \Exception $e): void    {        $responseTime = microtime(true) - $startTime;        // 记录到监控系统        $this->updateGatewayStats('failure', $responseTime, $e->getMessage());    }}

故障转移与灾备方案

自动降级配置

<?php// 根据时间段自动选择网关class TimebasedStrategy implements \Overtrue\EasySms\Contracts\StrategyInterface{    public function apply(array $gateways): array    {        $hour = date('H');                // 工作时间使用主通道,夜间使用备用通道        if ($hour >= 9 && $hour < 18) {            return ['aliyun', 'qcloud', 'yunpian']; // 主从顺序        } else {            return ['yunpian', 'aliyun', 'qcloud']; // 夜间备用顺序        }    }}

测试用例

单元测试示例

<?phpnamespace tests\service;use app\service\SmsService;use PHPUnit\framework\TestCase;class SmsServiceTest extends TestCase{    public function testSendVerificationCode()    {        $smsService = new SmsService();                // 使用模拟手机号        $testPhone = '13800138000';        $testCode = '123456';                $result = $smsService->sendVerificationCode($testPhone, $testCode);                $this->assertIsArray($result);        $this->assertArrayHasKey('success', $result);                // 在实际项目中,可以验证日志记录等副作用    }        public function testPhonevalidation()    {        $validPhones = ['13800138000', '13912345678'];        $invalidPhones = ['123456', '1380013800a', ''];                foreach ($validPhones as $phone) {            $this->assertTrue($this->validatePhone($phone));        }                foreach ($invalidPhones as $phone) {            $this->assertFalse($this->validatePhone($phone));        }    }        private function validatePhone(string $phone): bool    {        return preg_match('/^1[3-9]\d{9}$/', $phone) === 1;    }}

部署与运维建议

Docker 部署配置

# DockerfileFROM php:8.4-fpm# 安装必要扩展RUN docker-php-ext-install bcmath# 安装ComposerCOPY --from=composer:latest /usr/bin/composer /usr/bin/composerWORKDIR /var/www/htmlCOPY . .RUN composer install --no-dev --optimize-autoloader

总结

通过 overtrue/easy-sms 组件,我们实现了:

  1. 统一接入层:标准化多家短信服务商接入流程
  2. 自动故障转移:内置多网关轮询和降级机制
  3. 可观测性:完整的日志记录和监控支持
  4. 易于扩展:支持自定义策略和网关扩展
  5. 测试友好:便于编写单元测试和集成测试

该方案特别适合需要高可用性短信服务的生产环境,大幅降低了短信功能的技术复杂度和维护成本。


顶一下()     踩一下()

热门推荐

发表评论
0评