下面開始用 Node.js 進行 Web 開發。
我是通過《Node.js開發指南》這本書來學習 Node.js Web 開發的,書中使用的 Express 框架是 2.5.8,而我的是 4.14.1,所以遇到了許多問題,在文章中我都有提到並講解。
一、快速開始
1、建立專案
《Node.js開發指南》中建立專案的方式是:express -t ejs microblog,但是這種方式對於高版本的 Express 新建的標籤替換引擎並不是 .ejs,而是 .jade,如果要使用 .ejs 我們可以通過下面命令建立網站基本結構。
express -e NodeJSBlog
複製程式碼
執行命令後在當前目錄下出現了一些檔案,並且下邊提示我們通過 npm install 安裝依賴。
在 npm install 之後,開啟 NodeJSBlog 目錄下的 package.json,可以看到已安裝的包及對應的版本號。
2、啟動伺服器
注意,我們之前開啟 Node.js 伺服器,都是執行 node xxx.js,然後去瀏覽器訪問即可,但是 Express 4.x 以上就不是這種方式了,應該是 npm start,埠配置在 bin/www 中。
啟動成功訪問 localhost:3000/。
3、專案結構
我們看一下 express 在 NodeJSBlog 這個目錄下都生成了哪些檔案。
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 index = 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(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('/', index);
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 handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
複製程式碼
app.js 是專案的入口,首先引入了一系列我們所需要的模組,然後引入了 routes 目錄下的兩個本地模組,它的功能是為指定路徑組織返回內容,相當於 MVC 架構中的控制器。
接下來是檢視引擎設定, app.set() 是 Express 的引數設定工具,接受一個鍵(key)和一個值(value),可用的引數如下所示:
- basepath:基礎地址,通常用於 res.redirect() 跳轉。
- views:檢視檔案的目錄,存放模板檔案。
- view engine:檢視模板引擎。
- view options:全域性檢視引數物件。
- view cache:啟用檢視快取。
- case sensitive routes:路徑區分大小寫。
- strict routing:嚴格路徑,啟用後不會忽略路徑末尾的“ / ”。
- jsonp callback:開啟透明的 JSONP 支援
Express 依賴於 connect,提供了大量的中介軟體,可以通過 app.use() 啟用
routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
複製程式碼
routes/index.js 是路由檔案,相當於控制器,用於組織展示的內容,app.js 中通過 app.get('/', routes.index); 將“ / ”路徑對映到 exports.index 函式下,其中只有一個語句 res.render('index', { title: 'Express' }),功能是呼叫模板解析引擎,翻譯名為 index 的模板,並傳入一個物件作為引數,這個物件只有一個屬性,即 title: 'Express'。
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>
複製程式碼
index.ejs 是模板檔案,即 routes/index.js 中呼叫的模板,內容是:
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
複製程式碼
它的基礎是 HTML 語言,其中包含了形如 <%= title %> 的標籤,功能是顯示引用的變數,即 res.render 函式第二個引數傳入的物件的屬性。
補充 include
在書中 views 目錄下是有 layout.ejs 的,它可以讓所有模板去繼承它,<%- body %> 中是獨特的內容,其他部分是共有的,可以看作是頁面框架。
書中 layout.ejs:
<head>
<title>
<%= title %>
</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<%- body %>
</body>
</html>
複製程式碼
但是 Express 4.x 就沒有 layout.ejs 了,解決方法:
官方推薦了 include 方式,它不僅能實現 layout 的功能,還是將 view 的那些可複用的 html 片段提取成模組,在需要使用的地方直接用 <% include xxx %>。
例如先在 views 目錄下新建一個 public_file.ejs ,在裡面新增需要引用的公共檔案:
<link rel='stylesheet' href='/stylesheets/style.css' />
複製程式碼
然後修改一下 index.ejs,使用 <% include listitem %> 方式引用上邊公共檔案:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<% include public_file %>
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>
複製程式碼
重啟服務,訪問 localhost:3000/,可以看到 style.css 檔案正常引用。
每一次修改我們都需要重啟服務才能看到修改後的結果,您可以看我的《Node.js 應用程式自動重啟》這篇文章去安裝 supervisor 或 nodemon 兩個外掛來實現應用程式自動重啟。
二、路由控制
1、建立頁面路由
簡單說一下新增一個頁面的流程。
首先在 views 目錄下新建一個模板,例如 hello.ejs:
然後開啟 index.js 檔案,新增頁面路由資訊:
訪問 localhost:3000/hello。
補充:在《Node.js開發指南》這本書中,還需要向 app.js 檔案中新增頁面路由資訊,但在 Express 4.x 中是不需要的。
2、路徑匹配
上面的例子是為固定的路徑設定路由規則,Express 還支援更高階的路徑匹配模式,例如我們想要展示一個使用者的個人頁面,路徑為 /user/[username],可以用下面的方法定義路由 規則:
router.get('/user/:username', function(req, res, next) {
res.send('user: ' + req.params.username);
});
複製程式碼
重啟專案,訪問 localhost:3000/user/LiuZhenghe。
注意:呼叫模板解析引擎,用 res.render(),只是向頁面傳送資料,用 res.send()。
路徑規則 /user/:username 會被自動編譯為正規表示式,類似於 /user/([^/]+)/? 這樣的形式,路徑引數可以在響應函式中通過 req.params 的屬性訪問。
路徑規則同樣支援 JavaScript 正規表示式,例如 app.get(/user/([^/]+)/?,callback),這樣的好處在於可以定義更加複雜的路徑規則,而不同之處是匹配的引數是匿名的,因此需要通過 req.params[0]、req.params[1] 這樣的形式訪問。
3、REST 風格的路由規則
Express 支援 REST 風格的請求方式,在介紹之前我們先說明一下什麼是 REST。
REST 的意思是表徵狀態轉移(Representational State Transfer),它是一種基於 HTTP 協議的網路應用的介面風格,充分利用 HTTP 的方法實現統一風格介面的服務。
HTTP 協議定義了以下 8 種標準的方法:
- GET:請求獲取指定資源。
- HEAD:請求指定資源的響應頭。
- POST:向指定資源提交資料。
- PUT:請求伺服器儲存一個資源。
- DELETE:請求伺服器刪除指定資源。
- TRACE:回顯伺服器收到的請求,主要用於測試或診斷。
- CONNECT:HTTP/1.1 協議中預留給能夠將連線改為管道方式的代理伺服器。
- OPTIONS:返回伺服器支援的HTTP請求方法。
其中我們經常用到的是 GET、POST、PUT 和 DELETE 方法,根據 REST 設計模式,這4種方法通常分別用於實現以下功能。
- GET:獲取
- POST:新增
- PUT:更新
- DELETE:刪除
這是因為這 4 種方法有不同的特點,按照定義,它們的特點如下表所示:
請求方式 | 安全 | 冪等 |
---|---|---|
GET | 是 | 是 |
POST | 否 | 否 |
PUT | 否 | 是 |
DELETE | 否 | 是 |
所謂安全是指沒有副作用,即請求不會對資源產生變動,連續訪問多次所獲得的結果不受訪問者的影響,而冪等指的是重複請求多次與一次請求的效果是一樣的,比如獲取和更新操作是冪等的,這與新增不同,刪除也是冪等的,即重複刪除一個資源,和刪除一次是一樣的。
Express 對每種 HTTP 請求方法都設計了不同的路由繫結函式,例如前面例子全部是 app.get,表示為該路徑繫結了 GET 請求,向這個路徑發起其他方式的請求不會被響應。
下表是 Express 支援的所有 HTTP 請求的繫結函式。
請求方式 | 繫結函式 |
---|---|
GET | app.get(path, callback) |
POST | app.post(path, callback) |
PUT | app.put(path, callback) |
DELETE | app.delete(path, callback) |
PATCH | app.patch(path, callback) |
TRACE | app.trace(path, callback) |
CONNECT | app.connect(path, callback) |
OPTIONS | app.options(path, callback) |
所有方法 | app.all(path, callback) |
例如我們要繫結某個路徑的 POST 請求,則可以用 app.post(path, callback) 的 方法,需要注意的是 app.all 函式,它支援把所有的請求方式繫結到同一個響應函式,是一個非常靈活的函式,在後面我們可以看到許多功能都可以通過它來實現。
4、控制權轉移
Express 支援同一路徑繫結多個路由響應函式,例如:
index.js
// ...
/* 路徑匹配模式 */
router.all('/user/:username', function(req, res, next) {
res.send('all methods captured');
});
router.get('/user/:username', function(req, res, next) {
res.send('user: ' + req.params.username);
});
// ...
複製程式碼
當再次訪問 localhost:3000/user/LiuZhenghe 時,發現頁面被第一條路由規則捕獲。
原因是 Express 在處理路由規則時,會優先匹配先定義的路由規則,因此後面相同的規則被遮蔽。
Express 提供了路由控制權轉移的方法,即回撥函式的第三個引數 next,通過呼叫 next(),會將路由控制權轉移給後面的規則,例如:
// ...
/* 路徑匹配模式 */
router.all('/user/:username', function(req, res, next) {
console.log('all methods captured');
next();
});
router.get('/user/:username', function(req, res, next) {
res.send('user: ' + req.params.username);
});
// ...
複製程式碼
此時重新整理頁面,在控制檯可以看到“all methods captured”,瀏覽器顯示了 user: LiuZhenghe。
這是一個非常有用的工具,可以讓我們輕易地實現中介軟體,而且還能提高程式碼的複用程度,例如我們針對一個使用者查詢資訊和修改資訊的操作,分別對應了 GET 和 PUT 操作,而兩者共有的一個步驟是檢查使用者名稱是否合法,因此可以通過 next() 方法實現。
三、模板引擎
模板引擎也就是檢視,檢視決定了使用者最終能看到什麼,這裡我們用 ejs 為例介紹模板引擎的使用方法。
1、使用模板引擎
在 app.js 中,以下兩句設定了模板引擎和頁面模板的位置:
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
複製程式碼
在 index.js 中通過 res.render() 呼叫模板,res.render() 的功能是呼叫模板引擎,並將其產生的頁面直接返回給客戶端,它接受兩個引數,第一個是模板的名稱,即 views 目錄下的模板檔名,不包含檔案的副檔名;第二個引數是傳遞給模板的資料,用於模板翻譯。
ejs 的標籤系統非常簡單,它只有以下 3 種標籤:
- <% code %>:JavaScript 程式碼。
- <%= code %>:顯示替換過 HTML 特殊字元的內容。
- <%- code %>:顯示原始 HTML 內容。
我們可以用它們實現頁面模板系統能實現的任何內容。
2、片段檢視
《Node.js開發指南》中所講的片段檢視(partials)在 Express 4.x 中已經不支援了,在上面專案結構分析那一節中我曾補充過 include,這裡再次介紹一下它的用法。
官方推薦了 include 方式,它不僅能實現 layout 的功能,還是將 view 的那些可複用的 html 片段提取成模組,在需要使用的地方直接用 <% include xxx %>,看下面這個例子:
首先在 index.js 中新增以下內容:
// 片斷檢視
router.get('/list', function(reg, res) {
res.render('list', {
title: "List",
items: [2019, 'Node.js', 'NodeJSBlog', 'Express']
});
});
複製程式碼
然後新建 list.ejs 檔案並新增以下內容:
<ul>
<% items.forEach(function(listitem){ %>
<% include listitem %>
<% }) %>
</ul>
複製程式碼
同時新建 listitem.ejs 檔案並新增:
<li><%= listitem %></li>
複製程式碼
訪問 localhost:3000/list,可以看到以下內容:
四、開始建立部落格網站
1、功能分析
部落格網站首先應該有登入註冊功能,然後是最核心的功能——資訊發表,這個功能涉及到許多方面,包括資料庫訪問,前端顯示等。
一個完整的部落格系統,應該有評論,收藏,轉發等功能,處於本人目前的能力水平還不能都實現,先做一個部落格網站的雛形吧。
2、路由規劃
根據功能設計,我們把路由按照以下方案規劃:
- /:首頁
- /u/[user]:使用者的主頁
- /post:發表資訊
- /reg:使用者註冊
- /login:使用者登入
- /logout:使用者登出
以上頁面還可以根據使用者狀態細分,發表資訊以及使用者登出頁面必須是已登入使用者才能操作的功能,而使用者註冊和使用者登入所面向的物件必須是未登入的使用者,首頁和使用者主頁則針對已登入和未登入的使用者顯示不同的內容。
在 index.js 中新增以下內容:
router.get('/', function(req, res) {
res.render('index', {
title: 'Express'
});
});
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 由於要接受表單資訊,因此使用 router.post 註冊路由,/login 和 /reg 還要顯示使用者註冊時要填寫的表單,所以要以 router.get 註冊。
3、使用 Bootstrap
下載 jquery.js,bootstrap.css 和 bootstrap.js,放到 public 下對應的目錄中。
在 public_file.ejs 中引用,可以使用 include 方法給模板新增公共檔案。
去 Bootstrap官網 檢視並使用所需的模板或元件。
下圖是我從 Bootstrap 官網找的一個模板,並放到了 index.ejs 目錄下並進行了簡單地修改,並將頁面公共部分(頭尾部分)取出,用 include 方式來複用。
五、使用者註冊和登入
1、訪問資料庫
我們選用 MongoDB 作為網站的資料庫系統,它是一個開源的 NoSQL 資料庫,相比 MySQL 那樣的關係型資料庫,它更為輕巧、靈活,非常適合在資料規模很大、事務性不強的場合下使用。
連線資料庫
通過 npm 安裝 mongodb。
npm install mongodb --save
複製程式碼
補充:通過 --save 安裝,包名和版本號將會出現在 package.json 中。
接下來在專案主目錄中建立 settings.js 檔案,這個檔案用於儲存資料庫的連線資訊,我們將用到的資料庫命名為 NodeJSBlog,資料庫伺服器在本地,因此 settings.js 檔案的內容如下:
settings.js
module.exports = {
cookieSecret: 'NodeJSBlogbyvoid',
db: 'NodeJSBlog',
host: 'localhost',
};
複製程式碼
其中,db 是資料庫的名稱,host 是資料庫的地址,cookieSecret 用於 Cookie 加密與資料庫無關,我們留作後用。
接下來新建 models 目錄,並在目錄中建立 db.js:
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, 27017, {}), {
safe: true
});
複製程式碼
以上程式碼通過 module.exports 輸出了建立的資料庫連線,在後面的小節中我們會用到這個模組,由於模組只會被載入一次,以後我們在其他檔案中使用時均為這一個例項。
2、會話支援
《Node.js開發指南》中對會話支援是這樣描述的:
會話是一種持久的網路協議,用於完成伺服器和客戶端之間的一些互動行為。會話是一個比連線粒度更大的概念,一次會話可能包含多次連線,每次連線都被認為是會話的一次操作。在網路應用開發中,有必要實現會話以幫助使用者互動。例如網上購物的場景,使用者瀏覽了多個頁面,購買了一些物品,這些請求在多次連線中完成。許多應用層網路協議都是由會話支援的,如 FTP、Telnet 等,而 HTTP 協議是無狀態的,本身不支援會話,因此在沒有額外手段的幫助下,前面場景中伺服器不知道使用者購買了什麼。
為了在無狀態的 HTTP 協議之上實現會話,Cookie 誕生了。Cookie 是一些儲存在客戶端的資訊,每次連線的時候由瀏覽器向伺服器遞交,伺服器也向瀏覽器發起儲存 Cookie 的請求,依靠這樣的手段伺服器可以識別客戶端。我們通常意義上的 HTTP 會話功能就是這樣實現的。具體來說,瀏覽器首次向伺服器發起請求時,伺服器生成一個唯一識別符號併傳送給客戶端瀏覽器,瀏覽器將這個唯一識別符號儲存在 Cookie 中,以後每次再發起請求,客戶端瀏覽器都會向伺服器傳送這個唯一識別符號,伺服器通過這個唯一識別符號來識別使用者。
對於開發者來說,我們無須關心瀏覽器端的儲存,需要關注的僅僅是如何通過這個唯一識別符號來識別使用者。很多服務端指令碼語言都有會話功能,如 PHP,把每個唯一識別符號儲存到檔案中。Express 也提供了會話中介軟體,預設情況下是把使用者資訊儲存在記憶體中,但我們既然已經有了 MongoDB,不妨把會話資訊儲存在資料庫中,便於持久維護。
但是如果你的 Express 版本是 4.x,按照書中接下來的內容編寫程式碼,就會出各種問題,下面我來說一下 Express 4.x 中的會話支援。
在 Express 4.x 中我們需要自己安裝 express-session 包,然後新增引用:
var session = require('express-session');
複製程式碼
然後再引用 connect-mongo 包:
var MongoStore = require('connect-mongo')(session);
複製程式碼
最終在 app.js 中新增的內容就是:
var settings = require('./settings');
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
app.use(session({
secret: settings.cookieSecret,
store: new MongoStore({
db: settings.db,
})
}));
複製程式碼
但是此時程式會報錯:'Connection strategy not found':
從網上查詢到該問題之後找到了解決辦法,開啟 package.json 檔案,將 connect-mongo 的版本改為 0.8.2,然後執行 npm install 即可解決。
如果程式沒有報錯,那麼就成功了,你可以使用 mongo.exe 或視覺化工具來檢視資料庫是否新建成功。
3、註冊和登入
3.1、註冊
通過視覺化工具或 mongodb 終端新建一個使用者表 users。
註冊頁面:
首先新增註冊頁面的模板 views/reg.ejs,並新增表單結構:
<div class="container">
<div class="bs-example" data-example-id="basic-forms">
<form>
<div class="form-group">
<label for="username">使用者名稱</label>
<input type="text" class="form-control" id="username" placeholder="請輸入使用者名稱">
</div>
<div class="form-group">
<label for="password">密碼</label>
<input type="password" class="form-control" id="password" placeholder="請輸入密碼">
</div>
<div class="form-group">
<label for="password_repeat">再次輸入密碼</label>
<input type="password" class="form-control" id="password_repeat" placeholder="再次輸入密碼">
</div>
<button type="submit" class="btn btn-default">註冊</button>
</form>
</div>
</div>
複製程式碼
然後開啟 index.js 新增註冊頁面的路由資訊:
index.js
router.get('/reg', function(req, res) {
res.render('reg', {
title: '使用者註冊'
});
});
複製程式碼
然後訪問 localhost:3000/reg。
註冊響應:
在書中使用了 flash,但是最新版本的 Express 已經不支援 flash 了,你需要先通過 npm 安裝 connect-flash。
然後在 app.js 中新增如下程式碼:
var flash = require('connect-flash');
複製程式碼
在 routes/index.js 中新增 /reg 的 POST 響應函式:
routes/index.js
// 註冊響應
var crypto = require('crypto');
var User = require('../models/user.js');
var MongoClient = require('mongodb').MongoClient;
const DB_CONN_STR='mongodb://localhost:27017/users';
router.post('/reg', function(req, res) {
let newUser = {
username: req.body.username,
password: req.body.password,
password_repeat: req.body.password_repeat
};
let addStr = [{
username: newUser.username,
password: newUser.password
}];
MongoClient.connect(DB_CONN_STR, function(err, db) {
db.collection('users').findOne({
username: newUser.username
}, function(err, result) {
if (!result) {
if (newUser.password === newUser.password_repeat) {
MongoClient.connect(DB_CONN_STR, function(err, db) {
req.session.error = '註冊成功,請登入!';
db.collection('users').insert(addStr);
db.close();
return res.redirect('/login');
});
} else {
req.session.error = '兩次密碼不一致!';
return res.redirect('/register');
}
} else {
req.session.error = '使用者名稱已存在!';
return res.redirect('/register');
}
})
db.close();
});
});
複製程式碼
- req.body 就是 POST 請求資訊解析過後的物件,例如我們要訪問使用者傳遞的 password 域的值,只需訪問 req.body['password'] 即可。
- req.flash 是 Express 提供的一個奇妙的工具,通過它儲存的變數只會在使用者當前和下一次的請求中被訪問,之後會被清除,通過它我們可以很方便地實現頁面的通知和錯誤資訊顯示功能。
- res.redirect 是重定向功能,通過它會向使用者返回一個 303 See Other 狀態,通知瀏覽器轉向相應頁面。
- crypto 是 Node.js 的一個核心模組,功能是加密並生成各種雜湊,使用它之前首先要宣告 var crypto = require('crypto'),我們程式碼中使用它計算了密碼的雜湊值。
- User 是我們設計的使用者物件,在後面我們會詳細介紹,這裡先假設它的介面都是可用的,使用前需要通過 var User = require('../models/user.js') 引用。
- User.get 的功能是通過使用者名稱獲取已知使用者,在這裡我們判斷使用者名稱是否已經存在,User.save 可以將使用者物件的修改寫入資料庫。
- 通過 req.session.user = newUser 向會話物件寫入了當前使用者的資訊,在後面我們會通過它判斷使用者是否已經登入。
使用者模型
在前面的程式碼中,我們直接使用了 User 物件,User 是一個描述資料的物件,即 MVC 架構中的模型,前面我們使用了許多檢視和控制器,這是第一次接觸到模型。與檢視和控制器不同,模型是真正與資料打交道的工具,沒有模型,網站就只是一個外殼,不能發揮真實的作用,因此它是框架中最根本的部分。現在就讓我們來實現 User 模型吧。
在 models 目錄中建立 user.js 的檔案,內容如下:
models/user.js
var mongodb = require('./db');
function User(user) {
this.name = user.name;
this.password = user.password;
};
module.exports = User;
User.prototype.save = function save(callback) {
// 存入 Mongodb 的文件
var user = {
name: this.name,
password: this.password,
};
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 屬性新增索引
collection.ensureIndex('name', {
unique: true
});
// 寫入 user 文件
collection.insert(user, {
safe: true
}, function(err, user) {
mongodb.close();
callback(err, 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);
}
// 查詢 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.prototype.save 和 User.get,前者是物件例項的方法,用於將使用者物件的資料儲存到資料庫中,後者是物件建構函式的方法,用於從資料庫中查詢指定的使用者。
檢視互動
現在幾乎已經萬事俱備,只差檢視的支援了。為了實現不同登入狀態下頁面呈現不同內容的功能,我們需要建立動態檢視助手,通過它我們才能在檢視中訪問會話中的使用者資料,同時為了顯示錯誤和成功的資訊,也要在動態檢視助手中增加響應的函式。
在書中,在 app.js 中新增的檢視互動程式碼是:
app.dynamicHelpers({
user: function(req, res) {
return req.session.user;
},
error: function(req, res) {
var err = req.flash('error');
if (err.length)
return err;
else
return null;
},
success: function(req, res) {
var succ = req.flash('success');
if (succ.length)
return succ;
else
return null;
},
});
複製程式碼
但是在 Express 4.x 中會報錯“app.dynamicHelpers is not a function ”,此處應該新增:
app.js
app.use(flash());
app.use(function(req, res, next) {
res.locals.user = req.session.user;
res.locals.post = req.session.post;
var error = req.flash('error');
res.locals.error = error.length ? error : null;
var success = req.flash('success');
res.locals.success = success.length ? success : null;
next();
});
複製程式碼
注意,這段程式碼不要放的太靠後,應該放到路由控制程式碼之前。
接下來修改公共導航部分。
header.ejs
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">NodeJS Blog</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form navbar-right">
<% if (!user) { %>
<a href="/login" type="submit" class="btn btn-success">登入</a>
<a href="/reg" type="submit" class="btn btn-success">註冊</a>
<% } else { %>
<a href="" type="submit" class="btn">登出</a>
<% } %>
</form>
</div>
</div>
</nav>
複製程式碼
然後開啟註冊頁,輸入使用者名稱、密碼,點選註冊按鈕,發現又遇到坑了...“db.collection is not a function”。
引起這個錯誤的原因是你通過 npm 安裝的 mongodb 的版本和你 Node.js 運算元據的 api 版本不一致,檢視了一下 package.json,發現 mongodb 的版本是 3.1.13。
解決方法,下載低版本 mongodb,例:
"mongodb": "^2.2.33",
複製程式碼
npm install 安裝。
未完待續......