PHP项目如何实现短信验证码?

wen PHP项目 10

本文目录导读:

PHP项目如何实现短信验证码?

  1. 整体流程
  2. 代码实现
  3. 配置示例
  4. 数据库表结构(可选)
  5. 安全建议
  6. 集成短信服务商

我来介绍一下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;

安全建议

  1. 频率限制:设置每个手机号的发送间隔和每日限制
  2. IP限制:对同一IP的请求进行限制
  3. 验证码强度:使用6位数字,包含字母+数字更安全
  4. 过期时间:验证码有效期通常为3-5分钟
  5. 一次性使用:验证码验证成功后立即删除
  6. HTTPS:全程使用HTTPS传输
  7. 防暴力破解:限制验证码尝试次数

集成短信服务商

常用的短信服务商:

  • 阿里云短信:稳定,价格适中
  • 腾讯云短信:类似阿里云
  • Twilio:国际短信,国外使用
  • Submail:中国短信服务

选择合适的服务商后,参考其API文档进行集成即可。

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