搭建開發(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)境介紹。
- 環(huán)境登錄
無需密碼自動登錄,系統(tǒng)用戶名shiyanlou - 環(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)更充實。
再總結一下整個系列的實驗,提出了許多課程的知識點,示例代碼只是展示了核心部分,希望大家能嘗試在這種點到即止的方式中去查找解決辦法。
參考文檔: