18207 字
91 分钟

Node.js学习

2026-02-10
浏览量 加载中...

一、初识Node.js#

1.1 Node.js是什么?#

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用 JavaScript 编写,并且 Node.js 是一个事件驱动的,非阻塞的,基于 I/O 的异步操作的 JavaScript 运行环境。

1.2 Node.js的核心特性#

Npde.js 的核心特性有:

  • 基于事件驱动,非阻塞的异步 I/O
  • 模块化
  • 跨平台
  • 快速、高效
  • 丰富的第三方模块

1.3 使用Node运行JavaScript代码#

Node.js 运行 JavaScript 代码的方式有很多,最常用的是使用 Node.js 的命令行工具。 当我们要使用Node运行一个js文件时,可以使用以下命令:

Terminal window
node Node_Example.js
# 运行结果:
HELLO Node.js

二、Node.js 的模块系统#

2.1 fs模块#

fs模块是Node.js最常用的模块,主要是对文件进行读写、创建、删除、修改、移动等操作。

它的导入方法也很简单,使用require()方法导入即可

const fs = require('fs');

2.1.1 读取文件内容#

# // 导入fs 模块
const fs = require('fs')
// 操作文件
fs.readFile('./files/1.txt', 'utf-8', function (err, dataStrs) {
# // 打印失败的结果
# // 如果读取成功则err的值为null
# // 如果读取失败则err的值为错误信息 undefined
console.log(err);
console.log("-----");
# // 打印成功的结果
console.log(dataStrs);
})
# 1.txt 文件内容的输出结果:
null
-----
1111

2.1.2 异步读取文件#

const fs = require('fs')
fs.readFile('./files/1.txt', 'utf-8', function (err, dataStrs) {
if (err) {
return console.log('文件读取失败', err.message)
}
return console.log('文件读取成功', dataStrs)
})
# 输出结果:
文件读取成功 1111

2.1.3 写入文件内容#

  • 使用fs.writeFile()方法写入文件内容, 可以向指定的文件中写入内容,语法格式如下:
fs.writeFile(file, data[, options], callback])
  • 注意:当我们重复调用fs.writeFile()方法写入文件的时候,会覆盖掉之前的内容。

  • fs.writeFile()的示例代码:

const fs = require('fs')
# // 调用fs.writeFile方法写入文件内容
# // 参数1:写入文件的路径
# // 参数2:写入的内容
# // 参数3:回调函数
fs.writeFile('./files/2.txt', 'hello world', function(err){
# // 如果没有出错,则返回null,如果出错,则返回错误信息
console.log(err);
})
# 输出结果:
null

2.1.4 异步写入文件内容#

const fs = require('fs')
# // 调用fs.writeFile方法写入文件内容
# // 参数1:写入文件的路径
# // 参数2:写入的内容
# // 参数3:回调函数
fs.writeFile('./files/3.txt', 'hello world', function(err){
// 如果没有出错,则返回null,如果出错,则返回错误信息undefined
if(err){
return console.log('写入失败', err.message)
}
console.log('3.txt,写入成功');
})
# 输出结果为:
3.txt,写入成功

2.1.5 成绩整理案例:#

此案例是结合前面fs模块的读写操作进行对成绩.txt文件的操作,将读取到的内容进行整理,并写入新的文件中。

const fs = require('fs')
fs.readFile('./files/成绩.txt', 'utf-8', function (err, dataStrs) {
if (err) {
return console.log('文件读取失败', err.message);
}
console.log('文件读取成功', dataStrs);
// 1.将数据进行分割
const arrOld = dataStrs.split(' ')
console.log(arrOld);
// 2.遍历分割后的数据成为一个新数组
const arrNew = []
arrOld.forEach(item => {
arrNew.push(item.replace('=', ': '))
});
console.log(arrNew);
// 3.进行数据的拼接
const newStr = arrNew.join('\r\n')
console.log(newStr);
// 4.将拼接后的数据写入到文件中
fs.writeFile('./files/整理后的成绩.txt', newStr, function (err) {
if (err) {
return console.log('文件写入失败', err.message)
}
return console.log('文件写入成功')
})
})
// 输出结果:
小红: 99
小白: 100
小黄: 70
小黑: 66
小绿: 88
文件写入成功

2.1.6 fs模块-路径动态拼接的问题#

在使用fs模块操作文件时,如果提供的操作路径是以./或../开头的相对路径,很容易出现路径动态拼接错误的问题。 原因:代码在运行的时候,会以执行node命令时所处的目录,动态拼接出被操作文件的完整路径

示例代码:

const fs = require('fs')
fs.readFile('./files/1.txt', 'utf-8', function (err, dataStrs) {
if(err) {
return console.log('文件读取失败', err.message)
}
console.log('文件读取成功', dataStrs);
})
Terminal window
# 当我们没有处在node.js的根目录下时,在控制台执行以下命令时,会出现报错,文件无法被读取
node ./code/ndoe 动态路径错误问题.js
  • 解决方法:使用__dirname来获取当前文件的所在目录,并拼接成完整的文件路径

示例代码:

const fs = require('fs')
fs.readFile(__dirname + './files/1.txt', 'utf-8', function (err, dataStrs) {
if(err) {
return console.log('文件读取失败', err.message)
}
console.log('文件读取成功', dataStrs);
})

2.2 path路径模块#

2.2.1 什么是path路径模块?#

path模块是Node.js中官方提供,用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径处理的需求

例如:

  • path.join()方法用来拼接路径`
  • path.basename()方法用来从路径字符串中,将文件名解析出来`

当然如果要在JavaScript代码中,使用path模块来处理路径,则需要先导入模块,代码如下:

const path = require('path');

2.2.2 path.join()方法#

使用path.join()方法来拼接路径,可以把多个路径片段拼接成一个完整的路径字符串,代码如下:

  • 示例一:
// 导入模块
const path = require('path')
const pathStr = path.join('/a', '/b/c', '../', './d','e')
// 注意../是可以抵消一层路径的
console.log(pathStr) // 输出结果: \a\b\d\e
  • 示例二:
// 导入模块
const path = require('path')
const pathStr1 = path.join(__dirname, './files/1.txt')
console.log(pathStr1)
// 输出结果: D:\Node.js基础\day1\CODE.\files\1.txt
// 这里是根据我电脑上的路径来写的,只做演示作用

示例二的方法更加方便简洁,因此在实际开发中,我们推荐使用示例二。

在今后的开发中,凡是涉及到路径拼接的操作,都要使用path.join()方法来拼接路径,不要直接使用 + 进行字符串的拼接

  • 示例代码:
const fs = require('fs');
const path = require('path');
fs.readFile(path.join(__dirname, '/files/example.txt'), 'utf8', (err, dataStr) => {
if (err) {
console.error('文件读取失败', err.message);
}
console.log(dataStr);
})
// 输出结果:
path.join路径演示

2.2.3 path.basename()方法#

path.basename() 的语法格式:

使用path.basename()方法可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名,语法格式如下:

path.basename(pathp[, ext])
  • path 必选参数
  • ext 可选参数, 表示文件扩展名
  • 返回: 表示路径中的最后一部分

path.basename() 的使用示例:

const path = require('path');
const fpath = '/a/b/c/index.html';
var fullName = path.basename(fpath);
console.log(fullName); // 输出结果: index.html
const path = require('path')
const gpath = '/a/b/c/index.html'
// 参数二为可选参数, 表示文件扩展名
var nameWithoutExt = path.basename(gpath, '.html')
consolo.log(nameFullName) // 输出结果: index

2.2.4 path.extname()#

path.extname()的语法格式:

使用path.extname()方法,可以获取路径中你的扩展名部分, 语法格式如下:

path.extname(path)
  • path必选参数,表示一个路径的字符串
  • 返回返回得到的扩展名字符串

path.extname()的代码示例:

const path = require('path')
// 这是文件的存放路径
const fpath = '/a/b/c/index.html'
const fext = path.extname(fpath)
console.log(fext)
// 输出结果:.html

2.2.5 时钟案例#

接下来我将演示一个时钟案例,此案例目的是为了能够让学习者更好的理解node.js中模块化的设定和使用。

这是我们要解耦的html文件中的源码: 可以看到该源码中包含了cssjavascript部分 后面我们将对这两部分进行解耦

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>index首页</title>
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
background-image: linear-gradient(to bottom right, red, gold);
}
.box {
width: 400px;
56 collapsed lines
height: 250px;
background-color: rgba(255, 255, 255, 0.6);
border-radius: 6px;
position: absolute;
left: 50%;
top: 40%;
transform: translate(-50%, -50%);
box-shadow: 1px 1px 10px #fff;
text-shadow: 0px 1px 30px white;
display: flex;
justify-content: space-around;
align-items: center;
font-size: 70px;
user-select: none;
padding: 0 20px;
/* 盒子投影 */
-webkit-box-reflect: below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(0%, transparent), to(rgba(250, 250, 250, .2)));
}
</style>
</head>
<body>
<div class="box">
<div id="HH">00</div>
<div>:</div>
<div id="mm">00</div>
<div>:</div>
<div id="ss">00</div>
</div>
<script>
window.onload = function () {
// 定时器,每隔 1 秒执行 1 次
setInterval(() => {
var dt = new Date()
var HH = dt.getHours()
var mm = dt.getMinutes()
var ss = dt.getSeconds()
// 为页面上的元素赋值
document.querySelector('#HH').innerHTML = padZero(HH)
document.querySelector('#mm').innerHTML = padZero(mm)
document.querySelector('#ss').innerHTML = padZero(ss)
}, 1000)
}
// 补零函数
function padZero(n) {
return n > 9 ? n : '0' + n
}
</script>
</body>
</html>
  • 创建一个clock.js文件

创建一个名为clock.js的文件,并写入以下代码:

// 1.导入模块
const fs = require('fs')
const path = require('path')
// 2.创建正则表达式,用于匹配对应的字符串
// 2.1.匹配style标签
const regiStyle = /<style>[\s\S]*<\/style>/
// 2.2.匹配script标签
const regiScript = /<script>[\s\S]*<\/script>/
// 调用fs.readFile()方法,读取文件
fs.readFile(path.join(__dirname, './index.html'), 'utf8', function (err, dataStr) {
if(err) {
return console.log('读取文件失败!' + err.message)
}
console.log('读取文件成功!', dataStr)
// 调用方法, 传入dataStr读取到的内容
59 collapsed lines
// 1.拆分HTML中的Css结构方法
resoloveCss(dataStr)
// 2.拆分HTML中的JS结构方法
resolveJs(dataStr)
// 3.创建Html文件
resolveHtml(dataStr)
})
// 创建方法
// 1.resolve CSS 方法
function resoloveCss(htmlStr) {
// 1.提取(匹配html页面中符合条件的字符串)
const r1 = registStyle.exec(htmlStr)
console.log(r1);
// 2.替换
const newCss = r1[0].replace('<style>', '').replace('</style>', '')
console.log('替换后的newHtml为:', newCss);
// 3.写入
fs.writeFile(path.join(__dirname, './files/newCss.css'), newCss, function (err) {
if(err) {
return console.log('写入文件失败', err.message);
}
console.log('写入文件成功');
})
}
// 2.resolveJs()方法
function resolveJs(htmlData) {
// 1.提取(符合的字符串) 匹配成功则返回数组,失败则返回null
const s1 = registScript.exec(htmlData)
console.log(s1);
// 2.替换
const newS1 = s1[0].replace('<script>', '').replace('</script>', '')
console.log('newS1替换结构为:', newS1);
// 3.写入
fs.writeFile(path.join(__dirname, './files/newJs.js'), newS1, function(err) {
if(err) {
return console.log('写入文件失败', err.message);
}
console.log('写入文件成功');
})
}
// 3.创建Html文件
function resolveHtml(htmlData) {
const newHtml = htmlData.replace(registStyle, '<link rel="stylesheet" href="./newCss.css">').replace(registScript, '<script src="./newJs.js"></script>')
console.log('newHtml打印结果:', newHtml);
fs.writeFile(path.join(__dirname, './files/newHtml.html'), newHtml, function(err) {
if(err) {
return console.log('写入文件失败', err.message);
}
console.log('写入文件成功');
})
}

运行这个代码,会生成一个名为 newHtml.html 的文件,并写入新的 HTML 内容。 将原本的index.htmlhtml css javascript的内容全部分离出来,并逐个写入新的文件中,最后在通过html的link方法引入css和JavaScript文件,完成页面的模块化,这点与Vue框架并无太大差别

2.3 http模块#

2.3.1 什么是http模块?#

在认识到http模块之前,我们先了解一些概念

  • 什么是客户端,什么是服务器?

    在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器。

  • 而http模块是什么呢?

    http模块是Node.js官方提供的、用来创建Web服务器的模块。通过http模块提供的http.createServer()方法,就能方便的把一台普通电脑,变成一台Web服务器,从而对外提供web资源服务。

2.3.2 如何使用http模块#

如果希望使用http模块,首先需要引入该模块:

const http = require('http');

2.3.3 进一步理解http模块的作用#

服务器和普通电脑的区别在于,服务器上安装了web服务器软件,例如:IIS、Apache等。通过安装这些服务器软件,就能把一台电脑变成一台web服务器。

在Node.js中,我们不需要使用IIS、Apache等这些第三方服务器软件,因为我们可以基于Node.js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供web服务。

2.3.4 服务器相关的概念#

  1. IP地址:

    IP地址就是相当于互联网上每台计算机的唯一地址,因此IP地址具有唯一性,如果把“个人电脑”比作一台电话,那么IP地址就是这个电话的电话号码,只有在知道对方IP地址的前提下,才能对应电脑之间进行数据通信。

    IP地址的格式: 通常用点分十进制表示成(a.b.c.d)的形式,其中a,b,c,d都是0-255之间的十进制整数。例如:用点分十进制表示的IP地址(192.168.1.1)

    • 注意:

      1. 互联网中每台web服务器,都有自己的IP地址, 例如:大家可以在Windows的终端中运行 ping www.baidu.com 命令,就会看到百度的IP地址。

      2. 在开发期间,自己的电脑既是一台服务器,也是一个客户端,为了方便测试,可以在自己的浏览器中输入 127.0.0.1 这个IP地址,就可以访问到自己的电脑。

  2. 域名域名服务器

    尽管IP地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不方便于记忆,雨伞人们又发明了另一套字符串型地址方案,即所谓的域名(Domain Name)地址

    IP地址域名一一对应的关系,这份对应关系存放在一种叫做域名服务器(DNS, Domain Name Server)的电脑中。只需要通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现,因此,域名服务器就是提供IP地址和域名之间的转换服务的服务器

    • 注意:

      1. 单纯使用IP地址,互联网中的电脑也能够正常工作,但是有了域名的加持,能让互联网的世界变得更加方便。

      2. 在开发测试期间, 127.0.0.1对应的域名是localhost,它们都代表我们自己的这台电脑,在使用效果上没有任何区别。

  3. 端口号

    计算机中的端口号,就好像是现实生活中的门牌号一样,通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你手中。

    同样的道理,在一台电脑中,可以运行成百上千个web服务,每个web服务都对应一个唯一的端口号,客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的web服务进行处理

    • 注意:
      1. 每个端口号不能同时被多个web服务占用
      2. 在实际应用中,URL中的80端口可以被省略

2.3.5 创建最基本的web服务器#

  1. 创建web服务器的基本步骤

    1. 导入http模块
    2. 创建web服务器实例
    3. 为服务器实例绑定request事件,监听客户端的请求
    4. 启动服务器
    • 步骤一 导入http模块
    const http = require('http');
    • 步骤二 创建web服务器实例

      调用http.createServer()方法创建一个web服务器实例

    const server = http.createServer();
    • 步骤三 为服务器实例绑定request事件,监听客户端的请求

      为服务器实例绑定request事件,即可监听客户端发送的网络请求

    // 使用服务器实例的 .on 方法,为服务器实例绑定 request 事件
    server.on('request', (req, res) => {
    // 只要有客户端来请求外卖自己的服务器,就会触发request事件,从而调用这个事件处理函数
    console.log('Someone has made a request to our server!');
    })
    • 步骤四 启动服务器

      调用服务器实例的 .listen 方法,即可启动当前的web服务器实例:

    // 调用server.listen(端口号,cb回调), 即可启动服务器
    server.listen(80, () => {
    console.log('Our server is running!');
    })
  2. req请求对象

    只要服务器接收到了客户端的请求,就会调用通过server.on()为服务器绑定request事件处理函数

    如果想在事件处理器函数中,访问与客户端相关的数据或属性, 可以使用如下的方式:

    const http = require('http');
    const server = http.createServer();
    server.on('request', (req) => {
    // req是请求对象,它包含了与客户端相关的数据和属性,例如:
    // req.url 是客户端请求的URL地址
    const url = req.url;
    // req.method 是客户端的method请求类型
    const method = req.method;
    // 拼接url和method
    const str = `You request url is ${method} and request method is ${url}`;
    // 打印str
    console.log(str);
    })
    server.listen(80, () => {
    console.log('server running at http://127.0.0.1');
    })

2.3.6 res响应对象#

在服务器的request事件处理函数中,如果想访问与服务器相关的数据或属性,可以使用如下的方式:

server.on('request', (req, res) => {
// res 是响应对象,它包含了与服务器相关的数据和属性,例如:
// 要发送到客户端的字符串
const str = `You request url is ${req.url} and request method is ${req.method}`
// res.end 方法的作用:
// 向客户端发送指定的内容,并结束这次请求的处理过程
res.end(str)
})
  • 解决中文乱码问题

当调用res.end()方法时,Node.js会默认将数据转换成UTF-8编码,但是对于中文字符,这个编码方式能会导致乱码, 此时, 需要手动设置内容的编码格式, 解决办法也很简单, 设置响应头 Content-Type: text/html; charset=utf-8即可

server.on('request', (req, res) => {
const str = '中文乱码'
// 设置响应头
res.setHeader('Content-Type', 'text/html; charset=utf-8')
// 返回响应数据
res.end(str)
})

2.3.7 根据不同的url响应不同的html内容#

  1. 核心实现步骤

    • 获取请求的url地址
    • 设置默认的响应内容404 Not Found
    • 判断用户请求的是否为/或者/index.html首页
    • 判断用户请求的是否为/about.html关于页面
    • 设置Content-Type响应头
    • 使用res.end()方法把内容,响应给客户端
  2. 示例代码

    const http = require('http');
    const server = http.createServer()
    server.on('request', (req, res) => {
    const url = req.url;
    // 设置默认的内容为 404 Not Found
    let content = `<h1>'404 Not Found'</h1>`;
    if(url === '/') {
    content = `<h1>'首页'</h1>`;
    }else if(url === '/about.html') {
    content = `<h1>'关于'</h1>`;
    }
    // 设置响应头 防止中文乱码
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    // 返回 页面content 的响应内容
    res.end(content);
    })
    server.listen(80, () => {
    console.log('http server running at http://127.0.0.1');
    })

2.3.8 http时钟web服务器案例#

在path路径模块的章节中,我们已经实现了一个http时钟服务器,现在我们来实现一个http时钟服务器,这个服务器会返回当前时间。

const fs = require('fs');
const path = require('path');
const http = require('http');
const server = http.createServer();
server.on('request',(req,res) => {
const url = req.url;
// 这里的所表达的意思是代表根目录的意思
const fpath = path.join(__dirname, url);
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if(err) {
res.end('404 Not Found')
return
}
res.end(dataStr)
})
})
server.listen(80, () => {
console.log('http server running at http://127.0.0.1');
})

需要注意的时候,当我们运行这个服务器,进入的默认URL是空路径,也就是根目录,需要我们手动在网址输入框添加/clock/index.html路径访问这个文件夹中的html文件,才能显示页面。

三、Node.js中的模块化#

3.1 什么是模块化?#

在编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖多个小模块

把代码进行模块化拆分的好处是:

  1. 提高了代码的复用性
  2. 提高了代码的可维护性
  3. 可以实现按需加载

3.2 模块化的规范#

模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则

例如:

  1. 使用什么样的语法格式来引用模块
  2. 在模块中使用什么样的语法格式向外暴露成员

模块化规范的好处: 大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。

3.3 Node.js 中模块化的分类#

Node.js中根据模块来源的不同,将模块分为了3大类,分别是:

  1. 内置模块(内置模块是由Node.js官方提供的, 例如fspathhttp等)
  2. 自定义模块(用户创建的每个.js文件,都是自定义模块)
  3. 第三方模块(由第三方开发出来的模块,例如expressmongooselodash等)

3.4 加载模块#

使用强大的require()方法,可以加载内置模块、用户自定义模块、第三方提模块进行使用。

例如:

// 1. 加载内置模块
const fs = require('fs');
// 2.加载自定义模块 (提供自定义模块的路径)
const myModule = require('./myModule');
// 3.加载第三方模块
const request = require('request');
  • 值得注意的是,require()方法返回的是一个对象,对象中包含模块的导出内容。同时,require()方法会缓存模块,即多次调用require()方法,返回的都是同一个对象。

  • 关于在使用require加载用户自定义模块期间是可以省略.js后缀的,例如:require('./myModule')

  • 关于使用require()方法加载第三方模块,require()方法会自动将模块路径中的./转换成__dirname。例如:require('./myModule')会被转换成require(__dirname + '/myModule')

3.5 Node.js中的模块作用域#

3.5.1 什么是模块作用域?#

函数作用域类似,在自定义模块中定于的变量方法等成员,只能在当前模块内被访问。这种模块级别的访问限制,叫做模块作用域

当我们定义了一个自定义模块命名为custom.js 在其模块内部定义一个函数:

const username = '张三'
function showName() {
console.log('我的名字是' + username)
}

当我们在其他模块中引用这个模块时,无法直接访问模块内部定义的变量和函数:

const custom = require('./custom')
console.log(custom) // 输出空对象
  • 模块作用域的好处:模块作用域能够很好的防止全局变量污染的问题

3.5.2 向外共享模块作用域中的成员#

  1. module对象

在每个.js自定义模块中都有一个module对象,它里面存储了和当前模块的有关的信息 我们可以在控制台打印module

console.log(module)
// module对象 输出结果:
{
id: '.',
path: 'D:\\Node.js基础\\day2',
exports: {},
filename: 'D:\\Node.js基础\\day2\\07.module属性.js',
loaded: false,
children: [],
paths: [
'D:\\Node.js基础\\day2\\node_modules',
'D:\\Node.js基础\\node_modules',
'D:\\node_modules'
],
Symbol(kIsMainSymbol): true,
Symbol(kIsCachedByESMLoader): false,
Symbol(kURL): undefined,
Symbol(kFormat): undefined,
Symbol(kIsExecuting): true
}

module对象是一个大对象,在这个大对象中包含许多属性,其中对象中有 exports 属性,这个属性是一个对象,这个对象就是我们向模块外共享的成员。

  1. module.exports 对象

在自定义模块中,可以使用module.exports对象,将模块中的成员共享出去。

这里我们自定义一个模块,在模块中定义一个变量,并使用module.exports对象将变量共享出去。

module.exports.username = '张三'
module.exports.showName = function () {
console.log('我的名字是' + this.username)
}

在其他模块中,可以通过require方法加载模块,并获取模块中的成员。

const username= require('./modules/自定义模块')
console.log(username)
// username的输出结果是一个对象:
{ username: '张三', showName: [Function (anonymous)] }

当然,外界在使用require方法导入自定义模块时,得到的就是module.exports所指向的对象。

  1. 共享成员时的注意点

使用require方法导入模块时,导入的结果,永远以module.exports指向的对象为准

这里我们依旧定义一个自定义模块,进行演示

module.exports.username = '张三'
module.exports.age = 18
module.exports.showName = function () {
console.log('我的名字是' + this.username)
}
// 为module.exports提供一个全新对象
module.exports = {
nickname: '小张',
age: 19,
showName: function () {
console.log('我的名字是' + this.nickname)
}
}

在外部引用我们自定义好的模块

const username = require('./modules/自定义模块2')
console.log(username);
// 输出结果:
{ nickname: '小张', age: 19, showName: [Function: showName] }

我们不难发现,module.exports是有赋值的,新的值会覆盖掉旧的值。

  1. exports对象

由于modules.exports单词写起来笔记复杂,为了简化向外共享成员的代码,Node提供了exports对象。默认情况下,exports和module.exports指向同一个对象,最终共享的结果,还是以module.exports为准。

console.log(exports);
console.log(module.exports);
console.log(exports === module.exports);
// 输出结果:
{}
{}
true
  1. exports 和 module.exports 的使用误区

需要注意的是,require()模块时,得到的永远是module.exports指向的对象

这里我们定义一个模块用于演示二者的关系

exports.a = 1;
module.exports = {
b: 2
c: 3
};

显而易见的是,当我们外部引用此模块并打印出对象的属性时,得到的是module.exports指向的对象,即{b: 2, c: 3}

那么,exports和module.exports指向的顺序反过来呢?我们可以尝试一下:

module.exports.a =1
exports = {
b: 2
c: 3
};
// 输出结果:
{a: 1 }

从以上的输出结果来看,无论顺序如何变化,我们得到的永远是module.exports指向的对象

当我们同时使用exports和module.exports定义两个变量,又会发送什么呢?这里我们举一个实例

exports.a = 1;
module.exports.b = 2;
// 输出结果:
{ a: 1, b: 2 }

从该实例的输出结果来看,我们发现当exports和module.exports在定义不同变量时候,彼此并不会冲突,而是会合并成一个对象,并返回给调用者。

3.6 CommonJS模块化规范#

Node.js遵循了CommonJS模块化规范,CommonJS规定了模块的特效各模块之间如何相互依赖。

  • CommonJS的规定:
  1. 每个模块内部,module变量代表当前模块
  2. module变量是一个对象,它的exports属性(即module.exports)是对外的接口)
  3. 加载某个模块,其实是加载该模块的module.exports属性, require()方法用于加载模块

3.7 npm与包#

3.7.1 什么是包?#

Node.js中的第三方模块又叫做

  1. 包的来源:

不同于Node.js中的内置模块与自定义模块,包是由第三方个人或团队开发出来的, 免费供所有人使用 Node.js中的包都是免费且开源的,不需要付费即可免费下载使用

  1. 为什么需要包?

由于Node.js的内置模块仅提供了一些底层的API,导致在基于内置模块进行项目开发时,代码量会非常大,因此,Node.js提供了第三方模块,使得开发人员可以快速开发项目,省去很多重复的代码编写工作。

包是基于内置模块封装出来的,提供了更高级,更方便的API,极大的提高了开发效率

内置模块之间的关系,类似于JQuery浏览器内置API之间的关系。

  1. 从哪里下载包?

国外有一家IT公司,叫做npm, Inc.这家公司旗下有一个非常著名的网站: https://www.npmjs.com/ ,它是全球最大的包共享平台,你可以从这个网站上搜索到任何你需要的包,只要你有足够的耐心!

到目前位置,全球约1100 多万的开发人员,通过这个包共享平台,开发并共享了超过120 多万个包供我们使用。npm, Inc.公司提供了一个地址为 https://registry,npmjs.org/ 的服务器,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包。

  1. 如何下载包?

npm, Inc.公司提供了一个包管理工具,我们可以使用这个包管理工具,从 https://registry,npmjis.org/ 服务器把需要的包下载到本地使用。

这个包管理工具的名字叫做 Node Package Manager(简称 npm包管理工具),这个包管理工具随着Node.js的安装包一起被安装到了用户的电脑上。

大家可以在终端中执行npm -v命令,来查看自己电脑上所安装的npm包管理工具的版本号

3.7.2 npm 初体验#

初次装包完成后,在项目文件夹中多一个叫node_modules的文件夹,里面存放着npm安装的包和package-lock.json的配置文件

其中node_modules用来存放所有已安装到项目中的包。require()导入第三方包时,就是从这个目录查找并加载包。

package-lock.json文件用来记录node_modules中安装的包的下载信息,例如包的名字、版本号、下载地址等。

  1. 安装指定版本的包

默认情况下,使用npm install命令安装包的时候,汇总的安装最新版本的包,如果需要安装指定版本的包,可以在包名之后,通过@符号指定版本号, 例如:

Terminal window
npm install express@4.17.1
  1. 包的语义化版本规范

包的版本是以 “点分十进制” 形式进行定义的,总共有三位数字,例如4.17.1

其中每一位数字的含义如下:

  1. 主版本号

  2. 功能版本

  3. Bug修改版本

  4. 多人协作问题

由于第三方包的体积过大,不方便团队成员之间共享项目源代码

解决方案也很简单, 只需要共享时剔除node_modules

在项目根目录中,创建一个叫做package.json的配置文件,即可用来记录项目中安装了哪些包, 从而方便剔除node_modiles目录之后,在团队成员之间共享项目的源代码。

今后在项目开发中,一定要把node_modules文件夹,添加到.gitignore 忽略文件中

  1. 快速创建package.json

npm包管理工具提供了一个快捷命令,可以在指向命令时所处的目录中,快速创建package,json这个包管理

Terminal window
npm init -y
  1. 值得注意的是 npm init -y 该命令只能在英文的目录下成功运行并且只需运行一遍
  2. 运行npm install 命令安装包的时候,npm包管理工具会自动把包的名称版本号,记录到package.json中。

3.7.3 dependencies节点#

在package.json文件中,有一个dependencies节点,专门用来记录使用npm install命令安装了哪些包。

3.7.4 devDependencies节点#

如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies节点中。

与之对应的,如果某些包在开发项目上线之后都需要用到,则建议把这些包记录到dependencies节点中。

可以使用以下命令将包记录到Devdependencies节点中

Terminal window
# 简写
npm i 包名 -D
# 完整写法
npm install 包名 --save-dev

3.7.5 卸载包命令#

Terminal window
npm uninstall

3.7.6 包的分类#

使用npm包管理工具下载的包,共分为两大类,分别是:

  1. 项目包

    那些被安装到项目 node_modules目录中的包,都是项目包。

  2. 全局包

    2.1 开发依赖包(被记录到devDependencies节点中的包,只会在开发期间用到)

    2.2 核心依赖包(被记录到dependencies节点中的包,在开发期间和项目上线之后都会用到)

    在执行npm install命令时,如果提供了-g参数, 则会把包安装为全局包

    Terminal window
    # 安装全局包
    npm -intall 包名 -g
    # 卸载全局包
    npm uninstall 包名 -g
    • 注意:

      1. 只有工具性质的包,才有全局安装的必要性,因为它们提供了好用的终端命令。

      2. 判断某个包是否需要全局安装才能使用,可以参考官方提供的使用说明即可。

3.7.7 i5ting_toc morkdown转换html模块#

i5ting_toc是一个可以把md文档转为html页面的小工具,使用步骤如下:

Terminal window
npm install -g i5ting_toc
i5ting_toc -f 要转换的md文件路径 -o

3.7.8 规范的包结构#

在清楚了包的概念,以及如何下载和使用包之后,接下来,我们深入了解以下`包的内部结构

一个规范的包,它的组成结构,必须符合以下3点要求:

  1. 包必须以单独的目录而存在
  2. 包的顶级目录下的要必须包含package.json这个包管理配置文件
  3. package.json中必须包含name,version,main这三个属性,分别代表包的名字、版本号、包的入口

3.7.9 开发属于自己的包#

  1. 需要实现的功能

    1.1 格式化日期

    1.2 转义HTML中的特殊字符

    1.3 还原HTML中的特殊字符

  2. 初始化包的基本结构

    2.1 新建tools文件夹,作为包的根目录

    2.2 在tools文件夹中,新建如下三个文件:

    1. package.json(包管理配置文件)

    2. index.js(包的入口文件)

    3. README.md(包的说明文档)

    • 初始化package.json文件
    {
    "name": "tools",
    "version": "1.0.0",
    "main": "index.js",
    "description": "提供了格式化时间, HTMLEscape相关的功能",
    "keywords": [
    "itheima",
    "dateformat",
    "escape"
    ],
    "license": "ISC"
    }
    • 在index.js中定义方法
    // 这是包的入口文件
    function dateFormat(dateStr) {
    const dt = new Date(dateStr)
    const y = padZero(dt.getFullYear())
    const m = padZero(dt.getMonth() + 1)
    const d = padZero(dt.getDate())
    const hh = padZero(dt.getHours())
    const mm = padZero(dt.getMinutes())
    const ss = padZero(dt.getSeconds())
    return `${y}-${m}-${d}-${hh}-${mm}-${ss}`
    }
    // 提供补零方法
    function padZero(n) {
    return n > 9 ? n : '0' + n
    }
    39 collapsed lines
    // html转义方法
    function htmlEscape(htmlStr) {
    return htmlStr.replace(/<|>|"|&/g, (match) => {
    switch(match) {
    case '<':
    return '&lt;'
    case '>':
    return '&gt;'
    case '"':
    return '&quot;'
    case '&':
    return '&amp;'
    }
    })
    }
    // html还原方法
    function htmlReturn(str) {
    return str.replace(/&lt;|&gt;|&quot;|&amp;/g, (match) => {
    switch(match) {
    case '&lt;':
    return '<'
    case '&gt;':
    return '>'
    case '&quot;':
    return '"'
    case '&amp;':
    return '&'
    }
    })
    }
    // 暴露方法
    module.exports = {
    dateFormat,
    htmlEscape,
    htmlReturn
    }
  3. 将不同的功能进行模块化拆分

    3.1 将格式化时间的功能,拆分到src -> dateForamt.js

    3.2 将处理HTML字符串的功能,拆分到src -> htmlEscape.js

    3.3 在index.js中,导入两个模块,得到需要向外共享的方法

    3.4 在index.js中,使用modules.exports把对应的方法共享出去

  4. 编写包的说明文档

    包根目录中的README.md文件,是包的使用说明文档,通过它,我们可以事先把包的使用说明,以markdown的格式写出来,方便用户参考

    README文件中具体写什么内容,没有强制性要求;只要能够清晰地包的作用、用法、注意事项等描述清楚即可。

    我们所创建这个包的README.MD文档中,会包含以下6项内容: 安装方式、导入方式、格式化时间、转义HTML中的特殊字符、还原HTML中的特殊字符、开源协议

  5. 发布包

    5.1 注册npm账号

    访问https://www.npmjs.com/网站,点击sign up按钮,进入注册用户界面

    填写账号相关的信息:Full Name、Public Email、Username、Password

    点击Create an Account按钮,注册账号

    5.2 登录npm账号

    npm账号注册完成后,可以在终端执行npm login命令,依次输入用户名,密码,邮箱后,即可登录成功。

    5.2 把包发布到npm上

    将终端切换到包的根目录之后,运行npm publish命令,即可将包发布到npm上(注意:包名不能雷同)

  6. 删除已发布的包

运行npm unpublish 包名 --force命令,即可从npm删除已发布的包

  • 注意:
    1. npm unpublish 命令只能删除72小时以内发布的包

    2. npm unpublish删除的包,在24小时内不允许重复发布

    3. 发布包的时候要慎重,尽量不要往npm上发布没有意义的包

3.8 模块的加载机制#

我们从一张图来了解Node.js中模块的加载机制

模块的加载机制

  1. 优先从缓存中加载

    模块在第一次加载后会被缓存。这也意味着多次调用rrequire()不会导致模块的代码被执行多次。

    • 注意: 不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提供模块的加载效率
  2. 内置模块的加载机制

内置模块是由Node.js官方提供的模块,内置模块的加载优先级别最高

例如,require(“fs”)始终返回内置的fs模块,即使在node_modules目录下有名字相同的包也叫做fs。

  1. 自定义模块的加载机制

使用require()记载自定义模块时,必须指定./../开头的路径标识符。在加载自定义模块时,如果没有指定./或../这样的路径标识符,则node会把它当作内置模块第三方模块进行加载

同时,在使用require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:

  1. 按照确切的文件名进行加载

  2. 补全.js扩展名进行加载

  3. 补全.node扩展名进行加载

  4. 补全json扩展名进行加载

  5. 加载失败,终端报错

  6. 第三方模块的加载机制

如果传递require()的模块标识符不是一个内置模块,也没有以./../开头,则Node.js会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块。

如果没有找到对应的第三方模块,则移动到上一层父目录中,进行加载,直到文件系统的根目录

例如,假设在C:\User\Node\project\example.js文件里调用了require('tools'),则Node.js会按以下顺序查找:

  1. C:\User\Node\project\node_modules\tools

  2. C:\User\Node\node_modules\tools

  3. C:\User\node_modules\tools

  4. C:\node_modules\tools

  5. 如果都没有,则终端报错

  6. 目录作为模块

当把目录作为模块标识符,传递给require()进行加载的时候,有三种加载方式:

  1. 在被加载的目录下查找一个叫做package.json的文件,并寻找main属性,作为require()加载的入口

  2. 如果目录里没有package.json文件,或者main入口不存在或无法解析,则Node.js将会试图加载目录下的index.js文件

  3. 如果以上两步都失败了,则Node.js会在终端打印错误消息,报告模块的缺失: Error: connot find module ‘xxx’

四、 Express框架#

4.1 初识Express#

4.1.1 什么是Express?#

官方给出的概念: Express是基于Node.js平台快速、开放、极简的Web开发框架

通俗的理解:Express的作用和Node.js内置的http模块类似,是专门用来创建Web服务器的

Express的本质: 就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法。

Express的中文官网: http://www.expressjs.com.cn/

那么不使用Express能否创建web服务器?

答案是可以的,使用Node.js提供的原生http模块即可。

不过http内置模块用起来很复杂,开发效率低,而EXpress是基于内置的http模块进一步封装处理的,能够极大的提高开发效率

http内置模块和Express是什么关系?

类似于浏览器中的Web API 和 JQuery的关系。后者是基于前者进一步封装出来的。

4.1.2 Express能够做什么?#

对于前端程序员来说,最常见的两种服务器,分别是:

  1. Web网站服务器:专门对外提供Web网页资源的服务器。

  2. API接口服务器:专门对外提供API接口的服务器。

使用Express,我们可以方便、快速的创建Web网站的服务器或API接口的服务器。

4.2 Express的基本使用#

  • 安装

在项目所处的目录中,运行如下的终端命令,即可将express安装到项目中使用:

Terminal window
npm i express

4.2.1 创建最基本的Web服务器#

// 1. 导入express
const express = require('express')
// 2.创建web服务器
const app = express()
// 调用app.listen(端口号,启动成功后的回调函数),启动服务器
app.listen(80, ()=> {
console.log('express server running at http://127.0.0.1')
})

4.2.2 监听GET请求#

通过app.get()方法,可以监听客户端的GET请求,具体的语法格式如下:

app.get('请求URL', function(req,res) {/* 处理函数 */})

4.2.3 监听POST请求#

通过app.post()方法,可以监听客户端的POST请求,具体的语法格式如下:

app.post('请求URL', function(req,res) {/* 处理函数 */})

4.2.4 把内容响应给客户端#

通过res.send()方法,可以把处理好的内容,发送给客户端:

app.get('/user',(req,res) => {
// 向客户端发送JSON对象
res.send({name: 'zs', age: 20, gender:'男'})
})
app.post('/user', (req,res) => {
// 向客户端发送文本内容
res.send('请求成功')
})

4.2.5 获取URL中携带的查询参数#

通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:

app.get('/', (req,res) => {
// req.query默认是一个空对象
// 客户端使用? name = zs&age=20 这种查询字符串形式,发送到服务器的参数,
// 可以通过 req.query 对象访问到,例如:
// req.query.name req.query.age
console.log(req.query)
})

4.2.6 获取URL中的动态参数#

通过req.params对象,可以访问到URL中,通过:匹配到的动态参数:

app.get('/user/:id', (req, res) => {
// req.params 默认是一个空对象
// 里面存放着通过:动态匹配到的参数值
console.log(req.params)
})

4.3 托管静态资源#

4.3.1 express.static()#

express提供了一个非常好用的函数,叫做express.static(), 通过它,我们可以非常方便地创建一个静态资源服务器,通过如下代码就可以将public目录下的图片、css文件、JavaScript文件对外开发访问了:

app.use(express.static(`public`))

现在,你就可以访问public目录中的所有文件了

  • 注意: Express在在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存在静态文件的目录名不会出现在URL中

4.3.2 托管多个静态资源目录#

如果要托管多个静态资源目录, 请多次调用express.static()函数:

app.use(express.static(`public`))
app.use(express.static(`files`))

访问静态资源文件时,express.static()函数会根据目录的添加顺序查找所需的文件。

4.3.3 挂载路径前缀#

app.use('/public', express.static('public'))

4.4 nodemon#

4.4.1 为什么要使用nodemon?#

在编写调试Node.js项目中,如果修改了项目的代码,则需要频繁的手动close掉,然后再重新启动,非常繁琐。

现在,我们可以使用nodemon这个工具,它能够监听项目文件的变动,当代码被修改后,nodemon会自动帮我们重启项目,极大方便了开发和调试。

  • 安装nodemon

在终端中,运行如下命令,即可以将nodemon安装为全局可用的工具:

Terminal window
npm i -g nodemon

4.4.2 使用nodemon#

当基于Nodejs编写了一个网站应用的时候,传统的方式,是运行nodeapp.js命令,来启动项目。这样做的坏处是:

代码被修改之后,需要手动重启项目。

现在,我们可以将 node命令替换为 nodemon命令,使用nodemon appjs来启动项目。这样做的好处是:代码 被修改之后,会被nodemon监听到,从而实现自动重启项目的效果。

Terminal window
node app.js
# 将上面的终端命令,替换为下面的终端命令,即可实现自动重启项目的效果
nodemon app.js

4.5 Express路由#

4.5.1 什么是路由?#

广义上来讲,路由就是映射关系

4.5.2 Express中的路由#

在Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系

Express中的路由由3部分组成,分别是请求的类型请求的URL地址处理函数, 格式如下:

app.METHOD(PATH,HANDLER)
  • Express中的路由的例子:
// 匹配GET请求,且请求URL 为/
app.get('/', function(req,res) {
res.send('Hello World')
})
// 匹配POST请求,且请求URL 为/
app.post('/', function(req,res) {
res.send('Got a POST request')
})

4.5.3 路由的匹配过程#

每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。

在匹配时,会按照路由的顺序进行匹配,如果请求类型请求的URL同时匹配成功,则Express会将这次请求,转交给对应的function函数进行处理。

  • 路由匹配的注意点:

    1. 按照定义的先后顺序进行匹配

    2. 请求类型请求的URL同时匹配成功,才会调用对应的处理函数

4.5.4 路由的模块化#

为了方便对路由进行模块化的管理,Express不建议将路由直接挂在到app上,而是推荐将路由抽离为单独的模块

将路由抽离为单独的模块的步骤如下:

  1. 创建路由模块对应的.js文件

  2. 调用express.Router()函数创建路由对象

  3. 向路由对象上挂载具体的路由

  4. 使用modules.exports 向外共享路由对象

  5. 使用app.use()函数注册路由模块

实例:

// 导入express
var express = require('express');
// 创建路由对象
var router = express.Router();
// 挂载路由
router.get('/user/list', function(req, res) {
res.send('Get User List');
});
router.post('/user/add', function(req, res) {
res.send('Add New User')
})
// 向外导出路由对象
module.exports = router;

在app.js中,使用app.use()函数,注册路由模块:

const express = require('express')
const app = express()
// 导入路由模块
const useRouter = require('./02.创建路由模块')
// 使用路由模块
app.use(useRouter)
// app.use() 函数的作用,就是来注册全局中间件
app.listen(80, () => {
console.log('at 127.0.0.1');
})

4.5.5 为路由模块添加前缀#

类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单:

cosnt useRouter = require('./user.js')
app.use('/api', useRouter)

4.6 中间件#

4.6.1 中间件的概念#

中间件,就是一段代码,在处理请求和响应的过程中,执行某些特定的任务

像安检一样 你要进机场 ✈️ → 先过安检 → 才能上飞机。

上一级的输出,作为下一级的输入

4.6.2 中间件的调用流程#

当一个请求到达Express服务器时,可以连续调用多个中间件,从而对这次请求进行预处理

中间件的处理流程图

4.6.3 中间件的格式#

Express的中间件,本质上就是一个function处理喊出,Express中间件的格式如下:

var express = require('express')
var app = express()
app.get('/', function(req, res, next) {
next();
})
app.litten(3000);
  • 注意: 中间件函数的形参列表中, 必须包含next参数,而路由处理函数中只包含reqres参数。

next函数的作用:

next() 函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件路由

4.6.4 定义中间件函数#

中间件函数的定义格式如下:

const myMiddleware = function(req, res, next) {
console.log('这是一个简单的中间件函数');
// 传递给下一个中间件或路由
next();
}

4.6.5 全局生效的中间件#

客户端发起任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。

// 可以使用app.use()方法,注册全局生效的中间件
app.use(myuMiddleware)

有意思的是, app.use()方法,可以简化全局注册中间件的过程

// 这是定义全局中间件的简化形式
app.use((req, res, next) => {
console.log('这是一个简单的中间件函数');
next();
})

4.6.6 中间件的作用#

多个中间件之间,共享同一个req和res对象,基于这样的特性,我们可以在上游的中间件中,统一为req或res对象添加自定义的属性或方法,供下游的中间件使用。

实例:

const express = require('express');
const app = express();
app.use((req, res, next) => {
// 获取到请求到达服务器的时间
const time = Date.now();
// 为req对象,挂载自定义属性,从而把时间共享给后面的所有路由
req.startTime = time;
next();
})
app.get('/', (req, res) => {
res.send('hello world' + req.startTime);
})
app.get('/user',(req,res) => {
res.send('user page' + req.startTime);
})
app.listen(3000, () => {
console.log('server is running at port 3000');
})

4.6.7 定义多个全局中间件#

可以使用app.use()方法,一次定义多个全局中间件,这样客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用。

app.use((req, res, next) => {
console.log('这是第一个全局中间件');
})
app.use((req, res, next) => {
console.log('这是第二个全局中间件');
})
app.use((req, res, next) => {
console.log('这是第三个全局中间件');
})

4.6.8 定义局部中间件#

不使用app.use()方法,定义中间件,这种定义方式,叫做局部中间件。

const myMiddleware = (req, res, next) => {
console.log('局部中间件');
next();
}
app.get('/', myMiddleware, (req, res) => {
res.send('hello world');
})
// 上面的局部中间件,只对get请求的'/'进行拦截处理,不会影响下面的路由
app.get('/user', (req, res) => {
res.send('user page');
})

4.6.9 定义多个局部中间件#

可以在路由中,通过如下两种等价的方式,使用多个局部中间件:

app.get('/', [myMiddleware1, myMiddleware2], (req, res) => {res.send('hello world');})

4.6.10 了解中间件的5个使用注意事项#

  1. 一定要在路由之前注册中间件

  2. 客户端发送过来的请求,可以连续调用多个中间件进行处理。

  3. 执行完中间件的业务代码之后,不要忘记调用next()函数

  4. 为了防止代码逻辑混乱,调用next()函数后不要再写额外的代码。

  5. 连续调用多个中间件时,多个中间件之间,共享req和res对象。

4.7 中间件的分类#

为了方便理解记忆中间件的使用,Express官方把常见的中间件用法,分成了5大类:

  1. 应用级别的中间件

  2. 路由级别的中间件

  3. 错误级别的中间件

  4. Express内置中间件

  5. 第三方中间件

4.7.1 应用级别的中间件#

通过app.use()或app.get()或app.post(),绑定到app实例上的中间件,叫做应用级别的中间件,代码示例如下:

// 应用级别中间件(全局中间件)
app.use((req, res, next) => {
console.log('这是应用级中间件');
next();
})
// 应用级别中间件(局部中间件)
app.get('/', (req, res) => {
console.log('这是局部中间件');
res.send('hello world');
})

4.7.2 路由级别的中间件#

绑定到express.Router()实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别,只不过,应用级别中间件是绑定到app实例上,路由级别中间件绑定到router实例上, 代码示例如下:

var app = express()
var router = express.Router()
router.use((req, res, next) => {
console.log('这是路由级中间件');
next();
})
app.use('/', router)

4.7.3 错误级别的中间件#

错误级别中间件的作用: 专门来捕获整个项目中发送的异常错误,从而防止项目异常崩溃的问题。

app.get('/', (req, res, next) => { // 1. 路由
throw new Error('抛出一个错误') // 2. 抛出一个错误
res.send('hello world');
})
app.use((err, req, res, next) => { // 2.错误级别中间件
console.log('发送了错误' + eer.message); // 2.1在服务器中打印错误信息
res.send('Error: ' + err.message); // 2.2发送错误信息给客户端
})
  • 注意: 错误级别的中间件,必须注册在所有路由之后

4.7.4 Express内置中间件#

自Express 4.16.0版本起,Express内置了很多中间件,这些中间件不需要单独安装直接引入使用, 极大的提高了Express开发效率。

  1. express.static() 快速托管静态资源的内置中间件,例如: HTML文件、图片、CSS样式等(无兼容性)。

  2. express.json() 解析JSON格式的请求体数据(有兼容性, 仅在4.16.0以上版本可用)`

  3. express.urlencoded() 解析URL-encoded格式的请求体数据(有兼容性, 仅在4.16.0以上版本可用)`

使用方法:

app.use(express.static('./public'))
app.use(express.json())
app.use(express.urlencoded({encoded: false}))

下面对express.json 和 express.urlencoded()中间件的使用,进行演示:

const express = require('express');
const app = express();
// 调用express内置中间件:解析json格式数据
app.use(express.json())
app.use(express.urlencoded({extended: false}))
// 定义路由
app.post('/user', (req, res) => {
// 获取数据
console.log(req.body);
19 collapsed lines
res.send('ok')
})
app.post('/book', (req,res) => {
// 在服务器端,可以通过req.body来获取json格式数据和urlencoded格式数据
console.log(req.body);
res.send('ok')
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
// 输出结果:
// 1. 浏览器发送json格式数据
{ name: 'zhangsan', age: 18 }
// 2. 浏览器发送urlencoded格式数据
[Object: null prototype] { boookname: '水浒传', author: '施耐庵' }

4.7.5 第三方中间件#

非Express官方提供的中间件,我们自己编写的,或者别人封装好的,都是第三方中间件,可以按需下载或配置第三方中间件,从而提高项目的开发效率

例如: @express@4.16.0之前的版本中,经常使用body-parser这个第三方中间件,来解析请求体数据,使用步骤如下:

  1. 运行npm install body-parser按照中间件

  2. 使用require导入中间件

  3. 调用app.use()注册并使用中间件

实例:

const express = require('express');
const app = express();
const parser = require('body-parser');
// 导入解析表单的中间件
app.use(parser.urlencoded({extended: false}))
app.post('/user', (req,res) => {
// 使用req.body接收post数据
console.log(req.body);
res.send('ok')
})
app.listen(80, () => {
console.log('express server running at http://127.0.0.1');
})
  • 注意: Express内置的express.urlencoded中间件,就是基于body-parser这个第三方中间件进一步封装出来的。

4.7.6 自定义中间件#

自己动手模拟一个类似于express.urlencoded这样的中间件,来解析POST提交到服务器的表单数据

实现步骤:

  1. 定义中间件

  2. 监听req的data事件

  3. 监听req的end事件

  4. 使用querystring模块解析实体数据

  5. 将解析出来的数据对象挂载为req.body

  6. 将自定义中间件封装为模块

完整代码如下:

const express = require('express');
const app = express();
const querystring = require('querystring');
// 注册全局中间件
// 这是用于解析表单数据的中间件
app.use((req, res, next)=> {
// 定义中间件的具体业务逻辑
let str = '';
// 监听req对象的data事件(客户端发送过来的新的请求体数据)
req.on('data', (chunk) => {
28 collapsed lines
// 定义中间件具体的业务逻辑
// 1.定义一个str字符串,专门用来存储客户端发送过来的请求体数据
str += chunk;
// 2.监听req的data事件
req.on('data',(chunk)=> {
str += chunk;
})
// 3.监听req的end事件
req.on('end', () => {
// TODO: 把字符串格式的请求体数据,解析成对象格式
const body = querystring.parse(str);
// 把解析后的结果,挂载到req.body属性上,由于这个中间件,在所有路由中,req.body,都是可用的
req.body = body;
// 4.把next(),调用下一个中间件或者路由
next();
})
})
})
app.post('/user', (req, res) => {
console.log(req.body);
res.send('ok');
})
app.listen(80, () => {
console.log('http://127.0.0.1');
})

当然了,最好是进行封装,封装为一个自定义的中间件模块,方便调用, 使用exports导出

module.exports = bodyParser;

4.8 使用Express写接口#

4.8.1 创建基本服务器#

创建一个最基本的服务器,并监听80端口:

// 导入express
const express = require('express');
// 创建服务器实例
const app = express();
// 在这里写你的业务代码
// 监听端口,启动服务
app.listen(80, () => {
console.log('http://127.0.0.1');
})

4.8.2 创建API路由模块#

创建一个API路由模块,并使用app.use()注册这个路由模块,这样,就可以使用API路由模块中的路由了。

const express = require('express');
const apiRouter = express.Router();
// 在这里挂载对应的路由
module.exports = apiRouter;
// app.js[导入并注册路由模块]
const router = require('./apiRouter');
app.use('/api', router);

4.8.3 编写GET接口#

const express = require('express');
const app = express();
const router = express.Router();
// 在这里挂载对应的路由
router.get('/get', (req, res) => {
// 通过 req.query 获取客户端字符串,发送到服务器的数据
const query = req.query;
// 调用res.send()方法,向客户端响应处理的结果
res.send({
status: 0, //表示成功,1表示失败
message: 'GET请求成功', //状态的描述
data: query //需要响应给客户端的数据
});
});
module.exports = router;

后续我们只需在外部引入该文件,并使用app.use()方法挂载该路由模块即可

4.8.3 编写POST接口#

// 定义post接口
router.post('/post', (req, res) => {
const body = req.body;
res.send({
status: 0,
message: 'POST请求成功',
data: body
})
});
  • 注意: 两个请求接口,可以放在同一个文件中,也可以放在不同的文件中,引入方法依旧使用app.use()

4.9 跨域资源共享#

4.9.1 接口的跨域问题#

刚才编写的GET和POST接口,存在一个很严重的问题: 不支持跨域请求

造成这个问题的原因是,接口使用的http协议,而用本地打开的html文件l’l文件使用的是file协议。

浏览器对file协议不支持跨域请求,所以接口无法访问。

4.9.2 使用cors中间件解决跨域问题#

cors中间件,是一个第三方的中间件,用于解决跨域问题。

使用步骤:

  1. 安装cors中间件: npm install cors

  2. 引入cors中间件: const cors = require('cors')

  3. 使用cors中间件: app.use(cors())

4.9.3 什么是cors?#

cors是Cross-Origin Resource Sharing的缩写,中文翻译为跨域资源共享。

由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。

浏览器的同源安全策略默认会阻止网页跨域获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以解决浏览器前端的跨域访问限制。

CORS的注意事项:

  1. CORS主要在服务器端进行配置,客户端浏览器无须做任何额外的配置,即可请求开启了CORS的接口。

  2. CORS在浏览器中有兼容性,只有支持XMLHttpRequest Level 2的浏览器才能使用CORS。

4.9.4 CORS相关的三个响应头#

  1. Access-Control-Allow-Origin:表示允许的源,即允许的域名。

其语法如下:

Access-Control-Allow-Origin: <origin> | *

其中, orgigin参数的值指定了允许访问该资源的外域URL

例如,下面的字段值将只允许来自http://www.example.com的请求访问该资源:

res.setHeader('Access-Control-Allow-Origin', 'http://www.example.com');
  1. Access-Control-Allow-Headers: 表示允许的请求头字段

默认情况下, CORS仅支持客户端向服务器发送如下的9个请求头:

  • Accept、Acccept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type

如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过设置Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败。

  1. CORS-Allow-Methods: 表示允许的请求方法

默认情况下,CORS仅支持客户端发起的GET、POST、HEAD请求。

如果客户端希望通过PUT、DELETE、PATCH等请求方式向服务器发送请求,则需要在服务器端,通过设置Access-Control-Allow-Methods对请求方式进行声明,否则这次请求会失败。

示例:

// 只允许POST、PUT、DELETE、PATCH请求
res.setHeader('Access-Control-Allow-Methods', 'POST, PUT, DELETE, PATCH');
// 允许所有请求方式
res.setHeader('Access-Control-Allow-Methods', '*');

4.9.5 CORS简单请求与预检请求#

  1. 简单请求

同时满足以下条件时,为简单请求:

  1. 请求方式: GET、POST、HEAD

  2. 预检请求

只要符合以下任何一个条件的请求。都需要进行预检请求:

  1. 请求方法为GET、POST、HEAD、之外的请求Method类型

  2. 请求头中包含自定义头部字段

  3. 向服务器发送了application/json格式的请求体

在浏览器与服务器正是通信之前,浏览器会先发送OPTION请求进行预检,已获知服务器是否允许该实际请求,所以这一次的OPTION请求称为预检请求。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。`

  1. 简单请求和预检请求的区别

    1. 降低请求的特点: 客户端与服务器之间只会发送一次请求。

    2. 预检请求的特点:客户端与服务器之间会发送两次请求,OPTION预检请求成功之后,才会发起真正的请求。

五、MySQL模块#

5.1 安装并配置mysql模块#

在项目中操作数据库的步骤:

  1. 安装操作MySQL 数据库的第三方模块(mysql)

  2. 通过mysql模块连结到MySQL数据库

  3. 通过mysql模块执行SQL语句

  • 步骤一 安装mysql模块
Terminal window
# 由于老版本的mysql模块不支持最新版本的数据库协议,所以安装mysql2模块
npm install mysql2
  • 步骤二 连接数据库
// 1.导入mysql模块
const mysql = require('mysql2')
// 2.创建数据库连接对象
const db = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: '123456',
database: 'my_db_01',
})
// 测试 mysql 模块能否正常工作
db.query('select 1', (err, results) => {
// 如果执行 SQL 语句失败了,则 err 的值是一个 错误对象,否则 err 的值为 null
if(err) {
return console.log(err.message)
}
console.log(results)
})

最终的输出结果为

Terminal window
[ { '1': 1 } ]

5.2 查询和插入数据#

  1. 查询数据
// 查询users表中的所有数据
db.query('select * from users', (err, results) => {
// 如果执行 SQL 语句失败了,则 err 的值是一个 错误对象,否则 err 的值为 null
if (err) {
return console.log(err.message)
}
// console.log(results)
console.log(results)
})
  1. 插入数据
// 插入数据
const user = `INSERT INTO users (username, password) VALUES (?, ?)`
db.query(user, ['admin', '123456'], (err, results) => {
if (err) {
return console.log(err.message)
}
// 注意 如果执行的是 insert into 插入语句,则results 是一个对象
// 在这个对象中有一个属性叫 affectedRows
// 可以通过 affectedRows 来判断是否插入成功
if(results.affectedRows === 1) {
console.log('插入数据成功')
}
})

5.3 更新和删除数据#

  1. 更新数据
// 更新数据
const user = `UPDATE users SET username=?, password=? WHERE id=?`
db.query(user, ['admin123', '123456', 2], (err, results) => {
if (err) {
return console.log(err.message)
}
// 注意 如果执行的是 insert into 插入语句,则results 是一个对象
// 在这个对象中有一个属性叫 affectedRows
if(results.affectedRows === 1) {
console.log('更新数据成功')
}
})
  1. 删除数据
// 删除数据
const user = `DELETE FROM users WHERE id=?`
db.query(user, [2], (err, results) => {
if (err) {
return console.log(err.message)
}
if (results.affectedRows === 1) {
console.log('删除数据成功')
}else {
console.log('没有此用户')
}
})

5.4 标记删除#

使用逻辑删除,将数据标记为删除,而不是真正的删除数据。

  1. 在表结构中添加字段
ALTER TABLE users ADD status TINYINT DEFAULT 0;
  1. 删除数据
const sql = 'update users set status = 1 where username = ?'
// 删除数据
const user = `UPDATE users SET status=? WHERE id=?`
db.query(user, [1, 1], (err, results) => {
if (err) {
return console.log(err.message)
}
if (results.affectedRows === 1) {
console.log('标记删除数据成功')
}else {
console.log('没有此用户')
}
})

六、 Web开发模式#

目前主流Web开发模式有两种,分别是:

  1. 基于服务器端渲染的传统Web开发模式

  2. 基于前后端分离的Web开发模式

6.1 服务器渲染的Web开发模式#

服务器渲染的概念: 服务器发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的。 因此,客户端不需要使用Ajax这样的技术额外请求页面的数据。代码示例如下:

app.get('/idnex.html', (req, res) {
// 1. 要渲染的数据
const user = {name: 'zs', gae: 20}
// 2. 服务器端通过字符串的拼接,动态生成HTML内容
const html = `<h1>姓名:$${user.name}, 年龄:${user.age} </h1>`
// 3. 把生成好的页面内容响应给客户端,因此,客户端拿到的是带有真实数据的HTML页面
res.send(html)
})

服务器渲染的优缺点

  1. 优点:

    1. 前端耗时少。

    2. 有利于SEO。

  2. 缺点:

    1. 占用服务器端资源。

    2. 不利于前后端分类。

6.2 前后端分离的Web开发模式#

前后端分离的概念: 前后端分离的开发模式,依赖于Ajax技术的广泛应用。简而言之,前后端分离的Web开发模式,就是后端只负责API接口,前后和使用Ajax调用接口的开发模式。

前后端分离的Web开发模式的优缺点

  1. 优点:

    1. 开发体验好。

    2. 用户体验好。

    3. 减轻了服务器端的渲染压力。

  2. 缺点:

    1. 不利于SEO
  • 不过利用VUE、React等前端框架的SSR(server side render)技术能够很好的解决SEO问题。

6.3 如何选择Web开发模式#

不谈业务场景而盲目选择使用何种开发模式都是耍流氓。

  • 比如企业级网站,主要功能是展示而没有复杂的交互,并且需要良好的SEO,则这时我们就需要使用服务器端渲染。

  • 而类似后台管理项目,交互性比较强,不需要考虑SEO,那么就可以使用前后端分离的开发模式。

另外,具体使用何种开发模式并不是绝对的,为了同时兼顾首页的渲染速度前后端分离的开发效率,一些网站采用了首屏服务器渲染+其他页面前后端分离的开发模式。

七、身份认证的概念#

7.1 什么是身份认证?#

身份认证(Authentication)又称’身份验证’、‘鉴权’, 是指通过一定的手段,完成对用户身份的确认

7.2 为什么需要身份认证?#

身份认证是保证用户安全的必要条件。是为了确认当前所声称为某种身份的用户,确实是所声称的用户。

7.2.1 不同开发模式下的身份认证#

对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案:

  1. 服务器端推荐使用session或者cookie进行身份认证。

  2. 前后端分离推荐使用token或者jwt进行身份认证。

八、 session认证机制#

8.1 HTPP协议的无状态性#

了解HTTP协议无状态性是进一步学习Session认证机制的必要条件。

HTTP协议是无状态的,即一次请求和响应之间没有状态信息。这意味着,每次请求和响应之间都是新的,没有 Previous Request 和 Previous Response 的概念。

8.1.1 如何突破HTTP无状态性?#

对于超市来说,为了方便收银员在进行结算时给VIP用户打折,超市可以为每个VIP用户发放会员卡。

现实生活中的会员卡身份认证方式,在Web开发中的专业术语叫做Cookie

8.2 什么是Cookie?#

Cookie是存储在用户浏览器中的一段不超过4KB的字符串。它由一个名称(Name),一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围可选属性组成。

不同域名下的Cookie各自独立,每当客户端发起请求时,会自动当前域名下所有未过期的Cookie一同发送到服务器。

Cookie的几大特性:

  1. 自动发送

  2. 域名独立

  3. 过期时限

  4. 4KB限制

8.2.1 Cookie在身份证中的作用#

客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的Cookie,客户端会自动将Cookie保存在浏览器中。

随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。

8.2.2 Cookie不具有安全性#

由于Cookie是存储在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性,因此不建议服务器将终于的隐私数据,通过Cookie的形式发送给浏览器。

  • 注意: 千万不要使用Cookie存储重要且隐私的数据,比如用户的身份认证、密码等。

8.2.3 提高身份认证的安全性#

为了防止客户伪造会员卡,收银员在拿到客户出示的会员卡之后,可以在收银机上进行刷卡认证。只有在收银机确认存在的会员卡,才能被正常使用。

这种会员卡+刷卡认证的设计理念,就是Session认证机制的精髓。

8.2.4 Session的工作原理#

Session工作原理

8.3 在Express中使用Session认证#

  1. 安装Session模块
Terminal window
npm install express-session
  1. 配置Session
// 1.导入Session模块
var session = require('express-session');
// 2.配置Session中间件
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))

8.3.1 向session中保存数据#

当express-session中间件生效时,会为req对象添加session属性,即req.session, 这个属性是一个对象,可以保存用户信息

实例:

app.post('/api/login', (req, res) => {
// 判断用户名密码是否正确
if (req.body.username !== 'admin' && req.body.password !== '123') {
return res.send({ status: 1, msg: '登录失败' })
}
// 登录成功
req.session.user = req.body // 保存用户名到session对象中
req.sesstion.isLogin = true // 保存登录状态
res.send({ status: 0, msg: '登录成功' })
})

8.3.2 从session中获取数据#

可以直接从req.session中获取之前存储在session中的数据

实例:

// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
// 判断用户是否登录
if(!req.session.isLogin) {
return res.send({ status: 1, msg: '获取用户名失败' })
}
res.send({ status: 0, msg: '获取用户名成功', username: req.session.user.username })
})

8.3.3 删除session数据#

调用req.session.destroy()方法即可清空服务器的session信息

实例:

// 退出登录的接口
app.post('/api/logout', (req, res) => {
// 销毁session数据
req.session.destroy()
res.send({ status: 0, msg: '退出登录成功' })
})

九、JWT认证机制#

9.1 了解Session认证的局限性#

Session认证机制需要配合Cookie才能实现,由于Cookie默认不支持跨域访问,所以当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域Session认证。

  • 注意:
  1. 当前端请求后端接口不存在跨域问题的时候,推荐使用Session身份认证机制。

  2. 当前端请求后端接口存在跨域问题时,推荐使用JWT认证机制

9.2 什么是JWT?#

JWT(JSON Web Token)是一种用于在客户端和服务器之间传递数据的安全方法。是目前最流行的跨域认证解决方案

9.3 JWT的工作原理#

JWT的工作原理

总结: 用户的信息通过Token字符串的新式,保存在客户端浏览器中。服务器通过还原Token字符串的形式来认证用户的身份。

9.4 JWT的组成部分#

JWT 由三部分组成:header(头部)、payload(有效负载)、signature(签名)

三者之间使用英文的”.”分隔,格式如下:

Header.Payload.Signature

9.4.1 JWT的三个部分各自代表的含义#

JWT 的三个部分组成部分,从前到后分别是Hheader, payload, signature。

  1. Payload部分才是真正的用户信息,它是用户信息经过加密城市的字符串。

  2. Header和Signature部分是安全性相关的部分,只是为了保证Token的安全性。

9.5 JWT的使用方式#

客户端收到服务器返回的JWT后,通常会将它存储在localStorage或者SessionStorage中。

此后,客户端每次与服务器通信,都要带上这个JWT的字符串,从而进行身份认证,推荐的做法是把JWT放在HTPP请求头的Authorization字段中。

示例代码:

Authorization: Bearer <token>

9.5.1 安装JWT相关的包#

Terminal window
npm install jsonwebtoken express-jwt

其中:

  1. jsonwebtoken用于生成JMT字符串

  2. express-jwt用于将JWT字符串解析还原成JSON对象

9.5.2 导入JWT相关的包#

使用require()函数,分别导入JWT相关的两个包:

// 1. 导入用于生成JWT字符串的包
const jwt = require('jsonwebtoken');
// 2. 导入用于将客户发送过来的JWT字符串,解析还原成JSON对象的包
const expressJwt = require('express-jwt');

9.5.3 定义secret密钥#

为了保证JWT字符串的安全性,防止JWT字符串被恶意篡改, 我们需要专门定义一个用于加密解密的secret密钥。

  1. 当生成JWT字符串时,需要使用secret密钥对用户的信息进行加密,最终得到加密的JWT字符串

  2. 当把JWT字符串解析还原成JSON对象的时候,需要使用secret密钥进行解密

const secretKey = 'this is a secret key'

9.5.4 在登录成功后生成JWT字符串#

调用jsonwebtoken的sign()方法,将用户的信息加密成JWT字符串,响应给客户端:

// 登录接口
app.post('/api/login', function (req, res) {
// 将 req.body 请求体中的数据,转存为 userinfo 常量
const userinfo = req.body
// 登录失败
if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
return res.send({
status: 400,
message: '登录失败!'
})
}
// 登录成功
// TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
token = jwt.sign({username: userinfo.username}, secretKey, {expiresIn: '30s'})
res.send({
status: 200,
message: '登录成功!',
token: token // 要发送给客户端的 token 字符串
})
})

9.5.5 将JWT字符串还原为JSON对象#

客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的Authorization字段, 将Token字符串发送到服务器进行身份认证。

此时,服务器可以通过express-jwt中间件,将客户端发送过来的Token字符串还原为JSON对象。

// 使用app.use()注册中间件
// express.JWT({secret: secretKey}) 就是用来解析Token的中间件
// .unless({path: [/^\/api\//]}) 指定哪些接口不需要访问权限(即不需要提供 token 令牌),以数组的形式传入
app.use(expressJWT({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [/^\/api\//] }))
// 登录接口

9.5.6 使用req.auth获取用户信息#

当express-jwt这个中间件配置成功之后,即可在那些有权限的接口中,使用req.user对象,来访问从JWT字符串中解析出来的用户信息了,示例代码如下:

app.get('/admin/getinfo', (req, res) => {
console.log(req.user)
res.send({
status: 200,
message: '获取用户信息成功',
data: req.auth
})
})

9.5.7 捕获解析JWT失败后产生的错误#

当使用express-jwt解析Token字符串,如果客户端发送过来的Token字符串过期或者不合法,会产生一个解析失败的错误,影响项目的正常运行。我们可以通过Express的错误中间件,捕获这个错误并进行相关的处理,示例代码如下:

app.use(function (err, req, res, next) {
// token解析失败导致的错误
if(err.name === 'UnauthorizedError'){
return res.status(401).json({status: 401, message: '无效的Token'})
}
// 其他原因导致的错误
res.send({status: 500, message: '未知错误'})
})

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Node.js学习
https://github.com/emn178/markdown
作者
Aloha
发布于
2026-02-10
许可协议
CC BY-NC-SA 4.0

评论区

目录