appId = config('wechat.openplat.app_id',[]); $this->appSecret = config('wechat.openplat.secret',[]); $this->redirectUri = urlencode(config('wechat.openplat.release_host_url',[])); } /** * 第一步:生成授权URL,引导用户跳转到微信授权页面 * * @param string $scope 应用授权作用域 * snsapi_base - 静默授权,不弹出授权页面,只能获取openid * snsapi_userinfo - 弹出授权页面,可获取用户信息 * @param string $state 重定向后会带上state参数,开发者可以填写任意参数值 * @return string 授权URL */ public function getAuthorizeUrl($scope = 'snsapi_base', $state = 'STATE') { $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->appId}&redirect_uri={$this->redirectUri}&response_type=code&scope={$scope}&state={$state}#wechat_redirect"; return $url; } /** * 第二步:通过code获取access_token和openid * * @param string $code 授权code * @return array|false 成功返回数组,失败返回false */ public function getAccessTokenByCode($code) { $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->appId}&secret={$this->appSecret}&code={$code}&grant_type=authorization_code"; $result = $this->httpGet($url); if ($result) { $data = json_decode($result, true); if (!isset($data['errcode'])) { return $data; // 成功返回 } else { $this->logError("获取access_token失败", $data); return false; } } return false; } /** * 刷新access_token * * @param string $refreshToken 刷新token * @return array|false 成功返回数组,失败返回false */ public function refreshAccessToken($refreshToken) { $url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid={$this->appId}&grant_type=refresh_token&refresh_token={$refreshToken}"; $result = $this->httpGet($url); if ($result) { $data = json_decode($result, true); if (!isset($data['errcode'])) { return $data; // 成功返回 } else { $this->logError("刷新access_token失败", $data); return false; } } return false; } /** * 获取用户信息(需要scope为snsapi_userinfo) * * @param string $accessToken 接口调用凭证 * @param string $openid 用户唯一标识 * @return array|false 成功返回数组,失败返回false */ public function getUserInfo($accessToken, $openid) { $url = "https://api.weixin.qq.com/sns/userinfo?access_token={$accessToken}&openid={$openid}&lang=zh_CN"; $result = $this->httpGet($url); if ($result) { $data = json_decode($result, true); if (!isset($data['errcode'])) { return $data; // 成功返回 } else { $this->logError("获取用户信息失败", $data); return false; } } return false; } /** * 验证access_token是否有效 * * @param string $accessToken 接口调用凭证 * @param string $openid 用户唯一标识 * @return bool 是否有效 */ public function checkAccessToken($accessToken, $openid) { $url = "https://api.weixin.qq.com/sns/auth?access_token={$accessToken}&openid={$openid}"; $result = $this->httpGet($url); if ($result) { $data = json_decode($result, true); if (isset($data['errcode']) && $data['errcode'] == 0) { return true; // 有效 } } return false; // 无效 } /** * 完整的授权流程处理 * * @return array|false 成功返回用户信息数组,失败返回false */ public function handleAuthorization() { // 检查是否有授权code if (!isset($_GET['code'])) { // 没有code,跳转到授权页面 $url = $this->getAuthorizeUrl('snsapi_userinfo', 'authorize'); header("Location: {$url}"); exit; } // 获取授权code $code = $_GET['code']; // 通过code获取access_token $tokenData = $this->getAccessTokenByCode($code); if (!$tokenData) { return false; } // 存储token信息到session session_start(); $_SESSION['wechat_access_token'] = $tokenData['access_token']; $_SESSION['wechat_refresh_token'] = $tokenData['refresh_token']; $_SESSION['wechat_openid'] = $tokenData['openid']; $_SESSION['wechat_token_expire'] = time() + $tokenData['expires_in']; return $tokenData; } /** * 获取当前有效的access_token(自动刷新) * * @return string|false 有效的access_token */ public function getValidAccessToken() { session_start(); // 检查session中是否有token信息 if (!isset($_SESSION['wechat_access_token'])) { return false; } $accessToken = $_SESSION['wechat_access_token']; $refreshToken = $_SESSION['wechat_refresh_token']; $openid = $_SESSION['wechat_openid']; $expireTime = $_SESSION['wechat_token_expire']; // 检查token是否即将过期(提前5分钟刷新) if (time() > $expireTime - 300) { // 刷新token $newTokenData = $this->refreshAccessToken($refreshToken); if ($newTokenData) { // 更新session中的token信息 $_SESSION['wechat_access_token'] = $newTokenData['access_token']; $_SESSION['wechat_refresh_token'] = $newTokenData['refresh_token']; $_SESSION['wechat_token_expire'] = time() + $newTokenData['expires_in']; $accessToken = $newTokenData['access_token']; } else { // 刷新失败,需要重新授权 return false; } } return $accessToken; } /** * HTTP GET 请求 * * @param string $url 请求URL * @return string|false 响应内容 */ private function httpGet($url) { if (function_exists('curl_init')) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, 30); $response = curl_exec($ch); if (curl_errno($ch)) { $this->logError("CURL错误", curl_error($ch)); curl_close($ch); return false; } curl_close($ch); return $response; } else { // 备用方法:使用file_get_contents $context = stream_context_create([ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, ] ]); return file_get_contents($url, false, $context); } } /** * 错误日志记录 * * @param string $message 错误信息 * @param mixed $data 错误数据 */ private function logError($message, $data = null) { $log = date('Y-m-d H:i:s') . " - {$message}"; if ($data !== null) { $log .= " - " . (is_array($data) ? json_encode($data) : $data); } Log::info('wechat_oauth_error', '微信登录错误日志', ['data' => $log, 'error' => PHP_EOL]); } /** * 获取当前openid * * @return string|false openid */ public function getOpenId() { session_start(); return isset($_SESSION['wechat_openid']) ? $_SESSION['wechat_openid'] : false; } /** * 清除session中的授权信息 */ public function clearSession() { session_start(); unset( $_SESSION['wechat_access_token'], $_SESSION['wechat_refresh_token'], $_SESSION['wechat_openid'], $_SESSION['wechat_token_expire'] ); } }