浏览器中JS组成
Chrome 浏览器的 V8 解析引擎性能最好
Node.js是一个基于 Chrome V8引擎的 JavaScript运行环境
http://nodejs.cn/
JS基础语法 + Node.js内置API模块(fs、path、http等) + 第三方API模块(express、mysql等)
打开终端,终端输入命令 node -v
后,回车即可
终端:专门为开发人员设计的,用于实现人机交互的一种方式,需要记一些常用的终端命令
快捷方式
shift + 右键
打开powershell窗口终端中的快捷键
↑
可以快速定位到上一次执行的命令tab键
能够快速补全路径esc
键 能够清空当前已输入的命令cls
命令 能够清空当前的终端fs模块是Node.js官方提供的、用来操作文件的模块。提供了一系列的方法属性,用来满足用户对文件的操作需求
如果在JS代码中,使用fs模块操作文件,需要导入
let fs = require('fs')
可以读取指定文件内容,语法格式如下
fs.readFile(path[,options],callback)
参数
//导入fs模块,来操作文件
const fs = require('fs')
//调用方法
//参数1:读取读取存放路径
//参数2:读取文件时候采用的编码格式,一般默认指定utf8
//参数3:回调函数,拿失败和成功的结果, err dataStr
fs.readFile('./files/1.txt', 'utf8', function(err, dataStr) {
//失败的结果
//如果读取成功 err 为 NULL
//如果读取失败 err 为 错误对象,dataStr 为 undefined
console.log(err);
console.log('--------------');
//成功的结果
console.log(dataStr);
})
判断文件是否读取成功
fs.readFile('./files/1.txt', 'utf8', function(err, dataStr) {
if (err) {
return console.log('读取失败!' + err.message);
}
console.log('读取成功' + dataStr);
})
可以向指定文件中写入内容
fs.writeFile(file, data[,options],callback)
参数
//导入fs文件
const fs = require('fs')
// 调用方法写入文件内容
// 参数1:文件存放路径
// 参数2:写入内容
// 参数3:回调函数
fs.writeFile('./files/2.txt', 'abcd', function(err) {
// 如果文件写入成功 err值为NULL
// 写入失败 err 返回一个错误对象
console.log(err);
})
判断文件是否写入成功
fs.writeFile('./files/2.txt', 'abcd', function(err) {
if (err) {
return console.log('写入失败' + err.message);
}
console.log(err);
})
fs操作文件时,提供的操作路径是以./ 或 …/开头的相对路径时,很容易出现路径动态拼接错误问题
原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径
解决方法:在readFIle()中的path参数提供一个完整的路径
问题:移植性很差,不利于维护
fs.readFile('D:\\A_code\\Vs_Code\\study\\Node\\files\\1.txt', 'utf8', function(err, dataStr) {
// console.log(dataStr);
if (err) {
return console.log('读取失败!' + err.message);
}
console.log('读取成功' + dataStr);
})
__dirname
表示当前文件所处的目录fs.readFile(__dirname + '/files/1.txt', 'utf8', function(err, dataStr) {
if (err) {
return console.log('读取失败!' + err.message);
}
console.log('读取成功' + dataStr);
})
path模块是Node.js官方提供、用来处理路径的模块
如果在JS代码中,使用path模块操作文件,需要导入
const fs = require('fs')
用来将多个路径片段拼接成一个完成的路径字符串,语法如下
path.join([..paths])
参数
路径片段序列const path = require('path')
//注意 ../会抵消前面的路径
const pathStr = path.join('/a', '/b/c', '../', './d', 'e')
console.log(pathStr); // \a\b\d\e
用来从路径字符串中,将文件名解析出来,语法如下
path.basename(path[,ext])
参数
const path = require('path')
// 定义路径的存放路径
const fpath = '/a/b/c/index.html'
// const fullName = path.basename(fpath)
// console.log(fullName); // index.html
const nameWithoutExt = path.basename(fpath, '.html')
console.log(nameWithoutExt); // index
用来获取路径中的扩展名部分,语法如下
path.extname(path)
参数
const path = require('path')
// 定义路径的存放路径
const fpath = '/a/b/c/index.html'
const fext = path.extname(fpath)
console.log(fext); // .html
http模块是Node.js官方提供的、用来创建web服务器的模块,通过http模块提供的http.createServer()
方法能方便的把一台普通电脑,变成一台Web服务器,从而对外提供Web资源服务
如果在JS代码中,使用http模块操作文件,需要导入
const http = require('http')
服务器和普通电脑去呗在于,服务器上安装了web服务器软件,例如IIS、Apache等
通过安装这些服务器软件,就能把一台普通电脑变成一台web服务器
在Node.js中不需要这些第三方web服务器软件,可以基于Node.js提供http模块,通过几行代码,可以写一个服务器软件,从而对外提供web服务
IP地址就是每台计算机的唯一地址
IP地址的格式:通常用“点分十进制”表示(a.b.c.d)的形式,a,b,c,d都是0~255之间的十进制整数
IP地址是一长串数字,不直观所以发明一套字符型地址方案,即域名
IP地址与域名的对应关系:这份对应关系存放在一种叫做域名服务器DNS
域名服务器:就是提供IP地址和域名之间的转换服务的服务器
计算机的端口号,就像现实生蚝中的门牌号
一台电脑上,可以运行成百上千个web服务,每个web服务对应一个唯一端口号,客户端发送过来的网络请求通过端口号,可以准确地交给对应web服务器进行处理
// 导入http模块
const http = require('http')
// 创建web服务器实例
const server = http.createServer()
// 为服务器实例绑定request事件,监听客户端请求
server.on('request', function(req, res) {
console.log('Some visit our web server');
})
// 启动服务器
server.listen(8080, function() {
console.log('http server running at http://127.0.0.1:8080')
})
const http = require('http')
const server = http.createServer()
server.on('request', function(req, res) {
console.log('Some visit our web server');
})
server.listen(8080, function() {
console.log('http server running at http://127.0.0.1:8080')
})
server.on()为服务器绑定的request事件处理函数
访问与客户端相关的数据或属性
// req 是请求对象,包含了客户端相关的数据和属性
server.on('request', (req, res) => {
//req.url 是客户端请求的url地址
const url = req.url
// req.method 是客户端请求的method方法
const method = req.method
const str = `Your request url is ${url},and request method is ${method}`
console.log(str)
})
服务器request事件处理函数中,访问与服务器相关的数据或属性
// req 是请求对象,包含了客户端相关的数据和属性
server.on('request', (req, res) => {
//req.url 是客户端请求的url地址
const url = req.url
// req.method 是客户端请求的method方法
const method = req.method
const str = `Your request url is ${url},and request method is ${method}`
console.log(str)
// 调用res.end()方法,向客户端响应一些内容
res.end(str)
})
调用res.end()方法,向客户端发送中文内容时候,会出现乱码问题,需要手动设置内容编码格式
// req 是请求对象,包含了客户端相关的数据和属性
server.on('request', (req, res) => {
const str = `您请求的URL地址是 ${req.url},请求的 method 类型是 ${req.method}`;
//调用setHeader方法,设置Content-Type响应头,解决中文乱码问题
res.setHeader('Content-Type', 'text/html; charset=utf-8');
// 调用res.end()方法,向客户端响应一些内容
res.end(str)
})
const http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {
// 获取请求的url地址
const url = req.url
// 设置默认的响应内容为404 Not found
let content = '404 Not found
'
// 判断用户请求是否为/ 或 /index.html
// 判断用户请求是否为/about.html关于页面
if (url === '/' || url === '/index.html') {
content = '首页
'
} else if (url === '/about.html') {
content = '关于
'
}
// 设置Content-Type响应头,防止中文乱码
res.setHeader('Content-Type', 'text/html; charset=utf-8;')
// 使用res.end() 把内容响应给客户端
res.end(content)
})
server.listen(80, () => {
console.log('server running at http://127.0.0.1');
})
把文件的实际存放路径,作为每个资源的请求url地址
// 导入需要的模块
const http = require('http')
const path = require('path')
const fs = require('fs')
// 创建基本的web服务器
const server = http.createServer()
// 将资源的请求url地址映射为文件的存放路径
server.on('request', function(req, res) {
const url = req.url;
// const fpath = path.join(__dirname, url)
let fpath = ''
// 优化资源的请求路径
if (url === '/') {
fpath = path.join(__dirname, './clock/index.html')
} else {
fpath = path.join(__dirname, '/clock', url)
}
fs.readFile(fpath, 'utf8', (err, dataStr) => {
if (err) return res.end('404 Not found')
res.end(dataStr)
})
})
// 读取文件内容并响应给客户端
server.listen(80, () => {
console.log('server runing at http://127.0.0.1');
})
模块化是指解决 复杂问题 时,自顶向下把系统划分成若干模块的过程。对整个系统来说,模块是可组合、分解和更换的单元
模块化就是遵循固定的规则,把一个大文件拆成独立并互相依赖的多个小模块
好处
就是对代码进行模块化的拆分与组合时,需要遵守的那些规则
来源不同,分为3大类
使用强大的require()方法,可以加载需要的内置模块,用户自定义模块,第三方模块进行使用
// 加载需要的内置模块
const fs = require('fs')
// 加载用户自定义模块 可以省略后缀名
const custom = require('./custom.js')
// 加载第三方模块进行使用
const moment = require(moment)
注意:使用require() 方法加载其它模块时,会执行被加载模块中的代码
和函数作用域类似,在自定义模块定义的变量、方法等成员,只能能在当前模块被访问,这种模块级别访问限制,叫做模块作用域
防止全局变量污染的问题
每个.js自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息
自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用
require() 方法导入自定义模块时,得到的就是module.exports 所指向的对象
使用require() 方法导入模块时,导入的结束,永远以module.exports 指向的对象为准
Node提供了exports对象,默认情况下,exports和module.exports只向同一个对象,最终共享的结果以module.exports 指向的对象为准
误区
CommonJS模块化规范,规定了模块的特性和各模块之间如何相互依赖
规定
Node,js中的第三方模块又叫做包,也是第三方模块,只是叫法不同
不同的Node.js中的内置模块和自定义模块,包时由第三方个人或团队开发出来的,免费供所有人使用
Node.js内置模块仅提供了一些底层API,导致基于内置模块进行项目开发效率低
包时基于内置模块出来的,提供了更高级、更方便的API,极大提高了开发效率
包和内置模块之间的关系,类似于jQuery 和浏览器内置API 之间的关系
npm,Inc公司提供了网站:https://www.npmjs.com/ ,它是全球最大的包共享平台
npm,Inc公司提供了服务器:https://registry.npmjs.org ,来对外共享所有包
npm,Inc公司提供了包管理工具,从https://registry.npmjs.org/
这个包管理工具简称npm,随着Node.js的安装包一起被安装到电脑上
// 创建格式化时间的自定义模块
// 定义格式化时间的方法
// 创建补零函数
// 自定义模块中到处格式化时间的函数
// 导入格式化时间的自定义模块
// 调用格式化时间的函数
function dataFormat(dtStr) {
const dt = new Date(dtStr)
const y = 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
}
module.exports = {
dataFormat
}
const TIME = require('./05.dataFormat')
const dt = new Date()
const newDT = TIME.dataFormat(dt)
console.log(newDT);
在项目中安装指定名称的包,需要运行如下的命令
npm install 包的完整名称
//简写
npm i 包的完整名称
查看文档:https://www.npmjs.com/
多了一个node_modules的文件夹和package-lock.json的配置文件
默认情况下,使用npm install 命令安装包的时候,会自动安装最新版本的包。
可以通过@符号指定具体版本
npm i [email protected]
包的版本号是以“点分十进制” 形式进行定义的,总共有三位数字
版本号提升规则:只要前面版本号增长了,则后面的版本号归零
npm规定,在根目录中必须提供一个叫做package.json 的包管理配置文件,用来记录有关的一些配置信息
遇到的问题:第三方包的体积过大,不方便团队成员之间共享项目源代码
解决方案:共享时剔除node_modules
在项目根目录中,创建一个叫做package.json 的配置文件,用来记录项目中安装了哪些包
注意:一定要把node_modules文件夹,添加到.gitignore忽略文件中
npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建package.json文件
npm init -y
注意:
package.json文件中,有一个dependencies节点,专门用来记录npm install 命令安装了哪些包
拿到一个剔除了node_modules项目之后,需要把所有的包下载到项目中
可以执行npm install
命令(或 npm i) 一次性安装所有的依赖包
可以运行npm uninstall
命令
卸载成功后,会把卸载的包自动从package.json中移除掉
某些包只在项目开发阶段会用到,在项目上线之后不会用到,记录到devDependencies节点下
开发阶段,项目上线之后还会用到放在dependencies节点中
使用如下命令
npm install 包名 --save-dev
// 简写
npm install 包名 -D
npm下包时候,默认从国外http://registry.npmjs.org/服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢
淘宝在国内搭建了一个服务器,专门把国外官方服务器中的包同步到国内服务器,然后在国内提供下包服务,从而极大的提高了下包速度
镜像:一种文件存储形式,一个磁盘上的数据在两一个磁盘上存在一个完全相同的副本即为镜像
指的就是下包的服务器地址
#查看当前下包的镜像源
npm config get registry //https://registry.npmjs.org/
#将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobao.org/
为了更方便切换下包的镜像源,可以安装nrm小工具,利用nrm提供的终端命令,可以快速查看和切换下包的镜像源
#通过npm包管理器,将nrm安装为全局可用的工具
npm i nrm -g
#查看所有可用的镜像源
nrm ls
#将下包的镜像源切换为 taobao 镜像
nrm use taobao
那些被安装到项目的node_modules目录中的包,都是项目包
在执行npm install命令时,如果提供了-g参数,则会把包安装为全局包
全局包为被安装到C:\Users\ 用户\AppData\Roaming\npm\node_modules 目录下
npm i 包名 -g #全局安装指定的包
npm uninstall 包名 -g #卸载全局安装的包
注意:
i5tign_toc是一个可以把md文档转换成html页面的小工具
#下载
npm install -g i5ting_toc
#调用i5ting_toc,轻松实现md 转 html的功能
i5ting_toc -f 要转换的md文件路径 -o
清楚了包的概念,以及下载和使用包,了解一下包结构
一个规范的包,它的组成结构,必须符合一下3点要求:
{
"name": "itheima-tools",
"version": "1.0.0",
"main": "index.js",
"description": "提供了格式化事件,HtmlFscape的功能",
"keywords": ["itheima", "dataFormat", "escape"],
"license": "ISC"
}
// 包的入口文件
// 定义格式化时间的方法
function dataFormat(dateStr) {
const dt = new Date(dateStr)
const y = 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
}
// 向外暴露需要的成员
module.exports = {
dataFormat
}
// 定义转义html特殊字符
function htmlEscape(htmlstr) {
return htmlstr.replace(/<|>|"|&/g, (match) => {
switch (match) {
case '<':
return '<'
break;
case '>':
return '>'
break;
case '"':
return '"'
break;
case '&':
return '&'
break;
}
})
}
// 定义还原方法
function htmlUnEscape(htmlstr) {
return htmlstr.replace(/<|>|"|&/g, (match) => {
switch (match) {
case '<':
return '<'
break;
case '>':
return '>'
break;
case '"':
return '"'
break;
case '&':
return '&'
break;
}
})
}
包根目录中的README.md文件,是包的使用说明文档,通过它,可以事先把包的使用说明,以Markdown的格式写出来,方便用户参考
6项内容:安装方式、导入方式、格式化时间、转义HTML的特殊字符、还原HTML中的特殊字符、开源协议
在终端执行 npm login命令,依次输入用户名、密码、邮箱后即可
注意:必须把下包服务器切换到npm官方服务器,否则会导致发布包失败
将终端切换到包的根目录之后,运行npm publish命令即可(注意:包名不能雷同)
运行 npm unpublish 包名 --force
命令即可
模块在第一次加载后会被缓存。这就意味着多次调用require() 不会导致模块的代码被执行多次
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会有限从缓存中加载,从而提高模块加载效率
内置模块是由Node.js官方提供的,内置模块的加载优先级最高
node_modules目录下的包和内置模块重名, require的是内置模块
使用require() 加载自定义模块时,必须指定以 ./ 或 …/ 开头的路径标识符,如果没有指定,则node会把它当作内置模块或第三方模块进行加载
同时,如果导入自定义模块时,省略了文件的扩展名,Node.js会按顺序分别尝试加载以下文件
如果传递给require() 的模块标识符不是一个内置模块,也没有以 ./ 或 …/ 开头,则Node.js会以当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录
三种加载方式
官方概念:Express是基于 Node.js 平台,快速、开放、极简的Web开发框架
通俗理解:作用类似于http模块,是专门用来创建Web服务器
Express本质:就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法
中文网:https://www.expressjs.com.cn/
http内置模块用起来很复杂,开发效率低;Express是基于内置http模块进一步封装出来的,提高开发效率
对于前端程序员来说,常见两种服务器
可以方便、快速的创建Web网站的服务器或API接口服务器
在项目所处的目录中,运行如下终端命令,即可将express安装到项目使用
npm i [email protected]
// 导入express
const express = require('express')
// 创建web服务器
const app = express()
// 启动web服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1');
})
通过app.get() 方法,可以监听客户端GET请求
app.get('/user', (req, res) => {
// 调用 espress 提供的 res.send() 方法,向客户端提供一个JSON对象
res.send({ name: 'zs', age: 20, gender: '男' })
})
通过app.get() 方法,可以监听客户端POST请求
app.post('/user', (req, res) => {
// 调用 express 提供 res.send() 方法,向客户端提供一个 文本字符串
res.send('请求成功')
})
通过res.send() 方法,可以处理好内容,发送给客户端
// get()
app.get('/user', (req, res) => {
// 调用 espress 提供的 res.send() 方法,向客户端提供一个JSON对象
res.send({ name: 'zs', age: 20, gender: '男' })
})
// post()
app.post('/user', (req, res) => {
// 调用 express 提供 res.send() 方法,向客户端提供一个 文本字符串
res.send('请求成功')
})
通过req.query对象,可以访问到客户端通过查询字符串的形式,发送给服务器的参数
app.get('/', (req, res) => {
// 通过req.query可以获取客户端发过来的查询参数
// 默认情况下,req.query 是一个空对象
console.log(req.query);
res.send(req.query)
})
通过req.params 对象,可以访问到URL中,通过:匹配到的动态参数
// 注意:这里的 :id 是一个动态参数
app.get('/user/:id', (req, res) => {
// req.params 是动态匹配到的 URL 参数,默认也是一个空对象
console.log(req.params)
res.send(req.params)
})
express提供了非常好用的函数,叫做express.static(),通过它,可以非常方便地创建一个静态资源服务器
注意:Express在指定静态目录中查询文件,并对外提供资源的访问路径,因此存放静态文件的目录名不会出现在url中
const express = require('express')
const app = express()
// 在这里,调用express.static()方法,快速对外提供静态资源
app.use(express.static('./clock'))
app.listen(80, () => {
console.log('express server running at http://127.0.0.1');
})
多次调用express.static() 函数就行
访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件
app.use(express.static('./clock'))
app.use(express.static('./public'))
express.static
要为函数服务的文件创建虚拟路径前缀(该路径实际上并不存在于文件系统中) ,请指定静态目录的挂载路径,如下所示:
app.use('/static', express.static('public'))
在调试Node.js项目时候,如果修改了项目代码,则需要手动关掉,再重新启动,非常繁琐
可以使用nodemon,能够监听项目文件变换,自动重启项目
npm install -g nodemon
传统运行node app.js命令,启动项目,坏处是代码修改之后,需要手动重启项目
现在使用 nodemon app.js命令启动项目,好处:代码修改之后,会被nodemon监听到,从而实现自动重启效果
广义上:路由就是映射关系,是按键与服务之间的映射关系
路由:是指应用程序的端点 (URI) 如何响应客户端请求。该端点是 URI和特定的 HTTP 请求方法。
路由是指:客户端请求与服务器处理函数之间的映射关系
3部分组成:请求类型、请求URL地址、处理函数
app.METHOD(PATH, HANDLER)
//eg
app.get('/', function (req, res) {
res.send('Hello World!')
})
参数
METHOD
是一个HTTP 请求方法,小写。
PATH
是服务器上的路径。
HANDLER
是路由匹配时执行的函数。
每当一次请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数
匹配时,按照路由顺序进行匹配,如果请求类型和请求url同时匹配成功。才会调用函数
注意
挂载到app上
// GET method route
app.get('/', function (req, res) {
res.send('GET request to the homepage')
})
// POST method route
app.post('/', function (req, res) {
res.send('POST request to the homepage')
})
为了方便对路由进行模块化管理。将路由抽离为单独的模块,实现步骤如下
// 路由模块
// 导入Express
const express = require("express");
// 创建路由对象
const router = express.Router()
// 挂载具体路由
router.get('/user/list', (req, res) => {
res.send('Get user list.')
})
router.post('/user/add', (req, res) => {
res.send('POST user add.')
})
// 向外导出路由对象
module.exports = router
// 在新的服务器.js中
// 导入路由模块
const router = require('./12.router')
// 注册路由模块
// app.use() 函数作用,就是来注册全局中间件
app.use(router)
类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀一样
// app.use() 函数作用,就是来注册全局中间件
app.use('/api', router)
中间件:特指业务流程的中间处理环节
当一个请求到达Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ureDpv3-1650958673647)(D:\A_study\Node\study_image\1.中间件流程.jpg)]
本质上就是一个function处理函数
app.get('/user/:id', function (req, res, next) {
res.send('USER')
})
注意:中间件函数的形参列表中,必须包含next函数,而路由处理函数只包含req 和 res
next函数 是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
// 定义一个中间件函数
const mw = function(req, res, next) {
console.log('简单的中间件函数');
// 把流转关系,转交给下一个中间件函数
next()
}
客户端发起的任何请求,到达服务器之后,都会触发中间件,叫做全局生效的中间件
通过 app.use(中间件函数) ,即可定义一个全局生效的中间件
// 将mw 注册为全局生效的中间件
app.use(mw)
app.use(function(req, res, next) {
console.log('这是简单的中间件函数');
next()
})
多个中间件之间,共享一份req 和 res,基于这样特性,可以在上游的中间件中,统一req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用
app.use(function(req, res, next) {
// 获取到请求到达服务器的时间
const time = Date.now()
// 为req对象,挂在自定义属性,从而把时间共享给后面所有路由
req.startTime = time
next()
})
app.get('/', (req, res) => {
res.send('Home page.' + req.startTime)
})
app.get('/user', (req, res) => {
res.send('User page.' + req.startTime)
})
可以使用app.use() 连续定义多个全局中间件,客户端请求到达服务器之后,会按照中间件定义的先后顺序一次进行调用
// 第一个全局中间件
app.use(function(req, res, next) {
console.log('调用了第一个全局中间件');
next()
})
// 第二个全局中间件
app.use(function(req, res, next) {
// 获取到请求到达服务器的时间
console.log('调用了第二个全局中间件');
next()
})
// 结果
http:/127.0.0.1
调用了第一个全局中间件
调用了第二个全局中间件
不适用app.use() 定义的中间件,叫做局部生效的中间件
const mw1 = (req, res, next) => {
console.log('调用了局部生效的中间件');
next()
}
// 只在当前路由生效
app.get('/', mw1, (req, res) => {
res.send('Home page.')
})
app.get('/', mw1,mw2, (req, res) => {res.send('Home page.')})
// 等价
app.get('/', [mw1,mw2], (req, res) => {res.send('Home page.')})
为了方便理解记忆中间件使用,分为5大类
通过app.use() 或 app.get() 或 app.post(),绑定到app实例上的中间件,叫做应用级别的中间件
绑定到express.Route() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别的没有任何区别,只是一个绑定在app实例上,一个绑定在router实例上
var express = require('express')
var app = express()
var router = express.Router()
router.use(function (req, res, next) {
console.log('Time:', Date.now())
next()
})
错误处理中间件总是需要四个参数。必须提供四个参数以将其标识为错误处理中间件函数。即使您不需要使用该next
对象,您也必须指定它来维护签名。否则,该next
对象将被解释为常规中间件并且无法处理错误。
作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
//eg
// 只在当前路由生效
app.get('/', mw1, (req, res) => {
throw new Error('服务器内部方式错误');
res.send('Home page.')
})
app.get('/user', (req, res) => {
res.send('User page.')
})
app.use((err, req, res, next) => {
console.log('发生了错误 ' + err.message);
res.send('Error' + err.message)
})
注意:错误级别的中间件,必须注册在所有路由之后
Express内置3个常用的中间件,极大提高Express项目的开发效率和体验
// 配置解析 application/json 格式数据的内置中间件
app.use(express.json())
//配置解析 application/x-www-form-urlencoded 格式化数据的内置中间件
app.use(express.urlencoded({ extended:false }))
const express = require('express')
const app = express()
// 注意:除了错误级别的中间件,其他的中间件必须在路由之前配置
// 通过 express.json() 解析表单中的 JSON 格式 的数据
app.use(express.json())
app.post('/user', (req, res) => {
// 在服务器,可以使用req.body这个属性,来接受客户端发送过来的请求数据
// 默认情况下,如果不配置解析表单数据的中间件,则req.body默认等于 underfined
console.log(req.body);
res.send('ok')
})
app.listen(80, () => {
console.log('http:/127.0.0.1');
})
非Express官方内置的,叫做第三方中间件,可以按需下载并配置第三方中间件
经常使用body-parser,来解析请求体数据,使用步骤如下
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 默认等于 undefined
console.log(req.body);
res.send('ok')
})
app.listen(80, () => {
console.log('http:/127.0.0.1');
})
注意:Express内置的express.urlencoded 中间件,就是基于 body-parser 这个第三方中间件进一步封装出来的
自己手动迷你一个类似express.urlencoded 这样的中间件,来解析POST 提交的服务器表单数据
实现步骤
需要监听req 对象的data 事件,来获取客户端发送到服务器的数据
如果数据量比较大,无法一次发送完毕,则客户端会把数据切割后,分批发送给服务器
data事件可能会触发多次
let str = '';
// 监听 req 的 data 事件
req.on('data', (chunk) => {
str += chunk
})
当请求体数据接收完毕之后,自动出发 req 的 end 事件,然后拿到并处理完整的请求体数据
req.on('end', () => {
// 在str中存放的完整的请求体数据
console.log(str);
// TODO: 把字符串格式的请求体数据,解析成对象格式
})
Node.js 内置一个querystring模块,专门用来处理查询字符串,通过这个模块 parse() 函数,可以轻松查询字符串,解析成对象格式
上游的中间件和下游的中间件及路由之间,共享同一分req 和 res。因此可以挂载
// 监听 req 的 end 事件
req.on('end', () => {
// 在str中存放的完整的请求体数据
// console.log(str);
// TODO: 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
req.body = body
next()
})
// test.js
const express = require('express')
const app = express()
// 导入Node.js内置的querystring模块
const qs = require('querystring')
//这是解析表单数据的中间件
const customBodyParser = require('./20.custom-body-parser')
app.use(customBodyParser)
app.post('/user', (req, res) => {
// 如果没有配置任何解析数据的中间件,则req.body 默认等于 undefined
console.log(req.body);
res.send(req.body)
})
app.listen(80, () => {
console.log('http:/127.0.0.1');
})
// 20.custom-body-parser.js
const qs = require('querystring')
const bodyParser = (req, res, next) => {
// 定义中间件具体的业务逻辑
let str = '';
// 监听 req 的 data 事件
req.on('data', (chunk) => {
str += chunk
})
// 监听 req 的 end 事件
req.on('end', () => {
// 在str中存放的完整的请求体数据
// console.log(str);
// TODO: 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
req.body = body
next()
})
}
module.exports = bodyParser
// 导入express
const express = require('express')
// 创建web服务器
const app = express()
// 可以定义接口
// 启动web服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1');
})
// apiRouter.js
const express = require('express')
const router = express.Router
module.exports = router
//使用express写接口.js
// 可以定义接口
const router = require('./23.apiRouter')
app.use('/api', router)
router.get('/get', (req, res) => {
// 通过req.query 获取客户端通过查询字符串,发送给服务器的数据
const query = req.query
//调用send() 方法,向客户端相应处理的结果
res.send({
status: 0, // 0 表示成功,1 表示失败
msg: 'GET 请求成功',
data: query // 相应给客户端的数据
})
})
// apiRouter.js
// 定义POST接口
router.post('/post', (req, res) => {
// 通过req.body 获取请求体中包含的 URL-encoded 格式的数据
const body = req.body
// 调用res.send() ,向客户端响应结果
res.send({
status: 0,
msg: 'POST 请求成功!',
data: body
})
})
// 使用express写接口.js
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
注意:如果获取URL-encoded格式的请求体数据,必须配置中间件app.use(express.urlencoded({extend:false}))
上述编写的GET、POST接口,一个严重的问题就是不能跨域
Access to XMLHttpRequest at 'http://127.0.0.1/api/get?name=zs&age=20' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
访问由如上错误
解决接口跨域问题的解决方案主要有两种:
cors时Express的一个第三方中简件,通过安装和配置cors中简件,可以很方便解决跨域问题
使用步骤分为如下3步:
// 一定要在路由之前配置 cors 这个中间件,从而解决接口跨域问题
const cors = require('cors')
app.use(cors())
cors(跨域资源共享)由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端js带啊吗跨域获取资源,浏览器的同源策略默认会组织网页 ”跨域“ 获取资源
如果配置了CORS 相关的 HTTP 响应头,可以接触浏览器端的跨域访问限制
响应头部可以携带一个 Access-Control-Allow-Origin 字段,其语法如下
Access-Control-Allow-Origin: | *
origin参数的值指定了允许访问该资源的外域URL
eg,下面字段将只允许来自http://itheima.cn的请求
res.setHeader('Access-Control-Allow-Origin','http://itheima.cn')
如果值为 通配符*,表示允许来自任何域的请求
res.setHeader('Access-Control-Allow-Origin','*')
默认情况下 cors仅支持客户端向服务器发送如下9个请求头:
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Tyoe(值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败
res.setHeader('Access-Control-Allow-Headers','Content-Type,X-Custom-Header')
默认情况下 CORS仅支持客户端发起GET、POST、HEAD请求
如果客户端希望通过PUT、DELETE等方式请求服务器的资源、则需要在服务器端、通过Access-Control-Allow-Methods来指明实际请求所允许使用的HTTP方法
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
// 允许所有的 HTTP 的请求方式
res.setHeader('Access-Control-Allow-Methods','*')
客户端在请求CORS接口时,根据请求方式和请求头的不同,可以将CORS的请求分为两大类,分别是
满足两大条件,就属于简单请求
只要符合以下任何一个条件的请求,都需要进行预检请求
在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为“预检请求” 。服务器成功响应预检请求后,才会发送真正的请求,并且携带真是数据
概念:浏览器通过 标签的scr属性,请求服务器上的数据,同时,服务器返回一个函数的调用,这种请求数据的方式叫做JSONP
特点
如果一配置CORS跨域资源共享,为了防止冲突,必须配置CORS中间件之前声明JSONP接口,否则JSONP接口会被处理成开启CORS接口
// 必须在配置cors之前,配置JSONP接口
app.get('/api/jsonp', (req, res) => {
// TODO: 定义JSON 接口具体实现过程
const funcName = req.query.callback
const data = { name: 'zs', age: 22 }
const scriptStr = `${funcName}(${JSON.stringify(data)})`
res.send(scriptStr)
})
数据库是用来组织、存储和管理数据的仓库
为了方便管理互联网世界中的数据,就有了数据库管理系统的该你那,用户可以对数据库中的数据进行新增、查询、修改和删除
MySQL、Oracle、SQL Server 属于传统型数据库(又叫:关系型数据库或SQL数据库),这三者设计理念相同,用法比较类似
Mongodb属于新型数据库,它一定程度上弥补了传统型数据库的缺陷
数据组织结构:指的是数据以什么样的结构进行存储
传统型数据库数据组织结构,与Excel数据的组织结构比较类似
Excel数据的组织结构分为 工作簿、工作表、数据行、列 这4大部分
传统型数据库数据组织结构分为 数据库、数据表、数据行、字段 这4大部分
了解需要安装哪些MySQL相关的软件
对开发人员来说,只需要安装MySQL Server 和MySQL Workbench这两个软件
结构化查询语言,专门用来访问和处理数据库的编程语言
查询数据、插入数据、更新数据、删除数据
4中SQL语法
用于从表中查询数据,执行的结果存放在一个结果集中
注意:SQL语法中关键字对大小写不敏感
select */字段列表 from 表名 [where条件];
INSERT INTO 语句用于向数据表中插入新的数据行
insert into 表名 values(值列表)[,(值列表)] ------可以一次性插入多条记录
update 表名 set 字段 = 值 [where条件];
delect from 表名 [where条件];
where 子句用于限定选择条件,在select、update、delect语句中,皆可使用where子句来限定选择标准
//select
select */字段列表 from 表名 [where条件];
//update
update 表名 set 字段 = 值 [where条件];
//delect
delect from 表名 [where条件];
ORDER BY 语句用于指定的列对结果集进行排序
默认按照升序排列
使用DESC关键字可以降序排序
COUNT(*)用来返回查询结构的总数据条数
select count(*) from 表名称;
使用AS 为列设置别名
mysql模块是托管与npm上的第三方模块。提供了Node.js项目中链接和操作MySQL数据库的能力
npm install mysql
//导入mysql 模块
const mysql = require('mysql')
//建立与MySQL数据库连接
const db = mysql.createPool({
host: '127.0.0.1', // 数据库的IP地址
user: 'root', // 登录数据库的账号
password: 'root', // 登录数据的密码
database: 'my_db_01' // 指定要操作哪个数据库
})
调用db.query() 函数,指定要执行的SQL语句
// 检测mysql 模块是否正常工作 //select 1 仅仅只是用来测试是否连接正常
db.query('select 1', (err, results) => {
// mysql 模块工作期间报错了
if (err) return console.log(err.message);
console.log(results);
})
// 查询users 表中所有数据
const sqlStr = 'select * from users'
db.query(sqlStr, (err, results) => {
// 查询失败
if (err) return console.log(err.message);
// 查询成功
// 注意 执行的是 select 查询语句, 得到是数组
console.log(results);
})
affectedRows 属性,来判断是否插入数据成功
const user = { username: 'Spider-Man', password: 'pcc123' }
//英文 ? 表示占位符,可以替具体的值占位
const sqlStr = 'insert into users (username,password) values(?, ?)'
// 数组的方式替换占位符
db.query(sqlStr, [user.username, user.password], (err, results) => {
if (err) return console.log(err.message);
// 注意:如果执行的是 insert into 查询语句,则results 是一个对象
// 可以通过affectedRows 属性,来判断是否插入数据成功
if (results.affectedRows === 1) {
// 插入成功
console.log('插入数据成功');
}
})
如果数据对象的每个属性和数据表的字段一 一对应
// 插入数据便捷方法
const user = { username: 'Spider-Man2', password: 'pcc4321' }
//英文 ? 表示占位符,可以替具体的值占位
const sqlStr = 'insert into users set ?'
// 数组的方式替换占位符
db.query(sqlStr, user, (err, results) => {
if (err) return console.log(err.message);
// 注意:如果执行的是 insert into 查询语句,则results 是一个对象
// 可以通过affectedRows 属性,来判断是否插入数据成功
if (results.affectedRows === 1) {
// 插入成功
console.log('插入数据成功');
}
})
// 更新用户信息
const user = { id: 5, username: 'aaa', password: '000' }
const sqlStr = 'update users set username=?, password=? where id=?'
db.query(sqlStr, [user.username, user.password, user.id], (err, results) => {
if (err) return console.log(err.message);
// 注意:执行了 update 语句之后,执行结构,也是一个对象,可以通过 affectedRows 判断是否更新成功
if (results.affectedRows === 1) {
// 更新成功
console.log('更新数据成功');
}
})
// 更新用户信息的便捷方式
const user = { id: 5, username: 'aaaa', password: '000000' }
const sqlStr = 'update users set ? where id=?'
db.query(sqlStr, [user, user.id], (err, results) => {
if (err) return console.log(err.message);
// 注意:执行了 update 语句之后,执行结构,也是一个对象,可以通过 affectedRows 判断是否更新成功
if (results.affectedRows === 1) {
// 更新成功
console.log('更新数据成功');
}
})
删除数据时,推荐根据id 这样的唯一标识,来删除对应的数据
注意:SQL语句中多个占位符,则必须使用数组为每个占位符指定具体值,如果只有一个占位符,可以省略数组
// 删除为5 的用户
const sqlStr = 'delete from users where id = ?'
db.query(sqlStr, 5, (err, results) => {
if (err) return console.log(err.message);
if (results.affectedRows === 1) {
// 删除成功
console.log('删除数据成功');
}
})
使用DELETE语句,会把真正的把数据从表中删除掉,为了保险起见,推荐使用标记删除方式的形式,来模拟删除的动作。
所谓标记删除,就是设置类似于status 这样的状态字段,来标记这条数据是否被删除
执行删除动作时,不是执行DELETE语句,而是执行UPDATE 语句,把响应status 字段标记为删除即可
Web开发模式有两种
概念:服务器发送给客户端HTML 页面,是在服务器通过字符串的拼接,动态生成的,因此不需要使用Ajax这样的技术额外请求页面的数据
优点:
缺点:
概念:前后端分离的开发模式,依赖于Ajax技术广泛应用,简而言之,后端只负责提供API接口,前端使用Ajax调用接口的开发模式
优点:
缺点:
为了同时兼顾了首页渲染速度和前后端分离的开发效率,一些网站采用了首屏服务器端渲染+其他页面前后端分离的开发模式
身份认证又称 “身份验证” 、“鉴权”,是指通过一定手段,完成用户身份确定
目的:为了确认当前所声称为某种身份的用户,确定是所声称的用户
无状态性是指 客户端的每次HTTP请求都是独立的,连续多个请求之间没有直接关系,服务器不会主动保留每次HTTP请求的状态
现实生活中的会员卡身份认证方式,在web开发中的专业术语叫做 Cookie
Cookie 是存储在用户浏览器中的一段不超过 4KB 的字符串,它是由 一个名称、一个值和其它几个用于控制 Cookie有效期、安全性、适用范围 的可选属性组成
不同域名下的Cookie各自独立,每次客户端发起请求时,会自动把当前域名下所有未过期的Cookie 一同发送到服务器
Cookie几大特性:
客户端第一次请求服务器的时候,服务器通过响应头的形式,向服务器发送一个身份认证的Cookie,客户端会自动将Cookie 保存在浏览器
每当客户端请求服务器时,浏览器会自动将身份认证相关的Cookie,通过请求头形式发送给服务器,服务器即可验证客户端身份
浏览器也提供读写Cookie 的 API,因此Cookie 很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过Cookie 的形式发送给服务器
注意:千万不要使用Cookie 存储重要且隐私的数据
会员卡 + 刷卡认证的设计理念,就是 Session 认证机制的精髓
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vH2HwBYZ-1650958673649)(D:\A_study\Node\study_image\2.Session认证机制.webp)]
npm install express-session
express-session中间件安装成功,需要通过app.use() 来注册 session 中间件
// 配置 Session中间件
const session = require('express-session')
app.use(
session({
secret: 'itheima',
resave: false,
saveUninitialized: true
})
)
当express-session 中间件配置成功后,即可通过req.session来访问使用session对象,从而存储用户的关键信息
// 登录的 API 接口
app.post('/api/login', (req, res) => {
// 判断用户提交的登录信息是否正确
if (req.body.username !== 'admin' || req.body.password !== '000000') {
return res.send({ status: 1, msg: '登录失败' })
}
// TODO_02:请将登录成功后的用户信息,保存到 Session 中
// 注意:只有成功配置 express-session 这个中间件之后才能通过 req 点出来 session 这个属性
req.session.user = req.body // 用户信息
req.session.islogin = true // 用户状态
res.send({ status: 0, msg: '登录成功' })
})
可以直接从req.session对象上获取之前存储的数据
// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
// TODO_03:请从 Session 中获取用户的名称,响应给客户端
if (!req.session.islogin) {
return res.send({ status: 1, mag: 'fail' })
}
res.send({
status: 0,
msg: 'success',
username: req.session.user.username
})
})
调用 req.session.destroy()函数, 即可清空服务器保存的session信息
Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。
注意:
JWT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HXjZP8oP-1650958673650)(D:\A_study\Node\study_image\3.JWT认证机制.png)]
总结:用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。
JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
三者之间使用英文的“.”分隔,格式如下:
Header.Payload.Signature
JWT 的三个组成部分,从前到后分别是 Header、Payload、Signature。
客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。
此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中,格式如下:
Authorization: Bearer
npm install jsonwebtoken express-jwt
其中:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j6i4CS03-1650958673650)(D:\A_study\Node\study_image\4.导入JWT相关包.png)]
为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的 secret 密钥:
// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'itheima No1 ^_^'
调用 jsonwebtoken 包提供的 sign() 方法,将用户的信息加密成 JWT 字符串,响应给客户端:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UW22znps-1650958673651)(D:\A_study\Node\study_image\5.在登录成功后生成 JWT 字符串.png)]
客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证。
此时,服务器可以通过 express-jwt 这个中间件,自动将客户端发送过来的 Token 解析还原成 JSON 对象:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B10qXJz9-1650958673652)(D:\A_study\Node\study_image\6.将 JWT 字符串还原为 JSON 对象.png)]
当 express-jwt 这个中间件配置成功之后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串中解析出来的用户信息了,示例代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4nkRz0OG-1650958673653)(D:\A_study\Node\study_image\6.使用 req.user 获取用户信息.png)]
当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行。我们可以通过 Express 的错误中间件,捕获这个错误并进行相关的处理,示例代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mnnoW32H-1650958673653)(D:\A_study\Node\study_image\7. 捕获解析 JWT 失败后产生的错误.png)]
之前存储的数据
// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
// TODO_03:请从 Session 中获取用户的名称,响应给客户端
if (!req.session.islogin) {
return res.send({ status: 1, mag: 'fail' })
}
res.send({
status: 0,
msg: 'success',
username: req.session.user.username
})
})
调用 req.session.destroy()函数, 即可清空服务器保存的session信息
Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。
注意:
JWT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案。
[外链图片转存中…(img-HXjZP8oP-1650958673650)]
总结:用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。
JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
三者之间使用英文的“.”分隔,格式如下:
Header.Payload.Signature
JWT 的三个组成部分,从前到后分别是 Header、Payload、Signature。
客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。
此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中,格式如下:
Authorization: Bearer
npm install jsonwebtoken express-jwt
其中:
[外链图片转存中…(img-j6i4CS03-1650958673650)]
为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的 secret 密钥:
// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'itheima No1 ^_^'
调用 jsonwebtoken 包提供的 sign() 方法,将用户的信息加密成 JWT 字符串,响应给客户端:
[外链图片转存中…(img-UW22znps-1650958673651)]
客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证。
此时,服务器可以通过 express-jwt 这个中间件,自动将客户端发送过来的 Token 解析还原成 JSON 对象:
[外链图片转存中…(img-B10qXJz9-1650958673652)]
当 express-jwt 这个中间件配置成功之后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串中解析出来的用户信息了,示例代码如下:
[外链图片转存中…(img-4nkRz0OG-1650958673653)]
当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行。我们可以通过 Express 的错误中间件,捕获这个错误并进行相关的处理,示例代码如下:
[外链图片转存中…(img-mnnoW32H-1650958673653)]