在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 第1章 一個(gè)簡(jiǎn)單的博客
第9章 增加標(biāo)簽和標(biāo)簽頁(yè)面
番外篇之——使用 Mongoose
番外篇之——使用 Async
第4章 實(shí)現(xiàn)用戶頁(yè)面和文章頁(yè)面
第12章 增加友情鏈接
第14章 增加頭像
第7章 實(shí)現(xiàn)分頁(yè)功能
第5章 增加編輯與刪除功能
第11章 增加文章檢索功能
第3章 增加文件上傳功能
番外篇之——部署到 Heroku
第2章 使用 Markdown
第13章 增加404頁(yè)面
第16章 增加日志功能
第1章 一個(gè)簡(jiǎn)單的博客
番外篇之——使用 Handlebars
第10章 增加pv統(tǒng)計(jì)和留言統(tǒng)計(jì)
番外篇之——使用 Passport
第15章 增加轉(zhuǎn)載功能和轉(zhuǎn)載統(tǒng)計(jì)
第8章 增加存檔頁(yè)面
番外篇之——使用 generic pool
番外篇之——使用 _id 查詢
番外篇之——使用 Disqus
番外篇之——使用 KindEditor
第6章 實(shí)現(xiàn)留言功能

第1章 一個(gè)簡(jiǎn)單的博客

學(xué)習(xí)環(huán)境

Node.js : 0.10.32

Express : 4.10.2

MongoDB : 2.6.1

快速開(kāi)始

安裝 Express

express 是 Node.js 上最流行的 Web 開(kāi)發(fā)框架,正如他的名字一樣,使用它我們可以快速的開(kāi)發(fā)一個(gè) Web 應(yīng)用。我們用 express 來(lái)搭建我們的博客,打開(kāi)命令行,輸入:

$ npm install -g express-generator

安裝 express 命令行工具,使用它我們可以初始化一個(gè) express 項(xiàng)目。

新建一個(gè)工程

在命令行中輸入:

$ express -e blog
$ cd blog && npm install

初始化一個(gè) express 項(xiàng)目并安裝所需模塊,如下圖所示:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.1.jpg" alt="" />

然后運(yùn)行:

$ DEBUG=blog node ./bin/www

啟動(dòng)項(xiàng)目,此時(shí)命令行中會(huì)顯示 blog Express server listening on port 3000 +0ms,在瀏覽器里訪問(wèn) localhost:3000,如下圖所示:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.2.jpg" alt="" />

至此,我們用 express 初始化了一個(gè)工程項(xiàng)目,并指定使用 ejs 模板引擎,下一節(jié)我們講解工程的內(nèi)部結(jié)構(gòu)。

工程結(jié)構(gòu)

我們回頭看看生成的工程目錄里面都有什么,打開(kāi)我們的 blog 文件夾,里面如圖所示:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.3.jpg" alt="" />

app.js:?jiǎn)?dòng)文件,或者說(shuō)入口文件 package.json:存儲(chǔ)著工程的信息及模塊依賴,當(dāng)在 dependencies 中添加依賴的模塊時(shí),運(yùn)行 npm install,npm 會(huì)檢查當(dāng)前目錄下的 package.json,并自動(dòng)安裝所有指定的模塊 node_modules:存放 package.json 中安裝的模塊,當(dāng)你在 package.json 添加依賴的模塊并安裝后,存放在這個(gè)文件夾下 public:存放 image、css、js 等文件 routes:存放路由文件 views:存放視圖文件或者說(shuō)模版文件 bin:存放可執(zhí)行文件

打開(kāi)app.js,讓我們看看里面究竟有什么:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// uncomment after placing your favicon in /public
//app.use(favicon(__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;

這里我們通過(guò)require()加載了express、path 等模塊,以及 routes 文件夾下的index. js和 users.js 路由文件。 下面來(lái)講解每行代碼的含義。

(1) var app = express():生成一個(gè)express實(shí)例 app。 (2)app.set('views', path.join(dirname, 'views’)):設(shè)置 views 文件夾為存放視圖文件的目錄, 即存放模板文件的地方,dirname 為全局變量,存儲(chǔ)當(dāng)前正在執(zhí)行的腳本所在的目錄。 (3)app.set('view engine', 'ejs’):設(shè)置視圖模板引擎為 ejs。 (4)app.use(favicon(dirname + '/public/favicon.ico’)):設(shè)置/public/favicon.ico為favicon圖標(biāo)。 (5)app.use(logger('dev’)):加載日志中間件。 (6)app.use(bodyParser.json()):加載解析json的中間件。 (7)app.use(bodyParser.urlencoded({ extended: false })):加載解析urlencoded請(qǐng)求體的中間件。 (8)app.use(cookieParser()):加載解析cookie的中間件。 (9)app.use(express.static(path.join(dirname, 'public'))):設(shè)置public文件夾為存放靜態(tài)文件的目錄。 (10)app.use('/', routes);和app.use('/users', users):路由控制器。 (11)

app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

捕獲404錯(cuò)誤,并轉(zhuǎn)發(fā)到錯(cuò)誤處理器。 (12)

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
        });
    });
}

開(kāi)發(fā)環(huán)境下的錯(cuò)誤處理器,將錯(cuò)誤信息渲染error模版并顯示到瀏覽器中。 (13)

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

生產(chǎn)環(huán)境下的錯(cuò)誤處理器,將錯(cuò)誤信息渲染error模版并顯示到瀏覽器中。 (14)module.exports = app :導(dǎo)出app實(shí)例供其他模塊調(diào)用。

我們?cè)倏?bin/www 文件:

#!/usr/bin/env node
var debug = require('debug')('blog');
var app = require('../app');

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function() {
  debug('Express server listening on port ' + server.address().port);
});

(1)#!/usr/bin/env node:表明是 node 可執(zhí)行文件。 (2)var debug = require('debug')('blog’):引入debug模塊,打印調(diào)試日志。 (3)var app = require('../app’):引入我們上面導(dǎo)出的app實(shí)例。 (4)app.set('port', process.env.PORT || 3000):設(shè)置端口號(hào)。 (5)

var server = app.listen(app.get('port'), function() {
  debug('Express server listening on port ' + server.address().port);
});

啟動(dòng)工程并監(jiān)聽(tīng)3000端口,成功后打印 Express server listening on port 3000。

我們?cè)倏?routes/index.js 文件:

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res) {
  res.render('index', { title: 'Express' });
});

module.exports = router;

生成一個(gè)路由實(shí)例用來(lái)捕獲訪問(wèn)主頁(yè)的GET請(qǐng)求,導(dǎo)出這個(gè)路由并在app.js中通過(guò)app.use('/', routes); 加載。這樣,當(dāng)訪問(wèn)主頁(yè)時(shí),就會(huì)調(diào)用res.render('index', { title: 'Express' });渲染views/index.ejs模版并顯示到瀏覽器中。

我們?cè)倏纯?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>

在渲染模板時(shí)我們傳入了一個(gè)變量 title 值為 express 字符串,模板引擎會(huì)將所有 <%= title %> 替換為 express ,然后將渲染后生成的html顯示到瀏覽器中,如上圖所示。

在這一小節(jié)我們學(xué)習(xí)了如何創(chuàng)建一個(gè)工程并啟動(dòng)它,了解了工程的大體結(jié)構(gòu)和運(yùn)作流程,下一小節(jié)我們將學(xué)習(xí) express 的基本使用及路由控制。

路由控制

工作原理

routes/index.js 中有以下代碼:

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

這段代碼的意思是當(dāng)訪問(wèn)主頁(yè)時(shí),調(diào)用 ejs 模板引擎,來(lái)渲染 index.ejs 模版文件(即將 title 變量全部替換為字符串 Express),生成靜態(tài)頁(yè)面并顯示在瀏覽器中。

我們來(lái)作一些修改,以上代碼實(shí)現(xiàn)了路由的功能,我們當(dāng)然可以不要 routes/index.js 文件,把實(shí)現(xiàn)路由功能的代碼都放在 app.js 里,但隨著時(shí)間的推移 app.js 會(huì)變得臃腫難以維護(hù),這也違背了代碼模塊化的思想,所以我們把實(shí)現(xiàn)路由功能的代碼都放在 routes/index.js 里。官方給出的寫法是在 app.js 中實(shí)現(xiàn)了簡(jiǎn)單的路由分配,然后再去 index.js 中找到對(duì)應(yīng)的路由函數(shù),最終實(shí)現(xiàn)路由功能。我們不妨把路由控制器和實(shí)現(xiàn)路由功能的函數(shù)都放到 index.js 里,app.js 中只有一個(gè)總的路由接口。

最終將 app.js 修改為:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');

var app = express();

app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

routes(app);

app.listen(app.get('port'), function() {
  console.log('Express server listening on port ' + app.get('port'));
});

修改 index.js 如下:

module.exports = function(app) {
  app.get('/', function (req, res) {
    res.render('index', { title: 'Express' });
  });
};

現(xiàn)在,再運(yùn)行你的 app,你會(huì)發(fā)現(xiàn)主頁(yè)毫無(wú)二致。這里我們?cè)?routes/index.js 中通過(guò) module.exports 導(dǎo)出了一個(gè)函數(shù)接口,在 app.js 中通過(guò) require 加載了 index.js 然后通過(guò) routes(app) 調(diào)用了 index.js 導(dǎo)出的函數(shù)。

路由規(guī)則

express 封裝了多種 http 請(qǐng)求方式,我們主要只使用 get 和 post 兩種,即 app.get() 和 app.post() 。

app.get() 和 app.post() 的第一個(gè)參數(shù)都為請(qǐng)求的路徑,第二個(gè)參數(shù)為處理請(qǐng)求的回調(diào)函數(shù),回調(diào)函數(shù)有兩個(gè)參數(shù)分別是 req 和 res,代表請(qǐng)求信息和響應(yīng)信息 。路徑請(qǐng)求及對(duì)應(yīng)的獲取路徑有以下幾種形式:

req.query

// GET /search?q=tobi+ferret  
req.query.q  
// => "tobi ferret"  

// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse  
req.query.order  
// => "desc"  

req.query.shoe.color  
// => "blue"  

req.query.shoe.type  
// => "converse"  

req.body

// POST user[name]=tobi&user[email]=tobi@learnboost.com  
req.body.user.name  
// => "tobi"  

req.body.user.email  
// => "tobi@learnboost.com"  

// POST { "name": "tobi" }  
req.body.name  
// => "tobi"  

req.params

// GET /user/tj  
req.params.name  
// => "tj"  

// GET /file/javascripts/jquery.js  
req.params[0]  
// => "javascripts/jquery.js" 

req.param(name)

// ?name=tobi  
req.param('name')  
// => "tobi"  

// POST name=tobi  
req.param('name')  
// => "tobi"  

// /user/tobi for /user/:name   
req.param('name')  
// => "tobi"  

不難看出:

  • req.query: 處理 get 請(qǐng)求,獲取 get 請(qǐng)求參數(shù)
  • req.params: 處理 /:xxx 形式的 get 或 post 請(qǐng)求,獲取請(qǐng)求參數(shù)
  • req.body: 處理 post 請(qǐng)求,獲取 post 請(qǐng)求體
  • req.param(): 處理 get 和 post 請(qǐng)求,但查找優(yōu)先級(jí)由高到低為 req.params→req.body→req.query

路徑規(guī)則還支持正則表達(dá)式,更多請(qǐng)查閱 Express 官方文檔 。

添加路由規(guī)則

當(dāng)我們?cè)L問(wèn) localhost:3000 時(shí),會(huì)顯示:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.4.jpg" alt="" />

當(dāng)我們?cè)L問(wèn) localhost:3000/nswbmw 這種不存在的頁(yè)面時(shí)就會(huì)顯示:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.5.jpg" alt="" />

這是因?yàn)椴淮嬖?/nswbmw 的路由規(guī)則,而且它也不是一個(gè) public 目錄下的文件,所以 express 返回了 404 Not Found 的錯(cuò)誤。下面我們來(lái)添加這條路由規(guī)則,使得當(dāng)訪問(wèn) localhost:3000/nswbmw 時(shí),頁(yè)面顯示 hello,world!

注意:以下修改僅用于測(cè)試,看到效果后再把代碼還原回來(lái)。

修改 index.js,在 app.get('/') 函數(shù)后添加一條路由規(guī)則:

app.get('/nswbmw', function (req, res) {
  res.send('hello,world!');
});

重啟之后,訪問(wèn) localhost:3000/nswbmw 頁(yè)面顯示如下:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.6.jpg" alt="" />

很簡(jiǎn)單吧?這一節(jié)我們學(xué)習(xí)了基本的路由規(guī)則及如何添加一條路由規(guī)則,下一節(jié)我們將學(xué)習(xí)模板引擎的知識(shí)。

模版引擎

什么是模板引擎

模板引擎(Template Engine)是一個(gè)將頁(yè)面模板和要顯示的數(shù)據(jù)結(jié)合起來(lái)生成 HTML 頁(yè)面的工具。 如果說(shuō)上面講到的 express 中的路由控制方法相當(dāng)于 MVC 中的控制器的話,那模板引擎就相當(dāng)于 MVC 中的視圖。

模板引擎的功能是將頁(yè)面模板和要顯示的數(shù)據(jù)結(jié)合起來(lái)生成 HTML 頁(yè)面。它既可以運(yùn) 行在服務(wù)器端又可以運(yùn)行在客戶端,大多數(shù)時(shí)候它都在服務(wù)器端直接被解析為 HTML,解析完成后再傳輸給客戶端,因此客戶端甚至無(wú)法判斷頁(yè)面是否是模板引擎生成的。有時(shí)候模板引擎也可以運(yùn)行在客戶端,即瀏覽器中,典型的代表就是 XSLT,它以 XML 為輸入,在客戶端生成 HTML 頁(yè)面。但是由于瀏覽器兼容性問(wèn)題,XSLT 并不是很流行。目前的主流還是由服務(wù)器運(yùn)行模板引擎。 在 MVC 架構(gòu)中,模板引擎包含在服務(wù)器端??刂破鞯玫接脩粽?qǐng)求后,從模型獲取數(shù)據(jù),調(diào)用模板引擎。模板引擎以數(shù)據(jù)和頁(yè)面模板為輸入,生成 HTML 頁(yè)面,然后返回給控制器,由控制器交回客戶端。 ——《Node.js開(kāi)發(fā)指南》

什么是 ejs ?

ejs 是模板引擎的一種,也是我們這個(gè)教程中使用的模板引擎,因?yàn)樗褂闷饋?lái)十分簡(jiǎn)單,而且與 express 集成良好。

使用模板引擎

前面我們通過(guò)以下兩行代碼設(shè)置了模板文件的存儲(chǔ)位置和使用的模板引擎:

app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');

注意:我們通過(guò) express -e blog 只是初始化了一個(gè)使用 ejs 模板引擎的工程而已,比如 node_modules 下添加了 ejs 模塊,views 文件夾下有 index.ejs 。并不是說(shuō)強(qiáng)制該工程只能使用 ejs 不能使用其他的模板引擎比如 jade,真正指定使用哪個(gè)模板引擎的是 app.set('view engine', 'ejs'); 。

在 routes/index.js 中通過(guò)調(diào)用 res.render() 渲染模版,并將其產(chǎn)生的頁(yè)面直接返回給客戶端。它接受兩個(gè)參數(shù),第一個(gè)是模板的名稱,即 views 目錄下的模板文件名,擴(kuò)展名 .ejs 可選。第二個(gè)參數(shù)是傳遞給模板的數(shù)據(jù)對(duì)象,用于模板翻譯。

打開(kāi) views/index.ejs ,內(nèi)容如下:

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>

當(dāng)我們 res.render('index', { title: 'Express' }); 時(shí),模板引擎會(huì)把 <%= title %> 替換成 Express,然后把替換后的頁(yè)面顯示給用戶。

渲染后生成的頁(yè)面代碼為:

<!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>

注意:我們通過(guò) app.use(express.static(path.join(__dirname, 'public'))) 設(shè)置了靜態(tài)文件目錄為 public 文件夾,所以上面代碼中的 href='/stylesheets/style.css' 就相當(dāng)于 href='public/stylesheets/style.css' 。

ejs 的標(biāo)簽系統(tǒng)非常簡(jiǎn)單,它只有以下三種標(biāo)簽:

  • <% code %>:JavaScript 代碼。
  • <%= code %>:顯示替換過(guò) HTML 特殊字符的內(nèi)容。
  • <%- code %>:顯示原始 HTML 內(nèi)容。

注意: <%= code %> 和 <%- code %> 的區(qū)別,當(dāng)變量 code 為普通字符串時(shí),兩者沒(méi)有區(qū)別。當(dāng) code 比如為

hello

這種字符串時(shí),<%= code %> 會(huì)原樣輸出

hello

,而 <%- code %> 則會(huì)顯示 H1 大的 hello 字符串。

我們可以在 <% %> 內(nèi)使用 JavaScript 代碼。下面是 ejs 的官方示例:

The Data

supplies: ['mop', 'broom', 'duster']

The Template

<ul>
<% for(var i=0; i<supplies.length; i++) {%>
   <li><%= supplies[i] %></li>
<% } %>
</ul>

The Result

<ul>
  <li>mop</li>
  <li>broom</li>
  <li>duster</li>
</ul>

我們可以用上述三種標(biāo)簽實(shí)現(xiàn)頁(yè)面模板系統(tǒng)能實(shí)現(xiàn)的任何內(nèi)容。

頁(yè)面布局

這里我們不使用layout進(jìn)行頁(yè)面布局,而是使用更為簡(jiǎn)單靈活的include。include 的簡(jiǎn)單使用如下:

index.ejs

<%- include a %>
hello,world!
<%- include b %>

a.ejs

this is a.ejs

b.ejs

this is b.ejs

最終 index.ejs 會(huì)顯示:

this is a.ejs
hello,world!
this is b.ejs

這一節(jié)我們學(xué)習(xí)了模版引擎的相關(guān)知識(shí),下一節(jié)我們正式開(kāi)始學(xué)習(xí)如何從頭開(kāi)始搭建一個(gè)多人博客。

搭建多人博客

功能分析

搭建一個(gè)簡(jiǎn)單的具有多人注冊(cè)、登錄、發(fā)表文章、登出功能的博客。

設(shè)計(jì)目標(biāo)

未登錄:主頁(yè)左側(cè)導(dǎo)航顯示 home、login、register,右側(cè)顯示已發(fā)表的文章、發(fā)表日期及作者。 登陸后:主頁(yè)左側(cè)導(dǎo)航顯示 home、post、logout,右側(cè)顯示已發(fā)表的文章、發(fā)表日期及作者。 用戶登錄、注冊(cè)、發(fā)表成功以及登出后都返回到主頁(yè)。

未登錄:

主頁(yè):

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.7.jpg" alt="" />

登錄頁(yè):

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.8.jpg" alt="" />

注冊(cè)頁(yè):

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.9.jpg" alt="" />

登錄后:

主頁(yè):

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.10.jpg" alt="" />

發(fā)表頁(yè):

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.11.jpg" alt="" />

注意:沒(méi)有登出頁(yè),當(dāng)點(diǎn)擊 LOGOUT 后,退出登陸并返回到主頁(yè)。

路由規(guī)劃

我們已經(jīng)把設(shè)計(jì)的構(gòu)想圖貼出來(lái)了,接下來(lái)的任務(wù)就是完成路由規(guī)劃了。路由規(guī)劃,或者說(shuō)控制器規(guī)劃是整個(gè)網(wǎng)站的骨架部分,因?yàn)樗幱谡麄€(gè)架構(gòu)的樞紐位置,相當(dāng)于各個(gè)接口之間的粘合劑,所以應(yīng)該優(yōu)先考慮。

根據(jù)構(gòu)思的設(shè)計(jì)圖,我們作以下路由規(guī)劃:

/ :首頁(yè)
/login :用戶登錄
/reg :用戶注冊(cè)
/post :發(fā)表文章
/logout :登出

我們要求 /login 和 /reg 只能是未登錄的用戶訪問(wèn),而 /post 和 /logout 只能是已登錄的用戶訪問(wèn)。左側(cè)導(dǎo)航列表則針對(duì)已登錄和未登錄的用戶顯示不同的內(nèi)容。

修改 index.js 如下:

module.exports = function(app) {
  app.get('/', function (req, res) {
    res.render('index', { title: '主頁(yè)' });
  });
  app.get('/reg', function (req, res) {
    res.render('reg', { title: '注冊(cè)' });
  });
  app.post('/reg', function (req, res) {
  });
  app.get('/login', function (req, res) {
    res.render('login', { title: '登錄' });
  });
  app.post('/login', function (req, res) {
  });
  app.get('/post', function (req, res) {
    res.render('post', { title: '發(fā)表' });
  });
  app.post('/post', function (req, res) {
  });
  app.get('/logout', function (req, res) {
  });
};

如何針對(duì)已登錄和未登錄的用戶顯示不同的內(nèi)容呢?或者說(shuō)如何判斷用戶是否已經(jīng)登陸了呢?進(jìn)一步說(shuō)如何記住用戶的登錄狀態(tài)呢?我們通過(guò)引入會(huì)話(session)機(jī)制記錄用戶登錄狀態(tài),還要訪問(wèn)數(shù)據(jù)庫(kù)來(lái)保存和讀取用戶信息。下一節(jié)我們將學(xué)習(xí)如何使用數(shù)據(jù)庫(kù)。

使用數(shù)據(jù)庫(kù)

MongoDB簡(jiǎn)介

MongoDB 是一個(gè)基于分布式文件存儲(chǔ)的 NoSQL(非關(guān)系型數(shù)據(jù)庫(kù))的一種,由 C++ 語(yǔ)言編寫,旨在為 WEB 應(yīng)用提供可擴(kuò)展的高性能數(shù)據(jù)存儲(chǔ)解決方案。MongoDB 支持的數(shù)據(jù)結(jié)構(gòu)非常松散,是類似 json 的 bjson 格式,因此可以存儲(chǔ)比較復(fù)雜的數(shù)據(jù)類型。MongoDB 最大的特點(diǎn)是他支持的查詢語(yǔ)言非常強(qiáng)大,其語(yǔ)法有點(diǎn)類似于面向?qū)ο蟮牟樵冋Z(yǔ)言,幾乎可以實(shí)現(xiàn)類似關(guān)系數(shù)據(jù)庫(kù)單表查詢的絕大部分功能,而且還支持對(duì)數(shù)據(jù)建立索引。

MongoDB 沒(méi)有關(guān)系型數(shù)據(jù)庫(kù)中行和表的概念,不過(guò)有類似的文檔和集合的概念。文檔是 MongoDB 最基本的單位,每個(gè)文檔都會(huì)以唯一的 _id 標(biāo)識(shí),文檔的屬性為 key/value 的鍵值對(duì)形式,文檔內(nèi)可以嵌套另一個(gè)文檔,因此可以存儲(chǔ)比較復(fù)雜的數(shù)據(jù)類型。集合是許多文檔的總和,一個(gè)數(shù)據(jù)庫(kù)可以有多個(gè)集合,一個(gè)集合可以有多個(gè)文檔。

下面是一個(gè) MongoDB 文檔的示例:

{ 
  "_id" : ObjectId( "4f7fe8432b4a1077a7c551e8" ),
  "name" : "nswbmw",
  "age" : 22,
  "email" : [ "xxx@126.com", "xxx@gmail.com" ],
  "family" : {
    "mother" : { ... },
    "father" : { ... },
    "sister : {
      "name" : "miaomiao",
      "age" : 27,
      "email" : "xxx@163.com",
      "family" : {
        "mother" : { ... },
        "father" : { ... },
        "brother : { ... },
        "husband" : { ... },
        "son" : { ... }
      }
    }
  }
}

更多有關(guān) MongoDB 的知識(shí)請(qǐng)參閱 《mongodb權(quán)威指南》或查閱:http://www.mongodb.org/

安裝MongoDB

安裝 MongoDB 很簡(jiǎn)單,去官網(wǎng)下載對(duì)應(yīng)系統(tǒng)的 MongoDB 壓縮包即可。解壓后將文件夾重命名為 mongodb,并在 mongodb 文件夾里新建 blog 文件夾作為我們博客內(nèi)容的存儲(chǔ)目錄。進(jìn)入到 bin 目錄下:運(yùn)行:

mongod --dbpath ../blog/

以上命令的意思是:設(shè)置 blog 文件夾作為我們工程的存儲(chǔ)目錄并啟動(dòng)數(shù)據(jù)庫(kù)。

連接MongoDB

數(shù)據(jù)庫(kù)雖然安裝并啟動(dòng)成功了,但我們需要連接數(shù)據(jù)庫(kù)后才能使用數(shù)據(jù)庫(kù)。怎么才能在 Node.js 中使用 MongoDB 呢?我們使用官方提供的 node-mongodb-native 驅(qū)動(dòng)模塊,打開(kāi) package.json,在 dependencies 中添加一行:

"mongodb": "1.4.15"

然后運(yùn)行 npm install 更新依賴的模塊,稍等片刻后 mongodb 模塊就下載并安裝完成了。

接下來(lái)在工程的根目錄中創(chuàng)建 settings.js 文件,用于保存該博客工程的配置信息,比如數(shù)據(jù)庫(kù)的連接信息。我們將數(shù)據(jù)庫(kù)命名為 blog,因?yàn)閿?shù)據(jù)庫(kù)服務(wù)器在本地,所以 settings.js 文件的內(nèi)容如下:

module.exports = { 
  cookieSecret: 'myblog', 
  db: 'blog', 
  host: 'localhost',
  port: 27017
}; 

其中 db 是數(shù)據(jù)庫(kù)的名稱,host 是數(shù)據(jù)庫(kù)的地址,port是數(shù)據(jù)庫(kù)的端口號(hào),cookieSecret 用于 Cookie 加密與數(shù)據(jù)庫(kù)無(wú)關(guān),我們留作后用。

接下來(lái)在根目錄下新建 models 文件夾,并在 models 文件夾下新建 db.js ,添加如下代碼:

    var settings = require('../settings'),
        Db = require('mongodb').Db,
        Connection = require('mongodb').Connection,
        Server = require('mongodb').Server;
    module.exports = new Db(settings.db, new Server(settings.host, settings.port),
 {safe: true});

其中通過(guò) new Db(settings.db, new Server(settings.host, settings.port), {safe: true}); 設(shè)置數(shù)據(jù)庫(kù)名、數(shù)據(jù)庫(kù)地址和數(shù)據(jù)庫(kù)端口創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù)連接實(shí)例,并通過(guò) module.exports 導(dǎo)出該實(shí)例。這樣,我們就可以通過(guò) require 這個(gè)文件來(lái)對(duì)數(shù)據(jù)庫(kù)進(jìn)行讀寫了。

打開(kāi) app.js,在 var routes = require('./routes/index'); 下添加:

var settings = require('./settings');

會(huì)話支持

會(huì)話是一種持久的網(wǎng)絡(luò)協(xié)議,用于完成服務(wù)器和客戶端之間的一些交互行為。會(huì)話是一個(gè)比連接粒度更大的概念, 一次會(huì)話可能包含多次連接,每次連接都被認(rèn)為是會(huì)話的一次操作。在網(wǎng)絡(luò)應(yīng)用開(kāi)發(fā)中,有必要實(shí)現(xiàn)會(huì)話以幫助用戶交互。例如網(wǎng)上購(gòu)物的場(chǎng)景,用戶瀏覽了多個(gè)頁(yè)面,購(gòu)買了一些物品,這些請(qǐng)求在多次連接中完成。許多應(yīng)用層網(wǎng)絡(luò)協(xié)議都是由會(huì)話支持的,如 FTP、Telnet 等,而 HTTP 協(xié)議是無(wú)狀態(tài)的,本身不支持會(huì)話,因此在沒(méi)有額外手段的幫助下,前面場(chǎng)景中服務(wù)器不知道用戶購(gòu)買了什么。

為了在無(wú)狀態(tài)的 HTTP 協(xié)議之上實(shí)現(xiàn)會(huì)話,Cookie 誕生了。Cookie 是一些存儲(chǔ)在客戶端的信息,每次連接的時(shí)候由瀏覽器向服務(wù)器遞交,服務(wù)器也向?yàn)g覽器發(fā)起存儲(chǔ) Cookie 的請(qǐng)求,依靠這樣的手段服務(wù)器可以識(shí)別客戶端。我們通常意義上的 HTTP 會(huì)話功能就是這樣實(shí)現(xiàn)的。具體來(lái)說(shuō),瀏覽器首次向服務(wù)器發(fā)起請(qǐng)求時(shí),服務(wù)器生成一個(gè)唯一標(biāo)識(shí)符并發(fā)送給客戶端瀏覽器,瀏覽器將這個(gè)唯一標(biāo)識(shí)符存儲(chǔ)在 Cookie 中,以后每次再發(fā)起請(qǐng)求,客戶端瀏覽器都會(huì)向服務(wù)器傳送這個(gè)唯一標(biāo)識(shí)符,服務(wù)器通過(guò)這個(gè)唯一標(biāo)識(shí)符來(lái)識(shí)別用戶。 對(duì)于開(kāi)發(fā)者來(lái)說(shuō),我們無(wú)須關(guān)心瀏覽器端的存儲(chǔ),需要關(guān)注的僅僅是如何通過(guò)這個(gè)唯一標(biāo)識(shí)符來(lái)識(shí)別用戶。很多服務(wù)端腳本語(yǔ)言都有會(huì)話功能,如 PHP,把每個(gè)唯一標(biāo)識(shí)符存儲(chǔ)到文件中。

——《Node.js開(kāi)發(fā)指南》

express 也提供了會(huì)話中間件,默認(rèn)情況下是把用戶信息存儲(chǔ)在內(nèi)存中,但我們既然已經(jīng)有了 MongoDB,不妨把會(huì)話信息存儲(chǔ)在數(shù)據(jù)庫(kù)中,便于持久維護(hù)。為了使用這一功能,我們需要借助 express-session 和 connect-mongo 這兩個(gè)第三方中間件,在 package.json 中添加:

"express-session": "1.9.1",
"connect-mongo": "0.4.1"

運(yùn)行npm install安裝模塊,打開(kāi)app.js,添加以下代碼:

var session = require('express-session');
var MongoStore = require('connect-mongo')(session);

app.use(session({
  secret: settings.cookieSecret,
  key: settings.db,//cookie name
  cookie: {maxAge: 1000 * 60 * 60 * 24 * 30},//30 days
  store: new MongoStore({
    db: settings.db,
    host: settings.host,
    port: settings.port
  })
}));

使用 express-session 和 connect-mongo 模塊實(shí)現(xiàn)了將會(huì)化信息存儲(chǔ)到mongoldb中。secret 用來(lái)防止篡改 cookie,key 的值為 cookie 的名字,通過(guò)設(shè)置 cookie 的 maxAge 值設(shè)定 cookie 的生存期,這里我們?cè)O(shè)置 cookie 的生存期為 30 天,設(shè)置它的 store 參數(shù)為 MongoStore 實(shí)例,把會(huì)話信息存儲(chǔ)到數(shù)據(jù)庫(kù)中,以避免丟失。在后面的小節(jié)中,我們可以通過(guò) req.session 獲取當(dāng)前用戶的會(huì)話對(duì)象,獲取用戶的相關(guān)信息。

注冊(cè)和登陸

我們已經(jīng)準(zhǔn)備好了數(shù)據(jù)庫(kù)訪問(wèn)和會(huì)話的相關(guān)信息,接下來(lái)我們完成用戶注冊(cè)和登錄功能。

頁(yè)面設(shè)計(jì)

首先我們來(lái)完成主頁(yè)、登錄頁(yè)和注冊(cè)頁(yè)的頁(yè)面設(shè)計(jì)。

修改 views/index.ejs 如下:

<%- include header %>
這是主頁(yè)
<%- include footer %>

在 views 文件夾下新建 header.ejs,添加如下代碼:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Blog</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>

<header>
<h1><%= title %></h1>
</header>

<nav>
<span><a title="主頁(yè)" href="/">home</a></span>
<span><a title="登錄" href="/login">login</a></span>
<span><a title="注冊(cè)" href="/reg">register</a></span>
</nav>

<article>

新建 footer.ejs,添加如下代碼:

</article>
</body>
</html>

修改 public/stylesheets/style.css 如下:

/* inspired by http://yihui.name/cn/ */
*{padding:0;margin:0;}
body{width:600px;margin:2em auto;padding:0 2em;font-size:14px;font-family:"Microsoft YaHei";}
p{line-height:24px;margin:1em 0;}
header{padding:.5em 0;border-bottom:1px solid #cccccc;}
nav{float:left;font-family:"Microsoft YaHei";font-size:1.1em;text-transform:uppercase;margin-left:-12em;width:9em;text-align:right;}
nav a{display:block;text-decoration:none;padding:.7em 1em;color:#000000;}
nav a:hover{background-color:#ff0000;color:#f9f9f9;-webkit-transition:color .2s linear;}
article{font-size:16px;padding-top:.5em;}
article a{color:#dd0000;text-decoration:none;}
article a:hover{color:#333333;text-decoration:underline;}
.info{font-size:14px;}

運(yùn)行 app ,主頁(yè)顯示如下:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.12.jpg" alt="" />

接下來(lái)在 views 文件夾下新建 login.ejs,內(nèi)容如下:

<%- include header %>
<form method="post">
  用戶名:<input type="text" name="name"/><br />
  密碼:  <input type="password" name="password"/><br />
         <input type="submit" value="登錄"/>
</form>
<%- include footer %>

登錄頁(yè)面顯示如下:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.13.jpg" alt="" />

在 views 文件夾下新建 reg.ejs,內(nèi)容如下:

<%- include header %>
<form method="post">
  用戶名:  <input type="text" name="name"/><br />
  密碼:    <input type="password" name="password"/><br />
  確認(rèn)密碼:<input type="password" name="password-repeat"/><br />
  郵箱:    <input type="email" name="email"/><br />
           <input type="submit" value="注冊(cè)"/>
</form>
<%- include footer %>

注冊(cè)頁(yè)面顯示如下:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.14.jpg" alt="" />

至此,未登錄時(shí)的主頁(yè)、注冊(cè)頁(yè)、登錄頁(yè)都已經(jīng)完成。

現(xiàn)在,啟動(dòng)我們的博客看看吧。

注意:每次我們更新代碼后,都需要手動(dòng)停止并重啟應(yīng)用,使用 supervisor 模塊可以解決這個(gè)問(wèn)題,每當(dāng)我們保存修改的文件時(shí),supervisor 都會(huì)自動(dòng)幫我們重啟應(yīng)用。通過(guò):

$ npm install -g supervisor

安裝 supervisor 。使用 supervisor 命令啟動(dòng) app.js:

$ supervisor app

頁(yè)面通知

接下來(lái)我們實(shí)現(xiàn)用戶的注冊(cè)和登陸,在這之前我們需要引入 flash 模塊來(lái)實(shí)現(xiàn)頁(yè)面通知(即成功與錯(cuò)誤信息的顯示)的功能。

什么是 flash?

我們所說(shuō)的 flash 即 connect-flash 模塊https://github.com/jaredhanson/connect-flash,flash 是一個(gè)在 session 中用于存儲(chǔ)信息的特定區(qū)域。信息寫入 flash ,下一次顯示完畢后即被清除。典型的應(yīng)用是結(jié)合重定向的功能,確保信息是提供給下一個(gè)被渲染的頁(yè)面。

在 package.json 添加一行代碼:

"connect-flash": "0.1.1"  

然后 npm install 安裝 connect-flash 模塊。修改 app.js ,在 var settings = require('./settings'); 后添加:

var flash = require('connect-flash');

在 app.set('view engine', 'ejs'); 后添加:

app.use(flash());

現(xiàn)在我們就可以使用 flash 功能了。

注冊(cè)響應(yīng)

前面我們已經(jīng)完成了注冊(cè)頁(yè),當(dāng)然現(xiàn)在點(diǎn)擊注冊(cè)是沒(méi)有效果的,因?yàn)槲覀冞€沒(méi)有實(shí)現(xiàn)處理 POST 請(qǐng)求的功能,下面就來(lái)實(shí)現(xiàn)它。

在 models 文件夾下新建 user.js,添加如下代碼:

var mongodb = require('./db');

function User(user) {
  this.name = user.name;
  this.password = user.password;
  this.email = user.email;
};

module.exports = User;

//存儲(chǔ)用戶信息
User.prototype.save = function(callback) {
  //要存入數(shù)據(jù)庫(kù)的用戶文檔
  var user = {
      name: this.name,
      password: this.password,
      email: this.email
  };
  //打開(kāi)數(shù)據(jù)庫(kù)
  mongodb.open(function (err, db) {
    if (err) {
      return callback(err);//錯(cuò)誤,返回 err 信息
    }
    //讀取 users 集合
    db.collection('users', function (err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);//錯(cuò)誤,返回 err 信息
      }
      //將用戶數(shù)據(jù)插入 users 集合
      collection.insert(user, {
        safe: true
      }, function (err, user) {
        mongodb.close();
        if (err) {
          return callback(err);//錯(cuò)誤,返回 err 信息
        }
        callback(null, user[0]);//成功!err 為 null,并返回存儲(chǔ)后的用戶文檔
      });
    });
  });
};

//讀取用戶信息
User.get = function(name, callback) {
  //打開(kāi)數(shù)據(jù)庫(kù)
  mongodb.open(function (err, db) {
    if (err) {
      return callback(err);//錯(cuò)誤,返回 err 信息
    }
    //讀取 users 集合
    db.collection('users', function (err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);//錯(cuò)誤,返回 err 信息
      }
      //查找用戶名(name鍵)值為 name 一個(gè)文檔
      collection.findOne({
        name: name
      }, function (err, user) {
        mongodb.close();
        if (err) {
          return callback(err);//失敗!返回 err 信息
        }
        callback(null, user);//成功!返回查詢的用戶信息
      });
    });
  });
};

我們通過(guò) User.prototype.save 實(shí)現(xiàn)了用戶信息的存儲(chǔ),通過(guò) User.get 實(shí)現(xiàn)了用戶信息的讀取。

打開(kāi) index.js ,在最前面添加如下代碼:

var crypto = require('crypto'),
    User = require('../models/user.js');

通過(guò) require() 引入 crypto 模塊和 user.js 用戶模型文件,crypto 是 Node.js 的一個(gè)核心模塊,我們用它生成散列值來(lái)加密密碼。

修改 index.js 中 app.post('/reg') 如下:

app.post('/reg', function (req, res) {
  var name = req.body.name,
      password = req.body.password,
      password_re = req.body['password-repeat'];
  //檢驗(yàn)用戶兩次輸入的密碼是否一致
  if (password_re != password) {
    req.flash('error', '兩次輸入的密碼不一致!'); 
    return res.redirect('/reg');//返回注冊(cè)頁(yè)
  }
  //生成密碼的 md5 值
  var md5 = crypto.createHash('md5'),
      password = md5.update(req.body.password).digest('hex');
  var newUser = new User({
      name: name,
      password: password,
      email: req.body.email
  });
  //檢查用戶名是否已經(jīng)存在 
  User.get(newUser.name, function (err, user) {
    if (err) {
      req.flash('error', err);
      return res.redirect('/');
    }
    if (user) {
      req.flash('error', '用戶已存在!');
      return res.redirect('/reg');//返回注冊(cè)頁(yè)
    }
    //如果不存在則新增用戶
    newUser.save(function (err, user) {
      if (err) {
        req.flash('error', err);
        return res.redirect('/reg');//注冊(cè)失敗返回主冊(cè)頁(yè)
      }
      req.session.user = user;//用戶信息存入 session
      req.flash('success', '注冊(cè)成功!');
      res.redirect('/');//注冊(cè)成功后返回主頁(yè)
    });
  });
});

注意:我們把用戶信息存儲(chǔ)在了 session 里,以后就可以通過(guò) req.session.user 讀取用戶信息。

  • req.body: 就是 POST 請(qǐng)求信息解析過(guò)后的對(duì)象,例如我們要訪問(wèn) POST 來(lái)的表單內(nèi)的 name="password" 域的值,只需訪問(wèn) req.body['password'] 或 req.body.password 即可。
  • res.redirect: 重定向功能,實(shí)現(xiàn)了頁(yè)面的跳轉(zhuǎn),更多關(guān)于 res.redirect 的信息請(qǐng)查閱:http://expressjs.com/api.html#res.redirect 。
  • User:在前面的代碼中,我們直接使用了 User 對(duì)象。User 是一個(gè)描述數(shù)據(jù)的對(duì)象,即 MVC 架構(gòu)中的模型。前面我們使用了許多視圖和控制器,這是第一次接觸到模型。與視圖和控制器不同,模型是真正與數(shù)據(jù)打交道的工具,沒(méi)有模型,網(wǎng)站就只是一個(gè)外殼,不能發(fā)揮真實(shí)的作用,因此它是框架中最根本的部分。 現(xiàn)在,啟動(dòng)應(yīng)用,在瀏覽器輸入 localhost:3000 注冊(cè)試試吧!注冊(cè)成功后顯示如下:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.15.jpg" alt="" />

這樣我們并不知道是否注冊(cè)成功,我們查看數(shù)據(jù)庫(kù)中是否存入了用戶的信息,打開(kāi)一個(gè)命令行切換到 mongodb/bin/ (保證數(shù)據(jù)庫(kù)已打開(kāi)的前提下),輸入:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.16.jpg" alt="" />

可以看到,用戶信息已經(jīng)成功存入數(shù)據(jù)庫(kù)。

接下來(lái)我們實(shí)現(xiàn)當(dāng)注冊(cè)成功返回主頁(yè)時(shí),左側(cè)導(dǎo)航顯示 HOME 、POST 、LOGOUT ,右側(cè)顯示 注冊(cè)成功! 字樣,即添加 flash 的頁(yè)面通知功能。

修改 header.ejs,將

修改如下:

<nav>
<span><a title="主頁(yè)" href="/">home</a></span>
<% if (user) { %>
  <span><a title="發(fā)表" href="/post">post</a></span>
  <span><a title="登出" href="/logout">logout</a></span>
<% } else { %>
  <span><a title="登錄" href="/login">login</a></span>
  <span><a title="注冊(cè)" href="/reg">register</a></span>
<% } %>
</nav>

后添加如下代碼:

<% if (success) { %>
  <div><%= success %></div>
<% } %>
<% if (error) { %>
  <div><%= error %> </div>
<% } %>
修改 index.js ,將 app.get('/') 修改如下:

app.get('/', function (req, res) {
  res.render('index', {
    title: '主頁(yè)',
    user: req.session.user,
    success: req.flash('success').toString(),
    error: req.flash('error').toString()
  });
});

將 app.get('reg') 修改如下:

app.get('/reg', function (req, res) {
  res.render('reg', {
    title: '注冊(cè)',
    user: req.session.user,
    success: req.flash('success').toString(),
    error: req.flash('error').toString()
  });
});

現(xiàn)在運(yùn)行我們的博客,注冊(cè)成功后顯示如下:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.17.jpg" alt="" />

我們通過(guò)對(duì) session 的使用實(shí)現(xiàn)了對(duì)用戶狀態(tài)的檢測(cè),再根據(jù)不同的用戶狀態(tài)顯示不同的導(dǎo)航信息。 簡(jiǎn)單解釋一下流程:用戶在注冊(cè)成功后,把用戶信息存入 session ,頁(yè)面跳轉(zhuǎn)到主頁(yè)顯示 注冊(cè)成功! 的字樣。同時(shí)把 session 中的用戶信息賦給變量 user ,在渲染 index.ejs 文件時(shí)通過(guò)檢測(cè) user 判斷用戶是否在線,根據(jù)用戶狀態(tài)的不同顯示不同的導(dǎo)航信息。

success: req.flash('success').toString() 的意思是將成功的信息賦值給變量 success, error: req.flash('error').toString() 的意思是將錯(cuò)誤的信息賦值給變量 error ,然后我們?cè)阡秩?ejs 模版文件時(shí)傳遞這兩個(gè)變量來(lái)進(jìn)行檢測(cè)并顯示通知。

登錄與登出響應(yīng)

現(xiàn)在我們來(lái)實(shí)現(xiàn)用戶登錄的功能。

打開(kāi) index.js ,將 app.post('/login') 修改如下:

app.post('/login', function (req, res) {
  //生成密碼的 md5 值
  var md5 = crypto.createHash('md5'),
      password = md5.update(req.body.password).digest('hex');
  //檢查用戶是否存在
  User.get(req.body.name, function (err, user) {
    if (!user) {
      req.flash('error', '用戶不存在!'); 
      return res.redirect('/login');//用戶不存在則跳轉(zhuǎn)到登錄頁(yè)
    }
    //檢查密碼是否一致
    if (user.password != password) {
      req.flash('error', '密碼錯(cuò)誤!'); 
      return res.redirect('/login');//密碼錯(cuò)誤則跳轉(zhuǎn)到登錄頁(yè)
    }
    //用戶名密碼都匹配后,將用戶信息存入 session
    req.session.user = user;
    req.flash('success', '登陸成功!');
    res.redirect('/');//登陸成功后跳轉(zhuǎn)到主頁(yè)
  });
});

將 app.get('/login') 修改如下:

app.get('/login', function (req, res) {
    res.render('login', {
        title: '登錄',
        user: req.session.user,
        success: req.flash('success').toString(),
        error: req.flash('error').toString()});
});

(這樣就不會(huì)出現(xiàn) 'user is not defined' 的錯(cuò)誤了)

接下來(lái)我們實(shí)現(xiàn)登出響應(yīng)。修改 app.get('/logout') 如下:

app.get('/logout', function (req, res) {
  req.session.user = null;
  req.flash('success', '登出成功!');
  res.redirect('/');//登出成功后跳轉(zhuǎn)到主頁(yè)
});

注意:通過(guò)把 req.session.user 賦值 null 丟掉 session 中用戶的信息,實(shí)現(xiàn)用戶的退出。

登錄后頁(yè)面顯示如下:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.18.jpg" alt="" />

登出后頁(yè)面顯示如下:

http://wiki.jikexueyuan.com/project/express-mongodb-setup-blog/images/1.19.jpg" alt="" />

至此,我們實(shí)現(xiàn)了用戶注冊(cè)與登陸的功能,并且根據(jù)用戶登錄狀態(tài)顯示不同的導(dǎo)航。

頁(yè)面權(quán)限控制

我們雖然已經(jīng)完成了用戶注冊(cè)與登陸的功能,但并不能阻止比如已經(jīng)登陸的用戶訪問(wèn) localhost:3000/reg 頁(yè)面,讀者可親自嘗試下。為此,我們需要為頁(yè)面設(shè)置訪問(wèn)權(quán)限。即注冊(cè)和登陸頁(yè)面應(yīng)該阻止已登陸的用戶訪問(wèn),登出及后面我們將要實(shí)現(xiàn)的發(fā)表頁(yè)只對(duì)已登錄的用戶開(kāi)放。如何實(shí)現(xiàn)頁(yè)面權(quán)限的控制呢?我們可以把用戶登錄狀態(tài)的檢查放到路由中間件中,在每個(gè)路徑前增加路由中間件,即可實(shí)現(xiàn)頁(yè)面權(quán)限控制。我們添加 checkNotLogin 和 checkLogin 函數(shù)來(lái)實(shí)現(xiàn)這個(gè)功能。

function checkLogin(req, res, next) {
  if (!req.session.user) {
    req.flash('error', '未登錄!'); 
    res.redirect('/login');
  }
  next();
}

function checkNotLogin(req, res, next) {
  if (req.session.user) {
    req.flash('error', '已登錄!'); 
    res.redirect('back');//返回之前的頁(yè)面
  }
  next();
}

checkNotLogin 和 checkLogin 用來(lái)檢測(cè)是否登陸,并通過(guò) next() 轉(zhuǎn)移控制權(quán),檢測(cè)到未登錄則跳轉(zhuǎn)到登錄頁(yè),檢測(cè)到已登錄則跳轉(zhuǎn)到前一個(gè)頁(yè)面。

最終 index.js 代碼如下:

var crypto = require('crypto'),
    User = require('../models/user.js');

module.exports = function(app) {
  app.get('/', function (req, res) {
    res.render('index', {
      title: '主頁(yè)',
      user: req.session.user,
      success: req.flash('success').toString(),
      error: req.flash('error').toString()
    });
  });

  app.get('/reg', checkNotLogin);
  app.get('/reg', function (req, res) {
    res.render('reg', {
      title: '注冊(cè)',
      user: req.session.user,
      success: req.flash('success').toString(),
      error: req.flash('error').toString()
    });
  });

  app.post('/reg', checkNotLogin);
  app.post('/reg', function (req, res) {
    var name = req.body.name,
        password = req.body.password,
        password_re = req.body['password-repeat'];
    if (password_re != password) {
      req.flash('error', '兩次輸入的密碼不一致!'); 
      return res.redirect('/reg');
    }
    var md5 = crypto.createHash('md5'),
        password = md5.update(req.body.password).digest('hex');
    var newUser = new User({
        name: name,
        password: password,
        email: req.body.email
    });
    User.get(newUser.name, function (err, user) {
      if (err) {
        req.flash('error', err);
        return res.redirect('/');