您的位置:首页 > Web前端 > Node.js

nodejs开发指南读后感

2016-04-29 14:53 686 查看

http://www.cnblogs.com/tugenhua0707/p/5444722.html

nodejs开发指南读后感
阅读目录

使用nodejs创建http服务器;
supervisor的使用及nodejs常见的调式代码命令了解;
了解Node核心模块;
ejs模板引擎
Express
理解路由控制
学习使用node建立微博网站

回到顶部

使用nodejs创建http服务器;
1.创建http服务器,监听3000端口;

varhttp=require("http");
http.createServer(function(req,res){
res.writeHead(200,{'Content-Type':'text/html'});
res.write('<h1>Node2.js</h1>');
res.end('<p>HelloWorld</p>');
}).listen(3000);


1.http.Server的事件
http.Server是一个基于事件的HTTP服务器,所有的请求都被封装为独立的事件,
开发者只需要对它的事件编写响应函数即可实现HTTP服务器的所有功能。它继承自EventEmitter,提供了以下几个事件
1.request:当客户端请求到来时,该事件被触发,提供两个参数req和res,分别是http.ServerRequest和http.ServerResponse的实例,
表示请求和响应信息.
2.connection:当TCP连接建立时,该事件被触发,提供一个参数socket,为net.Socket的实例。
connection事件的粒度要大于request,因为客户端在Keep-Alive模式下可能会在同一个连接内发送多次请求。
3.close:当服务器关闭时,该事件被触发。注意不是在用户连接断开时。

代码如下:


varhttp=require('http');
varserver=newhttp.Server();
server.on('request',function(req,res){
res.writeHead(200,{'Content-Type':'text/html'});
res.write('<h1>Node.js</h1>');
res.end('<p>HelloWorld</p>');
});
server.listen(3004);
console.log("portis3004");


2.http.ServerRequest

http.ServerRequest是HTTP请求的信息,是后端开发者最关注的内容。它一般由
http.Server的request事件发送,作为第一个参数传递,通常简称request或req。ServerRequest提供一些属性.
http.ServerRequest提供了以下3个事件用于控制请求体传输。
1.data:当请求体数据到来时,该事件被触发。该事件提供一个参数chunk,表示接收到的数据。如果该事件没有被监听,
那么请求体将会被抛弃。该事件可能会被调用多次。
2.end:当请求体数据传输完成时,该事件被触发,此后将不会再有数据到来。
3.close:用户当前请求结束时,该事件被触发。不同于end,如果用户强制终止了传输,也还是调用close。

3.node.js中的url.parse方法使用说明

该方法的含义是:将URL字符串转换成对象并返回
基本语法:url.parse(urlStr,[parseQueryString],[slashesDenoteHost])
urlStrurl字符串
parseQueryString为true时将使用查询模块分析查询字符串,默认为false
slashesDenoteHost
1.默认为false,//foo/bar形式的字符串将被解释成{pathname:‘//foo/bar'}
2.如果设置成true,//foo/bar形式的字符串将被解释成{host:'foo',pathname:'/bar'}


varurl=require('url');
vara=url.parse('http://example.com:8080/one?a=index&t=article&m=default');
console.log(a);


打印如下:
{
protocol:'http:',
slashes:true,
auth:null,
host:'example.com:8080',
port:'8080',
hostname:'example.com',
hash:null,
search:'?a=index&t=article&m=default',
query:'a=index&t=article&m=default',
pathname:'/one',
path:'/one?a=index&t=article&m=default',
href:'http://example.com:8080/one?a=index&t=article&m=default'
}


1.node.js中请求如何获取get请求的内容我们可以使用上面介绍的url.parse方法


varhttp=require('http');
varurl=require('url');
varutil=require('util');
http.createServer(function(req,res){
res.writeHead(200,{'Content-Type':'text/plain'});
res.end(util.inspect(url.parse(req.url,true)));
}).listen(3001);


在浏览器运行http://127.0.0.1:3001/one?a=index&t=article&m=default这个,

打印如下:
{
protocol:null,
slashes:null,
auth:null,
host:null,
port:null,
hostname:null,
hash:null,
search:'?a=index&t=article&m=default',
query:{a:'index',t:'article',m:'default'},
pathname:'/one',
path:'/one?a=index&t=article&m=default',
href:'/one?a=index&t=article&m=default'
}

通过url.parse,原始的path被解析为一个对象,其中query就是我们所谓的GET请求的内容,而路径则是pathname。

2.如何获取post请求的内容
代码如下:


varhttp=require('http');
varquerystring=require('querystring');
varutil=require('util');

http.createServer(function(req,res){
varpost='';
req.on('data',function(chunk){
post+=chunk;
});
req.on('end',function(){
post=querystring.parse(post);
res.end(util.inspect(post));
});
}).listen(3002);



定义了一个post变量,用于在闭包中暂存请求体的信息。通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中。

在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。

回到顶部

supervisor的使用及nodejs常见的调式代码命令了解;
1.supervisor的使用
nodejs会在第一次引用到某部分时才会去解析脚本文件,之后直接从缓存里面去取,因此对于调式不方面,可以使用安装supervisor
supervisor可以帮助你实现这个功能,它会监视你对代码的改动,并自动重启Node.js
在mac下安装命令如下:sudonpminstall-gsupervisor运行js命令直接supervisorserver1.js即可

2.使用npm安装包有二种模式:本地模式和全局模式;本地模式如下:sudonpminstall包名
全局模式安装如下:sudonpminstall-g包名,他们之间的区别是:
本地模式:该模式是把包安装在当前目录的node_module子目录下,
Node.js的require在加载模块时会尝试搜寻node_modules子目录,因此使用npm本地模式安装的包可以直接被引用。

全局模式:为了减少多重副本而使用全局模式,而是因为本地模式不会注册PATH环境变量。举例说明,我们安装supervisor是为了在命令行中运行它,
譬如直接运行supervisorscript.js,这时就需要在PATH环境变量中注册supervisor。npm本地模式仅仅是把包安装到node_modules子目录下,
其中的bin目录没有包含在PATH环境变量中,不能直接在命令行中调用。而当我们使用全局模式安装时,npm会将包安装到系统目录,
譬如/usr/local/lib/node_modules/,同时package.json文件中bin字段包含的文件会被链接到/usr/local/bin/。/usr/local/bin/
是在PATH环境变量中默认定义的,因此就可以直接在命令行中运行supervisorscript.js命令了。
注意:使用全局模式安装的包并不能直接在JavaScript文件中用require获得,因为require不会搜索/usr/local/lib/node_modules/。

3.nodejs调式代码命令:
进入文件比如:node1.js;代码如下:
vara=1;
varb="hello";
varc=function(x){
console.log('hello'+x+a);
};
c(b);
在命令行中使用命令nodedebugnode1.js就可以进行如下调式代码:如图调式代码.png
node基本命令如下:
run:执行脚本,在第一行暂停
restart:重新执行脚本
cont,c:继续执行,直到遇到下一个断点
next,n:单步执行
step,s:单步执行并进入函数
out,o:从函数中步出
setBreakpoint(),sb():在当前行设置断点
setBreakpoint(‘f()’),sb(...):在函数f的第一行设置断点
setBreakpoint(‘script.js’,20),sb(...)在script.js的第20行设置断点
clearBreakpoint,cb(...)清除所有断点
backtrace,bt显示当前的调用栈
list(5)显示当前执行到的前后5行代码
watch(expr)把表达式expr加入监视列表
unwatch(expr)把表达式expr从监视列表移除
watchers显示监视列表中所有的表达式和值
repl在当前上下文打开即时求值环境
kill终止当前执行的脚本
scripts显示当前已加载的所有脚本
version显示V8的版本

4.使用node-inspector调试Node.js
1。使用sudonpminstall-gnode-inspector命令安装node-inspector

回到顶部

了解Node核心模块;
1.常用工具util;util是一个Node.js核心模块,提供常用函数的集合;

varutil=require('util');

util.inherits(constructor,superConstructor)是一个实现对象间的原型继承的函数.


functionBase(){
this.name='base';
this.base=1991;
this.sayHello=function(){
console.log('hello'+this.name);
};
}
Base.prototype.showName=function(){
console.log(this.name);
}
functionSub(){
this.name='Sub';
}
util.inherits(Sub,Base);

varobjBase=newBase();

objBase.showName();//base
objBase.sayHello();//hellobase
console.log(objBase);//Base{name:'base',base:1991,sayHello:[Function]}

varobjSub=newSub();
objSub.showName();//Sub
//objSub.sayHello();//报错,不能继承该方法
console.log(objSub);//Sub{name:'Sub'}


2.util.inspect(object,[showHidden],[depth],[colors])是一个将任意对象转换为字符串的方法,通常用于调试和错误输出。
它至少接受一个参数object,即要转换的对象。
showHidden:是一个可选参数,如果值为true,将会输出更多隐藏信息。
depth:表示最大递归的层数,如果对象很复杂,你可以指定层数以控制输出信息的多少。如果不指定depth,默认会递归2层,
指定为null表示将不限递归层数完整遍历对象。
colors:如果color值为true,输出格式将会以ANSI颜色编码,通常用于在终端显示更漂亮的效果。


varPerson=function(){
this.person="kongzhi";
this.toString=function(){
returnthis.name;
}
};
varobj=newPerson();

console.log(util.inspect(obj));//{person:'kongzhi',toString:[Function]}
console.log(util.inspect(obj,true));


输出如下:

{person:'kongzhi',toString:{[Function][length]:0,[name]:'',[arguments]:null,[caller]:null,[prototype]:
{[constructor]:[Circular]}}}


更多的util的工具函数请看这里https://nodejs.org/api/util.html#util_util_isarray_object3.事件发射器events

events模块只提供了一个对象:events.EventEmitter。EventEmitter的核心就是事件发射与事件监听器功能的封装。
代码如下:


varevents=require('events');
varemitter=newevents.EventEmitter();
//注册自定义事件
emitter.on('someEvent',function(arg1,arg2){
console.log("listen1",arg1,arg2);//listen1kongzhi
});
emitter.on('someEvent',function(arg1,arg2){
console.log('listen2',arg1,arg2);//listen2kongzhi
});

//触发事件
emitter.emit('someEvent','kong','zhi');


EventEmitter常用的API:
EventEmitter.on(event,listener)为指定事件注册一个监听器,接受一个字符串event和一个回调函数listener。
EventEmitter.emit(event,[arg1],[arg2],[...])发射event事件,传递若干可选参数到事件监听器的参数表。
EventEmitter.once(event,listener)为指定事件注册一个单次监听器,即监听器最多只会触发一次,触发后立刻解除该监听器。
EventEmitter.removeListener(event,listener)移除指定事件的某个监听器,listener必须是该事件已经注册过的监听器。
EventEmitter.removeAllListeners([event])移除所有事件的所有监听器,如果指定event,则移除指定事件的所有监听器。

EventEmitter定义了一个特殊的事件error,它包含了“错误”的语义,我们在遇到异常的时候通常会发射error事件。
当error被发射时,EventEmitter规定如果没有响应的监听器,Node.js会把它当作异常,退出程序并打印调用栈。
我们一般要为会发射error事件的对象设置监听器,避免遇到错误后整个程序崩溃。

如下代码:



varevents=require('events');
varemitter=newevents.EventEmitter();
emitter.emit('error');


详细的请看这里https://nodejs.org/api/events.html
4.文件系统fs

1.fs.readFile
fs.readFile(filename,[encoding],[callback(err,data)])是最简单的读取文件的函数。它接受一个必选参数filename,
表示要读取的文件名。第二个参数encoding是可选的,表示文件的字符编码。callback是回调函数,用于接收文件的内容。如果不指定encoding,
则callback就是第二个参数。回调函数提供两个参数err和data,err表示有没有错误发生,data是文件内容。如果指定了encoding,
data是一个解析后的字符串,否则data将会是以Buffer形式表示的二进制数据。
代码如下


varfs=require('fs');

//没有指定encoding,data将会是buffer形式表示的二进制数据
fs.readFile('text.txt',function(err,data){
if(err){
console.log(err);
}else{
console.log(data);
//<Buffer6161616473666466e9a29de9a29de8808ce7aa81e784b6616161647366
//6466e9a29de9a29de8808ce7aa81e784b661616164...>
}
});


fs.readFile('text.txt','utf-8',function(err,data){
if(err){
console.log(err);
}else{
console.log(data);
//aaadsfdf额额而突然aaadsfdf额额而突然aaadsfdf额额而突然aaadsfdf额额而突然aaadsfdf额额而突然aaadsfdf额额而突然
}
});


2.fs.readFileSync

fs.readFileSync(filename,[encoding])是fs.readFile同步的版本。它接受的参数和fs.readFile相同,

而读取到的文件内容会以函数返回值的形式返回。如果有错误发生,fs将会抛出异常,你需要使用try和catch捕捉并处理异常。

3.fs.open

基本语法:fs.open(path,flags,[mode],[callback(err,fd)])是POSIXopen函数的封装,
它接受两个必选参数,path为文件的路径,flags可以是以下值。
r:以读取模式打开文件。
r+:以读写模式打开文件。
w:以写入模式打开文件,如果文件不存在则创建。
w+:以读写模式打开文件,如果文件不存在则创建。
a:以追加模式打开文件,如果文件不存在则创建。
a+:以读取追加模式打开文件,如果文件不存在则创建。

mode参数用于创建文件时给文件指定权限,默认是06661。回调函数将会传递一个文件描述符fd
1.文件权限指的是POSIX操作系统中对文件读取和访问权限的规范,通常用一个八进制数来表示。例如0754表示文件所有者的权限是7(读、写、执行),
同组的用户权限是5(读、执行),其他用户的权限是4(读),写成字符表示就是-rwxr-xr--。
2文件描述符是一个非负整数,表示操作系统内核为当前进程所维护的打开文件的记录表索引
代码如下:


varfs=require('fs');
fs.open('./text2.txt','r',function(err,fd){
if(err){
console.log(err);
}else{
console.log(fd);//10表示当前目录的第十个文件
}
});


回到顶部

ejs模板引擎
app.set是Express的参数设置工具,接受一个键(key)和一个值(value),可用的参数如下所示
1.basepath:基础地址,通常用于res.redirect()跳转。
2.views:视图文件的目录,存放模板文件。
3.viewengine:视图模板引擎。
4.viewoptions:全局视图参数对象。
5.viewcache:启用视图缓存。
6.casesensitiveroutes:路径区分大小写。
7.strictrouting:严格路径,启用后不会忽略路径末尾的“/”。
8.jsonpcallback:开启透明的JSONP支持。
来看看app.js中通过以下两个语句设置了模板引擎和页面模板的位置.

app.set('views',path.join(__dirname,'views'));
app.set('viewengine','ejs');

为设置存放模板文件的路径,其中__dirname为全局变量,存放当前脚本所在目录
表明要使用的模板引擎是ejs,页面模板在views子目录下。
在routes/index.js的函数下通过如下语句渲染模板引擎,代码如下:
router.get('/',function(req,res,next){
res.render('index',{title:'Express'});
});
router.get("/")的含义是:截取Get请求方式的url中含有/的请求.
res.render的功能是调用模板引擎,并将其产生的页面直接返回给客户端。它接受两个参数,第一个是模板的名称,
即views目录下的模板文件名,不包含文件的扩展名;第二个参数是传递给模板的数据,用于模板翻译.
index.ejs内容如下:


<!DOCTYPEhtml>
<html>
<head>
<title><%=title%></title>
<linkrel='stylesheet'href='/stylesheets/style.css'/>
</head>
<body>
<h1><%=title%></h1>
<p>Welcometo<%=title%></p>
</body>
</html>


上面代码其中有两处<%=title%>,用于模板变量显示,它们在模板翻译时会被替换成Express,因为res.render传递了{title:'Express'}。
ejs有以下3种标签:
<%code%>:JavaScript代码。
<%=code%>:显示替换过HTML特殊字符的内容。
<%-code%>:显示原始HTML内容。
对于HTML页面的<head>部分以及页眉页脚中的大量内容是重复的内容,我们可以这样前面加<%-includeheader%>,后面加<%-includefooter%>.

学会使用片段视图(partials)
Express的视图系统还支持片段视图(partials),它就是一个页面的片段,通常是重复的内容,用于迭代显示。
通过它你可以将相对独立的页面块分割出去,而且可以避免显式地使用for循环。
1.安装express-partials。进入项目的根目录运行如下命令:
sudonpminstallexpress-partials
2.下载成功后.在app.js中引用此插件varpartials=require(‘express-partials’);
3.然后再开启此插件,在app.js中app.set(‘viewengine’,‘ejs’);代码后添加如下代码:
app.use(partials());
下面我们可以来使用片段视图了partials,做一个demo如下:
在app.js中新增以下内容:


//片段视图
app.get('/list',function(req,res){
res.render('list',{
title:'List',
items:[1991,'byvoid','express','Node.js']
});
});


在views目录下新建list.ejs,内容是:



<!DOCTYPEhtml>
<html>
<head>
<title><%=title%></title>
<linkrel='stylesheet'href='/stylesheets/style.css'/>
</head>
<body>
<ul><%-partial('listitem',items)%></ul>
</body>
</html>


同时新建listitem.ejs,内容是:
<li><%=listitem%></li>
重启后,在浏览器访问http://127.0.0.1:3000/list即可看到列表页面;
在源代码看到如下代码:


<!DOCTYPEhtml>
<html>
<head>
<title>List</title>
<linkrel='stylesheet'href='/stylesheets/style.css'/>
</head>
<body>
<ul><li>1991</li><li>byvoid</li><li>express</li><li>Node.js</li></ul>
</body>
</html>


partial是一个可以在视图中使用函数,它接受两个参数,第一个是片段视图的名称,第二个可以是一个对象或一个数组,如果是一个对象,
那么片段视图中上下文变量引用的就是这个对象;如果是一个数组,那么其中每个元素依次被迭代应用到片段视图。片段视图中
上下文变量名就是视图文件名,例如上面的'listitem'.

理解视图助手:

它的功能是允许在视图中访问一个全局的函数或对象,不用每次调用视图解析的时候单独传入。前面提到的partial就是一个视图助手。

视图助手有两类,分别是静态视图助手和动态视图助手。这两者的差别在于,静态视图助手可以是任何类型的对象,包括接受任意参数的函数,
但访问到的对象必须是与用户请求无关的,而动态视图助手只能是一个函数,这个函数不能接受参数,但可以访问req和res对象。
如下代码:
1.在app.js加入如下代码:


//视图助手
varutil=require('util');varhelper=require('./routes/helper');
app.use('/helper',helper);

//dynamicHelper
app.locals.inspect=function(obj){
returnutil.inspect(obj,true);
}
app.locals.headers=function(req,res){
returnreq.headers
}


2.在routes文件夹下新建一个helper.js,代码如下:


varexpress=require('express');
varrouter=express.Router();
router.get('/',function(req,res){
res.render('helper',{
title:'Helper',
_req:req,
_res:res
});
});
module.exports=router;


3.在views文件下新建helper.ejs;代码如下:


<!DOCTYPEhtml>
<html>
<head>
<title><%=title%></title>
<linkrel='stylesheet'href='/stylesheets/style.css'/>
</head>
<body>
<%=inspect(headers(_req,_res))%>
</body>
</html>
重启后,在浏览器访问如下:http://127.0.0.1:3000/helper













回到顶部

Express
如果一个包是某个工程依赖,那么我们需要在工程的目录下使用本地模式安装这个包,如果要通过命令行调用这个包中的命令,则需要用全局模式安装.
因此按理说我们使用本地模式安装Express即可。但是Express像很多框架一样都提供了QuickStart(快速开始)工具,这个工具的功能通常
是建立一个网站最小的基础框架,为了使用这个工具,我们需要用全局模式安装Express,因为只有这样我们才能在命令行中使用它。运行以下命令:
1.首先全局安装expresssudonpminstall-gexpress
上面安装后,发现使用express--help会提示如下:ExpressCommandnotfound
解决方法:在安装一个包,使用命令:sudonpminstall-gexpress-generator
原因:express3+已经把创建一个APP的功能分离出来为express-generator,没它你创建不了应用程序.
我们接着使用express--help就可以看到很多其他命令了;
2.通过以下命令建立网站的基本结构:express-tejemicroblog
网站的目录文件名就叫microblog,会在该目录下生成该文件夹;接着执行如下命令:cdmicroblog&&npminstall
会自动安装jade模板引擎和express,而不是ejs模板引擎了,我们可以查看网站的根目录中的package.json文件,内容如下:


{
"name":"microblog",
"version":"0.0.0",
"private":true,
"scripts":{
"start":"node./bin/www"
},
"dependencies":{
"body-parser":"~1.13.2",
"cookie-parser":"~1.3.5",
"debug":"~2.2.0",
"express":"~4.13.1",
"jade":"~1.11.0",
"morgan":"~1.6.1",
"serve-favicon":"~2.3.0"
}
}


解决方法:版本不一样,用错命令了,应该是express-emicroblog(-e就是ejs模板)
我们再来查看一下package.json内容如下:


{
"name":"microblog",
"version":"0.0.0",
"private":true,
"scripts":{
"start":"node./bin/www"
},
"dependencies":{
"body-parser":"~1.13.2",
"cookie-parser":"~1.3.5",
"debug":"~2.2.0",
"ejs":"~2.3.3",
"express":"~4.13.1",
"morgan":"~1.6.1",
"serve-favicon":"~2.3.0"
}
}


如上看到有ejs了;
3.在当前的项目目录下运行npminstall把package.json的所有依赖包都生成出到本地项目microblog目录下,会生成一个node_modules
模块内;

4.使用nodeapp.js没有效果,需要使用如下命令即可npmstart
出现情况:访问不到页面
解决方法:版本不一样,用错命令了,应该是npmstart
接着访问:http://127.0.0.1:3000/就可以看到welcometoexpress页面的界面了;
要关闭服务器的话,在终端中按Ctrl+C。注意,如果你对代码做了修改,要想看到修改后的效果必须重启服务器,
也就是说你需要关闭服务器并再次运行才会有效果。如果觉得有些麻烦,可以使用supervisor实现监视代码修改和自动重启
5.下面是我们项目的目前的工程结构如下:




6了解工程的结构;
6-1.app.js


varexpress=require('express');//引入express模块
varpath=require('path');//引入path模块
varfavicon=require('serve-favicon');
varlogger=require('morgan');
varcookieParser=require('cookie-parser');
varbodyParser=require('body-parser');

//routes是一个文件夹形式的本地模块,即./routes/index.js,它的功能是为指定路径组织返回内容,相当于MVC架构中的控制器。
varroutes=require('./routes/index');
varusers=require('./routes/users');

//函数创建了一个应用的实例,后面的所有操作都是针对于这个实例进行的
varapp=express();

//app.set是Express的参数设置工具,接受一个键(key)和一个值(value),可用的参数如下所示
//1.basepath:基础地址,通常用于res.redirect()跳转。
//2.views:视图文件的目录,存放模板文件。
//3.viewengine:视图模板引擎。
//4.viewoptions:全局视图参数对象。
//5.viewcache:启用视图缓存。
//6.casesensitiveroutes:路径区分大小写。
//7.strictrouting:严格路径,启用后不会忽略路径末尾的“/”。
//8.jsonpcallback:开启透明的JSONP支持。

//viewenginesetup
app.set('views',path.join(__dirname,'views'));
app.set('viewengine','ejs');

//Express依赖于connect,提供了大量的中间件,可以通过app.use启用
//1.bodyParser的功能是解析客户端请求,通常是通过POST发送的内容。
//2.router是项目的路由支持。
//3.static提供了静态文件支持。
//4.errorHandler是错误控制器。
//uncommentafterplacingyourfaviconin/public
//app.use(favicon(path.join(__dirname,'public','favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:false}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname,'public')));

app.use('/',routes);
app.use('/users',users);

//catch404andforwardtoerrorhandler
app.use(function(req,res,next){
varerr=newError('NotFound');
err.status=404;
next(err);
});

//errorhandlers

//developmenterrorhandler
//willprintstacktrace
if(app.get('env')==='development'){
app.use(function(err,req,res,next){
res.status(err.status||500);
res.render('error',{
message:err.message,
error:err
});
});
}

//productionerrorhandler
//nostacktracesleakedtouser
app.use(function(err,req,res,next){
res.status(err.status||500);
res.render('error',{
message:err.message,
error:{}
});
});

module.exports=app;


2.routes/index.js

routes/index.js是路由文件,相当于控制器,用于组织展示的内容:


varexpress=require('express');
varrouter=express.Router();
router.get('/',function(req,res,next){
res.render('index',{title:'Express'});
});

module.exports=router;
//上面的代码
router.get('/',function(req,res,next){
res.render('index',{title:'Express'});
});


其中只有一个语句res.render('index',{title:'Express'}),功能是调用模板解析引擎,翻译名为index的模板,
并传入一个对象作为参数,这个对象只有一个2属性,即title:'Express'。

3.index.ejs
index.ejs是模板文件,即routes/index.js中调用的模板,内容是:


<!DOCTYPEhtml>
<html>
<head>
<title><%=title%></title>
<linkrel='stylesheet'href='/stylesheets/style.css'/>
</head>
<body>
<h1><%=title%></h1>
<p>Welcometo<%=title%></p>
</body>
</html>


其中包含了形如<%=title%>的标签,功能是显示引用的变量,即res.render函数第二个参数传入的对象的属性。

4.在bin目录下有一个文件www,内容如下:


//Moduledependencies.
varapp=require('../app');
vardebug=require('debug')('microblog:server');
varhttp=require('http');

//GetportfromenvironmentandstoreinExpress.
varport=normalizePort(process.env.PORT||'3000');
app.set('port',port);

//CreateHTTPserver.
varserver=http.createServer(app);

//Listenonprovidedport,onallnetworkinterfaces.
server.listen(port);
server.on('error',onError);
server.on('listening',onListening);

//Normalizeaportintoanumber,string,orfalse.
functionnormalizePort(val){
varport=parseInt(val,10);

if(isNaN(port)){
//namedpipe
returnval;
}

if(port>=0){
//portnumber
returnport;
}

returnfalse;
}

//EventlistenerforHTTPserver"error"event.
functiononError(error){
if(error.syscall!=='listen'){
throwerror;
}

varbind=typeofport==='string'
?'Pipe'+port
:'Port'+port;

//handlespecificlistenerrorswithfriendlymessages
switch(error.code){
case'EACCES':
console.error(bind+'requireselevatedprivileges');
process.exit(1);
break;
case'EADDRINUSE':
console.error(bind+'isalreadyinuse');
process.exit(1);
break;
default:
throwerror;
}
}
//EventlistenerforHTTPserver"listening"event.
functiononListening(){
varaddr=server.address();
varbind=typeofaddr==='string'
?'pipe'+addr
:'port'+addr.port;
debug('Listeningon'+bind);
}


上面的代码最主要做的工作时,创建一个http服务器,并且监听默认的端口号是3000;

express()内置方法理解如下:
1.express()用来创建一个Express的程序。express()方法是express模块导出的顶层方法。如下代码:



varexpress=require('express');
varapp=express();


内置方法如下:
1.express.static(root,[options]):
express.static是Express中唯一的内建中间件。它以server-static模块为基础开发,负责托管Express应用内的静态资源。
1.参数root为静态资源的所在的根目录。
例如,假设在public目录放置了图片、CSS和JavaScript文件,你就可以:
app.use(express.static(path.join(__dirname,'public')));
现在,public目录下面的文件就可以访问了;比如如下css文件:http://127.0.0.1:3000/stylesheets/style.css注意:所有文件的路径都是相对于存放目录的(这里存放目录是public),因此,存放静态文件的目录名不会出现在URL中。
如果你的静态资源存放在多个目录下面,你可以多次调用express.static中间件:如下代码:


app.use(express.static('public'));
app.use(express.static('files'));


访问静态资源文件时,express.static中间件会根据目录添加的顺序查找所需的文件.
如果你希望所有通过express.static访问的文件都存放在一个“虚拟(virtual)”目录(即目录根本不存在)下面,
可以通过为静态资源目录指定一个挂载路径的方式来实现,如下所示:
app.use('/static',express.static('public'));
比如如下css文件:http://127.0.0.1:3000/static/stylesheets/style.css

回到顶部

理解路由控制
1.工作原理:
访问http://localhost:3000/,浏览器会向服务器发送请求,app会解析请求的路径,调用相应的逻辑。
app.use('/',routes);它的作用是routes文件夹下规定路径为“/”默认为index,而index.js代码如下:
router.get('/',function(req,res,next){
res.render('index',{title:'Express'});
});
通过res.render('index',{title:'Express'})调用视图模板index,传递title变量。最终视图模板生成HTML页面,返回给浏览器.
返回内容如下:


<!DOCTYPEhtml>
<html>
<head>
<title>Express</title>
<linkrel='stylesheet'href='/stylesheets/style.css'/>
</head>
<body>
<h1>Express</h1>
<p>WelcometoExpress</p>
</body>
</html>


浏览器在接收到内容以后,经过分析发现要获取/stylesheets/style.css,因此会再次向服务器发起请求。app.js中并没有一个路由规则
指派到/stylesheets/style.css,但app通过app.use(express.static(path.join(__dirname,'public')))配置了静态文件服务器,
因此/stylesheets/style.css会定向到app.js所在目录的子目录中的文件public/stylesheets/style.css.
这是一个典型的MVC架构,浏览器发起请求,由路由控制器接受,根据不同的路径定向到不同的控制器。控制器处理用户的具体请求,
可能会访问数据库中的对象,即模型部分。控制器还要访问模板引擎,生成视图的HTML,最后再由控制器返回给浏览器,完成
一次请求。

2.理解路由规则:
当我们在浏览器中访问譬如http://127.0.0.1:3000/a这样不存在的页面时,服务器会在响应头中返回404NotFound错误,浏览器显示如图



这是因为/a是一个不存在的路由规则.
如何创建路由规则?
假设我们要创建一个地址为/hello的页面,内容是当前的服务器时间,让我们看看具体做法。打开app.js;
1.在route下新建一个文件hello.js文件;代码如下:


varexpress=require('express');
varrouter=express.Router();
router.get('/',function(req,res,next){
res.send('Thetimeis'+newDate().toString());
});
module.exports=router;


2.在app.js头部引入hello模块;添加如下代码:
varhello=require('./routes/hello');

3.在已有的路由规则app.use('/users',users);后面添加一行:
app.use('/hello',hello);
重启服务器,现在我们就可以在浏览器下访问http://127.0.0.1:3000/hello就可以看到当前服务器的时间了.

3.理解路径匹配
Express还支持更高级的路径匹配模式。例如我们想要展示一个用户的个人页面,路径为/users/[username],可以用下面的方法定义路由规则:
在app.js加入如下代码:


//路径匹配
app.get('/users/:username',function(req,res){
res.send('user:'+req.params.username);
});


重启,浏览器访问http://127.0.0.1:3000/users/aa后,打印如下:




路径规则/users/:username会被自动编译为正则表达式,类似于\/users\/([^\/]+)\/?这样的形式。
路径参数可以在响应函数中通过req.params的属性访问。

4.理解REST风格的路由规则
Express支持REST风格的请求方式,REST的意思是表征状态转移(RepresentationalStateTransfer),它是一种基于HTTP协议的网络应用
的接口风格,充分利用HTTP的方法实现统一风格接口的服务。HTTP协议定义了以下8种标准的方法。
1.GET:请求获取指定资源。
2.HEAD:请求指定资源的响应头。
3.POST:向指定资源提交数据。
4.PUT:请求服务器存储一个资源。
5.DELETE:请求服务器删除指定资源。
6.TRACE:回显服务器收到的请求,主要用于测试或诊断。
7.CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
8.OPTIONS:返回服务器支持的HTTP请求方法。
根据REST设计模式,GET、POST、PUT和DELETE方法通常分别用于实现以下功能。
GET:获取
POST:新增
PUT:更新
DELETE:删除
一:理解控制权转移
Express支持同一路径绑定多个路由响应函数,我们在app.js代码加入如下代码;例如:


app.all('/user/:username',function(req,res){
res.send('allmethodscaptured');
});
app.get('/user/:username',function(req,res){
res.send('user:'+req.params.username);
});


但当你访问任何被这两条同样的规则匹配到的路径时,会发现请求总是被前一条路由规则捕获,后面的规则会被忽略。
原因是Express在处理路由规则时,会优先匹配先定义的路由规则,因此后面相同的规则被屏蔽。
Express提供了路由控制权转移的方法,即回调函数的第三个参数next,通过调用next(),会将路由控制权转移给后面的规则,例如:
代码改为如下:


app.all('/user/:username',function(req,res,next){
console.log('allmethodscaptured');
next();
});
app.get('/user/:username',function(req,res){
res.send('user:'+req.params.username);
});


上面的app.all函数,它支持把所有的请求方式绑定到同一个响应函数.
我们发现当访问被匹配到的路径时,如:http://127.0.0.1:3000/user/aa会发现终端中打印了allmethodscaptured,
而且浏览器中显示了user:aa。这说明请求先被第一条路由规则捕获,完成console.log使用next()转移控制权,
又被第二条规则捕获,向浏览器返回了信息。
这是一个非常有用的工具,可以让我们轻易地实现中间件,而且还能提高代码的复用程度。例如我们针对一个用户查询信息和修改信息的操作,
分别对应了GET和PUT操作,而两者共有的一个步骤是检查用户名是否合法,因此可以通过next()方法实现:
在app.js添加如下代码:


//控制权转移测试
varusers={'cc2':{
name:'Carbo',
website:'http://www.byvoid.com'
}
};
app.all('/user2/:username',function(req,res,next){//检查用户是否存在
if(users[req.params.username]){
next();
}else{
next(newError(req.params.username+'doesnotexist.'));
}
});

app.get('/user2/:username',function(req,res){
//用户一定存在,直接展示
res.send(JSON.stringify(users[req.params.username]));
});
app.put('/user2/:username',function(req,res){//修改用户信息
res.send('Done');
});


当在浏览器访问http://127.0.0.1:3000/user2/cc2会打印出一个对象出来,否则错误消息提示;
如上:app.all定义的这个路由规则实际上起到了中间件的作用,把相似请求的相同部分提取出来,有利于代码维护其他next方法如果接受了参数,
即代表发生了错误。使用这种方法可以把错误检查分段化,降低代码耦合度。

回到顶部

学习使用node建立微博网站
一:功能设计点如下:
1.微博应该以用户为中心,因此需要有用户的注册和登录功能。
2.微博网站最核心的功能是信息的发表,这个功能涉及许多方面,包括数据库访问、前端显示等。
二:路由规划
根据功能设计,我们把路由按照以下方案规划。
1./:首页
2./u/[user]:用户的主页
3./post:发表信息
4./reg:用户注册
5./login:用户登录
6./logout:用户登出
三:代码设计如下:
打开index.js,把Routes部分修改为:
router.get('/',function(req,res){});
router.get('/u/:user',function(req,res){});
router.post('/post',function(req,res){});
router.get('/reg',function(req,res){});
router.post('/reg',function(req,res){});
router.get('/login',function(req,res){});
router.post('/login',function(req,res){});
router.get('/logout',function(req,res){});

其中/post、/login和/reg由于要接受表单信息,因此使用app.post注册路由。/login和/reg
还要显示用户注册时要填写的表单,所以要以app.get注册。同时在routes/index.js中添加相应的函数.

界面和模板引擎划分如下:
首页:index.ejs
用户首页:user.ejs
登陆页面:login.ejs
注册页面:reg.ejs
公用页面:
header.ejs(顶部导航条)、
alert.ejs(顶部下方错误信息显示)、
footer(底部显示)、
say.ejs(发布微博)、
posts.ejs(按行按列显示已发布的微博)
2.先安装mongodb数据库
1.登录网站https://www.mongodb.org/下载mongodb数据库.下载后的文件命名为mongodb.
2.进入mongodb的根目录在终端输入:sudomkdir-p/data/db(创建/data/db目录)
3.在终端输入:sudochown-R你的系统登录用户名/data/db
4.进入mongodb的"bin"目录,使用命令“./mongod”启动mongoDBserver,

启动成功后最后一行应该是端口号,如配图,出现配图就能肯定你的Mongodb已经安装成了





5.新建终端标签,进入mongodb/bin目录下并输入./mongo登陆到数据库;如下图所示:




3.为了在Node.js中使用MongDB数据库,需要安装mongodb模块,打开package.json文件,在dependencies属性中添加一行代码,即:
"mongodb":">=1.4.8",接着进入项目的根目录运行"npminstall"命令更新依赖的模块。
接下来在项目的根目录下创建settings.js文件,该文件用于保存数据库信息,包括数据库名称、数据库地址和cookieSecret等,
settings.js文件代码如下:


module.exports={
cookieSecret:'microblogKongzhi',//用于cookie加密,与数据库无关
db:'microblog',//数据库名称
host:'127.0.0.1'//数据库地址
};


接下来在项目根目录下创建models文件夹,并在models文件夹下创建db.js文件,该文件用于创建数据库连接,代码如下:



varsettings=require('../settings'),//加载保存数据库基本信息的模块
Db=require('mongodb').Db,//加载MongDB数据库依赖模块,并调用相关对象
Server=require('mongodb').Server;

//设置数据库名称、数据库地址和数据库默认端口号创建一个数据库实例,然后通过module.exports输出创建的数据库连接
module.exports=newDb(settings.db,newServer(settings.host,27017),{safe:true});
//mongodb数据库服务器的默认端口号:27017



4.启动mongDb报如下错误:couldn'tconnecttoserver127.0.0.1:27017src/mongo/shell/mongo.js
mongdb启动的时候会报如上标题的错误,如下图错误:



1、若数据库出现如上不能连接的原因,可能是data目录下的mongod.lock文件问题,可以用如下命令修复:
进入bin目录下,运行如下命令进行修复:
./mongod--repair
或者直接删除mongod.lock;如下命令:
rm-f/usr/local/mongodb/data/db/mongod.lock
然后再启动mongodb;

4.会话支持
会话是一种持久的网络协议,用于完成服务器和客户端之间的一些交互行为。比如客户端cookie;Cookie是一些存储在客户端的信息,
每次连接的时候由浏览器向服务器递交,服务器也向浏览器发起存储Cookie的请求,依靠这样的手段服务器可以识别客户端。
具体来说,浏览器首次向服务器发起请求时,服务器生成一个唯一标识符并发送给客户端浏览器,浏览器将这个唯一标识符存储在Cookie中,
以后每次再发起请求,客户端浏览器都会向服务器传送这个唯一标识符,服务器通过这个唯一标识符来识别用户。
为了实现把会话信息存储于数据库这一功能,需要安装express-session和connect-mongo两个依赖包,打开package.json文件,
在dependencies属性中添加代码,即:
"connect-mongo":">=0.1.7",
"express-session":"~1.0.4",
然后运行"npminstall"命令更新依赖的模块。接下来打开app.js文件,添加如下代码:


//使用时新添加的,上面的依赖包是创建文件时自带的。
varsettings=require('./settings');//数据库连接依赖包
//session会话存储于数据库依赖包(与教程中的区别)
varsession=require('express-session');//session使用
varMongoStore=require('connect-mongo')(session);//mongodb使用
//提供session支持(与教程中的区别)
app.use(session({
secret:settings.cookieSecret,
key:settings.db,//cookiename
cookie:{maxAge:1000*60*60*24*30},//30days
resave:false,
saveUninitialized:true,
store:newMongoStore({
url:'mongodb://127.0.0.1/'+settings.db
})}));


注意:1.新添加到app.js的代码与教程中的代码的区别。由于新版本的express不再支持session包,需要自己安装express-session包,
因此需要先添加“express-session”包的引用,再把该引用作为参数传递给“connect-mongo”引用;同时针对提供session支持的代码也要稍作修改。
如果使用书中的代码将会报错。
2.url:'mongodb://127.0.0.1/'+settings.db一定要改成127.0.0.1,不能是localhost,否则会报如下错误:



5.用户注册功能模块;
1.首先需要在app.js加载路由控制,代码如下:
varroutes=require('./routes/index');
2.接着需要在app.js中定义匹配路由;代码如下:
app.use('/',routes);//指向了routes目录下的index.js文件
3.在routes文件下的index.js代码如下:


varexpress=require('express');
varrouter=express.Router();
router.get('/',function(req,res){
res.render('index',{title:'主页'});
});
router.get('/reg',function(req,res){
res.render('reg',{title:'用户注册',});
});
module.exports=router;


4.看下注册模块reg.ejs代码如下:


<%-includeheader.ejs%>
<formmethod="post">
<h3class="title">用户注册</h3>

<divclass="line">
<labelfor="uname">姓名:</label>
<inputtype="text"id="name"name="username"/>
</div>
<divclass="line">
<labelfor="upwd">密码:</label>
<inputtype="password"id="pwd"name="userpwd"/>
</div>
<divclass="line">
<labelfor="upwd2">确认密码:</label>
<inputtype="password"id="upwd2"name="pwdrepeat"/>
</div>
<divclass="line">
<buttontype="submit"id="btnreg">注册</button>
</div>
</form>
<%-includefooter.ejs%>
其中header.ejs代码如下:<!DOCTYPEhtml>
<html>
<head>
<title><%=title%></title>
<linkrel='stylesheet'href='/stylesheets/style.css'/>
</head>
<body>
<divid="header">
<ulid="menus">
<li><ahref="/"class="menuintroduce">简易博客</a></li>
<li><ahref="/"class="menu">首页</a></li>

<li><ahref="/login"class="menu">登陆</a></li>
<li><ahref="/reg"class="menu">注册</a></li>
</ul>
</div>
<divid="content">
footer.ejs代码如下:
</div>
<divid="footer">铜板街欢迎你~</div>
</body>
</html>


我们重庆服务器进入项目的根目录运行命令npmstart
在浏览器下访问http://127.0.0.1:3000/reg可以看到如下界面:




5.首先需要安装connect-flash,我们直接在package.json加入connect-flash依赖项;代码如下:
"connect-flash":"*",然后直接进入项目的根目录后执行命令npminstall命令,把依赖项加载出来.
作用是:保存的变量只会在用户当前和下一次的请求中被访问,之后会被清除,通过它我们可以很方便地实现页面的通知
和错误信息显示功能。因为用户注册功能需要实现"显示注册成功或错误的信息"的功能,所以需要使用它.
6.打开app.js文件,添加如下代码:
//引入flash模块来实现页面通知
varflash=require('connect-flash');
app.use(flash());//定义使用flash功能
实现用户注册功能,即实现用户注册处理post请求的功能。

6-1用户模型
首先我们在models文件夹下新建user.js文件,该文件的作用把新用户注册信息存储于数据库,以及从数据库读取指定用户的信息的功能.
User是一个描述数据的对象,即MVC架构中的模型。模型是与数据打交道的工具
user.js代码如下:
该文件的功能有2点:
1.把新用户注册信息存储于数据库。
2.从数据库读取指定用户的信息的功能.
代码如下:


varmongodb=require('./db');//加载数据库模块
//User构造函数,用于创建对象
functionUser(user){
this.name=user.name;
this.password=user.password;
};
//User对象方法:把用户信息存入Mongodb
User.prototype.save=function(callback){
varuser={//用户信息
name:this.name,
password:this.password
};
//打开数据库
mongodb.open(function(err,db){
if(err){
returncallback(err);
}
//读取users集合,users相当于数据库中的表
db.collection('users',function(err,collection){//定义集合名称users
if(err){
mongodb.close();
returncallback(err);
}
//把user对象中的数据,即用户注册信息写入users集合中
collection.insert(user,{safe:true},function(err,user){
mongodb.close();
callback(err,user);
});
});
})
};
//User对象方法:从数据库中查找指定用户的信息
User.get=functionget(username,callback){
mongodb.open(function(err,db){
if(err){
returncallback(err);
}
//读取users集合
db.collection('users',function(err,collection){
if(err){
mongodb.close();
returncallback(err);
}
//从users集合中查找name属性为username的记录
collection.findOne({name:username},function(err,doc){
mongodb.close();
if(doc){
//封装查询结果为User对象
varuser=newUser(doc);
callback(err,user);
}else{
callback(err,null);
}
});
});
});
};
//输出User对象
module.exports=User;


6-2路由转发-当做控制器,放在routes文件夹下的index.js编写

index.js中的代码,引入依赖模块,完善"router.post('/reg',function(req,res){});"代码,代码修改如下:


varexpress=require('express');
varrouter=express.Router();

//加载生成MD5值依赖模块
varcrypto=require('crypto');//加密和解密模块
varUser=require('../models/user');

router.get('/',function(req,res){
res.render('index',{title:'主页'});
});

router.get('/reg',function(req,res){
res.render('reg',{title:'用户注册',});
});
router.post('/reg',function(req,res){
//用户名和密码不能为空
if(req.body.username==""||req.body.userpwd==""||req.body.pwdrepeat==""){
//使用req.body.username获取提交请求的用户名,username为input的name

req.flash('error',"输入框不能为空!");
returnres.redirect('/reg');//返回reg页面
}
//两次输入的密码不一致,提示信息
if(req.body.userpwd!=req.body.pwdrepeat){
req.flash("error",'两次输入密码不一致!');
returnres.redirect('/reg');
}
//把密码转换为MD5值
varmd5=crypto.createHash('md5');
varpassword=md5.update(req.body.userpwd).digest('base64');

//用新注册用户信息对象实例化User对象,用于存储新注册用户和判断注册用户是否存在
varnewUser=newUser({
name:req.body.username,
password:password,
});
//检查用户是否存在
User.get(newUser.name,function(err,user){
//如果用户存在的话
if(user){
err='Usernamealreadyexists.';
}
if(err){
req.flash('error',err);//保存错误信息,用于界面显示提示
returnres.redirect('/reg');
}
//用户不存在的时候保存用户
newUser.save(function(err){
if(err){
req.flash('error',err);
returnres.redirect('/reg');
}
req.session.user=newUser;//保存用户名,用于判断用户是否已登录
req.flash('success',req.session.user.name+'注册成功');
res.redirect('/');
});

});
});
module.exports=router;


理解上面代码的知识点:
1.req.body就是POST请求信息解析过后的对象,例如我们要访问用户传递的password域的值,只需访问req.body['password']即可。
2.req.flash是Express提供的一个奇妙的工具,通过它保存的变量只会在用户当前和下一次的请求中被访问,之后会被清除,
通过它我们可以很方便地实现页面的通知和错误信息显示功能。
3.res.redirect是重定向功能,通过它会向用户返回一个303SeeOther状态,通知浏览器转向相应页面。
4.crypto是Node.js的一个核心模块,功能是加密并生成各种散列,使用它之前首先要声明varcrypto=require('crypto')。
我们代码中使用它计算了密码的散列值。
5.User是我们设计的用户对象,用前需要通过varUser=require('../models/user')引用。
6.User.get的功能是通过用户名获取已知用户,在这里我们判断用户名是否已经存在。User.save可以将用户对象的修改写入数据库。
7.通过req.session.user=newUser向会话对象写入了当前用户的信息,在后面我们会通过它判断用户是否已经登录。

6-3视图交互
为了实现用户不同登录状态下显示不同的页面的成功和错误等提示信息,我们需要创建视图助手,在视图中获取session中
的数据和要显示的错误或成功的信息,在app.js中添加如下代码:
切记:需要在app.use(flash());下添加下面的代码;要先链接数据库,否则会报错;
代码如下:


//为了实现用户不同登录状态下显示不同的页面成功或者错误提示信息
app.use(function(req,res,next){
//res.locals.xxx实现xxx变量全局化,在其他页面直接访问变量名即可
//访问session数据:用户信息
res.locals.user=req.session.user;
//获取要显示错误信息
varerror=req.flash('error');//获取flash中存储的error信息
res.locals.error=error.length?error:null;

//获取要显示成功信息
varsuccess=req.flash('success');
res.locals.success=success.length?success:null;
next();//控制权转移,继续执行下一个app。use()
});
//定义匹配路由
app.use('/',routes);//指向了routes目录下的index.js文件


app.locals对象是一个javascript对象,它的属性就是程序本地的变量。一旦设定,app.locals的各属性值将贯穿程序的整个生命周期.
在程序中,你可以在渲染模板时使用这些本地变量。它们是非常有用的,可以为模板提供一些有用的方法,以及app级别的数据。
通过req.app.locals,Locals可以在中间件中使用.

reg.ejs代码如下:


<%-includeheader.ejs%>
<%-includealert.ejs%>
<formmethod="post">
<h3class="title">用户注册</h3>

<divclass="line">
<labelfor="uname">姓名:</label>
<inputtype="text"id="name"name="username"/>
</div>
<divclass="line">
<labelfor="upwd">密码:</label>
<inputtype="password"id="pwd"name="userpwd"/>
</div>
<divclass="line">
<labelfor="upwd2">确认密码:</label>
<inputtype="password"id="upwd2"name="pwdrepeat"/>
</div>
<divclass="line">
<buttontype="submit"id="btnreg">注册</button>
</div>
</form>
<%-includefooter.ejs%>


header.ejs代码如下:


<!DOCTYPEhtml>
<html>
<head>
<title><%=title%></title>
<linkrel='stylesheet'href='/stylesheets/style.css'/>
</head>
<body>
<divid="header">
<ulid="menus">
<li><ahref="/"class="menuintroduce">简易博客</a></li>
<li><ahref="/"class="menu">首页</a></li>
<%if(!user){%>
<li><ahref="/login"class="menu">登陆</a></li>
<li><ahref="/reg"class="menu">注册</a></li>
<%}else{%>
<li><ahref="/logout"class="menu">退出</a></li>
<%}%>
</ul>
</div>
<divid="content">


alert.ejs代码如下:


<!--显示成功或错误信息-->
<%if(success){%>
<divclass="alert">
<%=success%>
</div>
<%}%>
<%if(error){%>
<divclass="alert">
<%=error%>
</div>
<%}%>


例如:用户注册时,两次密码输入不一致,点击“注册”按钮后的界面如下图所示





6.用户登录模块
1.打开index.js文件,完善“router.post('/login',function(req,res){});”代码块,代码如下:


router.post('/login',function(req,res){
//生成口令的散列值
varmd5=crypto.createHash('md5');
varpassword=md5.update(req.body.password).digest('base64');
//判断用户名和密码是否存在和正确
User.get(req.body.username,function(err,user){
if(!user){
req.flash('error','用户名不存在');
returnres.redirect('/login');
}
if(user.password!=password){
req.flash('error','用户密码不存在');
returnres.redirect('/login');
}
//保存用户信息
req.session.user=user;
req.flash("success","登录成功");
res.redirect('/');
});
});


7.用户退出功能
打开index.js文件,完善“router.get('/logout',function(req,res){});”代码块,代码如下:



router.get('/logout',function(req,res){
req.session.user=null;//清空session
req.flash('sucess','退出成功!');
res.redirect('/');
});



8.页面权限控制
登陆和注册功能只对未登录的用户有效;发布和退出功能只对已登录的用户有效。如何实现页面权限控制呢?我们可以把用户登录状态检查放到路由中间
件中,在每个路径前增加路由中间件,通过调用next()函数转移控制权,即可实现页面权限控制。因此在index.js中添加checkNotLogin和checkLogin
函数来检测是否登陆,并通过next()转移控制权,检测到未登录则跳转到登录页,检测到已登录则跳转到前一个页。
checkNotLogin该函数使用在用户注册和用户登录上,checkLogin函数使用在发布和退出功能上;
我们在index.js假如如下两个函数的代码:


functioncheckNotLogin(req,res,next){
//如果从session里面获取用户已存在的话
if(req.session.user){
req.flash('error','已登录');
returnres.redirect('/');
}
next();
//控制权转移:当不同路由规则向同一路径提交请求时,在通常情况下,请求总是被第一条路由规则捕获,
//后面的路由规则将会被忽略,为了可以访问同一路径的多个路由规则,使用next()实现控制权转移。
}
functioncheckLogin(req,res,next){
if(!req.session.user){
req.flash('error','未登录');
returnres.redirect('/login');
}
//已登录转移到下一个同一路径请求的路由规则操作
next();
}


注册和登陆基本的实现逻辑如下:
1.当我们访问http://127.0.0.1:3000/reg这个网址的时候,会进入注册页面;通过下面的代码转向reg.ejs页面去;
//用户注册
router.get('/reg',checkNotLogin);//页面权限控制,注册功能只对未登录用户可用

router.get('/reg',function(req,res){
res.render('reg',{title:'用户注册',});
});
2.当用户点击注册按钮的时候;通过如下代码判断及跳转;
router.post('/reg',checkNotLogin);
router.post('/reg',function(req,res){
//用户名和密码不能为空
if(req.body.username==""||req.body.userpwd==""||req.body.pwdrepeat==""){
//使用req.body.username获取提交请求的用户名,username为input的name
req.flash('error',"输入框不能为空!");
returnres.redirect('/reg');//返回reg页面
}
//两次输入的密码不一致,提示信息
if(req.body.userpwd!=req.body.pwdrepeat){
req.flash("error",'两次输入密码不一致!');
returnres.redirect('/reg');
}
//把密码转换为MD5值
varmd5=crypto.createHash('md5');
varpassword=md5.update(req.body.userpwd).digest('base64');

//用新注册用户信息对象实例化User对象,用于存储新注册用户和判断注册用户是否存在
varnewUser=newUser({
name:req.body.username,
password:password,
});
//检查用户是否存在
User.get(newUser.name,function(err,user){
if(user){//用户名存在
req.flash('error','Usernamealreadyexists.');//保存错误信息,用于界面显示提示
returnres.redirect('/reg');
}
//用户不存在的时候保存用户
newUser.save(function(err){
if(err){
req.flash('error',err);
returnres.redirect('/reg');
}
req.session.user=newUser;//保存用户名,用于判断用户是否已登录
req.flash('success',req.session.user.name+'注册成功');
res.redirect('/');
});
});
});
首先通过判断用户名和密码是否为空等操作;如果为空的话或者任何通过flash保存信息的话,都是通过如下的思路去转向页面的.如下步骤:
通过flash这句代码req.flash('error',"输入框不能为空!");提示;
之后会调用app.js代码中的如下代码:
//为了实现用户不同登录状态下显示不同的页面成功或者错误提示信息
app.use(function(req,res,next){
//res.locals.xxx实现xxx变量全局化,在其他页面直接访问变量名即可
//访问session数据:用户信息
res.locals.user=req.session.user;
//获取要显示错误信息
varerror=req.flash('error');//获取flash中存储的error信息

res.locals.error=error.length?error:null;

//获取要显示成功信息
varsuccess=req.flash('success');

res.locals.success=success.length?success:null;
next();//控制权转移,继续执行下一个app。use()
});
//定义匹配路由
app.use('/',routes);//指向了routes目录下的index.js文件
然后就转向与index.js了,如下代码:
router.get('/',function(req,res){
res.render('index',{title:'主页'});
});
之后就是渲染index.ejs模板了;代码如下:
<%-includeheader.ejs%>
<%-includealert.ejs%>
<%if(!user){%>
<divid="toppart">
<h2>欢迎来到简易博客</h2>

<p>该博客基于node.js+express+mongoDB来实现的。</p>
<br/>
<ahref="/login"class="btn">登陆</a>
<ahref="/reg"class="btn">注册</a>
</div>
<%}else{%>
<%-includesay.ejs%>
<%}%>
<%-includefooter.ejs%>
首先index.ejs包括头部和尾部及错误信息alert.ejs模板,该模板有2个提示信息,如果有error信息的话,就提示error信息;
否则的话,提示success的信息,该error或者success是通过app.js中的req.flash('success')获取的,然后通过
res.locals.success当做全局变量保存取来.然后再alert.ejs通过success或者error值判断,有就提示消息即可;
上面的时index.ejs模板如果用户没有登录的话,就提示用户登录注册页面,如果已经登录成功的话,就到say.ejs模板去,让用户发表留言:
如下页面:






9.发表微博功能
为了实现发布微博功能,首先创建Post对象,在models文件夹下创建post.js文件,该文件功能与user.js功能类似,用于存储新发布的微博及查询全部
或指定用户的微博,post.js代码如下:


//获取微博和保存微博
varmongodb=require('./db');
//Post构造函数,用于创建对象
functionPost(username,content,time){
this.user=username;//用户名
this.content=content;//发布内容
if(time){
this.time=time;//发布时间
}else{
varnow=newDate();
this.time=now.getFullYear()+"/"+(now.getMonth()+1)+"/"+now.getDate()+""+now.getHours()+":"+now.getSeconds();
}
}

//输出Post对象
module.exports=Post;

//对象方法:保存新发布的微博到数据库
Post.prototype.save=function(callback){
//存入MongoDB数据库
varpost={
user:this.user,
post:this.content,
time:this.time
};
mongodb.open(function(err,db){
if(err){
returncallback(err);
}
//读取posts集合,即数据库表
db.collection('posts',function(err,collection){
if(err){
mongodb.close();
returncallback(err);
}
//为user属性添加索引
collection.ensureIndex('user');
//把发布的微博信息post写入posts表中
collection.insert(post,{safe:true},function(err,post){
mongodb.close();
callback(err,post);
});
});
});
}
//获取全部或指定用户的微博记录
Post.get=functionget(username,callback){
mongodb.open(function(err,db){
if(err){
returncallback(err);
}
//读取posts集合
db.collection('posts',function(err,collection){
if(err){
mongodb.close();
returncallback(err);
}
//查找user属性为username的微博记录,如果username为null则查找全部记录
varquery={};
if(username){
query.user=username;
}
//查找符合条件的记录,并按时间顺序排列
collection.find(query).sort({time:-1}).toArray(function(err,docs){
mongodb.close();
if(err){
callback(err,null);
}
varposts=[];
//遍历查询结果
docs.forEach(function(doc,index){
//把结果封装成Post对象
varpost=newPost(doc.user,doc.post,doc.time);
//把全部结果封装成数组
posts.push(post);
});
callback(null,posts);
});
});
});
};


9-2.打开index.js,引入依赖包和完善发布微博功能的代码如下:


varPost=require("../models/post.js");//加载用户发表微博模块
//发表信息
router.post('/post',checkLogin);//页面权限控制

//发表微博
router.post('/post',function(req,res){//路由规则/post
varcurrentUser=req.session.user;//获取当前用户信息
if(req.body.post==""){//发布信息不能为空
req.flash('error','内容不能为空!');
returnres.redirect('/u/'+currentUser.name);
}
//实例化Post对象
varpost=newPost(currentUser.name,req.body.post);//req.body.post获取用户发表的内容
//调用实例方法,发表微博,并把信息保存到MongoDB数据库
post.save(function(err){
if(err){
req.flash('error',err);
returnres.redirect('/');
}
req.flash('success','发表成功');
res.redirect('/u/'+currentUser.name);
});
});
//用户页面的功能是显示该用户发表的所有微博信息
router.get('/u/:user',function(req,res){
User.get(req.params.user,function(err,user){
//判断用户是否存在
if(!user){
req.flash('error','用户不存在');
returnres.redirect('/');
}
//调用对象的方法用户存在,从数据库获取该用户的微博信息
Post.get(user.name,function(err,posts){
if(err){
req.flash('error',err);
returnres.redirect('/');
}
//调用user模板引擎,并传送数据(用户名和微博集合)
res.render('user',{
title:user.name,
posts:posts
});
});
});
});


实现上面发表微博的总体思路如下:
1.首先在say.ejs模板代码如下:
<formmethod="post"action="/post"id="public">
<inputtype="text"name="post"id="pctn"/>
<buttontype="submit"id="pbtn">发布</button>
</form>
当我输入框输入内容的时候,点击发布的时候,会首先路由到index.js代码中的
router.post('/post',checkLogin);//页面权限控制
首先判断用户是否登录,登录后通过视图助手的方式转到下一个同一路径请求的路由规则操作;
就是下面的post路由了,先判断用户是否为空;如果为空的话,显示不能为空信息,如下代码:
if(req.body.post==""){//发布信息不能为空
req.flash('error','内容不能为空!');
returnres.redirect('/u/'+currentUser.name);
}
接着就路由到router.get('/u/:user',function(req,res){})这个路由了;
接着顺利的话,就渲染user.ejs模板;代码如下:
//调用user模板引擎,并传送数据(用户名和微博集合)
res.render('user',{
title:user.name,
posts:posts
});
因此user.ejs模板代码如下:
<%-includeheader.ejs%>
<%-includealert.ejs%>
<%if(user){%>
<%includesay.ejs%>
<%}%>
<%-includeposts.ejs%>
<%-includefooter.ejs%>
如果内容为空的话,就在当前页面显示内容不能为空信息,如果添加成功的话,也会提示信息;接下来我们再来看看正常的情况下;如下post下的代码:
//实例化Post对象
varpost=newPost(currentUser.name,req.body.post);//req.body.post获取用户发表的内容
//调用实例方法,发表微博,并把信息保存到MongoDB数据库
post.save(function(err){
if(err){
req.flash('error',err);
returnres.redirect('/');
}
req.flash('success','发表成功');
res.redirect('/u/'+currentUser.name);
});
如果正常的情况下,就弹出发表成功文案,并且还是转向路由res.redirect('/u/'+currentUser.name);这个下面去渲染user.esj模板信息了;
最后就渲染posts.ejs代码了;代码如下:
<!--按行按列显示传入的posts的所有内容-->
<divid="bottompart">
<%posts.forEach(function(post,index){//遍历posts内容,每行显示三个
if(index%3==0){%>
<divclass="row">
<%}%>
<divclass="ctn">
<h3><ahref='/u/<%=post.user%>'class="username"><%=post.user%></a>说:</h3>
<p><%=post.time%><br/><%=post.content%></p>
</div>
<%if(index%3==2){%>
</div>
<%}%>
<%});%>
<%if(posts.length%3!=0){%>
</div>
<%}%>
</div>
通过遍历数组,每行放三列发表留言说说;
如下图所示:






10.首页
首页显示所有用户发表的微博,并且按照时间顺序进行排列。首先完善index.js中首页路由规则响应函数的代码如下:


//首页:显示所有的微博,并按照时间先后顺序排列
router.get('/',function(req,res){
//读取所有的用户微博,传递把posts微博数据集传给首页
Post.get(null,function(err,posts){
if(err){
posts=[];
}
//调用模板引擎,并传递参数给模板引擎
res.render('index',{title:'首页',posts:posts});
});
});


上面的是当我们在浏览器下这样访问的话http://127.0.0.1:3000/直接获取posts的数据传递给首页;然后再在index.ejs模板渲染出来;
当然index.ejs模板需要把posts.ejs模板加进来;因此index.ejs代码变为如下:


<%-includeheader.ejs%>
<%-includealert.ejs%>
<%if(!user){%>
<divid="toppart">
<h2>欢迎来到简易博客</h2>

<p>该博客基于node.js+express+mongoDB来实现的。</p>
<br/>
<ahref="/login"class="btn">登陆</a>
<ahref="/reg"class="btn">注册</a>
</div>
<%}else{%>
<%-includesay.ejs%>
<%}%>
<%-includeposts.ejs%>
<%-includefooter.ejs%>


接着我们在http://127.0.0.1:3000/浏览器访问如下效果:





github上的源码如下:
https://github.com/tugenhua0707/node_express_microblog
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: