Axios 是如何封裝 HTTP 請(qǐng)求的
Axios 毋庸多說(shuō)大家在前端開(kāi)發(fā)中常用的一個(gè)發(fā)送 HTTP 請(qǐng)求的庫(kù),用過(guò)的都知道。本文用來(lái)整理項(xiàng)目中常用的 Axios 的封裝使用。同時(shí)學(xué)習(xí)源碼,手寫實(shí)現(xiàn) Axios 的核心代碼。
Axios 常用封裝
是什么
Axios 是一個(gè)基于 promise 的 HTTP 庫(kù),可以用在瀏覽器和 node.js 中。它的特性:
- 從瀏覽器中創(chuàng)建 XMLHttpRequests
- 從 node.js 創(chuàng)建 http 請(qǐng)求
- 支持 Promise API
- 攔截請(qǐng)求和響應(yīng)
- 轉(zhuǎn)換請(qǐng)求數(shù)據(jù)和響應(yīng)數(shù)據(jù)
- 取消請(qǐng)求
- 自動(dòng)轉(zhuǎn)換 JSON 數(shù)據(jù)
- 客戶端支持防御 XSRF官網(wǎng)地址:http://www.axios-js.com/zh-cn/docs/#axios-config
Axios 使用方式有兩種:一種是直接使用全局的 Axios 對(duì)象;另外一種是通過(guò) axios.create(config) 方法創(chuàng)建一個(gè)實(shí)例對(duì)象,使用該對(duì)象。兩種方式的區(qū)別是通過(guò)第二種方式創(chuàng)建的實(shí)例對(duì)象更清爽一些;全局的 Axios 對(duì)象其實(shí)也是創(chuàng)建的實(shí)例對(duì)象導(dǎo)出的,它本身上加載了很多默認(rèn)屬性。后面源碼學(xué)習(xí)的時(shí)候會(huì)再詳細(xì)說(shuō)明。
請(qǐng)求
Axios 這個(gè) HTTP 的庫(kù)比較靈活,給用戶多種發(fā)送請(qǐng)求的方式,以至于有些混亂。細(xì)心整理會(huì)發(fā)現(xiàn),全局的 Axios(或者 axios.create(config)創(chuàng)建的對(duì)象) 既可以當(dāng)作對(duì)象使用,也可以當(dāng)作函數(shù)使用:
- // axios 當(dāng)作對(duì)象使用
- axios.request(config)
- axios.get(url[, config])
- axios.post(url[, data[, config]])
- // axios() 當(dāng)作函數(shù)使用。 發(fā)送 POST 請(qǐng)求
- axios({
- method: 'post',
- url: '/user/12345',
- data: {
- firstName: 'Fred',
- lastName: 'Flintstone'
- }
- });
后面源碼學(xué)習(xí)的時(shí)候會(huì)再詳細(xì)說(shuō)明為什么 Axios 可以實(shí)現(xiàn)兩種方式的使用。
取消請(qǐng)求
可以使用 CancelToken.source 工廠方法創(chuàng)建 cancel token:
- const CancelToken = axios.CancelToken;
- const source = CancelToken.source();
- axios.get('/user/12345', {
- cancelToken: source.token
- }).catch(function(thrown) {
- if (axios.isCancel(thrown)) {
- console.log('Request canceled', thrown.message);
- } else {
- // 處理錯(cuò)誤
- }
- });
- // 取消請(qǐng)求(message 參數(shù)是可選的)
- source.cancel('Operation canceled by the user.');
source 有兩個(gè)屬性:一個(gè)是 source.token 標(biāo)識(shí)請(qǐng)求;另一個(gè)是 source.cancel() 方法,該方法調(diào)用后,可以讓 CancelToken 實(shí)例的 promise 狀態(tài)變?yōu)?resolved,從而觸發(fā) xhr 對(duì)象的 abort() 方法,取消請(qǐng)求。
攔截
Axios 還有一個(gè)奇妙的功能點(diǎn),可以在發(fā)送請(qǐng)求前對(duì)請(qǐng)求進(jìn)行攔截,對(duì)相應(yīng)結(jié)果進(jìn)行攔截。結(jié)合業(yè)務(wù)場(chǎng)景的話,在中臺(tái)系統(tǒng)中完成登錄后,獲取到后端返回的 token,可以將 token 添加到 header 中,以后所有的請(qǐng)求自然都會(huì)加上這個(gè)自定義 header。
- //攔截1 請(qǐng)求攔截
- instance.interceptors.request.use(function(config){
- //在發(fā)送請(qǐng)求之前做些什么
- const token = sessionStorage.getItem('token');
- if(token){
- const newConfig = {
- ...config,
- headers: {
- token: token
- }
- }
- return newConfig;
- }else{
- return config;
- }
- }, function(error){
- //對(duì)請(qǐng)求錯(cuò)誤做些什么
- return Promise.reject(error);
- });
我們還可以利用請(qǐng)求攔截功能實(shí)現(xiàn) 取消重復(fù)請(qǐng)求,也就是在前一個(gè)請(qǐng)求還沒(méi)有返回之前,用戶重新發(fā)送了請(qǐng)求,需要先取消前一次請(qǐng)求,再發(fā)送新的請(qǐng)求。比如搜索框自動(dòng)查詢,當(dāng)用戶修改了內(nèi)容重新發(fā)送請(qǐng)求的時(shí)候需要取消前一次請(qǐng)求,避免請(qǐng)求和響應(yīng)混亂。再比如表單提交按鈕,用戶多次點(diǎn)擊提交按鈕,那么我們就需要取消掉之前的請(qǐng)求,保證只有一次請(qǐng)求的發(fā)送和響應(yīng)。
實(shí)現(xiàn)原理是使用一個(gè)對(duì)象記錄已經(jīng)發(fā)出去的請(qǐng)求,在請(qǐng)求攔截函數(shù)中先判斷這個(gè)對(duì)象中是否記錄了本次請(qǐng)求信息,如果已經(jīng)存在,則取消之前的請(qǐng)求,將本次請(qǐng)求添加進(jìn)去對(duì)象中;如果沒(méi)有記錄過(guò)本次請(qǐng)求,則將本次請(qǐng)求信息添加進(jìn)對(duì)象中。最后請(qǐng)求完成后,在響應(yīng)攔截函數(shù)中執(zhí)行刪除本次請(qǐng)求信息的邏輯。
- // 攔截2 重復(fù)請(qǐng)求,取消前一個(gè)請(qǐng)求
- const promiseArr = {};
- instance.interceptors.request.use(function(config){
- console.log(Object.keys(promiseArr).length)
- //在發(fā)送請(qǐng)求之前做些什么
- let source=null;
- if(config.cancelToken){
- // config 配置中帶了 source 信息
- source = config.source;
- }else{
- const CancelToken = axios.CancelToken;
- source = CancelToken.source();
- config.cancelToken = source.token;
- }
- const currentKey = getRequestSymbol(config);
- if(promiseArr[currentKey]){
- const tmp = promiseArr[currentKey];
- tmp.cancel("取消前一個(gè)請(qǐng)求");
- delete promiseArr[currentKey];
- promiseArr[currentKey] = source;
- }else{
- promiseArr[currentKey] = source;
- }
- return config;
- }, function(error){
- //對(duì)請(qǐng)求錯(cuò)誤做些什么
- return Promise.reject(error);
- });
- // 根據(jù) url、method、params 生成唯一標(biāo)識(shí),大家可以自定義自己的生成規(guī)則
- function getRequestSymbol(config){
- const arr = [];
- if(config.params){
- const data = config.params;
- for(let key of Object.keys(data)){
- arr.push(key+"&"+data[key]);
- }
- arr.sort();
- }
- return config.url+config.method+arr.join("");
- }
- instance.interceptors.response.use(function(response){
- const currentKey = getRequestSymbol(response.config);
- delete promiseArr[currentKey];
- return response;
- }, function(error){
- //對(duì)請(qǐng)求錯(cuò)誤做些什么
- return Promise.reject(error);
- });
最后,我們可以在響應(yīng)攔截函數(shù)中統(tǒng)一處理返回碼的邏輯:
- // 響應(yīng)攔截
- instance.interceptors.response.use(function(response){
- // 401 沒(méi)有登錄跳轉(zhuǎn)到登錄頁(yè)面
- if(response.data.code===401){
- window.location.href = "http://127.0.0.1:8080/#/login";
- }else if(response.data.code===403){
- // 403 無(wú)權(quán)限跳轉(zhuǎn)到無(wú)權(quán)限頁(yè)面
- window.location.href = "http://127.0.0.1:8080/#/noAuth";
- }
- return response;
- }, function(error){
- //對(duì)請(qǐng)求錯(cuò)誤做些什么
- return Promise.reject(error);
- })
文件下載
通常文件下載有兩種方式:一種是通過(guò)文件在服務(wù)器上的對(duì)外地址直接下載;還有一種是通過(guò)接口將文件以二進(jìn)制流的形式下載。
第一種:同域名 下使用 a 標(biāo)簽下載:
- // httpServer.js
- const express = require("express");
- const path = require('path');
- const app = express();
- //靜態(tài)文件地址
- app.use(express.static(path.join(__dirname, 'public')))
- app.use(express.static(path.join(__dirname, '../')));
- app.listen(8081, () => {
- console.log("服務(wù)器啟動(dòng)成功!")
- });
- // index.html
- <a href="test.txt" download="test.txt">下載</a>
第二種:二進(jìn)制文件流的形式傳遞,我們直接訪問(wèn)該接口并不能下載文件,一定程度保證了數(shù)據(jù)的安全性。比較多的場(chǎng)景是:后端接收到查詢參數(shù),查詢數(shù)據(jù)庫(kù)然后通過(guò)插件動(dòng)態(tài)生成 excel 文件,以文件流的方式讓前端下載。
這時(shí)候,我們可以將請(qǐng)求文件下載的邏輯進(jìn)行封裝。將二進(jìn)制文件流存在 Blob 對(duì)象中,再將其轉(zhuǎn)為 url 對(duì)象,最后通過(guò) a 標(biāo)簽下載。
- //封裝下載
- export function downLoadFetch(url, params = {}, config={}) {
- //取消
- const downSource = axios.CancelToken.source();
- document.getElementById('downAnimate').style.display = 'block';
- document.getElementById('cancelBtn').addEventListener('click', function(){
- downSource.cancel("用戶取消下載");
- document.getElementById('downAnimate').style.display = 'none';
- }, false);
- //參數(shù)
- config.params = params;
- //超時(shí)時(shí)間
- configconfig.timeout = config.timeout ? config.timeout : defaultDownConfig.timeout;
- //類型
- config.responseType = defaultDownConfig.responseType;
- //取消下載
- config.cancelToken = downSource.token;
- return instance.get(url, config).then(response=>{
- const content = response.data;
- const url = window.URL.createObjectURL(new Blob([content]));
- //創(chuàng)建 a 標(biāo)簽
- const link = document.createElement('a');
- link.style.display = 'none';
- link.href = url;
- //文件名 Content-Disposition: attachment; filename=download.txt
- const filename = response.headers['content-disposition'].split(";")[1].split("=")[1];
- link.download = filename;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- return {
- status: 200,
- success: true
- }
- })
- }
手寫 Axios 核心代碼
寫了這么多用法終于到正題了,手寫 Axios 核心代碼。Axios 這個(gè)庫(kù)源碼不難閱讀,沒(méi)有特別復(fù)雜的邏輯,大家可以放心閱讀 😂 。
源碼入口是這樣查找:在項(xiàng)目 node_modules 目錄下,找到 axios 模塊的 package.json 文件,其中 "main": "index.js", 就是文件入口。一步步我們可以看到源碼是怎么串起來(lái)的。
模仿上面的目錄結(jié)構(gòu),我們創(chuàng)建自己的目錄結(jié)構(gòu):
- axios-js
- │ index.html
- │
- └─lib
- adapter.js
- Axios.js
- axiosInstance.js
- CancelToken.js
- InterceptorManager.js
Axios 是什么
上面有提到我們使用的全局 Axios 對(duì)象其實(shí)也是構(gòu)造出來(lái)的 axios,既可以當(dāng)對(duì)象使用調(diào)用 get、post 等方法,也可以直接當(dāng)作函數(shù)使用。這是因?yàn)槿值?Axios 其實(shí)是函數(shù)對(duì)象 instance 。源碼位置在 axios/lib/axios.js 中。具體代碼如下:
- // axios/lib/axios.js
- //創(chuàng)建 axios 實(shí)例
- function createInstance(defaultConfig) {
- var context = new Axios(defaultConfig);
- //instance 對(duì)象是 bind 返回的函數(shù)
- var instance = bind(Axios.prototype.request, context);
- // Copy axios.prototype to instance
- utils.extend(instance, Axios.prototype, context);
- // Copy context to instance
- utils.extend(instance, context);
- return instance;
- }
- // 實(shí)例一個(gè) axios
- var axios = createInstance(defaults);
- // 向這個(gè)實(shí)例添加 Axios 屬性
- axios.Axios = Axios;
- // 向這個(gè)實(shí)例添加 create 方法
- axios.create = function create(instanceConfig) {
- return createInstance(mergeConfig(axios.defaults, instanceConfig));
- };
- // 向這個(gè)實(shí)例添加 CancelToken 方法
- axios.CancelToken = require('./cancel/CancelToken');
- // 導(dǎo)出實(shí)例 axios
- module.exports.default = axios;
根據(jù)上面的源碼,我們可以簡(jiǎn)寫一下自己實(shí)現(xiàn) Axios.js 和 axiosInstance.js:
- // Axios.js
- //Axios 主體
- function Axios(config){
- }
- // 核心方法,發(fā)送請(qǐng)求
- Axios.prototype.request = function(config){
- }
- Axios.prototype.get = function(url, config={}){
- return this.request({url: url, method: 'GET', ...config});
- }
- Axios.prototype.post = function(url, data, config={}){
- return this.request({url: url, method: 'POST', data: data, ...config})
- }
- export default Axios;
在 axiosInstance.js 文件中,實(shí)例化一個(gè) Axios 得到 context,再將原型對(duì)象上的方法綁定到 instance 對(duì)象上,同時(shí)將 context 的屬性添加到 instance 上。這樣 instance 就成為了一個(gè)函數(shù)對(duì)象。既可以當(dāng)作對(duì)象使用,也可以當(dāng)作函數(shù)使用。
- // axiosInstance.js
- //創(chuàng)建實(shí)例
- function createInstance(config){
- const context = new Axios(config);
- var instance = Axios.prototype.request.bind(context);
- //將 Axios.prototype 屬性擴(kuò)展到 instance 上
- for(let k of Object.keys(Axios.prototype)){
- instance[k] = Axios.prototype[k].bind(context);
- }
- //將 context 屬性擴(kuò)展到 instance 上
- for(let k of Object.keys(context)){
- instance[k] = context[k]
- }
- return instance;
- }
- const axios = createInstance({});
- axios.create = function(config){
- return createInstance(config);
- }
- export default axios;
也就是說(shuō) axios.js 中導(dǎo)出的 axios 對(duì)象并不是 new Axios() 方法返回的對(duì)象 context,而是 Axios.prototype.request.bind(context) 執(zhí)行返回的 instance,通過(guò)遍歷 Axios.prototype 并改變其 this 指向到 context;遍歷 context 對(duì)象讓 instance 對(duì)象具有 context 的所有屬性。這樣 instance 對(duì)象就無(wú)敵了,😎 既擁有了 Axios.prototype 上的所有方法,又具有了 context 的所有屬性。
請(qǐng)求實(shí)現(xiàn)
我們知道 Axios 在瀏覽器中會(huì)創(chuàng)建 XMLHttpRequest 對(duì)象,在 node.js 環(huán)境中創(chuàng)建 http 發(fā)送請(qǐng)求。Axios.prototype.request() 是發(fā)送請(qǐng)求的核心方法,這個(gè)方法其實(shí)調(diào)用的是 dispatchRequest 方法,而 dispatchRequest 方法調(diào)用的是 config.adapter || defaults.adapter 也就是自定義的 adapter 或者默認(rèn)的 defaults.adapter,默認(rèn)defaults.adapter 調(diào)用的是 getDefaultAdapter 方法,源碼:
- function getDefaultAdapter() {
- var adapter;
- if (typeof XMLHttpRequest !== 'undefined') {
- // For browsers use XHR adapter
- adapter = require('./adapters/xhr');
- } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
- // For node use HTTP adapter
- adapter = require('./adapters/http');
- }
- return adapter;
- }
哈哈哈,getDefaultAdapter 方法最終根據(jù)當(dāng)前的環(huán)境返回不同的實(shí)現(xiàn)方法,這里用到了 適配器模式。我們只用實(shí)現(xiàn) xhr 發(fā)送請(qǐng)求即可:
- //適配器 adapter.js
- function getDefaultAdapter(){
- var adapter;
- if(typeof XMLHttpRequest !== 'undefined'){
- //導(dǎo)入 XHR 對(duì)象請(qǐng)求
- adapter = (config)=>{
- return xhrAdapter(config);
- }
- }
- return adapter;
- }
- function xhrAdapter(config){
- return new Promise((resolve, reject)=>{
- var xhr = new XMLHttpRequest();
- xhr.open(config.method, config.url, true);
- xhr.send();
- xhr.onreadystatechange = ()=>{
- if(xhr.readyState===4){
- if(xhr.status>=200&&xhr.status<300){
- resolve({
- data: {},
- status: xhr.status,
- statusText: xhr.statusText,
- xhr: xhr
- })
- }else{
- reject({
- status: xhr.status
- })
- }
- }
- };
- })
- }
- export default getDefaultAdapter;
這樣就理順了,getDefaultAdapter 方法每次執(zhí)行會(huì)返回一個(gè) Promise 對(duì)象,這樣 Axios.prototype.request 方法可以得到執(zhí)行 xhr 發(fā)送請(qǐng)求的 Promise 對(duì)象。
給我們的 Axios.js 添加發(fā)送請(qǐng)求的方法:
- //Axios.js
- import getDefaultAdapter from './adapter.js';
- Axios.prototype.request = function(config){
- const adapter = getDefaultAdapter(config);
- var promise = Promise.resolve(config);
- var chain = [adapter, undefined];
- while(chain.length){
- promisepromise = promise.then(chain.shift(), chain.shift());
- }
- return promise;
- }
攔截器實(shí)現(xiàn)
攔截器的原理在于 Axios.prototype.request 方法中的 chain 數(shù)組,把請(qǐng)求攔截函數(shù)添加到 chain 數(shù)組前面,把響應(yīng)攔截函數(shù)添加到數(shù)組后面。這樣就可以實(shí)現(xiàn)發(fā)送前攔截和響應(yīng)后攔截的效果。
創(chuàng)建 InterceptorManager.js
- //InterceptorManager.js
- //攔截器
- function InterceptorManager(){
- this.handlers = [];
- }
- InterceptorManager.prototype.use = function(fulfilled, rejected){
- this.handlers.push({
- fulfilled: fulfilled,
- rejected: rejected
- });
- return this.handlers.length -1;
- }
- export default InterceptorManager;
在 Axios.js 文件中,構(gòu)造函數(shù)有 interceptors屬性:
- //Axios.js
- function Axios(config){
- this.interceptors = {
- request: new InterceptorManager(),
- response: new InterceptorManager()
- }
- }
這樣我們?cè)?Axios.prototype.request 方法中對(duì)攔截器添加處理:
- //Axios.js
- Axios.prototype.request = function(config){
- const adapter = getDefaultAdapter(config);
- var promise = Promise.resolve(config);
- var chain = [adapter, undefined];
- //請(qǐng)求攔截
- this.interceptors.request.handlers.forEach(item=>{
- chain.unshift(item.rejected);
- chain.unshift(item.fulfilled);
- });
- //響應(yīng)攔截
- this.interceptors.response.handlers.forEach(item=>{
- chain.push(item.fulfilled);
- chain.push(item.rejected)
- });
- console.dir(chain);
- while(chain.length){
- promisepromise = promise.then(chain.shift(), chain.shift());
- }
- return promise;
- }
所以攔截器的執(zhí)行順序是:請(qǐng)求攔截2 -> 請(qǐng)求攔截1 -> 發(fā)送請(qǐng)求 -> 響應(yīng)攔截1 -> 響應(yīng)攔截2
取消請(qǐng)求
來(lái)到 Axios 最精彩的部分了,取消請(qǐng)求。我們知道 xhr 的 xhr.abort(); 函數(shù)可以取消請(qǐng)求。那么什么時(shí)候執(zhí)行這個(gè)取消請(qǐng)求的操作呢?得有一個(gè)信號(hào)告訴 xhr 對(duì)象什么時(shí)候執(zhí)行取消操作。取消請(qǐng)求就是未來(lái)某個(gè)時(shí)候要做的事情,你能想到什么呢?對(duì),就是 Promise。Promise 的 then 方法只有 Promise 對(duì)象的狀態(tài)變?yōu)?resolved 的時(shí)候才會(huì)執(zhí)行。我們可以利用這個(gè)特點(diǎn),在 Promise 對(duì)象的 then 方法中執(zhí)行取消請(qǐng)求的操作??创a:
- //CancelToken.js
- // 取消請(qǐng)求
- function CancelToken(executor){
- if(typeof executor !== 'function'){
- throw new TypeError('executor must be a function.')
- }
- var resolvePromise;
- this.promise = new Promise((resolve)=>{
- resolveresolvePromise = resolve;
- });
- executor(resolvePromise)
- }
- CancelToken.source = function(){
- var cancel;
- var token = new CancelToken((c)=>{
- ccancel = c;
- })
- return {
- token,
- cancel
- };
- }
- export default CancelToken;
當(dāng)我們執(zhí)行 const source = CancelToken.source()的時(shí)候,source 對(duì)象有兩個(gè)字段,一個(gè)是 token 對(duì)象,另一個(gè)是 cancel 函數(shù)。在 xhr 請(qǐng)求中:
- //適配器
- // adapter.js
- function xhrAdapter(config){
- return new Promise((resolve, reject)=>{
- ...
- //取消請(qǐng)求
- if(config.cancelToken){
- // 只有 resolved 的時(shí)候才會(huì)執(zhí)行取消操作
- config.cancelToken.promise.then(function onCanceled(cancel){
- if(!xhr){
- return;
- }
- xhr.abort();
- reject("請(qǐng)求已取消");
- // clean up xhr
- xhr = null;
- })
- }
- })
- }
CancelToken 的構(gòu)造函數(shù)中需要傳入一個(gè)函數(shù),而這個(gè)函數(shù)的作用其實(shí)是為了將能控制內(nèi)部 Promise 的 resolve 函數(shù)暴露出去,暴露給 source 的 cancel 函數(shù)。這樣內(nèi)部的 Promise 狀態(tài)就可以通過(guò) source.cancel() 方法來(lái)控制啦,秒啊~ 👍
node 后端接口
node 后端簡(jiǎn)單的接口代碼:
- const express = require("express");
- const bodyParser = require('body-parser');
- const app = express();
- const router = express.Router();
- //文件下載
- const fs = require("fs");
- // get 請(qǐng)求
- router.get("/getCount", (req, res)=>{
- setTimeout(()=>{
- res.json({
- success: true,
- code: 200,
- data: 100
- })
- }, 1000)
- })
- // 二進(jìn)制文件流
- router.get('/downFile', (req, res, next) => {
- var name = 'download.txt';
- var path = './' + name;
- var size = fs.statSync(path).size;
- var f = fs.createReadStream(path);
- res.writeHead(200, {
- 'Content-Type': 'application/force-download',
- 'Content-Disposition': 'attachment; filename=' + name,
- 'Content-Length': size
- });
- f.pipe(res);
- })
- // 設(shè)置跨域訪問(wèn)
- app.all("*", function (request, response, next) {
- // 設(shè)置跨域的域名,* 代表允許任意域名跨域;http://localhost:8080 表示前端請(qǐng)求的 Origin 地址
- response.header("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
- //設(shè)置請(qǐng)求頭 header 可以加那些屬性
- response.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
- //暴露給 axios https://blog.csdn.net/w345731923/article/details/114067074
- response.header("Access-Control-Expose-Headers", "Content-Disposition");
- // 設(shè)置跨域可以攜帶 Cookie 信息
- response.header('Access-Control-Allow-Credentials', "true");
- //設(shè)置請(qǐng)求頭哪些方法是合法的
- response.header(
- "Access-Control-Allow-Methods",
- "PUT,POST,GET,DELETE,OPTIONS"
- );
- response.header("Content-Type", "application/json;charset=utf-8");
- next();
- });
- // 接口數(shù)據(jù)解析
- app.use(bodyParser.json())
- app.use(bodyParser.urlencoded({
- extended: false
- }))
- app.use('/api', router) // 路由注冊(cè)
- app.listen(8081, () => {
- console.log("服務(wù)器啟動(dòng)成功!")
- });
git 地址
如果大家能夠跟著源碼敲一遍,相信一定會(huì)有很多收獲。