nodejs开发指南读后感
阅读目录
使用nodejs创建http服务器;
1. 创建http服务器,监听3000端口;
var http = require("http"); http.createServer(function(req,res){ res.writeHead(200, {'Content-Type': 'text/html'}); res.write('<h1>Node2.js</h1>'); res.end('<p>Hello World</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 :当服务器关闭时,该事件被触发。注意不是在用户连接断开时。
代码如下:
var http = require('http'); var server = new http.Server(); server.on('request',function(req,res){ res.writeHead(200, {'Content-Type': 'text/html'}); res.write('<h1>Node.js</h1>'); res.end('<p>Hello World</p>'); }); server.listen(3004); console.log("port is 3004");
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])
urlStr url字符串
parseQueryString 为true时将使用查询模块分析查询字符串,默认为false
slashesDenoteHost
1. 默认为false,//foo/bar 形式的字符串将被解释成 { pathname: ‘//foo/bar' }
2. 如果设置成true,//foo/bar 形式的字符串将被解释成 { host: 'foo', pathname: '/bar' }
var url = require('url'); var a = 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方法
var http = require('http'); var url = require('url'); var util = 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请求的内容
代码如下:
var http = require('http'); var querystring = require('querystring'); var util = require('util'); http.createServer(function(req, res) { var post = ''; 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下 安装命令如下:sudo npm install -g supervisor 运行js命令 直接 supervisor server1.js 即可
2. 使用npm安装包有二种模式:本地模式和全局模式;本地模式如下:sudo npm install 包名
全局模式安装如下:sudo npm install -g 包名 ,他们之间的区别是:
本地模式:该模式是把包安装在当前目录的node_module子目录下,
Node.js 的require在加载模块时会尝试搜寻 node_modules子目录,因此使用npm本地模式安装 的包可以直接被引用。
全局模式:为了减少多重副本而使用全局模式,而是因为本地模式不会注册 PATH 环境变量。举例说明,我们安装 supervisor 是为了在命令行中运行它,
譬如直接运行 supervisor script.js,这时就需要在 PATH 环境变量中注册 supervisor。npm 本地模式仅仅是把包安装到 node_modules 子目录下,
其中 的 bin 目录没有包含在 PATH 环境变量中,不能直接在命令行中调用。而当我们使用全局模 式安装时,npm 会将包安装到系统目录,
譬如 /usr/local/lib/node_modules/,同时 package.json 文 件中 bin 字段包含的文件会被链接到 /usr/local/bin/。/usr/local/bin/
是在PATH 环境变量中默认 定义的,因此就可以直接在命令行中运行 supervisor script.js命令了。
注意:使用全局模式安装的包并不能直接在 JavaScript 文件中用 require 获 得,因为 require 不会搜索 /usr/local/lib/node_modules/。
3. nodejs调式代码命令:
进入文件比如:node1.js;代码如下:
var a = 1;
var b = "hello";
var c = function(x) {
console.log('hello' + x + a);
};
c(b);
在命令行中使用命令 node debug node1.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。使用 sudo npm install -g node-inspector 命令安装 node-inspector
了解Node核心模块;
1.常用工具 util; util 是一个 Node.js 核心模块,提供常用函数的集合;
var util = require('util');
util.inherits(constructor, superConstructor)是一个实现对象间的原型继承的函数.
function Base(){ this.name = 'base'; this.base = 1991; this.sayHello = function(){ console.log('hello'+this.name); }; } Base.prototype.showName = function(){ console.log(this.name); } function Sub(){ this.name = 'Sub'; } util.inherits(Sub, Base); var objBase = new Base(); objBase.showName(); // base objBase.sayHello(); // hellobase console.log(objBase); // Base { name: 'base', base: 1991, sayHello: [Function] } var objSub = new Sub(); 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 颜色编码,通常用于在终端显示更漂亮 的效果。
var Person = function(){ this.person = "kongzhi"; this.toString = function(){ return this.name; } }; var obj = new Person(); 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_object
3.事件发射器 events
events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就 是事件发射与事件监听器功能的封装。
代码如下:
var events = require('events'); var emitter = new events.EventEmitter(); // 注册自定义事件 emitter.on('someEvent',function(arg1,arg2){ console.log("listen1",arg1,arg2); // listen1 kong zhi }); emitter.on('someEvent',function(arg1,arg2){ console.log('listen2',arg1,arg2); // listen2 kong zhi }); // 触发事件 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 事件的对象设置监听器,避免遇到错误后整个程序崩溃。
如下代码:
var events = require('events'); var emitter = new events.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 形式表示的二进制数据。
代码如下:
var fs = require('fs'); // 没有指定encoding , data将会是buffer形式表示的二进制数据 fs.readFile('text.txt',function(err,data){ if(err) { console.log(err); }else { console.log(data); // <Buffer 61 61 61 64 73 66 64 66 e9 a2 9d e9 a2 9d e8 80 8c e7 aa 81 e7 84 b6 61 61 61 64 73 66 // 64 66 e9 a2 9d e9 a2 9d e8 80 8c e7 aa 81 e7 84 b6 61 61 61 64 ... > } });
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)])是 POSIX open 函数的 封装,
它接受两个必选参数,path 为文件的路径, flags 可以是以下值。
r :以读取模式打开文件。
r+ :以读写模式打开文件。
w :以写入模式打开文件,如果文件不存在则创建。
w+ :以读写模式打开文件,如果文件不存在则创建。
a :以追加模式打开文件,如果文件不存在则创建。
a+ :以读取追加模式打开文件,如果文件不存在则创建。
mode 参数用于创建文件时给文件指定权限,默认是 06661。回调函数将会传递一个文件描述符 fd
1.文件权限指的是 POSIX 操作系统中对文件读取和访问权限的规范,通常用一个八进制数来表示。例如 0754 表 示文件所有者的权限是 7 (读、写、执行),
同组的用户权限是 5 (读、执行),其他用户的权限是 4 (读), 写成字符表示就是 -rwxr-xr--。
2 文件描述符是一个非负整数,表示操作系统内核为当前进程所维护的打开文件的记录表索引
代码如下:
var fs = 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. view engine:视图模板引擎。
4. view options:全局视图参数对象。
5. view cache:启用视图缓存。
6. case sensitive routes:路径区分大小写。
7. strict routing:严格路径,启用后不会忽略路径末尾的“ / ”。
8. jsonp callback:开启透明的 JSONP 支持。
来看看app.js 中通过以下两个语句设置了模板引擎和页面模板的位置.
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', '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 内容如下:
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p> </body> </html>
上面代码其中有两处 <%= title %>,用于模板变量显示,它们在模板翻译时会被替换 成 Express,因为 res.render 传递了 { title: 'Express' }。
ejs 有以下3种标签:
<% code %>:JavaScript 代码。
<%= code %>:显示替换过 HTML 特殊字符的内容。
<%- code %>:显示原始 HTML 内容。
对于HTML 页面的<head>部分以及页眉页脚中的大量内容是重复的内容,我们可以这样前面加<%-include header%>,后面加<%-include footer%>.
学会使用片段视图(partials)
Express 的视图系统还支持片段视图(partials),它就是一个页面的片段,通常是重复的 内容,用于迭代显示。
通过它你可以将相对独立的页面块分割出去,而且可以避免显式地使 用 for 循环。
1. 安装express-partials。进入项目的根目录运行如下命令:
sudo npm install express-partials
2. 下载成功后.在app.js 中引用此插件 var partials = require(‘express-partials’);
3. 然后再开启此插件, 在app.js 中 app.set(‘view engine’, ‘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,内容是:
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='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 即可看到 列表页面;
在源代码看到如下代码:
<!DOCTYPE html> <html> <head> <title>List</title> <link rel='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加入如下代码:
// 视图助手 var util = require('util'); var helper = require('./routes/helper'); app.use('/helper', helper); // dynamic Helper app.locals.inspect = function(obj) { return util.inspect(obj, true); } app.locals.headers = function(req, res) { return req.headers }
2.在routes文件夹下新建一个helper.js,代码如下:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res) { res.render('helper', { title: 'Helper', _req: req, _res: res }); }); module.exports = router;
3. 在views文件下新建helper.ejs;代码如下:
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <%=inspect(headers(_req, _res))%> </body> </html> 重启后,在浏览器访问如下:http://127.0.0.1:3000/helper
Express
如果一个包是某个工程依赖,那么我们需要在工程的目录下 使用本地模式安装这个包,如果要通过命令行调用这个包中的命令,则需要用全局模式安装.
因此按理说我们使用本地模式安装 Express 即可。 但是Express 像很多框架一样都提供了 Quick Start(快速开始)工具,这个工具的功能通常
是建立一个网站最小的基础框架,为了使用这个工具,我们需要用全局模式安装 Express,因为只有这样我们才能在命令行中使用它。运行以下命令:
1.首先全局安装express sudo npm install -g express
上面安装后,发现使用 express --help 会提示如下:Express Command not found
解决方法:在安装一个包,使用命令:sudo npm install -g express-generator
原因:express3+已经把创建一个APP的功能分离出来为express-generator,没它你创建不了应用程序.
我们接着使用 express --help就可以看到很多其他命令了;
2. 通过以下命令建立网站的基本结构: express -t eje microblog
网站的目录文件名就叫 microblog,会在该目录下生成该文件夹; 接着执行如下命令:cd microblog && npm install
会自动安装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 -e microblog(-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. 在当前的项目目录下 运行 npm install 把package.json的所有依赖包都生成出到本地项目microblog目录下,会生成一个node_modules
模块内;
4. 使用node app.js没有效果,需要使用如下命令即可 npm start
出现情况:访问不到页面
解决方法:版本不一样,用错命令了,应该是npm start
接着访问:http://127.0.0.1:3000/ 就可以看到 welcome to express页面的界面了;
要关闭服务器的话,在终端中按 Ctrl + C。注意,如果你对代码做了修改,要想看到修 改后的效果必须重启服务器,
也就是说你需要关闭服务器并再次运行才会有效果。如果觉得 有些麻烦,可以使用 supervisor 实现监视代码修改和自动重启
5. 下面是我们项目的目前的工程结构如下:
6 了解工程的结构;
6-1. app.js
var express = require('express'); // 引入express模块 var path = require('path'); // 引入path模块 var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); // routes 是一个文件夹形式的本地模块,即./routes/index.js,它的功能 是为指定路径组织返回内容,相当于 MVC 架构中的控制器。 var routes = require('./routes/index'); var users = require('./routes/users'); // 函数创建了一个应用的实例,后面的所有操作都是针对于这个实例进行的 var app = express(); // app.set 是 Express 的参数设置工具,接受一个键(key)和一个值(value),可用的参 数如下所示 // 1. basepath:基础地址,通常用于 res.redirect() 跳转。 // 2. views:视图文件的目录,存放模板文件。 // 3. view engine:视图模板引擎。 // 4. view options:全局视图参数对象。 // 5. view cache:启用视图缓存。 // 6. case sensitive routes:路径区分大小写。 // 7. strict routing:严格路径,启用后不会忽略路径末尾的“ / ”。 // 8. jsonp callback:开启透明的 JSONP 支持。 // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); // Express 依赖于 connect,提供了大量的中间件,可以通过 app.use 启用 // 1. bodyParser 的功能是解析客户端请求,通常是通过 POST 发送的内容。 // 2. router 是项目的路由支持。 // 3. static 提供了静态文件支持。 // 4. errorHandler 是错误控制器。 // uncomment after placing your favicon in /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); // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); // error handlers // development error handler // will print stacktrace 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 }); }); } // production error handler // no stacktraces leaked to user 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 是路由文件,相当于控制器,用于组织展示的内容:
var express = require('express'); var router = 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 中调用的模板,内容是:
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p> </body> </html>
其中包含了形如 <%= title %> 的标签,功能是显示引用的 变量,即 res.render 函数第二个参数传入的对象的属性。
4. 在bin目录下有一个文件www,内容如下:
// Module dependencies. var app = require('../app'); var debug = require('debug')('microblog:server'); var http = require('http'); // Get port from environment and store in Express. var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); // Create HTTP server. var server = http.createServer(app); // Listen on provided port, on all network interfaces. server.listen(port); server.on('error', onError); server.on('listening', onListening); // Normalize a port into a number, string, or false. function normalizePort(val) { var port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; } //Event listener for HTTP server "error" event. function onError(error) { if (error.syscall !== 'listen') { throw error; } var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; } } // Event listener for HTTP server "listening" event. function onListening() { var addr = server.address(); var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); }
上面的代码最主要做的工作时,创建一个http服务器,并且监听默认的端口号是3000;
express()内置方法理解如下:
1.express()用来创建一个Express的程序。express()方法是express模块导出的顶层方法。如下代码:
var express = require('express'); var app = 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 页面,返回给浏览器.
返回内容如下:
<!DOCTYPE html> <html> <head> <title>Express</title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1>Express</h1> <p>Welcome to Express</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 这样不存在的页面时,服务器会在响应头中返回 404 Not Found 错误,浏览器显示如图
这是因为 /a 是一个不存在的路由规则.
如何创建路由规则?
假设我们要创建一个地址为 /hello 的页面,内容是当前的服务器时间,让我们看看具 体做法。打开 app.js;
1.在route下新建一个文件hello.js文件;代码如下:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next) { res.send('The time is ' + new Date().toString()); }); module.exports = router;
2. 在app.js头部引入hello模块;添加如下代码:
var hello = 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 的 意思是 表征状态转移(Representational State Transfer),它是一种基于 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('all methods captured'); }); 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('all methods captured'); next(); }); app.get('/user/:username', function(req, res) { res.send('user: ' + req.params.username); });
上面的 app.all 函数,它支持把所有的请求方式绑定到同一个响应函数.
我们发现当访问被匹配到的路径时,如:http://127.0.0.1:3000/user/aa 会发现终端中打印了 all methods captured,
而且浏览器中显示了 user:aa。这说明请求先被第一条路由规则捕获,完成 console.log 使用 next() 转移控制权,
又被第二条规则捕获,向浏览器 返回了信息。
这是一个非常有用的工具,可以让我们轻易地实现中间件,而且还能提高代码的复用程度。例如我们针对一个用户查询信息和修改信息的操作,
分别对应了 GET 和 PUT 操作,而 两者共有的一个步骤是检查用户名是否合法,因此可以通过 next() 方法实现:
在app.js添加如下代码:
// 控制权转移 测试 var users = { 'cc2': { name: 'Carbo', website: 'http://www.byvoid.com' } }; app.all('/user2/:username', function(req, res, next) { // 检查用户是否存在 if (users[req.params.username]) { next(); } else { next(new Error(req.params.username + ' does not exist.')); } }); 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的根目录 在终端输入: sudo mkdir -p /data/db (创建/data/db目录)
3. 在终端输入:sudo chown -R 你的系统登录用户名 /data/db
4. 进入mongodb 的 "bin"目录,使用命令 “./mongod” 启动mongoDB server,
启动成功后最后一行应该是端口号,如配图,出现配图就能肯定你的Mongodb已经安装成了
5. 新建终端标签,进入mongodb/bin目录下 并输入./mongo 登陆到数据库; 如下图所示:
3. 为了在Node.js中使用MongDB数据库,需要安装mongodb模块,打开package.json文件,在dependencies属性中添加一行代码,即:
"mongodb":">=1.4.8",接着进入项目的根目录 运行"npm install"命令更新依赖的模块。
接下来在项目的根目录下创建settings.js文件,该文件用于保存数据库信息,包括数据库名称、数据库地址和cookieSecret等,
settings.js文件代码如下:
module.exports={ cookieSecret:'microblogKongzhi',//用于cookie加密,与数据库无关 db:'microblog',//数据库名称 host:'127.0.0.1' //数据库地址 };
接下来在项目根目录下创建models文件夹,并在models文件夹下创建db.js文件,该文件用于创建数据库连接,代码如下:
var settings = require('../settings'), //加载保存数据库基本信息的模块 Db = require('mongodb').Db, //加载MongDB数据库依赖模块,并调用相关对象 Server = require('mongodb').Server; //设置数据库名称、数据库地址和数据库默认端口号创建一个数据库实例,然后通过module.exports输出创建的数据库连接 module.exports = new Db(settings.db, new Server(settings.host,27017),{safe: true}); //mongodb数据库服务器的默认端口号:27017
4. 启动mongDb报如下错误: couldn't connect to server 127.0.0.1:27017 src/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",
然后运行 "npm install" 命令更新依赖的模块。接下来打开app.js文件,添加如下代码:
//使用时新添加的,上面的依赖包是创建文件时自带的。 var settings = require('./settings');//数据库连接依赖包 //session会话存储于数据库依赖包(与教程中的区别) var session = require('express-session');//session使用 var MongoStore = require('connect-mongo')(session);//mongodb使用 //提供session支持(与教程中的区别) app.use(session({ secret: settings.cookieSecret, key: settings.db, //cookie name cookie: {maxAge: 1000 * 60 * 60 * 24 * 30},//30 days resave: false, saveUninitialized: true, store: new MongoStore({ 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 加载路由控制, 代码如下:
var routes = require('./routes/index');
2. 接着需要在app.js 中定义匹配路由;代码如下:
app.use('/', routes); //指向了routes目录下的index.js文件
3. 在routes文件下的index.js代码如下:
var express = require('express'); var router = 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代码如下:
<%- include header.ejs %> <form method="post"> <h3 class="title">用户注册</h3> <div class="line"> <label for="uname">姓名:</label> <input type="text" id="name" name="username"/> </div> <div class="line"> <label for="upwd">密码:</label> <input type="password" id="pwd" name="userpwd"/> </div> <div class="line"> <label for="upwd2">确认密码:</label> <input type="password" id="upwd2" name="pwdrepeat"/> </div> <div class="line"> <button type="submit" id="btnreg">注册</button> </div> </form> <%- include footer.ejs %> 其中header.ejs代码如下: <!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css'/> </head> <body> <div id="header"> <ul id="menus"> <li><a href="/" class="menu introduce">简易博客</a></li> <li><a href="/" class="menu">首页</a></li> <li><a href="/login" class="menu">登陆</a></li> <li><a href="/reg" class="menu">注册</a></li> </ul> </div> <div id="content"> footer.ejs代码如下: </div> <div id="footer">铜板街欢迎你~</div> </body> </html>
我们重庆服务器 进入项目的根目录 运行命令 npm start
在浏览器下访问 http://127.0.0.1:3000/reg 可以看到如下界面:
5. 首先需要安装connect-flash, 我们直接在package.json加入connect-flash依赖项; 代码如下:
"connect-flash": "*", 然后直接进入项目的根目录后 执行命令 npm install命令,把依赖项加载出来.
作用是: 保存的变量只会在用户当前 和下一次的请求中被访问,之后会被清除,通过它我们可以很方便地实现页面的通知
和错误信息显示功能。因为用户注册功能需要实现"显示注册成功或错误的信息"的功能,所以需要使用它.
6. 打开app.js文件,添加如下代码:
//引入 flash 模块来实现页面通知
var flash = require('connect-flash');
app.use(flash());//定义使用 flash 功能
实现用户注册功能,即实现用户注册处理post请求的功能。
6-1 用户模型
首先我们在models文件夹下 新建user.js文件,该文件的作用把新用户注册信息存储于数据库,以及从数据库读取指定用户的信息的功能.
User 是一个描述数据的对象,即 MVC 架构中的模型。模型是与数据打交道的工具
user.js代码如下:
该文件的功能有2点:
1. 把新用户注册信息存储于数据库。
2. 从数据库读取指定用户的信息的功能.
代码如下:
var mongodb = require('./db');//加载数据库模块 //User构造函数,用于创建对象 function User(user) { this.name = user.name; this.password = user.password; }; //User对象方法:把用户信息存入Mongodb User.prototype.save = function(callback){ var user = { //用户信息 name: this.name, password: this.password }; // 打开数据库 mongodb.open(function(err, db) { if (err) { return callback(err); } //读取users集合,users相当于数据库中的表 db.collection('users', function(err, collection) {//定义集合名称users if (err) { mongodb.close(); return callback(err); } //把user对象中的数据,即用户注册信息写入users集合中 collection.insert(user, {safe: true}, function(err, user) { mongodb.close(); callback(err, user); }); }); }) }; //User对象方法:从数据库中查找指定用户的信息 User.get = function get(username, callback) { mongodb.open(function(err, db) { if (err) { return callback(err); } //读取users集合 db.collection('users', function(err, collection) { if (err) { mongodb.close(); return callback(err); } //从users集合中查找name属性为username的记录 collection.findOne({name: username}, function(err, doc) { mongodb.close(); if (doc) { //封装查询结果为User对象 var user = new User(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) {});"代码,代码修改如下:
var express = require('express'); var router = express.Router(); //加载生成MD5值依赖模块 var crypto = require('crypto');// 加密和解密模块 var User = 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', "输入框不能为空!"); return res.redirect('/reg');//返回reg页面 } // 两次输入的密码不一致,提示信息 if(req.body.userpwd != req.body.pwdrepeat) { req.flash("error", '两次输入密码不一致!'); return res.redirect('/reg'); } //把密码转换为MD5值 var md5 = crypto.createHash('md5'); var password = md5.update(req.body.userpwd).digest('base64'); //用新注册用户信息对象实例化User对象,用于存储新注册用户和判断注册用户是否存在 var newUser = new User({ name: req.body.username, password: password, }); // 检查用户是否存在 User.get(newUser.name,function(err,user){ // 如果用户存在的话 if (user) { err = 'Username already exists.'; } if (err) { req.flash('error', err);//保存错误信息,用于界面显示提示 return res.redirect('/reg'); } // 用户不存在的时候 保存用户 newUser.save(function(err){ if (err) { req.flash('error', err); return res.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 是重定向功能,通过它会向用户返回一个 303 See Other 状态,通知浏览器转向相应页面。
4. crypto 是 Node.js 的一个核心模块,功能是加密并生成各种散列,使用它之前首先要声明 var crypto = require('crypto')。
我们代码中使用它计算了密码的散列值。
5. User 是我们设计的用户对象,用前需要通过 var User = 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; //获取要显示错误信息 var error = req.flash('error');//获取flash中存储的error信息 res.locals.error = error.length ? error : null; //获取要显示成功信息 var success = 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代码如下:
<%- include header.ejs %> <%- include alert.ejs %> <form method="post"> <h3 class="title">用户注册</h3> <div class="line"> <label for="uname">姓名:</label> <input type="text" id="name" name="username"/> </div> <div class="line"> <label for="upwd">密码:</label> <input type="password" id="pwd" name="userpwd"/> </div> <div class="line"> <label for="upwd2">确认密码:</label> <input type="password" id="upwd2" name="pwdrepeat"/> </div> <div class="line"> <button type="submit" id="btnreg">注册</button> </div> </form> <%- include footer.ejs %>
header.ejs代码如下:
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css'/> </head> <body> <div id="header"> <ul id="menus"> <li><a href="/" class="menu introduce">简易博客</a></li> <li><a href="/" class="menu">首页</a></li> <% if(!user) { %> <li><a href="/login" class="menu">登陆</a></li> <li><a href="/reg" class="menu">注册</a></li> <% }else{ %> <li><a href="/logout" class="menu">退出</a></li> <% } %> </ul> </div> <div id="content">
alert.ejs代码如下:
<!--显示成功或错误信息--> <% if(success){ %> <div class="alert"> <%= success %> </div> <% } %> <% if(error){ %> <div class="alert"> <%= error %> </div> <% } %>
例如:用户注册时,两次密码输入不一致,点击“注册”按钮后的界面如下图所示
6. 用户登录模块
1. 打开index.js文件,完善“router.post('/login', function (req, res) {});”代码块,代码如下:
router.post('/login', function(req, res) { //生成口令的散列值 var md5 = crypto.createHash('md5'); var password = md5.update(req.body.password).digest('base64'); //判断用户名和密码是否存在和正确 User.get(req.body.username,function(err,user){ if(!user) { req.flash('error', '用户名不存在'); return res.redirect('/login'); } if(user.password != password) { req.flash('error', '用户密码不存在'); return res.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假如如下两个函数的代码:
function checkNotLogin(req,res,next){ // 如果从session里面获取用户已存在的话 if (req.session.user) { req.flash('error', '已登录'); return res.redirect('/'); } next(); //控制权转移:当不同路由规则向同一路径提交请求时,在通常情况下,请求总是被第一条路由规则捕获, // 后面的路由规则将会被忽略,为了可以访问同一路径的多个路由规则,使用next()实现控制权转移。 } function checkLogin(req,res,next){ if (!req.session.user){ req.flash('error', '未登录'); return res.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', "输入框不能为空!");
return res.redirect('/reg');//返回reg页面
}
// 两次输入的密码不一致,提示信息
if(req.body.userpwd != req.body.pwdrepeat) {
req.flash("error", '两次输入密码不一致!');
return res.redirect('/reg');
}
//把密码转换为MD5值
var md5 = crypto.createHash('md5');
var password = md5.update(req.body.userpwd).digest('base64');
//用新注册用户信息对象实例化User对象,用于存储新注册用户和判断注册用户是否存在
var newUser = new User({
name: req.body.username,
password: password,
});
// 检查用户是否存在
User.get(newUser.name,function(err,user){
if (user) {//用户名存在
req.flash('error', 'Username already exists.');//保存错误信息,用于界面显示提示
return res.redirect('/reg');
}
// 用户不存在的时候 保存用户
newUser.save(function(err){
if (err) {
req.flash('error', err);
return res.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;
//获取要显示错误信息
var error = req.flash('error');//获取flash中存储的error信息
res.locals.error = error.length ? error : null;
//获取要显示成功信息
var success = 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模板了;代码如下:
<%- include header.ejs %>
<%- include alert.ejs %>
<% if(!user){ %>
<div id="toppart">
<h2>欢迎来到简易博客</h2>
<p>该博客基于node.js+express+mongoDB来实现的。</p>
<br/>
<a href="/login" class="btn">登陆</a>
<a href="/reg" class="btn">注册</a>
</div>
<% }else{ %>
<%- include say.ejs %>
<% } %>
<%- include footer.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代码如下:
//获取微博和保存微博 var mongodb = require('./db'); //Post构造函数,用于创建对象 function Post(username, content, time) { this.user = username;//用户名 this.content = content;//发布内容 if (time) { this.time = time;//发布时间 }else { var now=new Date(); this.time =now.getFullYear()+"/"+(now.getMonth()+1)+"/"+now.getDate()+" "+now.getHours()+":"+now.getSeconds(); } } //输出Post对象 module.exports = Post; //对象方法:保存新发布的微博到数据库 Post.prototype.save = function(callback){ //存入MongoDB数据库 var post = { user: this.user, post: this.content, time: this.time }; mongodb.open(function (err, db) { if (err) { return callback(err); } //读取posts集合,即数据库表 db.collection('posts', function (err, collection) { if (err) { mongodb.close(); return callback(err); } //为user属性添加索引 collection.ensureIndex('user'); //把发布的微博信息post写入posts表中 collection.insert(post, {safe: true}, function (err, post) { mongodb.close(); callback(err, post); }); }); }); } //获取全部或指定用户的微博记录 Post.get = function get(username, callback) { mongodb.open(function (err, db) { if (err) { return callback(err); } //读取posts集合 db.collection('posts', function (err, collection) { if (err) { mongodb.close(); return callback(err); } //查找user属性为username的微博记录,如果username为null则查找全部记录 var query = {}; if (username) { query.user = username; } //查找符合条件的记录,并按时间顺序排列 collection.find(query).sort({time: -1}).toArray(function (err, docs) { mongodb.close(); if (err) { callback(err, null); } var posts = []; //遍历查询结果 docs.forEach(function (doc, index) { //把结果封装成Post对象 var post = new Post(doc.user, doc.post, doc.time); //把全部结果封装成数组 posts.push(post); }); callback(null, posts); }); }); }); };
9-2. 打开index.js,引入依赖包和完善发布微博功能的代码如下:
var Post = require("../models/post.js");//加载用户发表微博模块 //发表信息 router.post('/post', checkLogin);//页面权限控制 //发表微博 router.post('/post', function (req, res) { //路由规则/post var currentUser = req.session.user; //获取当前用户信息 if(req.body.post == ""){ //发布信息不能为空 req.flash('error', '内容不能为空!'); return res.redirect('/u/' + currentUser.name); } //实例化Post对象 var post = new Post(currentUser.name, req.body.post);//req.body.post获取用户发表的内容 //调用实例方法,发表微博,并把信息保存到MongoDB数据库 post.save(function (err) { if (err) { req.flash('error', err); return res.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', '用户不存在'); return res.redirect('/'); } //调用对象的方法用户存在,从数据库获取该用户的微博信息 Post.get(user.name, function (err, posts) { if (err) { req.flash('error', err); return res.redirect('/'); } //调用user模板引擎,并传送数据(用户名和微博集合) res.render('user', { title: user.name, posts: posts }); }); }); });
实现上面发表微博的总体思路如下:
1. 首先在say.ejs模板代码如下:
<form method="post" action="/post" id="public">
<input type="text" name="post" id="pctn"/>
<button type="submit" id="pbtn">发布</button>
</form>
当我输入框输入内容的时候,点击发布的时候,会首先路由到index.js代码中的
router.post('/post', checkLogin);//页面权限控制
首先判断用户是否登录,登录后通过视图助手的方式转到下一个同一路径请求的路由规则操作;
就是下面的post路由了,先判断用户是否为空;如果为空的话,显示不能为空信息,如下代码:
if(req.body.post == ""){ //发布信息不能为空
req.flash('error', '内容不能为空!');
return res.redirect('/u/' + currentUser.name);
}
接着就路由到 router.get('/u/:user', function(req, res) {})这个路由了;
接着顺利的话,就渲染user.ejs模板;代码如下:
//调用user模板引擎,并传送数据(用户名和微博集合)
res.render('user', {
title: user.name,
posts: posts
});
因此user.ejs模板代码如下:
<%- include header.ejs %>
<%- include alert.ejs %>
<% if(user){ %>
<%include say.ejs %>
<% } %>
<%- include posts.ejs %>
<%- include footer.ejs %>
如果内容为空的话,就在当前页面显示内容不能为空信息,如果添加成功的话,也会提示信息;接下来我们再来看看正常的情况下;如下post下的代码:
//实例化Post对象
var post = new Post(currentUser.name, req.body.post);//req.body.post获取用户发表的内容
//调用实例方法,发表微博,并把信息保存到MongoDB数据库
post.save(function (err) {
if (err) {
req.flash('error', err);
return res.redirect('/');
}
req.flash('success', '发表成功');
res.redirect('/u/' + currentUser.name);
});
如果正常的情况下,就弹出发表成功文案,并且还是转向路由res.redirect('/u/' + currentUser.name);这个下面去渲染user.esj模板信息了;
最后就渲染posts.ejs代码了;代码如下:
<!--按行按列显示传入的posts的所有内容-->
<div id="bottompart">
<% posts.forEach(function(post, index){//遍历posts内容,每行显示三个
if(index % 3 == 0){ %>
<div class="row">
<% } %>
<div class="ctn">
<h3><a href='/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代码变为如下:
<%- include header.ejs %> <%- include alert.ejs %> <% if(!user){ %> <div id="toppart"> <h2>欢迎来到简易博客</h2> <p>该博客基于node.js+express+mongoDB来实现的。</p> <br/> <a href="/login" class="btn">登陆</a> <a href="/reg" class="btn">注册</a> </div> <% }else{ %> <%- include say.ejs %> <% } %> <%- include posts.ejs %> <%- include footer.ejs %>
接着我们在http://127.0.0.1:3000/ 浏览器访问如下效果:
github上的源码如下:
https://github.com/tugenhua0707/node_express_microblog
可以下载下来自己运行下即可;