📝 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使用文件缓存,避免频繁请求微信接口