[实践经验+代码]用node.js和express.js和jade搭建轻型cms系统
发布于 13 年前 作者 tianshuo 47029 次浏览 最后一次编辑是 8 年前

<h1>用node.js+express.js轻型CMS系统第一讲</h1> <br/><h2></h2> <br/><h2>先看看效果图</h2> <br/><a title=“秦运恒,专注iPad展示” href=“http://qinyh.com” target="_blank"><img src=“http://www.qinyh.com/images/snapshot.png” alt=“秦运恒官网效果图” width=“900” height=“540” /></a> <br/><h2>前言:</h2> <br/>我们主要做的是iphone/ipad程序,但关注node.js很久,因为我们多少总是要做网站,做后台。node.js就像一个非常快的ruby。对于我们而言,其实学习node.js起来还是很简单,网上资料很多,但没有看到一些比较完整的例子。<strong>所以回报一下大家,在这里把我们基本上最核心的源码分享给大家了。</strong> <br/><h2>背景介绍:</h2> <br/>我用node.js已经实现过一些小的应用利用websockets配合移动终端做网页同步显示,但是没有一个真正的项目。 <br/> <br/><strong>最近把公司官网移植到node.js上,已经上线:<a title=“秦运恒,专注iPad展示” href=“http://qinyh.com” target="_blank">qinyh.com</a>。</strong> <br/> <br/>  <br/> <br/>我们官网内容大概很少会变化,于是我们考虑把内容放在一个js文件里(chinese.json),而所有的路径放在另一个js文件里(route.json)。总共最后用了包括layout和404页面在内的八个视图。虽然不能说是一个完整的cms,但是已经实现了版面与内容的分离。也可以非常轻松地修改内容和图片。 <br/> <br/>我们还用ajax配合node-mailer实现异步的发信功能,这个以后跟大家介绍。 <br/><h2>核心技术介绍:</h2> <br/><ul> <br/> <li><strong>node.js</strong> (<a href=“http://nodejs.org/” target="_blank">nodejs.org</a>)运行快开发快的服务器框架,使用v8跑javascript</li> <br/> <li><strong>express.js</strong>(<a href=“http://expressjs.com/” target="_blank">expressjs.com</a>) node.js上目前最好的网站服务器框架,尤其特别合适做REST协议。</li> <br/> <li><strong>jade</strong> (<a href=“http://jade-lang.com/” target="_blank">jade-lang.com</a>)一个非常干净易用的html模板语言</li> <br/></ul> <br/><h2>框架结构</h2> <br/><ul> <br/> <li>代码 <br/><ul> <br/> <li>/server.js 80端口上的服务器(负责虚拟主机)</li> <br/> <li>/app.js 官网服务器</li> <br/> <li>/email.js 邮件模块(这次没有讲)</li> <br/></ul> <br/></li> <br/> <li>路由列表 <br/><ul> <br/> <li>/router.json 关联路径,视图和内容</li> <br/></ul> <br/></li> <br/> <li>视图 <br/><ul> <br/> <li>/views/layout.jade 基础模板</li> <br/> <li>/views/index.jade 首页</li> <br/> <li>/views/…  还有6个模板</li> <br/> <li>/views/404.jade 错误页面</li> <br/></ul> <br/></li> <br/> <li>内容 <br/><ul> <br/> <li>/chinese.json 存放内容和图片路径</li> <br/></ul> <br/></li> <br/> <li>其他静态资源 <br/><ul> <br/> <li>/public/js/… 存放js文件</li> <br/> <li>/public/css/… 存放css文件</li> <br/> <li>/public/images/… 存放图片</li> <br/></ul> <br/></li> <br/></ul> <br/><h2>联系我们</h2> <br/>有什么疑问可以直接留言,或者也可以联系我:hts_某种符号_qinyh.com (也欢迎node.js工程师投简历) <br/> <br/><hr /> <br/> <br/>接下来是代码 <br/><h2>/server.js</h2> <br/>我们使用forever(<a href=“https://github.com/indexzero/forever” target="_blank">https://github.com/indexzero/forever</a>)来跑node.js脚本,这样可以保证服务器不会down掉。 <br/>由于我们主机还想做别的项目所以我们要用到虚拟主机: <br/> <br/>  <br/><pre class=“brush: javascript; gutter: true; first-line: 1”> /** <br/> * Module dependencies. <br/> / <br/> <br/>var express = require(‘express’); <br/>var offical = require(’./app.js’); <br/> <br/>var site_vhosts=[],vhosts; <br/> <br/>// Virtual Hosts <br/>site_vhosts.push(express.vhost(‘qinyh.com’,offical)); <br/>site_vhosts.push(express.vhost(‘www.qinyh.com’,offical)); <br/> <br/>vhost=express.createServer.apply(this,site_vhosts); <br/> <br/>vhost.listen(80); <br/>console.log(“Express router Listening on port 80”);</pre> <br/><span style=“font-family: monospace;”> <br/></span> <br/> <br/><hr /> <br/> <br/><h2>/app.js</h2> <br/>以下才是我们程序的主干部分,忽略掉了ajax邮件发送模块⋯⋯下次再讲 <br/>注意只有当在production mode时,才会开启缓存,才保证性能。 <br/><pre class=“brush: javascript;”> /* <br/> * Module dependencies. <br/> / <br/> <br/>var express = require(‘express’); <br/>var app = module.exports = express.createServer(); <br/>var fs=require(‘fs’); <br/> <br/>// Configuration <br/>var oldconsole=console.log; <br/>log=function(obj,error){ <br/> if(process.platform!=“win32”){ <br/> var color=(error)?“33[1;31m”:“33[1;32m”; <br/> process.stdout.write(color); <br/> oldconsole(obj); <br/> process.stdout.write(“33[0m”); <br/> }else{ <br/> oldconsole(obj); <br/> } <br/>} <br/>console.log=function(obj){ <br/> log(obj,true); <br/>} <br/> <br/>app.configure(function(){ <br/> app.set(‘views’, __dirname + ‘/views’); <br/> app.set(‘view engine’, ‘jade’); <br/> app.use(express.bodyParser()); <br/> app.use(express.methodOverride()); <br/> app.use(express.static(__dirname + ‘/public’)); //注意顺序,为了能够用到404,要把这个提前。 <br/> app.use(app.router); <br/>}); <br/> <br/>app.configure(‘development’, function(){ <br/> app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); <br/> log(“Warning: Server in Development Mode, add NODE_ENV=production”,true); <br/>}); <br/> <br/>app.configure(‘production’, function(){ <br/> app.use(express.errorHandler()); <br/> log(“Production Mode”); <br/>}); <br/> <br/>// Read JSON files <br/>//这里出现过一个非常恶心的bug,我们发现我们拿windows记事本产生的json文件node.js解析会有问题,于是去掉第一个字节。为了保证安全,文件也上来加了一个回车。 <br/>//由于我们是一上来只解析一次,所以我们采用了同步方式</pre> <br/><pre class=“brush: javascript;”>var info=JSON.parse(fs.readFileSync(‘chinese.json’, ‘utf8’).substr(1)); <br/>var routes=JSON.parse(fs.readFileSync(‘router.json’,‘utf8’).substr(1)); <br/> <br/>// Start router <br/> <br/>var startRouter=function(path){ <br/> app.get(route, function(req,res){ <br/> //console.log("Connect to "+path); <br/> var page=info[routes[path].data]; <br/> res.render(routes[path].template,page);//最核心的一句 <br/> }); <br/>}; <br/> <br/>for(route in routes){//如果直接for循环而不是调用函数,你就会发现route永远是最后一个 <br/> startRouter(route); <br/>} <br/> <br/>//File not found <br/> <br/>app.get(’/’, function(req, res){ <br/> res.render(‘404’,{status: 404, <br/> title:‘404 - 文件未找到’}); <br/>}); <br/> <br/>try{ <br/>app.listen(3000); <br/>log(“Express server listening on port 3000”); <br/>}catch(e){ <br/> log("Error: "+e.message,1); <br/>}</pre> <br/><span style=“font-family: monospace;”> <br/></span> <br/> <br/><hr /> <br/> <br/><h2>/router.json</h2> <br/>我们通过router.json实现了路由表,可以看到index是view,而data是chinese.json里对应的内容:<span style=“font-family: monospace;”> <br/></span> <br/><pre class=“brush: javascript;”>{ <br/> “/”: { <br/> “template”: “index”, <br/> “data”: “index” <br/> }, <br/> “/products”: { <br/> “template”: “secondary”, <br/> “data”: “products” <br/> }, <br/> “/products/clothes”: { <br/> “template”: “products”, <br/> “data”: “clothes” <br/> }, <br/> “/products/wine”: { <br/> “template”: “products”, <br/> “data”: “wine” <br/> }, <br/> “/products/furniture”: { <br/> “template”: “products”, <br/> “data”: “furniture” <br/> }, <br/> “/solutions”: { <br/> “template”: “secondary”, <br/> “data”: “solutions” <br/> }, <br/> “/solutions/shop”: { <br/> “template”: “solutions”, <br/> “data”: “shop” <br/> }, <br/> “/solutions/e-shop”: { <br/> “template”: “solutions”, <br/> “data”: “eshop” <br/> }, <br/> “/solutions/next-gen”: { <br/> “template”: “solutions”, <br/> “data”: “nextgen” <br/> }, <br/> “/company”: { <br/> “template”: “secondary”, <br/> “data”: “company” <br/> }, <br/> “/company/team”: { <br/> “template”: “about”, <br/> “data”: “team” <br/> }, <br/> “/company/ideals”: { <br/> “template”: “company”, <br/> “data”: “ideals” <br/> }, <br/> “/company/contact”: { <br/> “template”: “contact”, <br/> “data”: “contact” <br/> }, <br/> “/company/jobs”: { <br/> “template”: “secondary”, <br/> “data”: “jobs” <br/> }, <br/> “/company/jobs/it”: { <br/> “template”: “jobs”, <br/> “data”: “it” <br/> }, <br/> “/company/jobs/design”: { <br/> “template”: “jobs”, <br/> “data”: “design” <br/> }, <br/> “/company/jobs/sales”: { <br/> “template”: “jobs”, <br/> “data”: “sales” <br/> } <br/>}</pre> <br/><pre><code> <br/></pre></code> <br/> <br/><hr /> <br/> <br/><h2>/chinese.json</h2> <br/>然后内容和图片是放在chinese.json里,我们由于一般改动很少,所以直接文本编辑器就很方便。如果经常更新,其实加一个后台也非常容易。 <br/>实际内容太多,只给两个例子 <br/><pre><code> <br/></pre></code> <br/><pre class=“brush: javascript;”>{ <br/> “index”: { <br/> “title”: “首页 | 北京秦运恒信息技术有限公司”, <br/> “motto”: “原来购物可以如此简单和生动”, <br/> “columns”: [ <br/> { <br/> “title”: “最新产品”, <br/> “desc”: “使用iPad展示商品为顾客提供更加丰富的渠道来探索和喜爱您的产品。”, <br/> “img”: “/images/index/img1.png”, <br/> “href”: “/products/” <br/> }, <br/> { <br/> “title”: “解决方案”, <br/> “desc”: “我们会与您一起找到适合您的解决方案,让您的事业蒸蒸日上。”, <br/> “img”: “/images/index/img2.png”, <br/> “href”: “/solutions/” <br/> }, <br/> { <br/> “title”: “关于我们”, <br/> “desc”: “年轻而专业。梦想加实干。敏捷和执着。这就是我们,您一定会喜欢跟我们合作。”, <br/> “img”: “/images/index/img3.png”, <br/> “href”: “/company/” <br/> } <br/> ] <br/> }, <br/> “products”: { <br/> “title”: “产品介绍 | 北京秦运恒信息技术有限公司”, <br/> “motto”: “精益求精,宁缺毋滥”, <br/> “banner”: “banner-2”, <br/> “columns”: [ <br/> { <br/> “title”: “精品家具专家”, <br/> “desc”: “苹果iPad上展现家具的全部风采,捕捉顾客的想象力和心,连样板间都可以展现上百种家具。”, <br/> “img”: “/images/product/img1.png”, <br/> “href”: “/products/furniture/” <br/> }, <br/> { <br/> “title”: “葡萄酒指南”, <br/> “desc”: “中国即将成为世界红酒消费第一大国,然而多数消费者只认得电视广告。有了这样的指南,您的好酒再也不愁无人问津。”, <br/> “img”: “/images/product/img2.png”, <br/> “href”: “/products/wine/” <br/> }, <br/> { <br/> “title”: “服装时尚导购”, <br/> “desc”: “顾客看到苹果iPad上的服装模特会动的那一刻,已经决定了您和其他店的区别。从今以后您的顾客可以和朋友一起坐着逛街了。”, <br/> “img”: “/images/product/img3.png”, <br/> “href”: “/products/clothes/” <br/> } <br/> ] <br/> }, <br/> … <br/>]</pre> <br/><pre><code> <br/></pre></code> <br/> <br/><hr /> <br/> <br/><h2>/views/layout.jade</h2> <br/>然后就是通用的模板layout.jade,放在views目录下,用jade写的,非常简约。!=body 是插入别的页面的地方。 <br/><pre class=“brush: javascript;”>!!! transitional <br/>html(xmlns=‘http://www.w3.org/1999/xhtml’) <br/> head <br/> meta(http-equiv=‘Content-Type’, content=‘text/html; charset=utf-8’) <br/> title= title <br/> link(rel=‘shortcut icon’, href=’/favicon.ico’, type=‘image/x-icon’) <br/> link(rel=‘stylesheet’, type=‘text/css’, href=’/css/index.css’) <br/> body <br/> #all <br/> div(style=‘clear:both’) <br/> #logo.content <br/> a(href=’/’) <br/> img(src=’/images/logo.png’, width=‘232’, height=‘35’, alt=‘北京秦运恒信息技术有限公司’) <br/> ul <br/> li <br/> a(href=’/’) 首页 <br/> li <br/> a(href=’/products/’) 产品介绍 <br/> li <br/> a(href=’/solutions/’) 解决方案 <br/> li <br/> a(href=’/company/’) 关于我们 <br/> li <br/> a(href=’/company/jobs/’) 加入我们 <br/> !=body <br/> #footer <br/> p(align=‘center’) @2010 北京秦运恒信息技术有限公司 京备ICP10028133号</pre> <br/> <br/><hr /> <br/> <br/><h2>/views/index.jade</h2> <br/>有那么多视图就不上传了,我就把首页上传给大家看看 <br/><pre class=“brush: javascript;”>#p-1.content <br/> p= motto <br/>#main.content <br/> - for (var i in columns) <br/> - var last=(i<columns.length-1)?’’:‘last’ <br/> dl(class=’#{last}’) <br/> dt <br/> a(href=’#{columns[i].href}’) <br/> img(src=’#{columns[i].img}’, width=‘276’, height=‘164’, alt=’#{columns[i].title}’) <br/> dd <br/> a(href=’#{columns[i].href}’) <br/> span=columns[i].title <br/> br <br/> | #{columns[i].desc} <br/>.slider-wrapper.theme-custom <br/> .slider-wrapper2 <br/> #slider.nivoSlider <br/> img(src=’…/images/index/banner1.png’, width=‘100%’, height=‘366’, alt=‘首页’) <br/> img(src=’…/images/index/banner2.png’, width=‘100%’, height=‘366’, alt=‘首页’) <br/> img(src=’…/images/index/banner3.png’, width=‘100%’, height=‘366’, alt=‘首页’) <br/> script(type=‘text/javascript’, src=’/js/jquery-1.6.1.min.js’) <br/> script(type=‘text/javascript’, src=’/js/jquery.nivo.slider.js’) <br/> script(type=‘text/javascript’) <br/> $(document).bind(“ready”,function() { <br/> $(’#slider’).nivoSlider(); <br/> });</pre> <br/> <br/><hr /> <br/> <br/><h2>其他资源</h2> <br/>其他资源一股脑扔倒/public目录下就可以了。 <br/><h2>请关注第二讲,AJAX和邮件系统</h2>

16 回复

好文,受益良多,期待第二讲

对了,我们本来在app.js里是可以fs.watchFile 两个一上来就载入的文件(router.json, chinese.json),但是由于我们的设计师喜欢用windows,而windows下的node.js不支持fs.watchFile,所以每次我们更新一下那两个文件,都需要重启一下服务器。实践中,大家如果用mac或者linux,可以加上对这两个文件的监听,每次文件发生变动后可以重新载入。 <br/>我们会把邮件系统和我们的改进放到下一讲,敬请关注。

非常好的实例

很高兴能看到将NODE.JS投入到商业项目中,让我看到了NODE.JS的未来。我目前用YUI+EXPRESS实现了一个WEB IM的 DEMO,支持群聊,私聊,可以独立创建群,记录用户和聊天记录使用REDIS,有机会可以多探讨下。 <br/> <br/>不过我拿GOOGLE的PAGESPPED为:http://qinyh.com/ 打分,很悲剧的事情, <br/>运行了4次,第1次是27/100分,后次是31/100分,最后一次还是27/100分,不知道是不是我这里的pagespeed出问题了,建议楼主查查原因。 <br/> <br/>pagespeed给出报告是图片,CSS,以及JS都没有压缩, <br/>个人认为,丢分的最大原因是PNG图片没有压缩 <br/>其中CSS还包括了很多注释。 <br/> <br/>之后我用FIREBUG看了一下,发现一个严重性能问题,每次大图的切换都会发送一次图片的请求,奇怪的是每次请求的图片之前都已经请求过了,这样将严重影响服务端的性能,(按CTRL+F5刷新)请楼主去查查原因。

@snoopy <br/>你是哪天运行的呢? <br/>为什么我运行的结果是 <br/>Page Speed Online <br/>网页 首页 | 北京秦运恒信息技术有限公司 的总体 Page Speed 得分为 98(满分 100 分)。 <br/> <br/>倒是有关你说的性能问题,我们想知道应该怎么改会比较好呢?

@tianshuo <br/>很高兴你能关注我的建议 <br/>问题1: <br/>我的page speed是通过以下域名安装的 <br/>http://code.google.com/intl/zh-CN/speed/page-speed/docs/using_chrome.html#Installing <br/>page speed是安装在 Chrome Developer Tools中的 <br/>回到家中,我又用Chrome Developer Tools的page speed测试还是27/100分。 <br/>不过我看了page speed在线确实是98分,我也很奇怪,不过CSS中有注释和未压缩的JS,应该很难得到98/100分。 <br/>不过这些问题不大 <br/> <br/>问题2: <br/>单位里ff5.0,家里是ff3.6都存在在ctrl+f5刷新后每隔一定时间就会去请求banner1-3.png这3张图片,应该和你用的jQuery Nivo Slider v2.6插件有关系,可能和修改IMG SRC属性有关,可以考虑更换算法,比如利用display:none来代替更换src的方法。不过这个问题在chrome没有发现。

大家好,app.js里读json的非常恶心的bug(node.js#1440)已经在node0.5.4得到解决了。可以直接parse不需要再手动去除一位了。

大家好,首先,我想谢谢tianshuo的分享。 <br/>我遇到了问题。。。 <br/>我用windows 的node.exe 来运行, <br/>setup 如下, <br/>mime ver 1.2.2 <br/>qs ver 0.3.0 <br/>express 1.0.7 <br/>connect 0.5.4 <br/>当我运行 node server.js 时, 我会遇到TypeError: Object # has no method ‘bodyParser’ <br/>好像connect 0.5.4没有bodyParser。 <br/>当我用connect 1.0.0 时,我会遇到TypeError: Cannot read property ‘prototype’ of undefined <br/>我想请问tianshuo是用什么setup的呢? <br/>谢谢

“这里出现过一个非常恶心的bug,我们发现我们拿windows记事本产生的json文件node.js解析会有问题,于是去掉第一个字节。为了保证安全,文件也上来加了一个回车。” <br/> <br/>你是用 utf-8 作為網站編碼嗎?如果是,那我猜你在用 notepad 存檔時,把 utf-8 的 BOM (byte order mark) 也存下去了。 <br/> <br/>請用其他編輯器,如Notepad++ 或是 UltraEdit 另存成沒有 BOM 的 utf-8 文件。

[…] [实践经验+代码]用node.js和express.js和jade搭建轻型cmd系统 […]

[…] 首页关于互联网关于产品关于技术关于生活RSS订阅关于几篇不错的 NodeJs教程【收藏】 由 xiaogangqq123 发表于 上午 12:54文章列表:从文件上传开始, 进入node.js的世界将使用npm管理的node.js项目部署到vCloudLabs用node.js建博客(一) – node.js安装及Express框架简介用node.js建博客(二) – 构建第一个markdown页面 用node.js建博客(三) – 用markdown写静态博客 用node.js建博客(四) – express中的404处理用node.js建博客(五) – 用vows以BDD方式测试程序 使用node.js建博客(六) – 添加代码高亮的支持 (Final)使用到的技术简介:Node.js:这个都不知道的话,估计你是不会来看这篇。Express:node.js中的Web框架,采用Ruby 中的 sintra.rb 开发方式;jade:一款node.js中的模板引擎markdown: 用来写博客很不错,对html语法进行提炼,GitHub中的readme就需要用他写Expresso:Express作者写的测试框架,基于TDD的方式Vows:BDD测试框架(需要翻**, nnd~ ** 词不让我打!)API-Easy:对Vows进行了封装,主要面向Rest-ful接口测试参考资料总汇:Node.js API中文手册Express中文入门手册Jade模板引擎的GitHubCommonJS Packages/1.0 WikiMarkdown语法中文说明Google-Code-Prettify使用文档使用node.js, markdown-js,prettify.js打造个人写作平台RestTeasy: Teat any API in node.js[实践经验+代码]用node.js和express.js和jade搭建轻型cmd系统 转自http://www.feedzshare.com/b/8061237/2若无标明,都为本人原创文章,转载请注明: 转载自小刚的博客|分享互联网本文链接地址: 几篇不错的 NodeJs教程【收藏】 […]

我觉得你把一个简单的网站复杂化了

非常好的实例!谢谢!

好像当机了

嗯,php部分被黑客攻击后网站一直还没恢复呢

express.vhost 在3.0里没有了还是怎么的?网上搜了一些资料,是content。host的?请问这是怎么做的?

回到顶部