PHP项目短信验证码如何对接发送

wen PHP项目 21

本文目录导读:

PHP项目短信验证码如何对接发送

  1. 基本流程
  2. 主流短信服务商对接
  3. 验证码生成与存储
  4. 完整的发送流程
  5. 最佳实践与安全建议
  6. 注意事项

我来详细介绍PHP项目对接短信验证码的完整流程和最佳实践。

基本流程

用户输入手机号 → 生成验证码 → 调用短信API发送 → 存储验证码(带有效期) → 用户输入验证码 → 校验

主流短信服务商对接

阿里云短信服务

安装SDK:

composer require alibabacloud/dysmsapi

发送短信验证码:

<?php
namespace App\Service;
use AlibabaCloud\Client\AlibabaCloud;
use AlibabaCloud\Client\Exception\ClientException;
use AlibabaCloud\Client\Exception\ServerException;
class SmsService
{
    private $accessKeyId;
    private $accessKeySecret;
    private $signName;
    public function __construct()
    {
        $this->accessKeyId = '你的AccessKeyId';
        $this->accessKeySecret = '你的AccessKeySecret';
        $this->signName = '你的短信签名';
    }
    /**
     * 发送验证码
     * @param string $phone 手机号
     * @param string $code 验证码
     * @return bool
     */
    public function sendVerifyCode($phone, $code)
    {
        AlibabaCloud::accessKeyClient($this->accessKeyId, $this->accessKeySecret)
            ->regionId('cn-hangzhou')
            ->asDefaultClient();
        try {
            $result = AlibabaCloud::rpc()
                ->product('Dysmsapi')
                ->version('2017-05-25')
                ->action('SendSms')
                ->method('POST')
                ->host('dysmsapi.aliyuncs.com')
                ->options([
                    'query' => [
                        'PhoneNumbers' => $phone,
                        'SignName' => $this->signName,
                        'TemplateCode' => 'SMS_你的模板ID',
                        'TemplateParam' => json_encode([
                            'code' => $code
                        ]),
                    ],
                ])
                ->request();
            $response = $result->toArray();
            if ($response['Code'] == 'OK') {
                return true;
            }
            throw new \Exception($response['Message']);
        } catch (ClientException $e) {
            throw new \Exception($e->getErrorMessage());
        } catch (ServerException $e) {
            throw new \Exception($e->getErrorMessage());
        }
    }
}

腾讯云短信服务

安装SDK:

composer require tencentcloud/sms

发送代码:

<?php
namespace App\Service;
use TencentCloud\Common\Credential;
use TencentCloud\Sms\V20210111\SmsClient;
use TencentCloud\Sms\V20210111\Models\SendSmsRequest;
class TencentSmsService
{
    private $secretId;
    private $secretKey;
    private $smsSdkAppId;
    private $signName;
    public function __construct()
    {
        $this->secretId = '你的SecretId';
        $this->secretKey = '你的SecretKey';
        $this->smsSdkAppId = '你的应用ID';
        $this->signName = '你的签名';
    }
    public function sendVerifyCode($phone, $code)
    {
        try {
            $cred = new Credential($this->secretId, $this->secretKey);
            $client = new SmsClient($cred, "ap-guangzhou");
            $req = new SendSmsRequest();
            $req->PhoneNumberSet = ["+86" . $phone];
            $req->SmsSdkAppId = $this->smsSdkAppId;
            $req->SignName = $this->signName;
            $req->TemplateId = "你的模板ID";
            $req->TemplateParamSet = [$code];
            $resp = $client->SendSms($req);
            return $resp->SendStatusSet[0]->Code === 'Ok';
        } catch (\Exception $e) {
            throw new \Exception($e->getMessage());
        }
    }
}

云片短信服务

<?php
namespace App\Service;
class YunpianSmsService
{
    private $apiKey;
    private $apiUrl = 'https://sms.yunpian.com/v2/sms/single_send.json';
    public function __construct()
    {
        $this->apiKey = '你的API Key';
    }
    public function sendVerifyCode($phone, $code)
    {
        $data = [
            'apikey' => $this->apiKey,
            'mobile' => $phone,
            'text' => "【公司名称】您的验证码是{$code},5分钟内有效。"
        ];
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->apiUrl);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($httpCode == 200) {
            $result = json_decode($response, true);
            return $result['code'] == 0;
        }
        return false;
    }
}

验证码生成与存储

验证码生成

<?php
namespace App\Service;
class VerifyCodeService
{
    /**
     * 生成验证码
     * @param int $length 长度
     * @param bool $isNumeric 是否纯数字
     * @return string
     */
    public static function generate($length = 6, $isNumeric = true)
    {
        if ($isNumeric) {
            $min = pow(10, $length - 1);
            $max = pow(10, $length) - 1;
            return strval(rand($min, $max));
        }
        $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        $code = '';
        for ($i = 0; $i < $length; $i++) {
            $code .= $chars[rand(0, strlen($chars) - 1)];
        }
        return $code;
    }
}

使用Redis存储验证码

<?php
namespace App\Service;
use Predis\Client;
class SmsCodeManager
{
    private $redis;
    private $expireTime = 300; // 5分钟有效期
    public function __construct()
    {
        $this->redis = new Client([
            'scheme' => 'tcp',
            'host'   => '127.0.0.1',
            'port'   => 6379,
        ]);
    }
    /**
     * 存储验证码
     * @param string $phone 手机号
     * @param string $code 验证码
     * @param string $type 验证码类型(register/login/reset)
     */
    public function saveCode($phone, $code, $type = 'verify')
    {
        $key = "sms_code:{$type}:{$phone}";
        $this->redis->setex($key, $this->expireTime, $code);
    }
    /**
     * 验证验证码
     * @param string $phone 手机号
     * @param string $code 验证码
     * @param string $type 验证码类型
     * @return bool
     */
    public function verifyCode($phone, $code, $type = 'verify')
    {
        $key = "sms_code:{$type}:{$phone}";
        $storedCode = $this->redis->get($key);
        if ($storedCode === null) {
            return false; // 验证码已过期
        }
        if ($storedCode !== $code) {
            return false; // 验证码错误
        }
        // 验证成功后删除验证码(防止重复使用)
        $this->redis->del($key);
        return true;
    }
    /**
     * 检查发送频率限制
     * @param string $phone 手机号
     * @return bool
     */
    public function checkRateLimit($phone)
    {
        $key = "sms_rate:{$phone}";
        $count = $this->redis->get($key);
        if ($count === null) {
            // 60秒内只能发送1次
            $this->redis->setex($key, 60, 1);
            return true;
        }
        if ($count >= 1) {
            return false; // 超过频率限制
        }
        $this->redis->incr($key);
        return true;
    }
}

使用数据库存储验证码

CREATE TABLE `sms_verify_codes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `phone` varchar(20) NOT NULL COMMENT '手机号',
  `code` varchar(10) NOT NULL COMMENT '验证码',
  `type` varchar(20) NOT NULL DEFAULT 'verify' COMMENT '验证码类型',
  `expire_time` datetime NOT NULL COMMENT '过期时间',
  `used` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已使用',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_phone_type` (`phone`, `type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

完整的发送流程

<?php
namespace App\Controller;
use App\Service\SmsService;
use App\Service\VerifyCodeService;
use App\Service\SmsCodeManager;
class SmsController
{
    private $smsService;
    private $codeManager;
    public function __construct()
    {
        $this->smsService = new SmsService();
        $this->codeManager = new SmsCodeManager();
    }
    /**
     * 发送验证码接口
     * @param string $phone 手机号
     * @param string $type 验证码类型
     * @return bool
     */
    public function sendCode($phone, $type = 'verify')
    {
        try {
            // 1. 验证手机号格式
            if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
                throw new \Exception('手机号格式不正确');
            }
            // 2. 检查发送频率
            if (!$this->codeManager->checkRateLimit($phone)) {
                throw new \Exception('发送太频繁,请稍后再试');
            }
            // 3. 生成验证码
            $code = VerifyCodeService::generate();
            // 4. 发送短信
            $result = $this->smsService->sendVerifyCode($phone, $code);
            if ($result) {
                // 5. 存储验证码
                $this->codeManager->saveCode($phone, $code, $type);
                return ['success' => true, 'message' => '验证码已发送'];
            }
            throw new \Exception('短信发送失败');
        } catch (\Exception $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }
    /**
     * 验证验证码接口
     * @param string $phone 手机号
     * @param string $code 验证码
     * @param string $type 验证码类型
     * @return bool
     */
    public function verifyCode($phone, $code, $type = 'verify')
    {
        try {
            if ($this->codeManager->verifyCode($phone, $code, $type)) {
                return ['success' => true, 'message' => '验证成功'];
            }
            throw new \Exception('验证码错误或已过期');
        } catch (\Exception $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }
}

最佳实践与安全建议

配置管理

// config/sms.php
return [
    'driver' => env('SMS_DRIVER', 'aliyun'),
    'aliyun' => [
        'access_key_id' => env('ALIYUN_SMS_ACCESS_KEY_ID'),
        'access_key_secret' => env('ALIYUN_SMS_ACCESS_KEY_SECRET'),
        'sign_name' => env('ALIYUN_SMS_SIGN_NAME'),
        'template_code' => env('ALIYUN_SMS_TEMPLATE_CODE'),
    ],
    'tencent' => [
        'secret_id' => env('TENCENT_SMS_SECRET_ID'),
        'secret_key' => env('TENCENT_SMS_SECRET_KEY'),
        'app_id' => env('TENCENT_SMS_APP_ID'),
        'sign_name' => env('TENCENT_SMS_SIGN_NAME'),
        'template_id' => env('TENCENT_SMS_TEMPLATE_ID'),
    ],
];

安全措施

// 1. IP限制
$ip = $_SERVER['REMOTE_ADDR'];
$ipKey = "sms_ip_limit:{$ip}";
if ($redis->get($ipKey) >= 10) { // 同一IP每小时最多10次
    throw new \Exception('发送过于频繁');
}
// 2. 图形验证码
// 在发送短信前,需要验证图形验证码
public function beforeSendCode($captchaCode)
{
    session_start();
    if (strtolower($captchaCode) !== strtolower($_SESSION['captcha'])) {
        throw new \Exception('图形验证码错误');
    }
}
// 3. 手机号白名单
private $whiteList = ['13800138000']; // 测试手机号
if (!in_array($phone, $this->whiteList) && !$this->isProduction()) {
    // 非生产环境,只对白名单手机号发送真实短信
    $this->logVerifyCode($phone, $code);
    return true;
}

异常处理

class SmsException extends \Exception
{
    public function __construct($message, $code = 0)
    {
        parent::__construct($message, $code);
        // 记录日志
        Log::error("SMS Error: {$message}", [
            'code' => $code,
            'trace' => $this->getTraceAsString()
        ]);
    }
}

测试环境处理

// 开发环境不真实发送短信
public function sendVerifyCode($phone, $code)
{
    if (env('APP_ENV') !== 'production') {
        // 记录日志或返回固定验证码
        Log::info("测试验证码: {$phone} -> {$code}");
        return true;
    }
    // 真实发送逻辑
}

注意事项

  1. 验证码有效期:通常5分钟,重要操作可缩短至1-2分钟
  2. 频率限制:同一手机号60秒内只能发送1次,每天最多10次
  3. 验证码长度:建议4-6位数字
  4. 防刷机制
    • IP限制
    • 图形验证码
    • 设备指纹
  5. 成本控制:配置不同短信模板,合理使用签名
  6. 监控告警:监控短信发送成功率,异常及时告警

选择合适的短信服务商,根据业务需求配置不同的验证码策略,注意安全防护和成本控制。

抱歉,评论功能暂时关闭!