Wechat::getUserInfo()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 14
dl 0
loc 23
rs 9.7998
c 1
b 0
f 0
cc 4
nc 4
nop 0
1
<?php
2
/**
3
 * 网站应用微信登录开发 https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=
4
 * 1.PC登录:微信开放平台创建'网站应用'
5
 * 2.Mobile登录:微信公众号(服务号/企业号)
6
 * 3.APP登录:微信开放平台创建'移动应用'
7
 *
8
 * 注: scope值
9
 *      1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)
10
 *      2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。(H5页面微信授权获取用户,注册成为用户id,可以做点赞关注等功能)
11
 *      3、用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。
12
 * 如想打通unionid的话需要将公众号绑定到同一个微信开放平台
13
 */
14
namespace tinymeng\OAuth2\Gateways;
15
16
use tinymeng\OAuth2\Connector\Gateway;
17
use tinymeng\OAuth2\Exception\OAuthException;
18
use tinymeng\OAuth2\Helper\ConstCode;
19
20
/**
21
 * Class Wechat
22
 * @package tinymeng\OAuth2\Gateways
23
 * @Author: TinyMeng <[email protected]>
24
 * @Created: 2018/11/9
25
 */
26
class Wechat extends Gateway
27
{
28
    const API_BASE            = 'https://api.weixin.qq.com/sns/';
29
    protected $AuthorizeURL   = 'https://open.weixin.qq.com/connect/qrconnect';
30
    protected $AccessTokenURL = 'https://api.weixin.qq.com/sns/oauth2/access_token';
31
    protected $jsCode2Session = 'https://api.weixin.qq.com/sns/jscode2session';
32
33
    /**
34
     * Description:  得到跳转地址
35
     * @author: JiaMeng <[email protected]>
36
     * Updater:
37
     * @return string
38
     */
39
    public function getRedirectUrl()
40
    {
41
        //存储state
42
        $this->saveState();
43
44
        //获取代理链接
45
        if(isset($this->config['proxy_url'])){
46
            return $this->getProxyURL();
47
        }
48
49
        //登录参数
50
        $this->switchAccessTokenURL();
51
        $params = [
52
            'appid'         => $this->config['app_id'],
53
            'redirect_uri'  => $this->config['callback'],
54
            'response_type' => $this->config['response_type'],
55
            'scope'         => $this->config['scope'],
56
            'state'         => $this->config['state'],
57
        ];
58
        return $this->AuthorizeURL . '?' . http_build_query($params) . '#wechat_redirect';
59
    }
60
61
    /**
62
     * Description:  获取中转代理地址
63
     * @author: JiaMeng <[email protected]>
64
     * Updater:
65
     * @return string
66
     */
67
    public function getProxyURL()
68
    {
69
        $params = [
70
            'appid'         => $this->config['app_id'],
71
            'response_type' => $this->config['response_type'],
72
            'scope'         => $this->config['scope'],
73
            'state'         => $this->config['state'],
74
            'redirect_uri'    => $this->config['callback'],
75
        ];
76
        return $this->config['proxy_url'] . '?' . http_build_query($params);
77
    }
78
79
    /**
80
     * Description:  获取当前授权用户的openid标识
81
     * @author: JiaMeng <[email protected]>
82
     * Updater:
83
     * @return mixed
84
     * @throws OAuthException
85
     */
86
    public function openid()
87
    {
88
        $this->getToken();
89
90
        if (isset($this->token['openid'])) {
91
            return $this->token['openid'];
92
        } else {
93
            throw new OAuthException('没有获取到微信用户ID!');
94
        }
95
    }
96
97
    /**
98
     * Description:  获取格式化后的用户信息
99
     * @return array
100
     * @throws OAuthException
101
     * @author: JiaMeng <[email protected]>
102
     * Updater:
103
     */
104
    public function userInfo()
105
    {
106
        $result = $this->getUserInfo();
107
108
        $userInfo = [
109
            'open_id' => $this->openid(),
110
            'union_id'=> $this->token['unionid'] ?? '',
111
            'access_token'=> $this->token['access_token'] ?? '',
112
            'channel' => ConstCode::TYPE_WECHAT,
113
            'nickname'=> $result['nickname']??'',
114
            'gender'  => $result['sex'] ?? ConstCode::GENDER,
115
            'avatar'  => $result['headimgurl']??'',
116
            'type'    => ConstCode::getTypeConst(ConstCode::TYPE_WECHAT, $this->type),
117
            // 额外信息
118
            'session_key'  => $result['session_key']??'',
119
            'native'   => $result,
120
        ];
121
        return $userInfo;
122
    }
123
124
    /**
125
     * Description:  获取原始接口返回的用户信息
126
     * @return array
127
     * @throws OAuthException
128
     * @author: JiaMeng <[email protected]>
129
     * Updater:
130
     */
131
    public function getUserInfo()
132
    {
133
        if($this->type == 'app'){//App登录
134
            if(!isset($_REQUEST['access_token']) ){
135
                throw new OAuthException("Wechat APP登录 需要传输access_token参数! ");
136
            }
137
            $this->token['access_token'] = $_REQUEST['access_token'];
138
        }elseif ($this->type == 'applets'){
139
            //小程序
140
            return $this->applets();
141
        }else {
142
            /** 获取token信息 */
143
            $this->getToken();
144
        }
145
146
        /** 获取用户信息 */
147
        $params = [
148
            'access_token'=>$this->token['access_token'],
149
            'openid'=>$this->openid(),
150
            'lang'=>'zh_CN',
151
        ];
152
        $data = $this->get(self::API_BASE . 'userinfo', $params);
153
        return json_decode($data, true);
154
    }
155
156
    /**
157
     * @return array|mixed|null
158
     * @throws OAuthException
159
     */
160
    public function applets(){
161
        /** 获取参数 */
162
        $params = $this->accessTokenParams();
163
        $params['js_code'] = $params['code'];
164
165
        /** 获取access_token */
166
        $token =  $this->get($this->jsCode2Session, $params);
167
        /** 解析token值(子类实现此方法) */
168
        $this->token = $this->parseToken($token);
169
        return $this->token;
170
    }
171
172
    /**
173
     * Description:  根据第三方授权页面样式切换跳转地址
174
     * @author: JiaMeng <[email protected]>
175
     * Updater:
176
     */
177
    private function switchAccessTokenURL()
178
    {
179
        /**
180
         *  第三方使用网站应用授权登录前请注意已获取相应网页授权作用域
181
         *  Pc网站应用 https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
182
         *  微信内网站应用: https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URL&response_type=code&scope=SCOPE&state=1#wechat_redirect
183
         */
184
        if ($this->display == 'mobile') {
185
            $this->AuthorizeURL = 'https://open.weixin.qq.com/connect/oauth2/authorize';
186
        } else {
187
            //微信扫码网页登录,只支持此scope
188
            $this->config['scope'] = 'snsapi_login';
189
        }
190
    }
191
192
    /**
193
     * Description:  重写 获取的AccessToken请求参数
194
     * @author: JiaMeng <[email protected]>
195
     * Updater:
196
     * @return array
197
     */
198
    protected function accessTokenParams()
199
    {
200
        $params = [
201
            'appid'      => $this->config['app_id'],
202
            'secret'     => $this->config['app_secret'],
203
            'grant_type' => $this->config['grant_type'],
204
            'code'       => $this->getCode(),
205
        ];
206
        return $params;
207
    }
208
209
    /**
210
     * Description:  解析access_token方法请求后的返回值
211
     * @author: JiaMeng <[email protected]>
212
     * Updater:
213
     * @param string $token 获取access_token的方法的返回值
214
     * @return mixed
215
     * @throws OAuthException
216
     */
217
    protected function parseToken($token)
218
    {
219
        $data = json_decode($token, true);
220
        if (isset($data['access_token'])) {
221
            return $data;
222
        }elseif (isset($data['session_key'])){
223
            //小程序登录
224
            return $data;
225
        } else {
226
            throw new OAuthException("获取微信 ACCESS_TOKEN 出错:{$token}");
227
        }
228
    }
229
230
    /**
231
     * 解密小程序 wx.getUserInfo() 敏感数据.
232
     * @param string $encryptedData
233
     * @param string $iv
234
     * @param string $sessionKey
235
     * @return array
236
     */
237
    public function descryptData($encryptedData, $iv, $sessionKey)
238
    {
239
        if (24 != strlen($sessionKey))
240
        {
241
            throw new \InvalidArgumentException('sessionKey 格式错误');
242
        }
243
        if (24 != strlen($iv))
244
        {
245
            throw new \InvalidArgumentException('iv 格式错误');
246
        }
247
        $aesKey = base64_decode($sessionKey);
248
        $aesIV = base64_decode($iv);
249
        $aesCipher = base64_decode($encryptedData);
250
        $result = openssl_decrypt($aesCipher, 'AES-128-CBC', $aesKey, 1, $aesIV);
251
        if (!$result)
252
        {
253
            throw new \InvalidArgumentException('解密失败');
254
        }
255
        $dataObj = json_decode($result, true);
256
        if (!$dataObj)
257
        {
258
            throw new \InvalidArgumentException('反序列化数据失败');
259
        }
260
261
        return $dataObj;
262
    }
263
264
    /**
265
     * 刷新AccessToken续期
266
     * @param string $refreshToken
267
     * @return bool
268
     * @throws OAuthException
269
     */
270
    public function refreshToken($refreshToken)
271
    {
272
        $params = [
273
            'appid'         => $this->config['app_id'],
274
            'grant_type'    => 'refresh_token',
275
            'refresh_token' => $refreshToken,
276
        ];
277
        
278
        $token = $this->get('https://api.weixin.qq.com/sns/oauth2/refresh_token', $params);
279
        $token = $this->parseToken($token);
280
        
281
        if (isset($token['access_token'])) {
282
            $this->token = $token;
283
            return true;
284
        }
285
        return false;
286
    }
287
288
    /**
289
     * 检验授权凭证AccessToken是否有效
290
     * @param string $accessToken
291
     * @return bool
292
     */
293
    public function validateAccessToken($accessToken = null)
294
    {
295
        try {
296
            $accessToken = $accessToken ?? $this->token['access_token'];
297
            $params = [
298
                'access_token' => $accessToken,
299
                'openid'      => $this->openid(),
300
            ];
301
            $result = $this->get(self::API_BASE . 'auth', $params);
302
            $result = json_decode($result, true);
303
            return isset($result['errcode']) && $result['errcode'] == 0;
304
        } catch (\Exception $e) {
305
            return false;
306
        }
307
    }
308
}