nodejs開發指南讀后感


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
可以下載下來自己運行下即可;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM