Main

February 8, 2007

给drupal增加多用户自定义模版的功能

某些原因,要用一下drupal。drupal是套很好的cms系统,当然也可以做blog用。优点自然不用说了,功能强,灵活,缺点也很显然。眼前就有三条:

1 效率太低,四处都是查询,一个页面打开估计没30次查询也要有20次。
2 插件水平差距太大。写成什么样的都有,快的慢的,好的坏的,虽然看起来大体一致,不过差距还是很大的。
3 blog这个模块只能算入门,距离可用还差很多。关键就是没有模版。单用户的时候好办,多用户的时候,就麻烦了。

我下面做的事情就是再试图解决多人blog模版这个问题。当然了,我也懒得花太多时间在上面,所以同样犯了“插件水平差距太大”的错误,不过好处是效率还不算太白痴,界面虽然也不怎么样,但是至少能用。反正如果你正好碰上这个问题,不妨参考一下。我是在drupal.org上翻了个遍也没找到合适的东西。

一 原理

所谓自定义模版,其实就是自定义css,无论是mt还是wp的模版,其实在页面结构上是确定的。两栏还是三栏都是确定的,除了整个换模版的结构,一般不好改,当然也没必要改。我们也按照这个思路,做一个常用的两栏模版。想更复杂其实也不难,不过就要改动blog.module这个模块了,为了维护和升级方便,我不想动这个代码。

那么我们做的就是,提供用户可以输入的html代码,作为左栏,提供用户可以修改的css代码,以便修改样式。因为左栏是html代码,用户就可以随意的往里面加入div的块,于是就可以方便的增加blogroll或是日历或是什么其他需要的功能。通过样式又可以调节这些模块的风格,这就基本够用了。

那么我们要做的事情其实就是:

1 提供一个模块
2 将这个模块作为block装入blog和node页面

虽然简单,但是并不简陋。要知道,wp的著名插件sidebar的原理也是这样的,我只是让他简单一点,界面简陋一点,但是功能可不差什么了。

二 模块
drupal的模块挺让人头疼,因为方法太多,四处都是回调函数,初看比较复杂。其实简单用几个方法就能完成了。恩,我的方法应该是比较笨,不正统的,但是的确能用。也好理解,所以我暂时也没打算改。

首先要注意的是hook这个词,在真正写函数的时候,要被写成模块的名字。比如说你写test.module,那么下面所有hook_的地方,都应该被写做test_。这样就对了。

我们要知道的几个回调函数是:

hook_help($section)
这个函数是为了让你的模块能出现在admin/modules的列表中。
hook_perm()
这个是为了定义访问权限
hook_access($op, $node)
让权限在admin/access control里面可以被分配给用户。
hook_block($op='list', $delta=0)
这个是真正生成block代码的地方了。重点。

行了,知道这4个函数,我们就可以开始开发了。

我把这个模块名字叫做yxsidebar.module (银杏sidebar,哈哈)
于是我的help函数就是这个样子的:
function yxsidebar_help($section)
{
switch ($section) {
case 'admin/modules#description':
return t('blog sidebar for Drupal.');
}
}

这样,在模块列表里面就能看到叫做yxsidebar,描述是“blog sidebar for Drupal.”的模块了,可以启用或是禁止。

然后就是
function yxsidebar_perm() {
return array('view style', 'edit style');
}
我只定义了浏览和编辑两种权限。具体用途后面说。

重头戏
yxsidebar__block($op='list', $delta=0)
代码长,不一一解释,只说需要注意的。

if(arg(0)=='node')
{
//if this node type is a blog ? and who owner this blog?
$sqlstr="select uid from node where nid=".arg(1)." and type='blog'";
$node= db_fetch_object(db_query($sqlstr));
$uid=$node->uid;
}
else if(arg(0)=='blog')
{
$uid=arg(1);
}
这个目的是只让我们定义的模版在blog相关的页面才出现。
blog相关的页面特点是,路径中包含了/blog/的,和数据库中type为blog的node。
所以需要到数据库做一次SELECT,确定node是否为blog类型。这地方就犯了我最开始说的第一个毛病--查询次数太多,降低效率,但是也没别的办法啊。这种“一切皆是node”的架构最大的缺陷大概就是这里了。我相信应该还有更简单的判断方法,但是我不知道⋯⋯这也是最开始说的问题2的原因。不是所有开发者都能把那么厚的文档熟读的。很多时候,我只是为了简单的做个功能,并没打算做drupal专家。

闲话说完,继续。

if(is_numeric($uid))
{
$styles= db_fetch_object(db_query('SELECT * from blogstyle where uid='.$uid.' limit 1;'));
if($styles)
{
$block['content'] .="\r\n".''."\r\n";
$block['content'] .="\r\n

\r\n".$styles->template."\r\n
\r\n";
}
}
}
这个简单,是接着上面的,就是如果$uid是数字,也即为blog相关页面,那么就从数据库取出这个用户的模版,放在block的content里面。在我这个例子中,数据库里面存放的只是css文件的名字。如果存放css代码,原理也类似,需要的话,自己改吧。

下面的代码比较长,简化一下就是:
if (user_access('edit style'))
{
if(arg(2)=='edit')
{
}
else if(arg(2)=='save')
{


这样的。用法就是,先判断是否有edit权限(记得hook_perm()里面的定义了吗?),如果有的话,就做一些动作。我在这里定义了form,用户可以提交修改。这个手法可能也不太正宗。不过目的是达到了。

就这么简单。如果进一步发挥,可以让界面更好一点。比如定义几个块,让用户直接选择,而不是和我这样直接用textarea直接写代码。这些都是锦上添花的事情了,也不难。按照这个原理,相信很容易做出来。

完整的代码放在下面了。yxsidebar.install我懒得写了,反正就是建个库存东西,按照自己的需要来吧。没什么难的。


yxsidebar.module

January 23, 2007

从case语句的性能说起

tiny同学给我看了这么一个帖子

这个帖子的楼主认为php里面,一堆case语句的性能很低,所以给改成了用函数调用和eval的方式。

第一眼看上去,我自然就知道这种说法是错的。原因和tiny同学说的差不多。在这个问题上,我突然发现现在写php的人们貌似都不怎么懂汇编?所以才导致一些根本的观念错误。说说我的看法吧:

1 case 在汇编里面应该是jmp ,直接的地址跳转性能很高,而且case的数量多少并不影响效率,也即性能是恒定的。
2 if...else语句在汇编里面是jz和jnz,判断的数量多少会改变效率。性能不是恒定的。所以case 和if...else是不同的。
3 函数调用是低性能的,因为调用函数首先要保持现场,然后进行一次call,最后返回,这中间要进行一堆push和pop操作,比if...else的指令周期要长出来n多,自然慢的多。
4 至于eval这种高级特性,都不用说了,自然比函数调用都要慢出来无数倍。

以上说的自然是c/c++的编译结果,不过天下大道都是一样的,想必同样的cpu和操作系统上,在这种巨大的性能差距面前,恐怕什么语言都不会颠倒过来。

事实上,性能优化和代码好看,从来就不是一件事。好看的代码未必是快的,难看的代码效率未必难看。快速开发工具和脚本语言的大量普及,往往混淆了性能和扩展性这两个方向性完全不同的问题。

扩展性一定是以性能损失为代价的。而极度追求效率的代码自然不会太容易读。回头看这个帖子,似乎用eval改写过的代码,除了看起来高深点,也未必有过去的case方式容易读懂。在cpu非常慢的时候,流行一堆性能优化的手段,现在不知道还在不在用(做游戏的或许还在用),不知底细的,看到这种代码,恐怕也会大骂写的糟糕。列举几条:

1 乘方运算直接写做乘法 2^3要写成 2*2*2
2 乘法运算直接写做加法 2*3 写做 2+2+2
(我怀疑这两条现在编译器是不是可以优化了?不用这么夸张了吧?)
3 不用循环,而是直接把代码贴n遍
4 把if换成case
5 不用函数,用宏,宏在编译期间会直接展开,不会产生刚才说的push和pop

这些方法现在看来有点匪夷所思吧?貌似java/c++的教科书上都在说,宏替换是降低代码可读性和难以维护的罪魁祸首。事实上,OOP和OOD的产生也从来不是为了性能,而是为了复用和软件工程。互联网开发其实和游戏开发比较类似,需要的是较高的性能和稳定性,对复用的要求不那么高,因为这个行当做的是逻辑并不复杂的特定需求。企业开发(典型的就是银行),是因为业务逻辑和组件的高度相似性,才需要复用和高度的工程化。OOP/OOD都是这些行业的救命稻草。在互联网行业,这东西不是那么玩的转的。看到用java和.net开发互联网应用的人,我心里就暗笑——这从一开始就错了啊。

在互联网的服务器开发这个领域,单机的性能优化已经越来越不重要——除了太离谱的。并行和分散压力才是真正的解决问题之道。遗憾的是很多公司根本没意识到这点。当他们挑选开发语言和开发方法的时候,可考虑过正在做什么,未来会怎么样吗?

李小龙解释截拳道时候说过类似于“有用的武功都难看。”之类的话。可见天下道理都有那么几分相通之处。

做一件事情之前,一定要先明确要做什么,目的是什么。否则就会陷入困境。

纯粹的OOP观念上,要尽量少用if...else什么的判断。单从易读的角度来看,多态真的比if或case好读懂吗?我真的不这么认为。我更相信,除了很少的时候是为了扩展,更多的时候是根本不怎么懂技术的技术领导不懂装懂,或是程序员为了显示自己水平够高故意写成那样的。

又想起来那个初级程序员到hacker高手们写打印"hello world"程序的笑话,顶尖高手就用一句:
echo "hello world!"

大道至简,莫过于此。
-------------------------------
update:看到有匿名为哈哈的用户在我的sinablog留言,问我是否看过case编译后的汇编结果。那么就多说两句。

1 看过。我也曾怀疑过windows消息循环所用的大量case是否会导致性能下降,所以专门试验过。当然汇编的结果和编译器有一定关系。其实这里还涉及了编译器优化的问题。少量的分支case确实会被优化为jnz之类的条件分支,这时候和if..else没本质区别,但大量的case则会被优化成jmp+ 跳转地址表的方式。所以使用大量case远比大量的if..else快。现代的编译器结果都应该是这样的。

2 系统设计当然是整体的事情,尤其是互联网服务。本文中亦有提到。质疑之前还请看完原文。

November 15, 2005

unix中root登陆安全的dirty case

我相信很多人不同意这是个dirty case。不过我仍然归为此类。其实dirty case并非贬义,甚至还带有一点点褒扬的意味,一点点hack的幽默。

unix最早是可以用root进行远程登陆的。后来这招有点麻烦,因为无数人用程序来猜测密码,任你密码再强,这种愚公移山的精神也不得不怕。

假如我们不知道后面的故事,这时候,解决方案大概有几种:

1 禁止root远程登录
2 设置验证次数,超过一定次数失败就封ip
3 设置ip过滤,只允许某些ip连接
4 要求root密码不的短于10个字母
5 ...

其实以上所述都在不同的地方用到。但是我最欣赏的,还是freebsd目前采用的方法。

freebsd设置了一个叫做wheel的组,然后禁止了root的远程登录,允许用户的远程登录,但只有wheel组的用户才能在登录之后通过su命令获得root权限。

这个办法简单粗暴,但是效果出奇的好。用户几乎没有成本,只是多记了一个用户账号。好处很大,禁止了root远程登录之后,就很难猜测了。首先要猜到属于wheel组的用户,然后才能有机会猜测root密码。猜root密码大概还容易点,猜测用户就不太可能了,谁知道用户名会用什么呢?

相比起来用证书来验证登录,或是限制ip,或是检测入侵等等相对正统的思路,这个办法真的要算个dirty case。效果如何呢?
假设第一个用户的用户名长度为6个字母,6位长的密码,那么就是猜测到这个用户的机会就是1/(1!+2!+...+6!)^2,于是,有可能猜测到root密码的几率就增大了(1!+2!+...+6!)^2倍。效果很可怕吧?

这个思路的形成,我简单的推测一下,大概是这样的(未必符合历史本来面目,只是猜测):

1 root登录危险
|
|--禁止root登录 (1)
|--允许登录但有限制 (2)

这是第一个分支,选择1还是选择2?
这时候,选择1,似乎简单一点。如果选择2,我们马上面临规则问题,而规则是非常难制定的,要引入大量的逻辑判断,引入逻辑判断则不可避免的要带来bug。两者相较,选择1似乎好一点。

于是,禁止了root登录。

2 禁止root登录
|
|--改良(1)
|--创造一种新方法(2)

选择2,就要创造一种不用密码登录的方法。这个方法也是成功的。就是通过rsa密钥访问。这个方法是最正规的,不过对于用户的要求也多了不少。

通过(1)是否能够演化出折衷的方案呢?这时候应该可以比较容易想到先要允许一个较为难以猜测的账号登录,然后神不知鬼不觉得让他具有root的权限。于是,wheel组这个方案就出台了。

虽然未必真正符合历史,想必也能提供一些思路上的帮助吧。


November 14, 2005

关于dirty case 王八拳

那些精巧的,庞大的架构,固然值得我们学习。但是,工作了一段时间就能发现,最能解决问题的,往往不是那种漂亮的架构。造成这样的情况原因很多,有时间因素,有成本因素,无论因为什么,事实就是如此,有些时候,不好看的方法,硬是顶用。Diaty Case不是一个专门的词,是我生造出来的,这个词最能表现这种中用不中看的方法和思想。

江湖中传说有一种拳法,叫做王八拳。其实这也不是传说,市井打架的时候随处可见,这拳法样子难看得很,但是威力却异常强大,很多江湖高手初次碰面的时候也照样被打于马下。

我们需要的也是这种东西,相信你一定有体会,太多的时候,没有时间去完善的钻研一个体系,更不可能花上几个月把相关文档都看完。那些时候你一定在抓狂,老天,不要跟我谈什么基础,谈什么架构,我就要解决这个问题,什么方法都行,只要能立刻解决。我们要的就是这种一招制敌的效果。难看?等我有了时间再说吧。不正统,但是的确能救命。这就是软件开发中的王八拳。

当然,王八拳并非不要基础,一个人高马大,脚跟扎实的壮汉打出来,跟弱不禁风的孩子打出来,效果完全不一样。所以,有时间的话,多看书,打好基础,实在碰上搞不定的问题,那就来dirty case吧。虽然这些解决方案难看,写出来也并不丢人,一是能解决别人的问题,二是也能让别人指点得失,也是学习的方法。

于是,这份电子报就专门来讨论dirty case了。写自己的dirty case,评看到的经典dirty case,开发的乐趣,也尽在其中。

请关注:王八拳打天下:软件开发中的Dirty Case

about me:
me.jpg
CC License. Some rights reserved.
署名·非商业用途·保持一致
本站之所有未作特别说明的内容均使用 创作共用协议.
POWERED_BY_MT_3.2