Don 发布的文章

让Discuz! X2.5版QQ互联的QQ注册跳转到完善资料页的方法

Discuz! X2.5的QQ互联增加了QQ互联游客组,使没有注册的QQ通过QQ登录后变为QQ互联游客组,可以获得一些浏览帖子内容和部分附件的权限。
当用户需要进行登录后的某项操作时,才提示他进行完善资料或者绑定已有帐号。

鉴于论坛有很多人反馈想改回原来Discuz! X2.0的注册方法,直接完善资料进行注册。
特给出一个小的修改方案予以实现,QQ登录完后跳转到完善资料页。
找到source\plugin\qqconnect\connect\connect_login.php文件,搜索代码

$utilService->redirect($referer);

改为

dheader('Location:member.php?mod=connect&referer=index.php');

云平台常见报错的解决方法

之前负责云平台版块,一直想要将云平台的常见问题总结下来方便大家排查解决问题。
由于各种原因,一直没时间全面的总结,大家遇到问题建议还是到官方论坛搜索一下,很多问题之前其实都已经解决了,并给出了相应的解决方法。

最近又发现有站长反应搬家后云平台出现问题,云平台报错如下:

出了点小错,由于站点ID/通信KEY等关键信息丢失导致Discuz!云平台服务出现异常,使用诊断工具检测站点ID和KEY,如有疑问请访问官方论坛寻求帮助

搬家同时更换了域名的情况:
1.有搬家前的数据库备份的情况,在备份数据中执行下面的sql。

SELECT `svalue` FROM `pre_common_setting` WHERE `skey` in ('my_siteid', 'my_sitekey')

查询出来的为当前的站点ID、KEY,请做好这两个值的备份。
然后进入新域名站点后台->云平台->诊断工具下,点击手动修改站点ID/KEY,如图。
[caption id="attachment_740" align="alignnone" width="300" caption="云平台诊断工具"]云平台诊断工具[/caption]

在弹层里填入前面查询的站点ID和站点KEY,同时将云平台的状态改为已开通,提交保存。
[caption id="attachment_741" align="alignnone" width="300" caption="云平台诊断工具"]云平台诊断工具[/caption]

然后进入新域名站点后台->云平台->站点信息下,点击同步站点信息。同步成功后,您的站点ID对应的域名就变成了您的新域名。

2.没有搬家前的数据库备份的情况,请将原来开通云平台的域名重新指向新域名的站点。然后通过老域名访问站点后台->云平台->诊断工具,点击手动修改站点ID/KEY,如图。
[caption id="attachment_742" align="alignnone" width="218" caption="云平台诊断工具"]云平台诊断工具[/caption]

在弹层里将站点ID/KEY删掉,同时将云平台的状态改为尚未开通云平台,提交保存。如果您的诊断工具里的状态没有站点ID/KEY,同时云平台状态已经是尚未开通就不用进行前面的操作。

然后使用老域名进入站点后台->云平台->云平台首页进行开通云平台,这时候会提示重复注册。按提示下载修复文件,然后上传到网站根目录下,使用老域名访问修复文件,按照提示找回站点ID/KEY即可。找回后,使用新域名访问站点后台->云平台->站点信息,点击同步站点信息。同步成功后,您的站点ID对应的域名就变成了您的新域名。

只是进行搬家,并没更换域名的情况:
您可以按上面的第2种方法进行自行找回ID/KEY。

其他情况请到官方发帖求助,请求官方协助解决。

Ucenter的用户注册和登录分析

因为排查一个问题,顺带着熟悉了一下Discuz!与Ucenter注册和登录的机制,特整理分析。

下面以Discuz! X2.5为例分析代码实现。

1.注册

找到source\class\class_member.php文件,有如下代码:

$uid = uc_user_register(addslashes($username), $password, $email, $questionid, $answer, $_G['clientip']);

uc_user_register定义在uc_client\client.php文件,代码如下:

function uc_user_register($username, $password, $email, $questionid = '', $answer = '', $regip = '') {
	return call_user_func(UC_API_FUNC, 'user', 'register', array('username'=>$username, 'password'=>$password, 'email'=>$email, 'questionid'=>$questionid, 'answer'=>$answer, 'regip' => $regip));
}

此函数会回调uc_server下的方法,执行文件为uc_server\control\user.php,执行代码如下:

function onregister() {
	$this->init_input();
	$username = $this->input('username');
	$password =  $this->input('password');
	$email = $this->input('email');
	$questionid = $this->input('questionid');
	$answer = $this->input('answer');
	$regip = $this->input('regip');

	if(($status = $this->_check_username($username)) < 0) {
		return $status;
	}
	if(($status = $this->_check_email($email)) < 0) {
		return $status;
	}

	$uid = $_ENV['user']->add_user($username, $password, $email, 0, $questionid, $answer, $regip);
	return $uid;
}

add_user定义在uc_server\model\user.php文件,代码如下:

function add_user($username, $password, $email, $uid = 0, $questionid = '', $answer = '', $regip = '') {
	$regip = empty($regip) ? $this->base->onlineip : $regip;
	$salt = substr(uniqid(rand()), -6);
	$password = md5(md5($password).$salt);
	$sqladd = $uid ? "uid='".intval($uid)."'," : '';
	$sqladd .= $questionid > 0 ? " secques='".$this->quescrypt($questionid, $answer)."'," : " secques='',";
	$this->db->query("INSERT INTO ".UC_DBTABLEPRE."members SET $sqladd username='$username', password='$password', email='$email', regip='$regip', regdate='".$this->base->time."', salt='$salt'");
	$uid = $this->db->insert_id();
	$this->db->query("INSERT INTO ".UC_DBTABLEPRE."memberfields SET uid='$uid'");
	return $uid;
}

这里会将用户信息写入Ucenter的用户表中。
在这里可以看到用户密码不是用明文存储的,加密的格式为:

md5(md5(用户密码) . 6位随机串)

2.登录

找到source\class\class_member.php文件,有如下代码:

$result = userlogin($_GET['username'], $_GET['password'], $_GET['questionid'], $_GET['answer'], $this->setting['autoidselect'] ? 'auto' : $_GET['loginfield'], $_G['clientip']);

userlogin定义在source\function\function_member.php文件,函数内部有如下代码:

if($isuid == 3) {
	if(!strcmp(dintval($username), $username)) {
		$return['ucresult'] = uc_user_login($username, $password, 1, 1, $questionid, $answer, $ip);
	} elseif(isemail($username)) {
		$return['ucresult'] = uc_user_login($username, $password, 2, 1, $questionid, $answer, $ip);
	}
	if($return['ucresult'][0] <= 0 && $return['ucresult'][0] != -3) {
		$return['ucresult'] = uc_user_login(addslashes($username), $password, 0, 1, $questionid, $answer, $ip);
	}
} else {
	$return['ucresult'] = uc_user_login(addslashes($username), $password, $isuid, 1, $questionid, $answer, $ip);
}

uc_user_login定义在uc_client\client.php文件,代码如下:

function uc_user_login($username, $password, $isuid = 0, $checkques = 0, $questionid = '', $answer = '') {
	$isuid = intval($isuid);
	$return = call_user_func(UC_API_FUNC, 'user', 'login', array('username'=>$username, 'password'=>$password, 'isuid'=>$isuid, 'checkques'=>$checkques, 'questionid'=>$questionid, 'answer'=>$answer));
	return UC_CONNECT == 'mysql' ? $return : uc_unserialize($return);
}

此函数会回调uc_server下的方法,执行文件为uc_server\control\user.php,执行代码如下:

function onlogin() {
	$this->init_input();
	$isuid = $this->input('isuid');
	$username = $this->input('username');
	$password = $this->input('password');
	$checkques = $this->input('checkques');
	$questionid = $this->input('questionid');
	$answer = $this->input('answer');
	if($isuid == 1) {
		$user = $_ENV['user']->get_user_by_uid($username);
	} elseif($isuid == 2) {
		$user = $_ENV['user']->get_user_by_email($username);
	} else {
		$user = $_ENV['user']->get_user_by_username($username);
	}

	$passwordmd5 = preg_match('/^\w{32}$/', $password) ? $password : md5($password);
	if(empty($user)) {
		$status = -1;
	} elseif($user['password'] != md5($passwordmd5.$user['salt'])) {
		$status = -2;
	} elseif($checkques && $user['secques'] != '' && $user['secques'] != $_ENV['user']->quescrypt($questionid, $answer)) {
		$status = -3;
	} else {
		$status = $user['uid'];
	}
	$merge = $status != -1 && !$isuid && $_ENV['user']->check_mergeuser($username) ? 1 : 0;
	return array($status, $user['username'], $password, $user['email'], $merge);
}

这里会验证用户输入的密码是否和Ucenter里存储的密码一致。
验证的格式为:

首先会验证用户密码是否为32位,如果不是则对用户输出的密码进行md5处理。(32位验证是后台设置的加密传输密码,开启后会先进行md5然后才传递给Ucenter)
Ucenter里存储的对应用户的加密后的密码 == md5(格式化后的用户输入的密码 . Ucenter里存储的对应用户的6位随机串)

关于“请先升级 Ucenter 到1.6.0以上版本”的问题排查

今天排查一个QQ互联升级的问题,需要将Discuz! X2.0升级到Discuz! X2.5,覆盖Discuz! X2.5文件后,执行update.php文件时一直报错————“请先升级 Ucenter 到1.6.0以上版本”,如图。

我检查了Ucenter的文件和数据库里的版本号都已经是1.6.0了,一开始我以为是我Discuz! X2.0的文件安装的有问题,于是我重新下载了一份完整版的Discuz! X2.0安装包,重新安装然后升级到Discuz! X2.5,执行update.php的时候仍然报错,还是上面的报错。

这下应该不是我文件的问题了,应该确实是程序里的问题。

按程序代码排查后,发现是由于安装时程序里默认指定了Ucenter的IP为127.0.0.1导致。
出现此问题的,可以登录到Ucenter后台->应用管理下,查看当前论坛应用的通信情况,如果通信失败,那么应该就是我上面说的这个原因。

怎么解决呢?其实很简单。
找到你的论坛应用,点击右侧的编辑链接,类似下图。

在编辑页面里,将应用IP删掉即可,如图。

报错的具体原因:
此问题出现的一个特殊的地方是我安装的时候都是选择的全新安装Ucenter,经过代码排查后发现。
在全新安装Ucenter的情况下,程序里默认指定死了Ucenter的IP,代码见install\include\install_function.php。

$ucip = '127.0.0.1';