*/ class Wechat { public $appid = 'wx81f7b48cef8b4cfb'; public $appsecret = 'e5d87a1aed0a553838286012a5685800'; private $access_token = '54_XqfLmdbsNdBLQlCbxMNto2vBcDnQjUf9Uxt_U6CHXiPgtdlwP2ju3KP5DqK0UT6wsxKAHhw0bGcI5og1K25RFGH9v0AT7hnN-WFcJa0vaQJgcXzqqE75zbWHn5qG6HdNq-rFbGn60AK9peTwLYReAGAVIB'; private $expires_time = 72000; private $_error = ''; /** * WeChat 构造函数 * @param boolean $refreshToken 为真,则刷新token */ public function __construct($refreshToken = false) { $config = $this->getConfig(); if (empty($config)) { exit($this->_error); } //var_dump($config);die; if (empty($config['appid']) || empty($config['appsecret'])) { exit('微信配置为空'); } $this->appid = $config['appid']; $this->appsecret = $config['appsecret']; // 检查access_token是否有效 if ($refreshToken || empty($config['access_token']) || $config['access_token_expire_time'] < strtotime('+5 minute')) { if (!$this->refreshAccessToken()) { exit($this->_error); } } else { $this->access_token = $config['access_token']; $this->expires_time = $config['access_token_expire_time']; } } /** * 获取微信配置 * @return array|bool * @author fuyelk */ private function getConfig() { // 检查有没有wechat表 try { $config = Db::name('wechat')->column('value', 'name'); } catch (PDOException $e) { // 表不存在 if ('1146' == $e->getData()['PDO Error Info']['Driver Error Code']) { // 创建表 $prefix = Config::get('database.prefix'); $sqlCreateTable = "CREATE TABLE `{$prefix}wechat` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',`name` varchar(100) CHARACTER SET utf8mb4 NOT NULL COMMENT '数据名',`value` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '值',`description` text CHARACTER SET utf8mb4 COMMENT '描述',`updatetime` int(10) DEFAULT NULL COMMENT '更新时间',PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='微信配置表';"; $sqlInsertData = "insert into `{$prefix}wechat` (`id`, `name`, `value`, `description`, `updatetime`) values ('1','appid','','APPID',NULL),('2','appsecret','','APP SECRET',NULL),('3','access_token','','SCCESS TOKEN',NULL),('4','access_token_expire_time','','SCCESS TOKEN过期时间',NULL),('5','qrcode','','二维码',NULL);"; try { Db::query($sqlCreateTable); Db::query($sqlInsertData); return Db::name('wechat')->column('value', 'name'); } catch (Exception $e) { $this->_error = '微信配置表不存在,创建失败'; return false; } } $this->_error = $e->getMessage(); return false; } catch (DbException $e) { $this->_error = $e->getMessage(); return false; } return $config; } /** * 向微信服务器请求获取accessToken,并存储 * return bool */ private function refreshAccessToken() { // 向微信服务器发access_token请求 $data = $this->wechatRequest('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $this->appid . '&secret=' . $this->appsecret); if (empty($data['access_token'])) { $this->_error = 'access_token获取失败'; return false; } $this->access_token = $data['access_token']; $this->expires_time = time() + $data['expires_in']; try { Db::name('wechat')->where('name', 'access_token')->update(['value' => $this->access_token, 'updatetime' => time()]); Db::name('wechat')->where('name', 'access_token_expire_time')->update(['value' => $this->expires_time, 'updatetime' => time()]); } catch (Exception $e) { $this->_error = $e->getMessage(); return false; } return true; } /** * http请求 * @param string $url http地址 * @param string $method 请求方式 * @param array $data 请求数据: *
     *  $data = [
     *      'image' => new \CURLFile($filePath),
     *      'access_token' => 'this-is-access-token'
     *       ...
     *  ]
     * 
* @return bool|string * @throws \Exception * @author fuyelk */ public function http_request($url, $method = 'GET', $data = []) { $addHeader = []; $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_ACCEPT_ENCODING => 'gzip,deflate', CURLOPT_URL => $url, CURLOPT_CUSTOMREQUEST => strtoupper($method), // 请求方式 CURLOPT_USERAGENT => "Mozilla / 5.0 (Windows NT 10.0; Win64; x64)",// 模拟常用浏览器的useragent CURLOPT_RETURNTRANSFER => true, // 获取的信息以文件流的形式返回,而不是直接输出 CURLOPT_SSL_VERIFYPEER => false, // https请求不验证证书 CURLOPT_SSL_VERIFYHOST => false, // https请求不验证hosts CURLOPT_MAXREDIRS => 10, // 最深允许重定向级数 CURLOPT_CONNECTTIMEOUT => 10,// 最长等待连接成功时间 CURLOPT_TIMEOUT => 30, // 最长等待响应完成时间 ]); // 发送请求数据 if ($data) { $data = json_encode($data, JSON_UNESCAPED_UNICODE); array_push($addHeader, 'Content-type:application/json'); curl_setopt($curl, CURLOPT_POSTFIELDS, $data); } curl_setopt($curl, CURLOPT_HTTPHEADER, $addHeader); $response = curl_exec($curl); $err = curl_error($curl); curl_close($curl); if ($err) throw new \Exception($err); return $response; } /** * 获取错误信息 * @return string * @author doyle * @date 2021/4/11 15:49 */ public function getError() { return $this->_error; } /** * 请求微信接口,并检查请求结果 * @param string $url 接口地址 * @param null $data [请求数据] * @param bool $callback [是否是回调] * @return bool|mixed|string * @author fuyelk * @date 2021/07/18 22:49 */ public function wechatRequest($url, $data = null, $callback = false) { $url = $url . (strpos($url, '?') ? '&' : '?') . 'access_token=' . $this->access_token; try { if (!empty($data)) { $res = $this->http_request($url, 'POST', $data); } else { $res = $this->http_request($url); } } catch (\Exception $e) { $this->_error = $e->getMessage(); return false; } $res = json_decode($res, true); // 接口出错 if (!empty($res['errcode'])) { $log = [ 'url' => $url, 'data' => $data, 'response' => $res ]; // 非回调,则检查是否AccessToken错误 if (!$callback && '40001' == $res['errcode'] && !empty($res['errmsg']) && strpos($res['errmsg'], 'access_token is invalid')) { // 刷新AccessToken if (!$this->refreshAccessToken()) { return false; } dta($log, '微信接口请求出错,AccessToken失效所致,正在尝试修复'); // 重新请求微信接口 return $this->wechatRequest($url, $data, true); } dta($log, '微信接口请求出错'); if (!empty($res['errmsg'])) { $this->_error = $res['errmsg']; } return false; } return $res; } /** * 在公网接口处重写此方法:验证服务器有效性 * @return int|mixed */ public function serverValidation() { $TOKEN = 'milingerfuyelkdoywb'; $signature = $_GET["signature"] ?? ""; $timestamp = $_GET["timestamp"] ?? ""; $nonce = $_GET["nonce"] ?? ""; $tmpArr = array($TOKEN, $timestamp, $nonce); sort($tmpArr, SORT_STRING); $tmpStr = implode($tmpArr); $tmpStr = sha1($tmpStr); if ($tmpStr == $signature) { return $_GET["echostr"] ?? ''; } return 'error'; } /** * 在公网接口处重写此方法:引导用户进入授权页,并跳转到目标页 * @param string $url 授权后需要跳转的页面地址,必须进行base64编码 * @author fuyelk */ public function bootToUrl($url = '') { $redirect_uri = request()->domain() . '/index/wechat/wechatRedirect?redirect=' . $url; $appid = $this->wechat->appid; $redirect_uri = urlencode($redirect_uri); $scope = 'snsapi_base'; // 静默授权 // $scope = 'snsapi_userinfo'; $wechat_authorize = "http://2.ipfsfil168.com/get-weixin-code.html?appid={$appid}&redirect_uri={$redirect_uri}&response_type=code&scope={$scope}&state=123#wechat_redirect"; $this->redirect($wechat_authorize); } /** * 在公网接口处重写此方法:用户授权后微信将用户重定向至此接口并携带code, * 使用code可获取access_token及用户openid,完成业务逻辑处理后拼接上openid将用户重定向指指定页面 */ public function wechatRedirect() { $code = input('code'); $redirect = input('redirect'); // 获取用户信息 if ($code) { $wechat_url = 'https://api.weixin.qq.com/sns/oauth2/access_token' . '?appid=' . $this->wechat->appid . '&secret=' . $this->wechat->appsecret . '&code=' . input('code') . '&grant_type=authorization_code'; try { $res = $this->wechat->http_request($wechat_url); } catch (\Exception $e) { $this->error($e->getMessage()); } $res = json_decode($res, true); if (empty($res['openid'])) { $this->error('登录失败'); } $redirect = base64_decode($redirect); $redirect = htmlspecialchars_decode($redirect); $query = parse_url(strstr($redirect, '?'), PHP_URL_QUERY); parse_str($query, $params); // 获取用户信息 if ('snsapi_userinfo' == $res['scope']) { $userinfo = file_get_contents("https://api.weixin.qq.com/sns/userinfo?access_token={$res['access_token']}&openid={$res['openid']}&lang=zh_CN"); $userinfo = json_decode($userinfo, true); if (!empty($userinfo['errcode']) && !empty($userinfo['errmsg'])) { exit($userinfo['errmsg']); } // [openid] => ou6NC6kBSloLS94gUhCyKeguIaYI // [nickname] => fuyelk // [sex] => 1 // 1=男,2=女,0=未知 // [language] => zh_CN // [city] => 郑州 // [province] => 河南 // [country] => 中国 // [headimgurl] => https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKY3E1A2MFK46y1fNv7OElhtQFefO4mrBy8kcQoFCHP9diaULlpeINYB1FJ7KNh4fx5v3rzAJ2LzFg/132 $invite_code = $params['sharecode'] ?? ''; // TODO 业务处理 // ... $token = 'abcdefg'; if (false === $token) { exit($this->_error ?: '登录失败'); } } else { exit('仅支持snsapi_userinfo'); } $redirect = $redirect . (strpos($redirect, '?') ? '&' : '?') . 'token=' . $token; $this->redirect($redirect ? urldecode($redirect) : '/'); } $this->error('授权失败'); } }