nodejs開發指南讀後感
閱讀目錄
使用nodejs建立http伺服器;
1. 建立http伺服器,監聽3000埠;
var http = require("http"); http.createServer(function(req,res){ res.writeHead(200, {'Content-Type': 'text/html'}); res.write('<h1>Node2.js</h1>'); res.end('<p>Hello World</p>'); }).listen(3000);
1. http.Server 的事件
http.Server 是一個基於事件的 HTTP 伺服器,所有的請求都被封裝為獨立的事件,
開發者只需要對它的事件編寫響應函式即可實現 HTTP 伺服器的所有功能。它繼承自 EventEmitter,提供了以下幾個事件
1.request:當客戶端請求到來時,該事件被觸發,提供兩個引數 req 和res,分別是 http.ServerRequest和http.ServerResponse 的例項,
表示請求和響應資訊.
2. connection:當 TCP 連線建立時,該事件被觸發,提供一個引數 socket,為 net.Socket 的例項。
connection 事件的粒度要大於 request,因為客戶端在 Keep-Alive 模式下可能會在同一個連線內傳送多次請求。
3. close :當伺服器關閉時,該事件被觸發。注意不是在使用者連線斷開時。
程式碼如下:
var http = require('http'); var server = new http.Server(); server.on('request',function(req,res){ res.writeHead(200, {'Content-Type': 'text/html'}); res.write('<h1>Node.js</h1>'); res.end('<p>Hello World</p>'); }); server.listen(3004); console.log("port is 3004");
2. http.ServerRequest
http.ServerRequest 是 HTTP 請求的資訊,是後端開發者最關注的內容。它一般由
http.Server 的 request 事件傳送,作為第一個引數傳遞,通常簡稱 request 或 req。 ServerRequest 提供一些屬性.
http.ServerRequest 提供了以下3個事件用於控制請求體 傳輸。
1. data :當請求體資料到來時,該事件被觸發。該事件提供一個引數 chunk,表示接 收到的資料。如果該事件沒有被監聽,
那麼請求體將會被拋棄。該事件可能會被調 用多次。
2. end :當請求體資料傳輸完成時,該事件被觸發,此後將不會再有資料到來。
3. close: 使用者當前請求結束時,該事件被觸發。不同於 end,如果使用者強制終止了傳輸,也還是呼叫close。
3.node.js中的url.parse方法使用說明
該方法的含義是:將URL字串轉換成物件並返回
基本語法:url.parse(urlStr, [parseQueryString], [slashesDenoteHost])
urlStr url字串
parseQueryString 為true時將使用查詢模組分析查詢字串,預設為false
slashesDenoteHost
1. 預設為false,//foo/bar 形式的字串將被解釋成 { pathname: ‘//foo/bar' }
2. 如果設定成true,//foo/bar 形式的字串將被解釋成 { host: 'foo', pathname: '/bar' }
var url = require('url'); var a = url.parse('http://example.com:8080/one?a=index&t=article&m=default'); console.log(a);
列印如下: { protocol: 'http:', slashes: true, auth: null, host: 'example.com:8080', port: '8080', hostname: 'example.com', hash: null, search: '?a=index&t=article&m=default', query: 'a=index&t=article&m=default', pathname: '/one', path: '/one?a=index&t=article&m=default', href: 'http://example.com:8080/one?a=index&t=article&m=default' }
1. node.js中請求如何獲取get請求的內容 我們可以使用上面介紹的 url.parse方法
var http = require('http'); var url = require('url'); var util = require('util'); http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(util.inspect(url.parse(req.url, true))); }).listen(3001);
在瀏覽器執行 http://127.0.0.1:3001/one?a=index&t=article&m=default 這個,
列印如下:
{
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: '?a=index&t=article&m=default',
query: { a: 'index', t: 'article', m: 'default' },
pathname: '/one',
path: '/one?a=index&t=article&m=default',
href: '/one?a=index&t=article&m=default'
}
通過 url.parse,原始的 path 被解析為一個物件,其中 query 就是我們所謂的 GET 請求的內容,而路徑則是 pathname。
2. 如何獲取post請求的內容
程式碼如下:
var http = require('http'); var querystring = require('querystring'); var util = require('util'); http.createServer(function(req, res) { var post = ''; req.on('data', function(chunk) { post += chunk; }); req.on('end', function() { post = querystring.parse(post); res.end(util.inspect(post)); }); }).listen(3002);
定義了一個 post 變數,用 於在閉包中暫存請求體的資訊。通過 req 的 data 事件監聽函式,每當接受到請求體的資料, 就累加到 post 變數中。
在 end 事件觸發後,通過 querystring.parse 將 post 解析為 真正的 POST 請求格式,然後向客戶端返回。
supervisor的使用及nodejs常見的調式程式碼命令瞭解;
1. supervisor的使用
nodejs會在第一次引用到某部分時才會去解析指令碼檔案,之後直接從快取裡面去取,因此對於調式不方面,可以使用安裝supervisor
supervisor 可以幫助你實現這個功能,它會監視你對程式碼的改動,並自動重啟 Node.js
在mac下 安裝命令如下:sudo npm install -g supervisor 執行js命令 直接 supervisor server1.js 即可
2. 使用npm安裝包有二種模式:本地模式和全域性模式;本地模式如下:sudo npm install 包名
全域性模式安裝如下:sudo npm install -g 包名 ,他們之間的區別是:
本地模式:該模式是把包安裝在當前目錄的node_module子目錄下,
Node.js 的require在載入模組時會嘗試搜尋 node_modules子目錄,因此使用npm本地模式安裝 的包可以直接被引用。
全域性模式:為了減少多重副本而使用全域性模式,而是因為本地模式不會註冊 PATH 環境變數。舉例說明,我們安裝 supervisor 是為了在命令列中執行它,
譬如直接執行 supervisor script.js,這時就需要在 PATH 環境變數中註冊 supervisor。npm 本地模式僅僅是把包安裝到 node_modules 子目錄下,
其中 的 bin 目錄沒有包含在 PATH 環境變數中,不能直接在命令列中呼叫。而當我們使用全域性模 式安裝時,npm 會將包安裝到系統目錄,
譬如 /usr/local/lib/node_modules/,同時 package.json 文 件中 bin 欄位包含的檔案會被連結到 /usr/local/bin/。/usr/local/bin/
是在PATH 環境變數中預設 定義的,因此就可以直接在命令列中執行 supervisor script.js命令了。
注意:使用全域性模式安裝的包並不能直接在 JavaScript 檔案中用 require 獲 得,因為 require 不會搜尋 /usr/local/lib/node_modules/。
3. nodejs調式程式碼命令:
進入檔案比如:node1.js;程式碼如下:
var a = 1;
var b = "hello";
var c = function(x) {
console.log('hello' + x + a);
};
c(b);
在命令列中使用命令 node debug node1.js 就可以進行如下調式程式碼:如圖調式程式碼.png
node基本命令如下:
run : 執行指令碼,在第一行暫停
restart:重新執行指令碼
cont, c:繼續執行,直到遇到下一個斷點
next, n:單步執行
step, s:單步執行並進入函式
out, o:從函式中步出
setBreakpoint(), sb(): 在當前行設定斷點
setBreakpoint(‘f()’), sb(...) : 在函式f的第一行設定斷點
setBreakpoint(‘script.js’, 20), sb(...) 在 script.js 的第20行設定斷點
clearBreakpoint, cb(...) 清除所有斷點
backtrace, bt 顯示當前的呼叫棧
list(5) 顯示當前執行到的前後5行程式碼
watch(expr) 把表示式 expr 加入監視列表
unwatch(expr) 把表示式 expr 從監視列表移除
watchers 顯示監視列表中所有的表示式和值
repl 在當前上下文開啟即時求值環境
kill 終止當前執行的指令碼
scripts 顯示當前已載入的所有指令碼
version 顯示 V8 的版本
4. 使用 node-inspector 除錯 Node.js
1。使用 sudo npm install -g node-inspector 命令安裝 node-inspector
瞭解Node核心模組;
1.常用工具 util; util 是一個 Node.js 核心模組,提供常用函式的集合;
var util = require('util');
util.inherits(constructor, superConstructor)是一個實現物件間的原型繼承的函式.
function Base(){ this.name = 'base'; this.base = 1991; this.sayHello = function(){ console.log('hello'+this.name); }; } Base.prototype.showName = function(){ console.log(this.name); } function Sub(){ this.name = 'Sub'; } util.inherits(Sub, Base); var objBase = new Base(); objBase.showName(); // base objBase.sayHello(); // hellobase console.log(objBase); // Base { name: 'base', base: 1991, sayHello: [Function] } var objSub = new Sub(); objSub.showName(); // Sub //objSub.sayHello(); // 報錯,不能繼承該方法 console.log(objSub); // Sub { name: 'Sub' }
2. util.inspect(object,[showHidden],[depth],[colors])是一個將任意物件轉換 為字串的方法,通常用於除錯和錯誤輸出。
它至少接受一個引數 object,即要轉換的物件。
showHidden:是一個可選引數,如果值為true,將會輸出更多隱藏資訊。
depth: 表示最大遞迴的層數,如果物件很複雜,你可以指定層數以控制輸出資訊的多少。如果不指定depth,預設會遞迴2層,
指定為 null 表示將不限遞迴層數完整遍歷物件。
colors: 如果color 值為 true,輸出格式將會以 ANSI 顏色編碼,通常用於在終端顯示更漂亮 的效果。
var Person = function(){ this.person = "kongzhi"; this.toString = function(){ return this.name; } }; var obj = new Person(); console.log(util.inspect(obj)); //{ person: 'kongzhi', toString: [Function] } console.log(util.inspect(obj,true));
輸出如下:
{ person: 'kongzhi', toString:{ [Function][length]: 0, [name]: '',[arguments]: null, [caller]: null, [prototype]: { [constructor]: [Circular] } } }
更多的util的工具函式 請看這裡 https://nodejs.org/api/util.html#util_util_isarray_object
3.事件發射器 events
events 模組只提供了一個物件: events.EventEmitter。EventEmitter 的核心就 是事件發射與事件監聽器功能的封裝。
程式碼如下:
var events = require('events'); var emitter = new events.EventEmitter(); // 註冊自定義事件 emitter.on('someEvent',function(arg1,arg2){ console.log("listen1",arg1,arg2); // listen1 kong zhi }); emitter.on('someEvent',function(arg1,arg2){ console.log('listen2',arg1,arg2); // listen2 kong zhi }); // 觸發事件 emitter.emit('someEvent','kong','zhi');
EventEmitter常用的API:
EventEmitter.on(event, listener) 為指定事件註冊一個監聽器,接受一個字串event 和一個回撥函式listener。
EventEmitter.emit(event, [arg1], [arg2], [...]) 發射 event 事件,傳遞若干可選引數到事件監聽器的參數列。
EventEmitter.once(event, listener) 為指定事件註冊一個單次監聽器,即監聽器最多隻會觸發一次,觸發後立刻解除該監聽器。
EventEmitter.removeListener(event, listener) 移除指定事件的某個監聽器,listener 必須是該事件已經註冊過的監聽器。
EventEmitter.removeAllListeners([event]) 移除所有事件的所有監聽器, 如果指定 event,則移除指定事件的所有監聽器。
EventEmitter 定義了一個特殊的事件 error,它包含了“錯誤”的語義,我們在遇到 異常的時候通常會發射 error 事件。
當 error 被髮射時,EventEmitter 規定如果沒有響 應的監聽器,Node.js 會把它當作異常,退出程式並列印呼叫棧。
我們一般要為會發射 error 事件的物件設定監聽器,避免遇到錯誤後整個程式崩潰。
如下程式碼:
var events = require('events'); var emitter = new events.EventEmitter(); emitter.emit('error');
詳細的請看這裡 https://nodejs.org/api/events.html
4.檔案系統 fs
1. fs.readFile
fs.readFile(filename,[encoding],[callback(err,data)])是最簡單的讀取 檔案的函式。它接受一個必選引數 filename,
表示要讀取的檔名。第二個引數 encoding 是可選的,表示檔案的字元編碼。callback 是回撥函式,用於接收檔案的內容。如果不指定encoding,
則callback就是第二個引數。回撥函式提供兩個引數err和data,err表 示有沒有錯誤發生,data 是檔案內容。如果指定了 encoding,
data 是一個解析後的字元 串,否則 data 將會是以 Buffer 形式表示的二進位制資料。
程式碼如下:
var fs = require('fs'); // 沒有指定encoding , data將會是buffer形式表示的二進位制資料 fs.readFile('text.txt',function(err,data){ if(err) { console.log(err); }else { console.log(data); // <Buffer 61 61 61 64 73 66 64 66 e9 a2 9d e9 a2 9d e8 80 8c e7 aa 81 e7 84 b6 61 61 61 64 73 66 // 64 66 e9 a2 9d e9 a2 9d e8 80 8c e7 aa 81 e7 84 b6 61 61 61 64 ... > } });
fs.readFile('text.txt','utf-8',function(err,data){ if(err) { console.log(err); }else { console.log(data); //aaadsfdf額額而突然aaadsfdf額額而突然aaadsfdf額額而突然aaadsfdf額額而突然aaadsfdf額額而突然aaadsfdf額額而突然 } });
2.fs.readFileSync
fs.readFileSync(filename, [encoding])是 fs.readFile 同步的版本。它接受 的引數和 fs.readFile 相同,
而讀取到的檔案內容會以函式返回值的形式返回。如果有錯 誤發生,fs 將會丟擲異常,你需要使用 try 和 catch 捕捉並處理異常。
3. fs.open
基本語法: fs.open(path, flags, [mode], [callback(err, fd)])是 POSIX open 函式的 封裝,
它接受兩個必選引數,path 為檔案的路徑, flags 可以是以下值。
r :以讀取模式開啟檔案。
r+ :以讀寫模式開啟檔案。
w :以寫入模式開啟檔案,如果檔案不存在則建立。
w+ :以讀寫模式開啟檔案,如果檔案不存在則建立。
a :以追加模式開啟檔案,如果檔案不存在則建立。
a+ :以讀取追加模式開啟檔案,如果檔案不存在則建立。
mode 引數用於建立檔案時給檔案指定許可權,預設是 06661。回撥函式將會傳遞一個檔案描述符 fd
1.檔案許可權指的是 POSIX 作業系統中對檔案讀取和訪問許可權的規範,通常用一個八進位制數來表示。例如 0754 表 示檔案所有者的許可權是 7 (讀、寫、執行),
同組的使用者許可權是 5 (讀、執行),其他使用者的許可權是 4 (讀), 寫成字元表示就是 -rwxr-xr--。
2 檔案描述符是一個非負整數,表示作業系統核心為當前程式所維護的開啟檔案的記錄表索引
程式碼如下:
var fs = require('fs'); fs.open('./text2.txt','r',function(err,fd){ if(err) { console.log(err); }else { console.log(fd); // 10 表示當前目錄的第十個檔案 } });
ejs模板引擎
app.set 是 Express 的引數設定工具,接受一個鍵(key)和一個值(value),可用的引數如下所示
1. basepath:基礎地址,通常用於 res.redirect() 跳轉。
2. views:檢視檔案的目錄,存放模板檔案。
3. view engine:檢視模板引擎。
4. view options:全域性檢視引數物件。
5. view cache:啟用檢視快取。
6. case sensitive routes:路徑區分大小寫。
7. strict routing:嚴格路徑,啟用後不會忽略路徑末尾的“ / ”。
8. jsonp callback:開啟透明的 JSONP 支援。
來看看app.js 中通過以下兩個語句設定了模板引擎和頁面模板的位置.
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
為設定存放模板檔案的路徑,其中__dirname為全域性變數,存放當前指令碼所在目錄
表明要使用的模板引擎是 ejs,頁面模板在 views 子目錄下。
在routes/index.js的函式下通過如下語句渲染模板引擎,程式碼如下:
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get("/")的含義是: 擷取Get請求方式的url中含有/的請求.
res.render 的功能是呼叫模板引擎,並將其產生的頁面直接返回給客戶端。它接受 兩個引數,第一個是模板的名稱,
即 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>
上面程式碼其中有兩處 <%= title %>,用於模板變數顯示,它們在模板翻譯時會被替換 成 Express,因為 res.render 傳遞了 { title: 'Express' }。
ejs 有以下3種標籤:
<% code %>:JavaScript 程式碼。
<%= code %>:顯示替換過 HTML 特殊字元的內容。
<%- code %>:顯示原始 HTML 內容。
對於HTML 頁面的<head>部分以及頁首頁尾中的大量內容是重複的內容,我們可以這樣前面加<%-include header%>,後面加<%-include footer%>.
學會使用片段檢視(partials)
Express 的檢視系統還支援片段檢視(partials),它就是一個頁面的片段,通常是重複的 內容,用於迭代顯示。
通過它你可以將相對獨立的頁面塊分割出去,而且可以避免顯式地使 用 for 迴圈。
1. 安裝express-partials。進入專案的根目錄執行如下命令:
sudo npm install express-partials
2. 下載成功後.在app.js 中引用此外掛 var partials = require(‘express-partials’);
3. 然後再開啟此外掛, 在app.js 中 app.set(‘view engine’, ‘ejs’); 程式碼後新增如下程式碼:
app.use(partials());
下面我們可以來使用片段檢視了 partials, 做一個demo如下:
在 app.js 中新增以下內容:
// 片段檢視 app.get('/list', function(req, res) { res.render('list', { title: 'List', items: [1991, 'byvoid', 'express', 'Node.js'] }); });
在 views 目錄下新建 list.ejs,內容是:
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <ul><%- partial('listitem', items) %></ul> </body> </html>
同時新建 listitem.ejs,內容是:
<li><%= listitem %></li>
重啟後 ,在瀏覽器訪問 http://127.0.0.1:3000/list 即可看到 列表頁面;
在原始碼看到如下程式碼:
<!DOCTYPE html> <html> <head> <title>List</title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <ul><li>1991</li><li>byvoid</li><li>express</li><li>Node.js</li></ul> </body> </html>
partial 是一個可以在檢視中使用函式,它接受兩個引數,第一個是片段檢視的名稱, 第二個可以是一個物件或一個陣列,如果是一個物件,
那麼片段檢視中上下文變數引用的就 是這個物件;如果是一個陣列,那麼其中每個元素依次被迭代應用到片段檢視。片段檢視中
上下文變數名就是檢視檔名,例如上面的'listitem'.
理解檢視助手:
它的功能是允許在檢視中訪問一個全域性的函式 或物件,不用每次呼叫檢視解析的時候單獨傳入。前面提到的 partial就是一個檢視助手。
檢視助手有兩類,分別是靜態檢視助手和動態檢視助手。這兩者的差別在於,靜態檢視 助手可以是任何型別的物件,包括接受任意引數的函式,
但訪問到的物件必須是與使用者請求無 關的,而動態檢視助手只能是一個函式,這個函式不能接受引數,但可以訪問 req 和 res 物件。
如下程式碼:
1.在app.js加入如下程式碼:
// 檢視助手 var util = require('util'); var helper = require('./routes/helper'); app.use('/helper', helper); // dynamic Helper app.locals.inspect = function(obj) { return util.inspect(obj, true); } app.locals.headers = function(req, res) { return req.headers }
2.在routes資料夾下新建一個helper.js,程式碼如下:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res) { res.render('helper', { title: 'Helper', _req: req, _res: res }); }); module.exports = router;
3. 在views檔案下新建helper.ejs;程式碼如下:
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <%=inspect(headers(_req, _res))%> </body> </html> 重啟後,在瀏覽器訪問如下:http://127.0.0.1:3000/helper
Express
如果一個包是某個工程依賴,那麼我們需要在工程的目錄下 使用本地模式安裝這個包,如果要通過命令列呼叫這個包中的命令,則需要用全域性模式安裝.
因此按理說我們使用本地模式安裝 Express 即可。 但是Express 像很多框架一樣都提供了 Quick Start(快速開始)工具,這個工具的功能通常
是建立一個網站最小的基礎框架,為了使用這個工具,我們需要用全域性模式安裝 Express,因為只有這樣我們才能在命令列中使用它。執行以下命令:
1.首先全域性安裝express sudo npm install -g express
上面安裝後,發現使用 express --help 會提示如下:Express Command not found
解決方法:在安裝一個包,使用命令:sudo npm install -g express-generator
原因:express3+已經把建立一個APP的功能分離出來為express-generator,沒它你建立不了應用程式.
我們接著使用 express --help就可以看到很多其他命令了;
2. 通過以下命令建立網站的基本結構: express -t eje microblog
網站的目錄檔名就叫 microblog,會在該目錄下生成該資料夾; 接著執行如下命令:cd microblog && npm install
會自動安裝jade模板引擎和express,而不是ejs模板引擎了,我們可以檢視網站的根目錄中的package.json檔案,內容如下:
{ "name": "microblog", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "body-parser": "~1.13.2", "cookie-parser": "~1.3.5", "debug": "~2.2.0", "express": "~4.13.1", "jade": "~1.11.0", "morgan": "~1.6.1", "serve-favicon": "~2.3.0" } }
解決方法: 版本不一樣,用錯命令了,應該是express -e microblog(-e就是ejs模板)
我們再來檢視一下package.json 內容如下:
{ "name": "microblog", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "body-parser": "~1.13.2", "cookie-parser": "~1.3.5", "debug": "~2.2.0", "ejs": "~2.3.3", "express": "~4.13.1", "morgan": "~1.6.1", "serve-favicon": "~2.3.0" } }
如上看到有ejs了;
3. 在當前的專案目錄下 執行 npm install 把package.json的所有依賴包都生成出到本地專案microblog目錄下,會生成一個node_modules
模組內;
4. 使用node app.js沒有效果,需要使用如下命令即可 npm start
出現情況:訪問不到頁面
解決方法:版本不一樣,用錯命令了,應該是npm start
接著訪問:http://127.0.0.1:3000/ 就可以看到 welcome to express頁面的介面了;
要關閉伺服器的話,在終端中按 Ctrl + C。注意,如果你對程式碼做了修改,要想看到修 改後的效果必須重啟伺服器,
也就是說你需要關閉伺服器並再次執行才會有效果。如果覺得 有些麻煩,可以使用 supervisor 實現監視程式碼修改和自動重啟
5. 下面是我們專案的目前的工程結構如下:
6 瞭解工程的結構;
6-1. app.js
var express = require('express'); // 引入express模組 var path = require('path'); // 引入path模組 var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); // routes 是一個資料夾形式的本地模組,即./routes/index.js,它的功能 是為指定路徑組織返回內容,相當於 MVC 架構中的控制器。 var routes = require('./routes/index'); var users = require('./routes/users'); // 函式建立了一個應用的例項,後面的所有操作都是針對於這個例項進行的 var app = express(); // app.set 是 Express 的引數設定工具,接受一個鍵(key)和一個值(value),可用的參 數如下所示 // 1. basepath:基礎地址,通常用於 res.redirect() 跳轉。 // 2. views:檢視檔案的目錄,存放模板檔案。 // 3. view engine:檢視模板引擎。 // 4. view options:全域性檢視引數物件。 // 5. view cache:啟用檢視快取。 // 6. case sensitive routes:路徑區分大小寫。 // 7. strict routing:嚴格路徑,啟用後不會忽略路徑末尾的“ / ”。 // 8. jsonp callback:開啟透明的 JSONP 支援。 // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); // Express 依賴於 connect,提供了大量的中介軟體,可以通過 app.use 啟用 // 1. bodyParser 的功能是解析客戶端請求,通常是通過 POST 傳送的內容。 // 2. router 是專案的路由支援。 // 3. static 提供了靜態檔案支援。 // 4. errorHandler 是錯誤控制器。 // 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('/', routes); 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 handlers // development error handler // will print stacktrace if (app.get('env') === 'development') { app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: err }); }); } // production error handler // no stacktraces leaked to user app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: {} }); }); module.exports = app;
2. routes/index.js
routes/index.js 是路由檔案,相當於控制器,用於組織展示的內容:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next) { res.render('index', { title: 'Express' }); }); module.exports = router; // 上面的程式碼 router.get('/', function(req, res, next) { res.render('index', { title: 'Express' }); });
其中只有一個語句 res.render('index', { title: 'Express' }),功能是 呼叫模板解析引擎,翻譯名為 index 的模板,
並傳入一個物件作為引數,這個物件只有一個 2 屬性,即 title: 'Express'。
3. index.ejs
index.ejs 是模板檔案,即 routes/index.js 中呼叫的模板,內容是:
<!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>
其中包含了形如 <%= title %> 的標籤,功能是顯示引用的 變數,即 res.render 函式第二個引數傳入的物件的屬性。
4. 在bin目錄下有一個檔案www,內容如下:
// Module dependencies. var app = require('../app'); var debug = require('debug')('microblog:server'); var http = require('http'); // Get port from environment and store in Express. var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); // Create HTTP server. var server = http.createServer(app); // Listen on provided port, on all network interfaces. server.listen(port); server.on('error', onError); server.on('listening', onListening); // Normalize a port into a number, string, or false. function normalizePort(val) { var port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; } //Event listener for HTTP server "error" event. function onError(error) { if (error.syscall !== 'listen') { throw error; } var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; } } // Event listener for HTTP server "listening" event. function onListening() { var addr = server.address(); var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); }
上面的程式碼最主要做的工作時,建立一個http伺服器,並且監聽預設的埠號是3000;
express()內建方法理解如下:
1.express()用來建立一個Express的程式。express()方法是express模組匯出的頂層方法。如下程式碼:
var express = require('express'); var app = express();
內建方法如下:
1. express.static(root, [options]):
express.static是Express中唯一的內建中介軟體。它以server-static模組為基礎開發,負責託管 Express 應用內的靜態資源。
1. 引數root為靜態資源的所在的根目錄。
例如,假設在 public 目錄放置了圖片、CSS 和 JavaScript 檔案,你就可以:
app.use(express.static(path.join(__dirname, 'public')));
現在,public 目錄下面的檔案就可以訪問了;比如如下css檔案:
http://127.0.0.1:3000/stylesheets/style.css
注意: 所有檔案的路徑都是相對於存放目錄的(這裡存放目錄是public),因此,存放靜態檔案的目錄名不會出現在 URL 中。
如果你的靜態資源存放在多個目錄下面,你可以多次呼叫 express.static 中介軟體:如下程式碼:
app.use(express.static('public'));
app.use(express.static('files'));
訪問靜態資原始檔時,express.static 中介軟體會根據目錄新增的順序查詢所需的檔案.
如果你希望所有通過 express.static 訪問的檔案都存放在一個“虛擬(virtual)”目錄(即目錄根本不存在)下面,
可以通過為靜態資源目錄指定一個掛載路徑的方式來實現,如下所示:
app.use('/static', express.static('public'));
比如如下css檔案:
http://127.0.0.1:3000/static/stylesheets/style.css
理解路由控制
1. 工作原理:
訪問 http://localhost:3000/,瀏覽器會向伺服器傳送請求,app 會 解析請求的路徑,呼叫相應的邏輯。
app.use('/', routes); 它的作用是routes資料夾下 規定路徑為“/” 預設為index,而index.js程式碼如下:
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
通 過 res.render('index', { title: 'Express' }) 呼叫檢視模板 index,傳遞 title 變數。最終檢視模板生成 HTML 頁面,返回給瀏覽器.
返回內容如下:
<!DOCTYPE html> <html> <head> <title>Express</title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1>Express</h1> <p>Welcome to Express</p> </body> </html>
瀏覽器在接收到內容以後,經過分析發現要獲取 /stylesheets/style.css,因此會再次向服 務器發起請求。app.js 中並沒有一個路由規則
指派到 /stylesheets/style.css,但 app 通過 app.use(express.static(path.join(__dirname, 'public'))) 配置了靜態檔案伺服器,
因此 /stylesheets/style.css 會定向到 app.js 所在目錄的子目錄中的檔案 public/stylesheets/style.css.
這是一個典型的 MVC 架構,瀏覽器發起請求,由路由控制器接受,根據不同的路徑定 向到不同的控制器。控制器處理使用者的具體請求,
可能會訪問資料庫中的物件,即模型部分。控制器還要訪問模板引擎,生成檢視的 HTML,最後再由控制器返回給瀏覽器,完成
一次請求。
2. 理解路由規則:
當我們在瀏覽器中訪問譬如 http://127.0.0.1:3000/a 這樣不存在的頁面時,伺服器會在響應頭中返回 404 Not Found 錯誤,瀏覽器顯示如圖
這是因為 /a 是一個不存在的路由規則.
如何建立路由規則?
假設我們要建立一個地址為 /hello 的頁面,內容是當前的伺服器時間,讓我們看看具 體做法。開啟 app.js;
1.在route下新建一個檔案hello.js檔案;程式碼如下:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next) { res.send('The time is ' + new Date().toString()); }); module.exports = router;
2. 在app.js頭部引入hello模組;新增如下程式碼:
var hello = require('./routes/hello');
3. 在已有的路由規則 app.use('/users', users); 後面新增一行:
app.use('/hello', hello);
重啟伺服器,現在我們就可以在瀏覽器下 訪問 http://127.0.0.1:3000/hello 就可以看到當前伺服器的時間了.
3. 理解路徑匹配
Express 還支援更高階的路徑匹配模式。例 如我們想要展示一個使用者的個人頁面,路徑為 /users/[username],可以用下面的方法定義路由 規則:
在app.js加入如下程式碼:
// 路徑匹配 app.get('/users/:username', function(req, res) { res.send('user: ' + req.params.username); });
重啟,瀏覽器訪問http://127.0.0.1:3000/users/aa 後,列印如下:
路徑規則/users/:username會被自動編譯為正規表示式,類似於\/users\/([^\/]+)\/? 這樣的形式。
路徑引數可以在響應函式中通過 req.params 的屬性訪問。
4. 理解REST 風格的路由規則
Express 支援 REST 風格的請求方式,REST 的 意思是 表徵狀態轉移(Representational State Transfer),它是一種基於 HTTP 協議的網路應用
的介面風格,充分利用 HTTP 的方法實現統一風格介面的服務。HTTP 協議定義了以下8 種標準的方法。
1. GET:請求獲取指定資源。
2. HEAD:請求指定資源的響應頭。
3. POST:向指定資源提交資料。
4. PUT:請求伺服器儲存一個資源。
5. DELETE:請求伺服器刪除指定資源。
6. TRACE:回顯伺服器收到的請求,主要用於測試或診斷。
7. CONNECT:HTTP/1.1 協議中預留給能夠將連線改為管道方式的代理伺服器。
8. OPTIONS:返回伺服器支援的HTTP請求方法。
根據 REST 設計模式,GET、POST、PUT 和 DELETE 方法通常分別用於實現以下功能。
GET:獲取
POST:新增
PUT:更新
DELETE:刪除
一:理解控制權轉移
Express 支援同一路徑繫結多個路由響應函式,我們在app.js程式碼加入如下程式碼;例如:
app.all('/user/:username', function(req, res) { res.send('all methods captured'); }); app.get('/user/:username', function(req, res) { res.send('user: ' + req.params.username); });
但當你訪問任何被這兩條同樣的規則匹配到的路徑時,會發現請求總是被前一條路由規 則捕獲,後面的規則會被忽略。
原因是 Express 在處理路由規則時,會優先匹配先定義的路 由規則,因此後面相同的規則被遮蔽。
Express 提供了路由控制權轉移的方法,即回撥函式的第三個引數next,通過呼叫 next(),會將路由控制權轉移給後面的規則,例如:
程式碼改為如下:
app.all('/user/:username', function(req, res,next) { console.log('all methods captured'); next(); }); app.get('/user/:username', function(req, res) { res.send('user: ' + req.params.username); });
上面的 app.all 函式,它支援把所有的請求方式繫結到同一個響應函式.
我們發現當訪問被匹配到的路徑時,如:http://127.0.0.1:3000/user/aa 會發現終端中列印了 all methods captured,
而且瀏覽器中顯示了 user:aa。這說明請求先被第一條路由規則捕獲,完成 console.log 使用 next() 轉移控制權,
又被第二條規則捕獲,向瀏覽器 返回了資訊。
這是一個非常有用的工具,可以讓我們輕易地實現中介軟體,而且還能提高程式碼的複用程度。例如我們針對一個使用者查詢資訊和修改資訊的操作,
分別對應了 GET 和 PUT 操作,而 兩者共有的一個步驟是檢查使用者名稱是否合法,因此可以通過 next() 方法實現:
在app.js新增如下程式碼:
// 控制權轉移 測試 var users = { 'cc2': { name: 'Carbo', website: 'http://www.byvoid.com' } }; app.all('/user2/:username', function(req, res, next) { // 檢查使用者是否存在 if (users[req.params.username]) { next(); } else { next(new Error(req.params.username + ' does not exist.')); } }); app.get('/user2/:username', function(req, res) { // 使用者一定存在,直接展示 res.send(JSON.stringify(users[req.params.username])); }); app.put('/user2/:username', function(req, res) { // 修改使用者資訊 res.send('Done'); });
當在瀏覽器訪問 http://127.0.0.1:3000/user2/cc2 會列印出一個物件出來,否則錯誤訊息提示;
如上: app.all 定義的這個路由規則實際上起到了中介軟體的作用,把相似請求 的相同部分提取出來,有利於程式碼維護其他next方法如果接受了引數,
即代表發生了錯誤。 使用這種方法可以把錯誤檢查分段化,降低程式碼耦合度。
學習使用node建立微博網站
一:功能設計點如下:
1. 微博應該以使用者為中心,因此需要有使用者的註冊和登入功能。
2. 微博網站最核心的功能是資訊的發表,這個功能涉及許多 方面,包括資料庫訪問、前端顯示等。
二:路由規劃
根據功能設計,我們把路由按照以下方案規劃。
1. /:首頁
2. /u/[user]:使用者的主頁
3. /post:發表資訊
4. /reg:使用者註冊
5. /login:使用者登入
6. /logout:使用者登出
三:程式碼設計如下:
開啟index.js, 把 Routes 部分修改為:
router.get('/', function(req, res) {});
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 由於要接受表單資訊,因此使用 app.post 註冊路由。/login 和 /reg
還要顯示使用者註冊時要填寫的表單,所以要以 app.get 註冊。同時在 routes/index.js 中新增相應的函式.
介面和模板引擎劃分如下:
首頁:index.ejs
使用者首頁:user.ejs
登陸頁面:login.ejs
註冊頁面:reg.ejs
公用頁面:
header.ejs(頂部導航條)、
alert.ejs(頂部下方錯誤資訊顯示)、
footer(底部顯示)、
say.ejs(釋出微博)、
posts.ejs(按行按列顯示已釋出的微博)
2. 先安裝mongodb資料庫
1. 登入網站 https://www.mongodb.org/ 下載mongodb資料庫.下載後的檔案命名為mongodb.
2. 進入mongodb的根目錄 在終端輸入: sudo mkdir -p /data/db (建立/data/db目錄)
3. 在終端輸入:sudo chown -R 你的系統登入使用者名稱 /data/db
4. 進入mongodb 的 "bin"目錄,使用命令 “./mongod” 啟動mongoDB server,
啟動成功後最後一行應該是埠號,如配圖,出現配圖就能肯定你的Mongodb已經安裝成了
5. 新建終端標籤,進入mongodb/bin目錄下 並輸入./mongo 登陸到資料庫; 如下圖所示:
3. 為了在Node.js中使用MongDB資料庫,需要安裝mongodb模組,開啟package.json檔案,在dependencies屬性中新增一行程式碼,即:
"mongodb":">=1.4.8",接著進入專案的根目錄 執行"npm install"命令更新依賴的模組。
接下來在專案的根目錄下建立settings.js檔案,該檔案用於儲存資料庫資訊,包括資料庫名稱、資料庫地址和cookieSecret等,
settings.js檔案程式碼如下:
module.exports={ cookieSecret:'microblogKongzhi',//用於cookie加密,與資料庫無關 db:'microblog',//資料庫名稱 host:'127.0.0.1' //資料庫地址 };
接下來在專案根目錄下建立models資料夾,並在models資料夾下建立db.js檔案,該檔案用於建立資料庫連線,程式碼如下:
var settings = require('../settings'), //載入儲存資料庫基本資訊的模組 Db = require('mongodb').Db, //載入MongDB資料庫依賴模組,並呼叫相關物件 Server = require('mongodb').Server; //設定資料庫名稱、資料庫地址和資料庫預設埠號建立一個資料庫例項,然後通過module.exports輸出建立的資料庫連線 module.exports = new Db(settings.db, new Server(settings.host,27017),{safe: true}); //mongodb資料庫伺服器的預設埠號:27017
4. 啟動mongDb報如下錯誤: couldn't connect to server 127.0.0.1:27017 src/mongo/shell/mongo.js
mongdb啟動的時候會報如上標題的錯誤,如下圖錯誤:
1、若資料庫出現如上不能連線的原因,可能是data目錄下的mongod.lock檔案問題,可以用如下命令修復:
進入bin目錄下,執行如下命令進行修復:
./mongod --repair
或者直接刪除mongod.lock;如下命令:
rm -f /usr/local/mongodb/data/db/mongod.lock
然後再啟動mongodb;
4. 會話支援
會話是一種持久的網路協議,用於完成伺服器和客戶端之間的一些互動行為。比如客戶端cookie;Cookie 是一些儲存在客戶端的資訊,
每次連線的時候由瀏覽器向伺服器遞交,伺服器也向瀏覽器發起儲存 Cookie 的 請求,依靠這樣的手段伺服器可以識別客戶端。
具體來說,瀏覽器首次向伺服器發起請求時,伺服器生成一個唯一識別符號併傳送給 客戶端瀏覽器,瀏覽器將這個唯一識別符號儲存在 Cookie 中,
以後每次再發起請求,客戶端 瀏覽器都會向伺服器傳送這個唯一識別符號,伺服器通過這個唯一識別符號來識別使用者。
為了實現把會話資訊儲存於資料庫這一功能,需要安裝express-session和connect-mongo兩個依賴包,開啟package.json檔案,
在dependencies屬性中新增程式碼,即:
"connect-mongo": ">=0.1.7",
"express-session": "~1.0.4",
然後執行 "npm install" 命令更新依賴的模組。接下來開啟app.js檔案,新增如下程式碼:
//使用時新新增的,上面的依賴包是建立檔案時自帶的。 var settings = require('./settings');//資料庫連線依賴包 //session會話儲存於資料庫依賴包(與教程中的區別) var session = require('express-session');//session使用 var MongoStore = require('connect-mongo')(session);//mongodb使用 //提供session支援(與教程中的區別) app.use(session({ secret: settings.cookieSecret, key: settings.db, //cookie name cookie: {maxAge: 1000 * 60 * 60 * 24 * 30},//30 days resave: false, saveUninitialized: true, store: new MongoStore({ url: 'mongodb://127.0.0.1/'+settings.db })}));
注意:1.新新增到app.js的程式碼與教程中的程式碼的區別。由於新版本的express不再支援session包,需要自己安裝express-session包,
因此需要先新增“express-session”包的引用,再把該引用作為引數傳遞給“connect-mongo”引用;同時針對提供session支援的程式碼也要稍作修改。
如果使用書中的程式碼將會報錯。
2. url: 'mongodb://127.0.0.1/'+settings.db 一定要改成127.0.0.1 ,不能是localhost,否則會報如下錯誤:
5. 使用者註冊功能模組;
1. 首先需要在app.js 載入路由控制, 程式碼如下:
var routes = require('./routes/index');
2. 接著需要在app.js 中定義匹配路由;程式碼如下:
app.use('/', routes); //指向了routes目錄下的index.js檔案
3. 在routes檔案下的index.js程式碼如下:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res) { res.render('index', { title: '主頁' }); }); router.get('/reg', function(req, res) { res.render('reg', {title: '使用者註冊', }); }); module.exports = router;
4. 看下注冊模組reg.ejs程式碼如下:
<%- include header.ejs %> <form method="post"> <h3 class="title">使用者註冊</h3> <div class="line"> <label for="uname">姓名:</label> <input type="text" id="name" name="username"/> </div> <div class="line"> <label for="upwd">密碼:</label> <input type="password" id="pwd" name="userpwd"/> </div> <div class="line"> <label for="upwd2">確認密碼:</label> <input type="password" id="upwd2" name="pwdrepeat"/> </div> <div class="line"> <button type="submit" id="btnreg">註冊</button> </div> </form> <%- include footer.ejs %> 其中header.ejs程式碼如下: <!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css'/> </head> <body> <div id="header"> <ul id="menus"> <li><a href="/" class="menu introduce">簡易部落格</a></li> <li><a href="/" class="menu">首頁</a></li> <li><a href="/login" class="menu">登陸</a></li> <li><a href="/reg" class="menu">註冊</a></li> </ul> </div> <div id="content"> footer.ejs程式碼如下: </div> <div id="footer">銅板街歡迎你~</div> </body> </html>
我們重慶伺服器 進入專案的根目錄 執行命令 npm start
在瀏覽器下訪問 http://127.0.0.1:3000/reg 可以看到如下介面:
5. 首先需要安裝connect-flash, 我們直接在package.json加入connect-flash依賴項; 程式碼如下:
"connect-flash": "*", 然後直接進入專案的根目錄後 執行命令 npm install命令,把依賴項載入出來.
作用是: 儲存的變數只會在使用者當前 和下一次的請求中被訪問,之後會被清除,通過它我們可以很方便地實現頁面的通知
和錯誤資訊顯示功能。因為使用者註冊功能需要實現"顯示註冊成功或錯誤的資訊"的功能,所以需要使用它.
6. 開啟app.js檔案,新增如下程式碼:
//引入 flash 模組來實現頁面通知
var flash = require('connect-flash');
app.use(flash());//定義使用 flash 功能
實現使用者註冊功能,即實現使用者註冊處理post請求的功能。
6-1 使用者模型
首先我們在models資料夾下 新建user.js檔案,該檔案的作用把新使用者註冊資訊儲存於資料庫,以及從資料庫讀取指定使用者的資訊的功能.
User 是一個描述資料的物件,即 MVC 架構中的模型。模型是與資料打交道的工具
user.js程式碼如下:
該檔案的功能有2點:
1. 把新使用者註冊資訊儲存於資料庫。
2. 從資料庫讀取指定使用者的資訊的功能.
程式碼如下:
var mongodb = require('./db');//載入資料庫模組 //User建構函式,用於建立物件 function User(user) { this.name = user.name; this.password = user.password; }; //User物件方法:把使用者資訊存入Mongodb User.prototype.save = function(callback){ var user = { //使用者資訊 name: this.name, password: this.password }; // 開啟資料庫 mongodb.open(function(err, db) { if (err) { return callback(err); } //讀取users集合,users相當於資料庫中的表 db.collection('users', function(err, collection) {//定義集合名稱users if (err) { mongodb.close(); return callback(err); } //把user物件中的資料,即使用者註冊資訊寫入users集合中 collection.insert(user, {safe: true}, function(err, user) { mongodb.close(); callback(err, user); }); }); }) }; //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); } //從users集合中查詢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物件 module.exports = User;
6-2 路由轉發-當做控制器,放在routes資料夾下的index.js編寫
index.js中的程式碼,引入依賴模組,完善"router.post('/reg', function (req, res) {});"程式碼,程式碼修改如下:
var express = require('express'); var router = express.Router(); //載入生成MD5值依賴模組 var crypto = require('crypto');// 加密和解密模組 var User = require('../models/user'); router.get('/', function(req, res) { res.render('index', { title: '主頁' }); }); router.get('/reg', function(req, res) { res.render('reg', {title: '使用者註冊', }); }); router.post('/reg', function(req, res) { // 使用者名稱 和 密碼 不能為空 if (req.body.username == "" || req.body.userpwd == "" || req.body.pwdrepeat == "") { //使用req.body.username獲取提交請求的使用者名稱,username為input的name req.flash('error', "輸入框不能為空!"); return res.redirect('/reg');//返回reg頁面 } // 兩次輸入的密碼不一致,提示資訊 if(req.body.userpwd != req.body.pwdrepeat) { req.flash("error", '兩次輸入密碼不一致!'); return res.redirect('/reg'); } //把密碼轉換為MD5值 var md5 = crypto.createHash('md5'); var password = md5.update(req.body.userpwd).digest('base64'); //用新註冊使用者資訊物件例項化User物件,用於儲存新註冊使用者和判斷註冊使用者是否存在 var newUser = new User({ name: req.body.username, password: password, }); // 檢查使用者是否存在 User.get(newUser.name,function(err,user){ // 如果使用者存在的話 if (user) { err = 'Username already exists.'; } if (err) { req.flash('error', err);//儲存錯誤資訊,用於介面顯示提示 return res.redirect('/reg'); } // 使用者不存在的時候 儲存使用者 newUser.save(function(err){ if (err) { req.flash('error', err); return res.redirect('/reg'); } req.session.user = newUser;//儲存使用者名稱,用於判斷使用者是否已登入 req.flash('success', req.session.user.name + '註冊成功'); res.redirect('/'); }); }); }); module.exports = router;
理解上面程式碼的知識點:
1. req.body 就是 POST 請求資訊解析過後的物件,例如我們要訪問使用者傳遞的password 域的值,只需訪問req.body['password'] 即可。
2. req.flash 是 Express 提供的一個奇妙的工具,通過它儲存的變數只會在使用者當前 和下一次的請求中被訪問,之後會被清除,
通過它我們可以很方便地實現頁面的通知和錯誤資訊顯示功能。
3. res.redirect 是重定向功能,通過它會向使用者返回一個 303 See Other 狀態,通知瀏覽器轉向相應頁面。
4. crypto 是 Node.js 的一個核心模組,功能是加密並生成各種雜湊,使用它之前首先要宣告 var crypto = require('crypto')。
我們程式碼中使用它計算了密碼的雜湊值。
5. User 是我們設計的使用者物件,用前需要通過 var User = require('../models/user') 引用。
6. User.get 的功能是通過使用者名稱獲取已知使用者,在這裡我們判斷使用者名稱是否已經存 在。User.save 可以將使用者物件的修改寫入資料庫。
7. 通過 req.session.user = newUser 向會話物件寫入了當前使用者的資訊,在後面 我們會通過它判斷使用者是否已經登入。
6-3 檢視互動
為了實現使用者不同登入狀態下顯示不同的頁面的成功和錯誤等提示資訊,我們需要建立檢視助手,在檢視中獲取session中
的資料和要顯示的錯誤或成功的資訊,在app.js中新增如下程式碼:
切記:需要在 app.use(flash());下新增下面的程式碼;要先連結資料庫,否則會報錯;
程式碼如下:
// 為了實現使用者不同登入狀態下顯示不同的頁面成功或者錯誤提示資訊 app.use(function(req,res,next){ //res.locals.xxx實現xxx變數全域性化,在其他頁面直接訪問變數名即可 //訪問session資料:使用者資訊 res.locals.user = req.session.user; //獲取要顯示錯誤資訊 var error = req.flash('error');//獲取flash中儲存的error資訊 res.locals.error = error.length ? error : null; //獲取要顯示成功資訊 var success = req.flash('success'); res.locals.success = success.length ? success : null; next();//控制權轉移,繼續執行下一個app。use() }); //定義匹配路由 app.use('/', routes); //指向了routes目錄下的index.js檔案
app.locals物件是一個javascript物件,它的屬性就是程式本地的變數。一旦設定,app.locals的各屬性值將貫穿程式的整個生命週期.
在程式中,你可以在渲染模板時使用這些本地變數。它們是非常有用的,可以為模板提供一些有用的方法,以及app級別的資料。
通過req.app.locals,Locals可以在中介軟體中使用.
reg.ejs程式碼如下:
<%- include header.ejs %> <%- include alert.ejs %> <form method="post"> <h3 class="title">使用者註冊</h3> <div class="line"> <label for="uname">姓名:</label> <input type="text" id="name" name="username"/> </div> <div class="line"> <label for="upwd">密碼:</label> <input type="password" id="pwd" name="userpwd"/> </div> <div class="line"> <label for="upwd2">確認密碼:</label> <input type="password" id="upwd2" name="pwdrepeat"/> </div> <div class="line"> <button type="submit" id="btnreg">註冊</button> </div> </form> <%- include footer.ejs %>
header.ejs程式碼如下:
<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css'/> </head> <body> <div id="header"> <ul id="menus"> <li><a href="/" class="menu introduce">簡易部落格</a></li> <li><a href="/" class="menu">首頁</a></li> <% if(!user) { %> <li><a href="/login" class="menu">登陸</a></li> <li><a href="/reg" class="menu">註冊</a></li> <% }else{ %> <li><a href="/logout" class="menu">退出</a></li> <% } %> </ul> </div> <div id="content">
alert.ejs程式碼如下:
<!--顯示成功或錯誤資訊--> <% if(success){ %> <div class="alert"> <%= success %> </div> <% } %> <% if(error){ %> <div class="alert"> <%= error %> </div> <% } %>
例如:使用者註冊時,兩次密碼輸入不一致,點選“註冊”按鈕後的介面如下圖所示
6. 使用者登入模組
1. 開啟index.js檔案,完善“router.post('/login', function (req, res) {});”程式碼塊,程式碼如下:
router.post('/login', function(req, res) { //生成口令的雜湊值 var md5 = crypto.createHash('md5'); var password = md5.update(req.body.password).digest('base64'); //判斷使用者名稱和密碼是否存在和正確 User.get(req.body.username,function(err,user){ if(!user) { req.flash('error', '使用者名稱不存在'); return res.redirect('/login'); } if(user.password != password) { req.flash('error', '使用者密碼不存在'); return res.redirect('/login'); } // 儲存使用者資訊 req.session.user = user; req.flash("success","登入成功"); res.redirect('/'); }); });
7. 使用者退出功能
開啟index.js檔案,完善“router.get('/logout', function (req, res) {});”程式碼塊,程式碼如下:
router.get('/logout', function(req, res) { req.session.user = null;//清空session req.flash('sucess', '退出成功!'); res.redirect('/'); });
8. 頁面許可權控制
登陸和註冊功能只對未登入的使用者有效;釋出和退出功能只對已登入的使用者有效。如何實現頁面許可權控制呢?我們可以把使用者登入狀態檢查放到路由中間
件中,在每個路徑前增加路由中介軟體,通過呼叫next()函式轉移控制權,即可實現頁面許可權控制。因此在index.js中新增checkNotLogin和checkLogin
函式來檢測是否登陸,並通過next()轉移控制權,檢測到未登入則跳轉到登入頁,檢測到已登入則跳轉到前一個頁。
checkNotLogin該函式使用在使用者註冊和使用者登入上,checkLogin函式使用在釋出和退出功能上;
我們在index.js假如如下兩個函式的程式碼:
function checkNotLogin(req,res,next){ // 如果從session裡面獲取使用者已存在的話 if (req.session.user) { req.flash('error', '已登入'); return res.redirect('/'); } next(); //控制權轉移:當不同路由規則向同一路徑提交請求時,在通常情況下,請求總是被第一條路由規則捕獲, // 後面的路由規則將會被忽略,為了可以訪問同一路徑的多個路由規則,使用next()實現控制權轉移。 } function checkLogin(req,res,next){ if (!req.session.user){ req.flash('error', '未登入'); return res.redirect('/login'); } //已登入轉移到下一個同一路徑請求的路由規則操作 next(); }
註冊和登陸基本的實現邏輯如下:
1. 當我們訪問http://127.0.0.1:3000/reg 這個網址的時候,會進入註冊頁面;通過下面的程式碼轉向 reg.ejs頁面去;
// 使用者註冊
router.get('/reg', checkNotLogin);//頁面許可權控制,註冊功能只對未登入使用者可用
router.get('/reg', function(req, res) {
res.render('reg', {title: '使用者註冊', });
});
2. 當使用者點選註冊按鈕的時候;通過如下程式碼判斷及跳轉;
router.post('/reg', checkNotLogin);
router.post('/reg', function(req, res) {
// 使用者名稱 和 密碼 不能為空
if (req.body.username == "" || req.body.userpwd == "" || req.body.pwdrepeat == "") {
//使用req.body.username獲取提交請求的使用者名稱,username為input的name
req.flash('error', "輸入框不能為空!");
return res.redirect('/reg');//返回reg頁面
}
// 兩次輸入的密碼不一致,提示資訊
if(req.body.userpwd != req.body.pwdrepeat) {
req.flash("error", '兩次輸入密碼不一致!');
return res.redirect('/reg');
}
//把密碼轉換為MD5值
var md5 = crypto.createHash('md5');
var password = md5.update(req.body.userpwd).digest('base64');
//用新註冊使用者資訊物件例項化User物件,用於儲存新註冊使用者和判斷註冊使用者是否存在
var newUser = new User({
name: req.body.username,
password: password,
});
// 檢查使用者是否存在
User.get(newUser.name,function(err,user){
if (user) {//使用者名稱存在
req.flash('error', 'Username already exists.');//儲存錯誤資訊,用於介面顯示提示
return res.redirect('/reg');
}
// 使用者不存在的時候 儲存使用者
newUser.save(function(err){
if (err) {
req.flash('error', err);
return res.redirect('/reg');
}
req.session.user = newUser; //儲存使用者名稱,用於判斷使用者是否已登入
req.flash('success', req.session.user.name + '註冊成功');
res.redirect('/');
});
});
});
首先通過判斷使用者名稱和密碼是否為空等操作;如果為空的話或者任何通過flash儲存資訊的話,都是通過如下的思路去轉向頁面的.如下步驟:
通過flash這句程式碼 req.flash('error', "輸入框不能為空!");提示;
之後會呼叫app.js程式碼中的如下程式碼:
// 為了實現使用者不同登入狀態下顯示不同的頁面成功或者錯誤提示資訊
app.use(function(req,res,next){
//res.locals.xxx實現xxx變數全域性化,在其他頁面直接訪問變數名即可
//訪問session資料:使用者資訊
res.locals.user = req.session.user;
//獲取要顯示錯誤資訊
var error = req.flash('error');//獲取flash中儲存的error資訊
res.locals.error = error.length ? error : null;
//獲取要顯示成功資訊
var success = req.flash('success');
res.locals.success = success.length ? success : null;
next();//控制權轉移,繼續執行下一個app。use()
});
//定義匹配路由
app.use('/', routes); //指向了routes目錄下的index.js檔案
然後就轉向與index.js了,如下程式碼:
router.get('/', function(req, res) {
res.render('index', { title: '主頁' });
});
之後就是渲染index.ejs模板了;程式碼如下:
<%- include header.ejs %>
<%- include alert.ejs %>
<% if(!user){ %>
<div id="toppart">
<h2>歡迎來到簡易部落格</h2>
<p>該部落格基於node.js+express+mongoDB來實現的。</p>
<br/>
<a href="/login" class="btn">登陸</a>
<a href="/reg" class="btn">註冊</a>
</div>
<% }else{ %>
<%- include say.ejs %>
<% } %>
<%- include footer.ejs %>
首先index.ejs包括頭部和尾部 及 錯誤資訊alert.ejs模板,該模板有2個提示資訊,如果有error資訊的話,就提示error資訊;
否則的話,提示success的資訊, 該error或者success是通過 app.js中的 req.flash('success')獲取的,然後通過
res.locals.success 當做全域性變數儲存取來.然後再alert.ejs通過success或者error值判斷,有就提示訊息即可;
上面的時index.ejs模板如果使用者沒有登入的話,就提示使用者登入註冊頁面,如果已經登入成功的話,就到say.ejs模板去,讓使用者發表留言:
如下頁面:
9. 發表微博功能
為了實現釋出微博功能,首先建立Post物件,在models資料夾下建立post.js檔案,該檔案功能與user.js功能類似,用於儲存新發布的微博及查詢全部
或指定使用者的微博,post.js程式碼如下:
//獲取微博和儲存微博 var mongodb = require('./db'); //Post建構函式,用於建立物件 function Post(username, content, time) { this.user = username;//使用者名稱 this.content = content;//釋出內容 if (time) { this.time = time;//釋出時間 }else { var now=new Date(); this.time =now.getFullYear()+"/"+(now.getMonth()+1)+"/"+now.getDate()+" "+now.getHours()+":"+now.getSeconds(); } } //輸出Post物件 module.exports = Post; //物件方法:儲存新發布的微博到資料庫 Post.prototype.save = function(callback){ //存入MongoDB資料庫 var post = { user: this.user, post: this.content, time: this.time }; mongodb.open(function (err, db) { if (err) { return callback(err); } //讀取posts集合,即資料庫表 db.collection('posts', function (err, collection) { if (err) { mongodb.close(); return callback(err); } //為user屬性新增索引 collection.ensureIndex('user'); //把釋出的微博資訊post寫入posts表中 collection.insert(post, {safe: true}, function (err, post) { mongodb.close(); callback(err, post); }); }); }); } //獲取全部或指定使用者的微博記錄 Post.get = function get(username, callback) { mongodb.open(function (err, db) { if (err) { return callback(err); } //讀取posts集合 db.collection('posts', function (err, collection) { if (err) { mongodb.close(); return callback(err); } //查詢user屬性為username的微博記錄,如果username為null則查詢全部記錄 var query = {}; if (username) { query.user = username; } //查詢符合條件的記錄,並按時間順序排列 collection.find(query).sort({time: -1}).toArray(function (err, docs) { mongodb.close(); if (err) { callback(err, null); } var posts = []; //遍歷查詢結果 docs.forEach(function (doc, index) { //把結果封裝成Post物件 var post = new Post(doc.user, doc.post, doc.time); //把全部結果封裝成陣列 posts.push(post); }); callback(null, posts); }); }); }); };
9-2. 開啟index.js,引入依賴包和完善釋出微博功能的程式碼如下:
var Post = require("../models/post.js");//載入使用者發表微博模組 //發表資訊 router.post('/post', checkLogin);//頁面許可權控制 //發表微博 router.post('/post', function (req, res) { //路由規則/post var currentUser = req.session.user; //獲取當前使用者資訊 if(req.body.post == ""){ //釋出資訊不能為空 req.flash('error', '內容不能為空!'); return res.redirect('/u/' + currentUser.name); } //例項化Post物件 var post = new Post(currentUser.name, req.body.post);//req.body.post獲取使用者發表的內容 //呼叫例項方法,發表微博,並把資訊儲存到MongoDB資料庫 post.save(function (err) { if (err) { req.flash('error', err); return res.redirect('/'); } req.flash('success', '發表成功'); res.redirect('/u/' + currentUser.name); }); }); // 使用者頁面的功能是顯示該使用者發表的所有微博資訊 router.get('/u/:user', function(req, res) { User.get(req.params.user, function (err, user) { //判斷使用者是否存在 if (!user) { req.flash('error', '使用者不存在'); return res.redirect('/'); } //呼叫物件的方法使用者存在,從資料庫獲取該使用者的微博資訊 Post.get(user.name, function (err, posts) { if (err) { req.flash('error', err); return res.redirect('/'); } //呼叫user模板引擎,並傳送資料(使用者名稱和微博集合) res.render('user', { title: user.name, posts: posts }); }); }); });
實現上面發表微博的總體思路如下:
1. 首先在say.ejs模板程式碼如下:
<form method="post" action="/post" id="public">
<input type="text" name="post" id="pctn"/>
<button type="submit" id="pbtn">釋出</button>
</form>
當我輸入框輸入內容的時候,點選發布的時候,會首先路由到index.js程式碼中的
router.post('/post', checkLogin);//頁面許可權控制
首先判斷使用者是否登入,登入後通過檢視助手的方式轉到下一個同一路徑請求的路由規則操作;
就是下面的post路由了,先判斷使用者是否為空;如果為空的話,顯示不能為空資訊,如下程式碼:
if(req.body.post == ""){ //釋出資訊不能為空
req.flash('error', '內容不能為空!');
return res.redirect('/u/' + currentUser.name);
}
接著就路由到 router.get('/u/:user', function(req, res) {})這個路由了;
接著順利的話,就渲染user.ejs模板;程式碼如下:
//呼叫user模板引擎,並傳送資料(使用者名稱和微博集合)
res.render('user', {
title: user.name,
posts: posts
});
因此user.ejs模板程式碼如下:
<%- include header.ejs %>
<%- include alert.ejs %>
<% if(user){ %>
<%include say.ejs %>
<% } %>
<%- include posts.ejs %>
<%- include footer.ejs %>
如果內容為空的話,就在當前頁面顯示內容不能為空資訊,如果新增成功的話,也會提示資訊;接下來我們再來看看正常的情況下;如下post下的程式碼:
//例項化Post物件
var post = new Post(currentUser.name, req.body.post);//req.body.post獲取使用者發表的內容
//呼叫例項方法,發表微博,並把資訊儲存到MongoDB資料庫
post.save(function (err) {
if (err) {
req.flash('error', err);
return res.redirect('/');
}
req.flash('success', '發表成功');
res.redirect('/u/' + currentUser.name);
});
如果正常的情況下,就彈出發表成功文案,並且還是轉向路由res.redirect('/u/' + currentUser.name);這個下面去渲染user.esj模板資訊了;
最後就渲染posts.ejs程式碼了;程式碼如下:
<!--按行按列顯示傳入的posts的所有內容-->
<div id="bottompart">
<% posts.forEach(function(post, index){//遍歷posts內容,每行顯示三個
if(index % 3 == 0){ %>
<div class="row">
<% } %>
<div class="ctn">
<h3><a href='/u/<%= post.user %>' class="username"><%= post.user %></a>說:</h3>
<p><%= post.time %><br/><%= post.content %></p>
</div>
<% if(index % 3 == 2){ %>
</div>
<% } %>
<% }); %>
<% if(posts.length % 3 != 0){ %>
</div>
<% } %>
</div>
通過遍歷陣列,每行放三列發表留言說說;
如下圖所示:
10. 首頁
首頁顯示所有使用者發表的微博,並且按照時間順序進行排列。首先完善index.js中首頁路由規則響應函式的程式碼如下:
//首頁:顯示所有的微博,並按照時間先後順序排列 router.get('/', function (req, res) { //讀取所有的使用者微博,傳遞把posts微博資料集傳給首頁 Post.get(null, function (err, posts) { if (err) { posts = []; } //呼叫模板引擎,並傳遞引數給模板引擎 res.render('index', {title: '首頁', posts: posts}); }); });
上面的是當我們在瀏覽器下這樣訪問的話 http://127.0.0.1:3000/ 直接獲取posts的資料傳遞給首頁;然後再在index.ejs模板渲染出來;
當然index.ejs模板需要把posts.ejs模板加進來;因此index.ejs程式碼變為如下:
<%- include header.ejs %> <%- include alert.ejs %> <% if(!user){ %> <div id="toppart"> <h2>歡迎來到簡易部落格</h2> <p>該部落格基於node.js+express+mongoDB來實現的。</p> <br/> <a href="/login" class="btn">登陸</a> <a href="/reg" class="btn">註冊</a> </div> <% }else{ %> <%- include say.ejs %> <% } %> <%- include posts.ejs %> <%- include footer.ejs %>
接著我們在http://127.0.0.1:3000/ 瀏覽器訪問如下效果:
github上的原始碼如下:
https://github.com/tugenhua0707/node_express_microblog
可以下載下來自己執行下即可;