nodejs开发web站点

1,991 views

Published on

nodejs开发web站点

Published in: Technology
  • Be the first to comment

nodejs开发web站点

  1. 1. 如何使用 nodejs 开发 web2.0 站点
  2. 2. 记得社区里有说,拿 nodejs 开发 web 站点,还处于刀耕火种的状态还有人说 nodejs 不适合开发 web 站点,只适合做服务器中间件真的是这样子吗?NO , cnode club雪球网 淘宝指数
  3. 3. https://github.com/xiaojue/tuer.me
  4. 4. Nginx 代理端口和处理静态文件 static convert carionpm-modules Nodejs sendmail redies Mongodb
  5. 5. 登陆注册 让 Nodejs 带你了解好玩的后端世 界内容发布图片服务多版共存调试方法运维技巧
  6. 6. ├── app.js├── backup ├── node_modules│ └── tuer201211251454.tar.gz │ ├── calendar├── checkService.js │ ├── canvas├── help.sh │ ├── connect-redis├── index.js │ ├── eventproxy├── lib │ ├── express├── model │ ├── ga│ ├── base.js │ ├── imagemagick│ └── init.js │ ├── jade├── public │ ├── mongodb│ ├── bootstrap │ ├── nodemailer│ ├── favicon.ico │ ├── rss│ ├── images │ └── xml2js│ ├── jquery│ ├── JSON-js│ ├── libs│ ├── seajs│ └── style├── README.md├── routes│ └── wap├── routes.js├── tuer├── views│ └── wap└── wapRoutes.js
  7. 7. Nginx 的一些概念和配置介绍Nginx 大家就比较熟悉了,和 apache 类似,通过配置就可以实现各种强大的功能。在这里简单介绍下如何来写适合 nodejs 的 nginx 配置,处理静态服务的就不赘述了。# 等同于 apache 里的 vhostserver { listen 80; server_name xxx.com www.xxx.com charset utf-8; location / { If( $http_user_agent ~* “(MSIE)”){ rewrite . htttp://fuck.you/ break; } proxy_set_header Host $host:80; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http://localhost:3000; # 你懂的。 }}代理端口和屏蔽 IE 就是这么简单 ~
  8. 8. Mongodb 的一些概念和方法介绍Mongodb 数据库简单来说就是以 bjson 的格式储存应用文档集合的一个东东。它的语法糖是纯 javascript 的,作为前端看起来一点也不陌生。它的数据结构是 json 格式的,作为前端每天都在和它打交道。它支持提供方便多服务器负载均衡的配置【当然我不会】还默认就开启了热区缓存。【热区缓存就是为你的应用自动缓存敏感数据,减少直接读库操作】mongodb 因为是键对值的,所以文档大小比 mysql 一类表结构数据库要大,但查询速度快。比较适合小巧的应用和数据关系简单的网站应用。一些主要方法:进入 mongo shell 后可以用 help 方法看全部命令。这里简单介绍一些常用方法。使用 mongodb 的 nodejs 驱动后,实例化后的 Db 对象拥有以下这些方法。getCollection ,find.findOne,sort,limit,skip,toArray,remove,update,insert,count.顾名思义就不解释了, mongodb 的语法比较简单,比 sql 好学多了。
  9. 9. Expressjs 框架的几个重要方法和概念介绍Expressjs 其实只是对 http.createServer 中的 req , res 包装了丰富的方法,并可以方便监听get , post 常用请求,然后简单的增加了一个 view 层的配置,而它的 controller 默认只有一个文件,且没有提供 model 层,需要我们自己简单搭建。首先 expressjs 在每一个 handle 中提供了 3 个参数App.get(/index,function(req,res,next){ // 当一个路由被拦截,它可以对方法进行处理,或者跳过,使用 next 方法。 // 其中的 req 为请求, res 为响应,当 res.render 或者 res.send , end 方法被调用时, // 此请求可视为结束,可在结束前设置 res 的 header ,即响应头信息。 // render 方法可渲染 views 层中的模版,并传入变量。 // send 方法则是直接输出信息。 req 中保存了请求头,请求 url 参数信息, post 信息值, app.use 加载的套件方法, 和一些便利方法,其中最常用的几个方法和属性为。
  10. 10. #touch model/init.js // 初始化数据库init.js →db.dropDatabase(); //mongdb 语法,语法糖也为 jsdb.createCollection(“users”);db.createCollection(“comment”);db.createCollection(“diary”);db.createCollection(“tips”);db.createCollection(“notebooks”);db.createCollection(“todos”);db.notebooks.insert({name:” 默认日记” ,owner:-1});db.users.insert({ accounts:”admintest@tuer.me”, create_at:new Date(), pwd:hex_md5(“1234qwer”), nick:”admin”, avatar:”data:image/png;base64,xxxxx”, profile:” 默认介绍”, firends:[], notebook:0, todocount:0, pageurl:””});
  11. 11. app.js →var express = require(“express”), RedisStore = require(connect-redis), app = express.createServer();function Configuration(app){ app.set(“views”,__dirname+/views); app.set(“view engine”,”jade”); app.set(“view options”,{layout:false}); app.use(express.bodypress({uploadDir:__dirname+/public/images/})); app.use(express.cookieParse()); app.use(express.session({secret:keyboard cat,store:new RedisStore})); app.use(express[static](__dirname+/public)); app.use(express.favicon(__dirname+/public/favicon.ico),{maxAge:2592000000}); app.use(app.router);}app.configure(function(){ Configuration(app);});require(./routes)(app);app.listen(3000);
  12. 12. 登陆和注册的实现
  13. 13. routes.js →var login = require(./routes/login),index = require(./routes/index),register = require(./routes/register),/*... 等等,引入拆分后的路由 ..*/error = require(./routes/error);module.exports = function(app){ app.redirect(“home”,”/”); app.redirect(“login”,”/login”); // 以上为注册快捷跳转 app.get(*,login.cookies); // 检查 cookie // 现在以登陆和注册模块来简单说明 app.get(“/login”,login.index); app.post(login,login.sigin); app.get(“logout”,login.logout); app.get(/register,register.index); app.post(/register/invite,register.invite); app.get(/register/active/:active,register.active); // 当没有匹配的路由时,转到 error 下的 notFound 方法 app.get(*,error.notFound);
  14. 14. login.js →var tuerbase = require(./model/base);exports.index = index;exports.sigin = sigin;exports.logout = logout;exports.cookies = cookies;var sigin = function(req,res){ if(req.session.is_login){ res.redirect(home); }else{ var proxy = new EventProxy(), accounts = req.body.email.trim(), pwd = req.body.pwd.trim(), remember = req.body.remember, render = function(data){ var errorMap = { 001: 帐号不存在 , 002: 帐号密码不正确 }; if(errorMap.hasOwnProperty(data)){ req.flash(error,errorMap[data]); }else{ signsuccess(req,res,data,accounts,pwd,remember,function(res){ Res.redirect(home); }); } } }}
  15. 15. login.js →proxy.assign(findLoginuser,render);if(accounts && pwd){ // 如果校验通过则开始查找}else{ proxy.trigger(findLoginuser,001);}var md5 = crypto.createHash(md5);md5.update(pwd);pwd = md5.digest(hex);tuerBase.findOne({ accounts:accounts},users,function(err,user){ if(err || !user) proxy.trigger(findLoginuser,001); else{ if(user[pwd] === pwd) proxy.trigger(findLoginuser,user); else proxy.trigger(findLoginuser,002); }});
  16. 16. login.js →signsuccess = function(req,res,userdata,accounts,pwd,remember,callback){ var cookie_accounts = base64.encode(accounts), maxAge = config.timeout, md5 = crypto.createHash(md5); md5.update(pwd); var cookie_pwd = base64.encode(md5.digest(hex)); if(remeber){ maxAge = 1000 * 60 * 60 * 24 * 7; res.cookie(remember,1,{maxAge:maxAge,path:/,domain:config.cookiepath}); } res.cookie(accounts,cookie_accounts,{/*... 写入 cookie..*/}); res.cookie(pwd,cookie_pwd,{/*... 写入 cookie..*/}); req.session.cookie.expires = false; req.session.cookie.maxAge = config.timeout; req.session.is_login = true; req.session.userdata = userdata; // 写入 session 数据 req.session.userdata.avatar = Avatar.getUrl(userdata._id); 增加头像地址 - 通过 id 转换 if(callback) callback(res);};
  17. 17. login.js →cookies = function(req,res,next){ var accounts = req.cookies.accounts, pwd = req.cookies.pwd, remember = req.cookies.remember; if(accounts && pwd){ //decode base64 字符 // 通过 findOne 方法去查找 accounts ,再察看是否有这个用户 // 流程同 siginin 方法。如果为不合法,或者密码错误,或者不存在用户。 // 则视为没登陆, session 设置为未登陆,调用 next 方法进入下个路由 // 如果密码和用户匹配,则调用 siginsuccess 方法,进行正确的登陆操作 }}register.jsvar mail = require(../lib/mail);exports.invite = invite; // 邀请方法exports.active = active; // 激活方法
  18. 18. 注册的流程为 , 通过用户输入的邮箱和昵称,发送激活信,带上时间戳和初始化随机密码,用户登陆邮箱后,通过激活链接,激活自己的帐号,超时则失败。其中的参数都使用 base64 和 md5 加密。下面看下如何使用 nodejs 发邮件:./lib/mail.js →var nodemailer = require(nodemailer),util = require(../lib/util.js);var transport = nodemailer.createTransport(Sendmail,/usr/sbin/sendmail); // 本地 sendmail 的执行路径exports.send_mail = function(options, callback) { var _conf = { sender: root@tuer.me, headers:{ } }; util.mix(_conf,options); transport.sendMail(_conf, function(err, success) { if (err) callback(err); else callback(null,success); });}
  19. 19. 内容发布的简单实现
  20. 20. ./route/diary.js → 看一下简化过的 save 方法和 detail 方法就知道了。save = function(req,res){ If(!req.session.is_login){ res.redirect(login); return; } var content = req.body.content; // 这里略过校验 tuerbase.save({content:content},diary,function(err,data){ if(err){ req.flash(error,err); res.redirect(back); // 返回 req 提交的页面 }else{ res.redirect(home); // 随便指向哪里,写入成功 . } });};// 这里的 handle 为 app.get(/detail/:id,diary.detail);detail = function(req,res){ var id = req.params.id; If(!id){ res.redirect(404); // 也可以设置为 next(); 最后找不到匹配路由则自动到达 404 页面 }else{ tuerBase.findDiaryById(id,function(err,diary){ if(err || !diary){ next(); }else{ // 这里的 diary/detail 为 views 下的 diary 目录下的 detail 模版 res.render(diary/detail,{ content:diary.content }); } });
  21. 21. FindById 方法如下:tuerbase.prototype.findById = function(id,collection,callback){ this.getCollection(collection,function(err,db){ if(err) callback(err); else { var _id; if(typeof id == string){ try{ _id = db.db.bson_serializer.ObjectID.createFromHexString(id); }catch(e){ callback(e); return; } } else _id = id; db.findOne({_id:_id},function(err,data){ if(err) callback(err); else callback(null,data); }); } Model 层中的 save 方法如下: });} tuerbase.prototype.save = function(data,collection,callback){ this.getCollection(collection,function(err,db){ if(err) callback(err); View 写法: else{ data[created_at] = new Date(); 如果使用 jade 模版,则 views 应 db.insert(data,function(err,data){ if(err) callback(err); div #{content} else callback(null,data); }); 输出则为 <div>content 内容 </div> } }); } 就是这么简单,你绝对可以。
  22. 22. 如何处理图片?
  23. 23. ./routes/diary.js 删除临时文件的代码 util.remove_temp = function(path){带图片上传的 save 方法 fs.unlink(path,function(){ if(err) throw err; });save = function(req,res){ }; var uploadPic = req.files.uploadPic, temp_path = uploadPic.path, 批量产生缩略图代码 type = function(){ util.bacthImages = function(path,callback){ var _type; var proxy = new EventProxy(), try{ finish = function(){ _type = .+uploadPic.type.split(/)[1]; callback(null); }catch(e){ }; return .undef; var queue = [80,150,300,500,unlink], } picname = Path.basename(path), return _type; Index = 0; }(), proxy.assign(80, 150, 300, 500, unlink, finish); filename = path.basename(temp_path), var resizesize = function(i){ picname = filename + type, var ret = { target_path = rootdir + /public/images/+picname; srcPath:path, if(uploadPic.size){ dstPath:rootdir+/public/images/+queue[i]+/+picname, If(!type.match(/jpg|png|jpeg|gif/gi)){ width:queue[i], // 错误处理,删除 temp 使用 remove_temp 方法 height:queue[i], Timeout:1000 * 30 return; } } return ret; if(uploadPic.size > 20971520){ }; // 大于 2MB return; function resizehandle(err){ } if(err){ fs.rename(temp_path,target_path,function(err){ try{ if(err){ fs.unlinkSync(path); // 给页面抛错误 fs.unlinkSync(...); 。。。 // 删除所有缩图 ; req.flash(error,err); }catch(e){ req.redirect(back); throw e; }else{ } util.bacthImages(target_path,function(err){ }else{ if(err){ proxy.trigger(queue[index]); Index ++; // 抛错 if(queue[index] == unlink){ }else{ fs.unlink(path,function(){ If(!err) proxy.trigger(unlink); // 保存下文件名到相应位置 }); res.redirect(home); // 正确的下一步页面 }else{ Imagemagick.resize(resizesize(index),resizehandle); } } }); } } } }); Imagemagick.resize(resizesize(index),resizehandle); }else{ }; util.remove_temp(temp_path); }}
  24. 24. 如何处理头像? base64 字符?
  25. 25. ./lib/avatar.js →// 根据 uid ,返回头像图片,包含截图和全图// 前端已经用 flash 压缩了总大小Var Canvas = require(canvas),Image = Canvas.Image;GetAvatar = function(uid,width,height,callback){ tuerBase.findUser(uid,function(err,user){ if(err) callback(err); else{ var avatar = user.avatar, //base64 字符 img = new Image, //canvas 对象中的 image img.onload = function(){}, img.onerror = function(err){ callback(err)}, img.src = avatar; } });}onload = function(){ var canvas,ctx; if(user.coords && width && height){ Canvas = new Canvas(width,height); ctx = canvas.getContext(2d); var coords = user.coords.split(,); // 原来格式为坐标宽度逗号隔开 ctx.drawImage(img,coords[2],coords[3],coords[0],coords[1],0,0,width,height);// 输出截图后的尺寸 }else{ canvas = new Canvas(img.width,img.height); ctx = canvas.getContext(2d); ctx.drawimage(img,0,0,img.width,img.height,0,0,img.width,img.height);// 输出原始尺寸 } canvas.toBuffer(function(err,buf){ if(err) callback(err); else{ if(user.lastMod) callback(null,buf,user.lastMod); // 有最后修改日期,给最后修改日期 else callback(null,buf,user.created_at); // 否则给创建日期
  26. 26. user.js → 调用方法app.get(/avatar/:id,user.avatar);avatar = function(req,res){ var uid = req.params.id; Avatar.getAvatar(uid,48,48,function(err,buf,lastMod){ if(err) res.redirect(500); else{ var D = new Date(), year = 1000 * 60 * 60 * 24 * 365, Expires = new Date(D.valueOf() + year).toString(); if(req.headers[if-modified-since] && lastMod == req.headers[if-modified-since]){ res.writeHead(304,not modified); res.end(); return; } res.header(Content-Type,image/png); res.header(Last-Modified,lastMod); res.header(Expires,Expires); res.header(Date,D.toString()); res.header(Cache-Control,max-age=+year); res.send(buf); } });};头像部分搞定啦。不怕用户清不掉缓存鸟 ~
  27. 27. Wap 版? HTML5 版?
  28. 28. Nginx 代理来搞定 ~if ( $http_user_agent ~* "(MSIE)|(MIDP)|(WAP)|(UP.Browser)|(Smartphone)|(Obigo)|(Mobile)|(AU.Browser)|(wxd.Mms)|(WxdB.Browser)|(CLDC)|(UP.Link)|(KM.Browser)|(UCWEB)|(SEMC-Browser)|(Mini)|(Symbian)|(Palm)|(Nokia)|(Panasonic)|(MOT-)|(SonyEricsson)|(NEC-)|(Alcatel)|(Ericsson)|(BENQ)|(BenQ)|(Amoisonic)|(Amoi-)|(Capitel)|(PHILIPS)|(SAMSUNG)|(Lenovo)|(Mitsu)|(Motorola)|(SHARP)|(WAPPER)|(LG-)|(LG/)|(EG900)|(CECT)|(Compal)|(kejian)|(Bird)|(BIRD)|(G900/V1.0)|(Arima)|(CTL)|(TDG)|(Daxian)|(DAXIAN)|(DBTEL)|(Eastcom)|(EASTCOM)|(PANTECH)|(Dopod)|(Haier)|(HAIER)|(KONKA)|(KEJIAN)|(LENOVO)|(Soutec)|(SOUTEC)|(SAGEM)|(SEC-)|(SED-)|(EMOL-)|(INNO55)|(ZTE)|(WindowsCE)|(Wget)|(Java)|(curl)|(Opera)" ) { rewrite . http://m.tuer.me/ break; }Nodejs :app.get(*,function(req,res,next){ ga.trackPage(req.url);// 谁说 wap 的 ga 就支持 php 和 java 啦? if(!req.accepts("html") && req.accepts("application/xhtml+xml")){ res.charset = UTF-8; res.header(Content-Type, application/xhtml+xml); // 给所有页面增加 header 头,如果支持解析 xhtml+xml 格式的则为正经 wap浏览器,否则不需要加,解析不了 ~ } next(); });HTML5 版本的还要我解释么 ~
  29. 29. 怎么调试?发现潜在 bug ?
  30. 30. # node –debug index.js# node-inspector# forever start index.js# forever list# cat ./root/.forever/xxx.log
  31. 31. 如何运维?
  32. 32. Forever ? crontab ? service ?甚至 monit ?那么我们开始吧 ~ 首先,把你的服务做成一个系统服务 ~ chkconfig 上场了 ~在 /etc/init.d/ 下建立系统服务脚本#!/bin/bash#chkconfig:345 99 01#description:tuerexport PKG_CONFIG_PATH=/usr/local/lib/pkgconfigexport LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATHDIR=/home/tuer2.0PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/binNODE_PATH=/home/tuer2.0/node_modulesNODE=/usr/local/bin/nodetest -x $NODE || exit 0function start_app { forever start "$DIR/index.js" -l "$DIR/logs/tuer.log" -o "$DIR/logs/tuer.out.log" -e "$DIR/logs/tuer.err.log"}function stop_app { forever stop "$DIR/index.js"}case $1 in start) start_app ;; stop) stop_app ;; restart) stop_app start_app ;; *) echo "usage: clearstonecc {start|stop}" ;;esacexit 0
  33. 33. # service tuer start | restart | stop# ok... 再也不用担心妈妈突然关机啦 ~# crontab 定时任务 ~#!/bin/shcd /home/tuer2.0/cur_dir=$(pwd)# 备份数据库date_now=`date +%Y%m%d%H%M`backmongodbFile=tuer$date_now.tar.gzcd $cur_dir/backup//usr/bin/mongodump -h 127.0.0.1 --port 10001 -d node-mongo-tuer -o my_mongodb_dump/rm *.tar.gztar czf $backmongodbFile my_mongodb_dump/rm my_mongodb_dump -rf# 重启服务service tuer restart# crontab -e# * */12* * * /bin/sh /home/tuer2.0/help.sh# 每 12 个小时重启一下, coder 们可以去睡大觉啦。。。
  34. 34. HTTP/1.1 200 3.17 secs: 5039 bytes ==> /HTTP/1.1 200 2.79 secs: 5039 bytes ==> /HTTP/1.1 200 3.53 secs: 5039 bytes ==> /HTTP/1.1 200 3.50 secs: 5039 bytes ==> /HTTP/1.1 200 3.49 secs: 5039 bytes ==> /HTTP/1.1 200 3.49 secs: 5039 bytes ==> /HTTP/1.1 200 3.49 secs: 5039 bytes ==> /HTTP/1.1 200 3.48 secs: 5039 bytes ==> /HTTP/1.1 200 3.48 secs: 5039 bytes ==> /HTTP/1.1 200 3.47 secs: 5039 bytes ==> /Lifting the server siege... done.Transactions: 674 hitsAvailability: 100.00 %Elapsed time: 9.27 secs // 花费时间Data transferred: 3.24 MBResponse time: 2.91 secsTransaction rate: 72.71 trans/sec // 平均每秒处理Throughput: 0.35 MB/sec // 每秒传输量Concurrency: 211.66 // 最高并Successful transactions: 674Failed transactions: 0Longest transaction: 4.47Shortest transaction: 0.52
  35. 35. 总 结1,nodejs 可以做网站,而且可以做的很好,很敏捷,模块多,体系相对去年更加健全。2,nodejs 真心不只能做打包工具,前端通过 nodejs 学习服务器,后端技术,门槛低,见效快 , 获取更多视野。3,nodejs 可以应用的地方很多,如内部系统,中小型项目,本地工具,甚至混搭 shell 。(噢, bash 的语法实在太难看懂了)4, 只有知己知彼,才能百战不殆,只有了解了后端知识,才能更好的服务于前端。
  36. 36. Q&A
  37. 37. Thank you !

×