本文目录导读:

我来详细介绍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;
}
// 真实发送逻辑
}
注意事项
- 验证码有效期:通常5分钟,重要操作可缩短至1-2分钟
- 频率限制:同一手机号60秒内只能发送1次,每天最多10次
- 验证码长度:建议4-6位数字
- 防刷机制:
- IP限制
- 图形验证码
- 设备指纹
- 成本控制:配置不同短信模板,合理使用签名
- 监控告警:监控短信发送成功率,异常及时告警
选择合适的短信服务商,根据业务需求配置不同的验证码策略,注意安全防护和成本控制。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播