123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- <?php
- namespace app\common\controller;
- use think\Config;
- use think\Db;
- use think\Exception;
- use think\exception\DbException;
- use think\exception\PDOException;
- /**
- * 微信公众平台基类
- * Class Wechat
- * @package app\common\controller
- * @version 1.0
- * @author fuyelk <fuyelk@fuyelk.com>
- */
- 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 <fuyelk@fuyelk.com>
- */
- 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 请求数据:
- * <pre>
- * $data = [
- * 'image' => new \CURLFile($filePath),
- * 'access_token' => 'this-is-access-token'
- * ...
- * ]
- * </pre>
- * @return bool|string
- * @throws \Exception
- * @author fuyelk <fuyelk@fuyelk.com>
- */
- 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 <doyle@doywb.com>
- * @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 <fuyelk@fuyelk.com>
- * @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 <fuyelk@fuyelk.com>
- */
- 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('授权失败');
- }
- }
|