月度归档:2013年06月

记一次艰苦卓绝的Discuz x3 论坛升级过程

首先吐槽一下discuz 的官方论坛. 你要想下载到正确版本的discuz实在不容易找到. 有兴趣自己去看吧. 就是因为这个原因, 我本来想要安装x2.5版本(那时x3 还是Beta版本), 结果不小心下载成了x2.  也就是不久前, x3才发布正式版.   我最近想要安装几个插件,和皮肤, 但是打开插件中心, 发现我所有的插件都安装不了, 说我的版本不支持.

我确信是x2.5 的插件, 语言版本也没问题(我一直以为自己的论坛是x2.5), 这就奇怪了. 我也觉得discuz不会有这么明显的bug啊.网上搜了很多,都说是版本不对, 请仔细核对版本. 这问题一直困然了我很久.  当时没有紧急的需求,也就放下了.

直到今天, 我想安装插件和皮肤, 我决定把这个安装不了插件的问题搞定. 最终还是要核对版本, 我突然想到好像在别人的论坛下面看到过 类似 "x2.5" 的版权申明(就是在论坛首页的下面声明的).  再看看我自己的是 "x2", 所以我猜测可能是我的版本安装错了.  所以本地搭建php环境(wamp server), 去discuz官方仔细找到2.5的下载地址. 本地安装.  证实我的猜测是对的. 我的论坛装错了.  现在查件中心绝大部分插件和皮肤都只支持2.5和3, 所以要想装查件, 只能升级了.

我的论坛已经有很多用户和数据了, 不能重装, 现在只能选择升级了. 好吧, 要升就直接升到最新x3吧.  好在官方的升级脚本还是比较详细的, 而且我也相信discuz的实力, 官方说支持从x2直接升到x3.

为了确保万无一失, 我先把服务器上的文件和数据库都备份到本地的php环境. 在本地"预升级"一次.  按照官方给的步骤,很简单就完成了.  打开页面看了一下,也没有发现问题. 放心了. 现在可以正式升级了.

第一步: 备份服务器的所有文件 和 数据库.

按照官方的说明把文件都拷贝上去: http://www.discuz.net/thread-3265731-1-1.html

因为我是用root身份登录到vps上去的,  所以拷贝上去的文件都是属于root的,  nginx 运行所使用的"www"用户是没有权限访问的. 所以要把权限都改对了,进入网站的根目录:

chown www -R *
chgrp www -R *

 

把网站的文件都改到 用户 "www" 用户的名下.

此时可以开始升级了. 运行: http://xxxxxxxx.com/install/update.php

问题出现了, 刚才预升级的时候, 这里就可以点下一步升级了. 但是此时提示 :

"请先升级 UCenter 到 1.6.0 以上版本。如果使用为Discuz! X自带UCenter,请先下载 UCenter 1.6.0, 在 utilities 目录下找到对应的升级程序,复制或上传到 Discuz! X 的 uc_server 目录下,运行该程序进行升级"

(当时没顾上截图)

什么? ucenter版本不对?  不可能啊, 我已经预升级一次了.  不会啊.   于是把服务器上的文件和数据库都恢复到升级前的状态, 进ucenter看, 发现版本号的确是1.6.  所以没问题.

然后又重复官方的教程.

最后运行: http://xxxxxxxx.com/install/update.php.   还是出现一样的提示.

反复按照官方的教程做了三次, 到这都是这个提示, 我确信我没有哪一步做错了. 这就奇怪了.

于是打开update.php文件, 找到这个提示的位置:

1

是这里在比版本号.

上面的code是我改过的, $oldversion 这个变量是我加的, 就是想把版本号显示出来, 看看到底是多少.

重新运行:  http://xxxxxxxx.com/install/update.php.

发现显示出来的版本号是空白. 什么也没有.

继续追踪: "uc_check_version" 函数, 因为版本号是从这的出来的.

搜索到uc_client/client.php

function uc_check_version() {
	$return = uc_api_post('version', 'check', array());
	$data = uc_unserialize($return);
	return is_array($data) ? $data : $return;
}

到了这里还是看不出来.

还是把服务器恢复原样, 和本地比看有什么却别.

恢复服务器文件和数据.

问题出在ucenter, 当然打开后台ucenter看看.

赫然发现: 通信失败

2

 

我很清楚的记得,  原来这里是绿色的通信成功的.

难道是因为和ucenter的通信失败了,  才导致update.php 文件获得ucenter的版本号失败, 所以导致我升级不成功的?

想到这里, 就要跟踪为啥通信会失败了.   (百度搜ucenter 通信失败, 很多人都说是论坛和ucenter之间的设置不一致导致的. 我也反复确认了很多次,设置没有问题.)

我们打开chrome的调试面板, 找到检查通信失败的地址:

点击左侧的 "应用管理", 会发现下面这一条ajax的调用:

3

把这个地址在浏览器中打开:

4

 

发现他果然返回了通信失败的字样.

从上面的url 我们依次找到: uc_server/control/admin/app.php 文件, 并定位到 onping函数:

5

图中可以看到我注掉的调试代码, 都是我自己加的,为了跟踪代码的流程. 我发现流程是 进入了 "else" 块, 然后出来之后 $status就是空白.  下面在判断如果status是1表示成功. 否则就是失败.

我在本地成功的环境下, 重现类似的场景, 发现也是进入了else块, 但是出来的时候 status是1.

那就继续追踪 test_api() 这个函数.

搜索 "test_api", 发现有两处定义, 分别在uc_client\model\misc.php  和 \uc_server\model\app.php.

第一处是空实现, 所以只能看第二处了.

	function test_api($url, $ip = '') {
		echo "in test pi".'<br>';
		$this->base->load('misc');
		if(!$ip) {
			$ip = $_ENV['misc']->get_host_by_url($url);
		}
                echo "line1:".$ip."<br>";
		if($ip < 0) {
			return FALSE;
		}
		echo "line2:".$ip."<br>";
		echo "line3:".$url."<br>";
		$ret = $_ENV['misc']->dfopen($url, 0, '', '', 1, $ip);
		echo "line4 ret value is:".$ret."<br>";
		return $ret;
	}

 

上面, 我加了一些调试代码.

发现 $ret是空白.

那就是dfopen的问题了.

搜索dfopen. 他有多处实现, 但是有两处比较可疑:

\uc_client\model\misc.php 和 \uc_server\model\misc.php

我现在两个实现的入口处都设置echo语句. 发现时走的第二处.

于是进一步跟踪第二处实现:

	function dfopen($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE	, $ip = '', $timeout = 15, $block = TRUE, $encodetype  = 'URLENCODE') {
		echo "server model misc dfopen:"."<br>";
		//error_log("[uc_server]\r\nurl: $url\r\npost: $post\r\n\r\n", 3, 'c:/log/php_fopen.txt');
		$return = '';
		$matches = parse_url($url);
		$host = $matches['host'];

............

		if(function_exists('fsockopen')) {
			echo "server model misc dfopen:fsockopen"."<br>";
			$fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
		} elseif (function_exists('pfsockopen')) {
			echo "server model misc dfopen:pfsockopen"."<br>";
			$fp = @pfsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
		} else {
			echo "server model misc dfopen:false"."<br>";
			$fp = false;
		}

................

 

我加了一些调试语句在里面.

当加到这里的时候,  想到,是不是 服务器的fsockopen函数被禁用了呢. 于是就没再继续加了. 赶快试. 上传文件, 刷新url.

果然输出了 "server model misc dfopen:false"

 

我擦, 原来是 fsockopen函数被禁用了啊.  赶快上传php的探针,  发现fsockopen果然被禁用了.

6

我这是才想起来,  前几天更换vps的时候, 没注意, 可能忘了打开fsockopen 函数了.

赶快去服务器:/usr/local/php/etc 中 打开php.ini  找到disable_functions这一行. 从中把fsockopen和pfsockopen都删掉.

然后重启php:  service php-fpm restart

然后刷新上面的url, 返回通信成功了.

好了,现在再回到开头.

把文件还原成升级前的样子, 再按照官方说明, 升级文件.

运行: http://xxxxxxxx.com/install/update.php

这次终于正常了, 显示准备完成,可以升级.

后面就比较顺利了. 自动升级数据库, 然后手动去吧缓存更新一下. 就好了.

就到这.

其中还省略了无数的弯路啊.

再次证明一个真理,  看似复杂的问题, 一定是由一个比较愚蠢的原因造成的.

 
byNeil
byNeil.com

原文来自 Blog by Neil, post 记一次艰苦卓绝的Discuz x3 论坛升级过程 转载请注明出处。本站保留一切权力

导入用户信息到discuz ucenter.

上一篇帖子: 直接导入帖子到Discuz 论坛数据库. 结束时说要写一篇导入用户的帖子, 一直没时间, 但是咱不能做太监,不是? 所以今天赶快补上. 在做discuz整合或者迁移是, 很多人可能遇到相同的问题, 就是用户数据怎么导入到discuz中.

discuz 的用户数据其实是存在 ucenter中的. ucenter是什么? 自己百度去. 简单的说, ucenter 就是discuz各个产品之间共享数据的媒介. 所以我们只需要导入到ucenter的表中就可以了.

同样通过上一篇文章中提到的比较方法,  我们发现用户数据时存在 pre_ucenter_members 这一张表中的.  欢迎大家交流心得, 访问我的独立博客 http://byNeil.com  .下面解释一下这个表的列的含义:

1. username:  用户名, 就是用户登录输的用户名.

2. password: 密码, 这个当然不是明文的密码, 至于怎么生成的, 后面再说. password hash = Md5(Md5(password) + salt);

3. email: 就是用户的email, 明文

4. regdate: 是一个int值, linux的时间戳,表示用户的注册时间.

5. salt: 盐.  这个比较有意思, 是为了增加用户密码的安全性的.  这个salt是一个 6位长的字符串, 它本身是注册时随机产生的.  它的作用就是用来混在密码一起产生密码的hash值的.  password hash = Md5(Md5(password) + salt);

 

有了这几列的意思, 导入就简单多了.  如果你知道原来用户的密码(不太可能, 除非是国内某著名网站明文存密码),  或者知道用户密码的 MD5值,  就可以用自己生成的salt来 为用户导入密码了.  这样用户就能用原来的密码登陆新网站了.   如果不知道, 那只有重置所有用户的密码.

具体code就不写了, 各个语言不一样, 自己琢磨.

 

 

 

 
byNeil
byNeil.com

原文来自 Blog by Neil, post 导入用户信息到discuz ucenter. 转载请注明出处。本站保留一切权力

翻译一篇很好的理解deflate算法的文章

最近做压缩算法. 用到了deflate压缩算法,  找了很多资料,  这篇文章算是讲的比较易懂的, 这篇文章不长,但却浅显易懂, 基本上涵盖了我想要知道的所有要点. 翻译出来, 留存.    可能对正在学习或者准备学习deflate算法的童鞋有所帮助.

先说一下deflate算法吧.  deflate是zip压缩文件的默认算法.   其实deflate现在不光用在zip文件中, 在7z, xz等其他的压缩文件中都用.   实际上deflate只是一种压缩数据流的算法. 任何需要流式压缩的地方都可以用.          deflate 还有微型改进版本deflate64. "微型改进型" 这个名词是我发明的, 为什么这么说, 因为改进实在太小了, 而性能提高也非常小. 以至于大名鼎鼎的开源zlib库到现在都不支持deflate64.      http://zlib.net/zlib_faq.html#faq40

开始正题, 原文地址: http://zlib.net/feldspar.html ,  我尽量按照原文直译,但是有些地方实在没必要就简单意译了. 有需要想交流的童鞋欢迎访问我的独立博客留言交流: http://byNeil.com

==========================华丽的分割线====================

Deflate 算法的介绍

作者: Antaeus Feldspar   feldspar@netcom.com

在理解deflate之前, 先理解它的两个组成算法非常重要: Huffman 编码 和 LZ77压缩

Huffman 编码

Huffman编码是一种前缀编码, 你其实知道, 但你可能不觉得. 比如打电话时, 从拨号开始, 你按下一串号码, 或许是5781112 或者别的号码. 一串号码就就能到达另一部特定的电话.  (翻译: 这个比方太罗嗦,本来不想翻译过来的.)

(这一段就不翻译了,没有实际内容.) Now suppose you're in an office setting with an internal switchboard, as many large companies do. All other phones within the bank only require five numbers to dial, instead of seven -- that's because it's expected that you'll be calling those numbers more often. You may still need to call other numbers, though -- so all of those numbers have a `9' added to the front.

...这就是前缀编码. 每一个你想要指定的元素都有一个由数字组成的代码. 由于每个代码都不可能是另外一个代码的前缀, 所以当你输入的时候,不会有歧义.

Huffman 编码是由一个特定算法生成的前缀编码. (翻译: 后面简单介绍如何生成Huffman编码, 都是教课书上的内容, 不翻译了, 自己看图, 直接跳过:) Here, instead of each code being a series of numbers between 0 and 9, each code is a series of bits, either 0 or 1. Instead of each code representing a phone, each code represents an element in a specific ``alphabet'' (such as the set of ASCII characters, which is the primary but not the only use of Huffman coding in DEFLATE).

A Huffman algorithm starts by assembling the elements of the ``alphabet,'' each one being assigned a ``weight'' -- a number that represents its relative frequency within the data to be compressed. These weights may be guessed at beforehand, or they may be measured exactly from passes through the data, or some combination of the two. In any case, the elements are selected two at a time, the elements with the lowest weights being chosen. The two elements are made to be leaf nodes of a node with two branches (I really hope you know nodes and trees...) Anyway, suppose we had a set of elements and weights that looked like this:

A 16
B 32
C 32
D 8
E 8
We would pick D and E first, and make them branches of a single node -- one being the `0' branch, and one the `1' branch.

( )
0 / \ 1
D E

...... (翻译中间省略无数废话,  都是在介绍怎么生成huffman树, 这个上过课的人都知道, 所以就不翻译了, 细节非常繁琐. 下面直接从 课本上没有的另一个算法 Lz77开始:)

LZ77 压缩
LZ77压缩算法靠查找重复的序列. 这里使用术语:"滑动窗口", 它的意思是:在任何的数据点上, 都记录了在此之前的字符. 32k的滑动窗口表示压缩器(解压器)能记录前32768个字符. 当下一个需要压缩的字符序列能够在滑动窗口中找到, 这个序列会被两个数字代替: 一个是距离,表示这个序列在窗口中的起始位置离窗口的距离, 一个是长度, 字符串的长度.

我觉得 看 比 说 更容易. 我们看看一些高压缩比的数据:

Blah blah blah blah blah!

字符流开始于: `B,' `l,' `a,' `h,' ` ,' and `b.' , 看看接下来的5个字符:

vvvvv
Blah blah blah blah blah!
^^^^^
接下来的5个字符正好和已经在数据流中的字符串相等, 而且刚好在当前数据点的前5个字符开始. 在这个case中,我们可以在数据流中输出特殊的字符, 一个长度数字 和一个距离数字.
目前数据是:
Blah blah b
压缩后的格式是:
Blah b[D=5,L=5]
压缩还能继续增加, 尽管要利用其全部的优势需要压缩器方面的智慧. 对比这两个相同的字符串, 对比它们各自的下一个字符,都是'l', 所以,我们可以把长度更新为6, 而不仅仅是5. 如果我们继续比较,发现下一个, 下下一个, 下下下一个都是一样的. 尽管前面的字符换已经延伸覆盖到了后面的字符串(需要压缩的字符串).
最终我们发现, 有18个字符相等. 当我们解压的时候, 当读到长度和距离对时, 我们并不知道这18个字符将会是什么. 但是如果我们把已有的字符放回去, 我们就知道更多. 而这有会让更多的字符放回去, 或者知道任何长度大于距离的数据对都会不断的重复数据. 所以解压器就可以这样做.
最终我们的高压缩性的数据能压缩成如下的样子:
Blah b[D=5, L=18]!
联合起来(翻译: 指的是把huffman编码和 lz77压缩算法联合起来用):
deflate 压缩器在怎样压缩数据上有非常大的灵活性. 程序员必须处理设计聪明的算法所面临的问题以做出正确的选择. 但是压缩器的确有一些选择.
压缩器有三种压缩模型:
1. 不压缩数据, 对于已经压缩过的数据,这是一个明智的选择. 这样的数据会会稍稍增加, 但是会小于在其上再应用一种压缩算法.
2. 压缩, 先用Lz77, 然后用huffman编码. 在这个模型中压缩的树是Deflate 规范规定定义的, 所以不需要额外的空间来存储这个树.
3. 压缩, 先用LZ77, 然后用huffman编码. 压缩树是由压缩器生成的, 并与数据一起存储.

数据被分割成不同的块, 每个块使用单一的压缩模式. 如过压缩器要在这三种压缩模式中相互切换, 必须先结束当前的块, 重新开始一个新的块.

关于Lz77和huffman如何一起工作的细节需要更进一步的检查. 一旦原始数据被转换成了字符和长度距离对组成的串, 这些数据必须由huffman编码表示.
尽管这不是一个标砖术语, 把开始读数据的点叫"拨号音", 毕竟在我们的类推中, 拨号音的确是我们指定一个电话的的拨号的开始点. 所以我们把这个起始点叫"拨号音". 在起始点之后, 只可能有三种元素能出现:字符, 长度距离对, 或者是块的结束标志. 因为我们必须能够区分它是什么. 所有可能的字符(字面量), 表示可能长度的区域, 以及特殊的块结束标记都被合并到同一个字母表中. 这个字母表就是huffman树的基础. 距离不需要包含在字母表中, 因为它只能出现在长度的后面. 一点字面量被解压, 或者长度距离对解压出来, 我们就开始了另一个拨号音, 重新开始读取. 当然,如果遇到块结束标志的话, 就会开始另外一个块,或者到达了压缩数据的末尾.
长度或者距离的编码实际上可以是: 一个基地址,后面跟上额外的bits(整数的形式,表示基地址后面的偏移),

可能deflate规范中最难理解的部分就是当数据时被特殊的树压缩的时候, 树与数据一起的压缩方式.
树 通过 编码长度(codelengths)来传输, 正如前面讨论的. 代码长度被全部放在一个由0到15之间的数字组成的序列中(huffman树种,代码长度必须不大于15, 这是最复杂部分, 而不是怎样约束元素的顺序).

并不是所有的元素都必须有一个代码长度. 如果字母表的最后的那些元素代码长度都是0, 他们能并且应该被忽略. 由于每个字母表中元素的个数都会被传输, 所以这两个字母表会被链接成单一的序列.

一旦编码长度序列组合完成,  它会被一种叫做 run-length 的压缩方式压缩. 当一行中的多个元素拥有一样的代码长度(常常是0) 的时候, 特殊的符号会用来表示这个长度的元素的个数.  现在这个序列式0到18之间的数字组成的了(可能带有额外的位组成修改基地址的值).

huffman 树就是为这个0到18的字母表创建的.

huffman树需要包含在数据中, 当然.  正如其他的huffman树一样, 它会被记录代码长度的形式包含进来.   然而, 它们的顺序不是 0, 1, 2, 3, 4 ... 16, 17, 18. 而是一个特殊的顺序:16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15.  这背后的逻辑是:越靠近序列末尾的代码,越可能是长度为0. 如果它们的确是0, 就能被省略掉. 直接记录的元素的个数也会包含在数据中.

这些就是我觉得在deflate 规范中的难点. 多花时间读规范,应该能帮助了解. 有任何别的问题请联系: - Antaeus Feldspar ( feldspar@netcom.com)

 

 

======================分割线 完==================

 

 

 
byNeil
byNeil.com

原文来自 Blog by Neil, post 翻译一篇很好的理解deflate算法的文章 转载请注明出处。本站保留一切权力