Twitch 商城網站
介紹:
1、Node.js搭建的網站,采用Mongodb數據庫
2、后臺框架 借用了 express ,模板引擎使用的 ejs,對于數據庫的操作使用的是 mongoose
網站搭建流程
入口文件 app.js
我們來作一些修改,以上代碼實現了路由的功能,我們當然可以不要 routes/index.js 文件,把實現路由功能的代碼都放在 app.js 里,但隨著時間的推移 app.js 會變得臃腫難以維護,這也違背了代碼模塊化的思想,所以我們把實現路由功能的代碼都放在 routes/index.js 里。官方給出的寫法是在 app.js 中實現了簡單的路由分配,然后再去 index.js 中找到對應的路由函數,最終實現路由功能。我們不妨把路由控制器和實現路由功能的函數都放到 index.js 里,app.js 中只有一個總的路由接口。
var routes = require('./routes/index');
//在執行到錯誤處理程序前 將 express實例 app 傳給 路由
routes(app);
routes/index.js 文件,總的路由接口
module.exports = function(app) {
app.get('/', function (req, res) {
res.render('index', { title: 'Express' });
});
};
app.js 和 index.js 修改成這樣就達成了總路由接口的配置了!
路由規則
express 封裝了多種 http 請求方式,我們主要只使用 get 和 post 兩種,即 app.get() 和 app.post()
app.get() 和 app.post() 的第一個參數都為請求的路徑,第二個參數為處理請求的回調函數,回調函數有兩個參數分別是 req 和 res,代表請求信息和響應信息 。路徑請求及對應的獲取路徑有以下幾種形式:這里就參考文檔
解析 :
- req.query: 處理 get 請求,獲取 get 請求參數
- req.params: 處理 /:xxx 形式的 get 或 post 請求,獲取請求參數
- req.body: 處理 post 請求,獲取 post 請求體
- req.param(): 處理 get 和 post 請求,但查找優先級由高到低為 req.params→req.body→req.query
路徑規則還支持正則表達式,更多請查閱 Express 官方文檔 。
接下來自己添加一個路由規則做下測試吧!
模板引擎
什么是模板引擎
模板引擎(Template Engine)是一個將頁面模板和要顯示的數據結合起來生成 HTML 頁面的工具。
如果說上面講到的 express 中的路由控制方法相當于 MVC 中的控制器的話,那模板引擎就相當于 MVC 中的視圖。
什么事ejs?
ejs 是模板引擎的一種,也是我們這個教程中使用的模板引擎,因為它使用起來十分簡單,而且與 express 集成良好。
使用模板引擎
前面我們通過以下兩行代碼設置了模板文件的存儲位置和使用的模板引擎:
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
注意:我們通過 express -e blog 只是初始化了一個使用 ejs 模板引擎的工程而已,比如 node_modules 下添加了 ejs 模塊,views 文件夾下有 index.ejs 。并不是說強制該工程只能使用 ejs 不能使用其他的模板引擎比如 jade,真正指定使用哪個模板引擎的是 app.set('view engine', 'ejs'); 。
在 routes/index.js 中通過調用 res.render() 渲染模版,并將其產生的頁面直接返回給客戶端。它接受兩個參數,第一個是模板的名稱,即 views 目錄下的模板文件名,擴展名 .ejs 可選。第二個參數是傳遞給模板的數據對象,用于模板翻譯。
當我們 res.render('index', { title: 'Express' }); 時,模板引擎會把 <%= title %> 替換成 Express,然后把替換后的頁面顯示給用戶。
注意:我們通過 app.use(express.static(path.join(__dirname, 'public'))) 設置了靜態文件目錄為 public 文件夾,所以上面代碼中的 href='/stylesheets/style.css' 就相當于 href='public/stylesheets/style.css'
ejs 的標簽系統非常簡單,它只有以下三種標簽:
- <% code %>:JavaScript 代碼。
- <%= code %>:顯示替換過 HTML 特殊字符的內容。
- <%- code %>:顯示原始 HTML 內容。
注意: <%= code %> 和 <%- code %> 的區別,當變量 code 為普通字符串時,兩者沒有區別。當 code 比如為 <h1>hello</h1> 這種字符串時,<%= code %> 會原樣輸出 <h1>hello</h1>,而 <%- code %> 則會顯示 H1 大的 hello 字符串。
我們可以在 <% %> 內使用 JavaScript 代碼。下面是 ejs 的官方示例:
The Data
supplies: ['mop', 'broom', 'duster']
The Template
<ul>
<% for(var i=0; i<supplies.length; i++) {%>
<li><%= supplies[i] %></li>
<% } %>
</ul>
The Result
<ul>
<li>mop</li>
<li>broom</li>
<li>duster</li>
</ul>
我們可以用上述三種標簽實現頁面模板系統能實現的任何內容。
頁面布局
這里我們不使用layout進行頁面布局,而是使用更為簡單靈活的include。include 的簡單使用如下:
index.ejs
<%- include a %>
hello,world!
<%- include b %>
a.ejs
this is a.ejs
b.ejs
this is b.ejs
最終index.ejs會顯示
this is a.ejs
hello,world!
this is b.ejs
根據上面,我們具體初步的了解了一些nodejs、express一些特性,接下來正式開始搭建多人博客。
搭建多人博客
功能分析
搭建一個簡單的具有多人注冊、登錄、發表文章、登出功能的博客。
設計目標
未登錄:主頁左側導航顯示 home、login、register,右側顯示已發表的文章、發表日期及作者。
登陸后:主頁左側導航顯示 home、post、logout,右側顯示已發表的文章、發表日期及作者。
用戶登錄、注冊、發表成功以及登出后都返回到主頁。
未登錄效果圖:
主頁:
登錄頁:

注冊頁:

登錄后的效果圖:
主頁:

發表頁:

注意:沒有登出頁,當點擊 LOGOUT 后,退出登陸并返回到主頁。
路由規劃
我們已經把設計的構想圖貼出來了,接下來的任務就是完成路由規劃了。路由規劃,或者說控制器規劃是整個網站的骨架部分,因為它處于整個架構的樞紐位置,相當于各個接口之間的粘合劑,所以應該優先考慮。
根據構思的設計圖,我們作以下路由規劃:
/ :首頁
/login :用戶登錄
/reg :用戶注冊
/post :發表文章
/logout :登出
我們要求 /login 和 /reg 只能是未登錄的用戶訪問,而 /post 和 /logout 只能是已登錄的用戶訪問。左側導航列表則針對已登錄和未登錄的用戶顯示不同的內容。
修改 index.js 如下:
// 路由控制器和實現路由功能的函數,都在這個文件里
module.exports = function(app){
// 主頁
app.get('/', function(req, res) {
res.render('index', { title: 'Express' });
});
// 注冊,跳轉注冊頁
app.get('/reg',function(req,res){
res.render('reg',{title:'注冊'});
});
// 注冊,注冊操作
app.post('/reg',function(req,res){
});
// 登錄,跳轉登錄頁
app.get('/login',function(req,res){
res.render('login',{title:'登錄'});
});
// 登錄,登錄操作
app.post('/login',function(req,res){
});
// 發表,跳轉發表頁
app.get('/post',function(req,res){
res.render('post',{title:'發表'});
});
// 發表,發表操作
app.post('/post',function(req,res){
});
// 注銷
app.get('/logout',function(req,res){
});
};
使用數據庫
MongoDB簡介
MongoDB 是一個基于分布式文件存儲的 NoSQL(非關系型數據庫)的一種,由 C++ 語言編寫,旨在為 WEB 應用提供可擴展的高性能數據存儲解決方案。MongoDB 支持的數據結構非常松散,是類似 json 的 bjson 格式,因此可以存儲比較復雜的數據類型。MongoDB 最大的特點是他支持的查詢語言非常強大,其語法有點類似于面向對象的查詢語言,幾乎可以實現類似關系數據庫單表查詢的絕大部分功能,而且還支持對數據建立索引。
MongoDB 沒有關系型數據庫中行和表的概念,不過有類似的文檔和集合的概念。文檔是 MongoDB 最基本的單位,每個文檔都會以唯一的 _id 標識,文檔的屬性為 key/value 的鍵值對形式,文檔內可以嵌套另一個文檔,因此可以存儲比較復雜的數據類型。集合是許多文檔的總和,一個數據庫可以有多個集合,一個集合可以有多個文檔。
更多有關 MongoDB 的知識請參閱 《mongodb權威指南》或查閱:http://www.mongodb.org/
安裝MongoDB
安裝 MongoDB 很簡單,去官網下載對應系統的 MongoDB 壓縮包即可。解壓后將文件夾重命名為 mongodb,并在 mongodb 文件夾里新建 blog 文件夾作為我們博客內容的存儲目錄。進入到 bin 目錄下:運行:
./mongod --dbpath ../blog/
以上命令的意思是:設置 blog 文件夾作為我們工程的存儲目錄并啟動數據庫。
連接MongoDB
數據庫雖然安裝并啟動成功了,但我們需要連接數據庫后才能使用數據庫。怎么才能在 Node.js 中使用 MongoDB 呢?我們使用官方提供的 node-mongodb-native 驅動模塊,打開 package.json,在 dependencies 中添加一行:
"mongodb": "1.4.15"
然后運行 npm install 更新依賴的模塊,稍等片刻后 mongodb 模塊就下載并安裝完成了。
創建setting.js文件
接下來在工程的根目錄中創建 settings.js 文件,用于保存該博客工程的配置信息,比如數據庫的連接信息。我們將數據庫命名為 blog,因為數據庫服務器在本地,所以 settings.js 文件的內容如下:
module.exports = {
cookieSecret: 'myblog',
db: 'blog',
host: 'localhost',
port: 27017
};
其中 db 是數據庫的名稱,host 是數據庫的地址,port是數據庫的端口號,cookieSecret 用于 Cookie 加密與數據庫無關,我們留作后用。
創建Models文件夾
接下來在根目錄下新建 models 文件夾,并在 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, settings.port),
{safe: true});
其中通過 new Db(settings.db, new Server(settings.host, settings.port), {safe: true}); 設置數據庫名、數據庫地址和數據庫端口創建了一個數據庫連接實例,并通過 module.exports 導出該實例。這樣,我們就可以通過 require 這個文件來對數據庫進行讀寫了。
打開 app.js,在 var routes = require('./routes/index'); 下添加:
var settings = require('./settings');
會話支持 session
express 也提供了會話中間件,默認情況下是把用戶信息存儲在內存中,但我們既然已經有了 MongoDB,不妨把會話信息存儲在數據庫中,便于持久維護。為了使用這一功能,我們需要借助 express-session 和 connect-mongo 這兩個第三方中間件,在 package.json 中添加:
"express-session": "1.9.1",
"connect-mongo": "0.4.1"
注意: 如報"error setting ttl index on collection : sessions"錯誤,把"mongodb"&"connect-mongo"版本號更到最新。
在package.json修改 "mongodb":“2.0.42”, “connect-mongo”:“0.8.2” 運行npm install安裝模塊,打開app.js,添加以下代碼:
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
app.use(session({
secret: settings.cookieSecret,
key: settings.db,//cookie name
cookie: {maxAge: 1000 * 60 * 60 * 24 * 30},//30 days
store: new MongoStore({
db: settings.db,
host: settings.host,
port: settings.port
})
}));
注意: connect-mongo 最新版需要改成如:
store: new MongoStore({
url: 'mongodb://localhost/blog'
})
使用 express-session 和 connect-mongo 模塊實現了將會化信息存儲到mongoldb中。secret 用來防止篡改 cookie,key 的值為 cookie 的名字,通過設置 cookie 的 maxAge 值設定 cookie 的生存期,這里我們設置 cookie 的生存期為 30 天,設置它的 store 參數為 MongoStore 實例,把會話信息存儲到數據庫中,以避免丟失。在后面的小節中,我們可以通過 req.session 獲取當前用戶的會話對象,獲取用戶的相關信息。
實現注冊和登陸
我們已經準備好了數據庫訪問和會話的相關信息,接下來我們完成用戶注冊和登錄功能。
頁面設計
首先我們來完成主頁、登錄頁和注冊頁的頁面設計。
主頁:index.ejs
<%- include header %>
這是主頁
<%- include footer %>
登錄頁:login.ejs
<%- include header %>
<form method="post">
用戶名: <input type="text" name="name"/><br /><br />
密 碼: <input type="password" name="password"/><br /><br />
<input type="submit" value="登錄"/>
<input type="reset" value="重置"/>
</form>
<%- include footer %>
注冊頁: reg.ejs
<%- include header %>
<form method="post">
用戶名: <input type="text" name="name"/><br /><br />
密 碼: <input type="password" name="password"/><br /><br />
確認密碼:<input type="password" name="password-repeat"/><br /><br />
郵 箱: <input type="email" name="email"/><br /><br />
<input type="submit" value="注冊"/>
<input type="reset" value="重置"/>
</form>
<%- include footer %>
到這里 啟動我們的博客看看吧!
實時更新組件
注意:每次我們更新代碼后,都需要手動停止并重啟應用,使用 supervisor 模塊可以解決這個問題,每當我們保存修改的文件時,supervisor 都會自動幫我們重啟應用。通過:
$ npm install -g supervisor
安裝 supervisor 。使用 supervisor 命令啟動 app.js:
$ supervisor app.js
頁面通知
接下來我們實現用戶的注冊和登陸,在這之前我們需要引入 flash 模塊來實現頁面通知(即成功與錯誤信息的顯示)的功能。
什么事Flash?
我們所說的 flash 即 connect-flash 模塊(https://github.com/jaredhanson/connect-flash),flash 是一個在 session 中用于存儲信息的特定區域。信息寫入 flash ,下一次顯示完畢后即被清除。典型的應用是結合重定向的功能,確保信息是提供給下一個被渲染的頁面。
在 package.json 添加一行代碼:
"connect-flash": "0.1.1"
然后 npm install 安裝 connect-flash 模塊。修改 app.js ,在 var settings = require('./settings'); 后添加:
var flash = require('connect-flash');
在 app.set('view engine', 'ejs'); 后添加:
app.use(flash());
現在我們就可以使用 flash 功能了。
注冊響應
前面我們已經完成了注冊頁,當然現在點擊注冊是沒有效果的,因為我們還沒有實現處理 POST 請求的功能,下面就來實現它。
在 models 文件夾下新建 user.js,添加如下代碼:
// 處理 用戶注冊 和 登錄
// 引入 連接數據庫實例 外部文件
var mongodb = require('./db');
// 獲取用戶的信息
function User(user) {
this.name = user.name;
this.password = user.password;
this.email = user.email;
}
// 導出,發布出去
module.exports = User;
// 存儲用戶信息 prototype 原型
User.prototype.save = function(callback) {
// Scheme 要存入數據庫的用戶文檔
var user = {
name : this.name,
password : this.password,
email: this.email
};
// 打開數據庫
mongodb.open(function(err,db) {
if(err) {
return callback(err);//錯誤,返回err信息
}
// 讀取 users 集合
db.collection('users',function (err,collection) {
if(err){
mongodb.close();
return callback(err);
}
// 將用戶數據插入 users 集合
collection.insert(user,{
safe : true
} , function(err,user) {
mongodb.close();
if(err) {
return callback(err);//錯誤,返回err信息
}
callback(null,user[0]);//成功!err為null,并返回存儲后的用戶文檔
});
});
});
};
// 讀取用戶信息
User.get = function(name,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 鍵)值為 name 的一個文檔
collection.findOne({name:name},function(err,user){
if(err) {
return callback(err);//失敗!返回錯誤信息
}
callback(null,user);//成功!返回查詢的用戶信息
});
});
});
};
我們通過 User.prototype.save 實現了用戶信息的存儲,通過 User.get 實現了用戶信息的讀取。
打開 index.js ,在最前面添加如下代碼:
var crypto = require('crypto'),
User = require('../models/user.js');
通過 require() 引入 crypto 模塊和 user.js 用戶模型文件,crypto 是 Node.js 的一個核心模塊,我們用它生成散列值來加密密碼。
修改 index.js 中 app.post('/reg') 如下:
注意:我們把用戶信息存儲在了 session 里,以后就可以通過 req.session.user 讀取用戶信息。
- req.body: 就是 POST 請求信息解析過后的對象,例如我們要訪問 POST 來的表單內的 name="password" 域的值,只需訪問 req.body['password'] 或 req.body.password 即可。
- res.redirect: 重定向功能,實現了頁面的跳轉,更多關于 res.redirect 的信息請查閱:http://expressjs.com/api.html#res.redirect 。
- User:在前面的代碼中,我們直接使用了 User 對象。User 是一個描述數據的對象,即 MVC 架構中的模型。前面我們使用了許多視圖和控制器,這是第一次接觸到模型。與視圖和控制器不同,模型是真正與數據打交道的工具,沒有模型,網站就只是一個外殼,不能發揮真實的作用,因此它是框架中最根本的部分。
接下來我們實現當注冊成功返回主頁時,左側導航顯示 HOME 、POST 、LOGOUT ,右側顯示 注冊成功! 字樣,即添加 flash 的頁面通知功能。
修改 header.ejs,將 <nav></nav> 修改如下:
<nav>
<span><a title="主頁" href="/">home</a></span>
<% if (user) { %>
<span><a title="發表" href="/post">post</a></span>
<span><a title="登出" href="/logout">logout</a></span>
<% } else { %>
<span><a title="登錄" href="/login">login</a></span>
<span><a title="注冊" href="/reg">register</a></span>
<% } %>
</nav>
在 <article> 后添加如下代碼:
<% if (success) { %>
<div><%= success %></div>
<% } %>
<% if (error) { %>
<div><%= error %> </div>
<% } %>
修改 index.js ,將 app.get('/') 修改如下:
app.get('/', function (req, res) {
res.render('index', {
title: '主頁',
user: req.session.user,
success: req.flash('success').toString(),
error: req.flash('error').toString()
});
});
將 app.get('reg') 修改如下:
app.get('/reg', function (req, res) {
res.render('reg', {
title: '注冊',
user: req.session.user,
success: req.flash('success').toString(),
error: req.flash('error').toString()
});
});
現在運行我們的博客,注冊成功后顯示如下:

這個時候訪問別的就會 出現一個 user is undefined。因為其他的路由還沒傳遞一個user 過去。
我們通過對 session 的使用實現了對用戶狀態的檢測,再根據不同的用戶狀態顯示不同的導航信息。
簡單解釋一下流程:用戶在注冊成功后,把用戶信息存入 session ,頁面跳轉到主頁顯示 注冊成功! 的字樣。同時把 session 中的用戶信息賦給變量 user ,在渲染 index.ejs 文件時通過檢測 user 判斷用戶是否在線,根據用戶狀態的不同顯示不同的導航信息。
success: req.flash('success').toString() 的意思是將成功的信息賦值給變量 success, error: req.flash('error').toString() 的意思是將錯誤的信息賦值給變量 error ,然后我們在渲染 ejs 模版文件時傳遞這兩個變量來進行檢測并顯示通知。
登錄與登出響應
現在我們來實現用戶登錄的功能。
打開 index.js ,將 app.post('/login') 修改如下:
app.post('/login', function (req, res) {
//生成密碼的 md5 值
var md5 = crypto.createHash('md5'),
password = md5.update(req.body.password).digest('hex');
//檢查用戶是否存在
User.get(req.body.name, function (err, user) {
if (!user) {
req.flash('error', '用戶不存在!');
return res.redirect('/login');//用戶不存在則跳轉到登錄頁
}
//檢查密碼是否一致
if (user.password != password) {
req.flash('error', '密碼錯誤!');
return res.redirect('/login');//密碼錯誤則跳轉到登錄頁
}
//用戶名密碼都匹配后,將用戶信息存入 session
req.session.user = user;
req.flash('success', '登陸成功!');
res.redirect('/');//登陸成功后跳轉到主頁
});
});
將 app.get('/login') 修改如下:
app.get('/login', function (req, res) {
res.render('login', {
title: '登錄',
user: req.session.user,
success: req.flash('success').toString(),
error: req.flash('error').toString()});
});
(這樣就不會出現 'user is not defined' 的錯誤了)
接下來我們實現登出響應。修改 app.get('/logout') 如下:
app.get('/logout', function (req, res) {
req.session.user = null;
req.flash('success', '登出成功!');
res.redirect('/');//登出成功后跳轉到主頁
});
注意:通過把 req.session.user 賦值 null 丟掉 session 中用戶的信息,實現用戶的退出。
登錄后頁面顯示如下:
