本文目录导读:

我来介绍一下PHP项目中实现短信验证码的完整方案。
整体流程
sequenceDiagram
participant User as 用户
participant Front as 前端页面
participant Back as 后端PHP
participant SMS as 短信服务商
participant Cache as 缓存/数据库
User->>Front: 输入手机号
Front->>Back: 发送验证码请求
Back->>Back: 生成随机验证码
Back->>Cache: 存储验证码(含过期时间)
Back->>SMS: 调用短信接口
SMS-->>User: 发送短信验证码
User->>Front: 输入验证码
Front->>Back: 提交验证
Back->>Cache: 获取存储的验证码
Back-->>Front: 返回验证结果
代码实现
1 发送验证码
<?php
// send_sms_code.php
require_once 'config.php';
require_once 'vendor/autoload.php';
class SmsService {
private $redis;
private $smsConfig;
public function __construct() {
// 连接Redis存储验证码
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
// 短信服务商配置
$this->smsConfig = [
'aliyun' => [
'accessKeyId' => 'your_access_key_id',
'accessSecret' => 'your_access_secret',
'signName' => '你的签名',
'templateCode' => 'SMS_123456789'
]
];
}
/**
* 发送验证码
*/
public function sendCode($phone) {
// 1. 生成6位随机验证码
$code = str_pad(rand(0, 999999), 6, '0', STR_PAD_LEFT);
// 2. 验证码有效期5分钟
$expireTime = 300;
// 3. 存储到Redis
$key = "sms_code:{$phone}";
$this->redis->setex($key, $expireTime, $code);
// 4. 记录发送频率(60秒内不能重复发送)
$rateKey = "sms_rate:{$phone}";
if ($this->redis->exists($rateKey)) {
throw new Exception('发送过于频繁,请稍后重试');
}
$this->redis->setex($rateKey, 60, '1');
// 5. 调用短信接口发送
return $this->sendSms($phone, $code);
}
/**
* 发送短信(以阿里云为例)
*/
private function sendSms($phone, $code) {
$params = [
'PhoneNumbers' => $phone,
'SignName' => $this->smsConfig['aliyun']['signName'],
'TemplateCode' => $this->smsConfig['aliyun']['templateCode'],
'TemplateParam' => json_encode(['code' => $code])
];
// 调用阿里云短信API
// 这里使用阿里云SDK示例
$result = $this->callAliyunSms($params);
return $result['Code'] === 'OK';
}
/**
* 校验验证码
*/
public function verifyCode($phone, $inputCode) {
$key = "sms_code:{$phone}";
$storedCode = $this->redis->get($key);
if ($storedCode === false) {
return ['success' => false, 'message' => '验证码已过期'];
}
if ($storedCode !== $inputCode) {
return ['success' => false, 'message' => '验证码错误'];
}
// 验证成功后删除验证码(防止重复使用)
$this->redis->del($key);
return ['success' => true, 'message' => '验证成功'];
}
}
// API接口
header('Content-Type: application/json; charset=utf-8');
if ($_POST['action'] === 'send') {
$phone = $_POST['phone'];
// 验证手机号格式
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
echo json_encode(['success' => false, 'message' => '手机号格式错误']);
exit;
}
try {
$sms = new SmsService();
$sms->sendCode($phone);
echo json_encode(['success' => true, 'message' => '验证码发送成功']);
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
}
if ($_POST['action'] === 'verify') {
$phone = $_POST['phone'];
$code = $_POST['code'];
$sms = new SmsService();
$result = $sms->verifyCode($phone, $code);
echo json_encode($result);
}
2 前端页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">短信验证码示例</title>
<style>
.container {
max-width: 400px;
margin: 50px auto;
padding: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-group input {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
.code-group {
display: flex;
gap: 10px;
}
.code-group input {
flex: 1;
}
.code-group button {
padding: 8px 15px;
background: #1890ff;
color: white;
border: none;
cursor: pointer;
white-space: nowrap;
}
.code-group button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<h2>发送短信验证码</h2>
<form id="smsForm">
<div class="form-group">
<label>手机号:</label>
<input type="tel" id="phone" placeholder="请输入手机号" maxlength="11">
</div>
<div class="form-group">
<label>验证码:</label>
<div class="code-group">
<input type="text" id="code" placeholder="请输入验证码" maxlength="6">
<button id="sendBtn" onclick="sendCode()">获取验证码</button>
</div>
</div>
<div class="form-group">
<button type="button" onclick="verifyCode()">验证</button>
</div>
</form>
<div id="message" style="margin-top: 20px;"></div>
</div>
<script>
let timer = null;
let countdown = 60;
// 发送验证码
function sendCode() {
const phone = document.getElementById('phone').value;
const btn = document.getElementById('sendBtn');
if (!/^1[3-9]\d{9}$/.test(phone)) {
alert('请输入正确的手机号');
return;
}
// 禁用按钮并开始倒计时
btn.disabled = true;
startCountdown(btn);
// 发送请求
fetch('send_sms_code.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `action=send&phone=${phone}`
})
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success);
if (!data.success) {
btn.disabled = false;
clearInterval(timer);
btn.textContent = '获取验证码';
}
})
.catch(error => {
console.error('Error:', error);
btn.disabled = false;
clearInterval(timer);
btn.textContent = '获取验证码';
});
}
// 倒计时
function startCountdown(btn) {
let seconds = 60;
btn.textContent = `${seconds}秒后重试`;
timer = setInterval(() => {
seconds--;
if (seconds <= 0) {
clearInterval(timer);
btn.disabled = false;
btn.textContent = '获取验证码';
} else {
btn.textContent = `${seconds}秒后重试`;
}
}, 1000);
}
// 验证验证码
function verifyCode() {
const phone = document.getElementById('phone').value;
const code = document.getElementById('code').value;
if (!phone || !code) {
alert('请填写完整信息');
return;
}
fetch('send_sms_code.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `action=verify&phone=${phone}&code=${code}`
})
.then(response => response.json())
.then(data => {
showMessage(data.message, data.success);
})
.catch(error => {
console.error('Error:', error);
});
}
// 显示消息
function showMessage(message, isSuccess) {
const div = document.getElementById('message');
div.innerHTML = `<div style="padding: 10px; background: ${isSuccess ? '#f6ffed' : '#fff2f0'}; border: 1px solid ${isSuccess ? '#b7eb8f' : '#ffccc7'}">${message}</div>`;
}
</script>
</body>
</html>
配置示例
<?php
// config.php
// 数据库配置(如果使用数据库存储)
define('DB_HOST', 'localhost');
define('DB_NAME', 'your_database');
define('DB_USER', 'your_username');
define('DB_PASS', 'your_password');
// Redis配置
define('REDIS_HOST', '127.0.0.1');
define('REDIS_PORT', 6379);
// 短信服务商配置
define('SMS_PROVIDER', 'aliyun'); // 可选:aliyun, tencent, twilio等
// 阿里云短信配置
define('ALIYUN_ACCESS_KEY', 'your_access_key');
define('ALIYUN_ACCESS_SECRET', 'your_access_secret');
define('ALIYUN_SIGN_NAME', '你的签名');
define('ALIYUN_TEMPLATE_CODE', 'SMS_123456789');
// 验证码配置
define('CODE_LENGTH', 6); // 验证码长度
define('CODE_EXPIRE', 300); // 过期时间(秒)
define('SEND_INTERVAL', 60); // 发送间隔(秒)
define('MAX_SEND_PER_DAY', 10); // 每日最大发送次数
数据库表结构(可选)
-- 验证码记录表
CREATE TABLE `sms_codes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`phone` varchar(11) NOT NULL COMMENT '手机号',
`code` varchar(6) NOT NULL COMMENT '验证码',
`status` tinyint(1) DEFAULT '0' COMMENT '状态:0未使用,1已使用',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`expired_at` datetime DEFAULT NULL COMMENT '过期时间',
`used_at` datetime DEFAULT NULL COMMENT '使用时间',
PRIMARY KEY (`id`),
KEY `idx_phone_code` (`phone`, `code`),
KEY `idx_expired` (`expired_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 短信发送记录表
CREATE TABLE `sms_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`phone` varchar(11) NOT NULL COMMENT '手机号',
`content` varchar(500) DEFAULT NULL COMMENT '短信内容',
`provider` varchar(20) DEFAULT NULL COMMENT '服务商',
`status` varchar(20) DEFAULT NULL COMMENT '发送状态',
`response` text COMMENT '接口返回信息',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_phone` (`phone`),
KEY `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
安全建议
- 频率限制:设置每个手机号的发送间隔和每日限制
- IP限制:对同一IP的请求进行限制
- 验证码强度:使用6位数字,包含字母+数字更安全
- 过期时间:验证码有效期通常为3-5分钟
- 一次性使用:验证码验证成功后立即删除
- HTTPS:全程使用HTTPS传输
- 防暴力破解:限制验证码尝试次数
集成短信服务商
常用的短信服务商:
- 阿里云短信:稳定,价格适中
- 腾讯云短信:类似阿里云
- Twilio:国际短信,国外使用
- Submail:中国短信服务
选择合适的服务商后,参考其API文档进行集成即可。