Thinkphp3.2与Discuz同步登录
2018-04-02 13:11 浏览(1866

很早以前就做过dz的同步登陆,这几天又和thinkphp结合搞了一下,做下记录。

这个教程基本不止适合thinkphp哦~。其它程序想实现同步登陆、退出、消息、注册都可以参照此教程。

首先,去下载我写好的代码压缩包,CSDN下载链接。(没办法,我的博客暂时没开发文件上传,还考虑到下载速度的问题)


1、移动文件

解压压缩包后

将 api 文件夹复制到 thinkphp 根目录(application同级目录)

将 Ucenter 文件夹复制到 application 目录


2、修改配置文件

修改 Application/Ucenter/Conf/config.php ,内容为dz后台添加应用的内容,勾选同步登陆

内容为 

define('UC_CONNECT', 'mysql');
define('UC_DBHOST', '127.0.0.1');
define('UC_DBUSER', 'root');
define('UC_DBPW', '123456');
define('UC_DBNAME', 'bbs');
define('UC_DBCHARSET', 'utf8');
define('UC_DBTABLEPRE', '`bbs`.pre_ucenter_');
define('UC_DBCONNECT', '0');
define('UC_KEY', 'c49dz25t6uovDHGDnu50xVoBTqPVMZf42RqczBs');
define('UC_API', 'http://bbs.test.com/uc_server');
define('UC_CHARSET', 'utf-8');
define('UC_IP', '');
define('UC_APPID', '2');
define('UC_PPP', '20');

修改 Application/Ucenter/config.inc.php ,按照注释填写即可,通信相关中的内容可参照上面 config.php 中的配置

内容为

define('UC_CONNECT', 'mysql');				// 连接 UCenter 的方式: mysql/NULL, 默认为空时为 fscoketopen()
							// mysql 是直接连接的数据库, 为了效率, 建议采用 mysql

							//数据库相关 (mysql 连接时, 并且没有设置 UC_DBLINK 时, 需要配置以下变量)
define('UC_DBHOST', '127.0.0.1');			// UCenter 数据库主机
define('UC_DBUSER', 'root');				// UCenter 数据库用户名
define('UC_DBPW', '123456');				// UCenter 数据库密码
define('UC_DBNAME', 'bbs');				// UCenter 数据库名称
define('UC_DBCHARSET', 'utf8');				// UCenter 数据库字符集
define('UC_DBTABLEPRE', '`bbs`.pre_ucenter_');		// UCenter 数据库表前缀

//通信相关
define('UC_KEY', 'c49dz25t6uovDHGDnu50xVoBTqPVMZf42RqczBs');				// 与 UCenter 的通信密钥, 要与 UCenter 保持一致
define('UC_API', 'http://bbs.test.com/uc_server');	// UCenter 的 URL 地址, 在调用头像时依赖此常量
define('UC_CHARSET', 'utf-8');				// UCenter 的字符集
define('UC_IP', '');					// UCenter 的 IP, 当 UC_CONNECT 为非 mysql 方式时, 并且当前应用服务器解析域名有问题时, 请设置此值
define('UC_APPID', '2');				// 当前应用的 ID
define('UC_PPP', '20');

$dbhost = '127.0.0.1';			// 数据库服务器
$dbuser = 'root';			// 数据库用户名
$dbpw = '123456';			// 数据库密码
$dbname = 'bbs';			// 数据库名
$pconnect = 0;				// 数据库持久连接 0=关闭, 1=打开
$tablepre = '`bbs`.pre_ucenter_';   	// 表名前缀, 同一数据库安装多个论坛请修改此处
$dbcharset = 'utf8';			// MySQL 字符集, 可选 'gbk', 'big5', 'utf8', 'latin1', 留空为按照论坛字符集设定

//同步登录 Cookie 设置
$cookiedomain = ''; 			// cookie 作用域
$cookiepath = '/';			// cookie 作用路径


3、方法解释和使用

Application/Ucenter/Controller/IndexController.php 为同步登录的控制器,内容如下

namespace Ucenter\Controller;

require_once APP_PATH . "Ucenter/config.inc.php";
require_once APP_PATH . "Ucenter/uc_client/client.php";

class IndexController extends \Think\Controller {

    public function tlogin($iusername, $ipassword) {
        //通过接口判断登录帐号的正确性,返回值为数组
        list($uid, $username, $password, $email, $merge, $phone) = uc_user_login($iusername, $ipassword);

        setcookie('Example_auth', '', -86400);
        if ($uid > 0) {
            //用户登陆成功,设置 Cookie,加密直接用 uc_authcode 函数
            setcookie('Example_auth', uc_authcode($uid . "\t" . $username, 'ENCODE'));
            //生成同步登录的代码
            $ucsynlogin = uc_user_synlogin($uid);
            return $this->_return($ucsynlogin, '', true);
        } elseif ($uid == -1) {
            return $this->_return($uid, '用户不存在,或者被删除');
        } elseif ($uid == -2) {
            return $this->_return($uid, '密码错误');
        } else {
            return $this->_return($uid, '未定义的操作');
        }
    }

    public function autologin() {
        $auth = $_COOKIE['Example_auth'];
        $auth = daddslashes(explode("\t", uc_authcode($auth, 'DECODE')));
        list($discuz_uid, $discuz_uname) = empty($auth) || count($auth) < 2 ? array('', '') : $auth;
        if ($discuz_uid) {
            $user = uc_get_user($discuz_uid, 1);
        }
        if ($user) {
            session('user_id', $user[0]);
            session('user', $user);
            session('user.user_id', $user[0]);
            session('user.user_name', $user[1]);
        }
    }
    public function logout() {
        $ucsynlogout = uc_user_synlogout();
        session('user', null);
        session('user_id', null);
        setcookie('Example_auth',null);
        return $ucsynlogout;
    }

    private function _return($data, $msg, $status = false) {
        return ['data' => $data, 'info' => $msg, 'status' => $status];
    }

}

这里要使用 php 自带的 setcookie 和 $_COOKIE 来设置和获取cookie,因为tp中的 cookie 方法设置有前缀,有可能获取不到

tlogin为登录,autologin为自动登录


测试

在 Application/Home/IndexController.php 中的方法,此 IndexController 继承一个叫 BaseController 的基类

public function test(){
    $ucenter = new \Ucenter\Controller\IndexController();
    $data = $ucenter->tlogin('admin', 'shiba123.!');
    echo '<pre>';
    print_r($data);
    echo '</pre>';
}
public function test1(){
    $ucenter = new \Ucenter\Controller\IndexController();
    $ucenter->autologin();
    echo '<pre>';
    print_r($_SESSION['user']);
    echo '</pre>';
}

访问 http://localhost/?m=home&a=test

返回

Array
(
    [data] => '<script type="text/javascript" src="http://bbs.test.com/api/uc.php?time=1522640515&code=生成的登录key" reload="1"></script>'
    [info] => 
    [status] => 1
)

status 为 1 表示成功,data会返回同步登录的代码,将此代码插入到任意一个dom中即可同步登录,此处是通知 bbs.test.com 这个论坛登录

此时就可以执行 autologin登录,当然这个自动登录放在 BaseController 中更好


访问 http://localhost/?m=home&a=test1

返回

Array
(
    [0] => 1
    [1] => admin
    [2] => admin@admin.com
    [user_id] => 1
    [user_name] => admin
)

根据 $_SESSION['user_id'] 就可以知道是否登录


如何实现自动登录?

在 BaseController 中的 __construct 中加入如下到代码

$ucenter = new \Ucenter\Controller\IndexController();
$ucenter->autologin();


同步退出,执行 logout 将返回的内容插入到 dom中,和同步登录类似

$ucenter = new \Ucenter\Controller\IndexController();
$data = $ucenter->logout();


比如想实现注册(uc_user_register),发消息(uc_pm_send)等功能,使用uc_client中的对应方法即可,还是很简单的,我就没一一列出


4、下面来讲讲同步登录的原理,方便以后出问题了排查

场景:在discuz中添加了一个叫官网的应用,在官网中登录,discuz中实现同步登录

在官网中执行 uc_user_login 方法的时候,首先会检查 Application/Ucenter/uc_client/data/cache/apps.php 这个文件中是否有多个应用,若是一个,当然就不会执行同步登录

若是多个应用,将会 uc_api_post 远程请求 discuz 根目录的 uc_server/control/user.php 中的 onsynlogin 方法,将 uc_server/data/cache/apps.php 中的应用缓存数据获取到,并执行加密

function onsynlogin() {
    $this->init_input();
    $uid = $this->input('uid');
    if ($this->app['synlogin']) {
        if ($this->user = $_ENV['user']->get_user_by_uid($uid)) {
            $synstr = '';
            foreach ($this->cache['apps'] as $appid => $app) {
                if ($app['synlogin']) {
                    if ($app['appid'] != $this->app['appid']) {
                        $synstr .= '<script type="text/javascript" src="' . $app['url'] . '/api/' . $app['apifilename'] . '?time=' . $this->time . '&code=' . urlencode($this->authcode('action=synlogin&username=' . $this->user['username'] . '&uid=' . $this->user['uid'] . '&password=' . $this->user['password'] . "&time=" . $this->time, 'ENCODE', $app['authkey'])) . '" reload="1"></script>';
                    }
                    if (is_array($app['extra']['extraurl']))
                        foreach ($app['extra']['extraurl'] as $extraurl) {
                            $synstr .= '<script type="text/javascript" src="' . $extraurl . '/api/' . $app['apifilename'] . '?time=' . $this->time . '&code=' . urlencode($this->authcode('action=synlogin&username=' . $this->user['username'] . '&uid=' . $this->user['uid'] . '&password=' . $this->user['password'] . "&time=" . $this->time, 'ENCODE', $app['authkey'])) . '" reload="1"></script>';
                        }
                }
            }
            return $synstr;
        }
    }
    return '';
}

code就是加密的内容,加密内容为时间戳,用户名,用户id,加密key为apps.php中authkey,必须和 Application/Ucenter/config.inc.php 中的 UC_KEY 相同,uc.php中才能解密

返回的 $synstr 是一段js代码,如下

<script type="text/javascript" src="http://bbs.test.com/api/uc.php?time=1522640515&code=加密code" reload="1"></script>

若是多个应用,将会有多个js,将此返回的内容插入到页面中,js会请求论坛根目录下的 api/uc.php

通过解密后执行 synlogin 方法,注意这一段代码

header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');

这是跨域执行cookie的关键,将会设置一个名叫 auth 的cookie,dz或判断这个 auth 来执行登录。

同理,dz登录,通知官网应用,也会执行 synlogin ,不过是设置的 Example_auth 这个cookie,所以,我们在官网应用中执行自动登录时,要获取 Example_auth 这个cookie,请看 Ucenter中的 autologin 方法


若是发现不能同步登录,请检查对应应用下的 uc_client/data/cache/apps.php 这个文件,还有 uc_server/data/cache/apps.php ,保证数量是和dz后台配置的应用数相同,若是不相同,删掉即可

评论(0)
发布评论
回复X
聊天室(0