More Related Content
Similar to nodejs开发web站点 (20)
More from xiaojueqq12345 (7)
nodejs开发web站点
- 2. 记得社区里有说,拿 nodejs 开发 web 站点,还处于刀耕火种的状态
还有人说 nodejs 不适合开发 web 站点,只适合做服务器中间件
真的是这样子吗?
NO , cnode club
雪球网 淘宝指数
- 6. 登陆注册 让 Nodejs 带你了解好玩的后端世
界
内容发布
图片服务
多版共存
调试方法
运维技巧
- 7. ├── 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
- 8. Nginx 的一些概念和配置介绍
Nginx 大家就比较熟悉了,和 apache 类似,通过配置就可以实现各种强大的功能。
在这里简单介绍下如何来写适合 nodejs 的 nginx 配置,处理静态服务的就不赘述了。
# 等同于 apache 里的 vhost
server {
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 就是这么简单 ~
- 9. 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 好学多了。
- 10. 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 加载的套件方法,
和一些便利方法,其中最常用的几个方法和属性为。
- 11. #touch model/init.js // 初始化数据库
init.js →
db.dropDatabase(); //mongdb 语法,语法糖也为 js
db.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:””
});
- 12. 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');
- 14. 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);
- 15. 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');
});
}
}
}
}
- 16. 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');
}
});
- 17. 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);
};
- 18. 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.js
var mail = require('../lib/mail');
exports.invite = invite; // 邀请方法
exports.active = active; // 激活方法
- 19. 注册的流程为 , 通过用户输入的邮箱和昵称,发送激活信,带上时间戳和初始化随机密码,
用户登陆邮箱后,通过激活链接,激活自己的帐号,超时则失败。
其中的参数都使用 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);
});
}
- 21. ./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
});
}
});
- 22. 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> }
});
}
就是这么简单,你绝对可以。
- 24. ./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);
}
}
- 26. ./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); // 否则给创建日期
- 27. 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);
}
});
};
头像部分搞定啦。不怕用户清不掉缓存鸟 ~
- 29. 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)|(Windows
CE)|(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 版本的还要我解释么 ~
- 31. # node –debug index.js
# node-inspector
# forever start index.js
# forever list
# cat ./root/.forever/xxx.log
- 33. Forever ? crontab ? service ?甚至 monit ?
那么我们开始吧 ~ 首先,把你的服务做成一个系统服务 ~ chkconfig 上场了 ~
在 /etc/init.d/ 下建立系统服务脚本
#!/bin/bash
#chkconfig:345 99 01
#description:tuer
export PKG_CONFIG_PATH='/usr/local/lib/pkgconfig'
export LD_LIBRARY_PATH='/usr/local/lib':$LD_LIBRARY_PATH
DIR='/home/tuer2.0'
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
NODE_PATH='/home/tuer2.0/node_modules'
NODE=/usr/local/bin/node
test -x $NODE || exit 0
function 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}" ;;
esac
exit 0
- 34. # service tuer start | restart | stop
# ok... 再也不用担心妈妈突然关机啦 ~
# crontab 定时任务 ~
#!/bin/sh
cd '/home/tuer2.0/'
cur_dir=$(pwd)
# 备份数据库
date_now=`date +%Y%m%d%H%M`
backmongodbFile=tuer$date_now.tar.gz
cd $cur_dir/backup/
/usr/bin/mongodump -h 127.0.0.1 --port 10001 -d node-mongo-tuer -o my_mongodb_dump/
rm *.tar.gz
tar 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 们可以去睡大觉啦。。。
- 35. 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 hits
Availability: 100.00 %
Elapsed time: 9.27 secs // 花费时间
Data transferred: 3.24 MB
Response time: 2.91 secs
Transaction rate: 72.71 trans/sec // 平均每秒处理
Throughput: 0.35 MB/sec // 每秒传输量
Concurrency: 211.66 // 最高并
Successful transactions: 674
Failed transactions: 0
Longest transaction: 4.47
Shortest transaction: 0.52