node.js 搭建blog

搭建開發(fā)環(huán)境并模擬交互數(shù)據(jù)

一、實驗說明

下述介紹為實驗樓默認環(huán)境,如果您使用的是定制環(huán)境,請修改成您自己的環(huán)境介紹。

三、功能模塊分析與設計
四、搭建開發(fā)環(huán)境

LouBlog 使用 nodeJS 搭建后臺,使用最受歡迎的 web 框架 Express 快速搭建。
有過 nodeJS 基礎的同學應該對此有一定的了解,簡單說一下,nodeJS 是基于 Chrome JavaScript 運行時建立的平臺,用于方便地搭建響應速度快、易于擴展的網(wǎng)絡應用。nodeJS 使用事件驅動,非阻塞 I/O 模型而得以輕量和高效。
再來介紹一下深受 nodeJS 開發(fā)者歡迎的 web 開發(fā)框架 Express 。Express 是一個基于 nodeJS 平臺的極簡、靈活的 web 應用開發(fā)框架,這好比如是 Flask 和 Python 的搭配一樣。Express 擁有豐富的 HTTP 快捷方法和任意排列組合的 Connect 中間件,方便快速、簡單地創(chuàng)建健壯、友好的 API

4.1 安裝node.js
1.安裝node.js參考菜鳥教程

http://www.runoob.com/nodejs/nodejs-install-setup.html

2.設置node.js環(huán)境變量,兩個

2.1

2.2 path添加node的安裝位置

4.2 使用Express生成項目框架

4.2.1 安裝 Express
在 Express v3.x 之前,還內置許多中間件,但在 v4.x 后,除了 static 都被分離為單獨的模塊,這也是許多初學者,面對的最大的問題,因為現(xiàn)在許多網(wǎng)上的文章還停留在 v3.x。
詳情可以訪問 Express中文網(wǎng)。
先通過 npm 全局安裝 Express:
sudo npm install -g express-generator
4.2.2 使用 Express
在安裝好 Express 開發(fā)框架及其命令行工具后,就可以快速生成我們需要的項目框架了。執(zhí)行下面這條命令:
express -e LouBlog
成功后便會生成所需的框架。
文件結構如下

|-- LouBlog
    |-- public
        |-- javascripts
        |-- images
        `-- stylesheets
            `-- style.css
    |-- routes
        |-- index.js
        `-- users.js
    |-- views
        |-- index.ejs
        `-- error.ejs
    |-- bin
        `-- www
    |-- app.js
    `-- package.json

簡單了解一下 Express 都為我們準備好了什么:
LouBlog: 自然就是我們項目名,如果沒有此文件夾,在創(chuàng)建項目框架時加上名稱,就像剛剛執(zhí)行完的那條命令,Express 就會自動幫我們創(chuàng)建此文件夾;若你在已是項目名的文件夾中生成框架,此項可省去。
public: 是我們項目的靜態(tài)資源,存放 imgs、js、css 等文件;
routes: 可以說是整個項目的控制部分,存放路由文件;
views: 存放項目的視圖文件;
bin: 存放可執(zhí)行文件;
app.js: 項目的啟動文件;
package.json: 文件中有項目的基本信息,包括項目名、版本號、開放權限、啟動命令等;以及項目的模塊依賴信息,當運行 npm install 時, npm 就會此文件,并根據(jù) dependencies 對象中的屬性安裝模塊。
簡單了解了 Express 為我們生成的框架,有沒有感受得到 Express 的強大呢,幫我做好了很多建站的基本工作,大大提高了效率。
注意:希望大家能多多學習生成的文件內容,尤其是 app.js 、routes/index.js 、bin/www 這幾個文件,
接下來我們做一點點修改,方便我們啟動項目服務,進入項目根目錄,并安裝項目的依賴模塊:

cd LouBlog
npm install

設置路由
修改 app.js 文件, 在文件最后的
module.exports = app; 之前添加一行代碼 app.listen(3000);
最后一部執(zhí)行啟動命令:
node app.js
啟動項目,通過瀏覽器訪問 localhost:3000,即可看到結果

4.3 熟悉Express框架
4.3.1 工作原理
接下來學習 Express 框架中非常重要的路由控制。
routers/index.js 中有以下代碼:

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

代碼意思是當訪問主頁時,調用 ejs 模板(這里提到的 ejs 模板將會在下一節(jié)中詳細講解)來渲染 views/index.ejs 模板文件。 其中 get 指 http 的 get 請求方式,Express 封裝了許多 http 請求方式,我們主要使用 get() 和 post() 兩種;
?參數(shù)一:'/' 則代表了其路由規(guī)則,這里指向項目根目錄,同時路由規(guī)則還支持正則表達式,這給我們設計路由帶來很多的方便;
?參數(shù)二:為處理請求的回調函數(shù),函數(shù)中又有兩個參數(shù) req 和 res,代表請求信息和響應信息。
路徑請求及對應的獲取路徑有以下幾種形式:
?req.query: 處理get請求,獲取get請求參數(shù)。
?req.body: 處理post請求,獲取post請求體。
?req.params: 處理/:XXX形式的get或post請求,獲取請求參數(shù)
?req.param(): 處理get和post請求,但查找優(yōu)先級由高到低為req.params -> req.body -> req.query。
res.render() 則將所有數(shù)據(jù)以 json 格式傳遞給模板引擎。
4.3.2 路由規(guī)則實踐
現(xiàn)在我們直接訪問 localhost:3000/login 會顯示:

這是因為我們還沒有建立 /login 這一路由規(guī)則
在 routers/index.js 文件中添加代碼

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

Ctrl + c 停止服務,node app.js 再次啟動服務,訪問 localhost:3000/login 后顯示:

出現(xiàn)這個錯誤是因為 view 中并沒用 login 對應的文件,添加 login.ejs 文件,文件中寫入 <%= title%>,直接刷新瀏覽器,這時便可以看到正確的顯示:

還記得之前簡單講解過 Express 生成的模板框架嗎,routes 中存放路由文件,views 中存放視圖文件,這就相當于 MVC 模式中的 C 和 V,而 index.ejs 文件中的 <%= title%> 是 ejs 模板引擎的語句,意思是將后臺傳遞來的 title 數(shù)據(jù)在頁面中顯示出來。
現(xiàn)在你應該大致了解了 Express 的路由工作原理,但在剛在的操作中,我們發(fā)現(xiàn)每次修改后臺代碼時,想要瀏覽修改結果,就需要先重啟服務。這無疑增加了開發(fā)的負擔。
使用 supervisor 模塊可以很好的解決這個問題,每當我們保存文件后,此模塊便會自動重啟服務,提高了開發(fā)效率。
首先要安裝此模塊:

sudo npm install -g supervisor

配置啟動命令:

supervisor app.js

之后,我們的項目啟動命名便從 node app.js 更改為

supervisor app.js

這樣我們的開發(fā)環(huán)境已經(jīng)初步配置完成,接下來我們根據(jù) Express 的路由控制原則來設計我們的博客項目
五、搭建路由模塊
依據(jù)我們博客指定好的功能,我們初步設計以下幾個路由規(guī)則:

/login
/logout
/reg
/post
/search
/edit/:_id
/remove/:_id

以上幾個路由規(guī)則分別對應“登錄”、“退出登錄”、“注冊”、“發(fā)表文章”、“查詢”、“編輯”、“刪除”功能。
再分別建立好對應的視圖文件:

views/login.ejs
views/register.ejs
views/index.ejs
views/post.ejs
views/search.ejs
views/edit.ejs

“刪除”功能只是在請求完成后返回一個狀態(tài)信息,因此不必要創(chuàng)建視圖文件。
完成上面兩步,就需要在瀏覽器中依次測試剛剛設計好的路由規(guī)則是否有效果,有沒有報出錯誤,那這節(jié)實驗的任務就完成了。

六、前端模板引擎

6.1 什么是模板引擎
模板引擎是一個將頁面模板和數(shù)據(jù)數(shù)據(jù)結合起來生成 HTML 頁面的工具。通過模板引擎,我們可以在 HTML 文件中直接使用后臺傳遞過來的數(shù)據(jù),而不必再使用通過解析 json 數(shù)據(jù),在拼接成字符串的形式渲染數(shù)據(jù),大大提高了開發(fā)效率。
6.2 什么是ejs

express -e   LouBlog

看過 Express API 的同學可能比較清楚,這里的 -e 正是指定 ejs 作為我們的模板引擎,而默認的模板引擎是 jade 。
那為何要選擇 ejs ,不選 jade?很大的一個原因是因為 ejs 的語法更符合前端開發(fā)者的習慣,項目的目的是為了能讓大家學會這一套開發(fā)流程,而不僅僅是一個模板引擎;反觀 jade ,很多初學者都很難馬上適應這中語法,更符合后臺開發(fā)者的習慣,特別是對縮進的嚴格要求,使得大家感覺與 Python 很像。
但這并不是說 jade 比 ejs 有多差,相反,有人做過測試,jade 的性能反而好過 ejs。這里只是為了大家快速上手開發(fā),所以選擇了ejs。大家有時間也可以嘗試使用 jade 開發(fā)本課程,親身體驗一下二者的差別吧。
6.3 使用ejs
在 Express 自動生成項目框架時,有這么兩行代碼

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

這里設置了模板文件的存儲位置和使用的模板引擎。
在 routers/index.js 中通過 res.render() 渲染模板。它接收兩個參數(shù),第一個是模板名稱,即 views 目錄下的模板文件名;第二個參數(shù)是傳遞給模板的數(shù)據(jù)對象。
舉個例子:當代碼為 res.render('index', {title: 'Express'}); 時,模板引擎會把 <%= title %> 替換為 Express ,然后把替換后的頁面展示給用戶。
相對于 jade 來說,ejs 非常簡單,只有三種標簽:

?<% code %>: javascript代碼,這代表我們能使用 if-else 之類的邏輯判斷語句
?<%= code%>: 顯示替換過HTML特殊字符的內容
?<%- code %>: 顯示原始HTML內容

6.4 頁面布局
使用模板的一個原因,除了能方便處理數(shù)據(jù),還有一點就是模板可以重用,我們可以將共用的部分分離為一個文件,并通過 include 引入。
6.4.1 分離共用模塊
ejs 模板引擎的 include 語法簡單粗暴,它不像許多模板能夠引入成對的標簽,而是硬生生將正常的 HTML 頁面截斷,直接將成對的標簽分在了不同的文件當中。
我們先來搭建一個首頁 index.ejs,課程最終完成的樣式如下:

希望大家能自由發(fā)揮你的創(chuàng)造力,建造有自己特色的樣式的博客,下面給出 index.ejs 的最簡代碼,通過不斷優(yōu)化,來學習 ejs 模板及其他知識點。

<!DOCTYPE html><html>
    <head>
        <meta charset="UTF-8">
        <title><%= title %></title>
        <link rel="stylesheet" href="/stylesheets/style.css">
        <body>
        <nav>
            <ul>
                <li><a href="#">register</a></li>
                <li><a href="#">login</a></li>
                <li><a href="#">post</a></li>
                <li><a href="#">logout</a></li>
            </ul>
        </nav>
        <div id="container">
            <%= title %>
        </div>
    </body></html>

從這個頁面的構造,以及我們之前的功能設計可以看出,
部分的導航條是共用部分,也就是在其頁面都有出現(xiàn),這就可以用到 include 將其提取為一個文件。
使用 include 后的文件及內容:
views/header.ejs 文件:

<!DOCTYPE html><html>
    <head>
        <meta charset="UTF-8">
        <title><%= title %></title>
        <link rel="stylesheet" href="/stylesheets/style.css">
        <body>
        <nav>
            <ul>
                <li><a href="#">register</a></li>
                <li><a href="#">login</a></li>
                <li><a href="#">post</a></li>
                <li><a href="#">logout</a></li>
            </ul>
        </nav>
        <div id="container">

views/footer.ejs 文件:

        </div>
    </body></html>
修改過后的 views/index.ejs 文件為:
<%- include header %>
<%= title %>
<%- include footer %>

以上便是 include 的簡單使用方法,ejs 模板引擎的基本功能也梳理完畢,在接下來的學習中,進一步體會 ejs 的魅力吧。

七、設計頁面

接下來,我們主要完成前端的展示部分,使用 bootstrap 前端框架快速搭建樣式優(yōu)美的響應式頁面。
7.1 使用 bootstrap 前端框架
7.1.1 引入 bootstrap
注意:bootstrap 所有的 JavaScript 插件都依賴 jquery, 并且通過查看 bootstrap 的官方文檔就可以知道,bootstrap 所依賴的 jquery 版本最低為 v1.9.1,因此,還需要大家自己嘗試引入 jquery 文件。
再補充一下 bootstrap 的幾種安裝方式:
1.手動下載并導入項目:從官網(wǎng)下載所需的 bootstrap 開發(fā)包,選擇“用于生產(chǎn)環(huán)境的 bootstrap”,需要的文件為 fonts/*、js/bootstrap.min.js、css/bootstrap.min.css;
2.通過 npm 安裝:執(zhí)行 npm install bootstrap --save。此時 bootstrap 就相當于一個模塊,通過 require('bootstrap') 導入后使用;
3.通過 bower 安裝:執(zhí)行 bower install bootstrap。bower 是客戶端技術的軟件包管理器,方便管理客戶端依賴關系,在安裝 bootstrap 之前應該先執(zhí)行 sudo npm install -g bower 全局安裝 bower;
4.使用CDN加速服務:bootstrap 中文網(wǎng)提供了免費的 CDN 加速服務,復制以下代碼,即可使用:

<link rel="stylesheet" >
<script src="http://cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
<script src="http://cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

建議你使用 bower 安裝 bootstrap。
7.1.2 使用 bootstrap
在 viwes/header.ejs 中添加上面的代碼,并將
部分替換為 bootstrap 的導航條組件:

<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">LouBlog</a>
        </div>

        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav navbar-left">
                <li><a href="/post">post</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
                            username
                        <span class="caret"></span>
                    </a>
                    <ul class="dropdown-menu">
                        <li><a href="#">about</a></li>
                        <li><a href="/logout">logout</a></li>
                    </ul>
                </li>
                <ul class="nav navbar-nav">
                    <li><a href="/login">login</a></li>
                    <li><a href="/reg">register</a></li>
                </ul>
            </ul>
            <form class="navbar-form navbar-right" role="search" action='/search' method="get">
                <div class="form-group">
                    <input type="text" class="form-control" placeholder="Search" name="title">
                </div>
                <button type="submit" class="btn btn-default">search</button>
            </form>
        </div>
    </div></nav>

這段代碼中就是使用 bootstrap 的一個簡單例子,也只用到了導航條組件,除此之外,我們開發(fā)博客系統(tǒng)還可能用到表單樣式,柵格系統(tǒng)等,同學們可選擇合適的組件,當然也可以使用其他前端框架,比如 AmazeUI 等,開發(fā)自己的博客系統(tǒng)。
另外需要提出的一點是,在上面的導航條中,同時出現(xiàn)了“登錄”和“用戶信息”等邏輯上不該同時出現(xiàn)的按鈕。這就需要通過 session 機制來判斷用戶的登錄狀態(tài),并返回應該顯示在頁面中的按鈕選項。這將在下一節(jié),添加 mongoDB 后做詳細的講解。
7.2 自己搭建頁面樣式
這一步主要是使用 bootstrap 框架,搭建每一個功能模塊的頁面樣式,自己可以更具自己的喜好,自由發(fā)揮,在此列出每一個頁面必要的元素,以方便后期講解的統(tǒng)一。
views/login.ejs(登錄頁面):
構建一個 form 表單,需填寫的內容包括 “用戶名” 和 “密碼”。
views/register.ejs(注冊頁面):
一個 form 表單,需填寫的內容包括 “用戶名”、“密碼”、“確認密碼”和“郵箱”。
views/index.ejs(主頁):
主頁展示文章信息,有 “標題”、“作者”、“創(chuàng)建日期”、“標簽”和“文章內容”。
views/post.ejs(發(fā)表頁面):
form 表單,需要填寫的內容包括 “標題”,“標簽”和“文章內容”,至于 “作者”和“創(chuàng)建時間” 將通過其他方式記錄。
views/search.ejs(查詢結果頁面):
展示內容和主頁一致。
views/edit.ejs(編輯頁面):
form 表單,和發(fā)表頁面一致,需要自動填寫原有的數(shù)據(jù),方便編輯。
7.3 模擬數(shù)據(jù)
接下來,我們通過修改 routes/index.js 向模板傳遞模擬數(shù)據(jù),方便我們理解數(shù)據(jù)傳遞過程、編寫首頁樣式。
跳轉首頁的路由規(guī)則為:

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

上一節(jié)提到,res.render() 會將數(shù)據(jù)傳遞給模板,其中參數(shù)一便是對應的模板,這里便是 index.ejs,而數(shù)據(jù)便是{...} 這一對象。所以,我們編寫的模擬數(shù)據(jù),就寫在這個對象里,以 json 格式為標準。

router.get('/', function(req, res, next) {
    res.render('index', {
        title: '主頁',
        arts: [{
            title: 'nodeJS入門',
            tags: 'nodeJS',
            author: '...',
            createTime: '',
            content: '...'
        },{
            title: 'nodeJS入門',
            tags: 'nodeJS',
            author: '...',
            createTime: '',
            content: '...'
        },{
            title: 'nodeJS入門',
            tags: 'nodeJS',
            author: '...',
            createTime: '',
            content: '...'
        }]
    });
});

這里簡單添加了三條數(shù)據(jù),模板 views/index.ejs 中添加:

<% arts.forEach(function(art) { %>
    <%= art.title %>
    <%= art.tags %>
    <%= art.author %>
    <%= art.createTime %>
<%= art.content %>
<% }) %>

這樣,模板便成功的從后臺得到了數(shù)據(jù),并展示到頁面中。頁面樣式很粗糙,這需要同學們自己完成。
八、本節(jié)總結
本節(jié)實驗簡單介紹了開發(fā)環(huán)境的搭建以及 Express 框架的使用,其中需要大家多花時間理解 Express 框架,從模板生成,到路由控制,希望大家能多看官網(wǎng)中的 API,熟悉 v3.x 和 v4.x 之間的區(qū)別,介紹了 ejs 模板的用法,熟悉 <% code %>、<%= code%>、<%- code%> 以及使用 <%- include views %> 進行模板重用是一重要知識點。
《mongoDB基礎教程》

使用 mongoDB 數(shù)據(jù)庫并整理功能模塊
一、實驗說明
下述介紹為實驗樓默認環(huán)境,如果您使用的是定制環(huán)境,請修改成您自己的環(huán)境介紹。

  1. 環(huán)境登錄
    無需密碼自動登錄,系統(tǒng)用戶名shiyanlou
  2. 環(huán)境介紹
    本實驗環(huán)境采用帶桌面的Ubuntu Linux環(huán)境,實驗中會用到桌面上的程序:
    1.LX終端(LXTerminal): Linux命令行終端,打開后會進入Bash環(huán)境,可以使用Linux命令
    2.Firefox:瀏覽器,可以用在需要前端界面的課程里,只需要打開環(huán)境里寫的HTML/JS頁面即可
    3.GVim:非常好用的編輯器,最簡單的用法可以參考課程Vim編輯器
    二、課程介紹
    這一節(jié),我們將學習在 Express 框架如何操作 mongoDB 中的數(shù)據(jù),以及如何配合 session 完成一些基本的邏輯狀態(tài)判斷,完成本節(jié),我們的 LouBlog 博客系統(tǒng)也將初具模型。
    Mongodb安裝
    https://www.mongodb.com/download-center#community
    1.啟動數(shù)據(jù)庫
    mongod.exe --dbpath c:\data\db
    2.連接數(shù)據(jù)庫
    mongod
    3.查詢數(shù)據(jù)庫和使用數(shù)據(jù)庫和查找數(shù)據(jù)表
 show dbs
Use datas
Db.users.find()

三、使用 mongoDB 數(shù)據(jù)庫
mongoDB 是一個基于分布式文件存儲的非關系型數(shù)據(jù)庫(NoSQL)的一種。它支持的數(shù)據(jù)結構非常松散,類似 json 的 bjson 格式。
mongoDB 沒有關系型數(shù)據(jù)庫中行和表的概念,但有類似文檔 (document)和集合(collection)的概念。文檔是 mongoDB 最基本的單位,集合是許多文檔的總和,一個數(shù)據(jù)庫可以有多個集合,一個集合可以有多個文檔。
因為實驗環(huán)境中默認安裝有 mongoDB 因此我們跳過安裝這一步,但先要通過以下指令開啟 mongoDB 服務;
開啟服務:

sudo service mongodb start

在根目錄下創(chuàng)建數(shù)據(jù)庫存儲目錄,并啟動服務:

sudo mkdir /data/db
mongod

最后輸入 mongo 即可訪問數(shù)據(jù)庫。
問題記錄:
運行mongodb出現(xiàn)計算機丟失api-ms-win-crt-runtime-|1-1-0.dll
解決:
http://blog.csdn.net/mblhq/article/details/53896920
3.1 連接 mongoDB
mongoDB 的服務啟動后,開始編寫程序建立連接,這里我們使用 mongoDB 的模型工具 -- mongoose,可以方便我們減化代碼,并且這還是為 nodeJS 設計的。
3.1.1 使用 mongoose 連接 mongoDB
運行以下命令安裝 mongoose 包:

npm install mongoose --save

接下需要修改 app.js,添加下面的代碼:

//先引入 mongoose 模塊
var mongoose = require('mongoose');
//連接數(shù)據(jù)庫
mongoose.connect('mongodb://localhost:27017/datas');
mongoose.connection.on('error', console.error.bind(console, '連接數(shù)據(jù)庫失敗'));

刷新頁面時,若沒有報出 “連接數(shù)據(jù)庫失敗” 則成功連接數(shù)據(jù)庫。接下來,我們便要建立數(shù)據(jù)庫模型,向數(shù)據(jù)庫中存儲數(shù)據(jù)。
3.2 設置 schema
schema 是 mongoose 中的模型對象,就類似關系型數(shù)據(jù)庫中的表結構,為 key/value 的鍵值對形式。
我們的博客系統(tǒng)中主要存儲用戶和文章兩類數(shù)據(jù),也就是需要建立兩個模型對象,暫且叫做 userSchema、articleSchema。
在根目錄下新建一個文件夾 models,再新建一個文件 model.js。
3.2.1 設置 userSchema
在 models/model.js 中引入 mongoose 模塊,并定義 schema 模型對象:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

根據(jù)上一節(jié)實驗中,我們統(tǒng)一定義的用戶屬性有 “用戶名”、“密碼”和“郵箱”,可以確定模型中的屬性:username、password、email,此外考慮到以后操作數(shù)據(jù)的方便,在添加一個屬性createTime。
就可以創(chuàng)建具體的模型對象:

var userSchema = new Schema({
    username: String,
    password: String,
    email: String,
    createTime: {
        type: Date,
        default: Date.now
    }
});

最后通過 mongoose 的 model() 方法,將 schema 發(fā)布為 model,model 具有抽象屬性和行為的數(shù)據(jù)庫操作對,這就使模型對象具有了數(shù)據(jù)庫的 CRUD 操作方法。
exports.User = mongoose.model('User', userSchema);
3.2.2 設置 articleSchema
同樣是在 models/model.js 下,我們創(chuàng)建文章模型對象 -- articleSchema:

var articleSchema = new Schema({
    title: String,
    author: String,
    tag: String,
    content: String,
    createTime: {
        type: Date,
        default: Date.now
    }
})

//發(fā)布為 model 
exports.Article = mongoose.model('Article', articleSchema);

注意:此處創(chuàng)建好的 model 對應數(shù)據(jù)庫中的集合,可以通過 show colections 查看數(shù)據(jù)庫中的所有集合。
創(chuàng)建好模型,就可以操作數(shù)據(jù)了。
3.3 操作數(shù)據(jù)
3.3.1 注冊用戶、發(fā)表文章
首先我們學習添加數(shù)據(jù)的操作 -- 注冊用戶和發(fā)表文章,在 routes/index.js 中,我們添加以下代碼:

var mongoose = require('mongoose');//引入加密模塊
var crypto = require('crypto');
//引入模型對象
var model = require('../models/model');
var User = model.User;

router.post('/reg', function(req, res, next) {
    //req.body 處理 post 請求
    var username = req.body.username,
        password = req.body.password,
        passwordRepeat = req.body.passwordRepeat;

    //檢查兩次輸入的密碼是否一致
    if(password != passwordRepeat) {
        console.log('兩次輸入的密碼不一致!');
        return res.redirect('/reg');
    }

    //檢查用戶名是否已經(jīng)存在
    //mongoose findOne() 方法
    User.findOne({username:username}, function(err, user) {
        if(err) {
            console.log(err);
            return res.redirect('/reg');
        }

        if(user) {
            console.log('用戶名已經(jīng)存在');
            return res.redirect('/reg');
        }

        //對密碼進行md5加密
        var md5 = crypto.createHash('md5'),
            md5password = md5.update(password).digest('hex'); //16進制

        var newUser = new User({
            username: username,
            password: md5password,
            email: req.body.email
        });

        //mongoose save()方法
        newUser.save(function(err, doc) {
            if(err) {
                console.log(err);
                return res.redirect('/reg');
            }
            console.log('注冊成功!');
            newUser.password = null;
            delete newUser.password;
            req.session.user = newUser;
            return res.redirect('/');
        });
    });
});

這樣便實現(xiàn)了注冊功能,需要注意 req.body 處理 post 請求的參數(shù),建立 User 模型對象實體操作數(shù)據(jù)庫,其實有 JavaScript 基礎的同學應該很熟悉這樣的寫法。
再來是文章發(fā)表功能,這時就要用到 articleSchema 模型對象:

var Article = model.Article;

router.post('/post', function(req, res, next) {
    var data = new Article({
        title: req.body.title,
        //這里的 author 元素通過 session 獲得,后面會詳細講解
        author: req.session.user.username,
        tag: req.body.tag,
        content: req.body.content
    });

    data.save(function(err, doc) {
        if(err) {
            req.flash('error', err);
            return res.redirect('/post');
        }
        console.log('文章發(fā)表成功!');
        return res.redirect('/');
    });
});

有了用戶注冊的基礎,發(fā)表文章就簡單許多了,但現(xiàn)在還沒講到 session 的運用,author 元素的值可以暫時通過 post 表單獲得,稍后講到 session 時,我們再改為上面的寫法即可。
3.3.2 刪除文章
接下來是文章的刪除操作,依然是 routes/index.js,修改我們第一節(jié)實驗寫好的路由規(guī)則 /remove/:_id:
//mongoose 的 remove() 方法,通過傳遞檢索參數(shù),直接刪除檢索結果

router.get('/remove/:_id', function(req, res, next) {

    //req.params 處理 /:xxx 形式的 get 或 post 請求,獲取請求參數(shù)
    Article.remove({_id: req.params._id}, function(err) {
        if(err) {
            console.log(err);
        } else {
            console.log('文章刪除成功!');
        }
        return res.redirect('back');
    })
});

3.3.3 編輯文章
編輯文章,不僅需要獲取文章信息,初始化表單內容,同時還需要有和發(fā)表文章一樣的功能:

router.get('/edit/:_id', function(req, res, next) {
    Article.findOne({_id: req.params._id}, function(err, art) {
        if(err) {
            console.log(err);
            return res.redirect('back');
        }
        res.render('edit', {
            title: '編輯',
            // code ....
            art: art
        });
    });
});

router.post('/edit/:_id', function(req, res, next) {
    //mongoose 的 update() 方法用過檢索參數(shù)并返回修改結果
    Article.update({_id: req.params._id},{
        title: req.body.title,
        tag: req.body.tag,
        content: req.body.content,
        createTime: Date.now()
    }, function(err, art) {
        if(err) {
            console.log(err);
            return res.redirect('back');
        }
        console.log('文章編輯成功!');
        return res.redirect('/u/' + req.session.user.username);
    });
});

3.3.4 查詢文章
這里我們可以通過正則表達式,實現(xiàn)模糊查詢,因為 Express 路由規(guī)則支持正則匹配查詢。

router.get('/search', function(req, res, next) {
    //req.query 獲取 get 請求的參數(shù),并構造為正則對象
    var query = req.query.title,
        title = new RegExp(query, 'i');
    Article
    .find({title: title})
    .sort('-createTime')
    .exec(function(err, arts) {
        if(err) {
            console.log(err);
            return res.redirect('/');
        }
        res.render('search', { 
            title: '查詢結果',
            arts: arts
        });
    });
});

完成以上四步,我們的博客系統(tǒng)就具有了基本的功能,但是還有幾個小問題:
?session 保存登錄狀態(tài);
?控制訪問權限;
解決這兩個問題,就需要靠接下來我們將要學習的 session 。
四、創(chuàng)建 session
session 是一種持久網(wǎng)絡協(xié)議,在客戶端與服務器之間起到交換數(shù)據(jù)包的作用。用戶登錄后的基本信息都會保存其中,Express 也提供了會話中間件,同時我們還可以將會話信息存儲到數(shù)據(jù)庫中,便于維護。為此,我們需要引入兩個中間件 express-session 和 connect-mongo,安裝方式如下:
4.1 引入中間件,創(chuàng)建 session

npm install express-session --save
npm install connect-mongo --save

接著我們要在 app.js 中添加以下代碼:

var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
//這里設置 session 參數(shù),并確保以下代碼在 `app.use('/', routes)` 前引入
app.use(session({
  key: 'session',
  secret: 'keboard cat',
  cookie: {maxAge: 1000 * 60 * 60 * 24},//1day
  store: new MongoStore({
    db: 'datas',
    mongooseConnection: mongoose.connection
  }),
  resave: false,
  saveUninitialized: true
}));

完成對 app.js 的以上修改之后,我們便能通過 req.session 獲取當前用戶的會話對象,獲取用戶的相關信息。
4.2 使用 session
舉兩個例子:
之前提到過的發(fā)表文章,其中的 author 屬性需要通過獲取 session 中保存的用戶信息,此處我們就可以修改發(fā)表文章的方法以實現(xiàn)我們的需求:

router.post('/post', function(req, res, next) {
    var data = new Article({
        title: req.body.title,
        //這里的 author 元素通過 session 獲得
        author: req.session.user.username,
        tag: req.body.tag,
        content: req.body.content
    });

    data.save(function(err, doc) {
        if(err) {
            console.log(err);
            return res.redirect('/post');
        }
        console.log('文章發(fā)表成功!');
        return res.redirect('/');
    });
});

session 另一個很大的作用就是判斷用戶登錄狀態(tài)并控制頁面的元素顯示:
我們就修改上一節(jié)給出的導航條代碼,修改如下:

<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">實驗樓</a>
        </div>

        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <% if(user) { %>
                <ul class="nav navbar-nav navbar-left">
                    <li><a href="/post">發(fā)表</a></li>
                </ul>
            <% } %>
            <ul class="nav navbar-nav navbar-right">
                <% if(user) { %>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
                                <%= user.username %>
                            <span class="caret"></span>
                        </a>
                        <ul class="dropdown-menu">
                            <li><a href="/u/<%= user.username %>">賬戶信息</a></li>
                            <li><a href="/logout">退出登錄</a></li>
                        </ul>
                    </li>
                <% } else { %>
                    <ul class="nav navbar-nav">
                        <li><a href="/login">登錄</a></li>
                        <li><a href="/reg">注冊</a></li>
                    </ul>
                <% } %>
            </ul>
            <form class="navbar-form navbar-right" role="search" action='/search' method="get">
                <div class="form-group">
                    <input type="text" class="form-control" placeholder="Search" name="title">
                </div>
                <button type="submit" class="btn btn-default">搜索</button>
            </form>
        </div>
    </div></nav>
代碼中我們可以看到有許多判斷,其中的參數(shù) user 便是通過 session 獲取的用戶信息,為此,我們還需要在 res.render()中傳遞 session:
res.render('index', { 
    title: '主頁',
    user: req.session.user
    // code ....
});

除了以上兩個例子,還有許多用到 session 的地方,大家在學習過程中可以自己好好感悟。
五、擴展功能
5.1 添加 flash 信息提示
上一節(jié)我們完成了博客系統(tǒng)的基本功能,但是有心的同學會發(fā)現(xiàn),示例代碼里很多的 console.log() 信息提示,這是打印一些返回信息以幫助我們調試代碼,但不知有多少同學這樣思考過 -- 每次刷新調試,都要看服務程序的信息提示好不方便,要是刷新就能在頁面顯示這些提示可好,此外,用戶登錄,發(fā)表文章,成功還好,要是哪出錯了卻沒有任何提示多不方便。
恭喜你已經(jīng)能夠主動思考、學習,接著往下你應該就會自己查詢解決辦法了吧。
只要走到這一步,相信大家就知道 flash 模塊能很好地幫助我們實現(xiàn)這一功能:
flash 存儲于 session 中,但與之前我們存入的用戶登錄信息不同,flash 信息將會在下一次刷新后被清楚。
首先還是安裝模塊,并引入:

npm install connect-flash --save

// app.js 中
var flash = require('connect-flash');
app.use(flash());

這樣,我們便成功引入了 flash 模塊,接下來就可以將所有的 console.log() 使用 req.flash('type', content) 替換,其中參數(shù)一是一個字符串,代表了信息的類型,我們常用的就是 'success' 和 'error' 兩種,參數(shù)二就是信息的具體內容。
這只是將信息保存到了 session 中,想要在頁面中展示,我們就必須要從 session 中獲取并傳遞給 ejs 模板,方法如下:

// code ...
    res.render('xxx', {
        // ...
        success: req.flash('success').toString();
        error: req.flash('error').toString();
        // ...
    });// code ...

模板獲得信息后就可以通過簡單的判斷語句展示這一內容:

            <%if(success){ %>
            <%=success %>

           <% } %>
           <%if(error){ %>
            <%=error %>

           <% } %>

// error 同上,樣式可以通過 bootstrap 調整。問題:這樣許多頁面都需要信息提示,我們應該在何處做以上調整?
這就是我們添加 flash 信息提示的大致過程。
5.2 添加分頁功能
分頁功能是很常見的一個功能,當展示的信息條目很多時能分批次顯示,減小了瀏覽器壓力,避免渲染速度過慢。
要實現(xiàn)分頁的功能,主要需要考慮這幾個元素:“當前頁碼”,“每頁展示個數(shù)”,“條目總數(shù)”;此外分頁主要有兩種展示形式:“只有上/下一頁”,“展示多個頁碼”。我們就嘗試實現(xiàn)簡單的 “只有上/下一頁”。
修改 views/index.ejs:

var page = 1;
var pageSize = 5;
router.get('/', function(req, res, next) {
    page = req.query.page ? parseInt(req.query.page) : 1;
    Article
    .count(function(err, total) {
        Article
        .find()
        //skip 跳過指定的頁數(shù)
        .skip((page - 1) * pageSize)
        //限制讀取 pageSize 條數(shù)據(jù)
        .limit(pageSize)
        //以 createTime 倒序排序
        .sort('-createTime')
        //執(zhí)行回調方法
        .exec(function(err, arts) {
            if(err) {
                req.flash('error',err);
                return res.redirect('/');
            }
            res.render('index', { 
                title: '主頁',
                user: req.session.user,
                success: req.flash('success').toString(),
                error: req.flash('error').toString(),
                total: total,
                page: page,
                pageSize: pageSize,
                isFirstPage: (page - 1) == 0,
                isLastPage: ((page - 1) * pageSize + arts.length) == total,
                arts: arts
            });
        });
    });
});

將所有數(shù)據(jù)傳遞給模板后,剩余的事就簡單了許多。其中的 skip() 和 limit() 是實現(xiàn)分頁的關鍵部分,也有很多人說當數(shù)據(jù)量少的時候,skip() 的效率還可以,但當數(shù)據(jù)量很大的時候,效率就會很差。大家有興趣可以查閱資料,嘗試發(fā)現(xiàn)效率更高的方法。
Js使用express render的數(shù)據(jù)

六、本節(jié)總結
本節(jié)添加了mongoDB,并使用 mongoose 連接了數(shù)據(jù)庫,創(chuàng)建 session 判斷用戶狀態(tài),完成了一些功能的擴展,使我們的 LouBlog 博客系統(tǒng)更充實。
再總結一下整個系列的實驗,提出了許多課程的知識點,示例代碼只是展示了核心部分,希望大家能嘗試在這種點到即止的方式中去查找解決辦法。
參考文檔:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容