搭建開(kāi)發(fā)環(huán)境并模擬交互數(shù)據(jù)
一、實(shí)驗(yàn)說(shuō)明
下述介紹為實(shí)驗(yàn)樓默認(rèn)環(huán)境,如果您使用的是定制環(huán)境,請(qǐng)修改成您自己的環(huán)境介紹。
三、功能模塊分析與設(shè)計(jì)
四、搭建開(kāi)發(fā)環(huán)境
LouBlog 使用 nodeJS 搭建后臺(tái),使用最受歡迎的 web 框架 Express 快速搭建。
有過(guò) nodeJS 基礎(chǔ)的同學(xué)應(yīng)該對(duì)此有一定的了解,簡(jiǎn)單說(shuō)一下,nodeJS 是基于 Chrome JavaScript 運(yùn)行時(shí)建立的平臺(tái),用于方便地搭建響應(yīng)速度快、易于擴(kuò)展的網(wǎng)絡(luò)應(yīng)用。nodeJS 使用事件驅(qū)動(dòng),非阻塞 I/O 模型而得以輕量和高效。
再來(lái)介紹一下深受 nodeJS 開(kāi)發(fā)者歡迎的 web 開(kāi)發(fā)框架 Express 。Express 是一個(gè)基于 nodeJS 平臺(tái)的極簡(jiǎn)、靈活的 web 應(yīng)用開(kāi)發(fā)框架,這好比如是 Flask 和 Python 的搭配一樣。Express 擁有豐富的 HTTP 快捷方法和任意排列組合的 Connect 中間件,方便快速、簡(jiǎn)單地創(chuàng)建健壯、友好的 API
4.1 安裝node.js
1.安裝node.js參考菜鳥(niǎo)教程
http://www.runoob.com/nodejs/nodejs-install-setup.html
2.設(shè)置node.js環(huán)境變量,兩個(gè)
2.1
2.2 path添加node的安裝位置
4.2 使用Express生成項(xiàng)目框架
4.2.1 安裝 Express
在 Express v3.x 之前,還內(nèi)置許多中間件,但在 v4.x 后,除了 static 都被分離為單獨(dú)的模塊,這也是許多初學(xué)者,面對(duì)的最大的問(wèn)題,因?yàn)楝F(xiàn)在許多網(wǎng)上的文章還停留在 v3.x。
詳情可以訪問(wèn) Express中文網(wǎng)。
先通過(guò) npm 全局安裝 Express:
sudo npm install -g express-generator
4.2.2 使用 Express
在安裝好 Express 開(kāi)發(fā)框架及其命令行工具后,就可以快速生成我們需要的項(xiàng)目框架了。執(zhí)行下面這條命令:
express -e LouBlog
成功后便會(huì)生成所需的框架。
文件結(jié)構(gòu)如下
|-- LouBlog
|-- public
|-- javascripts
|-- images
`-- stylesheets
`-- style.css
|-- routes
|-- index.js
`-- users.js
|-- views
|-- index.ejs
`-- error.ejs
|-- bin
`-- www
|-- app.js
`-- package.json
簡(jiǎn)單了解一下 Express 都為我們準(zhǔn)備好了什么:
LouBlog: 自然就是我們項(xiàng)目名,如果沒(méi)有此文件夾,在創(chuàng)建項(xiàng)目框架時(shí)加上名稱,就像剛剛執(zhí)行完的那條命令,Express 就會(huì)自動(dòng)幫我們創(chuàng)建此文件夾;若你在已是項(xiàng)目名的文件夾中生成框架,此項(xiàng)可省去。
public: 是我們項(xiàng)目的靜態(tài)資源,存放 imgs、js、css 等文件;
routes: 可以說(shuō)是整個(gè)項(xiàng)目的控制部分,存放路由文件;
views: 存放項(xiàng)目的視圖文件;
bin: 存放可執(zhí)行文件;
app.js: 項(xiàng)目的啟動(dòng)文件;
package.json: 文件中有項(xiàng)目的基本信息,包括項(xiàng)目名、版本號(hào)、開(kāi)放權(quán)限、啟動(dòng)命令等;以及項(xiàng)目的模塊依賴信息,當(dāng)運(yùn)行 npm install 時(shí), npm 就會(huì)此文件,并根據(jù) dependencies 對(duì)象中的屬性安裝模塊。
簡(jiǎn)單了解了 Express 為我們生成的框架,有沒(méi)有感受得到 Express 的強(qiáng)大呢,幫我做好了很多建站的基本工作,大大提高了效率。
注意:希望大家能多多學(xué)習(xí)生成的文件內(nèi)容,尤其是 app.js 、routes/index.js 、bin/www 這幾個(gè)文件,
接下來(lái)我們做一點(diǎn)點(diǎn)修改,方便我們啟動(dòng)項(xiàng)目服務(wù),進(jìn)入項(xiàng)目根目錄,并安裝項(xiàng)目的依賴模塊:
cd LouBlog
npm install
設(shè)置路由
修改 app.js 文件, 在文件最后的
module.exports = app; 之前添加一行代碼 app.listen(3000);
最后一部執(zhí)行啟動(dòng)命令:
node app.js
啟動(dòng)項(xiàng)目,通過(guò)瀏覽器訪問(wèn) localhost:3000,即可看到結(jié)果
4.3 熟悉Express框架
4.3.1 工作原理
接下來(lái)學(xué)習(xí) Express 框架中非常重要的路由控制。
routers/index.js 中有以下代碼:
router.get('/', functoin(req, res) {
res.render('index', { title: 'Express' });
});
代碼意思是當(dāng)訪問(wèn)主頁(yè)時(shí),調(diào)用 ejs 模板(這里提到的 ejs 模板將會(huì)在下一節(jié)中詳細(xì)講解)來(lái)渲染 views/index.ejs 模板文件。 其中 get 指 http 的 get 請(qǐng)求方式,Express 封裝了許多 http 請(qǐng)求方式,我們主要使用 get() 和 post() 兩種;
?參數(shù)一:'/' 則代表了其路由規(guī)則,這里指向項(xiàng)目根目錄,同時(shí)路由規(guī)則還支持正則表達(dá)式,這給我們?cè)O(shè)計(jì)路由帶來(lái)很多的方便;
?參數(shù)二:為處理請(qǐng)求的回調(diào)函數(shù),函數(shù)中又有兩個(gè)參數(shù) req 和 res,代表請(qǐng)求信息和響應(yīng)信息。
路徑請(qǐng)求及對(duì)應(yīng)的獲取路徑有以下幾種形式:
?req.query: 處理get請(qǐng)求,獲取get請(qǐng)求參數(shù)。
?req.body: 處理post請(qǐng)求,獲取post請(qǐng)求體。
?req.params: 處理/:XXX形式的get或post請(qǐng)求,獲取請(qǐng)求參數(shù)
?req.param(): 處理get和post請(qǐng)求,但查找優(yōu)先級(jí)由高到低為req.params -> req.body -> req.query。
res.render() 則將所有數(shù)據(jù)以 json 格式傳遞給模板引擎。
4.3.2 路由規(guī)則實(shí)踐
現(xiàn)在我們直接訪問(wèn) localhost:3000/login 會(huì)顯示:
這是因?yàn)槲覀冞€沒(méi)有建立 /login 這一路由規(guī)則
在 routers/index.js 文件中添加代碼
router.get('/login', function(req, res, next) {
res.render('login', {title: 'login'});
});
Ctrl + c 停止服務(wù),node app.js 再次啟動(dòng)服務(wù),訪問(wèn) localhost:3000/login 后顯示:
出現(xiàn)這個(gè)錯(cuò)誤是因?yàn)?view 中并沒(méi)用 login 對(duì)應(yīng)的文件,添加 login.ejs 文件,文件中寫(xiě)入 <%= title%>,直接刷新瀏覽器,這時(shí)便可以看到正確的顯示:
還記得之前簡(jiǎn)單講解過(guò) Express 生成的模板框架嗎,routes 中存放路由文件,views 中存放視圖文件,這就相當(dāng)于 MVC 模式中的 C 和 V,而 index.ejs 文件中的 <%= title%> 是 ejs 模板引擎的語(yǔ)句,意思是將后臺(tái)傳遞來(lái)的 title 數(shù)據(jù)在頁(yè)面中顯示出來(lái)。
現(xiàn)在你應(yīng)該大致了解了 Express 的路由工作原理,但在剛在的操作中,我們發(fā)現(xiàn)每次修改后臺(tái)代碼時(shí),想要瀏覽修改結(jié)果,就需要先重啟服務(wù)。這無(wú)疑增加了開(kāi)發(fā)的負(fù)擔(dān)。
使用 supervisor 模塊可以很好的解決這個(gè)問(wèn)題,每當(dāng)我們保存文件后,此模塊便會(huì)自動(dòng)重啟服務(wù),提高了開(kāi)發(fā)效率。
首先要安裝此模塊:
sudo npm install -g supervisor
配置啟動(dòng)命令:
supervisor app.js
之后,我們的項(xiàng)目啟動(dòng)命名便從 node app.js 更改為
supervisor app.js
這樣我們的開(kāi)發(fā)環(huán)境已經(jīng)初步配置完成,接下來(lái)我們根據(jù) Express 的路由控制原則來(lái)設(shè)計(jì)我們的博客項(xiàng)目
五、搭建路由模塊
依據(jù)我們博客指定好的功能,我們初步設(shè)計(jì)以下幾個(gè)路由規(guī)則:
/login
/logout
/reg
/post
/search
/edit/:_id
/remove/:_id
以上幾個(gè)路由規(guī)則分別對(duì)應(yīng)“登錄”、“退出登錄”、“注冊(cè)”、“發(fā)表文章”、“查詢”、“編輯”、“刪除”功能。
再分別建立好對(duì)應(yīng)的視圖文件:
views/login.ejs
views/register.ejs
views/index.ejs
views/post.ejs
views/search.ejs
views/edit.ejs
“刪除”功能只是在請(qǐng)求完成后返回一個(gè)狀態(tài)信息,因此不必要?jiǎng)?chuàng)建視圖文件。
完成上面兩步,就需要在瀏覽器中依次測(cè)試剛剛設(shè)計(jì)好的路由規(guī)則是否有效果,有沒(méi)有報(bào)出錯(cuò)誤,那這節(jié)實(shí)驗(yàn)的任務(wù)就完成了。
六、前端模板引擎
6.1 什么是模板引擎
模板引擎是一個(gè)將頁(yè)面模板和數(shù)據(jù)數(shù)據(jù)結(jié)合起來(lái)生成 HTML 頁(yè)面的工具。通過(guò)模板引擎,我們可以在 HTML 文件中直接使用后臺(tái)傳遞過(guò)來(lái)的數(shù)據(jù),而不必再使用通過(guò)解析 json 數(shù)據(jù),在拼接成字符串的形式渲染數(shù)據(jù),大大提高了開(kāi)發(fā)效率。
6.2 什么是ejs
express -e LouBlog
看過(guò) Express API 的同學(xué)可能比較清楚,這里的 -e 正是指定 ejs 作為我們的模板引擎,而默認(rèn)的模板引擎是 jade 。
那為何要選擇 ejs ,不選 jade?很大的一個(gè)原因是因?yàn)?ejs 的語(yǔ)法更符合前端開(kāi)發(fā)者的習(xí)慣,項(xiàng)目的目的是為了能讓大家學(xué)會(huì)這一套開(kāi)發(fā)流程,而不僅僅是一個(gè)模板引擎;反觀 jade ,很多初學(xué)者都很難馬上適應(yīng)這中語(yǔ)法,更符合后臺(tái)開(kāi)發(fā)者的習(xí)慣,特別是對(duì)縮進(jìn)的嚴(yán)格要求,使得大家感覺(jué)與 Python 很像。
但這并不是說(shuō) jade 比 ejs 有多差,相反,有人做過(guò)測(cè)試,jade 的性能反而好過(guò) ejs。這里只是為了大家快速上手開(kāi)發(fā),所以選擇了ejs。大家有時(shí)間也可以嘗試使用 jade 開(kāi)發(fā)本課程,親身體驗(yàn)一下二者的差別吧。
6.3 使用ejs
在 Express 自動(dòng)生成項(xiàng)目框架時(shí),有這么兩行代碼
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
這里設(shè)置了模板文件的存儲(chǔ)位置和使用的模板引擎。
在 routers/index.js 中通過(guò) res.render() 渲染模板。它接收兩個(gè)參數(shù),第一個(gè)是模板名稱,即 views 目錄下的模板文件名;第二個(gè)參數(shù)是傳遞給模板的數(shù)據(jù)對(duì)象。
舉個(gè)例子:當(dāng)代碼為 res.render('index', {title: 'Express'}); 時(shí),模板引擎會(huì)把 <%= title %> 替換為 Express ,然后把替換后的頁(yè)面展示給用戶。
相對(duì)于 jade 來(lái)說(shuō),ejs 非常簡(jiǎn)單,只有三種標(biāo)簽:
?<% code %>: javascript代碼,這代表我們能使用 if-else 之類的邏輯判斷語(yǔ)句
?<%= code%>: 顯示替換過(guò)HTML特殊字符的內(nèi)容
?<%- code %>: 顯示原始HTML內(nèi)容
6.4 頁(yè)面布局
使用模板的一個(gè)原因,除了能方便處理數(shù)據(jù),還有一點(diǎn)就是模板可以重用,我們可以將共用的部分分離為一個(gè)文件,并通過(guò) include 引入。
6.4.1 分離共用模塊
ejs 模板引擎的 include 語(yǔ)法簡(jiǎn)單粗暴,它不像許多模板能夠引入成對(duì)的標(biāo)簽,而是硬生生將正常的 HTML 頁(yè)面截?cái)?,直接將成?duì)的標(biāo)簽分在了不同的文件當(dāng)中。
我們先來(lái)搭建一個(gè)首頁(yè) index.ejs,課程最終完成的樣式如下:
希望大家能自由發(fā)揮你的創(chuàng)造力,建造有自己特色的樣式的博客,下面給出 index.ejs 的最簡(jiǎn)代碼,通過(guò)不斷優(yōu)化,來(lái)學(xué)習(xí) ejs 模板及其他知識(shí)點(diǎn)。
<!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>
從這個(gè)頁(yè)面的構(gòu)造,以及我們之前的功能設(shè)計(jì)可以看出,
部分的導(dǎo)航條是共用部分,也就是在其頁(yè)面都有出現(xiàn),這就可以用到 include 將其提取為一個(gè)文件。
使用 include 后的文件及內(nèi)容:
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>
修改過(guò)后的 views/index.ejs 文件為:
<%- include header %>
<%= title %>
<%- include footer %>
以上便是 include 的簡(jiǎn)單使用方法,ejs 模板引擎的基本功能也梳理完畢,在接下來(lái)的學(xué)習(xí)中,進(jìn)一步體會(huì) ejs 的魅力吧。
七、設(shè)計(jì)頁(yè)面
接下來(lái),我們主要完成前端的展示部分,使用 bootstrap 前端框架快速搭建樣式優(yōu)美的響應(yīng)式頁(yè)面。
7.1 使用 bootstrap 前端框架
7.1.1 引入 bootstrap
注意:bootstrap 所有的 JavaScript 插件都依賴 jquery, 并且通過(guò)查看 bootstrap 的官方文檔就可以知道,bootstrap 所依賴的 jquery 版本最低為 v1.9.1,因此,還需要大家自己嘗試引入 jquery 文件。
再補(bǔ)充一下 bootstrap 的幾種安裝方式:
1.手動(dòng)下載并導(dǎo)入項(xiàng)目:從官網(wǎng)下載所需的 bootstrap 開(kāi)發(fā)包,選擇“用于生產(chǎn)環(huán)境的 bootstrap”,需要的文件為 fonts/*、js/bootstrap.min.js、css/bootstrap.min.css;
2.通過(guò) npm 安裝:執(zhí)行 npm install bootstrap --save。此時(shí) bootstrap 就相當(dāng)于一個(gè)模塊,通過(guò) require('bootstrap') 導(dǎo)入后使用;
3.通過(guò) bower 安裝:執(zhí)行 bower install bootstrap。bower 是客戶端技術(shù)的軟件包管理器,方便管理客戶端依賴關(guān)系,在安裝 bootstrap 之前應(yīng)該先執(zhí)行 sudo npm install -g bower 全局安裝 bower;
4.使用CDN加速服務(wù):bootstrap 中文網(wǎng)提供了免費(fèi)的 CDN 加速服務(wù),復(fù)制以下代碼,即可使用:
<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 的導(dǎo)航條組件:
<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 的一個(gè)簡(jiǎn)單例子,也只用到了導(dǎo)航條組件,除此之外,我們開(kāi)發(fā)博客系統(tǒng)還可能用到表單樣式,柵格系統(tǒng)等,同學(xué)們可選擇合適的組件,當(dāng)然也可以使用其他前端框架,比如 AmazeUI 等,開(kāi)發(fā)自己的博客系統(tǒng)。
另外需要提出的一點(diǎn)是,在上面的導(dǎo)航條中,同時(shí)出現(xiàn)了“登錄”和“用戶信息”等邏輯上不該同時(shí)出現(xiàn)的按鈕。這就需要通過(guò) session 機(jī)制來(lái)判斷用戶的登錄狀態(tài),并返回應(yīng)該顯示在頁(yè)面中的按鈕選項(xiàng)。這將在下一節(jié),添加 mongoDB 后做詳細(xì)的講解。
7.2 自己搭建頁(yè)面樣式
這一步主要是使用 bootstrap 框架,搭建每一個(gè)功能模塊的頁(yè)面樣式,自己可以更具自己的喜好,自由發(fā)揮,在此列出每一個(gè)頁(yè)面必要的元素,以方便后期講解的統(tǒng)一。
views/login.ejs(登錄頁(yè)面):
構(gòu)建一個(gè) form 表單,需填寫(xiě)的內(nèi)容包括 “用戶名” 和 “密碼”。
views/register.ejs(注冊(cè)頁(yè)面):
一個(gè) form 表單,需填寫(xiě)的內(nèi)容包括 “用戶名”、“密碼”、“確認(rèn)密碼”和“郵箱”。
views/index.ejs(主頁(yè)):
主頁(yè)展示文章信息,有 “標(biāo)題”、“作者”、“創(chuàng)建日期”、“標(biāo)簽”和“文章內(nèi)容”。
views/post.ejs(發(fā)表頁(yè)面):
form 表單,需要填寫(xiě)的內(nèi)容包括 “標(biāo)題”,“標(biāo)簽”和“文章內(nèi)容”,至于 “作者”和“創(chuàng)建時(shí)間” 將通過(guò)其他方式記錄。
views/search.ejs(查詢結(jié)果頁(yè)面):
展示內(nèi)容和主頁(yè)一致。
views/edit.ejs(編輯頁(yè)面):
form 表單,和發(fā)表頁(yè)面一致,需要自動(dòng)填寫(xiě)原有的數(shù)據(jù),方便編輯。
7.3 模擬數(shù)據(jù)
接下來(lái),我們通過(guò)修改 routes/index.js 向模板傳遞模擬數(shù)據(jù),方便我們理解數(shù)據(jù)傳遞過(guò)程、編寫(xiě)首頁(yè)樣式。
跳轉(zhuǎn)首頁(yè)的路由規(guī)則為:
router.get('/', function(req, res, next) {
res.render('index', {title: '主頁(yè)'});
});
上一節(jié)提到,res.render() 會(huì)將數(shù)據(jù)傳遞給模板,其中參數(shù)一便是對(duì)應(yīng)的模板,這里便是 index.ejs,而數(shù)據(jù)便是{...} 這一對(duì)象。所以,我們編寫(xiě)的模擬數(shù)據(jù),就寫(xiě)在這個(gè)對(duì)象里,以 json 格式為標(biāo)準(zhǔn)。
router.get('/', function(req, res, next) {
res.render('index', {
title: '主頁(yè)',
arts: [{
title: 'nodeJS入門(mén)',
tags: 'nodeJS',
author: '...',
createTime: '',
content: '...'
},{
title: 'nodeJS入門(mén)',
tags: 'nodeJS',
author: '...',
createTime: '',
content: '...'
},{
title: 'nodeJS入門(mén)',
tags: 'nodeJS',
author: '...',
createTime: '',
content: '...'
}]
});
});
這里簡(jiǎn)單添加了三條數(shù)據(jù),模板 views/index.ejs 中添加:
<% arts.forEach(function(art) { %>
<%= art.title %>
<%= art.tags %>
<%= art.author %>
<%= art.createTime %>
<%= art.content %>
<% }) %>
這樣,模板便成功的從后臺(tái)得到了數(shù)據(jù),并展示到頁(yè)面中。頁(yè)面樣式很粗糙,這需要同學(xué)們自己完成。
八、本節(jié)總結(jié)
本節(jié)實(shí)驗(yàn)簡(jiǎn)單介紹了開(kāi)發(fā)環(huán)境的搭建以及 Express 框架的使用,其中需要大家多花時(shí)間理解 Express 框架,從模板生成,到路由控制,希望大家能多看官網(wǎng)中的 API,熟悉 v3.x 和 v4.x 之間的區(qū)別,介紹了 ejs 模板的用法,熟悉 <% code %>、<%= code%>、<%- code%> 以及使用 <%- include views %> 進(jìn)行模板重用是一重要知識(shí)點(diǎn)。
《mongoDB基礎(chǔ)教程》
使用 mongoDB 數(shù)據(jù)庫(kù)并整理功能模塊
一、實(shí)驗(yàn)說(shuō)明
下述介紹為實(shí)驗(yàn)樓默認(rèn)環(huán)境,如果您使用的是定制環(huán)境,請(qǐng)修改成您自己的環(huán)境介紹。
- 環(huán)境登錄
無(wú)需密碼自動(dòng)登錄,系統(tǒng)用戶名shiyanlou - 環(huán)境介紹
本實(shí)驗(yàn)環(huán)境采用帶桌面的Ubuntu Linux環(huán)境,實(shí)驗(yàn)中會(huì)用到桌面上的程序:
1.LX終端(LXTerminal): Linux命令行終端,打開(kāi)后會(huì)進(jìn)入Bash環(huán)境,可以使用Linux命令
2.Firefox:瀏覽器,可以用在需要前端界面的課程里,只需要打開(kāi)環(huán)境里寫(xiě)的HTML/JS頁(yè)面即可
3.GVim:非常好用的編輯器,最簡(jiǎn)單的用法可以參考課程Vim編輯器
二、課程介紹
這一節(jié),我們將學(xué)習(xí)在 Express 框架如何操作 mongoDB 中的數(shù)據(jù),以及如何配合 session 完成一些基本的邏輯狀態(tài)判斷,完成本節(jié),我們的 LouBlog 博客系統(tǒng)也將初具模型。
Mongodb安裝
https://www.mongodb.com/download-center#community
1.啟動(dòng)數(shù)據(jù)庫(kù)
mongod.exe --dbpath c:\data\db
2.連接數(shù)據(jù)庫(kù)
mongod
3.查詢數(shù)據(jù)庫(kù)和使用數(shù)據(jù)庫(kù)和查找數(shù)據(jù)表
show dbs
Use datas
Db.users.find()
三、使用 mongoDB 數(shù)據(jù)庫(kù)
mongoDB 是一個(gè)基于分布式文件存儲(chǔ)的非關(guān)系型數(shù)據(jù)庫(kù)(NoSQL)的一種。它支持的數(shù)據(jù)結(jié)構(gòu)非常松散,類似 json 的 bjson 格式。
mongoDB 沒(méi)有關(guān)系型數(shù)據(jù)庫(kù)中行和表的概念,但有類似文檔 (document)和集合(collection)的概念。文檔是 mongoDB 最基本的單位,集合是許多文檔的總和,一個(gè)數(shù)據(jù)庫(kù)可以有多個(gè)集合,一個(gè)集合可以有多個(gè)文檔。
因?yàn)閷?shí)驗(yàn)環(huán)境中默認(rèn)安裝有 mongoDB 因此我們跳過(guò)安裝這一步,但先要通過(guò)以下指令開(kāi)啟 mongoDB 服務(wù);
開(kāi)啟服務(wù):
sudo service mongodb start
在根目錄下創(chuàng)建數(shù)據(jù)庫(kù)存儲(chǔ)目錄,并啟動(dòng)服務(wù):
sudo mkdir /data/db
mongod
最后輸入 mongo 即可訪問(wèn)數(shù)據(jù)庫(kù)。
問(wèn)題記錄:
運(yùn)行mongodb出現(xiàn)計(jì)算機(jī)丟失api-ms-win-crt-runtime-|1-1-0.dll
解決:
http://blog.csdn.net/mblhq/article/details/53896920
3.1 連接 mongoDB
mongoDB 的服務(wù)啟動(dòng)后,開(kāi)始編寫(xiě)程序建立連接,這里我們使用 mongoDB 的模型工具 -- mongoose,可以方便我們減化代碼,并且這還是為 nodeJS 設(shè)計(jì)的。
3.1.1 使用 mongoose 連接 mongoDB
運(yùn)行以下命令安裝 mongoose 包:
npm install mongoose --save
接下需要修改 app.js,添加下面的代碼:
//先引入 mongoose 模塊
var mongoose = require('mongoose');
//連接數(shù)據(jù)庫(kù)
mongoose.connect('mongodb://localhost:27017/datas');
mongoose.connection.on('error', console.error.bind(console, '連接數(shù)據(jù)庫(kù)失敗'));
刷新頁(yè)面時(shí),若沒(méi)有報(bào)出 “連接數(shù)據(jù)庫(kù)失敗” 則成功連接數(shù)據(jù)庫(kù)。接下來(lái),我們便要建立數(shù)據(jù)庫(kù)模型,向數(shù)據(jù)庫(kù)中存儲(chǔ)數(shù)據(jù)。
3.2 設(shè)置 schema
schema 是 mongoose 中的模型對(duì)象,就類似關(guān)系型數(shù)據(jù)庫(kù)中的表結(jié)構(gòu),為 key/value 的鍵值對(duì)形式。
我們的博客系統(tǒng)中主要存儲(chǔ)用戶和文章兩類數(shù)據(jù),也就是需要建立兩個(gè)模型對(duì)象,暫且叫做 userSchema、articleSchema。
在根目錄下新建一個(gè)文件夾 models,再新建一個(gè)文件 model.js。
3.2.1 設(shè)置 userSchema
在 models/model.js 中引入 mongoose 模塊,并定義 schema 模型對(duì)象:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
根據(jù)上一節(jié)實(shí)驗(yàn)中,我們統(tǒng)一定義的用戶屬性有 “用戶名”、“密碼”和“郵箱”,可以確定模型中的屬性:username、password、email,此外考慮到以后操作數(shù)據(jù)的方便,在添加一個(gè)屬性createTime。
就可以創(chuàng)建具體的模型對(duì)象:
var userSchema = new Schema({
username: String,
password: String,
email: String,
createTime: {
type: Date,
default: Date.now
}
});
最后通過(guò) mongoose 的 model() 方法,將 schema 發(fā)布為 model,model 具有抽象屬性和行為的數(shù)據(jù)庫(kù)操作對(duì),這就使模型對(duì)象具有了數(shù)據(jù)庫(kù)的 CRUD 操作方法。
exports.User = mongoose.model('User', userSchema);
3.2.2 設(shè)置 articleSchema
同樣是在 models/model.js 下,我們創(chuàng)建文章模型對(duì)象 -- 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 對(duì)應(yīng)數(shù)據(jù)庫(kù)中的集合,可以通過(guò) show colections 查看數(shù)據(jù)庫(kù)中的所有集合。
創(chuàng)建好模型,就可以操作數(shù)據(jù)了。
3.3 操作數(shù)據(jù)
3.3.1 注冊(cè)用戶、發(fā)表文章
首先我們學(xué)習(xí)添加數(shù)據(jù)的操作 -- 注冊(cè)用戶和發(fā)表文章,在 routes/index.js 中,我們添加以下代碼:
var mongoose = require('mongoose');//引入加密模塊
var crypto = require('crypto');
//引入模型對(duì)象
var model = require('../models/model');
var User = model.User;
router.post('/reg', function(req, res, next) {
//req.body 處理 post 請(qǐng)求
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');
}
//對(duì)密碼進(jìn)行md5加密
var md5 = crypto.createHash('md5'),
md5password = md5.update(password).digest('hex'); //16進(jìn)制
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('注冊(cè)成功!');
newUser.password = null;
delete newUser.password;
req.session.user = newUser;
return res.redirect('/');
});
});
});
這樣便實(shí)現(xiàn)了注冊(cè)功能,需要注意 req.body 處理 post 請(qǐng)求的參數(shù),建立 User 模型對(duì)象實(shí)體操作數(shù)據(jù)庫(kù),其實(shí)有 JavaScript 基礎(chǔ)的同學(xué)應(yīng)該很熟悉這樣的寫(xiě)法。
再來(lái)是文章發(fā)表功能,這時(shí)就要用到 articleSchema 模型對(duì)象:
var Article = model.Article;
router.post('/post', function(req, res, next) {
var data = new Article({
title: req.body.title,
//這里的 author 元素通過(guò) session 獲得,后面會(huì)詳細(xì)講解
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('/');
});
});
有了用戶注冊(cè)的基礎(chǔ),發(fā)表文章就簡(jiǎn)單許多了,但現(xiàn)在還沒(méi)講到 session 的運(yùn)用,author 元素的值可以暫時(shí)通過(guò) post 表單獲得,稍后講到 session 時(shí),我們?cè)俑臑樯厦娴膶?xiě)法即可。
3.3.2 刪除文章
接下來(lái)是文章的刪除操作,依然是 routes/index.js,修改我們第一節(jié)實(shí)驗(yàn)寫(xiě)好的路由規(guī)則 /remove/:_id:
//mongoose 的 remove() 方法,通過(guò)傳遞檢索參數(shù),直接刪除檢索結(jié)果
router.get('/remove/:_id', function(req, res, next) {
//req.params 處理 /:xxx 形式的 get 或 post 請(qǐng)求,獲取請(qǐng)求參數(shù)
Article.remove({_id: req.params._id}, function(err) {
if(err) {
console.log(err);
} else {
console.log('文章刪除成功!');
}
return res.redirect('back');
})
});
3.3.3 編輯文章
編輯文章,不僅需要獲取文章信息,初始化表單內(nèi)容,同時(shí)還需要有和發(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() 方法用過(guò)檢索參數(shù)并返回修改結(jié)果
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 查詢文章
這里我們可以通過(guò)正則表達(dá)式,實(shí)現(xiàn)模糊查詢,因?yàn)?Express 路由規(guī)則支持正則匹配查詢。
router.get('/search', function(req, res, next) {
//req.query 獲取 get 請(qǐng)求的參數(shù),并構(gòu)造為正則對(duì)象
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: '查詢結(jié)果',
arts: arts
});
});
});
完成以上四步,我們的博客系統(tǒng)就具有了基本的功能,但是還有幾個(gè)小問(wèn)題:
?session 保存登錄狀態(tài);
?控制訪問(wèn)權(quán)限;
解決這兩個(gè)問(wèn)題,就需要靠接下來(lái)我們將要學(xué)習(xí)的 session 。
四、創(chuàng)建 session
session 是一種持久網(wǎng)絡(luò)協(xié)議,在客戶端與服務(wù)器之間起到交換數(shù)據(jù)包的作用。用戶登錄后的基本信息都會(huì)保存其中,Express 也提供了會(huì)話中間件,同時(shí)我們還可以將會(huì)話信息存儲(chǔ)到數(shù)據(jù)庫(kù)中,便于維護(hù)。為此,我們需要引入兩個(gè)中間件 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);
//這里設(shè)置 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
}));
完成對(duì) app.js 的以上修改之后,我們便能通過(guò) req.session 獲取當(dāng)前用戶的會(huì)話對(duì)象,獲取用戶的相關(guān)信息。
4.2 使用 session
舉兩個(gè)例子:
之前提到過(guò)的發(fā)表文章,其中的 author 屬性需要通過(guò)獲取 session 中保存的用戶信息,此處我們就可以修改發(fā)表文章的方法以實(shí)現(xiàn)我們的需求:
router.post('/post', function(req, res, next) {
var data = new Article({
title: req.body.title,
//這里的 author 元素通過(guò) 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 另一個(gè)很大的作用就是判斷用戶登錄狀態(tài)并控制頁(yè)面的元素顯示:
我們就修改上一節(jié)給出的導(dǎo)航條代碼,修改如下:
<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="/">實(shí)驗(yàn)樓</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">注冊(cè)</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 便是通過(guò) session 獲取的用戶信息,為此,我們還需要在 res.render()中傳遞 session:
res.render('index', {
title: '主頁(yè)',
user: req.session.user
// code ....
});
除了以上兩個(gè)例子,還有許多用到 session 的地方,大家在學(xué)習(xí)過(guò)程中可以自己好好感悟。
五、擴(kuò)展功能
5.1 添加 flash 信息提示
上一節(jié)我們完成了博客系統(tǒng)的基本功能,但是有心的同學(xué)會(huì)發(fā)現(xiàn),示例代碼里很多的 console.log() 信息提示,這是打印一些返回信息以幫助我們調(diào)試代碼,但不知有多少同學(xué)這樣思考過(guò) -- 每次刷新調(diào)試,都要看服務(wù)程序的信息提示好不方便,要是刷新就能在頁(yè)面顯示這些提示可好,此外,用戶登錄,發(fā)表文章,成功還好,要是哪出錯(cuò)了卻沒(méi)有任何提示多不方便。
恭喜你已經(jīng)能夠主動(dòng)思考、學(xué)習(xí),接著往下你應(yīng)該就會(huì)自己查詢解決辦法了吧。
只要走到這一步,相信大家就知道 flash 模塊能很好地幫助我們實(shí)現(xiàn)這一功能:
flash 存儲(chǔ)于 session 中,但與之前我們存入的用戶登錄信息不同,flash 信息將會(huì)在下一次刷新后被清楚。
首先還是安裝模塊,并引入:
npm install connect-flash --save
// app.js 中
var flash = require('connect-flash');
app.use(flash());
這樣,我們便成功引入了 flash 模塊,接下來(lái)就可以將所有的 console.log() 使用 req.flash('type', content) 替換,其中參數(shù)一是一個(gè)字符串,代表了信息的類型,我們常用的就是 'success' 和 'error' 兩種,參數(shù)二就是信息的具體內(nèi)容。
這只是將信息保存到了 session 中,想要在頁(yè)面中展示,我們就必須要從 session 中獲取并傳遞給 ejs 模板,方法如下:
// code ...
res.render('xxx', {
// ...
success: req.flash('success').toString();
error: req.flash('error').toString();
// ...
});// code ...
模板獲得信息后就可以通過(guò)簡(jiǎn)單的判斷語(yǔ)句展示這一內(nèi)容:
<%if(success){ %>
<%=success %>
<% } %>
<%if(error){ %>
<%=error %>
<% } %>
// error 同上,樣式可以通過(guò) bootstrap 調(diào)整。問(wèn)題:這樣許多頁(yè)面都需要信息提示,我們應(yīng)該在何處做以上調(diào)整?
這就是我們添加 flash 信息提示的大致過(guò)程。
5.2 添加分頁(yè)功能
分頁(yè)功能是很常見(jiàn)的一個(gè)功能,當(dāng)展示的信息條目很多時(shí)能分批次顯示,減小了瀏覽器壓力,避免渲染速度過(guò)慢。
要實(shí)現(xiàn)分頁(yè)的功能,主要需要考慮這幾個(gè)元素:“當(dāng)前頁(yè)碼”,“每頁(yè)展示個(gè)數(shù)”,“條目總數(shù)”;此外分頁(yè)主要有兩種展示形式:“只有上/下一頁(yè)”,“展示多個(gè)頁(yè)碼”。我們就嘗試實(shí)現(xiàn)簡(jiǎn)單的 “只有上/下一頁(yè)”。
修改 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 跳過(guò)指定的頁(yè)數(shù)
.skip((page - 1) * pageSize)
//限制讀取 pageSize 條數(shù)據(jù)
.limit(pageSize)
//以 createTime 倒序排序
.sort('-createTime')
//執(zhí)行回調(diào)方法
.exec(function(err, arts) {
if(err) {
req.flash('error',err);
return res.redirect('/');
}
res.render('index', {
title: '主頁(yè)',
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ù)傳遞給模板后,剩余的事就簡(jiǎn)單了許多。其中的 skip() 和 limit() 是實(shí)現(xiàn)分頁(yè)的關(guān)鍵部分,也有很多人說(shuō)當(dāng)數(shù)據(jù)量少的時(shí)候,skip() 的效率還可以,但當(dāng)數(shù)據(jù)量很大的時(shí)候,效率就會(huì)很差。大家有興趣可以查閱資料,嘗試發(fā)現(xiàn)效率更高的方法。
Js使用express render的數(shù)據(jù)
六、本節(jié)總結(jié)
本節(jié)添加了mongoDB,并使用 mongoose 連接了數(shù)據(jù)庫(kù),創(chuàng)建 session 判斷用戶狀態(tài),完成了一些功能的擴(kuò)展,使我們的 LouBlog 博客系統(tǒng)更充實(shí)。
再總結(jié)一下整個(gè)系列的實(shí)驗(yàn),提出了許多課程的知識(shí)點(diǎn),示例代碼只是展示了核心部分,希望大家能嘗試在這種點(diǎn)到即止的方式中去查找解決辦法。
參考文檔: