本項目基于 node.js 的 express 框架欲侮,數(shù)據(jù)庫使用 mongoDB
步驟
- 初始化 package.json:
項目目錄下執(zhí)行命令npm init -y
- 創(chuàng)建 git 倉庫:
項目目錄執(zhí)行命令git init
- 新建項目說明文件 README.md
- 安裝項目核心包 express 與 mongoose
- 配置模板引擎 express-art-template
- 創(chuàng)建 Views 目錄以存放 html 頁面文件
- 安裝 bootstrap 和 jquery
- 提取路由模塊并設計路由
- 設計用戶數(shù)據(jù) Schema
- 處理注冊頁面:
10.1 配置解析表單 POST 請求體插件 body-parser
10.2 配置密碼加密插件 blueimp-md5
10.3 配置express-session
插件削彬,通過 session 保存登錄狀態(tài) - 處理登錄頁面
- 處理退出頁面
路由設計
路徑 | 方法 | get 參數(shù) | post 參數(shù) | 是否需要登陸 | 功能 |
---|---|---|---|---|---|
/ | GET | 渲染首頁 | |||
/register | GET | 渲染注冊頁面 | |||
/register | POST | email曼库、nickname西雀、password | 處理注冊請求 | ||
/login | GET | 渲染登錄頁面 | |||
/login | POST | email、password | 處理登錄請求 | ||
/logout | GET | 處理退出請求 |
表單的同步提交和異步提交
同步提交與優(yōu)化:
表單具有默認提交行為厨钻,默認是同步的琐脏。同步表單提交,瀏覽器會鎖死(轉圈兒)等待服務端的響應結果吴裤。表單同步提交后鳖眼,無論服務端響應的是什么,瀏覽器都會把響應的結果直接渲染到當前容器中嚼摩,覆蓋掉當前頁面钦讳。這種交互方式給人不好的用戶體驗。為了避免這種不好的交互體驗枕面,可以對當前頁面進行重新渲染愿卒。
(場景:用戶注冊時提示郵箱或密碼已存在)
router.post('/register', function (req, res) {
// 1. 獲取表單提交的數(shù)據(jù) req.body ...省略代碼...
// 2. 查詢數(shù)據(jù)庫判斷該用戶是否存在 ...省略代碼...
// 3. 存在則得到查詢結果 data
if (data) {
// 4. 響應結果
return res.render('register.html', {
err_message: '郵箱或密碼已存在', // 回復后用于瀏覽器將報錯信息渲染到頁面
form: req.body // 回復后用于瀏覽器將用戶信息渲染到頁面
})
})
<!--渲染 err_message-->
<p>{{ err_message }}</p>
<!--表單提交的默認行為是發(fā)送 url 為 /register 的post請求-->
<form id="register_form" method="post" action="/register">
<div class="form-group">
<label for="email">郵箱</label>
<input type="email" class="form-control" id="email" name="email" placeholder="Email" autofocus value="{{ form && form.email }}">
</div>
<div class="form-group">
<label for="nickname">昵稱</label>
<input type="text" class="form-control" id="nickname" name="nickname" placeholder="Nickname" value="{{ form && form.nickname }}">
</div>
<div class="form-group">
<label for="password">密碼</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Password">
</div>
<button type="submit" class="btn btn-success btn-block">注冊</button>
</form>
異步提交:
ajax 等異步交互方式誕生后,表單就可以通過 ajax 進行異步提交了潮秘。異步表單提交琼开,瀏覽器不會等待服務端的響應結果,也不會再有響應結果返回后覆蓋掉當前頁面的糟糕交互體驗了枕荞。但需要注意的是柜候,同步交互時服務端可以通過 res.redirect
對客戶端進行重定向搞动,但異步交互時,服務端重新定向是無效的渣刷。因此只能客戶端判斷跳轉鹦肿。
<script>
$("#register_form").on('submit', function (e) {
e.preventDefault() //禁止表單的默認提交行為
var formData = $(this).serialize()
$.ajax({
url: '/register',
type: 'post',
data: formData,
dataType: 'json',
success: function (data) {
var err_code = data.err_code
if (err_code === 0) {
window.location.href = '/' // 服務端重定向針對異步請求無效
} else if (err_code === 1) {
window.alert('郵箱或昵稱已存在!')
} else if (err_code === 500) {
window.alert('服務器忙辅柴,請稍后重試箩溃!')
}
}
})
})
</script>
配置密碼加密插件 md5
用戶注冊時需要存儲用戶的密碼到數(shù)據(jù)庫,為了防止密碼泄露碌嘀,保證用戶密碼的安全性涣旨,我們需要對用戶密碼進行加密,md5 是一種常用的加密方式股冗。在 github 查找 node md5
找到 node 中使用的 md5 插件:blueimp-md5
- 安裝:
npm i blueimp-md5
- 使用:
var md5 = require('blueimp-md5')
然后直接使用md5(password)
方法即可加密
Cookie 與 Session
參考博客:https://www.cnblogs.com/whgk/p/6422391.html
Cookie和Session之間的區(qū)別和聯(lián)系
假如一個咖啡店有喝5杯咖啡免費贈一杯咖啡的優(yōu)惠霹陡,然而一次性消費5杯咖啡的機會微乎其微,這時就需要某種方式來紀錄某位顧客的消費數(shù)量止状。想象一下其實也無外乎下面的幾種方案:
1烹棉、該店的店員很厲害,能記住每位顧客的消費數(shù)量导俘,只要顧客一走進咖啡店,店員就知道該怎么對待了剔蹋。這種做法就是協(xié)議本身支持狀態(tài)旅薄。但是http協(xié)議本身是無狀態(tài)的
2、發(fā)給顧客一張卡片泣崩,上面記錄著消費的數(shù)量少梁,一般還有個有效期限。每次消費時矫付,如果顧客出示這張卡片凯沪,則此次消費就會與以前或以后的消費相聯(lián)系起來。 顧客就相當于瀏覽器买优。這種做法就是在客戶端保持狀態(tài)妨马。也就是cookie。
3杀赢、發(fā)給顧客一張會員卡烘跺,除了卡號之外什么信息也不紀錄,每次消費時脂崔,如果顧客出示該卡片滤淳,則店員在店里的紀錄本上找到這個卡號對應的紀錄添加一些消費信息。這種做法就是在服務器端保持狀態(tài)砌左。也就是session脖咐。
Cookie通過在客戶端記錄信息確定用戶身份铺敌,Session通過在服務器端記錄信息確定用戶身份。session和cookie的作用是類似的屁擅,都是為了存儲用戶相關的信息偿凭。
Cookie的機制
在網(wǎng)站中,http請求是無狀態(tài)的煤蹭,也就是說笔喉,即使第一次和服務器連接后并且登錄成功后,第二次請求服務器依然不能知道當前請求是哪個用戶硝皂。cookie的出現(xiàn)就是為了解決這個問題:瀏覽器請求服務器訪問web站點時常挚,服務器通過 http 響應將Cookie數(shù)據(jù)回給瀏覽器,瀏覽器將cookie數(shù)據(jù)存放在客戶端內存中稽物,當該用戶發(fā)送第二次請求的時候奄毡,就會自動的把上次請求存儲的cookie數(shù)據(jù)自動攜帶給服務器,服務器通過瀏覽器攜帶的數(shù)據(jù)就能識別當前用戶贝或。
cookie中的Domain和Path屬性標識了這個cookie是哪一個網(wǎng)站發(fā)送給瀏覽器的吼过;Expires屬性標識了cookie的有效時間:
如果對Expires屬性進行有效時間設置,當cookie的有效時間過了之后咪奖,這些數(shù)據(jù)就被自動刪除了盗忱;此時會話cookie保存在硬盤上。關閉瀏覽器后再次打開羊赵,這些cookie依然有效趟佃,直到超過設定的有效時間。存儲在硬盤上的cookie可以在不同的瀏覽器進程間共享昧捷,比如兩個IE窗 口闲昭。
如果不設置過期時間,則表示這個cookie生命周期為瀏覽器會話期間靡挥,只要關閉瀏覽器窗口序矩,cookie就消失。這種生命期為瀏覽會話期的cookie被稱為會話cookie跋破。會話cookie一般不保存在硬盤上簸淀,而是保存在內存里。
特點:cookie存儲在本地瀏覽器毒返,且存儲數(shù)據(jù)量有限啃擦,不同的瀏覽器有不同的存儲大小,但一般不超過4KB饿悬。因此使用cookie只能存儲一些小量的數(shù)據(jù)令蛉。
Session的機制
Session的機制
瀏覽器請求服務器訪問web站點時,程序需要為客戶端的請求創(chuàng)建一個session的時候,服務器首先會檢查這個客戶端請求是否已經(jīng)包含了一個session標識珠叔、稱為SESSIONID蝎宇,如果已經(jīng)包含了一個sessionid則說明以前已經(jīng)為此客戶端創(chuàng)建過session,服務器就按照sessionid把這個session檢索出來使用祷安,如果客戶端請求不包含session id姥芥,則服務器為此客戶端創(chuàng)建一個session并且生成一個與此session相關聯(lián)的session id,sessionid 的值應該是一個既不會重復汇鞭,又不容易被找到規(guī)律以仿造的字符串凉唐,這個sessionid將在本次響應中返回到客戶端保存,保存這個sessionid的方式就可以是cookie霍骄,這樣在交互的過程中台囱,瀏覽器可以自動的按照規(guī)則把這個標識發(fā)回給服務器,服務器根據(jù)這個sessionid就可以找得到對應的session读整。
一般情況下簿训,服務器會在一定時間內(默認20分鐘)保存這個session,過了時間限制米间,就會銷毀這個session强品。在銷毀之前,程序員可以將用戶的一些數(shù)據(jù)以Key和Value的形式暫時存放在這個session中屈糊。默認 Session 數(shù)據(jù)是內存存儲的的榛,服務器一旦重啟就會丟失,也有使用數(shù)據(jù)庫將這個session序列化后保存起來的逻锐,這樣的好處是沒了時間的限制夫晌,壞處是隨著時間的增加,這個數(shù)據(jù)庫會急速膨脹谦去,特別是訪問量增加的時候慷丽。一般還是采取前一種方式蹦哼,以減輕服務器壓力鳄哭。
特點:cookie存儲在本地瀏覽器,而session存儲在服務器纲熏。存儲在服務器的數(shù)據(jù)會更加的安全妆丘,不容易被竊取。但會占用服務器的資源局劲。
在 Express 中配置使用 express-session
插件
在 express 中勺拣,默認不支持 Session 和 Cookie,但是我們可以使用第三方中間件:express-session 來解決:
- 官網(wǎng):https://www.npmjs.com/package/express-session
- 安裝:
npm i express-session
- 配置(一定要在
app.use(router)
之前):
var session = require('express-session')
var app = express()
app.use(session({
secret: 'keyboard cat', //配置加密字符串
resave: false,
saveUninitialized: true,
// cookie: { secure: true } cookie 安全限制 https 協(xié)議
}))
- secret屬性是配置加密字符串鱼填,它會在原有加密基礎之上和這個字符串拼起來去加密药有,加密后的字符串作為 session id 發(fā)送給瀏覽器。目的是為了增加安全性,防止客戶端惡意偽造
- saveUninitialized 屬性為 true 則無論你是否使用 Session愤惰,都默認直接分配一個session id 給客戶端苇经。為 false 則真正存數(shù)據(jù)的時候才會分配 session id 給客戶端
- 當secure屬性設置為true時,cookie只有在https協(xié)議下才能上傳到服務器宦言,而在http協(xié)議下是沒法上傳的扇单,所以也不會被竊聽。
- 使用:
當把這個插件配置好之后奠旺,我們可以通過req.session
來發(fā)訪問和設置 Session 成員蜘澜,例如:
// 添加 Session 數(shù)據(jù)
req.session.foo = 'bar'
// 訪問 Session 數(shù)據(jù)
req.session.foo