本文目录导读:

- 数据库设计
- 用户类(User.php)
- OAuth用户类(OAuthUser.php)
- 微信登录类(WechatAuth.php)
- 短信验证码类(SmsService.php)
- 绑定手机号控制器(BindPhoneController.php)
- 处理微信登录回调(CallbackController.php)
- 前端绑定页面(bind_phone.php)
- 路由配置
- 安全建议
我来帮你实现PHP第三方登录绑定手机号的完整方案,这需要集成第三方登录(如微信、QQ、GitHub等)和手机号绑定功能。
数据库设计
-- 用户表 CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `password` varchar(255) DEFAULT NULL COMMENT '密码', `nickname` varchar(50) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_phone` (`phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 第三方登录用户表 CREATE TABLE `oauth_users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '关联用户ID', `provider` varchar(20) NOT NULL COMMENT '第三方平台: wechat, qq, github等', `openid` varchar(100) NOT NULL COMMENT '第三方平台唯一标识', `unionid` varchar(100) DEFAULT NULL COMMENT '微信unionid', `nickname` varchar(50) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_provider_openid` (`provider`, `openid`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
用户类(User.php)
<?php
class User {
private $db;
public function __construct($db) {
$this->db = $db;
}
/**
* 通过手机号查找用户
*/
public function findByPhone($phone) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE phone = ?");
$stmt->execute([$phone]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* 创建用户
*/
public function create($data) {
$sql = "INSERT INTO users (phone, password, nickname, avatar)
VALUES (?, ?, ?, ?)";
$stmt = $this->db->prepare($sql);
$stmt->execute([
$data['phone'] ?? null,
$data['password'] ?? null,
$data['nickname'] ?? null,
$data['avatar'] ?? null
]);
return $this->db->lastInsertId();
}
/**
* 更新用户手机号
*/
public function updatePhone($userId, $phone) {
$stmt = $this->db->prepare("UPDATE users SET phone = ? WHERE id = ?");
return $stmt->execute([$phone, $userId]);
}
/**
* 检查手机号是否已绑定其他账号
*/
public function phoneExists($phone, $excludeUserId = null) {
$sql = "SELECT id FROM users WHERE phone = ?";
$params = [$phone];
if ($excludeUserId) {
$sql .= " AND id != ?";
$params[] = $excludeUserId;
}
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
return $stmt->fetch() !== false;
}
}
OAuth用户类(OAuthUser.php)
<?php
class OAuthUser {
private $db;
private $userModel;
public function __construct($db) {
$this->db = $db;
$this->userModel = new User($db);
}
/**
* 通过OAuth信息查找用户
*/
public function findByProvider($provider, $openid) {
$stmt = $this->db->prepare(
"SELECT ou.*, u.phone, u.nickname as user_nickname
FROM oauth_users ou
LEFT JOIN users u ON ou.user_id = u.id
WHERE ou.provider = ? AND ou.openid = ?"
);
$stmt->execute([$provider, $openid]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* 创建OAuth关联
*/
public function create($userId, $provider, $openid, $unionid = null, $nickname = null, $avatar = null) {
$stmt = $this->db->prepare(
"INSERT INTO oauth_users (user_id, provider, openid, unionid, nickname, avatar)
VALUES (?, ?, ?, ?, ?, ?)"
);
return $stmt->execute([
$userId, $provider, $openid, $unionid, $nickname, $avatar
]);
}
/**
* 通过手机号查找OAuth用户
*/
public function findByPhone($phone) {
$stmt = $this->db->prepare(
"SELECT ou.* FROM oauth_users ou
LEFT JOIN users u ON ou.user_id = u.id
WHERE u.phone = ?"
);
$stmt->execute([$phone]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* 更新OAuth用户信息
*/
public function updateOAuthInfo($id, $nickname, $avatar) {
$stmt = $this->db->prepare(
"UPDATE oauth_users SET nickname = ?, avatar = ? WHERE id = ?"
);
return $stmt->execute([$nickname, $avatar, $id]);
}
}
微信登录类(WechatAuth.php)
<?php
class WechatAuth {
private $appId;
private $appSecret;
private $redirectUri;
public function __construct($appId, $appSecret, $redirectUri) {
$this->appId = $appId;
$this->appSecret = $appSecret;
$this->redirectUri = $redirectUri;
}
/**
* 获取授权URL
*/
public function getAuthUrl($state = '') {
$params = [
'appid' => $this->appId,
'redirect_uri' => $this->redirectUri,
'response_type' => 'code',
'scope' => 'snsapi_login',
'state' => $state ?: uniqid()
];
return 'https://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params);
}
/**
* 通过code获取access_token
*/
public function getAccessToken($code) {
$params = [
'appid' => $this->appId,
'secret' => $this->appSecret,
'code' => $code,
'grant_type' => 'authorization_code'
];
$url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
$response = $this->httpGet($url);
return json_decode($response, true);
}
/**
* 获取用户信息
*/
public function getUserInfo($accessToken, $openid) {
$params = [
'access_token' => $accessToken,
'openid' => $openid
];
$url = 'https://api.weixin.qq.com/sns/userinfo?' . http_build_query($params);
$response = $this->httpGet($url);
return json_decode($response, true);
}
private function httpGet($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}
短信验证码类(SmsService.php)
<?php
class SmsService {
private $db;
public function __construct($db) {
$this->db = $db;
}
/**
* 生成并保存验证码
*/
public function sendVerifyCode($phone) {
// 生成6位随机验证码
$code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
// 验证码10分钟内有效
$expiresAt = date('Y-m-d H:i:s', time() + 600);
// 保存到数据库
$stmt = $this->db->prepare(
"INSERT INTO sms_codes (phone, code, expires_at) VALUES (?, ?, ?)"
);
$stmt->execute([$phone, $code, $expiresAt]);
// TODO: 调用短信API发送验证码
// $this->sendSms($phone, $code);
// 开发环境直接返回验证码
return $code;
}
/**
* 验证验证码
*/
public function verifyPhoneCode($phone, $code) {
$stmt = $this->db->prepare(
"SELECT * FROM sms_codes
WHERE phone = ? AND code = ? AND expires_at > NOW()
ORDER BY id DESC LIMIT 1"
);
$stmt->execute([$phone, $code]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result) {
// 验证成功后删除验证码
$deleteStmt = $this->db->prepare("DELETE FROM sms_codes WHERE id = ?");
$deleteStmt->execute([$result['id']]);
return true;
}
return false;
}
}
绑定手机号控制器(BindPhoneController.php)
<?php
class BindPhoneController {
private $db;
private $userModel;
private $oauthUserModel;
private $smsService;
public function __construct($db) {
$this->db = $db;
$this->userModel = new User($db);
$this->oauthUserModel = new OAuthUser($db);
$this->smsService = new SmsService($db);
session_start();
}
/**
* 发送验证码
*/
public function sendVerifyCode() {
header('Content-Type: application/json');
$phone = $_POST['phone'] ?? '';
// 验证手机号格式
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
echo json_encode(['code' => 400, 'msg' => '手机号格式不正确']);
return;
}
// 生成并发送验证码
$code = $this->smsService->sendVerifyCode($phone);
echo json_encode([
'code' => 200,
'msg' => '验证码已发送',
'debug_code' => $code // 开发环境返回,生产环境删除
]);
}
/**
* 绑定手机号
*/
public function bindPhone() {
header('Content-Type: application/json');
// 检查用户登录状态
if (!isset($_SESSION['oauth_user'])) {
echo json_encode(['code' => 401, 'msg' => '未登录']);
return;
}
$phone = $_POST['phone'] ?? '';
$code = $_POST['code'] ?? '';
// 验证手机号格式
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
echo json_encode(['code' => 400, 'msg' => '手机号格式不正确']);
return;
}
// 验证验证码
if (!$this->smsService->verifyPhoneCode($phone, $code)) {
echo json_encode(['code' => 400, 'msg' => '验证码错误或已过期']);
return;
}
$oauthUser = $_SESSION['oauth_user'];
// 检查手机号是否已被绑定
if ($this->userModel->phoneExists($phone)) {
// 手机号已存在,询问是否绑定到已有账号
echo json_encode([
'code' => 409,
'msg' => '该手机号已绑定其他账号,是否需要合并?'
]);
return;
}
// 开始事务
$this->db->beginTransaction();
try {
// 创建新用户
$userId = $this->userModel->create([
'phone' => $phone,
'nickname' => $oauthUser['nickname'],
'avatar' => $oauthUser['avatar']
]);
// 创建OAuth关联
$this->oauthUserModel->create(
$userId,
$oauthUser['provider'],
$oauthUser['openid'],
$oauthUser['unionid'] ?? null,
$oauthUser['nickname'],
$oauthUser['avatar']
);
$this->db->commit();
// 更新会话
$_SESSION['user_id'] = $userId;
$_SESSION['phone'] = $phone;
unset($_SESSION['oauth_user']);
echo json_encode([
'code' => 200,
'msg' => '绑定成功',
'data' => ['user_id' => $userId]
]);
} catch (Exception $e) {
$this->db->rollBack();
echo json_encode(['code' => 500, 'msg' => '绑定失败,请重试']);
}
}
/**
* 处理账号合并
*/
public function mergeAccount() {
header('Content-Type: application/json');
$phone = $_POST['phone'] ?? '';
$code = $_POST['code'] ?? '';
$action = $_POST['action'] ?? ''; // bind: 绑定到已有账号, new: 创建新账号
// 验证验证码
if (!$this->smsService->verifyPhoneCode($phone, $code)) {
echo json_encode(['code' => 400, 'msg' => '验证码错误或已过期']);
return;
}
$oauthUser = $_SESSION['oauth_user'];
$targetUser = $this->userModel->findByPhone($phone);
if (!$targetUser && $action !== 'new') {
echo json_encode(['code' => 400, 'msg' => '用户不存在']);
return;
}
if ($action === 'new') {
// 创建新账号
$userId = $this->userModel->create([
'phone' => $phone,
'nickname' => $oauthUser['nickname']
]);
} else {
// 绑定到已有账号
$userId = $targetUser['id'];
}
// 检查是否已存在关联
$existing = $this->oauthUserModel->findByProvider(
$oauthUser['provider'],
$oauthUser['openid']
);
if ($existing) {
// 更新关联的用户ID
$stmt = $this->db->prepare(
"UPDATE oauth_users SET user_id = ? WHERE id = ?"
);
$stmt->execute([$userId, $existing['id']]);
} else {
// 创建新的关联
$this->oauthUserModel->create(
$userId,
$oauthUser['provider'],
$oauthUser['openid'],
$oauthUser['unionid'] ?? null
);
}
// 更新会话
$_SESSION['user_id'] = $userId;
$_SESSION['phone'] = $phone;
unset($_SESSION['oauth_user']);
echo json_encode([
'code' => 200,
'msg' => '操作成功',
'data' => ['user_id' => $userId]
]);
}
}
处理微信登录回调(CallbackController.php)
<?php
class CallbackController {
private $db;
private $wechatAuth;
private $oauthUserModel;
private $userModel;
public function __construct($db, $config) {
$this->db = $db;
$this->wechatAuth = new WechatAuth(
$config['app_id'],
$config['app_secret'],
$config['redirect_uri']
);
$this->oauthUserModel = new OAuthUser($db);
$this->userModel = new User($db);
session_start();
}
/**
* 处理微信回调
*/
public function wechatCallback() {
$code = $_GET['code'] ?? '';
if (empty($code)) {
// 用户取消了授权
header('Location: /login.php?error=access_denied');
exit;
}
// 获取access_token
$tokenData = $this->wechatAuth->getAccessToken($code);
if (isset($tokenData['errcode'])) {
header('Location: /login.php?error=' . $tokenData['errcode']);
exit;
}
// 获取用户信息
$userInfo = $this->wechatAuth->getUserInfo(
$tokenData['access_token'],
$tokenData['openid']
);
// 查找是否已有关联
$oauthUser = $this->oauthUserModel->findByProvider('wechat', $userInfo['openid']);
if ($oauthUser) {
// 已有绑定,直接登录
$_SESSION['user_id'] = $oauthUser['user_id'];
$_SESSION['phone'] = $oauthUser['phone'];
header('Location: /index.php');
exit;
} else {
// 未绑定,保存OAuth信息到会话,跳转到绑定页面
$_SESSION['oauth_user'] = [
'provider' => 'wechat',
'openid' => $userInfo['openid'],
'unionid' => $tokenData['unionid'] ?? null,
'nickname' => $userInfo['nickname'] ?? '',
'avatar' => $userInfo['headimgurl'] ?? ''
];
// 跳转到绑定手机号页面
if ($this->isNewUser($userInfo['openid'])) {
header('Location: /bind_phone.php');
} else {
header('Location: /bind_phone.php?action=merge');
}
exit;
}
}
private function isNewUser($openid) {
// 处理新用户逻辑
return true;
}
}
前端绑定页面(bind_phone.php)
<!DOCTYPE html>
<html>
<head>绑定手机号</title>
<style>
.container { max-width: 400px; margin: 50px auto; padding: 20px; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; }
input { width: 100%; padding: 8px; }
.btn { padding: 8px 15px; background: #4CAF50; color: white; border: none; }
.error { color: red; }
.success { color: green; }
</style>
</head>
<body>
<div class="container">
<h2>绑定手机号</h2>
<div id="message"></div>
<form id="bindForm">
<div class="form-group">
<label>手机号</label>
<input type="text" id="phone" placeholder="请输入手机号" maxlength="11">
</div>
<div class="form-group">
<label>验证码</label>
<input type="text" id="code" placeholder="请输入验证码" maxlength="6">
</div>
<div class="form-group">
<button type="button" id="sendCode">发送验证码</button>
<button type="submit" class="btn">绑定手机</button>
</div>
</form>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
// 发送验证码
$('#sendCode').click(function() {
var phone = $('#phone').val();
// 手机号验证
if (!/^1[3-9]\d{9}$/.test(phone)) {
showMessage('请输入正确的手机号', 'error');
return;
}
$.post('/api/send_verify_code.php', {
phone: phone
}, function(response) {
response = JSON.parse(response);
showMessage(response.msg, response.code === 200 ? 'success' : 'error');
if (response.code === 200) {
// 开始倒计时
var countdown = 60;
var $btn = $('#sendCode');
$btn.prop('disabled', true);
var timer = setInterval(function() {
countdown--;
$btn.text(countdown + '秒后重发');
if (countdown === 0) {
clearInterval(timer);
$btn.text('发送验证码');
$btn.prop('disabled', false);
}
}, 1000);
}
});
});
// 提交绑定
$('#bindForm').submit(function(e) {
e.preventDefault();
var phone = $('#phone').val();
var code = $('#code').val();
if (!phone || !code) {
showMessage('请填写完整信息', 'error');
return;
}
$.post('/api/bind_phone.php', {
phone: phone,
code: code
}, function(response) {
response = JSON.parse(response);
if (response.code === 200) {
showMessage('绑定成功,正在跳转...', 'success');
setTimeout(function() {
window.location.href = '/index.php';
}, 2000);
} else if (response.code === 409) {
// 手机号已被绑定
if (confirm('该手机号已绑定其他账号,是否合并?')) {
$.post('/api/merge_account.php', {
phone: phone,
code: code,
action: 'bind'
}, function(mergeResponse) {
mergeResponse = JSON.parse(mergeResponse);
if (mergeResponse.code === 200) {
showMessage('合并成功,正在跳转...', 'success');
setTimeout(function() {
window.location.href = '/index.php';
}, 2000);
}
});
}
} else {
showMessage(response.msg, 'error');
}
});
});
// 显示消息
function showMessage(msg, type) {
$('#message').attr('class', type).text(msg);
}
});
</script>
</body>
</html>
路由配置
// router.php
<?php
$routes = [
'/api/send_verify_code.php' => 'sendVerifyCode',
'/api/bind_phone.php' => 'bindPhone',
'/api/merge_account.php' => 'mergeAccount',
'/callback/wechat.php' => 'wechatCallback'
];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if (isset($routes[$path])) {
// 初始化数据库连接
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 配置信息
$config = [
'app_id' => '你的AppID',
'app_secret' => '你的AppSecret',
'redirect_uri' => 'http://yourdomain.com/callback/wechat.php'
];
if ($path === '/callback/wechat.php') {
$controller = new CallbackController($db, $config);
} else {
$controller = new BindPhoneController($db);
}
$action = $routes[$path];
$controller->$action();
} else {
http_response_code(404);
echo 'Not Found';
}
安全建议
-
验证码安全:
- 限制发送频率
- 验证码有效期设置
- 防止暴力破解
-
用户隐私:
- 手机号加密存储
- 敏感信息脱敏显示
-
防刷机制:
- 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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播