03/09/2010

说到WEB前端, 用户体验是足够YY的话题; 话说万丈高楼平地起, 这里实打实地先从网页基础结构说起.

HTML文档结构

从HTML的构成元素来看, 一个网页是立体的, 是由一层层的HTML元素排列叠加构成. 一个普通的HTML网页由如下3部分组成:

  • top-level - 顶级元素, 像是 html head body iframe # 作为"容器"
  • block-level - 块级元素, 像是 div p h1-h6 ul form .. # 依附body元素内, 用来"装载"呈现其子元素
  • inline-level - 内联元素, 比如 span img br li ol .. # 不独立存在, 依赖于块级元素

如果从 MVC 的概念来看, 对应关系如下:

M (Model, 数据层) - XHTML(application/xhtml+xml, xml是一种数据标记语言,可以用来存储传输数据, xhtml有其血统; 而且最新的HTML5已经支持离线本地存储)
V (View, 表现成) - CSS
C (Controller, 控制层) - JavaScript

(注: MVC 为 Model-View-Controller 的缩写, 是目前最为流行的WEB开发框架模式. M一般指数据层负责数据存取; V为表现交互层, 即供用户交互的前端网页; C为业务逻辑控制层, 负责数据层M与表现层V交互的具体逻辑)

虽然将网页这么解剖一番后, 但前面所说的网页都还是静态的, 也就是她目前只能用来呈现数据, 而且裸奔, 没有穿上衣服(css), 也不能谈笑自如(js), 怎么办呢?

显然不能看着办, 那样多尴尬, 这个时候就要给她穿上衣服才能无伤大雅. 也就是说要给网页加上样式, 下面就要说到CSS了.

问题是网页怎么才能穿上CSS这身魔衣呢? 方法很多, 诸如内嵌在网页中或者直接写在html元素的style属性里边又或者用外链的形式, 不过前两者都不够DRY(Don't Repeat Yourself ), 而且浪费网络流量; 最合适的应该是外链样式表, 这样一次编写, 到处可用. 当然, 个别特殊的场景, 也许需要单独hack具体元素的style属性.

似乎网页穿上CSS很简单, 但是如何才能根据网页的身材量体裁衣般地为其缝制这套CSS花衣裳呢? 或者说CSS如何与HTML沟通?

答案是使用 CSS selector (CSS选择器).

HTML与CSS Selectors

网页不光是立体的, 而且还是以HTML元素树结构的形式构成. 由于构成网页的HTML文档是树状结构, 那么用CSS选择器选择HTML具体的节点那就很简单了. 关于CSS选择器, 详见这篇指引文档: CSS选择器简明指南

有了CSS选择器, 就能为网页点缀并锦上添花. 什么排版布局呀, 色彩搭配呀, 文字图片啥的都生动起来了. 整一个华丽丽的静态网页, 如诗如画, 栩栩如生.

光穿得漂亮还不够, 不流露表情不作声不够动人. 面对成千上万的用户, 总得面带微笑流露喜气才行啦! 所以, 接下来的问题是如何让网页动起来呢?

答案是使用客户端脚本语言JavaScript. JavaScript通过操作DOM可以让网页活灵活现. 所以, DOM 是HTML与JavaScript建立关联的一套接口, 类似于CSS中的Selectors.

HTML与JavaScript DOM

究竟什么是DOM?

简单地说, DOM(文档对象模型)是HTML和XML的应用程序接口(API). DOM把整个页面规划成由节点层级构成的文档. 在JavaScript中, 用DOM API可以轻松地删除, 添加和替换节点等操作. DOM在JavaScript与HTML交互中用途甚广, 譬如目前互联网最为流行的Ajax特效就是通过XmlHttpRequest对象来向服务器发起异步请求, 从服务器获得数据后, 然后用JavaScript来操作DOM从而更新页面.

一个网页要楚楚动人, 需要用CSS量体裁衣, 施妆渲染; 同时, 一个网页要生动活泼, 需要用JavaScript点化开来, 构建交互. 当JS遇上CSS, 不约而同地邂逅在某个网页落; 至此, 她便风韵百媚般地展露在公众面前, 随即赶上用户体验这番潮流.

语义WEB与SEO

说到语义WEB, 得先问个不太正经的问题: 穿衣服与不穿衣服的MM谁更好看?

答案并不唯一, 因为评判的标准在于受众. 有的MM不穿衣服更让人青睐, 这时取决的标准在于MM身材与气质. 如同选模特, 要看内在气质与身材体韵; 语义化的WEB网页即如此, 跟那套CSS花衣裳没关系, 也不关JavaScript这根魔棒什么事儿; 因为语义化的受众不是浏览器用户, 而是针对讲究SEO规则的Robots.

语义化Web其实叫Semantics Web, 是用来做SEO(搜索引擎优化)的; 个人感觉中文翻译并不太直观, 不过久而久之也就习惯了, 语义是对robots而言, 受众是搜索引擎.

伴随着Web2.0的推波助澜, SEO大概也升级到2.0了, 搜索引擎一直以来都非常重视对文本内容的检索, 现今更偏向读取结构化的文档以便收录索引. 所以, 如何让你的网页让搜索引擎理解这门艺术便开始流行开来; 这也就是我们接下来要谈的语义化, 即 Semantics.

随着搜索引擎在互联网中的地位不断提升, 以及XML的使用频率和重要性增加, 它们作为Robots 2.0, 只读HTML/XHTML/XML源代码, 而对CSS忽略不见, 也就是说它们实际上更在乎网页内容的实际含义, 而不是你的网页好看与否. 也就是说让你的网页符合语义/SEO规范, 就是让这些Robots更好的读懂你的网页, 提高你的网站在搜索结果中的排名, 籍此获得更大的利益, 这只无形的手决定了现今网站对Semantics的重视, 甚至超过网页是否通过W3C的检测的重要性. 我个人认为的Semantics的含义就是:

将CSS去除, 你的网页表现依然规范, 易懂.

所以, 开门的那个问题我的答案是: 如果不穿衣服的MM很好看, 那么她穿上衣服后怎么都更好看.

似乎这个问题也该升级2.0了: 一个网站到底是讲究用户体验还是语义化更为合适呢? UED or Semantics, is a question. 想必, 此时的您已经有了合适的答案:

Both of them.

号外, 可能您已听说过CSS裸奔节. CSS Naked Day - CSS裸奔节

似乎裸奔也是一种文化, CSS裸奔节的目的在于推动Web标准, 追求简洁至上, 和CSS Zen Garden - CSS禅意花园 表达的理念颇为相似. 使用正确的(x)html, 语义标记, 构建良好的层次结构; 把页面外表设计抛弃, 直接展示body的气质骨感美.

这么一说, 语义WEB其实属于SEO范畴了, 事实本质就是如此. 要设计语义化的网页, 遵照SEO之网页设计技巧即可, 比如不要滥用div等结构元素, Robots并不太亲近此类元素; 重视使用h1/h2/p/ul/li/blockquote/table等语义元素的使用和规范. 严格意义上说单个网页, 只允许出现一次h1, 就是你的网页title, h1在Robots中的地位仅次于.当然, SEO之网页优化的技巧还有很多, 网上也有不少参考资料, 有兴趣的朋友可以自行搜索查阅.

WEB前端优化

做足基本功, 顺便也谈谈WEB前端优化技巧. web前端优化无非是针对以下就几点:

  1. 提供更快的网页加载速度 (js/css与html分离)
  2. 减轻服务器压力 (减少http请求数, 使用cdn)
  3. 节约带宽 (压缩输出)
  4. 尽可能地缓存

这些经验基本都是老生长谈, 这里稍微罗列一二.

优化技巧:

提供更快的网页加载速度

  • 更少地使用标签, HTML文档是自上而下,自外而内进行渲染的; HTML 标签使用得越少越好.仅使用必要的标签, 不必要的都删掉, 这样做的好处在于能够让网页文件保持小巧, 也可让浏览器快速完成对文档的解析, 即得到更快的呈现速度.

  • 外链js/css文件. 通常, 将js和css代码从html中分离用外链的方式便可以达到此效果. 另外, 加之浏览器能够缓存这些脚本或样式文件, 那么, 这些共用文件将不必多次下载.

  • 图像替换. 对于网页设计中图标应用, 用css的background属性代替html中的img.

  • 总之这里的规约就是DRY.

  • 将css stylesheets 外链到HTML中的Head部分; 将javascripts 外链至HTML Body底部; 都可以提供更快的页面渲染速度.

  • 避免使用CSS条件表达式. 减轻客户端浏览器压力, 避免页面渲染延迟.

  • 避免使用大图片, 设计更小的或改良图片

  • 将 favicon.ico 图标的大小减少到1K以下级别, 并让其可以在浏览器端缓存.

  • 另外针对移动设备, 有必要在后端脚本中检查设备的user-agent来判别用户使用的浏览器和设备, 为对移动设备设计相应的更小巧的模板.

减轻服务器压力

减少 http 请求数, 合并同类项.

  • 在生成环境中, 可以考虑将所有的Js/CSS文件分别整合进一个包里边进行外链.
  • 另外, 可以使用 CSS Sprites 这种插件将网页中的所有背景图片整合成一张背景图片.
  • 避免网页跳转

使用CDN(Content Delivery Network, 内容分发网络)

  • 将静态内容如js/css/images移至不同的子域, 配置单独的静态服务器, 从而将静态资源与动态请求分离, 可以有效地提高网站服务器的负载能力, 减轻网站服务器的压力.

节约带宽 (压缩输出)

  • Gzip压缩输出, 将网页文件大小保存在25K以下有着更快的加载速度, 也更利于搜索引擎索引收录. Gzip压缩输出可以有效地通过压缩网页文本文件为网页减肥.

  • 打包并最小化JavaScript和CSS, 在网站生产运行环境中, 将css/js 分别整合进一个单独的文件里边, 并去处里边的空格和换行.

尽可能地缓存

  • 为网站服务器配置 DNS 查询缓存, 提高请求中毫秒级的响应速度.
  • 为网页或其中的文件设置 Expires 或者 Cache-Control 头信息
  • 为网页文件配置 ETags, 使用ETags减少Web应用带宽和负载
  • 让Ajax可缓存

更为详尽的WEB前端优化技巧参阅这篇文档: Best Practices for Speeding Up Your Web Site

优化工具

  • YSlow - Yahoo!开发团队出的这款YSlow FireFox扩展组件很好用, 可以有效地帮忙分析网页性能以帮助优化.
  • CSS Sprites - 将网页中所有的背景图片整合成一张背景图片, 然后使用 background-position 复位.
  • asset_packager - Ruby On Rails 插件工具, js/css 打包压缩利器.

另外, FireFox 算是一个Web前端开发利器, 有很多好用的扩展组件可以助于Web前端开发. 我目前用的几个常用扩展如下:

  • Web Developer - 直接查看网页css代码功能, 页面信息的显示, 以及验证css和Html的功能非常实用
  • ColorZilla - 方便提取网页中任何元素的颜色
  • Html Validator - W3C验证检查工具. 可以查看网页中存在的不能通过验证的语句数目, 位置以及修改建议
  • FireBug + Yslow - 网页即时调试, 优化
  • Phoenix - HTML/JS/CSS 现场编辑,格式化查阅
Mar-9 4:28  Permalink to WEB前端那些事儿 

03/08/2010

以下为中文简版, 原文档详见: http://www.w3.org/TR/CSS2/selector.html

先来说说CSS最基本的选择器. 也叫简单选择器(simple selector), 分为以下几类:

  • 类别选择器(type selector) - 即文档中标签的名字, 比如段落标记p
  • 类选择器(class selector) - 根据元素的 class名来选择, 多选
  • id选择器(id selector) - 根据元素的 id 名来选择, 唯一
  • 属性选择器(attribute selector) - 根据元素的属性名及其值来选择
  • 伪类(pseudoclass) - 根据元素的位置来选择

简单选择器

分为以下几类

类别选择器(type selector)

p # 选中所有段落

如果省略类型名称, 网页中所有的节点都会被选中.

可以用类选择器, id 选择器, 属性选择器或伪类对类别选择器加以修饰, 每重修饰都可以削减被选中的节点数量.

类选择器(class selector)

p.some-calss # 选中class="some-class"的所有段落

id选择器(id selector)

p#some-id    # 选中id=''some-id"的段落

属性选择器(attribute selector)

p[name]          # 选中所有具备name属性的段落  
p[name=value]    #选中所有name=value的段落  
p[name^=string]  # ... name=value, value以'string'开头 ...  
p[name$=string]  # ... name=value, value以'string'结尾 ...  
p[name*=string]  # ... name=value, value必须包含'string' ...  
p[name~=string] # ... name=value,value必须包含完整的'string'这个词(前后以空格分隔), ...  
p[name|=string]  # ... name=value, value以'string'开头, 并且随后是空格 ...

类选择器和id选择器实际上是属性选择器中class=和id=的简写形式.

p.some-class # 等效于 p[class=some-class]  
p#some-id    # 等效于 p[id=some-id]

一个完整的选择器称为选择器链(selector chain), 由一个或多个简单选择器组成. 这样就可以描述元素之间的关系, 并且顺藤摸瓜式地选中指定元素(集).

选择器链(selector chain)

sel_1 sel_2s

所有 sel_2s 都以 sel_1 为祖先. (各个选择器之间以空格分隔)

sel_1 > sel_2s

所有sel_2 都以 sel_1 为父节点.因此:
table td # 匹配所有table元素内部的td元素
table > td # 在结构良好的HTML中不会匹配到任何元素, 因为td的父节点应该是tr

sel_1 + sel_2s

选中所有紧跟在sel_1后面的sel_2. "紧跟"表示这两个选择器描述的节点互为兄弟, 而非父子.
td.price + td.total # 选中所有紧跟在 <td class="price"> 后面, 并且class="total"的td节点

sel_1 ~ sel_2s

选中所有紧跟在sel_1后面的sel_2.
div#title ~ p # 选中所有紧跟在 <div id="title"> 后面的p元素

sel_1, sel_2s

选中所有被sel_1或者sel_2匹配到的元素.
p.warn, p.error # 选中所有class为warn或者error的段落

伪类(pseudoclass)

一般而言, 伪类让我们可以根据元素的位置来选中元素(不过也有例外情况). 所有伪类都以分号开头.

:root

只选中根元素. 有时可以用于测试XML应答。
order:root # 只有当应答的根节点是 <order> 时才返回该节点

sel:empty

只有当 sel 没有子节点或文本内容时才会选中.
div#error:empty # 选中内容为空的 <div id="error"> 节点

sel_1 sel_2:only-child

选中的节点必须是 sel_1 唯一的子节点。
dev :only-child # 选中所有只有一个子节点的div节点的子节点

sel_1 sel_2:first-child 选中所有的sel_2节点, 且它们必须是 sel_1 节点的第一个子节点. table tr:first-child # 选中每张表格的第一行

sel_1 sel_2:last-child

选中所有的sel_2节点,他们必须是sel_1节点的最后一个子节点.
table tr:last-child # 选择每张表格的最后一行

sel_1 sel_2:nth-child(n)

选中所有 sel_2 节点, 它们必须是 sel_1 节点的第n个子节点, n从1开始计数.
table tr:nth-child(2) # 选中每张表格的第2行 div p:nth-child(2) # 选中每个div元素的第二个且必须是p的元素

sel_1 sel_2:nth-last-child(n)

选中所有的sel_2节点, 且它们必须是sel_1节点的第n个子节点, n从最后一个子节点开始计数.
table tr:nth-last-child(2) 每张表格的倒数第2行

sel_1 sel_2:only-of-type

选中所有的 sel_2 节点, 它们必须是 sel_1节点仅有的子元素. (也就是说, sel_1节点可以有多个子节点, 但只能有一个类别为sel_2的子节点.)
div p:only-of-type # 选中所有"只包含一个段落"的div中包含的段落

sel_1 sel_2:first-of-type

选中父节点为 sel_1 节点的 sel_2 节点中的第一个.
div.warn p:first-of-type # <div class="warn">中的第一个段落

sel_1 sel_2:last-of-type

选中父节点为 sel_1 节点的 sel_2 节点中的最后一个.
div.warn p:first-of-type # <div class="warn">中的最后一个段落

sel_1 sel_2:nth-of-type(n)

选中所有的sel_2节点, 它们必须是 sel_1 节点的第n个子节点, n只对 sel_2 匹配到的元素计数.
div p:nth-of-type(2) # 每个div中的第二个段落

sel_1 sel_2:nth-last-of-type(n)

选中所有的 sel_2 节点, 它们必须是 sel_1 节点的第n个子节点, n只对 sel_2 匹配到的元素计数, 并且从最后一个元素开始倒计数.
div p:nth-last-of-type(2) # 每个div中的倒数第二个段落
nth-xxx 选择器接受的数值参数可以为以下几种形式:

d(a number)

对 d 节点计数

an+d(nodes from groups)

将子节点分成每a个一组, 然后从每个组中选择第n个节点.
div#story p:nth-child(3n+1) # 所有id="story"的第三个段落.

-an+d(nodes from groups)

将子节点分成每a个一组, 然后选择1到d组的第一个节点.
div#story p:nth-child(-n+2) # 前两个段落

odd(odd-numbered nodes)
even(even-numbered nodes)

选择基数或者偶数位置的子节点.

div#story p:nth-child(odd)  # 段落1,3,5,...
div#story p:nth-child(even) # 段落2,4,6,...

:not(sel)

选中所有不被sel选中的节点.
div :not(p) # 所有div中非段落的节点

Mar-8 18:36  Permalink to CSS选择器简明指南 

02/22/2010

查看系统当前的初始化服务

$ ls /etc/init.d/
$ ls /etc/rc?.d

增加或删除开机启动项

$ sudo update-rc.d php-fpm defaults  # 新增系统启动项,开机即运行php-fpm服务
$ sudo update-rc.d -f apache2 remove # 将apache2从启动项里删除

详细用法可查阅帮助信息

update-rc.d --help

update-rc.d -f <service> remove                 # 删除开机启动项服务
update-rc.d <service> start <order> <runlevels> # 新增系统启动项服务
update-rc.d <service> stop <order> <runlevels>  # 停用某系统启动项

要是删除不想要的软件包

apt-get [--purge] remove PackageName
$ sudo apt-get --purge remove openoffice.org* # 删除 openoffice.org 组件(注意通配符星号“*”)。
Feb-22 12:57  Permalink to Ubuntu/Debian管理系统启动项 

01/30/2010

看了Apple iPad 的宣传片,很是心动。感觉 Apple 就像网游厂商中的暴雪,只出精品,且经久不衰。然而,比暴雪更胜一筹的是,苹果每发布一款新产品,都会带来行业的革新并引发新潮。虽然苹果一直在被模仿,但却从未被超越过,经得起时间的考验,此乃大智慧也!当然,苹果的创新精神却从来都没有停留过,依然续写着她那神话般的传奇。这不,继 iPod,iPhone 红火一番之后,苹果于2010新年发布了 iPad 。

iPad 是啥,超大号iPhone?Not yet, 至少暂时还不能打电话呢,硬件上也有差距。过些时日说不定就可以啦,什么VOIP呀,Google Voice,Skype 啥的,装几个app不就好啦,甚至有着天然的 face to face 优势。

说到face to face,iPad 绝不亚于 iPhone 。9.7英寸多点触控屏可不是拿来盖的,偶尔当照照镜子说不定还行。

超大屏,多点触摸,上网聊天,浏览网页和收发email,听音乐,看视频,玩游戏,阅读电子书;不仅仅是这些,还可以像 iPhone 一样从 App Store 添加更多功能。功能如此强大的 iPad ,只是一块比A4纸还小得多的多点触摸屏而已,这就是苹果刚出的平板电脑。比 iPhone 大,比笔记本更小巧,超便携,超精致,话说一般的电脑能有这么轻便精致吗?小黑X200?易PC?上网本?Not yet..

也许你应该看看iPad的几张靓图;要是觉得还不够YY,看看iPad的资料片吧,上面所说的功能都有直观演示。

有此 iPad,上网本就不考虑了。如果你不太追求电子墨水的纸质般效果,当电子书阅读器也行。iPad 价格适中,所选所需,弹性定制即可。可是在兲朝,即便有银子也未必能够愉快顺畅地享受高科技呃。

倒是觉得 iPad 一出,必然引发平板电脑新潮,2010电脑革新平板年。上网本和电子阅读器何去何从,是转型还是做得更深入精透,有待时间来证明。

看完 iPad 的资料片演示,发现其中并无提及商务应用场景。不少国内商务人士不大习惯用苹果系统,有的朋友买了MacBook还装个XP,大部分人用的都是是青一色的IBM小黑,从轻便易用美观的角度来说,平板电脑也许还更适合走商务路线。在欧美早已是无线WIFI和3G的天下,国内3G也正慢慢形成气候,移动商务和办公自然有着潜在的迫切需求,信息科技加快了办公效率。以后开会你不用慌不择忙地驱车去会议室,随身带个平板电脑就好了,而且堵车时不耽误你时间,还忒能派上用场,会前用 iPad 照照镜子整整仪容都还来得及。姑且把这应用场景山寨地叫做“企业2.0”吧。Orz..

早在2年以前有接触过网络视频会议这玩意儿,可惜没有做下去;回过头再来看,移动视频会议系统更实在啊,触手可及,随时随地,打破时间和空间的限制,让沟通没有距离,what you say is what you see!互联网没有所谓的秘密,这不 Fuze Meeting 都跑到 Google Wave 的前面开始捣鼓了!

iPad with Fuze Meeting

有没有发现上面图中的 iPad 木有前置摄像头呢?哈哈,Android 加油吧!别再出个平板像 MID,外观上还得下足功夫,不指望能做到苹果那样精益求精。从 Google 的产品形态来看觉得Google更有做商务的天然优势,不物尽其用把价值最大化太可惜啦~

Google Wave

假以时日,Google Wave 更成熟一些,在 iPad 上耍就很爽了。倘若 Google Wave 出企业版(Enterprise Edition),整合一些商务所需的重要功能,然后很平滑地结合到 Google Apps(企业套件) 里边又何尝不可呢?

Jan-30 11:33  Permalink to iPad with web meetings 

12/31/2009

VPS 主机类型: Linode 360

可到 linode.com 购买,360MB 物理内存,16GB 磁盘存储空间,200GB 月流量(输入输出总和) ,1个独立IP,月租 $19.95 (信用卡支付)。数据中心目前选择 Fremont, CA, USA 最快,Dallas, TX, USA 其次,测速地址:http://www.linode.com/speedtest/ 。(Newark, NJ, USA已有部分IP段不幸遇难,请低调分享)

VPS 操作系统: Debian 5.0 (Lenny)

操作系统大小约 200M,比 Ubuntu 节省一半多的磁盘空间 ;还有很多其他可选的操作系统,可以根据自己的喜好和需要进行选择 。这篇文档同样适配于Ubuntu Server。

装完操作系统后,在本地用 SSH 方式登录远程主机,Windows 系统推荐使用方便小巧的 Putty (接受数据的字符集请选择 UTF-8,否则输出的非英文信息会乱码) 。这里假设VPS 的IP 地址是 12.34.56.78 。

$ ssh root@12.34.56.78 # 登入远程主机

设置源

使用apt-spy命令可以让系统自动寻找并设置速度最快的源。

$ apt-get install apt-spy # 下载安装 apt-spy 软件包
$ mv /etc/apt/sources.list /etc/apt/sources.list.bak # 备份默认的源文件
$ man apt-spy # 获取详细的使用方法
$ apt-spy update # 更新源的镜像列表文件 /var/lib/apt-spy/mirrors.txt
$ apt-spy -d stable -a North-America # 在北美地区寻找速度最快的stable版镜像,
并生成sources.list文件,也可使用 -o 参数输出到指定文件

更新系统

$ apt-get update
$ apt-get upgrade -y

安装所需软件包

$ apt-get -y install build-essential \
openssl libssl-dev openssh-server \
vim wget curl sudo libreadline5-dev \
zlib1g-dev

创建管理员帐号(非root)

由于root太出名了,可能出意外;基于安全考虑,从入口把关,在后面的SSH安全设置中会禁用root帐号登录。这里假设新建的用户名是 demo。

$ adduser demo # 创建用户名为demo的普通用户,读者朋友们操作时需将 demo 改成你想要的名字
$ vim /etc/sudoers

编辑/etc/sudoers,在文件的最末尾加上如下一行代码:

demo ALL=(ALL) ALL

上面一行设置 demo 用户的可操作范围(同root)。写入后保存并退出 (按Esc后键入 :wq 并按回车)。

生成SSH密钥对(客户端操作)

$ mkdir ~/.ssh # 创建用于存放密钥对的默认文件夹
$ ssh-keygen -t rsa # 生成SSH密钥
$ scp ~/.ssh/id_rsa.pub demo@12.34.56.78:~ # 上传SSH公钥至远程服务器

上面的命令行在当前用户的主目录中创建了一个 .ssh/ 目录,用于存放我们用ssh-keygen程序生成的密钥对。所谓“密钥对”,是指由ssh-keygen命令行产生的一份私钥(private key)和一份公钥(public key),两者构成一个密钥对。在以上示例中,私钥是~/.ssh/id_rsa ,公钥是~/.ssh/id_rsa.pub。私钥存放在本地的客户端机器中,公钥则存放在远程主机上;公钥用于服务端进行加密操作,私钥则用于客户端还原经混入公钥加密后的密文。我们把公钥public key放在远程系统指定用户的主目录中,当从本地开始进行ssh连接时,远程的sshd进程会产生一个随机字符串并用我们产生的public key进行加密,然后将加密后的密文发送给本地客户端机器,本地机器再用private key进行解密还原出这一随机字符串并发回给远程系统,如果经客户端还原后的随机字符串与原先服务端生成的字符串一致,那么远程主机则允许从此客户端登录,两台机器间便建立了连接。使用SSH PublicKey认证的好处在于无需提供远程系统的用户名和密码就能够与目标主机直连;即每当两台机器需要建立连接时,不用每次都输入远程主机的用户名和密码。

注册公钥(远程主机)

$ mkdir /home/demo/.ssh
$ mv /home/demo/id_rsa.pub /home/demo/.ssh/authorized_keys
$ chown -R demo:demo /home/demo/.ssh
$ chmod 700 /home/demo/.ssh
$ chmod 600 /home/demo/.ssh/authorized_keys

我们把在客户端生成的公钥放在要远程主机的~/.ssh/目录下并改名为authorized_keys, 并且保证文件除了属主外没有被人修改的权限。

SSH 安全设置

$ vim /etc/ssh/sshd_config # 编辑远程主机上的ssh配置文件,找到相关选项进行如下设置

Port 5120 # 将 SSH 秘道端口由默认的 22 改为 5120,可以自行更改,必须是没有被使用的端口
PermitRootLogin no # 禁用root登录
X11Forwarding no # 禁止用户运行远程主机上的X程序
UsePAM no # 关闭PAM密码认证(因为已使用PublicKey的认证方式)
UseDNS no # 禁用DNS解析(可能造成延时)
AllowUsers demo # 只允许用户demo以SSH方式登录

$ /etc/init.d/ssh restart # 重启SSH Server 以使配置生效

然后,退出当前连接;从本地客户端尝试以demo用户的身份登录该远程主机。

$ ssh demo@12.34.56.78 -p 5120 # 登录成功后继续

接下来我们可以放心地进行系统配置。

设置时区

$ sudo dpkg-reconfigure tzdata # 可选择 Asia / Shanghai, 或者使用 sudo tzselect 命令。
$ date

可以用如下命令通过网络同步系统时间(需联网,若无效可用su命令切换到root帐号操作)

$ sudo apt-get install ntp
$ sudo ntpdate ntp.ubuntu.com # 同步系统时间
$ date

系统语言设置(可选)

设置系统默认的语言环境的用处并不大,不过您可以根据自身需要,设置操作系统默认语言环境和字符集。下面的示例将操作系统的默认语言设置为简体中文,字符集为UTF-8。

$ sudo apt-get install locales # 下载安装区域语言包
$ sudo dpkg-reconfigure locales # 选择安装指定的语言包

在弹出的窗口中选择 en_GB.UTF-8 UTF-8 和 zh_CN.UTF-8 UTF-8 ,空格选中,方向键可以上下移动,tab键切换到按钮,回车确认,这将会安装选定的语言包;之后会询问设置默认的语言和字符集,按方向键选中后回车即可。

也可用如下命令行将系统默认的语言环境设置为简体中文UTF-8编码。

$ sudo locale-gen zh_CN.UTF-8
$ sudo /usr/sbin/update-locale LANG=zh_CN.UTF-8

设置主机名

$ hostname # 如果输出为空则表示尚未设定主机名称,继续按如下方式设置
$ sudo echo "your_host_name" > /etc/hostname # 将 "your_host_name" 换成你想要的名字
$ sudo hostname -F /etc/hostname # 更新主机名
$ hostname # 查看新的主机名是否生效

设置DNS

$ sudo vim /etc/resolv.conf

第一个 nameserver 后面的IP是系统解析域名的主DNS,第二个是备用DNS。下面示例将原来系统自动分配给我们的DNS注释掉,换成Google Public DNS 。只要DNS不是内网IP,您也可以用默认的DNS设置而无需进行更改。

nameserver 8.8.8.8
nameserver 8.8.4.4

绑定静态IP

$ sudo vim /etc/network/interfaces

# The loopback interface
auto lo
iface lo inet loopback

# auto eth0 #注释掉,禁用DHCP自动分配IP的方式
# iface eth0 inet dhcp # 注释掉,禁用DHCP自动分配IP的方式

# 接下来配置以太网IP地址及别名,IP/掩码/网关请自行修改。

# 开机时自动设置如下三个IP地址(这里自动载入的是eth0 及其两个别名eth0:0 eth0:1)
# auto 后面的以太网卡名称及其别名必须在下面的配置中存在。如果下面配置中删除一个IP地址这里也要做相应的变更。

auto eth0 eth0:0 eth0:1

# eth0 - 主IP地址

iface eth0 inet static
address 12.34.56.78 # IP 地址
netmask 255.255.255.0 # 子网掩码
gateway 12.34.56.1 # 网关

# eth0:0 - 绑定的另外一个独立IP(公网),如果你只有一个IP地址可以去掉此部分,不过配置也需要做相应的调整。

iface eth0:0 inet static
address 34.56.78.90
netmask 255.255.255.0
gateway 34.56.78.1

# eth0:1 - 内网IP,供机房内部局域网连接使用,无需外部网关
# 指定内网IP和掩码即可

iface eth0:1 inet static
address 192.168.123.234
netmask 255.255.128.0

保存并退出 :wq

重启eth0,使IP配置生效:

$ sudo /etc/init.d/network restart

安装 Git

$ sudo apt-get install git-core

安装数据库

安装 MySQL

$ sudo apt-get -y install mysql-server libmysqlclient15-dev mysql-client

安装 SQLite

$ sudo apt-get -y install sqlite3 libsqlite3-dev

安装 PostgreSQL

$ sudo apt-get -y install postgresql

上述第三行代码安装了 MySQL/PostgreSQL/SQLite 三种RDBMS,建议您根据需要选择安装。您也可以选择下载数据库安装文件的压缩包解压后进行编译安装。

安装 Ruby

服务器跑Ruby站点推荐用REE,下载地址:http://www.rubyenterpriseedition.com/download.html

$ mkdir ~/temp && cd ~/temp
$ wget http://rubyforge.org/frs/download.php/68719/ruby-enterprise-1.8.7-2010.01.tar.gz
$ tar xzvf ruby-enterprise-*.tar.gz
$ sudo ./ruby-enterprise-1.8.7-2010.01/installer
$ export RUBY_HOME=/opt/ruby-enterprise-1.8.7-2010.01
$ sudo ln -s $RUBY_HOME/bin/ruby /usr/bin/ruby
$ echo "export RUBY_HOME=$RUBY_HOME
 export PATH=$RUBY_HOME/bin:$PATH" > ~/.profile && ~/.profile
$ ruby -v

手动安装 RubyGem

$ wget "http://production.cf.rubygems.org/rubygems/rubygems-1.3.6.tgz"
$ tar -xzvf rubygems-1.3.6.tgz
$ cd rubygems-1.3.6
$ sudo ruby setup.rb
$ sudo ln -s $RUBY_HOME/bin/gem /usr/bin/gem

安装 Passenger & Nginx

$ sudo $RUBY_HOME/bin/passenger-install-nginx-module

控制台输出信息,询问以何种方式安装Nginx,选择第一项 1. Yes: download, compile and install Nginx for me. (recommended)

然后会下载Nginx源码包并解压到 /tmp 目录中。可以用命令行 $ ls -l /tmp 查看Nginx源码包及其解压后的文件夹是否存在。

如果想在安装Nginx时一并安装其他扩展库,可以先退出交互式Shell程序,再一次运行

$ sudo $RUBY_HOME/bin/passenger-install-nginx-module

不过这次要选择第二项 2. No: I want to customize my Nginx installation. (for advanced users)

当询问 “Where is your Nginx source code located?” 时,输入 Nginx 源码所在的目录:

/tmp/nginx-0.7.64

回车确认。接着会询问附加参数,即要加载的Nginx扩展模块。比如我们要加载SSL模块,可以输入:

--with-http_ssl_module

要加载其他模块,一并输入即可。Nginx 各模块参考:http://wiki.nginx.org/NginxModules

然后继续回车确认,直至编译安装完毕。

Nginx 相关设置

# 创建虚拟用户 www-data(虽是系统用户,但-s参数已指定并无登录系统的权限)
$ sudo useradd -s /sbin/nologin -r www-data

# 将www-data demo作为用户demo的附加群组,请自行修改用户名
$ sudo usermod -a -G www-data demo

$ mkdir -p ~/web/logs && cd ~/web
$ rails example.com

# 将~/web/及其子目录中文件的属组设置为 www-data
$ sudo chgrp -R www-data ~/web

# ~/web/及其子目录中文件的权限设置
$ sudo chmod -R 2750 ~/web

$ sudo su

# 用于存放可用站点的 vhosts
$ mkdir /opt/nginx/sites-available

# 用于存放可运行站点的快捷方式,软链接指向/opt/nginx/sites-available中的各 vhost 文件
$ mkdir /opt/nginx/sites-enabled

$ cd /home/demo/temp

设置 vhost (示例)

$ git clone git://gist.github.com/266085.git gist-266085
$ mv gist-266085/example.com.conf /opt/nginx/sites-available/
# 根据实际情况进行修改,比如WEB根目录
$ vim /opt/nginx/sites-available/example.com.conf
$ ln -s /opt/nginx/sites-available/example.com.conf /opt/nginx/sites-enabled
$ rm -rf gist-266085/

更新 Nginx 配置文件

$ mv /opt/nginx/conf/nginx.conf /opt/nginx/conf/nginx.conf.bak
$ git clone git://gist.github.com/265360.git gist-265360
$ mv gist-265360/nginx.conf /opt/nginx/conf/
$ rm -rf gist-265360/

Nginx命令行脚本

$ git clone git://github.com/jnstq/rails-nginx-passenger-ubuntu.git
$ mv rails-nginx-passenger-ubuntu/nginx/nginx /etc/init.d/nginx
$ chown root:root /etc/init.d/nginx
$ /etc/init.d/nginx start # stop | restart | force-reload | status | configtest | terminate
$ /usr/sbin/update-rc.d -f nginx defaults
$ rm -rf rails-nginx-passenger-ubuntu
$ exit

此时,打开浏览器输入 http://example.com 应该能看到 Rails app 的默认页面。

编译安装 ImageMagick

$ sudo apt-get remove imagemagick

$ sudo apt-get -y install libperl-dev gcc libjpeg62-dev libbz2-dev \
libtiff4-dev libwmf-dev libz-dev libpng12-dev libx11-dev \
libxt-dev libxext-dev libxml2-dev libfreetype6-dev liblcms1-dev \
libexif-dev perl libjasper-dev libltdl3-dev graphviz gs-gpl pkg-config

$ cd ~/temp
$ wget ftp://ftp.imagemagick.org/pub/ImageMagick/ImageMagick.tar.gz
$ tar xvfz ImageMagick.tar.gz
$ cd ImageMagick-*
$ ./configure && make
$ sudo make install
$ sudo ldconfig
$ rm -rf ~/tmp/ImageMagick-*

安装 PHP 和 FPM (PHP FastCGI 进程管理器)

先安装依赖包 libxml 和 libevent

$ sudo apt-get install -y libxml2-dev libevent-dev

libevent 主页:http://www.monkey.org/~provos/libevent/

$ export LE_VER=1.4.13-stable
$ cd ~/temp
# http://www.monkey.org/~provos/libevent-1.4.13-stable.tar.gz
$ wget "http://www.monkey.org/~provos/libevent-$LE_VER.tar.gz"
$ tar -zxvf "libevent-$LE_VER.tar.gz"
$ cd "libevent-$LE_VER"
$ ./configure && make
$ sudo make install
$ rm -rf "libevent-$LE_VER"

下载 FPM 并生成补丁

$ export PHP_VER=5.3.1
$ export FPM_VER=0.6
$ cd ~/temp
$ wget "http://launchpad.net/php-fpm/master/$FPM_VER/+download/php-fpm-$FPM_VER~$PHP_VER.tar.gz"
$ tar -zxvf "php-fpm-$FPM_VER~$PHP_VER.tar.gz"
$ "php-fpm-$FPM_VER-$PHP_VER/generate-fpm-patch"

下载PHP源码包并解压

$ wget "http://cn.php.net/get/php-$PHP_VER.tar.gz/from/cn2.php.net/mirror"
$ tar xvfz "php-$PHP_VER.tar.gz"
$ cd "php-$PHP_VER"

安装编译过程中需要的工具和库

$ sudo apt-get install -y autoconf autoconf2.13 mcrypt libmcrypt-dev libmcrypt4

打上FPM补丁然后编译安装PHP

$ patch -p1 < ../fpm.patch
$ ./buildconf --force
$ mkdir fpm-build && cd fpm-build
$ ../configure --with-fpm \
$ --with-config-file-path=/usr/local/lib/php.ini \
$ --with-libevent \
$ --with-fpm-user=www-data \
$ --with-fpm-group=www-data \
$ --with-mysql \
$ --with-zlib --with-gd \
$ --enable-mbstring \
$ --with-mcrypt && make
$ sudo make install

添加系统启动项

$ sudo update-rc.d php-fpm defaults
$ sudo invoke-rc.d php-fpm start

清理无用文件

$ cd ~/temp
$ rm -rf "php-$PHP_VER"
$ rm -rf "php-fpm-$FPM_VER-$PHP_VER"
$ rm fpm.patch

PHP 安全设置

$ vim /usr/local/lib/php.ini

修改php.ini,设置如下:

safe_mode = On
display_errors = Off
allow_url_fopen = Off
expose_php = Off
disable_functions = phpinfo, get_cfg_var,passthru,exec,system, \
popen,chroot,escapeshellcmd,escapeshellarg,shell_exec, \
proc_open,proc_get_status,ini_restore

重启 PHP FastCGI 进程

$ sudo /etc/init.d/php-fpm restart

安装 phpMyAdmin

$ export PMA_VER=3.2.5
$ cd ~/temp
$ wget "http://downloads.sourceforge.net/project/phpmyadmin/
  phpMyAdmin/$PMA_VER/phpMyAdmin-$PMA_VER-all-languages.tar.gz"
$ tar xzvf phpMyAdmin-$PMA_VER-all-languages.tar.gz
$ mv phpMyAdmin-$PMA_VER-all-languages/ ~/web/pma
$ sudo chgrp -R www-data ~/web/pma
$ cp ~/web/pma/config.sample.inc.php ~/web/pma/config.inc.php
$ vim ~/web/pma/config.inc.php # 编辑 $cfg['blowfish_secret'] = ' '; 后保存退出 ( :wq )
$ rm ~/web/pma/phpinfo.php

vhost 映射

$ cd ~/temp
$ git clone git://gist.github.com/266647.git gist-266647
$ sudo mv gist-266647/pma.conf /opt/nginx/sites-available/
$ sudo vim /opt/nginx/sites-available/pma.conf # 根据实际情况进行修改,比如WEB根目录
$ sudo ln -s /opt/nginx/sites-available/pma.conf /opt/nginx/sites-enabled
$ rm -rf gist-266647/

$ sudo /etc/init.d/nginx restart

此时,打开浏览器输入 http://pma.example.com:3307 应该能显示 phpMyAdmin 程序的登录界面。

未完待续…

祝大家新年愉快!Happy new year, every one! ;-)

Jan-28 21:32  Permalink to Rails & PHP Linux VPS速配指南 

09/01/2009

假设有个用户,如果没有上传头像,那么系统应该自动给他一张默认头像,在 Paperclip 里边这非常好办,只需在 has_attached_file 方法中指定 :default_url 参数就行了。示例代码如下,

class Avatar < ActiveRecord::Base
  self.table_name = 'users'

  has_attached_file :avatar,
    :styles => { :medium => "160x160#",:thumb => "48x48#" },
    :path => ":rails_root/public/avatars/:id/:style_:basename.:extension",
    :url => "/avatars/:id/:style_:basename.:extension",
    :default_url => "/images/defaults/avatar_:style_missing.jpg"

  validates_attachment_content_type :avatar, 
    :content_type => ['image/jpeg', 'image/png', 'image/pjpeg', 'image/x-png']

end

上面的代码中缩略了两种规格的小图,分别是 :original 和 :thumb。:default_url => “/images/defaults/avatar_:style_missing.jpg” 这行代码就是说:如果用户没有上传头像,就用默认的 /images/defaults/avatar_original_missing.jpg 或者 /images/defaults/avatar_thumb_missing.jpg 作为用户的默认头像。具体使用哪一张要看场合,比如你调用 @user.avatar.url ,那么当用户没有设置头像的情况下,系统会自动调用 /images/defaults/avatar_original_missing.jpg 这张图片(:default_style 缺省值是 :original);如果你调用@user.avatar.url(:thumb),当用户未设置头像的情况下,系统会调用 /images/defaults/avatar_thumb_missing.jpg 这张图片作为默认头像。前提条件下是 /images/defaults/avatar_original_missing.jpg 和 /images/defaults/avatar_thumb_missing.jpg 这两张图片都必须存在。

不过Paperclip目前有个bug,即便如上所述,Paperclip也无法显示默认图片。需要轻微地hack下源码,解决方案如下:

打开 :rails_root/vendor/paperclip/lib/paperclip/attachment.rb

找到代码中的url方法的如下这行代码(Paperclip::Attachment#line 103):

url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)

修改如下,

url = original_filename.empty? ? interpolate(@default_url, style) : interpolate(@url, style)

然后重启Web Server,问题解决。

Jan-28 20:53  Permalink to Paperclip读取默认图片 

09/01/2009

使用 Passenger 作为Web Server,在用Paperclip 处理文件上传的时候会抛出错误,像是“stream.3916.0 is not recognized by the ‘identify’ command” 这种。解决方案是手工指定 ImageMagick 安装后convert 命令所在的路径。

在Rails app配置文件里边加上这句就行:

Paperclip.options[:command_path] = '/usr/local/bin' # or '/path/to/imagemagick/bin’

如果你的系统用的是Ubuntu Server,又是编译安装的ImageMagick,而用了上面的方法后问题还存在,出现下面这种错误:

convert: error while loading shared libraries: libMagickCore.so.2: cannot open shared object file: No such file or directory

此时在终端里边运行命令 sudo ldconfig 就OK啦!

Jan-28 20:28  Permalink to 解决用Paperclip上传文件时出现的问题 

08/29/2009

Paperclip 是一款处理文件上传的Rails 插件,结合 ImageMagick 使用还可以实现强大的图像处理功能,在 Rails 应用中常用于处理图片上传和各种尺寸规格的缩略图。不过,目前thoughtbot团队开放的源码中尚未支持远程文件处理。

Google 一番后,发现已经有先行者hacked了Paperclip的这一缺陷。详见 http://github.com/chris/paperclip_url_support

以缩略远程图片为例,我们需要读取一个远程网址的图片作为临时文件, paperclip_url_support 这个脚本的作用就是将URL读取成本地的临时文件,然后交由Paperclip 进行相应的处理。

假设你的Rails app已经装上了Paperclip插件,并和我有同样的需求(缩略远程图片)。那么先将源码下载到本地:

git clone git://github.com/chris/paperclip_url_support.git

然后将 paperclip_url_support/lib/URLTempfile.rb 脚本拷贝至你Rails应用的 /lib 目录下。

接着在某一 Controller 里边 require 就可以用了。

为了便于读者朋友们理解,举个例子:

在 app/models/share_photo.rb 中,

class SharePhoto < ActiveRecord::Base

  has_one :share, :as => :share_source

  validates_format_of :url, 
    :with => /^(http|https).+\.(gif|jpg|bmp|png|jpeg)$/i,
    :message => "必须以http://开头,且后缀名只能是以 .gif .jpg .bmp .png .jpeg 结尾!"

  has_attached_file :image,
    :styles => { :original => "122x88#" },
    :path => ":rails_root/public/uploads/share_images/:id/:style_:basename.:extension",
    :url => "/uploads/share_images/:id/:style_:basename.:extension"

end

其中 has_attached_file 是 Paperclip 插件提供的方法,用来给model设置一个图像属性(示例中的属性是:image)。需要注意的是,:styles => { :original => “122×88#” } 这行代码有个小技巧。在 :styles 参数中,我们显示地设置了 :original 的宽和高分别为 122px 和 88px,由于是缩略远程图片,所以没必要将原图存到硬盘里,而只要存缩略图就行了;这里的:original意思就是说用处理后的122×88像素的缩略图覆盖原图,因为在:styles里边如果不显示地声明:original,那么Paperclip会自动存一份 :original 原图(即将原图保存在屋里硬盘上)。其中“#” 是图片缩略规则,意思是将图片等比缩放到合适的尺寸后并自动裁剪为122×88像素的矩形。

model中的图像处理可以看到全都由Paperclip处理。下面来讲讲controller应该怎么读取远程图片。

app/controllers/user/shares_controller.rb

require 'URLTempfile'

class User::SharesController < ApplicationController

  before_filter :login_required, :only => [:new, :create]

  def new
    @share = Share.new
  end

  def create
    @share_photo = SharePhoto.new(params[:share])
    @share_photo.image = URLTempfile.new(params[:share][:url])
    # ...
  end
  # ...
end

首先是引入 rails_app/lib/URLTempfile.rb 这个脚本(第1行),然后在 create 方法中设置了@share_photo实例的image 属性;你确实没看错,URLTempfile.new 方法中的参数 params[:share][:url] 确实是一个远程图片的网址。@share_photo.image = URLTempfile.new(params[:share][:url]) 这行代码就是说读取远程图片为临时文件并作为@share_photo实例的图像属性image 。当 @share_photo.save 的时候,SharePhoto 这个 model 首先会验证图片地址和格式是否有效,然后 has_attached_file 方法会用上Paperclip插件来完成这个临时图片的相关处理。

整个过程就是这样子,paperclip_url_support 负责读取远程文件为临时文件流,然后Paperclip负责处理这个临时文件,很简单,对不?

题外话,解读 :original => “122×88#” 中的这种 “#” 格式符。“#” 叫做 geometry rules,ImageMagick 中的 geometry rules 还有很多,像是”>”, “!”这样的符号。ImageMagick完整的截图规则是这样:x+-+-{%@!<>},释义详见 The geometry string 这一章节。“#” 格式符是 Paperclip 添加的,可以从模块Paperclip::Thumbnail的initialize方法中看到这句:

@crop = geometry[-1,1] == ‘#’

“#” 格式符处理缩略图很灵活,真的很好用!

Jan-28 20:12  Permalink to 用Paperclip缩略远程图片