初識 Node.js 與內置模塊
(初識)
1、知道什么是node.js
2、知道node.js可以做什么
3、node.js 中js的組成部分
(內置模塊)
4、用 fs 模塊讀寫操作文件
5、使用 path 模塊處理路徑
6、使用http 模塊寫一個基本的web服務器
初識 Node.js
回顧與思考
回憶瀏覽器中的 JavaScript 組成:
為什么JavaScript可以在瀏覽器中被執行,是因為瀏覽器中存在JavaScript解析引擎,而不同的瀏覽器使用的解析引擎是不一樣的:
因此可以總結出 JavaScript 的運行環境就包括下面兩個部分:
由上述內容我們可以總結出兩點:
1、V8 引擎負責解析和執行 JavaScript 代碼
2、內置 API 是由運行環境提供的特殊接口,只能在所屬的運行環境中被調用
那么 JavaScript 能否做后端開發呢?
當然是沒有問題的,JavaScript 本身只是一門語言,這門語言所編寫出來的代碼想要執行的話離不開運行時環境,如果將 JavaScript 的代碼放在瀏覽器里運行那么瀏覽器本身就是一個運行時環境,此時 JavaScript 就可以用來做前端開發,同時如果我們將 JavaScript 代碼放在 Node.js 環境(Node.js 也是一個 JavaScript 的運行時環境,只不過是一個后端的運行時環境)當中,通過 Node.js 就可以讓我們的 JavaScript 去做后端開發了。
Node.js 簡介
先來看一段官方的介紹:
為什么是基于 Chrome V8 呢?之前說過,因為它最牛逼。所以使用 V8 來做 Node.js 的引擎用以解析 JavaScript 的代碼,這樣可以讓 JavaScript 跑的更快。
所以我們的 Chrome 瀏覽器和 Node.js 用的是同一款 JavaScript 解析引擎,即 V8 ;
只不過 V8 用在不同的地方做的事情是不一樣的,在 Chrome 瀏覽器中用 V8 解析 JavaScript 做的是前端的開發,而在 Node.js 中使用 V8 解析 JavaScript 則做的是后端開發,僅此而已。
Node.js 的運行時環境
可以看見,Node.js 為我們提供了后端開發所需要的所有模塊的 API,因此學習 Node.js 很大程度上就是學習怎么使用這些內置的后端開發的 API。
Node.js 可以做什么
一句話,全棧!
Node.js 怎么學
Node.js 環境的安裝
官方網址:Node.js
直接下載完成后雙擊安裝即可,一路按照默認的來就行。
檢測安裝是否成功,打開 cmd 命令終端輸入 node -v,能正常顯示版本號即正常:
這樣就安裝完成了。
什么是終端
在 Node.js 環境中執行 JavaScript 代碼
下面介紹的是比較樸素的方式,因為現在都使用 IDE 了,我用的是 WebStorm,比較推薦,IDE 的使用就不介紹了,網上很多,不再贅述。
就兩步:
先來寫一個 1.js 文件:
然后進入終端,在當前文件目錄的命令行下,用 node 命令啟動即可:
這樣就可以運行 JavaScript 代碼啦。
有一種便捷打開命令行的方式,即在我們的文件目錄下,按住 shift 后點擊鼠標右鍵會有一個打開 PowerShell 的選項:
點擊它:
可以看見這種方式更加的快捷,那么 PowerShell 和我們之前的 CMD 方式有什么區別呢?
CMD 方式是出現的比較早的一種方式,是舊版本的 windows 里的終端,后來 windows 做了升級,改成了 PowerShell ,也就是新版本的 CMD(其實用哪個都行,但是 PowerShell 的功能更加強大一些)。
終端中常用的一些快捷鍵:
使用WebStorm編寫 Node.js 代碼沒有提示問題解決
參考這篇文章:使用WebStorm編寫 Node.js 代碼沒有提示問題解決
fs 文件系統模塊
什么是 fs 文件系統模塊
我們只需要知道在安裝 Node.js 的時候這些模塊就都被安裝到電腦本地了即可,然后需要用到哪個模塊,就使用 require 方法將其導入進來即可。
讀取指定文件中的內容
fs.readFile() 的語法格式
示例代碼如下:
// 1、到入 fs 模塊操作文件
const fs = require('fs')//2、調用 fs.readFile() 方法讀取文件
// 參數1、 讀取文件的存放路徑
// 參數2、 讀取文件時候采用的編碼格式,一般默認指定 utf8
// 參數3、 回調函數,拿到讀取失敗和成功的結果,err dataStr
fs.readFile('./1.txt', 'utf8', function (err, dataStr){// 打印失敗的結果// 如果讀取成功,則err 的值為null// 如果讀取失敗,則 err 的值為錯誤對象,dataStr 的值為 undefined// 因此可以通過判斷 err 對象是否為 null,來判斷文件讀取是否成功console.log(err)console.log("----------------")// 打印成功的結果// 如果讀取成功,那么dataStr就是從文件當中讀取的值// 如果讀取失敗,那么dataStr就是undefinedconsole.log(dataStr)
})
運行結果如下:
關于fs.readFile()第三個參數是回調函數的解釋:
fs.readFile('./1.txt', 'utf8', function (err, dataStr){console.log(err)console.log("----------------")console.log(dataStr)
})
這段代碼中的回調函數是作為 fs.readFile 方法的最后一個參數傳遞進去的。根據 Node.js 中的約定,回調函數的第一個參數通常用于表示錯誤信息,第二個參數則是成功時返回的數據或結果(這是 fs.readFile() 函數的形式約定)。
所以,當你使用 fs.readFile 方法時,如果文件讀取成功,Node.js 會將 err 參數置為 null,而將文件內容作為 dataStr 參數傳遞給回調函數;如果文件讀取失敗,Node.js 會將 err 參數設置為相應的錯誤對象,而 dataStr 則會是 undefined。
因此,通過檢查 err 參數是否為 null,你可以輕松地判斷文件讀取是否成功。如果 err 為 null,則表示讀取成功,可以在 dataStr 中訪問文件內容;如果 err 不為 null,則表示讀取失敗,dataStr 會是 undefined,同時你可以查看 err 對象來了解具體的錯誤信息。
向指定的文件中寫入內容
fs.writerFile()的語法格式
示例代碼如下:
const fs = require('fs')fs.writeFile('./2.txt', 'abcd', 'utf8', function (err){// 如果文件寫入成功,則 err 的值等于 null// 如果文件寫入失敗,則 err 的值等于一個錯誤對象console.log(err)
})
運行結果如下:
因為運行成功,因此 err 是 null 值。
fs 模塊 - 路徑動態拼接的問題
出現路徑拼接錯誤的問題,是因為提供了 ./ 或者 …/ 開頭的相對路徑。
如果要解決這個問題,可以直接提供一個完整的文件存放路徑(即絕對路徑)即可。
但是這又會帶來一種新的問題,就是代碼的移植性非常差,不利于維護。
那么如何完美解決這個問題呢?可以使用 Node.js 給我們提供的 __dirname 參數:
測試也比較簡單,打印出來即可知道 __dirname 就是代表當前文件所處的目錄,因此不再贅述。
path 路徑模塊
什么是 path 路徑模塊
路徑拼接
path.join() 的語法格式
代碼示例如下:
const path = require('path')// 注意:../ 會抵消前面的路徑
const pathStr = path.join('/a','/b/c','../','./d','e')console.log(pathStr) // 輸出:\a\b\d\e//今后凡是涉及到路徑拼接的操作,都要使用 path.join() 方法進行處理。
// 不要用 + 進行字符串拼接
const pathFile = path.join(__dirname, '/a/1.txt')console.log(pathFile)
運行結果如下:
獲取路徑中的文件名
path.basename() 的語法格式
示例代碼:
const path = require('path')//定義文件的存放路徑
const fpath = '/a/b/c/index.html'const fullName = path.basename(fpath)
console.log(fullName) //輸出:index.htmlconst nameWithoutExt = path.basename(fpath, '.html')
console.log(nameWithoutExt) //輸出:index
運行結果如下:
獲取路徑中的文件擴展名
path.extname() 的語法格式
示例代碼如下:
const path = require('path')//這是文件的存放路徑
const fpath = '/a/b/c/index.html'const fext = path.extname(fpath)console.log(fext)
運行結果如下:
關于 path 模塊需要注意的兩個小點
http 模塊
什么是 http 模塊
進一步理解 http 模塊的作用:
創建最基本的 web 服務器
上面的步驟細分下來其實就是下面這樣:
第一步:導入 http 模塊
第二步:創建 web 服務器實例
第三步:為服務器實例綁定 request 事件
第四步:啟動服務器
接下來我們來代碼實操一下:
//1、導入 http模塊
const http = require('http')
//2、創建 web 服務器實例
server = http.createServer()
//3、為服務器實例綁定 request 事件,監聽客戶端的請求
server.on('request', function (req, res){console.log('Someone visit our web server.')
})
//4、啟動服務器
server.listen(80,function (){console.log('Server running at http://127.0.0.1')
})
運行效果如下:
可以發現當我們在瀏覽器訪問該地址的時候,我們的監聽事件已經監聽到了事件。
補充:什么是箭頭函數
這是 JavaScript 中的箭頭函數語法。()=>{} 是一個簡單的箭頭函數,它表示一個沒有參數的函數,并返回一個空對象(即函數體內沒有執行任何操作)。箭頭函數是 ES6(ECMAScript 2015)中引入的一種新的函數聲明方式,它可以更簡潔地定義函數,并且在一些情況下具有更清晰的語義。
req 請求對象
代碼示例如下:
const http = require('http')
server = http.createServer()
// req 是請求對象,包含了與客戶端相關的數據和屬性
server.on('request', (req)=>{// req.url 是客戶端請求的 url 地址const url = req.url//req.method 是客戶端請求的 method 類型const method = req.methodconst str = `Your request url is ${url}, and request method is ${method}`console.log(str)
})server.listen(80,function (){console.log('Server running at http://127.0.0.1')
})
運行效果如下:
res 響應對象
示例代碼如下:
const http = require('http')
server = http.createServer()
// req 是請求對象,包含了與客戶端相關的數據和屬性
server.on('request', (req, res)=>{// req.url 是客戶端請求的 url 地址const url = req.url//req.method 是客戶端請求的 method 類型const method = req.methodconst str = `Your request url is ${url}, and request method is ${method}`console.log(str)// 調用 res.end() 方法向客戶端響應一些內容res.end(str)
})server.listen(80,function (){console.log('Server running at http://127.0.0.1')
})
運行效果如下:
解決中文亂碼的問題
代碼示例如下:
const http = require('http')
server = http.createServer()
// req 是請求對象,包含了與客戶端相關的數據和屬性
server.on('request', (req, res)=>{// req.url 是客戶端請求的 url 地址const url = req.url//req.method 是客戶端請求的 method 類型const method = req.methodconst str = `你的請求路徑是 ${url}, 請求方法是 ${method}`console.log(str)// 發送數據前設置響應頭,設置編碼方式res.setHeader('Content-Type', 'text/html; charset=utf-8')// 調用 res.end() 方法向客戶端響應一些內容res.end(str)
})server.listen(80,function (){console.log('Server running at http://127.0.0.1')
})
運行效果如下:
根據不同的 url 響應不同的 html 內容
動態響應內容的代碼示例:
const http = require('http')server = http.createServer()// req 是請求對象,包含了與客戶端相關的數據和屬性
server.on('request', (req, res)=>{//1、獲取請求的 url 地址const url = req.url//2、設置默認的響應內容為 404 Not Foundlet content = '404 Not Found'//3、判斷用戶請求的是否為 / 或者 /index.html 頁面//4、判斷用戶請求的是否為 /about.html 關于頁面if(url === '/' || url === '/index.html'){content = '<h1>首頁</h1>'}else if(url === '/about.html'){content = '<h1>關于頁面</h1>'}//5、設置 Content-Type 響應頭,防止中文亂碼res.setHeader('Content-Type', 'text/html; charset=utf-8')//6、使用 res.end() 把內容響應給客戶端res.end(content)
})server.listen(80,function (){console.log('Server running at http://127.0.0.1')
})
運行效果如下:
補充:${} 模板字符串語法
${} 是 JavaScript 中的模板字符串語法。在字符串中使用 ${} 可以插入變量或表達式的值。在這種情況下,${url} 表示將變量 url 的值插入到字符串中。這種語法使得字符串拼接更加清晰和簡潔。
模塊化
模塊化的基本概念
模塊化是指解決一個復雜問題時,自頂向下逐層把系統劃分為若干模塊的過程。對于整個系統來說,模塊是可組合、分解和更換的單元。
模塊化規范
Node.js 中模塊的分類
加載模塊
注意:在使用 require 加載用戶自定義模塊期間,可以省略 .js 的后綴名。
Node.js 中的模塊作用域
模塊作用域的好處就是:防止了全局變量污染的問題。
向外共享模塊作用域中的成員
module 對象
module.exports 對象
接下來我們用代碼來解釋一下。
先創建兩個自定義模塊,一個是自定義模塊.js 另一個是 test.js,然后我們 自定義模塊.js 文件先不寫任何內容,在 test.js 中先去 require 自定義模塊.js 中的內容試一下:
運行效果如下:
可以看見打印不出任何內容,因為我們在導入另一個模塊的時候,我們導入的其實是另一個模塊 module 的 exports 屬性,默認情況下 exports 屬性就是空的,這可以從上一小節講的 module 對象 中的圖里看到。
因此我們其實可以在 自定義模塊.js 中對外共享一些本模塊中的成員:
此時我們在運行 test.js 查看現在是否 modules.export 為空了:
可以看見不為空了,有內容的。
共享成員時的注意點
exports 對象
代碼驗證如下:
console.log(exports)
console.log(module.exports)
console.log(exports === module.exports)
運行效果如下:
exports 和 module.exports 的使用誤區
Node.js 中的模塊化規范
npm 與包
什么是包
包的來源
為什么需要包
從哪里下載包
如何下載包
查看自己電腦上的 npm 版本:
npm 初體驗
這種傳統方法就不演示了,可以看出來很繁瑣,因此我們直接使用第三方包來完成一樣的事情:
在項目中安裝包的命令
接下來我們來演示如何用 npm 下載 第三方包,這里我直接使用 WebStorm 的終端進行下載了:
可以看見此時就已經下載安裝好了。
寫代碼來實現我們的時間格式化操作:
//1、導入需要的包
const moment = require('moment')//2、查閱官方文檔明白第三方包要如何使用
const dt = moment().format('YYYY-MM-DD HH:mm:ss')
console.log(dt)
運行效果如下:
初次安裝后都多了哪些文件
可以檢查一下自己的項目:
注意:程序員不要手動修改 node_modules 或者 package-lock.json 文件中的任何代碼,npm 包管理工具會自動維護它們。
安裝指定版本的包
注意:如果我們要切換第三方包的版本并不需要先卸載我們現有版本的包,而是直接下新的即可,新的會覆蓋原來舊版本的包嗷。
包的語義化版本規范
包管理配置文件
多人協作的問題
如何記錄項目中安裝了哪些包
快速創建 package.json
dependencies 節點
一次性安裝所有的包
卸載包
devDependencies 節點
怎么知道哪些第三方包需要被保存到 devDependencies 哪些需要被保存到 dependencies 呢?
這些其實在官方文檔都會有說的,查閱官方文檔就行。
為什么下包速度慢
下包速度慢解決:淘寶 NPM 鏡像服務器
切換 NPM 的下包鏡像源
nrm 小工具
包的分類
使用 npm 包管理工具下載的包,共分為兩大類,分別是:項目包和全局包。
好用的第三方包:i5ting_toc
規范的包結構
package.json 為啥必須要包含 main 包入口的這個屬性呢?
這是因為在我們項目中使用 require 導入第三方包的時候其實走的就是這個 main 屬性,通過這個 main 屬性我們才能正常使用這個包的功能,即它是包的入口。
開發屬于自己的包
假設我們現在需要開發的包叫 itheima-tools 包,其需要實現下面的功能:
那么接下來我們就按照下面的步驟開發我們自己的包:
實現效果如下:
然后初始化 package.json :
如果你使用 WebStorm 進行文件創建的話,可以發現其是自動生成的,只要點擊創建就可以了。
不難發現這個 package.json 包其實就是一個 json 配置對象:
但是我們需要改一下,改成 PPT 中的那樣,否則沒辦法發布到 NPM 的服務器上:
上面需要解釋的幾個含義:
main:就是入口文件的位置
keywords:這是被發布到 npm 服務器上時別人可以檢索到這個包所使用的關鍵字有哪些
license:所遵循的開源許可協議,npm 官方推薦使用 ISC,因此我們用這個即可
然后編寫我們的包源碼:
將不同的功能進行模塊化拆分
src 是我們的新創建的源碼文件夾,拆分的部分將全部放進該文件夾中。
編寫包的說明文檔
發布包
主要有以下幾個步驟:
注意:在運行 npm login 命令之前,必須先把下包的服務器地址切換為 npm 的官方服務器。否則會導致發布包失敗!
刪除已發布的包
模塊的加載機制
Express.js
初識 Express.js
Express.js 簡介
進一步理解 Express
Express 能做什么
Express 安裝
這里看的課的老師用的 4.17.1 的版本,我看了一下現在官網的最新版本是 4.19.2:
按照包的語義化版本規范,因為是只上升了第二位數的大小,說明相較于 4.17.1 的版本,現在最新的版本也只不過是新增了一些功能并且修復了一點 bug 而已,因此我們直接安裝使用最新版即可:
查看版本:
Express 創建 web 服務器
來實現一下:
// 1、導入 Express
const express = require('express')
// 2、創建 web 服務器
const app = express()
// 3、啟動 web 服務器
// 調用 app.listen(端口號,啟動成功后的回調函數),啟動服務器
app.listen(80, ()=>{console.log('express server running at http://127.0.0.1')
})
運行效果如下:
Express 監聽 Get 和 Post 請求
示例代碼如下:
// 1、導入 Express
const express = require('express')
// 2、創建 web 服務器
const app = express()//4、監聽客戶端的 Get 和 Post 請求,并向客戶端響應具體的內容
app.get('/user',(req,res) => {//調用 express 提供的 res.send() 方法,向客戶端響應一個 JSON 對象res.send({name: 'zs',age : 20,gender: '男'})
})app.post('/user',(req,res) => {//調用 express 提供的 res.send() 方法,向客戶端響應一個文本字符串res.send('請求成功')
})// 3、啟動 web 服務器
app.listen(80, ()=>{console.log('express server running at http://127.0.0.1')
})
效果如下:
獲取 URL 中攜帶的查詢參數
示例代碼如下:
app.get('/', (req,res)=>{//通過 req.query 可以獲取到客戶端發送過來的 查詢參數//注意:默認情況下,req.query 是一個空對象console.log(req.query)res.send(req.query)
})
運行結果如下:
此時我們加上查詢參數運行結果將變為:
獲取 URL 中的動態參數
示例代碼如下:
//注意:這里的 :id 是一個動態的參數
app.get('/user/:id',(req,res)=>{// req.params 是動態匹配到的 URL 參數,默認是空對象console.log(req.params)res.send(req.params)
})
運行效果如下:
我們不止可以寫一個,可以寫多個動態參數:
//注意:這里的 :id 是一個動態的參數
app.get('/user/:id/:name',(req,res)=>{// req.params 是動態匹配到的 URL 參數,默認是空對象console.log(req.params)res.send(req.params)
})
運行效果如下:
托管靜態資源
express.static()
簡單測試一下:
運行效果如下:
托管多個靜態資源目錄
掛載路徑前綴
調試工具:nodemon 的安裝與使用
Express 路由
什么是路由
廣義上講,路由就是映射關系。
比如:
在上圖中,路由就是按鍵與服務之間的映射關系。
Express 中的路由
實際項目中的示例程序:
路由的匹配過程
路由的使用
這種方式之前就用過很多次了,只要知道有這種用法即可,實際的項目中我們不會采用這么直接的方式,因此不再贅述。
模塊化路由
創建路由模塊
我們新創建一個 router.js 模塊,也就是一個自定義模塊,在其內部創建和掛載我們的路由模塊:
// 這是路由模塊//1、導入 express
const express = require('express')
//2、創建路由對象
const router = express.Router()
//3、掛載具體的路由
router.get('/user/list',(req,res) => {res.send('Get user list')
})router.post('/user/add',(req,res) => {res.send('Add a new user')
})
//4、向外導出路由對象
module.exports = router
注冊路由模塊
// 1、導入 Express
const express = require('express')
// 2、創建 web 服務器
const app = express()// 1、導入路由模塊
const router = require('./router.js')
// 2、注冊路由模塊
app.use(router)// 3、啟動 web 服務器
app.listen(80, ()=>{console.log('express server running at http://127.0.0.1')
})
運行效果如下:
可以發現路由模塊化后也依然可以正常工作。
app.use() 函數的作用
一句話:該函數的作用就是用來注冊全局中間件的(后面就會講到了)。
為路由模塊添加前綴
Express 中間件
什么是中間件
中間件(Middleware),特指業務流程的中間處理環節。
比如:
Express 中間件的調用流程
Express 中間件的格式
next 函數的作用
Express 中間件的初體驗
代碼示例如下:
const express = require('express')const app = express()// 定義一個最簡單的中間件函數
const mw = function (req, res, next){console.log("這是最簡單的中間件函數")//把流轉關系,轉交給下一個中間件或者路由//(有中間件轉交給中間件,沒中間件就轉交給路由)next()
}app.listen(80, () => {console.log('http://localhost')
})
全局生效的中間件
示例代碼如下:
const express = require('express')const app = express()// 定義一個最簡單的中間件函數
const mw = function (req, res, next){console.log("這是最簡單的中間件函數")//把流轉關系,轉交給下一個中間件或者路由//(有中間件轉交給中間件,沒中間件就轉交給路由)next()
}//將 mw 注冊為全局生效的中間件
app.use(mw)app.get('/',(req,res)=>{console.log('Here is Home Page')res.send('Home page')
})app.get('/user',(req,res)=>{res.send('User page')
})app.listen(80, () => {console.log('http://localhost')
})
運行效果如下:
可以看見訪問瀏覽器時是成功打印了:
因為目前我們只定義了一個中間件,因此在 mw 中間件函數執行結束之后因為沒有其他中間件了,于是 next() 函數將流轉關系轉交給了路由模塊,所以先打印 “這是最簡單的中間件函數” 再打印 “Here is Home Page”。
定義全局中間件的簡化形式
中間件的作用
示例代碼:
const express = require('express')const app = express()// 定義一個最簡單的中間件函數
const mw = function (req, res, next){//獲取請求到達服務器的時間const time = Date.now()//為 req 對象掛載自定義屬性,從而把時間共享給后面的所有路由req.startTime = time//把流轉關系,轉交給下一個中間件或者路由//(有中間件轉交給中間件,沒中間件就轉交給路由)next()
}//將 mw 注冊為全局生效的中間件
app.use(mw)app.get('/',(req,res)=>{res.send('Home page'+req.startTime)
})app.get('/user',(req,res)=>{res.send('User page'+req.startTime)
})app.listen(80, () => {console.log('http://localhost')
})
運行效果:
定義多個全局中間件
局部生效的中間件
定義多個局部中間件
注意調用順序是從前往后的,即先執行 mw1 再執行 mw2。
了解中間件使用的5個使用注意事項
中間件的分類
應用級別的中間件:
路由級別的中間件:
錯誤級別的中間件:
注意:錯誤級別的中間件,必須注冊在所有路由之后!
Express 內置的中間件
express.json() 內置中間件
代碼示例:
const express = require('express')
const app = express()//注意:除了錯誤級別的中間件,其他的中間件,必須在路由之前進行配置
//通過 express.json() 這個中間件,解析表單中的 JSON 格式的數據
app.use(express.json())app.post('/user',(req,res)=>{// 在服務器中可以使用 req.body 這個屬性來接收客戶端發送過來的請求體數據// 默認情況下,如果不配置解析表單數據的中間件(也就是 express.json()),則 req.body 默認為 undefinedconsole.log(req.body)res.send('ok')
})app.listen(80, ()=>{console.log('http://localhost')
})
運行效果,我們使用 postman 工具來進行測試:
發送 JSON 類型的請求體數據如下:
此時控制臺中的輸出如下:
可以看見請求體數據被完美解析。
express.urlencoded() 內置中間件
示例代碼如下:
const express = require('express')
const app = express()//注意:除了錯誤級別的中間件,其他的中間件,必須在路由之前進行配置
//通過 express.urlencoded() 這個中間件,解析表單中的 JSON 格式的數據
//在這個函數內部我們還要傳遞一個固定的配置對象 extended ,將其設置為 false
//這是固定的寫法,只需要記住,不需要問為什么
app.use(express.urlencoded({ extended: false }))app.post('/book',(req,res)=>{// 在服務器中可以使用 req.body 這個屬性來接收客戶端發送過來的 url-encoded 格式的數據// 默認情況下,如果不配置解析 url-encoded 表單數據的中間件,則 req.body 默認為空console.log(req.body)res.send('ok')
})app.listen(80, ()=>{console.log('http://localhost')
})
發送數據類型為:
運行效果為:
可以看見正常解析了數據。
第三方中間件
自定義中間件
實現如下:
使用 Express 寫 API 接口
1、創建基本的服務器:
// app.js 主模塊
// 導入 express
const express = require('express')// 創建服務器實例
const app = express()// 調用 app.listen() 方法,指定端口號并啟動 web 服務器
app.listen(80,()=>{console.log('Express server is running at http://localhost')
})
2、創建 API 路由模塊
// apiRouter.js 路由模塊
const express = require('express')const router = express.Router()//在這里掛載對于的路由module.exports = router
然后在 app.js 中導入并注冊路由模塊:
// app.js 主模塊
// 導入 express
const express = require('express')// 創建服務器實例
const app = express()//導入路由模塊
const router = require('./apiRouter')
//把路由模塊注冊到 app 上
app.use('/api', router)// 調用 app.listen() 方法,指定端口號并啟動 web 服務器
app.listen(80,()=>{console.log('Express server is running at http://localhost')
})
3、編寫 Get 接口
// apiRouter.js 路由模塊
const express = require('express')const router = express.Router()//在這里掛載對應的路由
router.get('/get', (req,res)=>{//通過 req.query 獲取客戶端通過查詢字符串,發送到服務器的數據const query = req.query//調用 res.send() 方法,向客戶端響應處理的結果res.send({stats: 0,//0 表示處理成功,1 表示處理失敗msg: 'Get 請求成功', // 狀態描述data: query //需要響應給客戶端的數據})
})module.exports = router
運行結果如下:
4、編寫 Post 接口
// apiRouter.js 路由模塊
const express = require('express')const router = express.Router()//在這里掛載對應的路由
router.get('/get', (req,res)=>{//通過 req.query 獲取客戶端通過查詢字符串,發送到服務器的數據const query = req.query//調用 res.send() 方法,向客戶端響應處理的結果res.send({stats: 0,//0 表示處理成功,1 表示處理失敗msg: 'Get 請求成功', // 狀態描述data: query //需要響應給客戶端的數據})
})router.post('/post', (req,res)=>{//通過 req.body 獲取客戶端發送到服務器的請求體數據const body = req.body//調用 res.send() 方法,向客戶端響應處理的結果res.send({stats: 0,//0 表示處理成功,1 表示處理失敗msg: 'Post 請求成功', // 狀態描述data: body //需要響應給客戶端的數據})
})module.exports = router
要解析表單數據別忘了在導入路由之前先添加配置解析表單數據的中間件:
// app.js 主模塊
// 導入 express
const express = require('express')// 創建服務器實例
const app = express()//配置解析表單數據的中間件
app.use(express.urlencoded({ extended: false}))
app.use(express.json())//導入路由模塊
const router = require('./apiRouter')
//把路由模塊注冊到 app 上
app.use('/api', router)// 調用 app.listen() 方法,指定端口號并啟動 web 服務器
app.listen(80,()=>{console.log('Express server is running at http://localhost')
})
運行效果如下:
接口的跨域問題
接口跨域問題是指在Web開發中,由于瀏覽器的同源策略(Same-Origin Policy),導致在一個域下的網頁無法直接訪問另一個域下的資源。"跨域"指的是在瀏覽器中,當一個頁面的腳本請求訪問另一個域下的資源時,如果這個資源的域名、協議或端口與當前頁面所在的域不一致,就會引發跨域問題。
跨域問題會影響到包括Ajax請求、Web字體加載、嵌入式框架(如iframe)加載等場景。具體來說,如果你在一個網頁中使用Ajax請求另一個域下的數據,瀏覽器會阻止這個請求,因為涉及到跨域。
解決接口跨域問題的方法有很多,包括使用代理服務器、JSONP、CORS(跨域資源共享)等。CORS是一種比較常用的解決方案,它通過在服務器端設置一些響應頭,來告訴瀏覽器允許跨域請求。JSONP則是一種利用
實際開發中,我們推薦使用 CORS 的方法來解決跨域問題。
使用 CORS 中間件解決跨域問題
第一步:安裝
第二步和第三步:在路由之前先配置并注冊 cors 中間件到 app.js 上
// app.js 主模塊
// 導入 express
const express = require('express')// 創建服務器實例
const app = express()//配置解析表單數據的中間件
app.use(express.urlencoded({ extended: false}))
app.use(express.json())//在路由之前先配置 cors 中間件解決跨域問題
const cors = require('cors')
//注冊 cors 中間件
app.use(cors())//導入路由模塊
const router = require('./apiRouter')
//把路由模塊注冊到 app 上
app.use('/api', router)// 調用 app.listen() 方法,指定端口號并啟動 web 服務器
app.listen(80,()=>{console.log('Express server is running at http://localhost')
})
這樣就可以解決跨域問題啦。
什么是 CORS
CORS 的注意事項
CORS 響應頭部 -Access-Control-Allow-Origin
CORS 響應頭部 -Access-Control-Allow-Headers
CORS 響應頭部 -Access-Control-Allow-Methods
CORS 請求的分類
簡單請求
預檢請求
在瀏覽器與服務器正式通信之前,瀏覽器會先發送 OPTION 請求進行預檢,以獲知服務器是否允許該實際請求,所以這一次的 OPTION 請求稱為 ”預檢請求“ 。服務器成功響應預檢請求后,才會發送真正的請求,并且攜帶真實數據。
簡單請求和預檢請求之間的區別
數據庫與身份認證
注意這一章節的學習中省略了 MySQL 相關的基礎內容,如果不會的話建議先去系統學一下 MySQL 捏。
這里只講 Express 中如何操作 MySQL 數據庫嗷。
測試的表數據如下:
在 Express 中操作 MySQL
在項目中操作 MySQL 數據庫的步驟:
安裝 mysql 模塊:
配置 mysql 模塊:
示例代碼如下:
//mysql-cfg.js 數據庫模塊
//1、導入 mysql 模塊
const mysql = require('mysql')//2、建議與 mysql 數據庫的連接關系
const db = mysql.createPool({host: '127.0.0.1', //數據庫的 ip 地址user: 'root', //登錄數據庫的賬號password: '123456', //登錄數據庫的密碼database: 'node' //指定要操作哪一個數據庫
})
測試一下 mysql 模塊是否能正常工作:
//mysql-cfg.js 數據庫模塊
//1、導入 mysql 模塊
const mysql = require('mysql')//2、建議與 mysql 數據庫的連接關系
const db = mysql.createPool({host: '127.0.0.1', //數據庫的 ip 地址user: 'root', //登錄數據庫的賬號password: '123456', //登錄數據庫的密碼database: 'node' //指定要操作哪一個數據庫
})// 測試 mysql 模塊能否正常工作
// select 1 這條語句沒有任何意義,只是用來檢查 mysql 能否正常工作
db.query('select 1',(err, results)=>{// mysql 模塊工作期間報錯了if(err){return console.log(err.message)}//否則就是能夠正常的執行 SQL 語句console.log(results)
})
測試運行結果如下:
可以看見我們的 mysql 模塊是沒有問題的。
使用 mysql 模塊操作 MySQL 數據庫
查詢數據
示例代碼如下:
//mysql-cfg.js 數據庫模塊
//1、導入 mysql 模塊
const mysql = require('mysql')//2、建議與 mysql 數據庫的連接關系
const db = mysql.createPool({host: '127.0.0.1', //數據庫的 ip 地址user: 'root', //登錄數據庫的賬號password: '123456', //登錄數據庫的密碼database: 'node' //指定要操作哪一個數據庫
})// 查詢 users 表中所有的數據
const str = "select * from user"
db.query(str,(err, results)=>{// mysql 模塊工作期間報錯了if(err){return console.log(err.message)}//否則就是能夠正常的執行 SQL 語句console.log(results)
})
運行結果如下:
注意:如果執行的是 select 查詢語句,則執行的結果是數組。
插入數據
示例代碼:
//mysql-cfg.js 數據庫模塊
//1、導入 mysql 模塊
const mysql = require('mysql')//2、建議與 mysql 數據庫的連接關系
const db = mysql.createPool({host: '127.0.0.1', //數據庫的 ip 地址user: 'root', //登錄數據庫的賬號password: '123456', //登錄數據庫的密碼database: 'node' //指定要操作哪一個數據庫
})// 向 user 表中新增一條數據,其中username的值為 Spider,password 的值為123
const user = {username:'Spider', password:'123'}
//定義待執行的 SQL 語句
const sqlStr = 'insert into user (username, password) values (?, ?)'
//執行 SQL 語句
db.query(sqlStr,[user.username,user.password],(err,results)=>{if(err) return console.log(err.message)// 注意:如果執行的是 insert into 插入語句,則 results 是一個對象// 可以通過 affectedRows 屬性,來判斷是否插入數據成功if(results.affectedRows === 1) console.log('插入數據成功')
})
插入數據的便捷方式
示例代碼:
//mysql-cfg.js 數據庫模塊
//1、導入 mysql 模塊
const mysql = require('mysql')//2、建議與 mysql 數據庫的連接關系
const db = mysql.createPool({host: '127.0.0.1', //數據庫的 ip 地址user: 'root', //登錄數據庫的賬號password: '123456', //登錄數據庫的密碼database: 'node' //指定要操作哪一個數據庫
})// 向 user 表中新增一條數據,其中username的值為 Spider,password 的值為123
const user = {username:'Spider', password:'123'}
//定義待執行的 SQL 語句
//insert into 表名 set ?
const sqlStr = 'insert into user set ?'
//執行 SQL 語句
// 參數也不需要用數組形式了,直接將原對象進行插入即可(類似于一個 JavaBean)
db.query(sqlStr,user,(err,results)=>{if(err) return console.log(err.message)// 注意:如果執行的是 insert into 插入語句,則 results 是一個對象// 可以通過 affectedRows 屬性,來判斷是否插入數據成功if(results.affectedRows === 1) console.log('插入數據成功')
})
運行結果如下:
數據庫中也是正常的新增了數據:
更新數據
示例代碼:
//mysql-cfg.js 數據庫模塊
//1、導入 mysql 模塊
const mysql = require('mysql')//2、建議與 mysql 數據庫的連接關系
const db = mysql.createPool({host: '127.0.0.1', //數據庫的 ip 地址user: 'root', //登錄數據庫的賬號password: '123456', //登錄數據庫的密碼database: 'node' //指定要操作哪一個數據庫
})// 向 user 表中修改一條數據
const user = { id: 3, username:'阿斯頓', password:'123' }
//定義待執行的 SQL 語句
const sqlStr = 'update user set username=?,password=? where id=?'
//執行 SQL 語句
db.query(sqlStr,[user.username,user.password,user.id],(err,results)=>{if(err) return console.log(err.message)// 注意:如果執行的是 update 更新語句,則 results 是一個對象// 可以通過 affectedRows 屬性,來判斷是否更新數據成功if(results.affectedRows === 1) console.log('更新數據成功')
})
運行結果如下:
更新數據的便捷方式
刪除數據
標記刪除
前后端中的身份認證
Web 開發模式
目前主流的 Web 開發模式有兩種,分別是:
1、基于服務端渲染的傳統 Web 開發模式
2、基于前后端分離的新型 Web 開發模式
基于服務端渲染的傳統 Web 開發模式
基于前后端分離的新型 Web 開發模式
如何選擇 Web 開發模式
身份認證
什么是身份認證?
為什么需要身份認證
不同開發模式下的身份認證
HTTP 協議的無狀態性
如何突破 HTTP 無狀態的限制
什么是 Cookie
Cookie 在身份認證中的作用
Cookie 不具有安全性
注意:千萬不要使用 Cookie 存儲重要且隱私的數據!比如用戶的身份信息、密碼等。
提高身份認證的安全性
Session 的工作原理
在 Express 中使用 Session 認證
配置 Session
只需要下面幾步就可以使用 Session 認證了嗷:
配置對象中的 secret 屬性是一串字符串,用來加密用的。
接下來我們完成一下這些事情:
安裝 express-session 中間件:
開始配置 express-session 的全局中間件:
// app.js 主模塊
// 導入 express
const express = require('express')// 創建服務器實例
const app = express()// 引入session
const session = require('express-session')
// 注冊 session
app.use(session({secret: 'hahaha',resave: false,saveUninitialized: true
}))//配置解析表單數據的中間件
app.use(express.urlencoded({ extended: false}))
app.use(express.json())//在路由之前先配置 cors 中間件解決跨域問題
const cors = require('cors')
//注冊 cors 中間件
app.use(cors())//導入路由模塊
const router = require('./apiRouter')
//把路由模塊注冊到 app 上
app.use('/api', router)// 調用 app.listen() 方法,指定端口號并啟動 web 服務器
app.listen(80,()=>{console.log('Express server is running at http://localhost')
})
向 Session 中存數據
從 Session 中取數據
清空 Session
注意:只是清空了當前客戶端的 Session ,這并不會影響到其他客戶端的 Session。
因為之前說過 Session 的工作原理,每個 Session 都會對應一個客戶端瀏覽器所自動存收的一個 Cookie,因此相當于各個客戶端之間是隔離連接的,互不影響,至于底層是怎么實現的應該是在這個 express-session 第三方包中自動實現的,我們只要懂得怎么使用即可。
了解 Session 認證的局限性
什么是 JWT
JWT(英文全稱:JSON Web Token)是目前最流行的跨域認證解決方案。
JWT 的工作原理
JWT 的組成部分
JWT 的三個部分各自代表的含義
JWT 的使用方式
注意:千萬別忘記了在 token 字符串前加 Bearer 這個字符串!然后后面還要跟一個空格才能再跟 token 字符串!!!
比如我們在 postman 中要帶 token 的話,那么形式應該是下面這樣:
在 Express 中使用 JWT
主要有下面幾個步驟:
上圖中的第三個參數配置對象里,我們配置了該 Token 的過期時間為 30s,如果要設置成分鐘則后綴用 m ,小時的話設置為 h 即可。
注意:只要配置成功了 express-jwt 這個中間件,就可以把解析出來的用戶信息,掛載到 req.user 屬性上。
換句話說就是,req 本身是沒有 user 屬性的,但是只要配置成功了 express-jwt 這個中間件后,req 就有一個 user 屬性了,該屬性是由該中間件創建的。
而這個 user 對象中包含多少信息是由我們自己決定的,也就是在登錄成功之后我們進行加密的用戶信息對象信息有多少那么 user 對象中就有多少信息。
比如我們上面的示例中將 username 進行了加密,那么被創建的 user 對象就會只有一個 username 屬性。
如果我們的訪問請求攜帶了 token 進行 API 訪問的話,其返回值會有下面兩個額外的屬性:
iat 和 exp ,不過不用管,這是這個 JWT 第三方工具包用來控制過期時間用的兩個屬性。