12_Node.js Web 開發_部落格網站

Web前端開發小K發表於2019-02-18

下面開始用 Node.js 進行 Web 開發。

我是通過《Node.js開發指南》這本書來學習 Node.js Web 開發的,書中使用的 Express 框架是 2.5.8,而我的是 4.14.1,所以遇到了許多問題,在文章中我都有提到並講解。

GitHub 地址

一、快速開始

1、建立專案

《Node.js開發指南》中建立專案的方式是:express -t ejs microblog,但是這種方式對於高版本的 Express 新建的標籤替換引擎並不是 .ejs,而是 .jade,如果要使用 .ejs 我們可以通過下面命令建立網站基本結構。

express -e NodeJSBlog
複製程式碼

執行命令後在當前目錄下出現了一些檔案,並且下邊提示我們通過 npm install 安裝依賴。

12_Node.js Web 開發_部落格網站

12_Node.js Web 開發_部落格網站

12_Node.js Web 開發_部落格網站

在 npm install 之後,開啟 NodeJSBlog 目錄下的 package.json,可以看到已安裝的包及對應的版本號。

12_Node.js Web 開發_部落格網站

2、啟動伺服器

注意,我們之前開啟 Node.js 伺服器,都是執行 node xxx.js,然後去瀏覽器訪問即可,但是 Express 4.x 以上就不是這種方式了,應該是 npm start,埠配置在 bin/www 中。

12_Node.js Web 開發_部落格網站

12_Node.js Web 開發_部落格網站

啟動成功訪問 localhost:3000/。

12_Node.js Web 開發_部落格網站

3、專案結構

我們看一下 express 在 NodeJSBlog 這個目錄下都生成了哪些檔案。

12_Node.js Web 開發_部落格網站

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 檔案正常引用。

12_Node.js Web 開發_部落格網站

每一次修改我們都需要重啟服務才能看到修改後的結果,您可以看我的《Node.js 應用程式自動重啟》這篇文章去安裝 supervisor 或 nodemon 兩個外掛來實現應用程式自動重啟。

二、路由控制

1、建立頁面路由

簡單說一下新增一個頁面的流程。

首先在 views 目錄下新建一個模板,例如 hello.ejs:

12_Node.js Web 開發_部落格網站

然後開啟 index.js 檔案,新增頁面路由資訊:

12_Node.js Web 開發_部落格網站

訪問 localhost:3000/hello。

12_Node.js Web 開發_部落格網站

補充:在《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。

12_Node.js Web 開發_部落格網站

注意:呼叫模板解析引擎,用 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 時,發現頁面被第一條路由規則捕獲。

12_Node.js Web 開發_部落格網站

原因是 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。

12_Node.js Web 開發_部落格網站

這是一個非常有用的工具,可以讓我們輕易地實現中介軟體,而且還能提高程式碼的複用程度,例如我們針對一個使用者查詢資訊和修改資訊的操作,分別對應了 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,可以看到以下內容:

12_Node.js Web 開發_部落格網站

四、開始建立部落格網站

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 下對應的目錄中。

12_Node.js Web 開發_部落格網站

在 public_file.ejs 中引用,可以使用 include 方法給模板新增公共檔案。

12_Node.js Web 開發_部落格網站

Bootstrap官網 檢視並使用所需的模板或元件。

下圖是我從 Bootstrap 官網找的一個模板,並放到了 index.ejs 目錄下並進行了簡單地修改,並將頁面公共部分(頭尾部分)取出,用 include 方式來複用。

12_Node.js Web 開發_部落格網站

12_Node.js Web 開發_部落格網站

五、使用者註冊和登入

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':

12_Node.js Web 開發_部落格網站

從網上查詢到該問題之後找到了解決辦法,開啟 package.json 檔案,將 connect-mongo 的版本改為 0.8.2,然後執行 npm install 即可解決。

12_Node.js Web 開發_部落格網站

12_Node.js Web 開發_部落格網站

如果程式沒有報錯,那麼就成功了,你可以使用 mongo.exe 或視覺化工具來檢視資料庫是否新建成功。

12_Node.js Web 開發_部落格網站

12_Node.js Web 開發_部落格網站

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。

12_Node.js Web 開發_部落格網站

註冊響應:

在書中使用了 flash,但是最新版本的 Express 已經不支援 flash 了,你需要先通過 npm 安裝 connect-flash。

12_Node.js Web 開發_部落格網站

然後在 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();
});
複製程式碼

注意,這段程式碼不要放的太靠後,應該放到路由控制程式碼之前。

12_Node.js Web 開發_部落格網站

接下來修改公共導航部分。

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”。

12_Node.js Web 開發_部落格網站

引起這個錯誤的原因是你通過 npm 安裝的 mongodb 的版本和你 Node.js 運算元據的 api 版本不一致,檢視了一下 package.json,發現 mongodb 的版本是 3.1.13。

解決方法,下載低版本 mongodb,例:

"mongodb": "^2.2.33",
複製程式碼

npm install 安裝。

未完待續......


12_Node.js Web 開發_部落格網站

期待您的關注!

相關文章