├── .gitignore
├── composer.json
├── ErrorCase.php
├── Recorder.php
├── README.md
├── URL.php
├── Oauth.php
└── QC.php
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | .idea
3 | /vendor/
4 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "loveteemo/qqconnect",
3 | "description": "thinkphp 5 & qqconnect",
4 | "authors": [
5 | {
6 | "name": "long",
7 | "email": "admin@loveteemo.com"
8 | }
9 | ],
10 | "require": {
11 | "php": ">=5.4.0"
12 | },
13 | "autoload": {
14 | "psr-4": {
15 | "loveteemo\\qqconnect\\": "/"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ErrorCase.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 | namespace loveteemo\qqconnect;
12 |
13 | class ErrorCase{
14 |
15 | //定义错误
16 | private $errorMsg;
17 |
18 | //错误代码
19 | public function __construct(){
20 | $this->errorMsg = array(
21 | "20001" => "
配置文件无法读取,独立配置文件请修改Recorder第22行 [配置文件名.qqconnect]
",
22 | "30001" => "域名不匹配,这个请求可能是CSRF攻击.
",
23 | "50001" => "可能是服务器无法请求https协议
可能未开启curl支持,请尝试开启curl支持,重启web服务器,如果问题仍未解决,请联系我们"
24 | );
25 | }
26 |
27 | //显示错误
28 | public function showError($code, $description = '$'){
29 | echo "";
30 | if(isset($this->errorMsg[$code])){
31 | echo $this->errorMsg[$code];
32 | }else{
33 | echo "错误代码:{$code}手册点我
";
34 | }
35 | die;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Recorder.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 | namespace loveteemo\qqconnect;
12 |
13 | class Recorder{
14 | private static $data;
15 | private $inc;
16 | private $error;
17 |
18 | public function __construct(){
19 | $this->error = new ErrorCase();
20 |
21 | $this->inc = config('auth.qqconnect');
22 | if(empty($this->inc)){
23 | $this->error->showError("20001");
24 | }
25 |
26 | if(empty(session('QC_userData'))){
27 | self::$data = array();
28 | }else{
29 | self::$data = session('QC_userData');
30 | }
31 | }
32 |
33 | public function write($name,$value){
34 | self::$data[$name] = $value;
35 | }
36 |
37 | public function read($name){
38 | if(empty(self::$data[$name])){
39 | return null;
40 | }else{
41 | return self::$data[$name];
42 | }
43 | }
44 |
45 | public function readInc($name){
46 | if(empty($this->inc[$name])){
47 | return null;
48 | }else{
49 | return $this->inc[$name];
50 | }
51 | }
52 |
53 | public function delete($name){
54 | unset(self::$data[$name]);
55 | }
56 |
57 | function __destruct(){
58 | session('QC_userData', self::$data);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ThinkPHP 5 & Oauth2.0 QQ登录
2 |
3 | 类库是基于Oauth2.0结合ThinkPHP 5 修改部分内容,仅适配 ThinkPhP 5
4 |
5 | 个人博客主页: http://www.loveteemo.com
6 |
7 | ## 安装方法
8 |
9 | composer安装:
10 |
11 | ``` bash
12 | composer require loveteemo/qqconnect
13 | ```
14 |
15 | 添加公共配置:
16 | ``` php
17 | // QQ 互联配置 在config.php中添加
18 | 'qqconnect' => [
19 | 'appid' => '',
20 | 'appkey' => '',
21 | 'callback' => '',
22 | 'scope' => 'get_user_info',
23 | 'errorReport' => true
24 | ]
25 | ```
26 |
27 | ## 示例
28 |
29 | ### 页面编写:
30 |
31 | ```
32 | QQ登录
33 | ```
34 |
35 | ### 控制器编写:
36 |
37 | 登录
38 | ``` php
39 | namespace app\index\controller;
40 | use loveteemo\qqconnect\QC;
41 | class Base
42 | {
43 | public function qqlogin()
44 | {
45 | $qc = new QC();
46 | return redirect($qc->qq_login());
47 | }
48 | }
49 | ```
50 |
51 | 回调
52 | ``` php
53 | namespace app\index\controller;
54 | use loveteemo\qqconnect\QC;
55 | class Base
56 | {
57 | public function callback()
58 | {
59 | $Qc = new QC();
60 | $access_token = $Qc->qq_callback();
61 | $openid = $Qc->get_openid();
62 | $Qc = new QC($access_token,$openid);
63 | $qq_user_info = $Qc->get_user_info();
64 | //打印数据
65 | //dump($qq_user_info);die;
66 | // ...
67 | // 用户逻辑
68 | return redirect(url('Index/index'));
69 | }
70 | }
71 | ```
72 |
73 | 获取到的QQ数据
74 | ``` html
75 |
76 | array(18) {
77 | ["ret"] => int(0)
78 | ["msg"] => string(0) ""
79 | ["is_lost"] => int(0)
80 | ["nickname"] => string(21) "那年,烟雨重楼"
81 | ["gender"] => string(3) "男"
82 | ["province"] => string(6) "广东"
83 | ["city"] => string(6) "深圳"
84 | ["year"] => string(4) "1993"
85 | ["figureurl"] => string(73) "http://qzapp.qlogo.cn/qzapp/101232670/7C8F797F30B08554A6E39A537F9A324B/30"
86 | ["figureurl_1"] => string(73) "http://qzapp.qlogo.cn/qzapp/101232670/7C8F797F30B08554A6E39A537F9A324B/50"
87 | ["figureurl_2"] => string(74) "http://qzapp.qlogo.cn/qzapp/101232670/7C8F797F30B08554A6E39A537F9A324B/100"
88 | ["figureurl_qq_1"] => string(69) "http://q.qlogo.cn/qqapp/101232670/7C8F797F30B08554A6E39A537F9A324B/40"
89 | ["figureurl_qq_2"] => string(70) "http://q.qlogo.cn/qqapp/101232670/7C8F797F30B08554A6E39A537F9A324B/100"
90 | ["is_yellow_vip"] => string(1) "0"
91 | ["vip"] => string(1) "0"
92 | ["yellow_vip_level"] => string(1) "0"
93 | ["level"] => string(1) "0"
94 | ["is_yellow_year_vip"] => string(1) "0"
95 | }
96 |
97 | ```
98 |
99 |
100 |
--------------------------------------------------------------------------------
/URL.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 | namespace loveteemo\qqconnect;
12 |
13 | class URL{
14 | private $error;
15 |
16 | public function __construct(){
17 | $this->error = new ErrorCase();
18 | }
19 |
20 | /**
21 | * combineURL
22 | * 拼接url
23 | * @param string $baseURL 基于的url
24 | * @param array $keysArr 参数列表数组
25 | * @return string 返回拼接的url
26 | */
27 | public function combineURL($baseURL,$keysArr){
28 | $combined = $baseURL."?";
29 | $valueArr = array();
30 |
31 | foreach($keysArr as $key => $val){
32 | $valueArr[] = "$key=$val";
33 | }
34 |
35 | $keyStr = implode("&",$valueArr);
36 | $combined .= ($keyStr);
37 |
38 | return $combined;
39 | }
40 |
41 | /**
42 | * get_contents
43 | * 打开Https需要开启 php.ini 的ssl extension=php_openssl.dll
44 | * 服务器通过get请求获得内容
45 | * @param string $url 请求的url,拼接后的
46 | * @return string 请求返回的内容
47 | */
48 | public function get_contents($url){
49 | if (ini_get("allow_url_fopen") == "1") {
50 | // 如果这里报错,请找到对应目录新建证书文件 http://curl.haxx.se/docs/caextract.html
51 | $response = file_get_contents($url);
52 | }else{
53 | $ch = curl_init();
54 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
55 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
56 | curl_setopt($ch, CURLOPT_URL, $url);
57 | $response = curl_exec($ch);
58 | curl_close($ch);
59 | }
60 |
61 | //-------请求为空
62 | if(empty($response)){
63 | $this->error->showError("50001");
64 | }
65 |
66 | return $response;
67 | }
68 |
69 | /**
70 | * get
71 | * get方式请求资源
72 | * @param string $url 基于的baseUrl
73 | * @param array $keysArr 参数列表数组
74 | * @return string 返回的资源内容
75 | */
76 | public function get($url, $keysArr){
77 | $combined = $this->combineURL($url, $keysArr);
78 | return $this->get_contents($combined);
79 | }
80 |
81 | /**
82 | * post
83 | * post方式请求资源
84 | * @param string $url 基于的baseUrl
85 | * @param array $keysArr 请求的参数列表
86 | * @param int $flag 标志位
87 | * @return string 返回的资源内容
88 | */
89 | public function post($url, $keysArr, $flag = 0){
90 |
91 | $ch = curl_init();
92 | if(! $flag) curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
93 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
94 | curl_setopt($ch, CURLOPT_POST, TRUE);
95 | curl_setopt($ch, CURLOPT_POSTFIELDS, $keysArr);
96 | curl_setopt($ch, CURLOPT_URL, $url);
97 | $ret = curl_exec($ch);
98 |
99 | curl_close($ch);
100 | return $ret;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Oauth.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 | namespace loveteemo\qqconnect;
12 |
13 | class Oauth{
14 |
15 | //QQ互联版本
16 | const VERSION = "2.0";
17 | //授权地址
18 | const GET_AUTH_CODE_URL = "https://graph.qq.com/oauth2.0/authorize";
19 | //token地址
20 | const GET_ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token";
21 | //openid地址
22 | const GET_OPENID_URL = "https://graph.qq.com/oauth2.0/me";
23 |
24 | protected $recorder;
25 |
26 | public $urlUtils;
27 |
28 | protected $error;
29 |
30 |
31 | function __construct(){
32 | $this->recorder = new Recorder();
33 | $this->urlUtils = new URL();
34 | $this->error = new ErrorCase();
35 | }
36 |
37 | //登陆
38 | public function qq_login($callbakc_url = ''){
39 | $appid = $this->recorder->readInc("appid");
40 | $callback = $callbakc_url ? : $this->recorder->readInc("callback");
41 | $scope = $this->recorder->readInc("scope");
42 |
43 | $state = md5(uniqid(rand(), TRUE));
44 | $this->recorder->write('state',$state);
45 |
46 | $keysArr = array(
47 | "response_type" => "code",
48 | "client_id" => $appid,
49 | "redirect_uri" => $callback,
50 | "state" => $state,
51 | "scope" => $scope
52 | );
53 |
54 | return $this->urlUtils->combineURL(self::GET_AUTH_CODE_URL, $keysArr);
55 | }
56 |
57 | //回调
58 | public function qq_callback(){
59 | $state = $this->recorder->read("state");
60 |
61 | if($_GET['state'] != $state){
62 | $this->error->showError("30001");
63 | }
64 |
65 | $keysArr = array(
66 | "grant_type" => "authorization_code",
67 | "client_id" => $this->recorder->readInc("appid"),
68 | "redirect_uri" => urlencode($this->recorder->readInc("callback")),
69 | "client_secret" => $this->recorder->readInc("appkey"),
70 | "code" => $_GET['code']
71 | );
72 |
73 | //------构造请求access_token的url
74 | $token_url = $this->urlUtils->combineURL(self::GET_ACCESS_TOKEN_URL, $keysArr);
75 | $response = $this->urlUtils->get_contents($token_url);
76 |
77 | if(strpos($response, "callback") !== false){
78 |
79 | $lpos = strpos($response, "(");
80 | $rpos = strrpos($response, ")");
81 | $response = substr($response, $lpos + 1, $rpos - $lpos -1);
82 | $msg = json_decode($response);
83 |
84 | if(isset($msg->error)){
85 | $this->error->showError($msg->error, $msg->error_description);
86 | }
87 | }
88 |
89 | $params = array();
90 | parse_str($response, $params);
91 |
92 | $this->recorder->write("access_token", $params["access_token"]);
93 | return $params["access_token"];
94 |
95 | }
96 |
97 | //获取OPENID
98 | public function get_openid(){
99 |
100 | //-------请求参数列表
101 | $keysArr = array(
102 | "access_token" => $this->recorder->read("access_token")
103 | );
104 |
105 | $graph_url = $this->urlUtils->combineURL(self::GET_OPENID_URL, $keysArr);
106 | $response = $this->urlUtils->get_contents($graph_url);
107 |
108 | //--------检测错误是否发生
109 | if(strpos($response, "callback") !== false){
110 |
111 | $lpos = strpos($response, "(");
112 | $rpos = strrpos($response, ")");
113 | $response = substr($response, $lpos + 1, $rpos - $lpos -1);
114 | }
115 |
116 | $user = json_decode($response);
117 | if(isset($user->error)){
118 | $this->error->showError($user->error, $user->error_description);
119 | }
120 |
121 | //------记录openid
122 | $this->recorder->write("openid", $user->openid);
123 | return $user->openid;
124 |
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/QC.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 | namespace loveteemo\qqconnect;
12 |
13 | class QC extends Oauth{
14 | private $kesArr;
15 |
16 | private $APIMap;
17 |
18 | public function __construct($access_token = "", $openid = ""){
19 | parent::__construct();
20 |
21 | //如果access_token和openid为空,则从session里去取
22 | if($access_token === "" || $openid === ""){
23 | $this->keysArr = array(
24 | "oauth_consumer_key" => (int)$this->recorder->readInc("appid"),
25 | "access_token" => $this->recorder->read("access_token"),
26 | "openid" => $this->recorder->read("openid")
27 | );
28 | }else{
29 | $this->keysArr = array(
30 | "oauth_consumer_key" => (int)$this->recorder->readInc("appid"),
31 | "access_token" => $access_token,
32 | "openid" => $openid
33 | );
34 | }
35 |
36 | //初始化APIMap add_share和add_one_blog接口已于2014.3.31正式下线
37 | $this->APIMap = array(
38 |
39 | "add_blog" => array(
40 | "https://graph.qq.com/blog/add_one_blog",
41 | array("title", "format" => "json", "content" => null),
42 | "POST"
43 | ),
44 | "add_topic" => array(
45 | "https://graph.qq.com/shuoshuo/add_topic",
46 | array("richtype","richval","con","#lbs_nm","#lbs_x","#lbs_y","format" => "json", "#third_source"),
47 | "POST"
48 | ),
49 | "get_user_info" => array(
50 | "https://graph.qq.com/user/get_user_info",
51 | array("format" => "json"),
52 | "GET"
53 | ),
54 | "add_album" => array(
55 | "https://graph.qq.com/photo/add_album",
56 | array("albumname", "#albumdesc", "#priv", "format" => "json"),
57 | "POST"
58 | ),
59 | "upload_pic" => array(
60 | "https://graph.qq.com/photo/upload_pic",
61 | array("picture", "#photodesc", "#title", "#albumid", "#mobile", "#x", "#y", "#needfeed", "#successnum", "#picnum", "format" => "json"),
62 | "POST"
63 | ),
64 | "list_album" => array(
65 | "https://graph.qq.com/photo/list_album",
66 | array("format" => "json")
67 | ),
68 | "check_page_fans" => array(
69 | "https://graph.qq.com/user/check_page_fans",
70 | array("page_id" => "314416946","format" => "json")
71 | ),
72 |
73 | "add_t" => array(
74 | "https://graph.qq.com/t/add_t",
75 | array("format" => "json", "content","#clientip","#longitude","#compatibleflag"),
76 | "POST"
77 | ),
78 | "add_pic_t" => array(
79 | "https://graph.qq.com/t/add_pic_t",
80 | array("content", "pic", "format" => "json", "#clientip", "#longitude", "#latitude", "#syncflag", "#compatiblefalg"),
81 | "POST"
82 | ),
83 | "del_t" => array(
84 | "https://graph.qq.com/t/del_t",
85 | array("id", "format" => "json"),
86 | "POST"
87 | ),
88 | "get_repost_list" => array(
89 | "https://graph.qq.com/t/get_repost_list",
90 | array("flag", "rootid", "pageflag", "pagetime", "reqnum", "twitterid", "format" => "json")
91 | ),
92 | "get_info" => array(
93 | "https://graph.qq.com/user/get_info",
94 | array("format" => "json")
95 | ),
96 | "get_other_info" => array(
97 | "https://graph.qq.com/user/get_other_info",
98 | array("format" => "json", "#name", "fopenid")
99 | ),
100 | "get_fanslist" => array(
101 | "https://graph.qq.com/relation/get_fanslist",
102 | array("format" => "json", "reqnum", "startindex", "#mode", "#install", "#sex")
103 | ),
104 | "get_idollist" => array(
105 | "https://graph.qq.com/relation/get_idollist",
106 | array("format" => "json", "reqnum", "startindex", "#mode", "#install")
107 | ),
108 | "add_idol" => array(
109 | "https://graph.qq.com/relation/add_idol",
110 | array("format" => "json", "#name-1", "#fopenids-1"),
111 | "POST"
112 | ),
113 | "del_idol" => array(
114 | "https://graph.qq.com/relation/del_idol",
115 | array("format" => "json", "#name-1", "#fopenid-1"),
116 | "POST"
117 | ),
118 |
119 | "get_tenpay_addr" => array(
120 | "https://graph.qq.com/cft_info/get_tenpay_addr",
121 | array("ver" => 1,"limit" => 5,"offset" => 0,"format" => "json")
122 | )
123 | );
124 | }
125 |
126 | //调用相应api
127 | private function _applyAPI($arr, $argsList, $baseUrl, $method){
128 | $pre = "#";
129 | $keysArr = $this->keysArr;
130 |
131 | $optionArgList = array();//一些多项选填参数必选一的情形
132 | foreach($argsList as $key => $val){
133 | $tmpKey = $key;
134 | $tmpVal = $val;
135 |
136 | if(!is_string($key)){
137 | $tmpKey = $val;
138 |
139 | if(strpos($val,$pre) === 0){
140 | $tmpVal = $pre;
141 | $tmpKey = substr($tmpKey,1);
142 | if(preg_match("/-(\d$)/", $tmpKey, $res)){
143 | $tmpKey = str_replace($res[0], "", $tmpKey);
144 | $optionArgList[$res[1]][] = $tmpKey;
145 | }
146 | }else{
147 | $tmpVal = null;
148 | }
149 | }
150 |
151 | //-----如果没有设置相应的参数
152 | if(!isset($arr[$tmpKey]) || $arr[$tmpKey] === ""){
153 |
154 | if($tmpVal == $pre){//则使用默认的值
155 | continue;
156 | }else if($tmpVal){
157 | $arr[$tmpKey] = $tmpVal;
158 | }else{
159 | if($v = $_FILES[$tmpKey]){
160 |
161 | $filename = dirname($v['tmp_name'])."/".$v['name'];
162 | move_uploaded_file($v['tmp_name'], $filename);
163 | $arr[$tmpKey] = "@$filename";
164 |
165 | }else{
166 | $this->error->showError("api调用参数错误","未传入参数$tmpKey");
167 | }
168 | }
169 | }
170 |
171 | $keysArr[$tmpKey] = $arr[$tmpKey];
172 | }
173 | //检查选填参数必填一的情形
174 | foreach($optionArgList as $val){
175 | $n = 0;
176 | foreach($val as $v){
177 | if(in_array($v, array_keys($keysArr))){
178 | $n ++;
179 | }
180 | }
181 |
182 | if(! $n){
183 | $str = implode(",",$val);
184 | $this->error->showError("api调用参数错误",$str."必填一个");
185 | }
186 | }
187 |
188 | if($method == "POST"){
189 | if($baseUrl == "https://graph.qq.com/blog/add_one_blog") $response = $this->urlUtils->post($baseUrl, $keysArr, 1);
190 | else $response = $this->urlUtils->post($baseUrl, $keysArr, 0);
191 | }else if($method == "GET"){
192 | $response = $this->urlUtils->get($baseUrl, $keysArr);
193 | }
194 |
195 | return $response;
196 |
197 | }
198 |
199 | /**
200 | * _call
201 | * 魔术方法,做api调用转发
202 | * @param string $name 调用的方法名称
203 | * @param array $arg 参数列表数组
204 | * @since 5.0
205 | * @return array 返加调用结果数组
206 | */
207 | public function __call($name,$arg){
208 | //如果APIMap不存在相应的api
209 | if(empty($this->APIMap[$name])){
210 | $this->error->showError("api调用名称错误","不存在的API: $name");
211 | }
212 |
213 | //从APIMap获取api相应参数
214 | $baseUrl = $this->APIMap[$name][0];
215 | $argsList = $this->APIMap[$name][1];
216 | $method = isset($this->APIMap[$name][2]) ? $this->APIMap[$name][2] : "GET";
217 |
218 | if(empty($arg)){
219 | $arg[0] = null;
220 | }
221 |
222 | //对于get_tenpay_addr,特殊处理,php json_decode对\xA312此类字符支持不好
223 | if($name != "get_tenpay_addr"){
224 | $response = json_decode($this->_applyAPI($arg[0], $argsList, $baseUrl, $method));
225 | $responseArr = $this->objToArr($response);
226 | }else{
227 | $responseArr = $this->simple_json_parser($this->_applyAPI($arg[0], $argsList, $baseUrl, $method));
228 | }
229 |
230 |
231 | //检查返回ret判断api是否成功调用
232 | if($responseArr['ret'] == 0){
233 | return $responseArr;
234 | }else{
235 | $this->error->showError($response->ret, $response->msg);
236 | }
237 |
238 | }
239 |
240 | //php 对象到数组转换
241 | private function objToArr($obj){
242 | if(!is_object($obj) && !is_array($obj)) {
243 | return $obj;
244 | }
245 | $arr = array();
246 | foreach($obj as $k => $v){
247 | $arr[$k] = $this->objToArr($v);
248 | }
249 | return $arr;
250 | }
251 |
252 |
253 | /**
254 | * get_access_token
255 | * 获得access_token
256 | * @param void
257 | * @since 5.0
258 | * @return string 返加access_token
259 | */
260 | public function get_access_token(){
261 | return $this->recorder->read("access_token");
262 | }
263 |
264 | //简单实现json到php数组转换功能
265 | private function simple_json_parser($json){
266 | $json = str_replace("{","",str_replace("}","", $json));
267 | $jsonValue = explode(",", $json);
268 | $arr = array();
269 | foreach($jsonValue as $v){
270 | $jValue = explode(":", $v);
271 | $arr[str_replace('"',"", $jValue[0])] = (str_replace('"', "", $jValue[1]));
272 | }
273 | return $arr;
274 | }
275 | }
276 |
--------------------------------------------------------------------------------