Node — 第六天(前后端分离)及(身份验证)
綜合應(yīng)用服務(wù)端知識點(diǎn)搭建項(xiàng)目
下載安裝所需的第三方模塊
npm init -y npm i express cors mysql # express 用于搭建服務(wù)器 # cors 用于解決跨域 # mysql 用于操作數(shù)據(jù)庫# 后面用到什么,再下載創(chuàng)建app.js
之前,我們開啟一個服務(wù)器,js文件名,一般都是 01-xxx.js 03-xxx.js。
對于正常的一個項(xiàng)目來說,用于開啟服務(wù)的js文件,一般都叫做 app.js
// 開啟服務(wù) const express = require('express'); const app = express(); app.listen(3006, () => console.log('server running~'));使用路由模塊精簡項(xiàng)目結(jié)構(gòu)
使用路由模塊的原因(意義)
- 試想,如果項(xiàng)目有100個路由(接口),全部放到app.js中,這樣的代碼不好維護(hù),效率也比較低下
- 所以需要按照路由(接口)功能的不同,分別使用小文件存放這些路由(接口),這些小文件就叫做路由模塊
- 比如,登錄注冊相關(guān)的,全部放到 ./routers/login.js 中
- 比如,文章處理相關(guān)的,全部放到 ./routers/article.js 中
- 比如,書籍管理相關(guān)的,全部放到 ./routers/books.js 中
代碼如何實(shí)現(xiàn)
- 路由文件中的代碼
- 加載express
- 創(chuàng)建路由對象
- 將路由(接口)掛載到路由對象上
- 導(dǎo)出路由對象
- app.js 中的代碼
- 加載路由模塊,并注冊成中間件。注冊的時候,可以統(tǒng)一加前綴
開啟服務(wù)(nodemon app.js) ,然后通過瀏覽器來測試一下你的接口是否能夠正常訪問
創(chuàng)建數(shù)據(jù)庫、數(shù)據(jù)表
數(shù)據(jù)庫,還可以使用昨天的 user 數(shù)據(jù)庫。
在里面創(chuàng)建一張數(shù)據(jù)表(books)
添加幾條模擬數(shù)據(jù):
粗略的完成獲取書籍的接口
-
目前:
- 數(shù)據(jù)準(zhǔn)備好了
- 接口能夠正常訪問了
-
下面要做的事情:
- getbooks接口中,通過mysql模塊,查詢所有的書籍,并把結(jié)果響應(yīng)給客戶端
- 中間件解決跨域問題
getbooks接口
router.get('/getbooks', async (req, res) => {// 查詢數(shù)據(jù)庫user、數(shù)據(jù)表books里面的數(shù)據(jù),把查詢的結(jié)果響應(yīng)給客戶端const mysql = require('mysql'); // require加載一個模塊后,會緩存起來const conn = mysql.createConnection({host: 'localhost',user: 'root',password: '12345678',database: 'user' // 填寫數(shù)據(jù)庫名});conn.connect();conn.query('select * from books', (err, result) => {if (err) return console.log(err);res.json({status: 200,msg: '獲取成功',data: result});});conn.end(); });應(yīng)用級別的中間件解決跨域
- 應(yīng)用級別的中間件
- 寫到app.js中的中間件
- 該中間件會影響所有的路由
- 路由級別的中間件
- 寫到 路由文件中的中間件
- 該中間件只會影響當(dāng)前的路由文件
因?yàn)檎麄€項(xiàng)目的全部路由都需要解決跨域問題,所以需要定義應(yīng)用級別的中間件
所以,在app.js中,加入如下代碼:
// 配置中間件,解決跨域問題 app.use((req, res, next) => {res.set({'Access-Control-Allow-Origin': '*'});next(); });封裝db.js
試想,后面還有很多路由中需要對數(shù)據(jù)庫進(jìn)行操作,難道每次執(zhí)行SQL語句,都要寫5個步驟(操作MySQL的5個步驟)嗎?
答:肯定不是,那樣的話,代碼太多。代碼復(fù)用性太差了
解決辦法:封裝
具體做法:
- 創(chuàng)建 db.js
- 里面封裝 使用mysql模塊的5個步驟
- 導(dǎo)出函數(shù)
- 其他路由中,需要加載(導(dǎo)入)db模塊,然后調(diào)用函數(shù)即可
db.js 中的代碼:
function db(sql, params, callback) {const mysql = require('mysql');const conn = mysql.createConnection({host: 'localhost',user: 'root',password: '12345678',database: 'user' // 填寫數(shù)據(jù)庫名});conn.connect();conn.query(sql, params, callback);conn.end(); }// 導(dǎo)出函數(shù) module.exports = db;getbooks接口中使用:
// 獲取書籍接口 router.get('/getbooks', async (req, res) => {// 查詢數(shù)據(jù)庫user、數(shù)據(jù)表books里面的數(shù)據(jù),把查詢的結(jié)果響應(yīng)給客戶端db('select * from books', null, (err, result) => {if (err) return console.log(err);res.json({status: 200,msg: '獲取成功',data: result});}); });使用Promise
如果查詢書籍信息的時候,還要查詢總記錄數(shù),怎么辦?
答:嵌套查詢,嵌套的層數(shù)太多,就會形成回調(diào)地獄
解決辦法:使用Promise
具體做法:
- 在db.js中,加個一個封裝Promise的函數(shù)db2
- db2中調(diào)用db函數(shù),從而完成Promise的封裝
- 導(dǎo)出db2
優(yōu)化上述兩個函數(shù):
function db (sql, params = null) {const mysql = require('mysql'); // require加載一個模塊后,會緩存起來const conn = mysql.createConnection({host: 'localhost',user: 'root',password: '12345678',database: 'user' // 填寫數(shù)據(jù)庫名});return new Promise((resolve, reject) => {// 這里寫異步代碼conn.connect();conn.query(sql, params, (err, result) => {err ? reject(err) : resolve(result);});conn.end();}); }// 導(dǎo)出函數(shù) module.exports = db;完整的圖示
Web 開發(fā)模式
目前主流的 Web 開發(fā)模式有兩種,分別是:
服務(wù)端渲染的開發(fā)模式
特點(diǎn):
-
所有的web資源由同一個服務(wù)器統(tǒng)一管理(前后端代碼必須放到一起)
-
頁面和頁面中使用的數(shù)據(jù),由服務(wù)器組裝,最后將完整的HTML頁面響應(yīng)給客戶端
代碼:
// 用于開啟服務(wù)const fs = require('fs'); const express = require('express'); const app = express(); app.listen(3000, () => console.log('啟動了'));// 顯示首頁的接口 app.get('/index.html', (req, res) => {// res.send('1111')fs.readFile('./public/index.html', 'utf-8', (err, data) => {if (err) return console.log(err);data = data.replace('{{title}}', '憫農(nóng)');data = data.replace('{{content}}', '鋤禾日當(dāng)午,汗滴禾下土');res.send(data);}); });真實(shí)的服務(wù)端渲染模式和前后端分離的模式,難度上差不多。
優(yōu)點(diǎn):
- **前端耗時少。**因?yàn)榉?wù)器端負(fù)責(zé)動態(tài)生成 HTML 內(nèi)容,瀏覽器只需要直接渲染頁面即可。尤其是移動端,更省電。
- 有利于SEO。因?yàn)榉?wù)器端響應(yīng)的是完整的 HTML 頁面內(nèi)容,所以爬蟲更容易爬取獲得信息,更有利于 SEO(搜索引擎)。
缺點(diǎn):
-
**占用服務(wù)器端資源。**即服務(wù)器端完成 HTML 頁面內(nèi)容的拼接,如果請求較多,會對服務(wù)器造成一定的訪問壓力。
-
不利于前后端分離,開發(fā)效率低。使用服務(wù)器端渲染,則無法進(jìn)行分工合作,尤其對于前端復(fù)雜度高的項(xiàng)目,不利于 項(xiàng)目高效開發(fā)。
前后端分離的開發(fā)模式
特點(diǎn):
- 依賴于Ajax技術(shù)。
- 后端不提供完整的 HTML 頁面內(nèi)容,而 是提供一些 API 接口
- 前端通過 Ajax 調(diào)用后端提供的 API 接口,拿到 json 數(shù)據(jù) 之后再在前端進(jìn)行 HTML 頁面的拼接,最終展示在瀏覽器上。
簡而言之,前后端分離的 Web 開發(fā)模式,就是后端只負(fù)責(zé)提供 API 接口,前端使用 Ajax 調(diào)用接口的開發(fā)模式。
優(yōu)點(diǎn):
- **開發(fā)體驗(yàn)好。**前端專注于 UI 頁面的開發(fā),后端專注于api 的開發(fā),且前端有更多的選擇性。
- **用戶體驗(yàn)好。**Ajax 技術(shù)的廣泛應(yīng)用,極大的提高了用戶的體驗(yàn),可以輕松實(shí)現(xiàn)頁面的局部刷新。
- **減輕了服務(wù)器端的渲染壓力。**因?yàn)轫撁孀罱K是在每個用戶的瀏覽器中生成的。
缺點(diǎn):
- **不利于SEO。**因?yàn)橥暾?HTML 頁面需要在客戶端動態(tài)拼接完成,所以爬蟲對無法爬取頁面的有效信息。(解決方 案:利用 Vue、React 等前端框架的 SSR (server side render)技術(shù)能夠很好的解決 SEO 問題!)
如何選擇 Web 開發(fā)模式
不談業(yè)務(wù)場景而盲目選擇使用何種開發(fā)模式都是耍流氓。
-
比如企業(yè)級網(wǎng)站(公司的網(wǎng)站),主要功能是展示而沒有復(fù)雜的交互,并且需要良好的 SEO,則這時我們就需要使用服務(wù)器端渲染;
-
而類似后臺管理頁面,交互性比較強(qiáng),不需要 SEO 的考慮,那么就可以使用前后端分離的開發(fā)模式。
-
另外,具體使用何種開發(fā)模式并不是絕對的,為了同時兼顧了首頁的渲染速度和前后端分離的開發(fā)效率,一些網(wǎng)站采用了 首屏服務(wù)器端渲染,即對于用戶最開始打開的那個頁面采用的是服務(wù)器端渲染,而其他的頁面采用前后端分離開發(fā)模式。
身份認(rèn)證機(jī)制
對于服務(wù)端渲染和前后端分離這兩種開發(fā)模式來說,分別有著不同的身份認(rèn)證方案:
- 服務(wù)端渲染推薦使用 Session 認(rèn)證機(jī)制(Session也會用到Cookie)
- 前后端分離推薦使用 JWT 認(rèn)證機(jī)制
Cookie
原理
實(shí)現(xiàn)身份認(rèn)證
- 搭建基礎(chǔ)的服務(wù)器
-
下載安裝第三方模塊 express 和 cookie-parser
-
創(chuàng)建app.js
-
加載所需模塊
- const express = require('express');
- const cookieParser = require('cookie-parser');
-
- 中間件配置 cookie-parser
- app.use(cookieParser())
- 實(shí)現(xiàn)三個路由
- /login.html (里面直接響應(yīng)login.html頁面)
- /api/login
- /index.html (里面直接響應(yīng)index.html頁面)
- 創(chuàng)建存放index頁面的public文件夾
- 創(chuàng)建index.html
- 創(chuàng)建login.html
- 完成登錄接口
- 如果登錄成功,設(shè)置cookie。res.cookie('key', 'value', 配置項(xiàng));
- 跳轉(zhuǎn)到 /index.html 路由
- /index.html 路由中,根據(jù)cookie判斷是否登錄,從而完成身份認(rèn)證
詳見代碼
const express = require('express'); const cookieParser = require('cookie-parser'); const path = require('path');const app = express(); app.listen(3000, () => console.log('啟動了'));// 接收POST請求體 app.use(express.urlencoded({extended: false})); // 配置cookie-parser app.use(cookieParser());// 準(zhǔn)備三個路由// 用于顯示登錄頁面 app.get('/login.html', (req, res) => {// sendFile方法,可以讀取文件,并將讀取的結(jié)果響應(yīng)給客戶端// 要求,參數(shù)必須是一個絕對路徑res.sendFile(path.join(__dirname, 'public', 'login.html')); });// 用于完成登錄驗(yàn)證的(判斷賬號密碼是否正確的接口) app.post('/api/login', (req, res) => {// console.log(req.body);// 約定,假設(shè)賬號是 admin、密碼是123if (req.body.username === 'admin' && req.body.password === '123') {// 登錄成功,跳轉(zhuǎn)到index.html// 設(shè)置cookie// res.cookie('key', 'value', '選項(xiàng)');// res.cookie('isLogin', 1); // 沒有填選項(xiàng),默認(rèn)cookie有效期是會話結(jié)束res.cookie('isLogin', 1, {maxAge: 2*60*1000});res.send('<script>alert("登錄成功"); location.href="/index.html";</script>');} else {// 登錄失敗} });// 顯示index.html頁面的 app.get('/index.html', (req, res) => {// 獲取cookie// console.log(req.cookies);if (req.cookies.isLogin && req.cookies.isLogin === '1') {res.sendFile(path.join(__dirname, 'public', 'index.html'));} else {// 沒有登錄res.send('<script>alert("請先登錄"); location.href="/login.html";</script>');} });優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn)
- 體積小
- 客戶端存放,不占用服務(wù)器空間
- 瀏覽器會自動攜帶,不需要寫額外的代碼,比較方便
- 缺點(diǎn)
- 客戶端保存,安全性較低。但可以存放加密的字符串來解決
- 可以實(shí)現(xiàn)跨域,但是難度大,難理解,代碼難度高
- 不適合前后端分離式的開發(fā)
適用場景
- 傳統(tǒng)的服務(wù)器渲染模式
- 存儲安全性較低的數(shù)據(jù),比如視頻播放位置等
Session
原理
實(shí)現(xiàn)身份認(rèn)證
-
搭建基礎(chǔ)的服務(wù)器
-
下載安裝第三方模塊 express 和 express-session
-
創(chuàng)建app.js
-
加載所需模塊
- const express = require('express');
- const session = require('express-session');
-
-
中間件配置 session
app.use(session({secret: 'adfasdf', // 這個隨便寫saveUninitialized: false,resave: false })) -
實(shí)現(xiàn)三個路由
- /login.html (里面直接響應(yīng)login.html頁面)
- /api/login
- /index.html (里面直接響應(yīng)index.html頁面)
-
創(chuàng)建存放index頁面的public文件夾
- 創(chuàng)建index.html
- 創(chuàng)建login.html
-
完成登錄接口
-
如果登錄成功,使用session記錄用戶信息。
req.session.isLogin = 1; req.session.username = req.body.username; -
跳轉(zhuǎn)到 /index.html 路由
-
-
/index.html 路由中,根據(jù)session判斷是否登錄,從而完成身份認(rèn)證
詳見代碼
const express = require('express'); const session = require('express-session'); const path = require('path');const app = express(); app.listen(3000, () => console.log('啟動了'));// 接收POST請求體 app.use(express.urlencoded({extended: false})); // 配置session app.use(session({secret: 'asdf23sfsd23',// 下面兩項(xiàng),設(shè)置成true或者false,都可以。使用內(nèi)存存儲session的時候,下面兩項(xiàng)沒作用saveUninitialized: false,resave: false }));// 準(zhǔn)備三個路由// 用于顯示登錄頁面 app.get('/login.html', (req, res) => {// sendFile方法,可以讀取文件,并將讀取的結(jié)果響應(yīng)給客戶端// 要求,參數(shù)必須是一個絕對路徑res.sendFile(path.join(__dirname, 'public', 'login.html')); });// 用于完成登錄驗(yàn)證的(判斷賬號密碼是否正確的接口) app.post('/api/login', (req, res) => {// console.log(req.body);// 假設(shè)賬號任意,密碼必須是123if (req.body.password === '123') {// 假設(shè)登錄成功// req.session.xxxx = 'yyyy'req.session.isLogin = 1; /*<p>歡迎你:{{username}}</p> */req.session.username = req.body.username;// 做出響應(yīng)res.send('<script>alert("登錄成功"); location.href="/index.html";</script>');} });// 顯示index.html頁面的 app.get('/index.html', (req, res) => {// 獲取session req.sessionif (req.session.isLogin && req.session.isLogin == 1) {const fs = require('fs');fs.readFile('./public/index.html', 'utf-8', (err, data) => {if (err) return console.log(err);// 更換用戶名data = data.replace('{{username}}', req.session.username);res.send(data);});} else {res.send('<script>alert("請登錄"); location.href="/login.html";</script>');}});優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn)
- 服務(wù)端存放,安全性較高
- 瀏覽器會自動攜帶cookie,不需要寫額外的代碼,比較方便
- 適合服務(wù)器端渲染模式
- 缺點(diǎn)
- 會占用服務(wù)器端空間
- session實(shí)現(xiàn)離不開cookie,如果瀏覽器禁用cookie,session不好實(shí)現(xiàn)
- 不適合前后端分離式的開發(fā)
適用場景
- 傳統(tǒng)的服務(wù)器渲染模式
- 安全性要求較高的數(shù)據(jù)可以使用session存放,比如用戶私密信息、驗(yàn)證碼等
總結(jié)
以上是生活随笔為你收集整理的Node — 第六天(前后端分离)及(身份验证)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Node — 第五天
- 下一篇: Node — 第七天 (大事件项目接口实