Typecho社会化登录插件TeConnect

前言

大卫科技 Blog原创作品,版权属于:大卫科技 Blog(转载请著名出处)!
原文链接https://www.iyuu.cn/archives/88/
如需转载,可下载Markdown原文:点此下载md.txt

本插件已部署,欢迎体验:qq-login.png

一、功能介绍

Typecho互联登录插件,目前已支持15种第三方登录:QQ/腾讯微博/新浪微博/网易微博/人人网/360/豆瓣/Github/Google/Msn/点点/淘宝网/百度/开心网/搜狐。
特色亮点功能:同一用户,可以绑定15种不同的登录方式!
在原项目TeConnect的基础上,进行完全的二次开发、优化及修复。重点有:

  1. 重新设计数据表结构,删除原connect表,后续具有完美的扩展性及兼容性;
  2. 已开发支持15种第三方登录,后续可以支持更多……;
  3. 优化会员绑定逻辑,修复原项目登录状态下绑定错乱、重复绑定等Bug;
  4. 增加会员uuid机制,自动关联users数据表的uid字段,支持更多功能开发的可能;
  5. 优化解绑逻辑,和第三方资料更新逻辑等。

项目仓库

码云:https://gitee.com/ledc/TeConnect
github:https://github.com/ledccn/TeConnect


二、插件下载

样式1样式2样式3样式4样式5
bt_blue_76X24.pngbt_white_76X24.pngbt_blue_24X24.pngbt_white_24X24.pngbt_92X120.png

三、安装步骤 qq-login.png 即可查看!

  1. 解压插件到Plugins目录;
  2. 在后台启用插件,并配置插件参数(方法见:参数配置 - 配置示例);
  3. 在当前使用主题的适当位置添加TeConnect_Plugin::show()方法,代码:

    <?php TeConnect_Plugin::show(); ?>

    handsome Theme示例图:handsome Theme示例图
    getAvator函数.png
    下载你喜欢的png登录图标,放到/usr/plugins/TeConnect/login_ico/{type}.png,然后修改登录按钮样式在Plugin.php 第125行,代码:
    <a href="{url}"><img src="/usr/plugins/TeConnect/login_ico/{type}.png" alt="{type}-{title}" style="margin-top: 0.8em;"></a>

  4. 在第三方平台设置网站回调域,注意区分http、https地址重写隐藏index.php(方法见:参数配置 - 配置示例)。
  5. 如果您的主题开启了全站PJAX,需要把以下代码放入PJAX回调函数内:

    var exdate = new Date();
    exdate.setDate(exdate.getDate() + 1);
    document.cookie = "TeConnect_Referer=" + encodeURI(window.location.href) + "; expires=" + 
    exdate.toGMTString() + "; path=/";

四、参数配置

配置示例

名称类型配置示例网站回调域
腾讯QQqqqq:APP_KEY,APP_SECRET,腾讯QQhttps://127.0.0.1/oauth_callback?type=qq
腾讯微博tencenttencent:APP_KEY,APP_SECRET,腾讯微博https://127.0.0.1/oauth_callback?type=tencent
新浪微博sinasina:APP_KEY,APP_SECRET,新浪微博https://127.0.0.1/oauth_callback?type=sina
网易微博t163t163:APP_KEY,APP_SECRET,网易微博https://127.0.0.1/oauth_callback?type=t163
人人网renrenrenren:APP_KEY,APP_SECRET,人人网https://127.0.0.1/oauth_callback?type=renren
360x360x360:APP_KEY,APP_SECRET,360https://127.0.0.1/oauth_callback?type=x360
豆瓣doubandouban:APP_KEY,APP_SECRET,豆瓣https://127.0.0.1/oauth_callback?type=douban
Githubgithubgithub:APP_KEY,APP_SECRET,Githubhttps://127.0.0.1/oauth_callback?type=github
Googlegooglegoogle:APP_KEY,APP_SECRET,Googlehttps://127.0.0.1/oauth_callback?type=google
MSNmsnmsn:APP_KEY,APP_SECRET,MSNhttps://127.0.0.1/oauth_callback?type=msn
点点diandiandiandian:APP_KEY,APP_SECRET,点点https://127.0.0.1/oauth_callback?type=diandian
淘宝网taobaotaobao:APP_KEY,APP_SECRET,淘宝网https://127.0.0.1/oauth_callback?type=taobao
百度baidubaidu:APP_KEY,APP_SECRET,百度https://127.0.0.1/oauth_callback?type=baidu
开心网kaixinkaixin:APP_KEY,APP_SECRET,开心网https://127.0.0.1/oauth_callback?type=kaixin
搜狐微博sohusohu:APP_KEY,APP_SECRET,搜狐微博https://127.0.0.1/oauth_callback?type=sohu

1:后台互联配置

具体格式为:type:appid,appkey,title,注释:

  • type:第三方登录帐号类型
  • appid:第三方开放平台申请的应用id
  • appkey:第三方开放平台申请的应用key
  • title:登录按钮的标题
    在后台互联配置中,直接以文本形式填写,一行为一个帐号系统的参数;

为减少错误发生,您可以复制对应的配置示例,把APP_KEYAPP_SECRET改成您自己的参数就可以了!
例如:qq:APP_KEY,APP_SECRET,腾讯QQ
改成:qq:101015836,547s87f8s7df7sd877ji75s78sdfd,腾讯QQ
粘贴到后台互联配置,即完成了腾讯QQ登录的配置,其他类型同理!

2:网站回调域配置 bt_blue_76X24.png 即可查看

此处内容需要评论回复后(审核通过)方可阅读。

五、第三方账号绑定流程

1、方案选择

我参考了国内主流的几家互联网公司的第三方账号登录功能,发现主要分成两种设计方案;
一种是账号强绑,像京东、小米等,在第三方账号授权通过后,需要用户绑定自己的账号;
一种是今日头条、知乎,在第三方账号授权通过后,随机给用户生成一个账号或者调用第三方账号昵称,无需绑定账号,即可成功登录。
目前,两种方案都支持,您可以在后台开启或关闭强制绑定

2、绑定流程一(未登录状态)

绑定流程一(未登录状态).png
用户在登录界面点击第三方账号,授权通过后,我们获得用户第三方账号的OpenID,由此判断用户的第三方账号之前是否绑定过,如果绑定过则直接登录成功。
如果没有绑定过,则跳到账号绑定页面。账号绑定页面需要分成已有账号直接绑定,和没有账号,新注册账号进行绑定两种情况。
当用户已有账号时,通过输入账号密码校验身份,校验通过后即可绑定成功/登录成功。
当用户没有账号时,用户可通过注册新账号,注册成功后即可绑定成功/登录成功。

3、绑定流程二(登录状态)

在个人账号中心里提供绑定管理的功能和界面,在用户已经登录的情况下,可以直接绑定第三方账号,只要获得授权通过,即可绑定成功。


六、第三方帐号解绑流程

在个人账号中心>绑定管理中,可以对已经绑定的第三方账号进行解绑操作。在这里需要注意,由于用户长期使用第三方账号登录,实际上是由第三方账号承担了提交账号和保护账号安全的工作,因此在解绑第三方账号时,我们需要提醒用户,解绑以后只能通过本平台账号密码方式来登录。最好是提示用户记住当前账号
提示用户记住当前账号.jpg
京东的解绑账号功能

另一方面,由于之前是由第三方账号“帮平台”做了账号安全的工作,因此在解绑账号的时候,我们需要考虑如何保护账号安全。因此可以在解绑的时候,对账号做一定的安全校验或安全保护。
我们最终定的方案是 当用户在解绑时,需要校验手机短信验证码,如果没有绑定手机,则提示用户先去绑定手机。
第三方帐号解绑流程.png
解绑流程

总结
第三方账号虽然是一个小功能,但是在设计过程中,我们要结合自身产品的特点来确定产品方案和产品流程。授权之后,是直接登录成功,还是绑定自己平台的账号,这是由自己产品特点决定。同时,对新增账号来说,如何设计用户账号的安全,也需要根据产品特点和安全策略来设计适合的产品流程。


七、数据表结构(不开发,可以不看)

typecho_oauth_user数据库表结构:

字段类型注释
uidint(10) unsigned NOT NULL COMMENT用户ID
access_tokenvarchar(255) NOT NULL COMMENT用户对应access_token
datetimetimestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT最后登录
expires_inint(10) unsigned NOT NULL DEFAULT '0' COMMENTaccess_token过期时间戳
gendertinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT性别0未知,1男,2女
head_imgvarchar(255) NOT NULL COMMENT头像
namevarchar(38) NOT NULL COMMENT名字
nicknamevarchar(38) NOT NULL COMMENT第三方昵称
openidchar(50) NOT NULL COMMENT第三方平台的用户唯一标识
refresh_tokenvarchar(255) NOT NULL COMMENT刷新有效期token
typechar(32) NOT NULL COMMENT第三方平台的类型
uuidint(10) unsigned NOT NULL COMMENT对应users表uid

特色亮点功能:同一用户,可以绑定15种不同的登录方式!
【推荐:后台关闭强制绑定】

  1. 管理员已登录状态时,直接互联登录,可以绑定管理员身份!(源码第128行此时users表不变,oauth_user表创建绑定关系,uid、uuid等于管理员uid);
  2. 管理员未登录时,直接互联登录(源码第153行此时users表创建新用户、oauth_user表绑定关系:uid、uuid等于新用户uid);
  3. 在第2步完成后,退出互联登录;重新用管理员登录成功后不退出,再次互联登录(源码第256行此时users表不变,oauth_user表更新绑定关系:uuid不变、uid等于管理员uid);
  4. 同一用户,可以绑定15种不同的登录方式!但是,同类型的第三方账号仅可绑定一个!通俗点:用户不能绑定2个QQ号码来进行登录。(源码250行、267行
  5. oauth_user表完整保存用户的关键授权参数access_token、expires_in、datetime,可以在用户离线的情况下调用API;
  6. uuid完整记录着原始绑定关系,支持解绑后数据不丢失。

typecho_oauth_user表结构.png
oauth_user表数据结构.png


关键代码

关键PHP代码段

/**
     * 获取Oauth登录地址,重定向
     *
     * @access public
     * @param string $type 第三方登录类型
     */
    public function oauth(){
        $type = $this->request->get('type');
        if(is_null($type)){
            throw new Typecho_Widget_Exception("请选择登录方式!");
        }
        $options = TeConnect_Plugin::options();
        $type = strtolower($type);
        //判断登录方式是否支持
        if(!isset($options[$type])){
            throw new Typecho_Widget_Exception("暂不支持该登录方式! {$type}");
        }
        //组装回调地址
        $callback_url = Typecho_Common::url('/oauth_callback?type='.$type, $this->options->index);
        //加载ThinkOauth类并实例化一个对象
        require_once 'ThinkOauth.php';
        $sdk = ThinkOauth::getInstance($type);
        //302重定向
        $this->response->redirect($sdk->getRequestCodeURL($callback_url));
    }
    /**
     * 第三方登录回调
     *
     * @access public
     * @param array $do POST来的用户绑定数据
     * @param string $type 第三方登录类型
     */
    public function callback(){
        if(!isset($_SESSION)){
            session_start();
            $this->auth = isset($_SESSION['__typecho_auth']) ? $_SESSION['__typecho_auth']  : array();
            $this->oauth_user = isset($_SESSION['__typecho_oauth_user']) ? $_SESSION['__typecho_oauth_user']  : array();
        }
        //仅处理来自绑定界面POST提交的数据,第三方回调会跳过
        if($this->request->isPost()){
            $do = $this->request->get('do');
            if(!in_array($do,array('bind','reg'))){
                throw new Typecho_Widget_Exception("错误数据!");
            }

            if(!isset($this->auth['openid']) || !isset($this->auth['type'])){
                $this->response->redirect($this->options->index);
            }
            $func = 'doCallback'.ucfirst($do);
            $this->$func();
            unset($_SESSION['__typecho_auth']);
            $this->response->redirect($this->options->index);
        }

        //第三方登录回调处理
        $options = TeConnect_Plugin::options();
        if(empty($this->auth)){
            $this->auth['code'] = $this->request->get('code','');
            $this->auth['type'] = $this->request->get('type','');

            if(empty($this->auth['code']) && empty($this->auth['type']) && !isset($options[$this->auth['type']])){
                //缺少code、type、type未开启,直接跳主页
                $this->response->redirect($this->options->index);
            }
            //转小写
            $this->auth['type'] = $type = strtolower($this->auth['type']);
            //组装回调地址
            $callback_url = Typecho_Common::url('/oauth_callback?type='.$type, $this->options->index);
            //加载ThinkOauth类并实例化一个对象
            require_once 'ThinkOauth.php';
            $sdk = ThinkOauth::getInstance($type);
            //请求接口(返回值包含openid)
            $token = $sdk->getAccessToken($callback_url, $this->auth['code']);
            if(is_array($token)){
                //获取第三方账号数据
                $user_info = $this->$type($token);
                $oauth_user = array(
                    'uid'=>0,
                    'access_token'=>$token['access_token'],
                    'expires_in'=>isset($token['expires_in']) ? $this->options->gmtTime+$token['expires_in']: 0,
                    'gender'=>$user_info['gender'],
                    'head_img'=>$user_info['head_img'],
                    'name'=>$user_info['name'],
                    'nickname'=>$user_info['nickname'],
                    'openid'=>$token['openid'],
                    'type'=>$type,
                    );
                //获取openid
                $this->auth['openid'] = $token['openid'];
                $this->auth['nickname'] = $user_info['nickname'];
            }else{
                $this->response->redirect($this->options->index);
            }
        }
        //登录状态
        if ($this->user->hasLogin()) {
            //UUID会员的原始ID
            $this->auth['uuid'] = $this->user->uid;

            //直接绑定第三方账号
            $this->bindUser($this->user->uid, $oauth_user, $this->auth['type']);
            //提示,并跳转
            $this->widget('Widget_Notice')->set(array('成功绑定账号!'));
            $this->response->redirect($this->options->index);
        }

        //未登录状态,查询第三方账号的绑定关系
        $isConnect = $this->findConnectUser($oauth_user,$this->auth['type']);
        if($isConnect){
            //已经绑定,直接登录
            $this->useUidLogin($isConnect['uid']);
            //提示,并跳转
            $this->widget('Widget_Notice')->set(array('已成功登陆!'));
            $this->response->redirect($this->options->index);
        }

        //未登录状态且未绑定,控制显示绑定界面
        $custom = $this->options->plugin('TeConnect')->custom;
        if(!$custom && !empty($this->auth['nickname'])){
            $dataStruct = array(
                'screenName'=>  $this->auth['nickname'],
                'created'   =>  $this->options->gmtTime,
                'group'     =>  'subscriber'
            );
            //新注册账号
            $uid = $this->regConnectUser($dataStruct,$oauth_user);
            if($uid){
                $this->widget('Widget_Notice')->set(array('已成功注册并登陆!'));
            }else{
                $this->widget('Widget_Notice')->set(array('注册用户失败!'),'error');
            }
            $this->response->redirect($this->options->index);
        }else{
            //用户绑定界面
            if(!isset($_SESSION['__typecho_auth']))
            $_SESSION['__typecho_auth'] = $this->auth;
            $_SESSION['__typecho_oauth_user'] = $oauth_user;

            //未绑定,引导用户到绑定界面
            $this->render('callback.php');
        }
    }
    //绑定已有用户
    protected function doCallbackBind(){
        $name = $this->request->get('name');
        $password = $this->request->get('password');

        if(empty($name) || empty($password)){
            $this->widget('Widget_Notice')->set(array('帐号或密码不能为空!'),'error');
            $this->response->goBack();
        }
        $isLogin = $this->user->login($name,$password);
        if($isLogin){
            //UUID会员的原始ID
            $this->auth['uuid'] = $this->user->uid;

            $this->widget('Widget_Notice')->set(array('已成功绑定并登陆!'));
            $this->bindUser($this->user->uid,$this->oauth_user,$this->auth['type']);
        }else{
            $this->widget('Widget_Notice')->set(array('帐号或密码错误!'),'error');
            $this->response->goBack();
        }
    }
    //注册新用户
    protected function doCallbackReg(){
        $url = $this->request->get('url');

        $validator = new Typecho_Validate();
        $validator->addRule('mail', 'required', _t('必须填写电子邮箱'));
        $validator->addRule('mail', array($this, 'mailExists'), _t('电子邮箱地址已经存在'));
        $validator->addRule('mail', 'email', _t('电子邮箱格式错误'));
        $validator->addRule('mail', 'maxLength', _t('电子邮箱最多包含200个字符'), 200);

        $validator->addRule('screenName', 'required', _t('必须填写昵称'));
        $validator->addRule('screenName', 'xssCheck', _t('请不要在昵称中使用特殊字符'));
        $validator->addRule('screenName', array($this, 'screenNameExists'), _t('昵称已经存在'));

        if($url){
            $validator->addRule('url', 'url', _t('个人主页地址格式错误'));
        }

        /** 截获验证异常 */
        if ($error = $validator->run($this->request->from('mail', 'screenName', 'url'))) {
            /** 设置提示信息 */
            $this->widget('Widget_Notice')->set($error);
            $this->response->goBack();
        }

        $dataStruct = array(
            'mail'      =>  $this->request->mail,
            'screenName'=>  $this->request->screenName,
            'created'   =>  $this->options->gmtTime,
            'group'     =>  'subscriber'
        );
        $uid = $this->regConnectUser($dataStruct, $this->oauth_user);
        if($uid){
            $this->widget('Widget_Notice')->set(array('已成功注册并登陆!'));
        }
    }

    protected function regConnectUser($data,$oauth_user){
        $insertId = $this->insert($data);
        if($insertId){
            //UUID会员的原始ID
            $this->auth['uuid'] = $insertId;

            $this->bindUser($insertId,$oauth_user,$this->auth['type']);
            $this->useUidLogin($insertId);
            return $insertId;
        }else{
            return false;
        }
    }

    //处理用户与第三方账号的绑定关系(逻辑复杂)
    protected function bindUser($uid,$oauth_user,$type){
        $oauth_user['uid'] = $uid;
        if(isset($this->auth['uuid'])){
            $oauth_user['uuid'] = $this->auth['uuid'];
        }
        //查询当前登录的账号是否绑定?
        $connect = $this->db->fetchRow($this->db->select()
            ->from('table.oauth_user')
            ->where('uid = ?', $uid)
            ->where('type = ?', $type)
            ->limit(1));
        if(empty($connect)){
            //未绑定
            $oauthRow = $this->findConnectUser($oauth_user,$type);
            if($oauthRow){
                //已存在第三方账号,更新绑定关系
                $this->db->query($this->db
                ->update('table.oauth_user')
                ->rows(array('uid' => $uid))
                ->where('openid = ?', $oauth_user['openid'])
                ->where('type = ?', $type));
            }else{
                //未绑定,插入数据并绑定
                $this->db->query($this->db->insert('table.oauth_user')->rows($oauth_user));
            }
        }else{
            //已绑定,判断更新条件,避免绑定错乱(同类型的第三方账号,用户只能绑定一个)
            if($connect[$type.'OpenId'] == $openid){
                ###更新资料tudo
            }else{
                ###换绑tudo
            }
        }
    }
    //查找第三方账号
    protected function findConnectUser($oauth_user,$type){
        $user = $this->db->fetchRow($this->db->select()
            ->from('table.oauth_user')
            ->where('openid = ?', $oauth_user['openid'])
            ->where('type = ?', $type)
            ->limit(1));
        return empty($user)? 0 : $user;
    }
    //使用用户uid登录
    protected function useUidLogin($uid,$expire = 0){
        $authCode = function_exists('openssl_random_pseudo_bytes') ?
        bin2hex(openssl_random_pseudo_bytes(16)) : sha1(Typecho_Common::randString(20));
        $user = array('uid'=>$uid,'authCode'=>$authCode);

        Typecho_Cookie::set('__typecho_uid', $uid, $expire);
        Typecho_Cookie::set('__typecho_authCode', Typecho_Common::hash($authCode), $expire);

        //更新最后登录时间以及验证码
        $this->db->query($this->db
            ->update('table.users')
            ->expression('logged', 'activated')
            ->rows(array('authCode' => $authCode))
            ->where('uid = ?', $uid));
        $this->db->query($this->db
            ->update('table.oauth_user')
            ->rows(array('datetime' => date("Y-m-d H:i:s",time())))
            ->where('uid = ?', $uid));
    }

    public function render($themeFile){
        /** 文件不存在 */
        if (!file_exists($this->_themeDir . $themeFile)) {
            Typecho_Common::error(500);
        }
        /** 输出模板 */
        require_once $this->_themeDir . $themeFile;
    }
    /**
     * 获取主题文件
     *
     * @access public
     * @param string $fileName 主题文件
     * @return void
     */
    public function need($fileName){
        require $this->_themeDir . $fileName;
    }

    //登录成功,获取腾讯QQ用户信息
    public function qq($token){
        $qq = ThinkOauth::getInstance('qq', $token);
        $data = $qq->call('user/get_user_info');
        if($data['ret'] == 0){
            $userInfo['name'] = $data['nickname'];
            $userInfo['nickname'] = $data['nickname'];
            $userInfo['head_img'] = $data['figureurl_2'];

            if($data['gender'] == '男'){
                $userInfo['gender'] = 1;
            }elseif($data['gender'] == '女'){
                $userInfo['gender'] = 2;
            }else{
                $userInfo['gender'] = 0;
            }
            return $userInfo;
        } else {
            $this->widget('Widget_Notice')->set(array("获取腾讯QQ用户信息失败:{$data['msg']}"),'error');
        }
    }

    //登录成功,获取腾讯微博用户信息
    public function tencent($token){
        $tencent = ThinkOauth::getInstance('tencent', $token);
        $data    = $tencent->call('user/info');
        if($data['ret'] == 0){
            $userInfo['name'] = $data['data']['name'];
            $userInfo['nickname'] = $data['data']['nick'];
            $userInfo['head_img'] = $data['data']['head'];
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取腾讯微博用户信息失败:{$data['msg']}");
        }
    }

    //登录成功,获取新浪微博用户信息
    public function sina($token){
        $sina = ThinkOauth::getInstance('sina', $token);
        $data = $sina->call('users/show', "uid={$sina->openid()}");
        if($data['error_code'] == 0){
            $userInfo['name'] = $data['name'];
            $userInfo['nickname'] = $data['screen_name'];
            $userInfo['head_img'] = $data['avatar_large'];
            return $userInfo;
        } else {
            //throw_exception("获取新浪微博用户信息失败:{$data['error']}");
        }
    }

    //登录成功,获取网易微博用户信息
    public function t163($token){
        $t163 = ThinkOauth::getInstance('t163', $token);
        $data = $t163->call('users/show');
        if($data['error_code'] == 0){
            $userInfo['name'] = $data['name'];
            $userInfo['nickname'] = $data['screen_name'];
            $userInfo['head_img'] = str_replace('w=48&h=48', 'w=180&h=180', $data['profile_image_url']);
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取网易微博用户信息失败:{$data['error']}");
        }
    }

    //登录成功,获取人人网用户信息
    public function renren($token){
        $renren = ThinkOauth::getInstance('renren', $token);
        $data   = $renren->call('user/get');
        if(!isset($data['error'])){
            $userInfo['name'] = $data['response']['name'];
            $userInfo['nickname'] = $data['response']['name'];
            $userInfo['head_img'] = $data['response']['avatar'][20]['url'];
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取人人网用户信息失败:{$data['error_msg']}");
        }
    }

    //登录成功,获取360用户信息
    public function x360($token){
        $x360 = ThinkOauth::getInstance('x360', $token);
        $data = $x360->call('user/me');
        if($data['error_code'] == 0){
            $userInfo['name'] = $data['name'];
            $userInfo['nickname'] = $data['name'];
            $userInfo['head_img'] = $data['avatar'];
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取360用户信息失败:{$data['error']}");
        }
    }

    //登录成功,获取豆瓣用户信息
    public function douban($token){
        $douban = ThinkOauth::getInstance('douban', $token);
        $data   = $douban->call('user/~me');
        if(empty($data['code'])){
            $userInfo['name'] = $data['name'];
            $userInfo['nickname'] = $data['name'];
            $userInfo['head_img'] = $data['avatar'];
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取豆瓣用户信息失败:{$data['msg']}");
        }
    }

    //登录成功,获取Github用户信息
    public function github($token){
        $github = ThinkOauth::getInstance('github', $token);
        $data   = $github->call('user');
        if(empty($data['code'])){
            $userInfo['name'] = $data['login'];
            $userInfo['nickname'] = $data['name'];
            $userInfo['head_img'] = $data['avatar_url'];
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取Github用户信息失败:{$data}");
        }
    }

    //登录成功,获取Google用户信息
    public function google($token){
        $google = ThinkOauth::getInstance('google', $token);
        $data   = $google->call('userinfo');
        if(!empty($data['id'])){
            $userInfo['name'] = $data['name'];
            $userInfo['nickname'] = $data['name'];
            $userInfo['head_img'] = $data['picture'];
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取Google用户信息失败:{$data}");
        }
    }

    //登录成功,获取msn用户信息
    public function msn($token){
        $msn  = ThinkOauth::getInstance('msn', $token);
        $data = $msn->call('me');
        if(!empty($data['id'])){
            $userInfo['name'] = $data['name'];
            $userInfo['nickname'] = $data['name'];
            $userInfo['head_img'] = '微软暂未提供头像URL,请通过 me/picture 接口下载';
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取msn用户信息失败:{$data}");
        }
    }

    //登录成功,获取点点用户信息
    public function diandian($token){
        $diandian  = ThinkOauth::getInstance('diandian', $token);
        $data      = $diandian->call('user/info');
        if(!empty($data['meta']['status']) && $data['meta']['status'] == 200){
            $userInfo['name'] = $data['response']['name'];
            $userInfo['nickname'] = $data['response']['name'];
            $userInfo['head_img'] = "https://api.diandian.com/v1/blog/{$data['response']['blogs'][0]['blogUuid']}/avatar/144";
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取点点用户信息失败:{$data}");
        }
    }

    //登录成功,获取淘宝网用户信息
    public function taobao($token){
        $taobao = ThinkOauth::getInstance('taobao', $token);
        $fields = 'user_id,nick,sex,buyer_credit,avatar,has_shop,vip_info';
        $data   = $taobao->call('taobao.user.buyer.get', "fields={$fields}");
        if(!empty($data['user_buyer_get_response']['user'])){
            $user = $data['user_buyer_get_response']['user'];
            $userInfo['name'] = $user['user_id'];
            $userInfo['nickname'] = $user['nick'];
            $userInfo['head_img'] = $user['avatar'];
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取淘宝网用户信息失败:{$data['error_response']['msg']}");
        }
    }

    //登录成功,获取百度用户信息
    public function baidu($token){
        $baidu = ThinkOauth::getInstance('baidu', $token);
        $data  = $baidu->call('passport/users/getLoggedInUser');
        if(!empty($data['uid'])){
            $userInfo['name'] = $data['uid'];
            $userInfo['nickname'] = $data['uname'];
            $userInfo['head_img'] = "http://tb.himg.baidu.com/sys/portrait/item/{$data['portrait']}";
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取百度用户信息失败:{$data['error_msg']}");
        }
    }

    //登录成功,获取开心网用户信息
    public function kaixin($token){
        $kaixin = ThinkOauth::getInstance('kaixin', $token);
        $data   = $kaixin->call('users/me');
        if(!empty($data['uid'])){
            $userInfo['name'] = $data['uid'];
            $userInfo['nickname'] = $data['name'];
            $userInfo['head_img'] = $data['logo50'];
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取开心网用户信息失败:{$data['error']}");
        }
    }

    //登录成功,获取搜狐用户信息
    public function sohu($token){
        $sohu = ThinkOauth::getInstance('sohu', $token);
        $data = $sohu->call('user/get_info');
        if('success' == $data['message'] && !empty($data['data'])){
            $userInfo['name'] = $data['data']['open_id'];
            $userInfo['nickname'] = $data['data']['nick'];
            $userInfo['head_img'] = $data['data']['icon'];
            $userInfo['gender'] = 0;
            return $userInfo;
        } else {
            //throw_exception("获取搜狐用户信息失败:{$data['message']}");
        }
    }

本插件已部署,欢迎体验:qq-login.png

最后修改:2020 年 08 月 04 日 07 : 00 PM
如果觉得我的文章对你有用,请随意赞赏

98 条评论

  1. 命运星辰

    启动插件时跳转500

  2. 54646646

    现在还能用吗?你站的QQ登录出现警告页面了,没更新了吗?

  3. Heresy

    每一个网站申请一个API?

  4. 卷土

    不小心点了禁用插件,再次开启;再次qq登录,用户还是需要重新绑定

  5. 有事微信电联

    500错误把表里值允许为NULL就好了,或者加个默认值,再或者去改mysql配置文件

    1. David
      @有事微信电联

      最新的github上已修复。

      1. 778
        @David

        Plugin.php on line 60

  6. 清风而已。

    之前一直用着好好的,今天提示需要配置回调地址。以为腾讯互联出了问题,上去看了也没有变化,应该如何解决

  7. Vergil

    学习一下

  8. 草泥马

    老大,为啥QQ登录后不像本站一样能获取QQ昵称呢,全是空白资料,如果开启登录完善资料,填写后点击确定不跳转

  9. 登录成功后如何隐藏按钮?

  10. INFJ

    路过,大佬66666

  11. 听风吹雨

    学习一下,冒个泡

  12. yuaneuro

    大佬牛逼

  13. 浅汐

    还不错

  14. iMorning

    500错误解决方案:修改typecho_oauth_user表,加上默认值即可

    1. David
      @iMorning

      最新的github上已修复。

  15. iMorning

    致命错误是这个:SQLSTATE[HY000]: General error: 1364 Field 'access_token' doesn't have a default value

  16. iMorning

    QQ登录报错:Notice: Undefined variable: oauth_user in D:\htdocs\usr\plugins\TeConnect\Widget.php on line 135
    Server Error

    1. David
      @iMorning

      最新的github上已修复。

  17. iMorning

    看看,说不定能用到。

  18. 隔壁老李头儿

    看起来很不错的插件~

  19. 无言

    不错!顶~

  20. 临江寒

    前来体验

  21. 鹿小鼠

    大佬审核一下呀~ 想用这个插件~

  22. 青歌

    我安装了回调地址打开显示错误Argument 1 passed to themeInit() must be an instance of Widget_Archive, instance of TeConnect_Widget given, called in /www/wwwroot/usr/plugins/TeConnect/Widget.php on line 24

    1. David
      @青歌

      安装步骤第一步,仔细看看。

  23. 鹿小鼠

    这么好的东西一定要顶一下!

  24. 早晚☁️

    |´・ω・)ノ

  25. Xtop

    谢谢分享

  26. c

    体验下

  27. 居桉

    学习一下~

  28. 眴文

    |´・ω・)ノ

  29. sunjix5852

    能和LoveKKVIP 共同使用吗?

  30. 10086

    有没有卸载方法,

  31. 淼先森

    大佬,微信公众号授权登录可以安排上吗

  32. 红鲤鱼与绿鲤鱼与驴

    试试看 貌似没头像

  33. qqqq

    测试

  34. 悟空

    ୧(๑•̀⌄•́๑)૭

  35. Inkedus

    学习一下

  36. cklx

    登陆跳转显示500,Server Error
    看了一下日志:
    [Fri Nov 15 15:32:56.257195 2019] [proxy_fcgi:error] [pid 1379:tid 139921167705856] [client 00000000:8811] AH01071: Got error 'PHP message: PHP Notice: Undefined variable: oauth_user in /data/wwwroot/rplib.cn/usr/plugins/TeConnect/Widget.php on line 135', referer: https://rplib.cn/admin/login.php

    1. David
      @cklx

      最新的github上已修复。

  37. 知稚丶

    我也来学习学习下OωO

  38. 混迹‘s Blog

    体验完毕,滴!访客卡!请上车的乘客系好安全带,现在是:Tue Nov 12 2019 13:29:27 GMT+0800 (中国标准时间)

  39. 混迹

    滴!访客卡!请上车的乘客系好安全带,现在是:Tue Nov 12 2019 13:29:27 GMT+0800 (中国标准时间)

  40. 千阪桑

    我登录出现了错误数据,链接https://blog.xiaohongyan.cn
    帮我看看

    1. 千阪桑
      @千阪桑

      解决了,但是用qq登录后跳转,没显示已经登录。还是处于游客状态

  41. 看了半天才发现要评论才有步骤

  42. koko

    大佬开发版的typecho绑定账号和注册都是直接跳首页变成未登录了

  43. -

    博主,我这边发现一个问题,就是qq注册的用户,后台删除了,就再也不能qq登录注册了,怎么修复?

    1. David
      @-

      有2个表。另一个表,也删除。

  44. 春风

    小白表示完全看不懂

  45. koko

    膜拜一下准备上车

  46. 卡洛Karro

    小白表示完全看不懂

  47. 原谅糖

    感谢分享。有一说一,QQ 提供的这几个样式都好丑

    1. David
      @原谅糖

      确实如是。

  48. racns

    不行,严格按照上面的步骤去配置插件了,但是最后使用QQ登录的时候显示这个错误Database Query Error

    1. David
      @racns

      请安装最新版本的,再试一下。
      仔细检查配置是否出错。

      1. 宝藏男孩
        @David

        微博登录同样出现Database Query Error,插件也按照你说的设置了

        1. David
          @宝藏男孩

          最新的github上已修复。

  49. racns

    我的天,好棒呀

  50. stars_kim

    ヾ(≧∇≦*)ゝ

  51. stars_kim

    666

  52. 发啊

    福娃福娃二分

  53. 轩沫博客

    感觉图标有点大。

    1. David
      @轩沫博客

      需要多大的图标,可以去QQ官方下载源文件修改。

  54. 柰枚re

    (●—●)拿走了

  55. asatmda

    厉害了

  56. 七月

    不行哦,Database Query Error
    nginx 500

  57. 微信抢房

    不错,增加微信登陆就完美了

  58. 自由

    学习 想看隐藏文件

  59. 小柒

    学习 想看隐藏文件

  60. haha

    haha

  61. 北极熊

    顶一下

  62. a'ゞTake Me Hand、

    666

  63. 亿汐

    很强

  64. 梦境缠绕

    我在QQ互联看到的是 APP ID:和APP Key:,APP_KEY,APP_SECRET在哪看啊 ,好像格式还不一样OωO

    1. David
      @梦境缠绕

      APP ID:和APP Key,是一样的意思。

  65. ⁡⁡⁡⁡⁢⁠

    test

  66. futao

    |´・ω・)ノ先看一下隐藏内容

  67. [...]https://www.iyuu.cn/archives/88/[...]

  68. Marchen

    需要完善一下,就是登录后在哪个页面登录的就回到哪个页面,这样就更好了,比如登录后台,第三方登录后直接跳去首页,又要重新进后台,这样比较麻烦的

    1. David
      @Marchen

      2020年8月3日19:10:00 功能已做好,推送至github。更新体验下吧!

    2. David
      @Marchen

      这个功能可以有,抽空做一下。人性化很多。

  69. 颔首

    666牛B哦!

  70. 月丶小柒

    我来了

  71. 小威云

    看看

  72. 暗尘弥散

    这个“适当位置”就很魔性了,整了半天愣是没整明白

    1. David
      @暗尘弥散

      每个模板登录按钮所在位置不一样。具体分析。

  73. 张益达

    牛批

    1. 模板
      @张益达

      评论一下看下,评论一下看下

  74. 一稿计划

    (๑•̀ㅁ•́ฅ)
    留个记号

  75.  

    冒个泡

  76. 渐行渐远

    学习学习!

  77. 大卫科技客服

    此贴留名记录。2019年8月11日

  78. 泽泽社长

    之前用TeConnect的怎么做数据迁移

    1. David
      @泽泽社长

      我做个升级脚本,做好了放到本页面。

      1. 客服-Protty
        @David

        做了吗(´இ皿இ`)

发表评论 取消回复