| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- <?php
- namespace App\Servers\Wechat;
- use App\Facades\Servers\Logs\Log;
- /**
- * 微信网站应用
- * @author 唐远望
- * @version 1.0
- * @date 2026-01-19
- */
- class WeChatWebApp
- {
- private $appId;
- private $appSecret;
- private $redirectUri;
- /**
- * 构造函数
- *
- * @param string $appId 应用唯一标识
- * @param string $appSecret 应用密钥
- * @param string $redirectUri 授权回调地址
- */
- public function __construct()
- {
- $this->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']
- );
- }
- }
|