目錄[-]javascript
node.js: 0.10.7+
npm install -g express
咱們須要用全局模式安裝 express,由於只有這樣咱們才能在命令行中使用它。目前 express 最新版本爲 express 3.2.4。
從此的學習把 D:\blog 文件夾做爲咱們的工程目錄。windows 下打開 cmd 切換到 D 盤,輸入 express -e ejs blog
(注意 express 3.* 中安裝 ejs 再也不是 -t 而是 -e,能夠輸入 express -h
查看),而後輸入 cd blog&npm install
安裝所需模塊,以下圖所示:
安裝完成後輸入 node app
,此時命令行中會顯示 Express server listening on port 3000,在瀏覽器裏輸入 localhost:3000
,以下所示:
咱們用 express 初始化了一個工程,並指定使用 ejs 模板引擎,下一節咱們講解工程的內部結構。
咱們回頭看看生成的工程目錄裏面有什麼,打開 D:\blog,裏面如圖所示:
app.js:啓動文件。
package.json:存儲着工程的信息及所需的依賴模塊,當在 dependencies 中添加依賴時,運行npm install,會檢查當前目錄下的 package.json,並自動安裝全部指定的依賴模塊。
node_modules:存放 package.json 中安裝的模塊,當你在 package.json 添加依賴的模塊並安裝後,默認存放在這個文件夾下
public:存放 image、css、js 等文件
routes:存放路由文件
views:存放模版文件
打開 app.js,讓咱們看看裏面究竟有什麼東西:
/** * Module dependencies. */ var express = require('express') , routes = require('./routes') , user = require('./routes/user') , http = require('http') , path = require('path'); var app = express(); // all environments app.set('port', process.env.PORT || 3000); app.set('views', __dirname + '/views'); app.set('view engine', 'ejs'); app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(path.join(__dirname, 'public'))); // development only if ('development' == app.get('env')) { app.use(express.errorHandler()); } app.get('/', routes.index); app.get('/users', user.list); http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port')); });
在 node.js 中模塊分爲核心模塊和文件模塊兩種,核心模塊是經過 require('xxxx') 導入的,文件模塊是以 require('/xxxx') 或 require('./xxxx')、require('../xxxx') 形式導入的;核心模塊是用c/c++編譯的二進制模塊,而文件模塊是後綴爲.js、.json、.node 的文件,在 node.js 中一個文件/文件夾也能夠稱之爲一個模塊。更多關於模塊及模塊加載順序的信息請查閱官網:http://nodejs.org/api/all.html#all_modules
這裏導入了 express、http、path 核心模塊,routes 文件夾下的 index.js 和 user.js 文件模塊。
由於 express 框架是依賴 connect 框架(Node的一箇中間件框架)建立而成的,可查閱 connect 文檔:http://www.senchalabs.org/connect/和 express 官方文檔:http://expressjs.com/api.html瞭解更多內容。
app.set(name, value):設置 name
的值爲 value
app.set('port', process.env.PORT || 3000):設置端口爲 process.env.PORT
或 3000
app.set('views', __dirname + '/views'):設置 views 文件夾爲視圖文件的目錄,存放模板文件,__dirname 爲全局變量,存儲着當前正在執行腳本所在的目錄名。
app.set('view engine', 'ejs'):設置視圖模版引擎爲 ejs
app.use([path], function):使用中間件 function
,可選參數path
默認爲」/」
app.use(express.favicon()):connect 內建的中間件,使用默認的 favicon 圖標,若是想使用本身的圖標,需改成 app.use(express.favicon(__dirname + '/public/images/favicon.ico'));
這裏咱們把自定義的 favicon.ico 放到了 public/images 文件夾下。
app.use(express.logger('dev')):connect 內建的中間件,在開發環境下使用,在終端顯示簡單的不一樣顏色的日誌,好比在啓動 app.js 後訪問 localhost:3000,終端會輸出:
Express server listening on port 3000 GET / 200 21ms - 206b GET /stylesheets/style.css 304 4ms
數字200顯示爲綠色,304顯示爲藍色。假如你去掉這一行代碼,無論你怎麼刷新網頁,終端都只有一行 Express server listening on port 3000。
app.use(express.bodyParser()):connect 內建的中間件,用來解析請求體,支持 application/json,
application/x-www-form-urlencoded, 和 multipart/form-data。
app.use(express.methodOverride()):connect 內建的中間件,能夠協助處理 POST 請求,假裝 PUT、DELETE 和其餘 HTTP 方法。
app.use(app.router):設置應用的路由(可選),詳細請參考:http://stackoverflow.com/questions/12695591/node-js-express-js-how-does-app-router-work
app.use(express.static(path.join(__dirname, 'public'))):connect 內建的中間件,設置根目錄下的 public 文件夾爲靜態文件服務器,存放 image、css、js 文件於此。
if ('development' == app.get('env')) {app.use(express.errorHandler());}:開發環境下的錯誤處理,輸出錯誤信息。
app.get('/', routes.index):路由控制器,若是用戶訪問」 / 「路徑,則由 routes.index 來控制,routes/index.js 內容以下:
exports.index = function(req, res){ res.render('index', { title: 'Express' }); };
經過 exports.index 導出 index 函數接口,app.get('/', routes.index)
至關於:
app.get('/', function(req, res){ res.render('index', { title: 'Express' }); };)
res.render('index', { title: 'Express' }):調用 ejs 模板引擎解析 views/index.ejs(咱們以前經過 app.set('views', __dirname + '/views')
設置了模版文件默認存儲在 views 下),並傳入一個對象做爲參數,這個對象只有一個屬性 title: 'Express',即用字符串 Express 替換 views/index.ejs 中全部 title 變量,後面咱們將會了解更多關於模板引的內容。
http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port')); });
這段代碼的意思是建立服務器並監聽3000端口,成功後在命令行中顯示 Express server listening on port 3000,而後咱們就能夠經過在瀏覽器輸入 localhost:3000 來訪問了。
這一小節咱們學習瞭如何建立一個工程並啓動它,瞭解了工程的大致結構,下一節咱們將學習 Express 的基本使用及路由控制。
上面提到過 app.js 中 app.get('/', routes.index)
能夠用如下代碼取代:
app.get('/', function(req, res){ res.render('index', { title: 'Express' }); };)
這段代碼的意思是當訪問主頁時,調用 ejs 模板引擎,傳入 title 變量的值爲字符串 Express,來渲染 index.ejs 模版文件,生成靜態頁面並顯示在瀏覽器裏。
咱們來做一些修改,以上代碼實現了路由的功能,咱們固然能夠不要 routes/index.js 文件,把實現路由功能的代碼都放在 app.js 裏,但隨着時間的推移 app.js 會變得難以維護,這也違背了代碼模塊化的思想,因此咱們把實現路由功能的代碼都放在 routes/index.js 裏。官方給出的寫法是在 app.js 中實現了簡單的路由分配,而後再去 index.js 中找到對應的路由函數,最終實現路由功能。咱們不妨把路由控制器和實現路由功能的函數都放到 index.js 裏,app.js 中只有一個總的路由接口。
打開 app.js,刪除 , user = require('./routes/user')
(咱們這裏用不到 routes/user.js,同時刪除這個文件)和
app.get('/', routes.index); app.get('/users', user.list);
在 app.js 最後添加:
routes(app);
修改 index.js 以下:
module.exports = function(app){ app.get('/',function(req,res){ res.render('index', { title: 'Express' }); }); };
如今,再運行你的 app,你會發現主頁毫無二致。這兩種寫法的區別就比如:
你的朋友結婚了,你收到請帖要赴宴。到了酒店門口被總管給攔住了。官方的寫法是總管看了看請帖而後給你指了朋友團的地方,而後你過去坐下。咱的寫法是總管看了看請帖簡單確認了下你是被邀請的人,而後你進去本身找到朋友團的地方坐下。
express 封裝了多種 http 請求方式,但咱們這裏主要只使用 get 和 post 兩種。
get 和 post 的第一個參數都爲請求的路徑,第二個參數爲處理請求的回調函數,它有兩個參數分別是 req 和 res,表示請求信息和響應信息 。路徑請求及對應的獲取路徑有如下幾種形式:
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 請求
req.params 處理 /:xxx 形式的 get 請求
req.body 處理 post 請求
req.param(name) 能夠處理 get 和 post 請求,但查找優先級由高到低爲 req.params→req.body→req.query
路徑規則還支持正則表達式,更多請查閱:http://expressjs.com/api.html
當咱們訪問 localhost:3000 時,會顯示:
當咱們訪問 localhost:3000/nswbmw 這種不存在的頁面時就會顯示:
這是由於不存在 /nswbmw 的路由規則,並且它也不是一個 public 目錄下的文件,因此 Express 返回了 404 Not Found 的錯誤。
下面咱們來添加這條路由規則,使得當訪問 localhost:3000/nswbmw 時,頁面顯示 hello,world!
注意:如下修改僅用於測試,看到效果後再把代碼還原回來。
修改 index.js,在 app.get('/')
函數後添加一條路由規則:
app.get('/nswbmw',function(req,res){ res.send('hello.world!'); });
訪問 localhost:3000/nswbmw 頁面顯示以下:
很簡單吧?這一節咱們學習了基本的路由規則及如何添加一條路由規則,下一節咱們將學習模板引擎的知識。
模板引擎(Template Engine)是一個將頁面模板和要顯示的數據結合起來生成 HTML 頁面的工具。
若是說上面講到的 express 中的路由控制方法至關於 MVC 中的控制器的話,那模板引擎就至關於 MVC 中的視圖。
模板引擎的功能是將頁面模板和要顯示的數據結合起來生成 HTML 頁面。它既能夠運
行在服務器端又能夠運行在客戶端,大多數時候它都在服務器端直接被解析爲 HTML,解析
完成後再傳輸給客戶端,所以客戶端甚至沒法判斷頁面是不是模板引擎生成的。有時候模板
引擎也能夠運行在客戶端,即瀏覽器中,典型的表明就是 XSLT,它以 XML 爲輸入,在客
戶端生成 HTML 頁面。可是因爲瀏覽器兼容性問題,XSLT 並非很流行。目前的主流仍是
由服務器運行模板引擎。在 MVC 架構中,模板引擎包含在服務器端。控制器獲得用戶請求後,從模型獲取數據,
調用模板引擎。模板引擎以數據和頁面模板爲輸入,生成 HTML 頁面,而後返回給控制器,
由控制器交回客戶端。——《Node.js開發指南》
什麼是 ejs ?
ejs 是模板引擎的一種,也是咱們這個教程中使用的模板引擎,由於它十分簡單,並且與 Express 集成良好。
前面咱們經過如下兩行代碼設置了模板引擎和頁面模板的存儲位置:
app.set('views', __dirname + '/views'); app.set('view engine', 'ejs');
在 routes/index.js 中經過調用 res.render 渲染模版,並將其產生的頁面直接返回給客戶端。它接受兩個參數,第一個是模板的名稱,即 views 目錄下的模板文件名,擴展名 .ejs 可選;第二個參數是傳遞給模板的數據,用於模板翻譯。
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>
當咱們 res.render('index', { title: 'Express' });
時,模板引擎會把 <%= title %> 替換成 Express,而後把替換後的頁面現實給用戶。
渲染後生成的頁面代碼爲:
<!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>
注意:咱們設置了靜態文靜目錄爲 public(app.use(express.static(path.join(__dirname, 'public')))
),因此上面代碼中的 href='/stylesheets/style.css'
就至關於 href='public/stylesheets/style.css'
。
ejs 的標籤系統很是簡單,它只有如下3種標籤。
<% code %>:JavaScript 代碼。 <%= code %>:顯示替換過 HTML 特殊字符的內容。 <%- code %>:顯示原始 HTML 內容。
注意: <%= code %>
和 <%- code %>
的區別,當變量 code 爲字符串時,二者沒有區別;當 code 爲好比<h1>hello</h1>
時,<%= code %>
會原樣輸出 <h1>hello</h1>
,而<%- code %>
則會輸出H1大的 hello。
EJS 的官方示例:
The Data
{ title: 'Cleaning Supplies', supplies: ['mop', 'broom', 'duster'] }
The Template
<ul> <% for(var i=0; i<supplies.length; i++) {%> <li><%= supplies[i] %></li> <% } %> </ul>
The Result
mop
broom
duster
咱們能夠用上述三種方式實現頁面模板系統能實現的任何內容。
Express 3.* 中咱們再也不使用 layout.ejs 進行頁面佈局,轉而使用 include 來替代。include 的簡單使用以下:
a.ejs
<%- include b %> hello,world! <%- include c %>
b.ejs
this is b
c.ejs
this is c
最終 a.ejs 會顯示:
this is b hello,world! this is c
這一節咱們學習了模版引擎的相關知識,下一節咱們正式開始學習如何從頭開始搭建一個多人博客。
做爲入門教程,咱們要搭建的博客具備簡單的容許多人註冊、登陸、發表文章、登出的功能。
未登陸:主頁左側導航顯示 home、login、register,右側顯示已發表的文章、發表日期及做者。
登錄後:主頁左側導航顯示 home、post、logout,右側顯示已發表的文章、發表日期及做者。
用戶登陸、註冊、發表成功以及登出後都返回到主頁。
用戶登錄前:
主頁:
登陸頁:
註冊頁:
用戶登陸後:
主頁:
發表頁:
注意:沒有登出頁,當點擊 LOGOUT 後,退出登錄並返回到主頁。
咱們已經把設計的構想圖貼出來了,接下來的任務就是寫路由規劃了。路由規劃,或者說控制器規劃是整個網站的骨架部分,由於它處於整個架構的樞紐位置,至關於各個接口之間的粘合劑,因此應該優先考慮。
根據構思的設計圖,咱們做如下路由規劃:
/ :首頁 /login :用戶登陸 /reg :用戶註冊 /post :發表文章 /logout :登出
login 和 reg 頁只能是未登陸的用戶訪問,而 post 和 logout 頁只能是已登陸的用戶訪問。首頁則針對已登陸和未登陸的用戶顯示不一樣的內容。
修改 index.js 以下:
module.exports = function(app){ app.get('/',function(req,res){ res.render('index', { title: '主頁' }); }); app.get('/reg',function(req,res){ res.render('reg', { title: '註冊' }); }); 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: '發表' }); }); app.post('/post',function(req,res){ }); app.get('/logout',function(req,res){ }); };
如何針對已登陸和未登陸的用戶顯示不一樣的內容呢?或者說如何判斷用戶是否已經登錄了呢?進一步說如何記住用戶的登陸狀態呢?咱們經過引入會話機制,來記錄用戶登陸狀態,還要訪問數據庫來保存和讀取用戶信息。下一節咱們將學習如何使用數據庫。
MongoDB 是一個對象數據庫,它沒有表、行等概念,也沒有固定的模式和結構,全部的數據以文檔的形式存儲。所謂文檔就是一個關聯數組式的對象,它的內部由屬性組成,一個屬性對應的值多是一個數、字符串、日期、數組,甚至是一個嵌套的文檔。
——《Node.js開發指南》
下面是一個 MongoDB 文檔的示例:
{ "_id" : ObjectId( "4f7fe8432b4a1077a7c551e8" ), "name" : "nswbmw", "age" : 22, "email" : [ "xxx@126.com", "xxx@gmail.com" ], "family" : { "mother" : { ... }, "father" : { ... }, "sister : { ... }, "address" : "earth" } }
更多有關 MongoDB 的知識請參考 《mongodb權威指南》或查閱:http://www.mongodb.org/
安裝 mongodb 很簡單,去官網(http://www.mongodb.org/downloads)下載最新版的 mongodb,解壓到D盤並把文件夾重命名爲 mongodb,並在 mongodb 文件夾下新建 blog 文件夾做爲咱們的存儲目錄。打開 cmd,切換到 d:\mongodb\bin 目錄下,在命令行中輸入 mongod -dbpath 「d:\mongodb\blog」 設置 blog 文件夾做爲咱們工程的存儲目錄,這樣數據庫就成功啓動了。爲了方便之後使用數據庫,咱們在桌面上新建 「啓動mongodb.bat」 ,並寫入 d:\mongodb\bin\mongod.exe -dbpath d:\mongodb\blog
,這樣咱們之後只需運行桌面上的 「啓動mongodb.bat」 就可啓動數據庫了。
數據庫雖然安裝並啓動成功了,但咱們須要鏈接數據庫後才能使用數據庫。怎麼才能在 Node.js 中使用 MongoDb 呢?咱們須要使用 node-mongodb-native 模塊,打開 package.json,在 dependencies 中添加一行代碼:
{ "name": "blog", "version": "0.0.1", "private": true, "scripts": { "start": "node app.js" }, "dependencies": { "express": "3.2.4", "ejs": "*", "mongodb": "*" } }
而後運行 npm install 更新依賴的模塊,稍等片刻後模塊就下載並安裝完成。
接下來在工程的目錄中建立 settings.js 文件,這個文件用於保存數據庫的鏈接信息。咱們將數據庫命名爲 blog,數據庫服務器在本地,所以 settings.js 文件的內容以下:
module.exports = { cookieSecret: 'myblog', db: 'blog', host: 'localhost' };
其中 db 是數據庫的名稱,host 是數據庫的地址。cookieSecret 用於 Cookie 加密與數據庫無關,咱們留做後用。
接下來在根目錄下新建 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, Connection.DEFAULT_PORT, {}));
咱們先不用弄清楚代碼爲何這麼寫,咱們如今只需知道以上代碼經過 module.exports 輸出了建立的數據庫鏈接,在後面的小節中咱們會用到這個模塊。
會話是一種持久的網絡協議,用於完成服務器和客戶端之間的一些交互行爲。會話是一個比鏈接粒度更大的概念, 一次會話可能包含屢次鏈接,每次鏈接都被認爲是會話的一次操做。在網絡應用開發中,有必要實現會話以幫助用戶交互。例如網上購物的場景,用戶瀏覽了多個頁面,購買了一些物品,這些請求在屢次鏈接中完成。許多應用層網絡協議都是由會話支持的,如 FTP、Telnet 等,而 HTTP 協議是無狀態的,自己不支持會話,所以在沒有額外手段的幫助下,前面場景中服務器不知道用戶購買了什麼。
爲了在無狀態的 HTTP 協議之上實現會話,Cookie 誕生了。Cookie 是一些存儲在客戶端的信息,每次鏈接的時候由瀏覽器向服務器遞交,服務器也向瀏覽器發起存儲 Cookie 的請求,依靠這樣的手段服務器能夠識別客戶端。咱們一般意義上的 HTTP 會話功能就是這樣實現的。具體來講,瀏覽器首次向服務器發起請求時,服務器生成一個惟一標識符併發送給客戶端瀏覽器,瀏覽器將這個惟一標識符存儲在 Cookie 中,之後每次再發起請求,客戶端
瀏覽器都會向服務器傳送這個惟一標識符,服務器經過這個惟一標識符來識別用戶。 對於開發者來講,咱們無須關心瀏覽器端的存儲,須要關注的僅僅是如何經過這個惟一標識符來識別用戶。不少服務端腳本語言都有會話功能,如 PHP,把每一個惟一標識符存儲到文件中。Express 也提供了會話中間件,默認狀況下是把用戶信息存儲在內存中,但咱們既然已經有了 MongoDB,不妨把會話信息存儲在數據庫中,便於持久維護。
——《Node.js開發指南》
爲了使用這一功能,咱們首先要獲取一個叫作 connect-mongo 的模塊,在 package.json 中添加一行代碼:
{ "name": "blog", "version": "0.0.1", "private": true, "scripts": { "start": "node app.js" }, "dependencies": { "express": "3.2.4", "ejs": "*", "mongodb": "*", "connect-mongo": "*" } }
運行 npm install 安裝模塊。而後打開 app.js,在 , path = require('path')
後添加如下代碼:
, MongoStore = require('connect-mongo')(express) , settings = require('./settings');
在 app.use(express.methodOverride());
後添加:
app.use(express.cookieParser()); app.use(express.session({ secret: settings.cookieSecret, key: settings.db, cookie: {maxAge: 1000 * 60 * 60 * 24 * 30},//30 days store: new MongoStore({ db: settings.db }) }));
其中 express.cookieParser() 是 Cookie 解析的中間件。express.session() 則提供會話支持,secret 用來防止篡改 cookie,key 的值爲 cookie 的名字,經過設置 cookie 的 maxAge 值設定cookie的生存期,這裏咱們設置 cookie 的生存期爲30天,設置它的 store 參數爲 MongoStore 實例,把會話信息存儲到數據庫中,以免丟失。在後面的小節中,咱們能夠經過 req.session 獲取當前用戶的會話對象,以維護用戶相關的信息。
咱們已經準備好了數據庫訪問和會話存儲的相關信息,接下來咱們完成用戶註冊和登陸功能。
首先咱們來完成主頁、登陸頁和註冊頁的頁面設計。
修改 views/index.ejs 以下:
<%- include header %> 這是主頁 <%- 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="主頁" href="/">home</a></span> <span><a title="登陸" href="/login">login</a></span> <span><a title="註冊" href="/reg">register</a></span> </nav> <article>
在 views 下新建 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;}
此時運行 app.js ,主頁顯示以下:
接下來在 views 下新建 login.ejs,內容以下:
<%- include header %> <form method="post"> 用戶名:<input type="text" name="name"/><br /> 密碼: <input type="password" name="password"/><br /> <input type="submit" value="登陸"/> </form> <%- include footer %>
登陸頁面顯示以下:
在 views 下新建reg.ejs,內容以下:
<%- include header %> <form method="post"> 用戶名: <input type="text" name="name"/><br /> 密碼: <input type="password" name="password"/><br /> 確認密碼:<input type="password" name="password-repeat"/><br /> 郵箱: <input type="email" name="email"/><br /> <input type="submit" value="註冊"/> </form> <%- include footer %>
註冊頁面顯示以下:
至此,未登陸時的主頁、註冊頁、登陸頁都已經完成。
接下來咱們將實現用戶註冊和登錄,在這以前咱們須要引入 flash 模塊來實現頁面的通知和錯誤信息顯示的功能。
什麼是 flash?
req.flash 是 Express 提供的一個奇妙的工具,經過它保存的變量只會在用戶當前和下一次的請求中被訪問( req.flash 是存放在session裏的),以後會被清除,經過它咱們能夠很方便地實現頁面的通知和錯誤信息顯示功能。
——《Node.js開發指南》
在 package.json 添加一行代碼:
{ "name": "blog", "version": "0.0.1", "private": true, "scripts": { "start": "node app.js" }, "dependencies": { "express": "3.2.4", "ejs": "*", "mongodb": "*", "connect-mongo": "*", "connect-flash": "*" } }
而後 npm install 安裝 flash 模塊,修改 app.js 在 , settings = require('./settings')
後添加:
, flash = require('connect-flash');
在 app.set('view engine', 'ejs');
後添加:
app.use(flash());
如今咱們就可使用 flash 功能了。
前面咱們已經完成了註冊頁,固然如今點擊註冊是沒有效果的,由於咱們尚未實現處理 POST 請求的功能,下面就來實現它。
在 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; User.prototype.save = function(callback) {//存儲用戶信息 //要存入數據庫的用戶文檔 var user = { name: this.name, password: this.password, email: this.email }; //打開數據庫 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 集合 collection.insert(user,{safe: true}, function(err, user){ mongodb.close(); callback(err, user);//成功!返回插入的用戶信息 }); }); }); }; User.get = function(name, 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); } //查找用戶名 name 值爲 name文檔 collection.findOne({ name: name },function(err, doc){ mongodb.close(); if(doc){ var user = new User(doc); callback(err, user);//成功!返回查詢的用戶信息 } else { callback(err, null);//失敗!返回null } }); }); }); };
咱們暫時先不去研究這些代碼,咱們只需知道經過 User.prototype.save
實現了用戶信息的存儲,經過 User.get
實現了用戶信息的讀取。
打開 routes/index.js ,在最前面添加以下代碼:
var crypto = require('crypto'), User = require('../models/user.js');
經過 require 引入 user.js 用戶模型和 crypto 模塊,crypto 是 Node.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']; //檢驗用戶兩次輸入的密碼是否一致 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: req.body.name, password: password, email: req.body.email }); //檢查用戶名是否已經存在 User.get(newUser.name, function(err, user){ if(user){ err = '用戶已存在!'; } 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;//用戶信息存入session req.flash('success','註冊成功!'); res.redirect('/'); }); }); });
注意:咱們把用戶信息存儲在了 session 裏,之後就能夠經過 req.session.user 讀取用戶信息。
req.body: 就是 POST 請求信息解析事後的對象,例如咱們要訪問用戶傳遞的 name=「password」 域的值,只需訪問 req.body['password'] 或 req.body.password 便可。
res.redirect: 重定向功能,實現了頁面的跳轉,更多關於 res.redirect 的信息請查閱:http://expressjs.com/api.html#res.redirect
User:在前面的代碼中,咱們直接使用了 User 對象。User 是一個描述數據的對象,即 MVC
架構中的模型。前面咱們使用了許多視圖和控制器,這是第一次接觸到模型。與視圖和控制器不一樣,模型是真正與數據打交道的工具,沒有模型,網站就只是一個外殼,不能發揮真實的做用,所以它是框架中最根本的部分。
在桌面新建 「啓動app.bat」 並寫入 :
supervisor d:\blog\app //node d:\blog\app
之後咱們能夠經過打開 「啓動app.bat」 來啓動咱們的博客,而沒必要每次都要在命令行中啓動。
如今前後運行你的 「啓動mongodb.bat」 和 「啓動app.bat」 ,打開瀏覽器輸入 localhost:3000/reg 註冊試試吧!註冊成功後顯示以下:
咱們查看數據庫中是否存入了用戶的信息,運行 d:\mongodb\bin\mongo ,輸入:
用戶信息已經成功存入數據庫。但這還不是咱們想要的效果,咱們想要的效果是當註冊成功返回主頁時,左側導航顯示 HOME 、POST 、LOGOUT ,右側顯示 「註冊成功!」字樣。下面咱們來實現它。
修改 index.js ,將 app.get('/')
修改以下:
app.get('/',function(req,res){ res.render('index',{ title:'主頁', user: req.session.user, success:req.flash('success').toString(), error:req.flash('error').toString() }); });
修改 header.ejs,將<nav></nav>
修改以下:
<nav> <span><a title="主頁" href="/">home</a></span> <% if(locals.user){ %> <span><a title="發表" 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="註冊" href="/reg">register</a></span> <% } %> </nav>
在 <article>
後添加以下代碼:
<% if (locals.success) { %> <div ><%= locals.success %></div> <% } %> <% if (locals.error) { %> <div><%= locals.error %> </div> <% } %>
如今註冊成功後顯示以下:
咱們經過對 session 的使用實現了對用戶狀態的檢測,再根據不一樣的用戶狀態顯示不一樣的導航信息。
簡單解釋一下流程:用戶在註冊成功後,把用戶信息存入 session ,同時頁面跳轉到主頁顯示 「註冊成功!」 。而後把 session 中的用戶信息賦給全局變量 res.locals.user ,在渲染 ejs 文件時經過檢測 locals.user 判斷用戶是否在線,根據用戶狀態的不一樣顯示不一樣的導航信息。
如今咱們來實現登錄的響應。
打開 index.js 將 app.post('/login')
修改以下:
app.post('/login', function(req, res){ //生成密碼的散列值 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'); } //檢查密碼是否一致 if(user.password != password){ req.flash('error', '密碼錯誤!'); return res.redirect('/login'); } //用戶名密碼都匹配後,將用戶信息存入 session req.session.user = user; req.flash('success','登錄成功!'); res.redirect('/'); }); });
接下來咱們實現登出響應。修改 app.get('/logout')
以下:
app.get('/logout', function(req, res){ req.session.user = null; req.flash('success','登出成功!'); res.redirect('/'); });
經過把 req.session.user 賦值 null 丟掉 session 中用戶的信息,實現用戶的退出。
登陸後頁面顯示以下:
登出後頁面顯示以下:
至此,咱們實現了用戶註冊與登錄的功能,而且根據用戶登陸狀態顯示不一樣的導航。
咱們雖然已經完成了用戶註冊與登錄的功能,但並不能阻止好比已經登錄的用戶訪問 localhost:3000/reg 頁面(讀者可親自嘗試下)。爲此,咱們須要爲頁面設置訪問權限。即註冊和登錄頁面應該阻止已登錄的用戶訪問,登出及後面咱們將要實現的發表頁只對已登陸的用戶開放。如何實現頁面權限的控制呢?咱們能夠把用戶登陸狀態的檢查放到路由中間件中,在每一個路徑前增長路由中間件,便可實現頁面權限控制。咱們添加 checkNotLogin 和 checkLogin 函數。
最終 index.js 中代碼以下:
var crypto = require('crypto'), User = require('../models/user.js'); module.exports = function(app){ app.get('/',function(req,res){ res.render('index',{ title:'主頁', 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: '註冊', 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: req.body.name, password: password, email: req.body.email }); //檢查用戶名是否已經存在 User.get(newUser.name, function(err, user){ if(user){ err = '用戶已存在!'; } 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;//用戶信息存入session req.flash('success','註冊成功!'); res.redirect('/'); }); }); }); app.get('/login', checkNotLogin); app.get('/login', function(req, res){ res.render('login',{ title: '登陸', user: req.session.user, success: req.flash('success').toString(), error: req.flash('error').toString() }); }); app.post('/login', checkNotLogin); app.post('/login', function(req, res){ //生成密碼的散列值 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'); } //檢查密碼是否一致 if(user.password != password){ req.flash('error', '密碼錯誤!'); return res.redirect('/login'); } //用戶名密碼都匹配後,將用戶信息存入 session req.session.user = user; req.flash('success','登錄成功!'); res.redirect('/'); }); }); app.get('/post', checkLogin); app.get('/post',function(req,res){ res.render('post', { title: '發表', user: req.session.user, success: req.flash('success').toString(), error: req.flash('error').toString() }); }); app.post('/post', checkLogin); app.post('/post',function(req,res){ }); app.get('/logout', checkLogin); app.get('/logout', function(req, res){ req.session.user = null; req.flash('success','登出成功!'); res.redirect('/'); }); }; function checkLogin(req, res, next){ if(!req.session.user){ req.flash('error','未登陸!'); return res.redirect('/login'); } next(); } function checkNotLogin(req,res,next){ if(req.session.user){ req.flash('error','已登陸!'); return res.redirect('/'); } next(); }
注意:爲了維護用戶狀態和 flash 通知功能的使用,咱們在 app.get('/reg)'
和 app.get('/login)
和 app.get('/post')
裏添加了如下代碼:
user: req.session.user, success: req.flash('success').toString(), error: req.flash('error').toString()
如今咱們的博客已經具有了用戶註冊、登錄、頁面權限控制的功能,接下來咱們完成博客最核心的部分——發表文章。在這一節,咱們將會實現發表文章的功能,完成整個博客的設計。
咱們先來完成發表頁的頁面設計。在 views 文件夾下新建 post.ejs ,添加以下代碼:
<%- include header %> <form method="post"> 標題:<br /> <input type="text" name="title" /><br /> 正文:<br /> <textarea name="post" rows="20" cols="100"></textarea><br /> <input type="submit" value="發表" /> </form> <%- include footer %>
仿照用戶模型,咱們將文章模型命名爲 Post 對象,它擁有與 User 類似的接口,分別是 Post.get 和 Post.prototype.save。Post.get 的功能是從數據庫中獲取文章,能夠按指定用戶獲取,也能夠獲取所有的內容。Post.prototype.save 是 Post 對象原型的方法,用來將文章保存到數據庫。
在 models 文件夾下新建 post.js ,添加以下代碼:
var mongodb = require('./db'); function Post(name, title, post) { this.name = name; this.title= title; this.post = post; } module.exports = Post; Post.prototype.save = function(callback) {//存儲一篇文章及其相關信息 var date = new Date(); //存儲各類時間格式,方便之後擴展 var time = { date: date, year : date.getFullYear(), month : date.getFullYear() + "-" + (date.getMonth()+1), day : date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate(), minute : date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() } //要存入數據庫的文檔 var post = { name: this.name, time: time, title:this.title, post: this.post }; //打開數據庫 mongodb.open(function (err, db) { if (err) { return callback(err); } //讀取 posts 集合 db.collection('posts', function (err, collection) { if (err) { mongodb.close(); return callback(err); } //將文檔插入 posts 集合 collection.insert(post, { safe: true }, function (err,post) { mongodb.close(); callback(null); }); }); }); }; Post.get = function(name, 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); } var query = {}; if (name) { query.name = name; } //根據 query 對象查詢文章 collection.find(query).sort({ time: -1 }).toArray(function (err, docs) { mongodb.close(); if (err) { callback(err, null);//失敗!返回 null } callback(null, docs);//成功!以數組形式返回查詢的結果 }); }); }); };
接下來咱們給發表文章註冊響應,打開 index.js ,在 User = require('../models/user.js')
後添加一行代碼:
Post = require('../models/post.js');
修改 app.post('/post')
以下:
app.post('/post', function(req, res){ var currentUser = req.session.user, post = new Post(currentUser.name, req.body.title, req.body.post); post.save(function(err){ if(err){ req.flash('error', err); return res.redirect('/'); } req.flash('success', '發佈成功!'); res.redirect('/'); }); });
最後,咱們修改 index.ejs ,讓主頁右側顯示發表過的文章及其相關信息。
打開 index.ejs ,修改以下:
<%- include header %> <% posts.forEach(function(post, index){ %> <p><h2><a href="#"><%= post.title %></a></h2></p> <p class="info"> 做者:<a href="#"><%= post.name %></a> | 日期:<%= post.time.minute %> </p> <p><%- post.post %></p> <% }) %> <%- include footer %>
打開 index.js ,修改 app.get('/')
以下:
app.get('/', function(req,res){ Post.get(null, function(err, posts){ if(err){ posts = []; } res.render('index',{ title: '主頁', user: req.session.user, posts: posts, success: req.flash('success').toString(), error: req.flash('error').toString() }); }); });
至此,咱們的博客就建成了。