微信手机号授权登录 API 文档

基于Yii框架的微信小程序手机号一键登录接口

📱 微信手机号授权登录接口

📋 接口基本信息

POST https://hapi.caixiangxiang.com/wechat/loginByPhone

功能描述:微信小程序手机号一键授权登录接口,通过微信登录code和手机号授权code获取用户手机号,实现快速登录注册。

控制器类:WechatController
动作方法:actionLoginByPhone()
框架版本:Yii 1.x (PHP 5.6+兼容)

🔄 登录流程说明

1 前端获取登录code:调用wx.login()获取临时登录凭证code
2 前端获取手机号授权:用户点击授权按钮,通过getPhoneNumber获取加密数据
3 发送授权请求:前端将loginCode和phoneCode发送到服务端
4 服务端获取openid:使用loginCode调用微信接口获取openid和session_key
5 服务端获取手机号:使用access_token和phoneCode调用微信接口获取用户手机号
6 用户处理:根据手机号查找用户,不存在则创建新用户
7 返回登录凭证:生成token并返回用户信息

📤 请求参数

参数名 类型 必填 默认值 说明
phoneCode String - 微信小程序获取手机号的授权码,通过getPhoneNumber获取
loginCode String - 微信登录code,通过wx.login()获取

📥 响应数据

成功响应
错误响应

HTTP状态码:200

{ "code": 200, "msg": "登录成功", "data": { "token": "c81e728d9d4c2f636f067f89cc14862c_1700000000", "user_id": 12345, "username": "用户1380", "phone": "13800138000", "country_code": "86", "is_new_user": true } }

响应字段说明

字段 类型 说明
code Integer 状态码,200表示成功
msg String 操作结果消息
data Object 登录返回数据
data.token String 登录凭证token,后续请求需要在header中携带
data.user_id Integer 用户唯一标识ID
data.username String 用户名,新用户默认为"用户+手机号后4位"
data.phone String 用户手机号
data.country_code String 国家码,如"86"
data.is_new_user Boolean 是否为新用户,true表示首次登录并创建用户
HTTP状态码 错误消息 可能原因
400 缺少必要参数 请求参数phoneCode或loginCode为空
500 微信登录失败: [errmsg] 微信登录code无效或已过期
500 获取用户标识失败 微信接口返回openid为空
500 获取手机号失败: [errmsg] 手机号授权code无效或已过期
500 获取手机号信息失败 微信接口返回的手机号信息为空
500 用户处理失败 用户创建或更新失败,数据库错误
500 登录失败: [异常信息] 服务器内部错误,代码执行异常

⚠️ 重要注意事项

1. 双code机制:本接口需要同时提供loginCode和phoneCode,分别用于获取openid和手机号

2. code有效期:两个code都只能使用一次,且有效时间很短(约5分钟)

3. 手机号唯一性:系统会根据手机号查找用户,确保一个手机号只对应一个用户

4. 新用户处理:首次登录的用户会自动创建账户,用户名为"用户+手机号后4位"

5. 第三方认证:系统会记录微信openid与用户的关系,支持多端登录

🔍 使用示例

微信小程序端调用示例

// 微信小程序手机号授权登录 Page({ data: { loginCode: '', phoneCode: '' }, // 页面加载时获取登录code onLoad() { this.getLoginCode(); }, // 获取登录code getLoginCode() { wx.login({ success: (res) => { if (res.code) { this.setData({ loginCode: res.code }); } else { console.error('获取登录code失败', res.errMsg); } } }); }, // 获取手机号授权 getPhoneNumber(e) { if (e.detail.errMsg === "getPhoneNumber:ok") { // 获取到手机号授权code const phoneCode = e.detail.code; // 调用手机号登录接口 wx.request({ url: 'https://hapi.caixiangxiang.com/wechat/loginByPhone', method: 'POST', data: { phoneCode: phoneCode, loginCode: this.data.loginCode }, success: (res) => { if (res.data.code === 200) { const userData = res.data.data; console.log('手机号登录成功', userData); // 存储token和用户信息到本地 wx.setStorageSync('token', userData.token); wx.setStorageSync('userInfo', userData); // 提示用户登录成功 wx.showToast({ title: userData.is_new_user ? '注册成功' : '登录成功', icon: 'success' }); // 跳转到首页 setTimeout(() => { wx.switchTab({ url: '/pages/index/index' }); }, 1500); } else { console.error('手机号登录失败', res.data.msg); wx.showToast({ title: res.data.msg, icon: 'none' }); } }, fail: (err) => { console.error('请求失败', err); wx.showToast({ title: '网络请求失败', icon: 'none' }); } }); } else { console.log('用户拒绝授权手机号'); wx.showToast({ title: '需要手机号授权才能登录', icon: 'none' }); } }, // 重新获取登录code refreshLoginCode() { this.getLoginCode(); } });

JavaScript Fetch 示例

// 手机号授权登录函数 async function phoneLoginByWechat(phoneCode, loginCode) { try { const response = await fetch('https://hapi.caixiangxiang.com/wechat/loginByPhone', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ phoneCode: phoneCode, loginCode: loginCode }) }); const result = await response.json(); if (result.code === 200) { // 登录成功,保存token和用户信息 localStorage.setItem('token', result.data.token); localStorage.setItem('userInfo', JSON.stringify(result.data)); // 返回用户数据 return { success: true, data: result.data, message: result.msg }; } else { return { success: false, error: result.msg, code: result.code }; } } catch (error) { console.error('手机号登录失败:', error); return { success: false, error: '网络请求失败,请检查网络连接', code: 0 }; } } // 使用示例 // 假设已获取到phoneCode和loginCode const phoneCode = '081abc000xyz123'; const loginCode = '091xyz000abc456'; phoneLoginByWechat(phoneCode, loginCode).then(result => { if (result.success) { console.log('登录成功,用户信息:', result.data); // 跳转到首页或执行其他操作 } else { console.error('登录失败:', result.error); // 显示错误提示 } });

cURL 示例

curl -X POST "https://hapi.caixiangxiang.com/wechat/loginByPhone" \ -H "Content-Type: application/json" \ -d '{ "phoneCode": "081abc000xyz123", "loginCode": "091xyz000abc456" }'

🔧 服务端配置

微信小程序配置

AppID: wx1da9a0c5e8d0d249

AppSecret: b50f0e094fded17a483cf96def8445dc

权限要求: 需要在小程序后台开启"获取手机号"权限

数据库表结构

-- 用户表 TUser CREATE TABLE IF NOT EXISTS TUser ( user_id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL, phone VARCHAR(20), real_name VARCHAR(50), avatar VARCHAR(255), gender TINYINT DEFAULT 0, level INT DEFAULT 1, points INT DEFAULT 0, status TINYINT DEFAULT 1, register_time DATETIME, last_login_time DATETIME, last_login_ip VARCHAR(50), is_del TINYINT DEFAULT 0 ); -- 第三方认证表 TUserOauth CREATE TABLE IF NOT EXISTS TUserOauth ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, oauth_type VARCHAR(20) NOT NULL, openid VARCHAR(100) NOT NULL, unionid VARCHAR(100), access_token VARCHAR(255), create_time DATETIME, update_time DATETIME, UNIQUE KEY user_oauth (user_id, oauth_type) );

💻 控制器完整代码

📝 WechatController.php

<?php /** * 微信小程序手机号授权登录控制器 * 兼容PHP 5.6+ 版本,修复语法错误 */ class WechatController extends Controller { private $appid = 'wx1da9a0c5e8d0d249'; private $appSecret = 'b50f0e094fded17a483cf96def8445dc'; /** * 手机号授权登录接口 */ public function actionLoginByPhone() { header('Content-Type: application/json; charset=utf-8'); try { // 兼容性获取参数方式 $rawData = file_get_contents('php://input'); $params = json_decode($rawData, true); $phoneCode = isset($params['phoneCode']) ? $params['phoneCode'] : ''; $loginCode = isset($params['loginCode']) ? $params['loginCode'] : ''; if (empty($phoneCode) || empty($loginCode)) { echo json_encode(array( 'code' => 400, 'msg' => '缺少必要参数', 'data' => array() )); return; } // 1. 获取session_key和openid $wechatInfo = $this->getWechatSession($loginCode); if (!$wechatInfo || (isset($wechatInfo['errcode']) && $wechatInfo['errcode'] != 0)) { error_log("获取微信session失败: " . json_encode($wechatInfo)); // 兼容PHP 5.6的错误信息处理 $errorMsg = '未知错误'; if (isset($wechatInfo['errmsg'])) { $errorMsg = $wechatInfo['errmsg']; } elseif (is_array($wechatInfo) && isset($wechatInfo['errmsg'])) { $errorMsg = $wechatInfo['errmsg']; } echo json_encode(array( 'code' => 500, 'msg' => '微信登录失败: ' . $errorMsg, 'data' => array() )); return; } $openid = isset($wechatInfo['openid']) ? $wechatInfo['openid'] : ''; $sessionKey = isset($wechatInfo['session_key']) ? $wechatInfo['session_key'] : ''; if (empty($openid)) { echo json_encode(array( 'code' => 500, 'msg' => '获取用户标识失败', 'data' => array() )); return; } // 2. 获取用户手机号 $phoneInfo = $this->getUserPhoneNumber($phoneCode); if (!$phoneInfo || (isset($phoneInfo['errcode']) && $phoneInfo['errcode'] != 0)) { $errorMsg = isset($phoneInfo['errmsg']) ? $phoneInfo['errmsg'] : '未知错误'; echo json_encode(array( 'code' => 500, 'msg' => '获取手机号失败: ' . $errorMsg, 'data' => array() )); return; } // 安全获取手机号信息 $purePhoneNumber = ''; $countryCode = ''; if (isset($phoneInfo['phone_info'])) { $phoneInfoData = $phoneInfo['phone_info']; $purePhoneNumber = isset($phoneInfoData['purePhoneNumber']) ? $phoneInfoData['purePhoneNumber'] : ''; $countryCode = isset($phoneInfoData['countryCode']) ? $phoneInfoData['countryCode'] : ''; } if (empty($purePhoneNumber)) { echo json_encode(array( 'code' => 500, 'msg' => '获取手机号信息失败', 'data' => array() )); return; } // 3. 查找或创建用户 $user = $this->findOrCreateUser($openid, $purePhoneNumber, $sessionKey); if (!$user) { echo json_encode(array( 'code' => 500, 'msg' => '用户处理失败', 'data' => array() )); return; } // 4. 使用Yii认证系统生成token Yii::app()->user->id = $user['user_id']; $token = Yii::app()->user->getAuth(); // 5. 记录登录日志 $this->recordLoginLog($user['user_id']); // 6. 返回成功结果 echo json_encode(array( 'code' => 200, 'msg' => '登录成功', 'data' => array( 'token' => $token, 'user_id' => $user['user_id'], 'username' => isset($user['username']) ? $user['username'] : '', 'phone' => $purePhoneNumber, 'country_code' => $countryCode, 'is_new_user' => isset($user['is_new']) ? $user['is_new'] : false ) )); } catch (Exception $e) { error_log("手机号登录异常: " . $e->getMessage()); echo json_encode(array( 'code' => 500, 'msg' => '登录失败: ' . $e->getMessage(), 'data' => array() )); } } /** * 获取微信session信息 */ private function getWechatSession($code) { $url = "https://api.weixin.qq.com/sns/jscode2session?appid={$this->appid}&secret={$this->appSecret}&js_code={$code}&grant_type=authorization_code"; $result = $this->curlGet($url); if (!$result) { return false; } $data = json_decode($result, true); return $data; } /** * 获取用户手机号 */ private function getUserPhoneNumber($code) { $accessToken = $this->getAccessToken(); if (!$accessToken) { return false; } $url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={$accessToken}"; $postData = json_encode(array('code' => $code)); $result = $this->curlPost($url, $postData); if (!$result) { return false; } $data = json_decode($result, true); return $data; } /** * 获取access_token */ private function getAccessToken() { // 简易缓存机制 $cacheFile = dirname(__FILE__) . '/access_token.cache'; if (file_exists($cacheFile)) { $cacheContent = file_get_contents($cacheFile); $cacheData = json_decode($cacheContent, true); if ($cacheData && time() - $cacheData['timestamp'] < 7000) { return $cacheData['access_token']; } } $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appid}&secret={$this->appSecret}"; $result = $this->curlGet($url); if (!$result) { return false; } $tokenInfo = json_decode($result, true); if (isset($tokenInfo['access_token'])) { $cacheData = array( 'access_token' => $tokenInfo['access_token'], 'timestamp' => time() ); file_put_contents($cacheFile, json_encode($cacheData)); return $tokenInfo['access_token']; } return false; } /** * 查找或创建用户 */ private function findOrCreateUser($openid, $phoneNumber, $sessionKey) { $transaction = Yii::app()->db->beginTransaction(); try { // 先根据手机号查找用户 $user = TUser::model()->find('phone = :phone AND is_del = 0', array( ':phone' => $phoneNumber )); $isNewUser = false; if (!$user) { // 创建新用户 $user = new TUser(); $user->username = '用户' . substr($phoneNumber, -4); $user->phone = $phoneNumber; $user->real_name = ''; $user->avatar = ''; $user->gender = 0; $user->level = 1; $user->points = 0; $user->status = 1; $user->register_time = date('Y-m-d H:i:s'); $user->last_login_time = date('Y-m-d H:i:s'); $user->last_login_ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; $user->is_del = 0; if ($user->save()) { $isNewUser = true; } else { throw new Exception('创建用户失败: ' . json_encode($user->getErrors())); } } else { // 更新最后登录信息 $user->last_login_time = date('Y-m-d H:i:s'); $user->last_login_ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; if (!$user->save()) { throw new Exception('更新用户信息失败'); } } // 更新或创建第三方认证 if (!$this->updateOrCreateOauth($user->user_id, $openid, null, $sessionKey)) { throw new Exception('更新第三方认证失败'); } $transaction->commit(); return array_merge($user->attributes, array('is_new' => $isNewUser)); } catch (Exception $e) { $transaction->rollback(); error_log("用户处理异常: " . $e->getMessage()); return false; } } /** * 更新或创建第三方认证 */ private function updateOrCreateOauth($userId, $openid, $unionid, $sessionKey) { // 查找是否已存在认证记录 $oauth = TUserOauth::model()->find('user_id = :user_id AND oauth_type = "wechat"', array( ':user_id' => $userId )); if (!$oauth) { $oauth = new TUserOauth(); $oauth->user_id = $userId; $oauth->oauth_type = 'wechat'; $oauth->openid = $openid; $oauth->unionid = $unionid; $oauth->create_time = date('Y-m-d H:i:s'); } $oauth->access_token = $sessionKey; $oauth->update_time = date('Y-m-d H:i:s'); return $oauth->save(); } /** * 记录登录日志 */ private function recordLoginLog($userId) { // 这里可以记录登录日志到数据库 Yii::log("用户ID: {$userId} 手机号登录成功", CLogger::LEVEL_INFO, 'wechat.login'); } /** * CURL GET请求 */ private function curlGet($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_TIMEOUT, 10); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); $result = curl_exec($ch); if (curl_errno($ch)) { error_log("CURL GET错误: " . curl_error($ch)); curl_close($ch); return false; } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode != 200) { error_log("CURL GET HTTP错误码: " . $httpCode); curl_close($ch); return false; } curl_close($ch); return $result; } /** * CURL POST请求 */ private function curlPost($url, $data) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json', 'Content-Length: ' . strlen($data) )); $result = curl_exec($ch); if (curl_errno($ch)) { error_log("CURL POST错误: " . curl_error($ch)); curl_close($ch); return false; } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode != 200) { error_log("CURL POST HTTP错误码: " . $httpCode); curl_close($ch); return false; } curl_close($ch); return $result; } }

代码说明

1. PHP版本兼容性:代码兼容PHP 5.6+,使用了isset()进行空值判断

2. 异常处理:使用try-catch捕获异常,确保接口稳定性

3. 事务处理:用户创建和第三方认证更新使用数据库事务,确保数据一致性

4. 错误日志:关键步骤都有错误日志记录,便于问题排查

5. 缓存机制:access_token使用文件缓存,避免频繁请求微信接口

🏗️ 类结构与依赖

📊 类关系图

WechatController - 微信登录控制器类
属性:
• $appid = 'wx1da9a0c5e8d0d249'
• $appSecret = 'b50f0e094fded17a483cf96def8445dc'
公共方法:
• actionLoginByPhone() - 手机号授权登录入口
私有方法:
• getWechatSession($code) - 获取微信session
• getUserPhoneNumber($code) - 获取用户手机号
• getAccessToken() - 获取微信access_token
• findOrCreateUser($openid, $phone, $sessionKey) - 查找或创建用户
• updateOrCreateOauth($userId, $openid, $unionid, $sessionKey) - 更新第三方认证
• recordLoginLog($userId) - 记录登录日志
• curlGet($url) - CURL GET请求
• curlPost($url, $data) - CURL POST请求
依赖的数据模型
TUser (用户表模型)
• user_id - 用户ID
• username - 用户名
• phone - 手机号
• real_name - 真实姓名
• avatar - 头像
• gender - 性别
• level - 等级
• points - 积分
• status - 状态
• register_time - 注册时间
• last_login_time - 最后登录时间
• last_login_ip - 最后登录IP
• is_del - 删除标志
TUserOauth (第三方认证模型)
• id - 主键ID
• user_id - 用户ID
• oauth_type - 认证类型(wechat)
• openid - 微信openid
• unionid - 微信unionid
• access_token - 访问令牌(session_key)
• create_time - 创建时间
• update_time - 更新时间

🔄 方法调用流程

1 actionLoginByPhone() 被调用
↓ 验证参数
2 getWechatSession() 获取openid和session_key
↓ 调用微信jscode2session接口
3 getAccessToken() 获取access_token
↓ 检查缓存或调用微信token接口
4 getUserPhoneNumber() 获取用户手机号
↓ 调用微信getuserphonenumber接口
5 findOrCreateUser() 查找或创建用户
↓ 开启数据库事务
• 根据手机号查找用户
• 不存在则创建新用户
• 更新登录信息
6 updateOrCreateOauth() 更新第三方认证
↓ 保存openid和session_key
7 生成token并返回结果
↓ 提交事务,返回用户数据

🔐 安全与权限设计

1. 数据安全

  • 敏感信息保护:AppSecret存储在服务端,绝不暴露给客户端
  • 参数验证:所有输入参数都进行有效性验证
  • 错误信息处理:详细的错误分类和友好的错误提示
  • 事务处理:关键操作使用数据库事务,确保数据一致性

2. 微信接口调用

// 微信接口调用频率限制 $cacheFile = dirname(__FILE__) . '/access_token.cache'; if (file_exists($cacheFile)) { $cacheContent = file_get_contents($cacheFile); $cacheData = json_decode($cacheContent, true); // access_token有效期为7200秒,这里设置7000秒后刷新 if ($cacheData && time() - $cacheData['timestamp'] < 7000) { return $cacheData['access_token']; } }

3. 错误处理策略

try { // 业务逻辑代码 $transaction = Yii::app()->db->beginTransaction(); // ... 数据库操作 $transaction->commit(); } catch (Exception $e) { $transaction->rollback(); error_log("用户处理异常: " . $e->getMessage()); return false; }

🚀 部署与使用指南

1. 环境要求

组件 版本要求 说明
PHP 5.6+ 支持JSON扩展和cURL扩展
Yii Framework 1.x 使用Yii的MVC架构和数据库操作
MySQL 5.5+ 支持事务和UTF-8编码
微信小程序 最新稳定版 支持getPhoneNumber API

2. 部署步骤

1 配置微信小程序
• 在小程序后台获取AppID和AppSecret
• 开启"获取手机号"权限
• 配置服务器域名(hapi.caixiangxiang.com)
2 部署服务端代码
• 将WechatController.php放入controllers目录
• 配置数据库连接
• 创建TUser和TUserOauth数据表
3 配置Web服务器
• 确保服务器支持HTTPS
• 配置URL路由规则
• 设置适当的文件权限
4 测试接口
• 使用微信开发者工具测试
• 验证登录流程
• 检查数据库记录

3. 常见问题排查

问题1:获取手机号失败

可能原因:

• phoneCode无效或已过期

• 小程序未开启"获取手机号"权限

• access_token获取失败

解决方案:

检查微信小程序后台配置,确保已开启手机号获取权限,重新获取phoneCode。

问题2:用户创建失败

可能原因:

• 数据库连接失败

• 数据表字段不匹配

• 手机号已存在但状态异常

解决方案:

检查数据库连接和表结构,查看错误日志中的具体错误信息。