PHP项目如何实现用户手机绑定?

wen java案例 3

本文目录导读:

PHP项目如何实现用户手机绑定?

  1. 整体流程
  2. 数据库设计
  3. 核心代码实现
  4. 前端实现(示例)
  5. 安全注意事项
  6. 扩展功能
  7. 最佳实践建议

我来详细介绍PHP项目中实现用户手机绑定的完整方案。

整体流程

graph TD
    A[用户输入手机号] --> B[发送验证码]
    B --> C[用户输入验证码]
    C --> D[验证码校验]
    D --> E{验证通过?}
    E -->|是| F[绑定手机号]
    E -->|否| G[提示错误]
    F --> H[更新数据库]
    H --> I[绑定成功]

数据库设计

-- 用户表增加手机号字段
ALTER TABLE `users` 
ADD COLUMN `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
ADD COLUMN `phone_verified` tinyint(1) DEFAULT 0 COMMENT '手机是否验证',
ADD COLUMN `bind_time` datetime DEFAULT NULL COMMENT '绑定时间';
-- 验证码表
CREATE TABLE `verification_codes` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `phone` varchar(20) NOT NULL COMMENT '手机号',
    `code` varchar(10) NOT NULL COMMENT '验证码',
    `type` tinyint(1) DEFAULT 1 COMMENT '1-绑定 2-解绑 3-修改',
    `expire_time` datetime NOT NULL COMMENT '过期时间',
    `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
    `used` tinyint(1) DEFAULT 0 COMMENT '是否已使用',
    PRIMARY KEY (`id`),
    KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

核心代码实现

发送验证码

<?php
// send_code.php
require_once 'config.php';
class PhoneBind {
    private $db;
    private $smsGateway;
    public function sendVerificationCode($phone) {
        // 1. 验证手机号格式
        if (!$this->validatePhone($phone)) {
            return ['code' => -1, 'msg' => '手机号格式不正确'];
        }
        // 2. 检查手机号是否已被绑定
        if ($this->isPhoneBound($phone)) {
            return ['code' => -2, 'msg' => '该手机号已绑定其他账号'];
        }
        // 3. 检查发送频率(同一手机号60秒内只能发一次)
        if (!$this->checkSendLimit($phone)) {
            return ['code' => -3, 'msg' => '发送太频繁,请稍后再试'];
        }
        // 4. 生成验证码
        $code = str_pad(rand(0, 999999), 6, '0', STR_PAD_LEFT);
        // 5. 保存验证码到数据库
        $expireTime = date('Y-m-d H:i:s', time() + 300); // 5分钟有效
        $this->saveCode($phone, $code, $expireTime);
        // 6. 发送短信
        $result = $this->sendSMS($phone, $code);
        if ($result['success']) {
            return ['code' => 1, 'msg' => '验证码已发送'];
        } else {
            return ['code' => 0, 'msg' => '发送失败,请重试'];
        }
    }
    private function validatePhone($phone) {
        return preg_match('/^1[3-9]\d{9}$/', $phone);
    }
    private function isPhoneBound($phone) {
        $sql = "SELECT id FROM users WHERE phone = ? AND phone_verified = 1";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$phone]);
        return $stmt->rowCount() > 0;
    }
    private function checkSendLimit($phone) {
        $sql = "SELECT created_at FROM verification_codes 
                WHERE phone = ? AND type = 1 
                ORDER BY created_at DESC LIMIT 1";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$phone]);
        $last = $stmt->fetch();
        if ($last) {
            $lastTime = strtotime($last['created_at']);
            if (time() - $lastTime < 60) {
                return false;
            }
        }
        return true;
    }
    private function saveCode($phone, $code, $expireTime) {
        $sql = "INSERT INTO verification_codes (phone, code, type, expire_time) 
                VALUES (?, ?, 1, ?)";
        $stmt = $this->db->prepare($sql);
        return $stmt->execute([$phone, $code, $expireTime]);
    }
    private function sendSMS($phone, $code) {
        // 使用短信服务商API(如阿里云短信、腾讯云短信等)
        // 这里是示例代码,需要替换为实际的短信服务
        $sms = new SMSApi();
        return $sms->send($phone, "您的验证码是:{$code},5分钟内有效");
    }
}

验证并绑定手机号

<?php
// bind_phone.php
class PhoneBind {
    public function bindPhone($phone, $code, $userId) {
        // 1. 验证验证码
        $verifyResult = $this->verifyCode($phone, $code);
        if (!$verifyResult['success']) {
            return $verifyResult;
        }
        // 2. 开始事务
        $this->db->beginTransaction();
        try {
            // 3. 标记验证码已使用
            $this->markCodeUsed($phone, $code);
            // 4. 更新用户手机号
            $sql = "UPDATE users SET 
                    phone = ?, 
                    phone_verified = 1, 
                    bind_time = NOW() 
                    WHERE id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$phone, $userId]);
            // 5. 提交事务
            $this->db->commit();
            return ['code' => 1, 'msg' => '绑定成功'];
        } catch (Exception $e) {
            $this->db->rollBack();
            return ['code' => 0, 'msg' => '绑定失败,请重试'];
        }
    }
    private function verifyCode($phone, $code) {
        // 清除过期的验证码
        $this->cleanExpiredCodes();
        $sql = "SELECT * FROM verification_codes 
                WHERE phone = ? AND code = ? AND type = 1 
                AND used = 0 AND expire_time > NOW() 
                ORDER BY created_at DESC LIMIT 1";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$phone, $code]);
        $record = $stmt->fetch();
        if (!$record) {
            return ['success' => false, 'code' => -1, 'msg' => '验证码无效或已过期'];
        }
        return ['success' => true];
    }
    private function markCodeUsed($phone, $code) {
        $sql = "UPDATE verification_codes SET used = 1 
                WHERE phone = ? AND code = ?";
        $stmt = $this->db->prepare($sql);
        return $stmt->execute([$phone, $code]);
    }
    private function cleanExpiredCodes() {
        $sql = "DELETE FROM verification_codes WHERE expire_time < NOW()";
        return $this->db->exec($sql);
    }
}

短信服务接口示例(以阿里云为例)

<?php
// sms.php
require_once 'aliyun-sdk/aliyun-php-sdk-core/Config.php';
use Dm\Request\V20151123 as Dm;
class SMSApi {
    private $accessKeyId = 'your_access_key_id';
    private $accessSecret = 'your_access_secret';
    private $signName = '您的签名';
    private $templateCode = 'SMS_123456789'; // 短信模板ID
    public function send($phone, $code) {
        try {
            $iClientProfile = DefaultProfile::getProfile("cn-hangzhou", $this->accessKeyId, $this->accessSecret);
            $client = new DefaultAcsClient($iClientProfile);
            $request = new Dm\SingleSendSmsRequest();
            $request->setSignName($this->signName);
            $request->setTemplateCode($this->templateCode);
            $request->setRecNum($phone);
            $request->setParamString(json_encode(['code' => $code]));
            $response = $client->getAcsResponse($request);
            return ['success' => true, 'data' => $response];
        } catch (Exception $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
}

前端实现(示例)

<!-- bind_phone.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">绑定手机号</title>
</head>
<body>
    <div class="bind-form">
        <h3>绑定手机号</h3>
        <div class="form-group">
            <label>手机号:</label>
            <input type="tel" id="phone" maxlength="11" placeholder="请输入手机号">
        </div>
        <div class="form-group">
            <label>验证码:</label>
            <div class="code-input">
                <input type="text" id="code" maxlength="6" placeholder="请输入验证码">
                <button id="sendCode" onclick="sendCode()">获取验证码</button>
            </div>
        </div>
        <button onclick="bindPhone()" class="submit-btn">立即绑定</button>
    </div>
    <script>
    // 发送验证码
    function sendCode() {
        let phone = document.getElementById('phone').value;
        let btn = document.getElementById('sendCode');
        if (!/^1[3-9]\d{9}$/.test(phone)) {
            alert('请输入正确的手机号');
            return;
        }
        btn.disabled = true;
        let countdown = 60;
        btn.textContent = countdown + 's';
        // 倒计时
        let timer = setInterval(() => {
            countdown--;
            btn.textContent = countdown + 's';
            if (countdown <= 0) {
                clearInterval(timer);
                btn.disabled = false;
                btn.textContent = '获取验证码';
            }
        }, 1000);
        // 发送请求
        fetch('send_code.php', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({phone: phone})
        })
        .then(response => response.json())
        .then(data => {
            if (data.code !== 1) {
                alert(data.msg);
                clearInterval(timer);
                btn.disabled = false;
                btn.textContent = '获取验证码';
            }
        })
        .catch(error => {
            alert('发送失败,请重试');
            clearInterval(timer);
            btn.disabled = false;
            btn.textContent = '获取验证码';
        });
    }
    // 绑定手机号
    function bindPhone() {
        let phone = document.getElementById('phone').value;
        let code = document.getElementById('code').value;
        if (!phone || !code) {
            alert('请填写完整信息');
            return;
        }
        fetch('bind_phone.php', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                phone: phone,
                code: code,
                user_id: getCurrentUserId() // 从session或其他方式获取
            })
        })
        .then(response => response.json())
        .then(data => {
            if (data.code === 1) {
                alert('绑定成功');
                // 跳转或刷新页面
                window.location.href = 'user_center.php';
            } else {
                alert(data.msg);
            }
        });
    }
    </script>
</body>
</html>

安全注意事项

防刷机制

// 增加IP限制
class SecurityCheck {
    public function checkRateLimit($phone, $ip) {
        // 同一IP每小时最多发送10次
        $sql = "SELECT COUNT(*) FROM verification_codes 
                WHERE ip = ? AND created_at > ?";
        $count = $this->db->query($sql, [$ip, date('Y-m-d H:i:s', strtotime('-1 hour'))])->fetchColumn();
        if ($count >= 10) {
            return false;
        }
        // 同一手机号每天最多发送5次
        $sql = "SELECT COUNT(*) FROM verification_codes 
                WHERE phone = ? AND created_at > ?";
        $count = $this->db->query($sql, [$phone, date('Y-m-d H:i:s', strtotime('-1 day'))])->fetchColumn();
        if ($count >= 5) {
            return false;
        }
        return true;
    }
}

验证码安全

  • 使用6位数字验证码(平衡安全性和用户体验)
  • 验证码有效期5分钟
  • 一次性使用
  • 增加图形验证码防止机器攻击

扩展功能

手机号解绑

public function unbindPhone($userId, $phone, $code) {
    // 类似绑定流程,验证身份后解绑
    // 清除手机号和验证状态
}

更换手机号

public function changePhone($userId, $oldPhone, $newPhone, $code) {
    // 先验证旧手机号
    // 再绑定新手机号
}

最佳实践建议

  1. 短信服务商选择:选择可靠的短信服务商(阿里云、腾讯云、华为云等)
  2. 错误处理:完善的错误日志记录
  3. 用户引导:清晰的交互流程提示
  4. 安全性:使用HTTPS,防止中间人攻击
  5. 测试环境:使用测试模板或虚拟号进行测试

这样完整的手机绑定系统就实现了,可以根据实际需求进行调整和扩展。

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