整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          百度工程師帶你體驗引擎中的nodejs

          者 | 糖果candy


          導讀

          introduction

          如果你是一個前端程序員,你不懂得像PHP、Python或Ruby等動態編程語言,然后你想創建自己的服務,那么Node.js是一個非常好的選擇。

          Node.js 是運行在服務端的 JavaScript,如果你熟悉Javascript,那么你將會很容易學會Node.js。

          當然,如果你是后端程序員,想部署一些高性能的服務,那么學習Node.js也是一個非常好的選擇。


          全文6723字,預計閱讀時間17分鐘。


          GEEK TALK

          01

          什么是Node.js?

          我們先看一下官方對Node.js的定義:Node.js是一個基于V8 JavaScript引擎的JavaScript運行時環境。

          但是這句話可能有點籠統:

          1、什么是JavaScript運行環境?

          2、為什么JavaScript需要特別的運行環境呢?

          3、什么又是JavaScript引擎?

          4、什么是V8?

          帶著這些疑問我們先了解一下nodejs的歷史,在 Node.js 出現之前,最常見的 JavaScript 運行時環境是瀏覽器,也叫做 JavaScript 的宿主環境。瀏覽器為 JavaScript 提供了 DOM API,能夠讓 JavaScript 操作瀏覽器環境(JS 環境)。

          2009 年初 Node.js 出現了,它是基于 Chrome V8 引擎開發的 JavaScript 運行時環境,所以 Node.js 也是 JavaScript 的一種宿主環境。而它的底層就是我們所熟悉的 Chrome 瀏覽器的 JavaScript 引擎,因此本質上和在 Chrome 瀏覽器中運行的 JavaScript 并沒有什么區別。但是,Node.js 的運行環境和瀏覽器的運行環境還是不一樣的。

          通俗點講,也就是說Node.js基于V8引擎來執行JavaScript的代碼,但是不僅僅只有V8引擎。

          我們知道V8可以嵌入到任何C++應用程序中,無論是Chrome還是Node.js,事實上都是嵌入了V8引擎來執行JavaScript代碼,但是在Chrome瀏覽器中,還需要解析、渲染HTML、CSS等相關渲染引擎,另外還需要提供支持瀏覽器操作的API、瀏覽器自己的事件循環等。

          另外,在Node.js中我們也需要進行一些額外的操作,比如文件系統讀/寫、網絡IO、加密、壓縮解壓文件等操作。

          那么接下來我們來看一下瀏覽器是如何解析渲染的。


          GEEK TALK

          02

          瀏覽器是怎么渲染一個頁面的?

          瀏覽器渲染一個網頁,簡單來說可以分為以下幾個步驟:

          • HTML 解析:在這個過程之前,瀏覽器會進行 DNS 解析及 TCP 握手等網絡協議相關的操作,來與用戶需要訪問的域名服務器建議連接,域名服務器會給用戶返回一個 HTML 文本用于后面的渲染 (這一點很關鍵,要注意)。
          • 渲染樹的構建:瀏覽器客戶端在收到服務端返回的 HTML 文本后,會對 HTML 的文本進行相關的解析,其中 DOM 會用于生成 DOM 樹來決定頁面的布局結構,CSS 則用于生成 CSSOM 樹來決定頁面元素的樣式。如果在這個過程遇到腳本或是靜態資源,會執行預加載對靜態資源進行提前請求,最后將它們生成一個渲染樹。

          • 布局:瀏覽器在拿到渲染樹后,會進行布局操作,來確定頁面上每個對象的大小和位置,再進行渲染。
          • 渲染:我們電腦的視圖都是通過 GPU 的圖像幀來顯示出來的,渲染的過程其實就是將上面拿到的渲染樹轉化成 GPU 的圖像幀來顯示。
          • 首先,瀏覽器會根據布局樹的位置進行柵格化(用過組件庫的同學應該不陌生,就是把頁面按行列分成對應的層,比如 12 柵格,根據對應的格列來確定位置),最后得到一個合成幀,包括文本、顏色、邊框等;其次,將合成幀提升到 GPU 的圖像幀,進而顯示到頁面中,就可以在電腦上看到我們的頁面了。

          相信看到這里,大家對瀏覽器怎么渲染出一個頁面已經有了大致的了解。頁面的繪制其實就是瀏覽器將 HTML 文本轉化為對應頁面幀的過程,頁面的內容及渲染過程與第一步拿到的 HTML 文本是緊密相關的。


          GEEK TALK

          03

          事件循環和異步IO

          首先要了解事件循環是什么?那么我們先了解進程和線上的概念。

          • 進程和線程:都是操作系統的概念。
          • 進程:計算機已經運行的程序,線程:操作系統能夠調度的最小單位啟動一個應用默認是開啟一個進程(也可能是多進程)。

          每一個進程中都會啟動一個線程用來執行程序中的代碼,這個線程稱為主線程。

          舉例子:比如工廠相當于操作系統,工廠里的車間相當于進程,車間里的工人相當于是線程,所以進程相當于是線程的容器。


          那么瀏覽器是一個進程嗎?它里面是只有一個線程嗎?

          目前瀏覽器一般都是多進程的,一般開啟一個tab就會開啟一個新的進程,這個是防止一個頁面卡死而造成的所有頁面都無法響應,整個瀏覽器需要強制退出。

          其中每一個進程當中有包含了多個線程,其中包含了執行js代碼的線程。

          js代碼是在一個單獨的線程中執行的,單線程同一時間只能做一件事,如果這件事非常耗時,就意味著當前的線程會被阻塞,瀏覽器時間循環維護兩個隊列:宏任務隊列和微任務隊列。

          • 宏任務隊列(macrotask queue):ajax、setTimeout、setInterval、DOM監聽、UI Rendering等;
          • 微任務隊列(microtask queue):Promise的then回調。


          那么事件循環對于兩個隊列的優先級是怎么樣的呢?

          1. main script中的代碼優先執行(編寫的頂層script代碼);
          2. 在執行任何一個宏任務之前(不是隊列,是一個宏任務),都會先查看微任務隊列中是否有任務需要執行也就是宏任務執行之前,必須保證微任務隊列是空的;
          3. 如果不為空,那么久優先執行微任務隊列中的任務(回調)。

          new promise()是同步,promise.then,promise.catch,resolve,reject 是微任務。


          GEEK TALK

          04

          使用事件驅動程序

          Node.js 使用事件驅動模型,當web server接收到請求,就把它關閉然后進行處理,然后去服務下一個web請求。

          當這個請求完成,它被放回處理隊列,當到達隊列開頭,這個結果被返回給用戶。

          這個模型非常高效可擴展性非常強,因為webserver一直接受請求而不等待任何讀寫操作。(這也被稱之為非阻塞式IO或者事件驅動IO)

          在事件驅動模型中,會生成一個主循環來監聽事件,當檢測到事件時觸發回調函數。


          整個事件驅動的流程就是這么實現的,非常簡潔。有點類似于觀察者模式,事件相當于一個主題(Subject),而所有注冊到這個事件上的處理函數相當于觀察者(Observer)。

          Node.js 有多個內置的事件,我們可以通過引入 events 模塊,并通過實例化 EventEmitter 類來綁定和監聽事件,如下實例:

          // 引入 events 模塊
          var events = require('events');
          // 創建 eventEmitter 對象
          var eventEmitter = new events.EventEmitter();

          以下程序綁定事件處理程序:

          // 綁定事件及事件的處理程序
          eventEmitter.on('eventName', eventHandler);

          我們可以通過程序觸發事件:

          // 觸發事件
          eventEmitter.emit('eventName');


          實例

          創建 main.js 文件,代碼如下所示:

          // 引入 events 模塊var events = require('events');
          // 創建 eventEmitter 對象var eventEmitter = new events.EventEmitter();
          
          
          // 創建事件處理程序var connectHandler = functionconnected() {
             console.log('連接成功~~~');
          
          
             // 觸發 data_received 事件 
             eventEmitter.emit('data_received');
          }
          
          
          // 綁定 connection 事件處理程序
          eventEmitter.on('connection', connectHandler);
          
          
          // 使用匿名函數綁定 data_received 事件
          eventEmitter.on('data_received', function(){
             console.log('數據接收完畢。');
          });
          
          
          // 觸發 connection 事件 
          eventEmitter.emit('connection');
          
          
          console.log("程序執行完畢。");

          接下來讓我們執行以上代碼:

          $ node main.js
          連接成功~~~
          數據接收完畢。
          程序執行完畢。


          GEEK TALK

          05

          Node.js架構以及與瀏覽器的區別


          上圖是 Node.js 的基本架構,我們可以看到,(Node.js 是運行在操作系統之上的),它底層由 V8 JavaScript 引擎,以及一些 C/C++ 寫的庫構成,包括 libUV 庫、c-ares、llhttp/http-parser、open-ssl、zlib 等等。

          其中,libUV 負責處理事件循環,c-ares、llhttp/http-parser、open-ssl、zlib 等庫提供 DNS 解析、HTTP 協議、HTTPS 和文件壓縮等功能。

          在這些模塊的上一層是中間層,中間層包括Node.js Bindings、Node.js Standard Library以及C/C++ AddOns。Node.js Bindings層的作用是將底層那些用 C/C++ 寫的庫接口暴露給 JS 環境,而Node.js Standard Library是 Node.js 本身的核心模塊。至于C/C++ AddOns,它可以讓用戶自己的 C/C++ 模塊通過橋接的方式提供給Node.js。

          中間層之上就是 Node.js 的 API 層了,我們使用 Node.js 開發應用,主要是使用 Node.js 的 API 層,所以 Node.js 的應用最終就運行在 Node.js 的 API 層之上。

          總結一下:Node.js 系統架構圖,主要就是application、V8 javascript 引擎、Node.js bindings, libuv這4個部分組成的。

          • Application: nodejs應用,就是我們寫的js代碼。
          • V8: JavaScript 引擎,分析js代碼后去調用Node api。
          • Node.js bindings:Node api,這些API最后由libuv驅動。
          • Libuv:異步I/O,實現異步非阻塞式的核心模塊,libuv這個庫提供兩個最重要的東西是事件循環和線程池,兩者共同構建了異步非阻塞I/O模型。
          • 以線程為緯度來劃分,可以分為Node.js線程和其他C++線程。
          • 應用程序啟動一個線程,在一個Node.js線程里完成,Node.js的I/O操作都是非阻塞式的,把大量的計算能力分發到其他的C++線程,C++線程完成計算后,再把結果回調到Node.js線程,Node.js線程再把內容返回給應用程序。

          瀏覽器中的事件循環是根據HTML5規范來實現的,不同的瀏覽器可能有不同的實現,而node中是libuv 實現的

          因為 Node.js 不是瀏覽器,所以它不具有瀏覽器提供的 DOM API。

          1. 比如 Window 對象、Location 對象、Document 對象、HTMLElement 對象、Cookie 對象等等。
          2. 但是,Node.js 提供了自己特有的 API,比如全局的 global 對象,
          3. 也提供了當前進程信息的 Process 對象,操作文件的 fs 模塊,以及創建 Web 服務的 http 模塊等等。這些 API 能夠讓我們使用 JavaScript 操作計算機,所以我們可以用 Node.js 平臺開發 web 服務器。

          也有一些對象是 Node.js 和瀏覽器共有的,如 JavaScript 引擎的內置對象,它們由 V8 引擎提供。常見的還有:

          • 基本的常量 undefined、null、NaN、Infinity;
          • 內置對象 Boolean、Number、String、Object、Symbol、Function、Array、Regexp、Set、Map、Promise、Proxy;
          • 全局函數 eval、encodeURIComponent、decodeURIComponent等等。

          此外,還有一些方法不屬于引擎內置 API,但是兩者都能實現,比如 setTimeout、setInterval 方法,Console 對象等等。


          5.1 阻塞IO和非阻塞IO

          如果我們希望在程序中對一個文件進行操作,那么我們就需要打開這個文件:通過文件描述符。

          我們思考:JavaScript可以直接對一個文件進行操作嗎?

          看起來是可以的,但是事實上我們任何程序中的文件操作都是需要進行系統調用(操作系統的文件系統);事實上對文件的操作,是一個操作系統的IO操作(輸入、輸出)。

          操作系統為我們提供了阻塞式調用和非阻塞式調用:

          • 阻塞式調用: 調用結果返回之前,當前線程處于阻塞態(阻塞態CPU是不會分配時間片的),調用線程只有在得到調用結果之后才會繼續執行。
          • 非阻塞式調用: 調用執行之后,當前線程不會停止執行,只需要過一段時間來檢查一下有沒有結果返回即可。

          所以我們開發中的很多耗時操作,都可以基于這樣的 非阻塞式調用:
          比如網絡請求本身使用了Socket通信,而Socket本身提供了select模型,可以進行非阻塞方式的工作;
          比如文件讀寫的IO操作,我們可以使用操作系統提供的基于事件的回調機制。


          5.2 非阻塞IO的問題

          但是非阻塞IO也會存在一定的問題:我們并沒有獲取到需要讀取(我們以讀取為例)的結果,那么就意味著為了可以知道是否讀取到了完整的數據,我們需要頻繁的去確定讀取到的數據是否是完整的。

          這個過程我們稱之為輪訓操作。

          那么這個輪訓的工作由誰來完成呢?
          如果我們的主線程頻繁的去進行輪訓的工作,那么必然會大大降低性能,并且開發中我們可能不只是一個文件的讀寫,可能是多個文件,而且可能是多個功能:網絡的IO、數據庫的IO、子進程調用。
          libuv提供了一個線程池(Thread Pool):線程池會負責所有相關的操作,并且會通過輪訓等方式等待結果。當獲取到結果時,就可以將對應的回調放到事件循環(某一個事件隊列)中。事件循環就可以負責接管后續的回調工作,告知JavaScript應用程序執行對應的回調函數。


          5.3 阻塞和非阻塞,同步和異步的區別?

          首先阻塞和非阻塞是對于被調用者來說的;在我們這里就是系統調用,操作系統為我們提供了阻塞調用和非阻塞調用,同步和異步是對于調用者來說的。

          • 在我們這里就是自己的程序;
          • 如果我們在發起調用之后,不會進行其他任何的操作,只是等待結果,這個過程就稱之為同步調用;
          • 如果我們再發起調用之后,并不會等待結果,繼續完成其他的工作,等到有回調時再去執行,這個過程就是異步調用。

          Libuv采用的就是非阻塞異步IO的調用方式。


          5.4 Node事件循環的階段

          我們最前面就強調過,事件循環像是一個橋梁,是連接著應用程序的JavaScript和系統調用之間的通道:

          無論是我們的文件IO、數據庫、網絡IO、定時器、子進程,在完成對應的操作后,都會將對應的結果和回調
          函數放到事件循環(任務隊列)中;
          事件循環會不斷的從任務隊列中取出對應的事件(回調函數)來執行;
          但是一次完整的事件循環Tick分成很多個階段:

          定時器(Timers):本階段執行已經被 setTimeout() 和 setInterval() 的調度回調函數。
          待定回調(Pending Callback):對某些系統操作(如TCP錯誤類型)執行回調,比如TCP連接時接收

          idle, prepare:僅系統內部使用。
          輪詢(Poll):檢索新的 I/O 事件;執行與 I/O 相關的回調;
          檢測:setImmediate() 回調函數在這里執行。
          關閉的回調函數:一些關閉的回調函數,如:socket.on('close', ...)。


          5.5 Node事件循環的階段圖解


          GEEK TALK

          06

          Node.js常見的內置模塊與全局變量

          如想了解更多全局對象可參考以下鏈接 :https://m.runoob.com/nodejs/nodejs-global-object.html

          作者:糖果candy

          來源:微信公眾號:百度Geek說

          出處:https://mp.weixin.qq.com/s/QI9l4kKCbJxifNZt3QP3Lg

          . 基礎概念

          Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行環境,使用了一個事件驅動、非阻塞式 I/O 模型,讓 JavaScript 運行在服務端的開發平臺。

          官方地址:https://nodejs.org/en

          中文地址:https://nodejs.org/zh-cn

          代碼初體驗:

           console.log("hello NodeJS")
          
          // 1.進入到對應 js 文件的目錄下
          // 2.執行 node 1-hello.js 
          // 3.輸出:hello NodeJS

          示例代碼地址:https://github.com/chenyl8848/node.js-study

          1. Buffer

          1.1 概念

          Buffer 是一個類似于數組的對象,用于表示固定長度的字節序列。Buffer 本質是一段內存空間,專門用來處理二進制數據。

          1.2 特點

          • Buffer 大小固定且無法調整
          • Buffer 性能較好,可以直接對計算機內存進行操作
          • 每個元素的大小為 1 字節(byte)

          1.3 使用

          1.3.1 Buffer 的創建

          Node.js 中創建 Buffer 的方式主要如下幾種:

          Buffer.alloc

           // 創建了一個長度為 10 字節的 Buffer,相當于申請了 10 字節的內存空間,每個字節的值為 0
          // 結果為 <Buffer 00 00 00 00 00 00 00 00 00 00>
          let buf_1 = Buffer.alloc(10);

          Buffer.allocUnsafe

           // 創建了一個長度為 10 字節的 Buffer,Buffer 中可能存在舊的數據, 可能會影響執行結果,所以叫 unsafe
          let buf_2 = Buffer.allocUnsafe(10);

          Buffer.from

           // 通過字符串創建 Buffer
          let buf_3 = Buffer.from('hello');
          // 通過數組創建 Buffer
          let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);

          1.3.2 Buffer 與字符串的轉化

          可以借助 toString 方法將 Buffer 轉為字符串

           // buffer 與字符串的轉換
          const buffer = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
          // 默認使用 UTF-8 的編碼格式 
          console.log(buffer.toString())

          注意:toString 默認是按照 utf-8 編碼方式進行轉換的。

          1.3.3 Buffer 的操作

          Buffer 可以直接通過 [] 的方式對數據進行處理。

           const buffer = Buffer.from('hello');
          // 二進制:1101000
          console.log(buffer[0].toString(2))
          
          // 修改 buffer
          buffer[0] = 95
          console.log(buffer.toString())
          
          // 溢出 如果修改的數值超過 255 ,則超過 8 位數據會被舍棄
          const buffer = Buffer.from('hello')
          // 會舍棄高位的數字,因為八位的二進制最高值為 255   0001 0110 1001  => 0110 1001
          buffer[0] = 361
          console.log(buffer)
          
          // 中文 一個 utf-8 的字符 一般 占 3 個字節
          const buffer = Buffer.from('你好')
          console.log(buffer)

          注意:

          如果修改的數值超過255,則超過8位數據會被舍棄 一個 utf-8 的字符一般占3個字節

          2. fs 模塊

          fs 全稱為 file system,稱之為文件系統,是 Node.js 中的內置模塊,可以對計算機中的磁盤進行操作。

          2.1 文件寫入

          文件寫入就是將數據保存到文件中,有如下的 API

          方法

          說明

          writeFile

          異步寫入

          writeFileSync

          同步寫入

          appendFile/appendFileSync

          追加寫入

          createWriteStream

          流式寫入

          2.1.1 異步寫入

          語法: fs.writeFile(file, data, [options], callback)

          參數說明:

          • file:文件名
          • data:待寫入的數據
          • options:選項設置 (可選)
          • callback:寫入回調

          返回值: undefined

          代碼示例:

           // 1.導入 fs 模塊
          const fs = require('fs')
          
          // 2.寫入文件
          // writeFile 異步寫入,四個參數:1.文件路徑 2.寫入內容 3.配置信息 4.回調函數
          // 文件寫入成功
          fs.writeFile('./座右銘.txt', '封狼居胥,禪于姑衍,飲馬瀚海', error => {
              // errror 為 null就是寫入成功
              if (error) {
                  console.log('文件寫入失敗')
                  return;
              }
              console.log('文件寫入成功')
          });

          2.1.2 同步寫入

          語法: fs.writeFileSync(file, data, [options]) 參數說明:

          • file:文件名
          • data:待寫入的數據
          • options:選項設置 (可選) 返回值: undefined 代碼示例:
           // 1.導入 fs 模塊
          const fs = require('fs')
          
          // 2.寫入文件
          // 同步寫入,沒有回調函數
          fs.writeFileSync('./座右銘.txt', '封狼居胥,禪于姑衍,飲馬瀚海,燕石勒然')

          Node.js 中的磁盤操作是由其他線程完成的,結果的處理有兩種模式:

          同步處理:JavaScript 主線程會等待其他線程的執行結果,然后再繼續執行主線程的代碼,效率較低

          異步處理:JavaScript 主線程不會等待其他線程的執行結果,直接執行后續的主線程代碼,效率較好

          2.1.3 追加寫入

          appendFile 作用是在文件尾部追加內容,appendFile 語法與 writeFile 語法完全相同

          語法:

          • fs.appendFile(file, data, [options], callback)
          • fs.appendFileSync(file, data, [options])

          返回值: 二者都為 undefined

          代碼示例:

           // 1.引入 fs 模塊
          const fs = require('fs');
          
          const content = '\r\n但使龍城飛將在,不教胡馬度陰山';
          
          // fs.appendFile('./座右銘.txt', content, error => {
          //     // errror 為 null就是寫入成功
          //     if (error) {
          //         console.log('文件追加寫入失敗')
          //         return;
          //     }
          //     console.log('文件追加寫入成功');
          // })
          
          // 同步文件追加寫入
          // fs.appendFileSync('./座右銘.txt', content)
          
          // 使用 writeFile 的方式追加文件寫入
          fs.writeFile('./座右銘.txt', content, {flag: 'a'}, error => {
              if (error) {
                  console.log('文件追加寫入失敗')
                  return;
              }
              console.log('文件追加寫入成功')
          })
          
          console.log('hello world')

          2.1.4 流式寫入

          語法: fs.createWriteStream(path, [options])

          參數說明:

          • path:文件路徑
          • options:選項配置( 可選 )

          返回值: Object

          代碼示例:

           // 1.導入 fs 模塊
          const fs = require('fs');
          
          // 2.創建寫入流對象
          const writeStream = fs.createWriteStream('./觀書有感.txt');
          
          // 3.寫入內容
          writeStream.write('半畝方塘一鑒開\r\n')
          writeStream.write('天光云影共徘徊\r\n')
          writeStream.write('問渠那得清如許\r\n')
          writeStream.write('為有源頭活水來\r\n')
          
          // 4.關閉通道 不是必須
          // writeStream.close();

          程序打開一個文件是需要消耗資源的,流式寫入可以減少打開關閉文件的次數。

          流式寫入方式適用于大文件寫入或者頻繁寫入的場景, writeFile 適合于寫入頻率較低的場景。

          2.2 文件讀取

          文件讀取顧名思義,就是通過程序從文件中取出其中的數據,有如下幾種 API:

          方法

          說明

          readFile

          異步讀取

          readFileSync

          同步讀取

          createReadStream

          流式讀取

          2.2.1 異步讀取

          語法: fs.readFile(path, [options], callback)

          參數說明:

          • path:文件路徑
          • options:選項配置
          • callback:回調函數

          返回值: undefined

          代碼示例:

           // 1.引入 fs 模塊
          const fs = require('fs')
          
          // 2.異步讀取
          fs.readFile('./座右銘.txt', (error, data) => {
              if (error) {
                  console.log('文件讀取錯誤')
                  return
              }
              console.log(data.toString())
          })

          2.2.2 同步讀取

          語法: fs.readFileSync(path, [options])

          參數說明:

          • path:文件路徑
          • options:選項配置

          返回值: string | Buffer

          代碼示例:

           // 1.引入 fs 模塊
          const fs = require('fs')
          
          // 2.同步讀取
          let data = fs.readFileSync('./座右銘.txt');
          console.log(data.toString())

          2.2.3 流式讀取

          語法: fs.createReadStream(path, [options])

          參數說明:

          • path:文件路徑
          • options:選項配置(可選)

          返回值:Object

          代碼示例:

           // 1.導入 fs 模塊
          const fs = require('fs')
          
          // 2.創建讀取流對象
          const rs = fs.createReadStream('./觀書有感.txt');
          
          // 3.綁定 data 事件
          rs.on('data', chunk => {
              // chunk:塊兒、大塊兒
              console.log(chunk)
              console.log(chunk.length)
              console.log(chunk.toString())
          })
          
          // 4.結束事件(可選)
          rs.on('end', () => {
              console.log('文件讀取完成')
          })

          2.3 文件移動與重命名

          Node.js 中,可以使用 rename 或 renameSync 來移動或重命名文件或文件夾

          語法:

          • fs.rename(oldPath, newPath, callback)
          • fs.renameSync(oldPath, newPath)

          參數說明:

          • oldPath:文件當前的路徑
          • newPath:文件新的路徑
          • callback:操作后的回調

          代碼示例:

           // 1.導入 fs 模塊
          const fs = require('fs');
          
          // 2.文件重命名
          // fs.rename('./座右銘-2.txt', './西漢名將.txt', error => {
          //     if (error) {
          //         console.log('文件重命名失敗')
          //            return ;
          //     }
          //     console.log('文件重命名成功')
          // })
          
          // 3.文件移動
          // 文件夾如果不存在,會報錯誤 Error: ENOENT: no such file or directory
          fs.rename('./西漢名將.txt', './文件/西漢名將.txt', error => {
              if (error) {
                  console.log(error, '移動文件出錯');
                  return ;
              }
              console.log('操作成功')
          })

          2.4 文件刪除

          Node.js 中,可以使用 unlink 或 unlinkSync 或 rm 或 rmSync 來刪除文件

          語法:

          • fs.unlink(path, callback)
          • fs.unlinkSync(path)

          參數說明:

          • path:文件路徑
          • callback:操作后的回調

          代碼示例:

           // 1.引入 fs 模塊
          const fs = require('fs')
          
          // 2.調用 unlink 方法 
          // unlinkSync:同步刪除
          // fs.unlink('./座右銘-3.txt', error => {
          //     if (error) {
          //         console.log('刪除文件錯誤', error)
          //         return;
          //     }
          //     console.log('刪除文件成功')
          // })
          
          // 3.調用 rm 方法
          // rmSync:同步刪除
          fs.rm('./文件/西漢名將.txt', error => {
              if (error) {
                  console.log('文件刪除失敗', error)
                  return;
              }
              console.log('文件刪除成功')
          })

          2.5 文件夾操作

          Node.js 中可以通過如下 API 對文件夾進行創建、讀取、刪除等操作

          方法

          說明

          mkdir / mkdirSync

          創建文件夾

          readdir / readdirSync

          讀取文件夾

          rmdir / rmdirSync

          刪除文件夾

          2.5.1 創建文件夾

          Node.js 中,可以使用 mkdir 或 mkdirSync 來創建文件夾

          語法:

          • fs.mkdir(path, [options], callback)
          • fs.mkdirSync(path, [options])

          參數說明:

          • path:文件夾路徑
          • options:選項配置(可選)
          • callback:操作后的回調

          示例代碼:

           // 1.引入 fs 模塊
          const fs = require('fs')
          
          // 2.創建文件夾
          // mkdir make:制作 directory:目錄 
          fs.mkdir('./html', error => {
              if (error) {
                  console.log('創建目錄失敗', error)
                  return;
              }
              console.log('創建目錄成功')
          })
          
          // 3.遞歸創建文件夾
          fs.mkdir('./a/b/c', {
              recursive: true
          }, error => {
              if (error) {
                  console.log("遞歸創建文件夾失敗", error)
                  return;
              }
              console.log('遞歸創建文件夾成功')
          })
          
          // 4.遞歸同步創建文件夾
          fs.mkdirSync('./a/b/c', {recursive: true});

          2.5.2 讀取文件夾

          Node.js 中,可以使用 readdir 或 readdirSync 來讀取文件夾

          語法:

          • fs.readdir(path, [options], callback)
          • fs.readdirSync(path, [options])

          參數說明:

          • path:文件夾路徑
          • options:選項配置(可選)
          • callback:操作后的回調

          示例代碼:

           // 1.引入 fs 模塊
          const fs = require('fs')
          
          // 2.讀取文件夾
          // readdir read:讀取 dir:directory 目錄
          fs.readdir('./', (error, data) => {
              if (error) {
                  console.log('讀取文件夾錯誤', error)
                  return;
              }
              // [
              //     '1-文件寫入.js',
              //     '2-追加寫入.js',
              //     '3-流式寫入.js',
              //     '4-文件讀取.js',
              //     '5-流式讀取.js',
              //     '6-練習-文件復制.js',
              //     '7-文件重命名與移動.js',
              //     '8-刪除文件.js',
              //     '9-文件夾操作.js',
              //     'a',
              //     'html',
              //     '座右銘.txt',
              //     '文件',
              //     '觀書有感.txt'
              // ]
              console.log(data)
          })
          
          //同步讀取
          // let data = fs.readdirSync('./');
          // console.log(data);

          2.5.3 刪除文件夾

          Node.js 中,可以使用 rmdir 或 rmdirSync 來刪除文件夾

          語法:

          • fs.rmdir(path, [options], callback)
          • fs.rmdirSync(path, [options]) 參數說明:
          • path:文件夾路徑
          • options:選項配置(可選)
          • callback:操作后的回調

          示例代碼:

           // 1.引入 fs 模塊
          const fs = require('fs')
          
          // 2.刪除文件夾
          // fs.rmdir('./文件', error => {
          //     if (error) {
          //         console.log('刪除文件夾失敗', error)
          //         return;
          //     }
          //     console.log('刪除文件夾成功')
          // })
          
          // 3.遞歸刪除文件夾 
          // 遞歸刪除文件夾失敗 [Error: ENOTEMPTY: directory not empty, rmdir 'E:\JavaEE\frontend\nodejs-study\2-fs文件系統\a'] 
          
          // 不推薦使用
          // fs.rmdir('./a', {recursive: true}, error => {
          //     if (error) {
          //         console.log('遞歸刪除文件夾失敗', error)
          //         return ;
          //     }
          //     console.log('遞歸刪除文件夾成功')
          // })
          
          // 推薦使用
          fs.rm('./a', {recursive: true}, error => {
              if (error) {
                  console.log('遞歸刪除文件失敗', error)
                  return;
              }
              console.log('遞歸刪除文件成功')
          })
          
          //同步遞歸刪除文件夾
          fs.rmdirSync('./a', {recursive: true})

          2.6 查看資源狀態

          Node.js 中,可以使用 stat 或 statSync 來查看資源的詳細信息

          語法:

          • fs.stat(path, [options], callback)
          • fs.statSync(path, [options])

          參數說明:

          • path:文件夾路徑
          • options:選項配置(可選)
          • callback:操作后的回調

          示例代碼:

           // 1.引入 fs 模塊
          const fs = require('fs')
          
          // 2.stat 方法 status 的縮寫:狀態
          fs.stat('./觀書有感.txt', (error, data) => {
              if (error) {
                  console.log('操作失敗', error)
                  return;
              }
              // Stats {
              //     dev: 985301708,
              //     mode: 33206,
              //     nlink: 1,
              //     uid: 0,
              //     gid: 0,
              //     rdev: 0,
              //     blksize: 4096,
              //     ino: 281474979770305,
              //     size: 92,
              //     blocks: 0,
              //     atimeMs: 1684373309132.9426,
              //     mtimeMs: 1684289136772.1648,
              //     ctimeMs: 1684289136772.1648,
              //     birthtimeMs: 1684289136770.7136,
              //     atime: 2023 - 05 - 18 T01: 28: 29.133 Z,
              //     mtime: 2023 - 05 - 17 T02: 05: 36.772 Z,
              //     ctime: 2023 - 05 - 17 T02: 05: 36.772 Z,
              //     birthtime: 2023 - 05 - 17 T02: 05: 36.771 Z
              // }
              console.log(data)
              console.log(data.isFile())
              console.log(data.isDirectory())
          })
          
          // 3.同步獲取狀態
          let data = fs.statSync('./觀書有感.txt');
          console.log(data)
          console.log(data.isFile())
          console.log(data.isDirectory())

          結果值對象結構:

          • size:文件體積
          • birthtime:創建時間
          • mtime:最后修改時間
          • isFile:檢測是否為文件
          • isDirectory:檢測是否為文件夾

          2.7 相對路徑問題

          fs 模塊對資源進行操作時,路徑的寫法有兩種:

          • 相對路徑 ./座右銘.txt:當前目錄下的座右銘.txt 座右銘.txt:等效于上面的寫法 ../座右銘.txt:當前目錄的上一級目錄中的座右銘.txt
          • 絕對路徑 D:/Program Files:windows 系統下的絕對路徑 /usr/bin:Linux 系統下的絕對路徑

          相對路徑中所謂的當前目錄,指的是命令行的工作目錄,而并非是文件的所在目錄。所以當命令行的工作目錄與文件所在目錄不一致時,會出現一些 BUG。

          2.8 __dirname

          __dirname 與 require 類似,都是 Node.js 環境中的 '全局' 變量 __dirname 保存著當前文件所在目錄的絕對路徑,可以使用 __dirname 與文件名拼接成絕對路徑

          代碼示例:

           let data = fs.readFileSync(__dirname + '/data.txt');
          console.log(data);

          使用 fs 模塊的時候,盡量使用 __dirname 將路徑轉化為絕對路徑,這樣可以避免相對路徑產生的 Bug

          2.9 練習

          實現文件復制的功能

          代碼示例:

           /**
           * 需求:
           *  復制【座右銘.txt】
           */
          
          // 1.引入 fs 模塊
          const fs = require('fs');
          // 全局對象,即 global 對象的屬性,無須聲明即可訪問。它用于描述當前 Node 進程狀態的對象,提供了一個與操作系統的簡單接口。
          const process = require('process')
          
          // 方式一:使用 readFile
          // 2.讀取文件
          // let data = fs.readFileSync('./座右銘.txt');
          
          // // 3.寫入文件
          // fs.writeFileSync('./座右銘-2.txt', data);
          
          // // 查看系統耗費內存
          // // rss: 19795968 字節
          // console.log(process.memoryUsage())
          
          // 方式二:使用流式操作
          // 2.創建流式讀取
          let rs = fs.createReadStream('./座右銘.txt');
          
          // 3.創建流式寫入
          let ws = fs.createWriteStream('./座右銘-3.txt');
          
          // // 4.綁定 data 事件
          // rs.on('data', chunk => {
          //     ws.write(chunk);
          // })
          
          // // 5.綁定 end 事件
          // rs.on('end', () => {
          //     // rss: 20885504 字節 相比于同步的方式占用內存會比較小
          //     console.log(process.memoryUsage())
          // })
          
          // 4.使用 pipe(管道) 可直接復制
          rs.pipe(ws)

          3. path 模塊

          path 模塊提供了操作路徑的功能,有如下幾個較為常用的 API:

          方法

          說明

          path.resolve

          拼接規范的絕對路徑常用

          path.sep

          獲取操作系統的路徑分隔符

          path.parse

          解析路徑并返回對象

          path.basename

          獲取路徑的基礎名稱

          path.dirname

          獲取路徑的目錄名

          path.extname

          獲得路徑的擴展名

          代碼示例:

           // 1.引入 path、fs 模塊
          const path = require('path')
          const fs = require('fs')
          
          // 寫入文件
          // fs.writeFileSync(__dirname + '/index.html', 'love')
          // E:\JavaEE\frontend\nodejs-study\3-path模塊/index.html
          // console.log(__dirname + '/index.html')
          
          // 使用 path 解決
          // E:\JavaEE\frontend\nodejs-study\3-path模塊\index.html
          console.log(path.resolve(__dirname, './index.html'))
          // E:\JavaEE\frontend\nodejs-study\3-path模塊\index.html
          console.log(path.resolve(__dirname, 'index.html'))
          // E:\index.html\test
          console.log(path.resolve(__dirname, '/index.html', './test'))
          
          // 分隔符
          // \ windows:\ linux:/
          console.log(path.sep)
          
          // parse 方法 __dirname '全局變量'
          // __filename '全局變量' 文件的絕對路徑
          console.log(__filename)
          
          let str = 'E:\\JavaEE\\frontend\\nodejs-study\\3-path模塊\\index.html';
          
          // {
          //     root: 'E:\\',
          //     dir: 'E:\\JavaEE\\frontend\\nodejs-study\\3-path模塊',
          //     base: 'index.html',
          //     ext: '.html',
          //     name: 'index'
          // }
          console.log(path.parse(str))
          
          // index.html
          console.log(path.basename(str))
          
          // E:\JavaEE\frontend\nodejs-study\3-path模塊
          console.log(path.dirname(str))
          
          // .html
          console.log(path.extname(str))

          4. http 模塊

          4.1 創建 http 服務

          4.1.1 操作步驟

          代碼示例:

           // 1.引入 http 模塊
          const http = require('http')
          
          // 2.創建 http 服務
          const server = http.createServer((request, response) => {
              
              // response.end 設置響應體 必須要返回,且只能返回一次
              // 多個 response.end 報錯 Error [ERR_STREAM_WRITE_AFTER_END]: write after end
              // response.end("hello world")
              // response.end("hello world")
          
              // 解決中文亂碼
              response.setHeader('content-type', 'text/html;charset=utf-8')
              response.end('你好,世界')
          })
          
          // 3.啟動服務并監聽端口
          server.listen(9000, () => {
              console.log('http 服務啟動成功')
          })

          http.createServer 里的回調函數的執行時機:當接收到 http 請求的時候,就會執行

          4.1.2 測試

          瀏覽器請求對應端口 http://127.0.0.1:9000

          4.1.3 注意事項

          • 命令行 ctrl + c 停止服務
          • 當服務啟動后,更新代碼必須重啟服務才能生效
          • 響應內容中文亂碼的解決辦法

          response.setHeader('content-type', 'text/html;charset=utf-8')

          • 端口號被占用 關閉當前正在運行監聽端口的服務 修改其他端口號 如果端口被其他程序占用,可以使用資源監視器找到占用端口的程序,然后使用任務管理器關閉對應的程序

          4.2 獲取 http 請求報文

          想要獲取請求的數據,需要通過 request 對象

          方法

          說明

          request.method

          請求方法

          request.httpVersion

          請求版本

          request.url

          請求路徑

          require('url').parse(request.url).pathname

          URL 路徑

          require('url').parse(request.url, true).query

          URL 查詢字符串

          request.headers

          請求頭

          request.on('data', function(chunk))
          request.on('end', function());

          請求體

          注意事項:

          • request.url 只能獲取路徑以及查詢字符串,無法獲取 URL 中的域名以及協議的內容
          • request.headers 將請求信息轉化成一個對象,并將屬性名都轉化成了『小寫』
          • 關于路徑:如果訪問網站的時候,只填寫了 IP 地址或者是域名信息,此時請求的路徑為『/』
          • 關于 favicon.ico:這個請求是屬于瀏覽器自動發送的請求

          代碼示例:

           // 1.引入 http 模塊
          const http = require('http')
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
              // 獲取請求的方法
              console.log(request.method)
              // 獲取請求的 url
              console.log(request.url)
              // 獲取 http 協議版本號
              console.log(request.httpVersion)
              // 獲取請求頭
              console.log(request.headers)
              // 獲取請求主機地址
              console.log(request.headers.host)
          
              response.end("hello world")
          })
          
          // 3.啟動服務并監聽端口‘
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          代碼示例:

           // 1.引入 http 模塊
          const http = require('http')
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
              
              // 1.聲明一個變量
              let body = ''
          
              // 2.綁定 data 事件
              request.on('data', chunk => {
                  body += chunk
              })
          
              // 3.綁定 end 事件
              request.on('end', () => {
                  console.log(body)
                  response.end("hello world")
              })
          
          })
          
          // 3.啟動服務并監聽端口‘
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          代碼示例:

           // 1.引入 http 模塊
          const http = require('http')
          
          // 1.導入 url 模塊
          const url = require('url')
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
              // request: http://127.0.0.1:9000/search?keyword=123&username=chen
          
              // 2.解析 request.url
              // let res1 = url.parse(request.url)
              // Url {
              //     protocol: null,
              //     slashes: null,
              //     auth: null,
              //     host: null,
              //     port: null,
              //     hostname: null,
              //     hash: null,
              //     search: '?keyword=123&username=chen',
              //     query: 'keyword=123&username=chen',
              //     pathname: '/search',
              //     path: '/search?keyword=123&username=chen',
              //     href: '/search?keyword=123&username=chen'
              // }
              // console.log(res1)
          
          
              // let res2 = url.parse(request.url, true)
              // Url {
              //     protocol: null,
              //     slashes: null,
              //     auth: null,
              //     host: null,
              //     port: null,
              //     hostname: null,
              //     hash: null,
              //     search: '?keyword=123&username=chen',
              //     query: [Object: null prototype] {
              //         keyword: '123',
              //         username: 'chen'
              //     },
              //     pathname: '/search',
              //     path: '/search?keyword=123&username=chen',
              //     href: '/search?keyword=123&username=chen'
              // }
              // console.log(res2)
          
              let res = url.parse(request.url, true)
              // 請求路徑
              // /search
              console.log(res.pathname)
              // 查詢字符串
              // 123
              console.log(res.query.keyword)
          
              response.end('hello world')
          })
          
          // 3.啟動服務并監聽端口‘
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          代碼示例:

           // 1.引入 http 模塊
          const http = require('http')
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
              // request: http://127.0.0.1:9000/search?keyword=123&username=chen
          
              // 第二個參數要寫完整 http://127.0.0.1  只寫 ip 會報錯
              let url = new URL(request.url, 'http://127.0.0.1')
          
              // URL {
              //     href: 'http://127.0.0.1/search?keyword=123&username=chen',
              //     origin: 'http://127.0.0.1',
              //     protocol: 'http:',
              //     username: '',
              //     password: '',
              //     host: '127.0.0.1',
              //     hostname: '127.0.0.1',
              //     port: '',
              //     pathname: '/search',
              //     search: '?keyword=123&username=chen',
              //     searchParams: URLSearchParams {
              //         'keyword' => '123', 'username' => 'chen'
              //     },
              //     hash: ''
              // }
              // console.log(url)
          
              // 請求路徑
              console.log(url.pathname)
              
              // 請求路徑參數
              console.log(url.searchParams.get('keyword'))
          
              response.end('hello world')
          })
          
          // 3.啟動服務并監聽端口‘
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          4.3 設置 http 響應報文

          方法

          說明

          response.statusCode

          設置響應狀態碼

          response.statusMessage

          設置響應狀態描述

          response.setHeader('鍵名', '鍵值')

          設置響應頭信息

          response.write('xx')response.end('xxx')

          設置響應體

          write 和 end 的兩種使用情況:

           // 1.write 和 end 的結合使用 響應體相對分散
          response.write('xx');
          response.write('xx');
          response.write('xx');
          
           //每一個請求,在處理的時候必須要執行 end 方法,且只能執行一次
          response.end();
          
          // 2.單獨使用 end 方法 響應體相對集中
          response.end('xxx');

          代碼示例:

           // 1.引入 http 模塊
          const http = require('http')
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
              
              // 1.設置響應狀態碼
              // response.statusCode = 200
              // response.statusCode = 404
          
              // 2.設置響應信息 不常用
              // response.statusMessage = 'hello world'
          
              // 3.設置響應頭
              response.setHeader('content-type', 'text/html;charset=utf-8')
              response.setHeader('Server', 'Node.js')
              response.setHeader('myHeader', 'myHeader')
              response.setHeader('test', ['love', 'love', 'love'])
          
              // 4.設置響應體
              response.write('love\r\n')
              response.write('love\r\n')
              response.write('love\r\n')
              response.write('love\r\n')
          
              response.end('hello world')
          
          })
          
          // 3.啟動服務并監聽端口‘
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          4.4 練習

          4.4.1 http 請求練習

          需求: 當請求方式為 get 請求時,請求路徑為 /login 返回 login 當請求方式為 get 請求時,請求路徑為 /register 返回 register

          代碼示例:

           /**
           * 需求:
           *  當請求方式為 get 請求時,請求路徑為 /login 返回 login
           *  當請求方式為 get 請求時,請求路徑為 /register 返回 register
           */
          
          // 1.引入 http 模塊
          const http = require('http')
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
              
              // 請求方式
              let { method } = request
          
              // 請求路徑
              let { pathname } = new URL(request.url, 'http://127.0.0.1')
          
              if (method === 'GET' && pathname === '/login') {
                  response.end('login')
              } else if (method === 'GET' && pathname === '/register') {
                  response.end('register')
              } else {
                  response.end('hello world')
              }
          })
          
          // 3.啟動服務并監聽端口‘
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          4.4.1 http 響應練習

          需求: 回一個4行3列的表格,且要求表格有隔行換色效果,且點擊單元格能高亮顯示

          代碼示例:

           /**
           * 需求:
           *  返回一個4行3列的表格,且要求表格有隔行換色效果,且點擊單元格能高亮顯示
           */
          
          
          // 1.引入 http 模塊
          const http = require('http')
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
          
              response.end(`
              <!DOCTYPE html>
                  <html lang="en">
          
                  <head>
                      <meta charset="UTF-8">
                      <meta http-equiv="X-UA-Compatible" content="IE=edge">
                      <meta name="viewport" content="width=device-width, initial-scale=1.0">
                      <title>Document</title>
                      <style>
                          td {
                              padding: 20px 40px;
                          }
          
                          table tr:nth-child(odd) {
                              background: rgb(179, 165, 201);
                          }
          
                          table tr:nth-child(even) {
                              background: #fcb;
                          }
          
                          table,
                          td {
                              border-collapse: collapse;
                          }
                      </style>
                  </head>
          
                  <body>
                      <table border="1">
                          <tr>
                              <td></td>
                              <td></td>
                              <td></td>
                          </tr>
                          <tr>
                              <td></td>
                              <td></td>
                              <td></td>
                          </tr>
                          <tr>
                              <td></td>
                              <td></td>
                              <td></td>
                          </tr>
                          <tr>
                              <td></td>
                              <td></td>
                              <td></td>
                          </tr>
                      </table>
                      <script>
                          //獲取所有的 td
                          let tds = document.querySelectorAll('td');
                          //遍歷
                          tds.forEach(item => {
                              item.onclick = function () {
                                  this.style.background = '#222';
                              }
                          })
                      </script>
                  </body>
          
                  </html>
              `)
          
          })
          
          // 3.啟動服務并監聽端口‘
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          代碼優化:

           /**
           * 需求:
           *  返回一個4行3列的表格,且要求表格有隔行換色效果,且點擊單元格能高亮顯示
           */
          
          
          // 1.引入 http 模塊
          const http = require('http')
          
          // 1.引入 fs 模塊
          const fs = require('fs')
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
          
              // 2.讀取文件內容
              let file = fs.readFileSync(__dirname + '/10-table.html')
          
              response.end(file)
          
          })
          
          // 3.啟動服務并監聽端口‘
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          4.5 靜態資源服務

          靜態資源是指內容長時間不發生改變的資源,例如圖片、視頻、CSS 文件、JS 文件、HTML 文件、字體文件等。

          動態資源是指內容經常更新的資源,例如百度首頁、網易首頁、京東搜索列表頁面等。

          4.5.1 網頁中的 URL

          網頁中的 URL 主要分為兩大類:相對路徑與絕對路徑

          絕對路徑可靠性強,而且相對容易理解,在項目中運用較多

          形式

          特點

          http://xxx.com/web

          直接向目標資源發送請求,容易理解,網站的外鏈會用到此形式

          //xxx.com/web

          與頁面 URL 的協議拼接形成完整 URL 再發送請求,大型網站用的比較多

          /web

          與頁面 URL 的協議、主機名、端口拼接形成完整 URL 再發送請求,中小型網站

          相對路徑在發送請求時,需要與當前頁面 URL 路徑進行計算,得到完整 URL 后,再發送請求 例如當前網頁 URL 為 http://www.xxx.com/course/h5.html

          形式

          最終的 URL

          ./css/app.css

          http://www.xxx.com/course/css/app.css

          js/app.js

          http://www.xxx.com/course/js/app.js

          ../img/logo.png

          http://www.xxx.com/img/logo.png

          ../../mp4/show.mp4

          http://www.atguigu.com/mp4/show.mp4

          4.5.2 設置資源類型(mime 類型)

          媒體類型(通常稱為 Multipurpose Internet Mail Extensions 或 MIME 類型 )是一種標準,用來表示文檔、文件或字節流的性質和格式。

          mime 類型結構: [type]/[subType] 例如: text/html text/css image/jpeg image/png application/json

          http 服務可以設置響應頭 Content-Type 來表明響應體的 mime 類型,瀏覽器會根據該類型決定如何處理資源 下面是常見文件對應的 mime 類型:

           {
              html: 'text/html',
              css: 'text/css',
              js: 'text/javascript',
              png: 'image/png',
              jpg: 'image/jpeg',
              gif: 'image/gif',
              mp4: 'video/mp4',
              mp3: 'audio/mpeg',
              json: 'application/json'
          }
          

          說明:對于未知的資源類型,可以選擇 application/octet-stream 類型,瀏覽器在遇到該類型的響應時,會對響應體內容進行獨立存儲,也就是下載。

          代碼示例:

           /**
           * 需求:
           *  根據不同的請求路徑,返回不同的文件
           */
          
          // 1.引入模塊
          const http = require('http')
          const fs = require('fs')
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
              
              // 獲取請求路徑
              let { pathname } = new URL(request.url, 'https://127.0.0.1:9000')
              if (pathname === '/') {
                  // 獲取要響應的文件
                  let data = fs.readFileSync(__dirname + '/11-table.html')
                  // 設置響應
                  response.end(data)
              } else if (pathname === '/index.css') {
                  // 獲取要響應的文件
                  let data = fs.readFileSync(__dirname + '/index.css')
                  // 設置響應
                  response.end(data)
              } else if (pathname === '/index.js') {
                  // 獲取要響應的文件
                  let data = fs.readFileSync(__dirname + '/index.js')
                  // 設置響應
                  response.end(data)
              } else {
                  response.end(`<h1>404 Not Found</h1>`)
              }
          })
          
          // 3.啟動服務,并監聽端口‘
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          代碼優化:

           /**
           * 需求:
           *  創建一個 HTTP 服務,端口為 9000,滿足如下需求
           *      GET  /index.html        響應  page/index.html 的文件內容
           *      GET  /css/app.css       響應  page/css/app.css 的文件內容
           *      GET  /images/logo.png   響應  page/images/logo.png 的文件內容
           */
          
          // 1.引入模塊
          const http = require('http')
          const fs = require('fs')
          const path = require('path')
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
              
              // 獲取文件根路徑
              let root = path.resolve(__dirname + '/page')
              // 獲取文件路徑
              let { pathname } = new URL(request.url, 'https://127.0.0.1:9000')
              // 拼接文件路徑
              let filePath = root + pathname
              
              // 獲取文件
              fs.readFile(filePath, (error, data) => {
                  // 設置字符集,解決打開文件中文亂碼的問題
                  response.setHeader('content-type', 'text/html;charset=utf-8')
          
                  if (error) {
                      response.end('讀取文件錯誤')
                  } 
          
                  response.end(data)
                  return;
              })
          })
          
          // 3.啟動服務并監聽端口
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          代碼優化:

           /**
           * 需求:
           *  創建一個 HTTP 服務,端口為 9000,滿足如下需求
           *      GET  /index.html        響應  page/index.html 的文件內容
           *      GET  /css/app.css       響應  page/css/app.css 的文件內容
           *      GET  /images/logo.png   響應  page/images/logo.png 的文件內容
           *  根據不同的文件類型,返回不同的類型
           */
          
          // 1.引入模塊
          const http = require('http')
          const fs = require('fs')
          const path = require('path')
          
          // 聲明一個變量
          let mimes = {
              html: 'text/html',
              css: 'text/css',
              js: 'text/javascript',
              png: 'image/png',
              jpg: 'image/jpeg',
              gif: 'image/gif',
              mp4: 'video/mp4',
              mp3: 'audio/mpeg',
              json: 'application/json'
          }
          
          // 2.創建服務
          const server = http.createServer((request, response) => {
          
              // 獲取文件根路徑
              let root = path.resolve(__dirname + '/page')
              // 獲取文件路徑
              let {
                  pathname
              } = new URL(request.url, 'https://127.0.0.1:9000')
              // 拼接文件路徑
              let filePath = root + pathname
          
              // 獲取文件
              fs.readFile(filePath, (error, data) => {
          
                  if (error) {
                      // 設置字符集,解決打開文件中文亂碼的問題
                      response.setHeader('content-type', 'text/html;charset=utf-8')
                      switch(error.code) {
                          case 'ENOENT': 
                              response.statusCode = 404
                              response.end(`<h1>404 Not Found</h1>`)
                              break;
                          case 'EPERM':
                              response.statusCode = 403
                              response.end(`<h1>403 Forbidden</h1>`)
                              break;
                          default: 
                              response.statusCode = 500
                              response.end(`<h1>500 Internal </h1>`)
                              break;
                      }
                      return;
                  }
          
                  // 獲取文件后綴
                  let ext = path.extname(filePath).slice(1)
                  // 獲取對應的類型
                  let type = mimes[ext]
                  if (type) {
                      // 匹配到了
                      if (ext === 'html') {
                          response.setHeader('content-type', type + ';charset=utf-8')
                      } else {
                          response.setHeader('content-type', type)
                      }
                  } else {
                      // 沒有匹配到
                      // 這種返回格式可以實現下載效果
                      response.setHeader('content-type', 'application/octet-stream')
                  }
          
                  response.end(data)
              })
          })
          
          // 3.啟動服務并監聽端口
          server.listen(9000, () => {
              console.log('服務啟動成功')
          })

          4.5.3 GET 和 POST 請求的區別

          • GET 和 POST 是 http 協議請求的兩種方式。
          • GET 主要用來獲取數據,POST 主要用來提交數據
          • GET 帶參數請求是將參數綴到 URL 之后,在地址欄中輸入 URL 訪問網站就是 GET 請求,POST 帶參數請求是將參數放到請求體中
          • POST 請求相對 GET 安全一些,因為在瀏覽器中參數會暴露在地址欄
          • GET 請求大小有限制,一般為 2K,而 POST 請求則沒有大小限制

          5. 模塊化

          5.1 基礎概念

          模塊化:將一個復雜的程序文件依據一定規則(規范)拆分成多個文件的過程稱之為模塊化。

          模塊:拆分出的每個文件就是一個模塊,模塊的內部數據是私有的,不過模塊可以暴露內部數據以便其他模塊使用

          模塊化項目:編碼時是按照模塊一個一個編碼的, 整個項目就是一個模塊化的項目

          模塊化好處:

          • 防止命名沖突
          • 高復用性
          • 高維護性

          5.2 模塊暴露

          模塊暴露的方式有兩種:

          • module.exports = value
          • exports.name = value

          說明:

          module.exports 可以暴露任意數據和方法 不能使用 exports = value 的形式暴露,模塊內部 module 與 exports 具有隱式關系 exports = module.exports = {},require 返回的是目標模塊中 module.exports 的值

          代碼示例: hello.js

           const type = 'hello world'
          
          function helloWorld() {
              console.log('你好,世界....')
          }
          
          // 暴露數據、方法
          module.exports = {
              type,
              helloWorld
          }
          
          // exports.type = type
          // module.exports = helloWorld
          
          // exports.type = type
          // exports.helloWorld = helloWorld

          index.js

           // 引入自定應模塊
          const hello = require('./hello')
          
          // 調用模塊數據/方法
          console.log(hello)
          hello.helloWorld()
          // hello()

          5.3 模塊導入

          在模塊中使用 require 傳入文件路徑即可引入文件 require 使用的一些注意事項:

          • 對于自己創建的模塊,導入時路徑建議寫相對路徑,且不能省略 ./ 和 ../
          • js 和 json 文件導入時可以不用寫后綴,c/c++ 編寫的 node 擴展文件也可以不寫后綴,但是一般用不到
          • 如果導入其他類型的文件,會以 js 文件進行處理
          • 如果導入的路徑是個文件夾,則會首先檢測該文件夾下 package.json 文件中 main 屬性對應的文件,如果存在則導入,反之如果文件不存在會報錯。如果 main 屬性不存在,或者 package.json 不存在,則會嘗試導入文件夾下的 index.js 和 index.json,如果還是沒找到,就會報錯
          • 導入 node.js 內置模塊時,直接 require 模塊的名字即可,無需加 ./ 和 ../

          5.4 模塊導入流程

          require 導入自定義模塊的基本流程:

          1、將相對路徑轉為絕對路徑,定位目標文件

          2、緩存檢測

          3、讀取目標文件代碼

          4、裹為一個函數并執行(自執行函數),通過 arguments.callee.toString() 查看自執行函數

          5、緩存模塊的值

          6、返回 module.exports 的值

          代碼示例:

           /**
           * require 導入原理 偽代碼
           */
          
          // 1.引入模塊
          const path = require('path')
          const fs = require('fs')
          
          // 2.定義一個緩存數組
          let caches = []
          
          function require(file) {
              // 3.將相對路徑轉成絕對路徑
              let absolutePath = path.resolve(__dirname, file)
              
              // 4.檢測是否有緩存
              if (caches[absolutePath]) {
                  return caches[absolutePath]
              }
          
              // 5.讀取文件的代碼
              let code = fs.readFileSync(absolutePath).toString()
              // 6.封裝一個函數
              let module = {}
              let exports = module.exports = {}
          
              (function (exports, require, module, __fileName, __dirname) {
                  const test = {
                      name: 'hello world'
                  }
                  module.exports = test
          
                  console.log(arguments.callee.toString())
              })(exports, require, module, __filename, __dirname)
          
              // 7.緩存結果
              caches[absolutePath] = module.exports
          
              // 7.返回 module.exports 的值
              return module.exports
          }

          6. 包管理工具

          6.1 基礎概念

          包:英文單詞是 package,代表了一組特定功能的源碼集合 包管理工具:管理的應用軟件,可以對進行下載安裝、更新、刪除、上傳等操作。借助包管理工具,可以快速開發項目,提升開發效率。包管理工具是一個通用的概念,很多編程語言都有包管理工具,所以 掌握好包管理工具非常重要

          常用的包管理工具:

          • npm
          • cnpm
          • yarn
          • pnpm

          6.2 npm

          npm 全稱 Node Package Manager,翻譯為中文意思是Node 的包管理工具

          npm 是 Node.js 官方內置的包管理工具,是必須要掌握住的工具

          常見使用命令:

          命令

          說明

          npm -v

          查看版本號

          npm init

          命令的作用是將文件夾初始化為一個包, 交互式創建 package.json 文件

          npm s/search

          搜索包

          npm install npm i

          下載安裝包

          npm i -g

          全局安裝

          npm i <包名@版本號>

          安裝指定版本的包

          npm remove npm r

          局部刪除依賴

          npm remove -g

          全局刪除依賴

          6.2.1 初始化

          創建一個空目錄,然后以此目錄作為工作目錄啟動命令行工具,執行 npm init. npm init 命令的作用是將文件夾初始化為一個,交互式創建 package.json 文件。 package.json 文件是包的配置文件,每個包都必須要有, package.json 文件內容:

           {
              "name": "1-npm", #包的名字
              "version": "1.0.0", #包的版本
              "description": "", #包的描述
              "main": "index.js", #包的入口文件
              "scripts": { #腳本配置
              "test": "echo \"Error: no test specified\" && exit 1"
              },
              "author": "", #作者
              "license": "ISC" #開源證書
          }

          初始化的過程中還有一些注意事項:

          name ( 包名 ) 不能使用中文、大寫,默認值是文件夾的名稱 ,所以文件夾名稱也不能使用中文和大寫 version ( 版本號 )要求 x.x.x 的形式定義, x 必須是數字,默認值是 1.0.0 ISC 證書與 MIT 證書功能上是相同的,關于開源證書擴展閱讀 http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html package.json 可以手動創建與修改 使用 npm init -y 或者 npm init --yes 極速創建 package.json

          6.2.2 包管理

          通過 npm install 和 npm i 命令安裝包

           npm install uniq
          npm i uniq

          運行之后文件夾下會增加兩個資源

          • node_modules 文件夾存放下載的包,node_modules 文件夾大多數情況都不會存入版本庫(git、svn 等)
          • package-lock.json 包的鎖文件,用來鎖定包的版本

          可以使用 require 導入 npm 包

           // 導入 uniq 包
          const uniq = require('uniq')

          require 導入的基本流程:

          • 在當前文件夾下 node_modules 中尋找同名的文件夾
          • 在上級目錄中下的 node_modules 中尋找同名的文件夾,直至找到磁盤根目錄

          安裝的包,也稱為依賴依賴生產依賴開發依賴,二者的使用如下:

          類型

          命令

          說明

          生產依賴

          npm i -S uniq npm i --save uniq

          -S 等效于 --save,-S 是默認選項 包信息保存在 package.json 中 dependencies 屬性

          開發依賴

          npm i -D less npm i --save-dev less

          -D 等效于 --save-dev 包信息保存在 package.json 中 devDependencies 屬性

          項目中可能會遇到版本不匹配的情況,有時就需要安裝指定版本的包,可以使用下面的命令

           npm i jquery@1.11.2

          可以執行安裝選項 -g 進行全局安裝

           npm i -g nodemon

          全局安裝完成之后就可以在命令行的任何位置運行 nodemon 命令,該命令的作用是自動重啟 Node 應用程序

          說明:

          全局安裝的命令不受工作目錄位置影響 可以通過 npm root -g 可以查看全局安裝包的位置 不是所有的包都適合全局安裝,只有全局類的工具才適合,可以通過查看包的官方文檔來確定

          項目中可能需要刪除某些不需要的包,可以使用下面的命令

           ## 局部刪除
          npm remove uniq
          npm r uniq
          ## 全局刪除
          npm remove -g nodemon

          6.2.3 啟動項目

          可以通過配置 package.json 中的 scripts 屬性來啟動項目

           {
              "scripts": {
                  "server": "node server.js",
                  "start": "node index.js",
              },
          }

          配置完成之后,執行命令啟動項目

          npm run server
          npm run start

          不過 start 比較特別,使用時可以省略 run

           npm start

          npm run 有自動向上級目錄查找的特性,跟 require 函數一樣

          6.3 cnpm

          cnpm 是一個淘寶構建的 npmjs.com 的完整鏡像,也稱為淘寶鏡像,網址:https://npmmirror.com/。

          cnpm 服務部署在國內阿里云服務器上,可以提高包的下載速度。官方也提供了一個全局工具包 cnpm,操作命令與 npm 大體相同。

          可以通過 npm 來安裝 cnpm 工具:

           npm install -g cnpm --registry=https://registry.npmmirror.com

          常用操作命令:

          命令

          說明

          cnpm init / cnpm init

          初始化

          cnpm i uniq cnpm i -S uniq cnpm i -D uniq cnpm i -g nodemon

          安裝包

          cnpm i

          安裝項目依賴

          cnpm r uniq

          刪除

          npm 也可以使用淘寶鏡像,配置的方式有兩種:

          • 直接配置
          • 工具配置

          執行如下命令即可完成配置:

           npm config set registry https://registry.npmmirror.com/

          使用 nrm 配置 npm 的鏡像地址 npm registry manager

          安裝 nrm

          npm i -g nrm

          修改鏡像

          nrm use taobao

          檢查是否配置成功(選做)

          npm config list

          檢查 registry 地址是否為 https://registry.npmmirror.com/, 如果是則表明成功

          補充說明:

          建議使用第二種方式進行鏡像配置,因為后續修改起來會比較方便 雖然 cnpm 可以提高速度,但是 npm 也可以通過淘寶鏡像進行加速,所以 npm 的使用率還是高于 cnpm

          6.4 yarn

          yarn 是由 Facebook 在 2016 年推出的新的 Javascript 包管理工具,官方網址:https://yarnpkg.com/

          yarn 特點:

          • 速度超快:yarn 緩存了每個下載過的包,所以再次使用時無需重復下載。同時利用并行下載以最大化資源利用率,因此安裝速度更快。
          • 超級安全:在執行代碼之前,yarn 會通過算法校驗每個安裝包的完整性
          • 超級可靠:使用詳細、簡潔的鎖文件格式和明確的安裝算法,yarn 能夠保證在不同系統上無差異的工作

          可以使用 npm 安裝 yarn

           npm i -g yarn

          yarn 常用命令:

          命令

          說明

          yarn init / yarn init -y

          初始化

          yarn add uniq 生產依賴 yarn add less --dev 開發依賴 yarn global add nodemon 全局安裝

          安裝包

          yarn remove uniq 刪除項目依賴包 yarn global remove nodemon 全局刪除包

          刪除包

          yarn/yarn install

          安裝項目依賴

          yarn # 不需要添加 run

          運行命令別名

          可以通過如下命令配置淘寶鏡像

           yarn config set registry https://registry.npmmirror.com/

          查看 yarn 的配置項

           yarn config list

          npm 和 yarn 選擇:

          • 個人項目:如果是個人項目, 哪個工具都可以 ,可以根據自己的喜好來選擇
          • 公司項目:如果是公司要根據項目代碼來選擇,可以 通過鎖文件判斷項目的包管理工具 npm 的鎖文件為 package-lock.json yarn 的鎖文件為 yarn.lock

          切記:包管理工具不要混著用

          6.5 管理發布包

          可以將自己開發的工具包發布到 npm 服務上,方便自己和其他開發者使用,操作步驟如下:

          1、創建文件夾,并創建文件 index.js, 在文件中聲明函數,使用 module.exports 暴露

          2、npm 初始化工具包,package.json 填寫包的信息 (包的名字是唯一的)

          3、注冊賬號:https://www.npmjs.com/signup

          4、激活賬號(一定要激活賬號)

          5、修改為官方的官方鏡像(命令行中運行 nrm use npm)

          6、命令行下 npm login 填寫相關用戶信息

          7、命令行下 npm publish 提交包

          后續可以對自己發布的包進行更新,操作步驟如下:

          1、更新包中的代碼

          2、測試代碼是否可用

          3、修改 package.json 中的版本號

          4、發布更新命令 npm publish

          執行如下命令刪除包

           npm unpublish --force

          刪除包需要滿足一定的條件,https://docs.npmjs.com/policies/unpublish

          • 是包的作者
          • 發布小于 24 小時
          • 大于 24 小時后,沒有其他包依賴,并且每周小于 300 下載量,并且只有一個維護者

          6.6 nvm

          nvm 全稱 Node Version Manager 顧名思義它是用來管理 Node 版本的工具,方便切換不同版本的 Node.js。

          nvm 的使用非常的簡單,跟 npm 的使用方法類似。

          下載地址: https://github.com/coreybutler/nvm-windows/releases

          常用命令:

          命令

          說明

          nvm list available

          顯示所有可以下載的 Node.js 版本

          nvm list

          顯示已安裝的版本

          nvm install 18.12.1

          安裝 18.12.1 版本的 Node.js

          nvm install latest

          安裝最新版的 Node.js

          nvm uninstall 18.12.1

          刪除某個版本的 Node.js

          nvm use 18.12.1

          切換 18.12.1 的 Node.js

          7. express 框架

          7.1 介紹與使用

          express 是一個基于 Node.js 平臺的極簡、靈活的 WEB 應用開發框架,官方網址:https://www.expressjs.com.cn/

          簡單來說,express 是一個封裝好的工具包,封裝了很多功能,便于我們開發 web 應用(http 服務)

          express 下載 express 本身是一個 npm 包,所以可以通過 npm 安裝:

          npm init
          npm i express

          express 初體驗:

           // 1.引入模塊
          const express = require('express')
          
          // 2.創建服務
          const app = express()
          
          // 3.創建路由
          app.get('/hello', (req, resp) => {
              resp.end('hello world')
          })
          
          // 4.啟動服務并監聽端口
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          7.2 路由

          7.2.1 路由初體驗

          官方定義:路由確定了應用程序如何響應客戶端對特定端點的請求。

          一個路由的組成有請求方法、路徑、回調函數組成。 express 中提供了一系列方法,可以很方便的使用路由,使用格式如下:

           app.<method>(path,callback)

          代碼示例:

           // 1.引入模塊
          const express = require('express')
          
          // 2.創建服務
          const app = express()
          
          // 3.創建路由
          // get 請求
          app.get('/hello', (req, resp) => {
              resp.end('hello world')
          })
          
          // 首頁路由
          app.get('/', (req, resp) => {
              resp.end('home')
          })
          
          // post 請求
          app.post('/login', (req, resp) => {
              resp.end('login')
          })
          
          // 所有的請求方式都可以
          app.all('/register', (req, resp) => {
              resp.end('register')
          })
          
          // 404
          app.all('*', (req, resp) => {
              resp.end(`<h1>404 Not Found</h1>`)
          })
          
          // 4.啟動服務并監聽端口
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          7.2.2 獲取請求參數

          express 框架封裝了一些 API 來方便獲取請求報文中的數據,并且兼容原生 http 模塊的獲取方式.。 代碼示例:

           // 引入模塊
          const express = require('express')
          
          // 創建應用服務
          const app = express()
          
          // 創建路由
          app.get('/hello', (req, resp) => {
          
              // 原生方式
              // console.log(req.method)
              // console.log(req.url)
              // console.log(req.httpVersion)
              // console.log(req.headers)
          
              // express 操作
              console.log(req.path)
              console.log(req.query)
              // 獲取請求ip
              console.log(req.ip)
          
              resp.end('hello world')
          })
          
          // 監聽端口,啟動服務
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          express 框架也可以獲取到路由參數。路由參數指的是 URL 路徑中的參數(數據)。 代碼示例:

           // 導入模塊
          const express = require('express')
          
          // 創建應用服務
          const app = express()
          
          // 創建路由
          app.get('/:id/:token', (req, resp) => {
              console.log(req.params.id, req.params.token)
              resp.end('hello world')
          })
          
          // 監聽端口,啟動服務
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          express 可以使用 body-parser 包處理請求體。 安裝 body-parser

           npm i body-parser

          代碼示例:

           /**
           * 需求:
           *  GET   /login  顯示表單網頁
           *  POST  /login  獲取表單中的『用戶名』和『密碼』
           */
          
          // 導入模塊
          const express = require('express')
          
          // 使用 body-parser 獲取請求體數據
          // 安裝 body-parser
          // npm i body-parser
          // 導入
          const bodyParser = require('body-parser')
          
          // 創建應用服務
          const app = express()
          
          // 解析 json 請求格式的請求體中間件
          const jsonParser = bodyParser.json()
          
          // 解析 queryString 請求格式的請求體中間件
          const urlencodedParser = bodyParser.urlencoded({extended: false})
          
          // 創建路由
          app.get('/login', (req, resp) => {
              resp.sendFile(__dirname + '/11-form.html')
          })
          
          app.post('/login', urlencodedParser, (req, resp) => {
          
              // 獲取用戶名和密碼
              console.log(req.body)
              resp.send('登錄成功')
          })
          
          app.all('*', (req, resp) => {
              resp.send(`<h1>404 Not Found</h1>`)
          })
          
          // 監聽端口,啟動服務
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          7.2.3 設置響應

          express 框架封裝了一些 API 來方便給客戶端響應數據,并且兼容原生 http 模塊的獲取方式。 代碼示例:

           // 引入模塊
          const express = require('express')
          
          // 創建應用服務
          const app = express()
          
          // 創建路由
          app.get('/hello', (req, resp) => {
              // 原生響應
              // resp.statusCode = 404 
              // resp.statusMessage = 'Not Found'
              // resp.setHeader('aaaa', 'bbbb')
              // resp.write('hello express ')
          
              // express 操作
              // resp.status(500)
              // resp.set('aaa', '123')
              // 自動返回 utf8 編碼。此時無需再寫 resp.end()
              // resp.send('hello express ')
          
              // 可以鏈式調用
              resp.status(500).set('xxx', 'yyy').send('你好,世界')
          })
          
          // 監聽端口,啟動服務
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          還可以設置其他響應:

           // 導入模塊
          const express = require('express')
          
          // 創建應用服務
          const app = express()
          
          // 創建路由
          app.get('/hello', (req, resp) => {
              // resp.end('hello world')
          
              // 重定向
              // resp.redirect('https://www.baidu.com')
          
              // 下載響應
              // resp.download(__dirname + '/singers.json')
          
              // 返回json
              // resp.json({
              //     username: '張三',
              //     age: 18
              // })
          
              // 返回文件內容
              resp.sendFile(__dirname + '/2-form.html')
          })
          
          // 監聽端口,啟動服務
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          7.2.4 路由模塊化

          對路由進行模塊化,可以更好的管理路由。

          admin.js 路由

           // 導入模塊
          const express = require('express')
          
          // 創建應用服務
          const router = express()
          
          // 創建路由
          router.get('/admin', (req, resp) => {
              resp.send('后臺首頁')
          })
          
          router.get('/setting', (req, resp) => {
              resp.send('個人設置')
          })
          
          module.exports = router

          home.js 路由

           // 導入模塊
          const express = require('express')
          
          // 創建應用服務
          const router = express()
          
          // 創建路由
          router.get('/', (req, resp) => {
              resp.send('前臺首頁')
          })
          
          router.get('/home', (req, resp) => {
              resp.send('home')
          })
          
          module.exports = router

          index.js 路由

           // 導入模塊
          const express = require('express')
          
          // 創建應用服務
          const app = express()
          
          // 創建路由
          // 導入路由
          const admin = require('./routes/admin')
          const home = require('./routes/home')
          
          app.use(admin)
          app.use(home)
          
          app.all('*', (req, resp) => {
              resp.send(`<h1>404 Not Found</h1>`)
          })
          
          // 監聽端口,啟動服務
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          7.3 中間件

          中間件(Middleware)本質是一個回調函數。可以像路由回調一樣訪問請求對象(request),響應對象(response)。

          中間件的作用就是使用函數封裝公共操作,簡化代碼。

          中間件有兩種類型:

          • 全局中間件
          • 路由中間件

          7.3.1 全局中間件

          每一個請求到達服務端之后都會執行全局中間件函數。

          代碼示例:

           /**
           * 需求:
           *  記錄每個請求的 ip 和 url
           */
          
          // 引入模塊
          const express = require('express')
          const fs = require('fs')
          const path = require('path')
          
          // 創建應用服務
          const app = express()
          
          // 創建全局路由中間件
          let globalMiddleware = (req, resp, next) => {
              // 獲取 url 和 ip
              let {ip, url} = req
              // 使用追加的方式記錄到日志文件
              fs.appendFileSync(path.resolve(__dirname, './access.log'), `${ip} ${url} \r\n`)
              // 調用 next
              next()
          }
          
          // 使用全局路由中間件
          app.use(globalMiddleware)
          
          // 創建路由
          app.get('/home', (req, resp) => {
              resp.send('前臺首頁')
          })
          
          app.get('/admin', (req, resp) => {
              resp.send('后臺首頁')
          })
          
          app.all('*', (req, resp) => {
              resp.send(`<h1>404 Not Found</h1>`)
          })
          
          // 監聽端口,啟動服務
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          7.3.2 路由中間件

          如果只需要對某一些路由進行功能封裝,則就需要路由中間件。 調用格式如下:

           app.get('/路徑',`中間件函數`,(request,response)=>{
          });
          
          app.get('/路徑',`中間件函數1`,`中間件函數2`,(request,response)=>{
          });

          代碼示例:

           /**
           * 需求:
           *  針對 /admin  /setting 的請求, 要求 URL 攜帶 code=521 參數, 如未攜帶提示『暗號錯誤』
           */
          // 引入模塊
          const express = require('express')
          
          // 創建應用服務
          const app = express()
          
          // 創建路由中間件
          let routerMiddleware = (req, resp, next) => {
              // 獲取 code
              let { code } = req.query
          
              if (code !== '521') {
                  resp.send('暗號錯誤')
                  return;
              }
              next()
          }
          
          // 創建路由
          app.get('/home', (req, resp) => {
              resp.send('前臺首頁')
          })
          
          app.get('/admin', routerMiddleware, (req, resp) => {
              resp.send('后臺首頁')
          })
          
          app.get('/setting', routerMiddleware, (req, resp) => {
              resp.send('個人設置')
          })
          
          app.all('*', (req, resp) => {
              resp.send(`<h1>404 Not Found</h1>`)
          })
          
          // 監聽端口,啟動服務
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          7.3.3 靜態資源中間件

          express 內置處理靜態資源的中間件。

          代碼示例:

           // 導入模塊
          const express = require('express')
          
          // 創建應用服務
          const app = express()
          
          // 創建路由
          app.get('/', (req, resp) => {
              resp.send('前臺首頁')
          })
          
          // 靜態資源中間件設置
          // '/' 路徑默認訪問 idnex.html,如果與動態資源的請求路徑一致時,誰先加載就誰先返回
          app.use(express.static(__dirname + '/public'))
          
          app.all('*', (req, resp) => {
              resp.send(`<h1>404 Not Found</h1>`)
          })
          
          // 監聽端口,啟動服務
          app.listen(9000, () => {
              console.log('服務啟動成功')
          })

          注意事項:

          index.html 文件為默認打開的資源 如果靜態資源與路由規則同時匹配,誰先匹配誰就響應 路由響應動態資源,靜態資源中間件響應靜態資源

          7.4 EJS 模板引擎

          模板引擎是分離用戶界面和業務數據的一種技術。

          EJS 是一個高效的 Javascript 的模板引擎。

          官網: https://ejs.co/

          中文站:https://ejs.bootcss.com/

          7.4.1 EJS 初體驗

          下載安裝 EJS

           npm i ejs --save
           // 安裝 ejs
          // npm i ejs
          // 導入模塊
          const ejs = require('ejs')
          const fs = require('fs')
          
          // 聲明變量
          let text = '中國'
          let weather = '艷陽高照,萬里無云'
          let str = fs.readFileSync(__dirname + '/1-html.html').toString()
          
          let result = ejs.render(str, {text, weather})
          console.log(result)
           <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta http-equiv="X-UA-Compatible" content="IE=edge">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Document</title>
          </head>
          <body>
              <h1>我愛你<%= text %></h1></h1>
              <h2><%= weather %></h1></h1>
          </body>
          </html>

          7.4.2 EJS 常用語法

          字符串輸出

           <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta http-equiv="X-UA-Compatible" content="IE=edge">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Document</title>
          </head>
          <body>
              <h1>我愛你<%= text %></h1></h1>
              <h2><%= weather %></h1></h1>
          </body>
          </html>

          列表渲染

           <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta http-equiv="X-UA-Compatible" content="IE=edge">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Document</title>
          </head>
          <body>
              <ul>
                  <% list.forEach( item => {%>
                      <li><%= item %></li>
                  <% })%>
              </ul>
          </body>
          </html>

          條件判斷

           <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta http-equiv="X-UA-Compatible" content="IE=edge">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Document</title>
          </head>
          <body>
              <% if (isLogin) { %>
                  <span>歡迎回來</span>
              <% } else { %>
                  <button>登錄</button>  <button>注冊</button>
              <% } %>
          </body>
          </html>

          7.5 腳手架

          腳手架:可以快速創建一個應用的框架,應用框架是指在項目開發過程中常用的目錄或文件,比如:css目錄、js目錄等,以提高開發效率。

          express-generator 是實現 express 框架的腳手架。

          安裝 express-generator

           npm i express-generator -g

          初始化

           express --view=ejs 項目包名

          package.json 文件

           {
            "name": "15-express-generator",
            "version": "0.0.0",
            "private": true,
            "scripts": {
              "start": "node ./bin/www"
            },
            "dependencies": {
              "cookie-parser": "~1.4.4",
              "debug": "~2.6.9",
              "ejs": "~2.6.1",
              "express": "~4.16.1",
              "http-errors": "~1.6.3",
              "morgan": "~1.9.1"
            }
          }

          進入項目,安裝依賴

           npm i

          啟動項目

           npm start

          瀏覽器訪問,端口默認是:3000 localhost:3000

          8. MongoDB

          8.1 基礎概念

          MongoDB 是一個基于分布式文件存儲的數據庫。

          官方地址:https://www.mongodb.com/

          下載地址: https://www.mongodb.com/try/download/community

          MongoDB 操作語法與 JavaScript 類似,容易上手,學習成本低。

          MongoDB 中有三個重要概念需要掌握:

          • 數據庫(Database) 是一個數據倉庫,數據庫服務下可以創建很多數據庫,數據庫中可以存放很多集合
          • 集合(Collection) 集合類似于 JS 中的數組,在集合中可以存放很多文檔
          • 文檔(Document) 文檔是數據庫中的最小單位,類似于 JS 中的對象

          可以通過 JSON 文件來理解 MongoDB 中的概念: 一個 JSON 文件好比是一個數據庫,一個 Mongodb 服務下可以有 N 個數據庫;JSON 文件中的一級屬性的數組值好比是集合;數組中的對象好比是文檔對象中的屬性,有時也稱之為字段。

          一般情況下,一個項目使用一個數據庫,一個集合會存儲同一種類型的數據。

          8.2 操作命令

          8.2.1 數據庫命令

          顯示所有的數據庫

           show dbs

          切換到指定的數據庫,如果數據庫不存在會自動創建數據庫

           use 數據庫名

          顯示當前所在的數據庫

           db

          刪除當前數據庫

           use 庫名
          db.dropDatabase()

          8.2.2 集合命令

          創建集合

           db.createCollection('集合名稱')

          顯示當前數據庫中的所有集合

           show collections

          刪除某個集合

           db.集合名.drop()

          重命名集合

           db.集合名.renameCollection('newName')

          8.2.3 文檔命令

          插入文檔

           db.集合名.insert(文檔對象)

          查詢文檔

           db.集合名.find(查詢條件)

          _id 是 MongoDB 自動生成的唯一編號,用來唯一標識文檔

          更新文檔

           db.集合名.update(查詢條件,新的文檔)
          db.集合名.update({name:'張三'},{$set:{age:19}})

          刪除文檔

           db.集合名.remove(查詢條件)

          8.3 Mongooose

          8.4.1 介紹與使用

          Mongoose 是一個 對象文檔模型庫,可以方便使用代碼操作 MongoDB 數據庫。

          官網 http://www.mongoosejs.net/

          代碼示例:

           // 1.導入 mongoose
          const mongoose = require('mongoose')
          
          // 設置 strictQuery 為 true
          mongoose.set('strictQuery', true)
          
          // 2.連接 mongodb 服務
          mongoose.connect('mongodb://127.0.0.1:27017/test')
          
          // 3.設置連接成功的回調函數
          mongoose.connection.once('open', () => {
              // 創建新文檔
          
              // 1、創建文檔的結構模型
              let bookSchema = new mongoose.Schema({
                  name: String,
                  author: String,
                  price: Number
              });
          
              // 2、創建文檔模型對象,用于操作文檔
              // book 就是集合名,mongoose 會默認加上 s,在mongodb數據庫中顯示的是 books
              let bookModel = mongoose.model('book', bookSchema);
          
              // 3、操作文檔-新增
              bookModel.create({
                  name: "海邊的卡夫卡",
                  author: '樹上春樹',
                  price: 49
              }, (err, data) => {
                  // 判斷是否有錯誤
                  if (err) {
                      console.log('文檔新增失敗', err)
                      return;
                  }
                  console.log('文檔新增成功', data)
              });
          
              console.log('連接成功')
          })
          
          // 4.設置連接失敗的回調函數
          mongoose.connection.on('error', () => {
              console.log('連接失敗')
          })
          
          // 5.設置連接關閉的回調函數
          mongoose.connection.on('close', () => {
              console.log('連接關閉')
          })

          8.4.2 字段操作

          文檔結構可選的常用字段類型列表

          類型

          描述

          String

          字符串

          Number

          數字

          Boolean

          布爾值

          Array

          數組,也可以使用 [] 來標識

          Date

          日期

          Buffer

          Buffer 對象

          Mixed

          任意類型,需要使用 mongoose.Schema.Types.Mixed 指定

          ObjectId

          對象 ID,需要使用 mongoose.Schema.Types.ObjectId 指定

          Decimal128

          高精度數字,需要使用 mongoose.Schema.Types.Decimal128 指定

          Mongoose 有一些內建驗證器,可以對字段值進行驗證

          必填項

           name: {
              type: String,
              // 設置是否必填
              // name: ValidatorError: Path `name` is required.
              required: true
          }

          默認值

           author: {
              type: String,
              // 設置默認值
              default: '匿名'
          }

          枚舉值

           tages: {
              type: String,
              // 枚舉
              // tages: ValidatorError: `架空` is not a valid enum value for path `tages`.
              enum: ['言情', '歷史', '勵志']
          }

          唯一值

           name: {
              type: String,
              // 是否唯一
              unique: true
          }

          unique 需要重建集合才能有效果。

          永遠不要相信用戶的輸入!

          8.4.3 增刪改查

          插入一條

           bookModel.create({
              name: "海邊的卡夫卡",
              author: '樹上春樹',
              price: 49
          }, (err, data) => {
              // 判斷是否有錯誤
              if (err) {
                  console.log('文檔新增失敗', err)
                  return;
              }
              console.log('文檔新增成功', data)
          });

          批量插入

           bookModel.insertMany([{
                  name: '西游記',
                  author: '吳承恩',
                  price: 19.9,
              }, {
                  name: '紅樓夢',
                  author: '曹雪芹',
                  price: 29.9,
              }, {
                  name: '三國演義',
                  author: '羅貫中',
                  price: 25.9,
              }, {
                  name: '水滸傳',
                  author: '施耐庵',
                  price: 20.9,
              }, {
                  name: '活著',
                  author: '余華',
                  price: 19.9,
              }, {
                  name: '狂飆',
                  author: '徐紀周',
                  price: 68,
              }, {
                  name: '大魏能臣',
                  author: '黑男爵',
                  price: 9.9,
              },
              {
                  name: '知北游',
                  author: '洛水',
                  price: 59,
              }, {
                  name: '道君',
                  author: '躍千愁',
                  price: 59,
              }, {
                  name: '七煞碑',
                  author: '游泳的貓',
                  price: 29,
              }, {
                  name: '獨游',
                  author: '酒精過敏',
                  price: 15,
              }, {
                  name: '大潑猴',
                  author: '甲魚不是龜',
                  price: 26,
              },
              {
                  name: '黑暗王者',
                  author: '古羲',
                  price: 39,
              },
              {
                  name: '不二大道',
                  author: '文刀手予',
                  price: 89,
              },
              {
                  name: '大潑猴',
                  author: '甲魚不是龜',
                  price: 59,
              },
              {
                  name: '長安的荔枝',
                  author: '馬伯庸',
                  price: 45,
              },
              {
                  name: '命運',
                  author: '蔡崇達',
                  price: 59.8,
              },
              {
                  name: '如雪如山',
                  author: '張天翼',
                  price: 58,
              },
              {
                  name: '三體',
                  author: '劉慈欣',
                  price: 23,
              },
              {
                  name: '秋園',
                  author: '楊本芬',
                  price: 38,
              },
              {
                  name: '百年孤獨',
                  author: '范曄',
                  price: 39.5,
              },
              {
                  name: '在細雨中呼喊',
                  author: '余華',
                  price: 25,
              },
          ], (error, data) => {
              if (error) {
                  console.log('批量新增失敗', error)
                  return
              }
              console.log('批量新增成功')
          })

          刪除一條數據

           bookModel.deleteOne({name: '西游記'}, (error, data) => {
              if (error) {
                  console.log('刪除文檔失敗', data)
                  return
              }
              console.log('刪除文檔成功', data)
          })

          批量刪除

           bookModel.deleteMany({
              name: '海邊的卡夫卡'
          }, (error, data) => {
              if (error) {
                  console.log('批量刪除失敗', error)
                  return
              }
              console.log('批量刪除成功', data)
          })

          更新一條數據

           bookModel.updateOne({
              name: '海邊的卡夫卡'
          }, {
              name: '西游記',
              author: '吳承恩',
              price: 59
          }, (error, data) => {
              if (error) {
                  console.log('更新文檔失敗', error)
                  return;
              }
          
              console.log('更新文檔成功', data)
          })

          批量更新數據

            bookModel.updateMany({
              name: '海邊的卡夫卡'
          }, {
              price: 89
          }, (error, data) => {
              if (error) {
                  console.log('批量更新文檔失敗', error)
                  return
              } 
              console.log('批量更新文檔成功', data)
          })

          查詢一條數據

           bookModel.findOne({name: '西游記'}, (error, data) => {
              if (error) {
                  console.log('讀取單個文檔錯誤', error)
                  return
              }
              console.log('讀取單個文檔成功', data)
          })

          批量查詢數據

           bookModel.find({name: '西游記'}, (error, data) => {
              if (error) {
                  console.log('文檔讀取錯誤', error)
                  return;
              }
              console.log('文檔讀取成功', data)
          })

          8.4.4 條件控制

          MongoDB 不能 >、<、>=、<=、!== 等運算符,需要使用替代符號:

          > 使用 $gt < 使用 $lt >= 使用 $gte <= 使用 $lte !== 使用 $ne

           // 獲取價格 小于 20 的圖書
          bookModel.find({price: {$lt: 20}}, (error, data) => {
              if (error) {
                  console.log('文檔讀取錯誤', error)
                  return;
              }
              console.log('文檔讀取成功', data)
          })

          邏輯運算 $or 邏輯或的情況

           //曹雪芹 或者 余華的書
          bookModel.find({
              $or: [
                  {
                      author: '曹雪芹'
                  }, 
                  {
                      author: '余華'
                  }
              ]
          }, (error, data) => {
              if (error) {
                  console.log('文檔讀取錯誤', error)
                  return
              }
              console.log('文檔讀取成功', data)
          })

          $and 邏輯與的情況

           //價格大于等于 30 且小于 70
          bookModel.find({
              $and: [
                  {
                      price: {
                          $gte: 30
                      }
                  }, {
                      price: {
                          $lt: 70
                      }
                  }
              ]
          }, (error, data) => {
              if (error) {
                  console.log('文檔讀取錯誤', error)
                  return;
              }
              console.log('文檔讀取成功', data)
          })

          正則匹配 條件中可以直接使用 JS 的正則語法,通過正則可以進行模糊查詢

           //正則表達式, 搜索書籍名稱中帶有 `三` 的圖書
          let queryCondition = '三'
          bookModel.find({name: /三/}, (error, data) => {
              if (error) {
                  console.log('文檔讀取錯誤', error)
                  return;
              }
              console.log('文檔讀取成功', data)
          })
          
          bookModel.find({name: new RegExp(queryCondition)}, (error, data) => {
              if (error) {
                  console.log('文檔讀取錯誤', error)
                  return;
              }
              console.log('文檔讀取成功', data)
          })

          8.4.5 個性化讀取

          字段篩選

           // 只查詢 書名 作者
          // 1:查詢 0:不查詢
          bookModel.find().select({name: 1, author: 1, _id: 0}).exec((error, data) => {
              if (error) {
                  console.log('文檔讀取錯誤', error)
                  return
              }
              console.log('文檔讀取成功', data)
          })

          數據排序

           // 對數據進行排序
          // 1:升序 -1:降序
          bookModel.find({author: '余華'}).select({name: 1, price: 1}).sort({price: -1}).exec((error, data) => {
              if (error) {
                  console.log('文件讀取錯誤', error)
                  return
              }
              console.log(data)
          })

          數據截取

           // 數據列表集合截取
          // 可用于分頁
          bookModel.find()
              .select({name: 1, author: 1, _id: 0})
              .sort({price: -1})
              .skip(2)
              .limit(3)
              .exec((error, data) => {
                  if (error) {
                      console.log('文件讀取錯誤', error)
                      return
                  }
                  console.log(data)
              })

          9. 接口

          9.1 RESTful API

          RESTful API 是一種特殊風格的接口,主要特點有如下幾個:

          • URL 中的路徑表示資源 ,路徑中不能有動詞 ,例如 create , delete , update 等這些都不能有
          • 操作資源要與 HTTP 請求方法對應
          • 操作結果要與 HTTP 響應狀態碼對應

          規則示例:

          操作

          請求類型

          URL

          新增歌曲

          POST

          /song

          刪除歌曲

          DELETE

          /song/10

          修改歌曲

          PUT

          /song/10

          修改歌曲

          PATCH

          /song/10

          獲取所有歌曲

          GET

          /song

          獲取單個歌曲

          GET

          /song/10

          9.2 json-server

          json-server 本身是一個 JS 編寫的工具包,可以快速搭建 RESTful API 服務。

          官方地址: https://github.com/typicode/json-server

          操作步驟: 全局安裝 json-server

           npm i -g json-server

          創建 JSON 文件(db.json),編寫基本結構

           {
              "song": [
                  { "id": 1, "name": "干杯", "singer": "五月天" },
                  { "id": 2, "name": "當", "singer": "動力火車" },
                  { "id": 3, "name": "不能說的秘密", "singer": "周杰倫" }
              ]
          }

          JSON 文件所在文件夾作為工作目錄,執行如下命令

           json-server --watch db.json

          默認監聽端口為 3000

          10. 會話控制

          所謂會話控制就是對會話進行控制。

          HTTP 是一種無狀態的協議,它沒有辦法區分多次的請求是否來自于同一個客戶端,無法區分用戶而產品中又大量存在的這樣的需求,所以我們需要通過會話控制來解決該問題。

          常見的會話控制技術有三種:

          • cookie
          • session
          • token

          10.1 cookie

          cookie 是 HTTP 服務器發送到用戶瀏覽器并保存在本地的一小塊數據,保存在瀏覽器端的一小塊數據且是按照域名劃分保存的。

          express 中可以使用 cookie-parser 來進行對 cookie 的操作。

          示例代碼:

           // 導入express
          const express = require('express')
          const cookieParser = require('cookie-parser')
          
          // 創建應用對象
          const app = express()
          app.use(cookieParser())
          
          // 創建路由
          app.get('/', (req, resp) => {
              resp.send('home')
          })
          
          // 設置cookie
          app.get('/set-cookie', (req, resp) => {
              // 在瀏覽器關閉時會銷毀
              // resp.cookie('name', 'zhangsan')
          
              // 設置 cookie 的過期時間。設置一分鐘以后過期
              resp.cookie('name', 'lisi', {maxAge: 60 * 1000})
          
              resp.send('hello world')
          })
          
          // 獲取 cookie 
          app.get('/get-cookie', (req, resp) => {
              console.log(req.cookies)
              resp.send(`歡迎您${req.cookies.name}`)
          })
          
          // 刪除 cookie
          app.get('/remove-cookie', (req, resp) => {
              resp.clearCookie('name')
              resp.send('刪除cookie成功')
          })
          
          // 監聽端口,啟動服務
          app.listen(3000, () => {
              console.log('服務啟動成功')
          })

          不同瀏覽器中的 cookie 是相互獨立的,不共享。

          10.2 session

          session 是保存在服務器端的一塊兒數據 ,保存當前訪問用戶的相關信息。

          express 中可以使用 express-session 對 session 進行操作。

          示例代碼:

           // 引入模塊
          const express = require('express')
          const session = require('express-session')
          const MongoStore = require('connect-mongo')
          
          // 創建應用服務
          const app = express()
          // 設置 session 的中間件
          app.use(session({
              // 設置 cookie 的 name,默認值是:connect.sid
              name: 'sid', 
              // 參與加密的字符串(又稱簽名)  加鹽
              secret: '123456',
              // 是否為每次請求都設置一個cookie用來存儲session的id
              saveUninitialized: false,
              // 是否在每次請求時重新保存session  20 分鐘    4:00  4:20
              resave: true,
              store: MongoStore.create({
                  //數據庫的連接配置
                  mongoUrl: 'mongodb://127.0.0.1:27017/test'
              }),
              cookie: {
                  // 開啟后前端無法通過 JS 操作
                  httpOnly: true,
                  // 這一條 是控制 sessionID 的過期時間的!!! 5分鐘過期
                  maxAge: 5 * 60 * 1000
              }
          }))
          
          // 創建路由
          app.get('/', (req, resp) => {
              resp.send('home')
          })
          
          // 登錄時設置 session
          app.get('/login', (req, resp) => {
              // 校驗一下用戶名密碼是否正確
              if (req.query.username === 'admin' && req.query.password === 'admin') {
                  // 設置 session 信息
                  req.session.username = req.query.username
                  req.session.uid = '258aefccc'
                  resp.send('登錄成功')
              } else {
                  resp.send('登錄失敗')
              }
          })
          
          // 獲取 session
          app.get('/cart', (req, resp) => {
              // 檢測 session 是否存在用戶信息
              if (req.session.username) {
                  resp.send(`歡迎您${req.session.username}`)
              } else {
                  resp.send('請先登錄')
              }
          })
          
          // 退出登錄刪除 session
          app.get('/logout', (req, resp) => {
              req.session.destroy(() => {
                  resp.send('成功退出登錄')
              })
          })
          
          // 監聽端口,啟動服務
          app.listen(3000)

          session 和 cookie 的區別 cookie 和 session 的區別主要有如下幾點:

          • 存在的位置 cookie:瀏覽器端 session:服務端
          • 安全性 cookie 是以明文的方式存放在客戶端的,安全性相對較低 session 存放于服務器中,所以安全性相對較好
          • 網絡傳輸量 cookie 設置內容過多會增大報文體積,會影響傳輸效率 session 數據存儲在服務器,只是通過 cookie 傳遞 id,所以不影響傳輸效率
          • 存儲限制 瀏覽器限制單個 cookie 保存的數據不能超過 4K,且單個域名下的存儲數量也有限制 session 數據存儲在服務器中,所以沒有這些限制

          10.3 token

          token 是服務端生成并返回給 HTTP 客戶端的一串加密字符串, token 中保存著用戶信息。

          token 的特點:

          • 服務端壓力更小
          • 數據存儲在客戶端
          • 相對更安全
          • 數據加密
          • 可以避免 CSRF(跨站請求偽造)
          • 擴展性更強
          • 服務間可以共享
          • 增加服務節點更簡單

          JWT(JSON Web Token)是目前最流行的跨域認證解決方案,可用于基于 token 的身份驗證 JWT 使 token 的生成與校驗更規范,可以使用 jsonwebtoken 包 來操作 token.

          代碼操作:

          nodejs作為一個優秀的異步IO框架,其本身就是用來作為http web服務器使用的,nodejs中的http模塊,提供了很多非常有用的http相關的功能。

          雖然nodejs已經帶有http的處理模塊,但是對于現代web應用程序來說,這或許還不太夠,于是我們有了express框架,來對nodejs的內容進行擴展。

          今天我們將會介紹一下使用nodejs和express來開發web應用程序的區別。

          使用nodejs搭建HTTP web服務

          nodejs提供了http模塊,我們可以很方便的使用http模塊來創建一個web服務:

          const http = require('http')
          
          const hostname = '127.0.0.1'
          const port = 3000
          
          const server = http.createServer((req, res) => {
            res.statusCode = 200
            res.setHeader('Content-Type', 'text/plain')
            res.end('welcome to www.flydean.com\n')
          })
          
          server.listen(port, hostname, () => {
            console.log(`please visit http://{hostname}:{port}/`)
          })
          

          上面創建的http服務監聽在3000端口。我們通過使用createServer方法來創建這個http服務。

          該方法接受一個callback函數,函數的兩個參數分別是 req (http.IncomingMessage 對象)和一個res(http.ServerResponse 對像)。

          在上面的例子中,我們在response中設置了header和body值,并且以一個end方法來結束response。

          請求nodejs服務

          我們創建好http web服務之后,一般情況下是從web瀏覽器端進行訪問和調用。但是我們有時候也需要從nodejs后端服務中調用第三方應用的http接口,下面的例子將會展示如何使用nodejs來調用http服務。

          先看一個最簡單的get請求:

          const http = require('http')
          const options = {
            hostname: 'www.flydean.com',
            port: 80,
            path: '/',
            method: 'GET'
          }
          
          const req = http.request(options, res => {
            console.log(`status code: ${res.statusCode}`)
          
            res.on('data', d => {
              console.log(d);
            })
          })
          
          req.on('error', error => {
            console.error(error)
          })
          
          req.end()
          

          上面代碼我們使用了http.request來創建一個request,并且傳入了我們自帶的options參數。

          我們通過res的回調事件來進行相應的處理。

          再看一個簡單的post請求:

          const http = require('http')
          
          const data = JSON.stringify({
            name: 'flydean'
          })
          
          const options = {
            hostname: 'www.flydean.com',
            port: 80,
            path: '/',
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'Content-Length': data.length
            }
          }
          
          const req = http.request(options, res => {
            console.log(`status code: ${res.statusCode}`)
          
            res.on('data', d => {
              console.log(d);
            })
          })
          
          req.on('error', error => {
            console.error(error)
          })
          
          req.write(data)
          req.end()
          

          post和get相似,不同的是options中的method不一樣,同時put可以有多種請求類型,所以我們需要在headers中指定。

          同樣的,PUT 和 DELETE 也可以使用同樣的方式來調用。

          第三方lib請求post

          直接使用nodejs底層的http.request有點復雜,我們需要自己構建options,如果使用第三方庫,比如axios可以讓post請求變得更加簡單:

          const axios = require('axios')
          
          axios
            .post('http://www.flydean.com', {
              name: 'flydean'
            })
            .then(res => {
              console.log(`status code: ${res.statusCode}`)
              console.log(res)
            })
            .catch(error => {
              console.error(error)
            })
          

          上面的例子中,我們直接使用axios的post請求,并將請求結果封存成了promise,然后通過then和catch來進行相應數據的處理。非常的方便。

          獲取http請求的正文

          在上面的例子中,我們通過監聽req的data事件來輸出http請求的正文:

            res.on('data', d => {
              console.log(d);
            })
          })
          

          這樣做其實是有問題的,并不一定能夠獲得完整的http請求的正文。

          因為res的on data事件是在服務器獲得http請求頭的時候觸發的,這個時候請求的正文可能還沒有傳輸完成,換句話說,請求回調中的request是一個流對象。

          我們需要這樣處理:

          const server = http.createServer((req, res) => {
            let data = []
            req.on('data', chunk => {
              data.push(chunk)
            })
            req.on('end', () => {
              console.log(JSON.parse(data));
            })
          })
          

          當每次觸發data事件的時候,我們將接受到的值push到一個數組里面,等所有的值都接收完畢,觸發end事件的時候,再統一進行輸出。

          這樣處理顯然有點麻煩。

          我們介紹一個在express框架中的簡單方法,使用 body-parser 模塊:

          const bodyParser = require('body-parser')
          
          app.use(
            bodyParser.urlencoded({
              extended: true
            })
          )
          
          app.use(bodyParser.json())
          
          app.post('/', (req, res) => {
            console.log(req.body)
          })
          

          上面的例子中,body-parser對req進行了封裝,我們只用關注與最后的結果即可。

          Express和使用express搭建http web服務

          express是什么呢?

          express是基于 Node.js 平臺,快速、開放、極簡的 web 開發框架。它提供一系列強大的特性,幫助你創建各種 Web 和移動設備應用。

          豐富的 HTTP 快捷方法和任意排列組合的 Connect 中間件,讓你創建健壯、友好的 API 變得既快速又簡單。

          Express 不對 Node.js 已有的特性進行二次抽象,我們只是在它之上擴展了 Web 應用所需的基本功能。

          express helloworld

          我們看一下怎么使用Express來搭建一個helloworld:

          var express = require('express');
          var app = express();
          app.get('/', function (req, res) {
            res.send('Hello World!');
          });
          var server = app.listen(3000, function () {
            var host = server.address().address;
            var port = server.address().port;
            console.log('Example app listening at http://%s:%s', host, port);
          });
          
          

          簡單的使用app.listen即可搭建好一個http web服務。

          express路由

          有了web服務,我們需要對不同的請求路徑和請求方式進行不同的處理,這時候就需要使用到了express路由功能:

          // 對網站首頁的訪問返回 "Hello World!" 字樣
          app.get('/', function (req, res) {
            res.send('Hello World!');});
          // 網站首頁接受 POST 請求
          app.post('/', function (req, res) {
            res.send('Got a POST request');});
          // /user 節點接受 PUT 請求
          app.put('/user', function (req, res) {
            res.send('Got a PUT request at /user');});
          // /user 節點接受 DELETE 請求
          app.delete('/user', function (req, res) {
            res.send('Got a DELETE request at /user');});
          

          更高級一點的,我們還可以在請求路徑中做路由匹配:

          // 匹配 acd 和 abcd
          app.get('/ab?cd', function(req, res) {
            res.send('ab?cd');});
          // 匹配 abcd、abbcd、abbbcd等
          app.get('/ab+cd', function(req, res) {
            res.send('ab+cd');
          });
          // 匹配 abcd、abxcd、abRABDOMcd、ab123cd等
          app.get('/ab*cd', function(req, res) {
            res.send('ab*cd');
          });
          // 匹配 /abe 和 /abcde
          app.get('/ab(cd)?e', function(req, res) {
           res.send('ab(cd)?e');});
          
          // 匹配任何路徑中含有 a 的路徑:
          app.get(/a/, function(req, res) {
            res.send('/a/');
          });
          
          // 匹配 butterfly、dragonfly,不匹配 butterflyman、dragonfly man等
          app.get(/.*fly/, function(req, res) {
            res.send('/.*fly/');
          });
          
          

          Express 路由句柄中間件

          有時候,一個請求可能有多個處理器,express提供了路由句柄(中間件)的功能,我們可自由組合處理程序。

          注意,在路由句柄中,我們需要調用next方法,來觸發下一個路由方法。

          var cb0 = function (req, res, next) {
            console.log('CB0');
            next();}
          var cb1 = function (req, res, next) {
            console.log('CB1');
            next();}
          app.get('/example/d', [cb0, cb1], function (req, res, next) {
            console.log('response will be sent by the next function ...');
            next();
          }, function (req, res) {
            res.send('Hello from D!');
          });
          

          上面的請求會經過cb0,cb1和自定義的兩個function,最終結束。

          Express 響應方法

          express提供了很多響應方法API,可以方便我們的代碼編寫:

          方法描述res.download()提示下載文件。res.end()終結響應處理流程。res.json()發送一個 JSON 格式的響應。res.jsonp()發送一個支持 JSONP 的 JSON 格式的響應。res.redirect()重定向請求。res.render()渲染視圖模板。res.send()發送各種類型的響應。res.sendFile以八位字節流的形式發送文件。res.sendStatus()設置響應狀態代碼,并將其以字符串形式作為響應體的一部分發送。

          Express 的靜態資源

          通常來說,靜態資源是不需要服務端進行處理的,在express中,可以使用express.static來指定靜態資源的路徑:

          app.use(express.static('public'));
          現在,public 目錄下面的文件就可以訪問了。
          http://localhost:3000/images/kitten.jpg
          http://localhost:3000/css/style.css
          http://localhost:3000/js/app.js
          http://localhost:3000/images/bg.png
          http://localhost:3000/hello.html
          //多個靜態資源目錄
          app.use(express.static('public'));
          app.use(express.static('files'));
          //靜態前綴
          app.use('/static', express.static('public'));
          http://localhost:3000/static/images/kitten.jpg
          http://localhost:3000/static/css/style.css
          

          Express 使用模板引擎

          web應用當然需要html文件,express中可以使用多種模板語言,讓編寫html頁面更加容易。如果想要使用模板引擎。我們可以使用下面的步驟:

          1. views, 放模板文件的目錄,比如: app.set(‘views’, ‘./views’)
          2. view engine, 模板引擎,比如: app.set(‘view engine’, ‘jade’)
          3. 在 views 目錄下生成名為 index.jade 的 Jade 模板文件,內容如下:
          html
            head
              title!= title
            body
              h1!= message
          
          1. 在nodejs服務端配置route規則
          //配置route 規則
          app.get('/', function (req, res) {
            res.render('index', { title: 'Hey', message: 'Hello there!'});
          });
          

          總結

          nodejs和express是非常方便的http web服務框架,希望大家能夠喜歡。

          本文作者:flydean程序那些事

          本文鏈接:http://www.flydean.com/nodejs-http-express/

          本文來源:flydean的博客

          歡迎關注我的公眾號:「程序那些事」最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!


          主站蜘蛛池模板: 无码少妇A片一区二区三区| 国产99精品一区二区三区免费| 日本大香伊一区二区三区| 亚洲国产精品一区二区三区久久| 久久一区二区免费播放| 亚洲第一区二区快射影院| 日韩国产精品无码一区二区三区 | 精品欧洲AV无码一区二区男男| 国产精品视频一区麻豆| 亚洲乱码国产一区三区| 女人18毛片a级毛片一区二区| 无码欧精品亚洲日韩一区| 无码少妇一区二区浪潮av| 人妻久久久一区二区三区| 精品视频在线观看一区二区 | 成人一区二区三区视频在线观看| 无码人妻啪啪一区二区| 91福利视频一区| 日韩精品无码一区二区三区免费 | 黑人一区二区三区中文字幕| 韩国一区二区视频| 亚洲熟妇无码一区二区三区| 一区二区三区视频在线| 精品国产一区二区三区2021| 鲁大师成人一区二区三区| 国产一区二区三区在线电影| 国产成人一区二区精品非洲| 免费高清在线影片一区| 成人免费视频一区| 日韩动漫av在线播放一区| 久久久久人妻精品一区三寸蜜桃| 日韩一区二区三区在线观看| 中文字幕在线播放一区| 日本免费电影一区二区| 奇米精品视频一区二区三区| 亚洲综合av一区二区三区| 一区二区三区无码高清视频| 久久精品国产一区二区三区| 国产精品视频一区二区三区四 | 无码国产精品久久一区免费| 亚洲一区无码中文字幕乱码|