node.js学习总结
- 什么是node.js
- node.js的内置模块
-
- fs系统模块
- path路径模块
- http模块
- 模块化
- npm与包
- express
- express路由
- express+mysql
- 前后端身份认证
- JWT认证机制
什么是node.js
node.js是基于Chrome V8引擎的javaScrit运行环境
node.js中的javaScript运行环境
注意:
1.浏览器是javaScript的前端运行环境
2.Node.js是javaScript的后端运行环境
3.node.js中无法调取DOM和BOM等浏览器内置API
node.js可以做什么?
1.基于express框架,可以快速构建Web应用
2.基于Electron框架,可以构建跨平台的桌面应用
3.基于restify框架,可以快速构建API接口项目
4.读写和操作数据库,创建实用的命令行工具辅助前端开发,etc
nodejs的学习路径:
javaScript基础语法+node.js内置模块(fs,path,http等)+第三方API模块(express,mysql等)
node.js的内置模块
fs系统模块
fs是node.js官方提供的,用来操作文件的模块。它提供了一系列的方法和属性用来满足用户对文件的操作需求
1.fs.readFile()方法,用来读取指定文件的内容
2.fs.writeFile()方法,用来向指定的文件中写入内容
在js代码中使用fs模块(nodejs内置函数)
1.导入
const fs = require('fs')
1.读取指定文件内容
使用fs.readFile()读取指定文件的内容:
语法:
fs.readFile(path,option,callback)
参数解读:
1.path 必选参数,字符串,表示文件的路径
2.option 可选参数,表示使用什么编码格式来读取文件
3.callback 必选参数,文件读取后,通过回调函数拿到读取结果
示例:
const fs = require('fs') //导入fs模块
fs.readFile('./a.txt', 'utf8',function(err,dataStr){
console.log(err)
console.log(dataStr)
})
2.向指定模块中写入内容
fs.writeFile()的语法格式
使用fs.writeFile()方法写入
1.fs.writeFile(path,data,option,function)
参数解读:
参数1:path 必选参数,文件的路径
参数2:data 必选参数,表示要写入的内容
参数3:编码格式
参数4:回调函数 必选参数
实例:
const fs = require('fs')
fs.writeFile('./file/a.txt',"hello",function(err){
console.log(err)
})
*注意:
1.fs.writeFile() 方法只能用来创建文件,不能用来创建路径
2.重复调用fs.writeFile()写入同一个文件,新写入的文件内容会覆盖之前的旧内容
3.fs模块–路径动态拼接的问题
在使用fs 模块操作文件时,如果提供的操作路径是以/或../开头的相对路径时,很容易出现路径动态拼接错误的问题。
原因:代码在运行的时候,会以执行node 命令时所处的目录,动态拼接出被操作文件的完整路径。
解决这个问题:
1.提供一个完整的文件路径
2.不使用./ ../ 使用_dirname 表示当前文件所在的目录
//解决文件路径拼接问题
const fs = require('fs')
// __dirname 表示当前文件所处的目录
console.log(__dirname);
fs.readFile(__dirname+"/file/a.txt",'utf8',function(err, data) {
if (err){
return console.error(err)
}
console.log(data)
})
path路径模块
path模块是Node.js官方提供的,用来处理路径的模块,它提供了一系列的方法和属性,用来满足用户对路径的处理和需求
例如:
path.join()方法,用来将路径片段拼接成一个完整的路径字符串
path.basename()方法,用来从路径中将文件名解析出来
在js代码中使用fs模块(nodejs内置函数)
1.导入
const path = require('path')
路径拼接path.join()
path.join()方法。可以将多个路径片段拼接为完整的路径字符串
const pathStr = path.join('/a','/b/c','../','./d','e')
console.log(pathStr) // ../会抵消前面的一个路径 \a\b\d\e
const pathStr1 = path.join(__dirname,'/file/1.txt')
console.log(pathStr1) // 输出 当前文件所在目录 \file\1.txt
*涉及到路径一般使用path.join()进行路径的拼接
path.basename()的语法格式
使用path.basename()方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名
path.basename(pth,ext)
path 必选参数,表示一个路径的字符串
ext 可选参数,表示文件的扩展名
返回 表示路径中最后的一部分
获取路径中的扩展名
使用path.extname()方法,可以获取路径中扩展名部分,语法格式如下
path.extname(path)
path 必选参数,表示一个路径的字符串
返回 返回得到的扩展名字符串
http模块
1.什么是http模块
http模块是Node.js 官方提供的、用来创建web服务器的模块。通过 http模块提供的 http=createServer()方法
就能方便的把一台普通的电脑,变成一台Web 服务器,从而对外提供 Web资源服务。
2.如何使用http模块
1.首先导入 const http = require('http');
3.http模块的作用
在Node,js中,我们不需要使用lIS、Apache等这些第三方web服务器软件。因为我们可以基于Node.js 提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供web服务。
服务器相关的概念
1.IP地址
Ip地址就是互联网上每台计算机的唯一地址,因此IP地址具有唯一性。如果把"个人电脑"比作"一台电话",那么"IP地址"就相当于"电话号码"只有在知道对方IP地址的前提下,才能与对应的电脑之间进行数据通信。
2.域名和域名服务器
尽管IРP地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名(Domain Name)地址
IP地址和域名是一—对应的关系,这份对应关系存放在一种叫做域名服务器(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供IР地址和域名之间的转换服务的服务器。
3.端口号
计算机中的端口号,就好像是现实生活中的门牌号一样。通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你的手中。
同样的道理,在一台电脑中,可以运行成百上千个web服务。每个web服务都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的web 服务进行处理。
每个端口号只能被一个web服务使用
创建最基本的web服务器
1.创建web服务的基本步骤
1.导入http模块
const http = require('http');
2.创建web服务实例
通过http.creatServer()方法快速创建web服务实例
const server = http.createServer();
3.为服务器实例绑定request事件,建通客户端的请求
// 使用服务器实例 .on()方法,为服务器绑定一个request事件
server.on('request',(req,res)=>{
// 只要客户端来请求我们自己的服务器 就会触发 request事件 从而调用这个事件处理函数
console.log("Someone visit our web server")
})
4.启动服务
调用服务器实例的.listen()方法,即可启动当前的web服务实例:
//调用server。listen(端口号,cb回调方法) 即可启动web服务器
server.listen(80,()=>{
console.log('请访问 http://127.0.0.1:80')
})
2.req请求对象
只要服务器接收到客户端的请i去,就会调用server.on()为服务器绑定的request事件处理函数
如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下方式:
server.on('request',(req, res) => {
// req.url 是客户端请求的url地址
const url = req.url;
// req.method 是客户端请求的method类型
const method = req.method;
const str = `你请求的url:${url},请求方式是:${method}`;
console.log(str);
})
3.res响应对象
在服务器的request事件处理函数中,如果想访问服务器相关的数据或属性,可以使用如下的方式
server.on('request',(req, res) => {
// req.url 是客户端请求的url地址
const url = req.url;
// req.method 是客户端请求的method类型
const method = req.method;
const str = `你请求的url:${url},请求方式是:${method}`;
//为了防止中文显示乱码的问题,需要设置响应头Content-Type的值为 text/html; charset=utf-8
res.setHeader( 'Content-Type' ,'text/html; charset=utf-8 ')
//调用res.send向客户端响应一些内容
res.end(str)
})
*如果不设置相应头 res.end()返回中文会乱码
设置响应头:res.setHeader( 'Content-Type' ,'text/html; charset=utf-8 ')
根据不同的url地址响应不同的html内容
核心步骤:
1.获取请求的url地址
2.设置默认的响应内容 404 NOT Fount
3.判断用户请的是否为/或/index.html 首页
4.判断用户请求的是否为/about.html 页面
5.设置content-Type响应头,防止中文乱码
6.使用res.end把内容响应给客户端
模块化
编程领域中的模块化,就是遵守固定的规则,把一个大的文件查分成独立并且相互依赖的多个小模块
把代码进行模块化才分的好处:
1.提高代码的复用性
2.提高代码的可维护性
3.可以实现按需加载
模块化规范
模块化规范就是对模块化进行才分组合时需要遵守的规则
使用什么样的语法格式来引用模块?
在模块中使用什么样的语法格式向外暴露成员
模块化规范的好处:
大家遵守同样的模块化规范写代码,降低了沟通的成本,极大的方便了各模块之间的相互调用。
1.node.js中模块的分类
node.js根据模块的来源不同 ,将模块计划分为3大类,分别是:
1.内置模块(内置模块由nodejs官方提供的,例如:fs,path,http等)
2.自定义模块(用户创建的.js文件,都是自定义模块)
3.第三方模块(由第三方开发出来的模块,使用前需要下载)
2.加载模块
使用require()方法,可以加载内置模块,用户自定义模块,第三方模块进行使用
//1.加载内置模块
const fs = require('fs');
//2.加载自定义模块
const custom = require('./custom.js');
//3.加载第三方模块
const moment = require('moment');
*注意:
使用require方法加载其他模块时候,会执行被加载模块中的代码
在加载require加载用户模块期间,可以省略后缀名
3.node.js中模块作用域
什么式模块作用域
和作用域函数类似,在自定义模块中定义的变量,方法等成员,只能在当前模块内被访问,这种模块级别的访问限制叫做模块作用域
模块作用域的好处:
防止全局变量污染问题
4.向外共享模块作用域中的成员
1.module对象
在每个.js自定义模块中都有一个module对象,他里面存储了和当前模块有关的信息
打印module
Module {
id: '.',
path: 'E:\\student\\NODEJS\\04.模块化',
exports: {},
parent: null,
filename: 'E:\\student\\NODEJS\\04.模块化\\05.演示module对象.js',
loaded: false,
children: [],
paths: [
'E:\\student\\NODEJS\\04.模块化\\node_modules',
'E:\\student\\NODEJS\\node_modules',
'E:\\student\\node_modules',
'E:\\node_modules'
]
}
2.module.exports对象 默认为空对象
在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用
外界用require()方法导入自定义模块时,得到的就是module.exports所指的对象
3.共享成员的注意点
使用require()方法导入模块时,导入的结果 永远以module.exports指向的对象为准
4.exports对象
由于module.expres写起来比较复杂,为了简化代码,提供了exports对象。默认情况下
exports和module.exports指向同一个对象,最终结果还是以module.exports指向的为准
*注意:为了防止混乱,建议不要再同一个模块中同时使用exports和module.expres
5.node.js中的模块化规范
node.js遵循了conmmonJS模块化规范,commonJS规定了模块的特性和各模块之间如何相互依赖
commonJS
1.每个模块内部,module变量代表当前模块
2.module变量是一个对象,它的exports属性(即module.exports)是对外接口
3.加载模块时候,其实加载该模块的module.exports属性。require()方法用于加载模块
npm与包
1.什么是包?
nodejs中的第三方模块又叫做包
2.包的来源
不同于node.js中内置模块和自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用
注意:Node.js中的包都是免费且开源的,不需要付费即可免费下载使用
3.为什么需要包?
由于node.js的内置模块仅仅提供了一些底层API,导致在基于模块进行项目开发时候,效率很低
包是基于内置模块封装出来的,提供了高级,更方便的API,极大的提高了开发效率
4.从哪里下载包
从https://www.npmjs.com/ 网站搜索自己所需要的包
5.如何下载包
npm 包管理器 安装node的时候自动安装了
6.在项目中装包的命令
npm install 包的完整名称
简写 npm i 包的完整名称
7.初次装包,文件里面多了哪些文件?
初次装包完成后,在项目文件夹下多了一个叫做 node_modules的文件夹和package-lock.json的配置文件
node modules文件夹用来存放所有已安装到项目中的包。require()导入第三方包时,就是从这个目录中查找并加载包。package-lockjson配置文件用来记录node_modules目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等。
*注意:程序员不要手动修改node modules或package-lockjson文件中的任何代码,npm包管理工具会自动维护它们。
8.安装指定版本的包
默认情况下,使用npm install命令安装包的时候,会自动安装最新版本的包。如果需要安装指定版本的包,可以在包名之后,通过@符号指定具体的版本,例如:npm i [email protected]
9.包的语义化版本规范
包的版本号是以“点分十进制”形式进行定义的,总共有三位数字,例如2.24.0其中每一位数字所代表的的含义如下:
第1位数字:大版本
第2位数字:功能版本第3位数字:Bug修复版本
版本号提升的规则:只要前面的版本号增长了,则后面的版本号归零。
10.3.快速创建package.json
npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建package.json这个包管理配置文件:
//作用:在执行命令所处的目录中,快速新建package.json文件
npm init -y
注意:
上述命令只能在英文的目录下成功运行!所以,项目文件夹的名称一定要使用英文命名.不要使用中文,不能出现空格。
运行npm install 命令安装包的时候,npm包管理工具会自动把包的名称和版本号,记录到package.json中。
11.一次性安装所有的包
可以运行npm install命令(或npm i)一次性安装所有的依赖包:
1//执行npm install命令时,npm包管理工具会先读取package.json 中的 dependencies 节点
2//读取到记录的所有依赖包名称和版本号之后,npm包管理工具会把这些包一次性下载到项目中
3 npm install
12.卸载包
npm uninstall 包名
13.devDependencies节点
如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies节点中。
与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到dependencies 节点中。
您可以使用如下的命令,将包记录到devDependencies节点中:
1//安装指定的包,并记录到devDependencies节点中
2 npm i 包名-D
3//注意:上述命令是简写形式,等价于下面完整的写法:4 npm install 包名 --save-dev
14.解决下包速度慢的问题
1.为什么下包速度慢
在使用npm下包的时候,默认从国外的https://registry.npmjs.org/服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢。
2.切换npm的下包镜像源
下包的镜像源,指的就是下包的服务器地址。
1#查看当前的下包镜像源
2 npm config get registry
3# 将下包的镜像源切换为淘宝镜像源
4 npm config set registry=https://registry.npm.taobao.org/
5 #检查镜像源是否下载成功
6 npm config get registry
3.nrm
为了更方便的切换下包的镜像源,我们可以安装nrm这个小工具,利用nrm 提供的终端命令,可以快速查看和切换下包的镜像源。
1 #通过npm包管理器,将 nrm安装为全局可用的工具2 npm i nrm -g
3#查看所有可用的镜像源
4 nrm ls
5#将下包的镜像源切换为taobao镜像6 nrm use taobao
express
Express简介
1.什么是Express
官方给出的概念:Express是基于Node.js 平台,快速、开放、极简的Web开发框架。
通俗的理解:Express的作用和Node.js内置的http模块类似,是专门用来创建Web服务器的。Express的本质:就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法。
Express的中文官网: http://www.expressis.com.cn/
Express 能做什么
对于前端程序员来说,最常见的两种服务器,分别是:
Web 网站服务器:专门对外提供Web 网页资源的服务器。API接口服务器:专门对外提供API接口的服务器。
使用Express,我们可以方便、快速的创建Web 网站的服务器或 API接口的服务器。
1.安装
在项目所处的目录中,运行如下的终端命令,即可将express安装到项目中使用:
1 npm i [email protected]
2.创建基本的Web服务器
1 // 1.导入express
2 const express = require( ' express ' )
3 // 2.创建web服务器
4 const app = express()
6 // 3.调用app.listen(端口号,启动成功后的回调函数),启动服务器
7 app.listen(80,() =>{
console.log( ' express server running at http: //127.0.0.1')})
3.监听get请求
通过app.get( )方法,可以监听客户端的GET请求,语法如下:
1.参数1∶客户端请求的URL 地址
2.参数2:请求对应的处理函数
3.req:请求对象((包含了与请求相关的属性与方法)
4.res:响应对象(包含了与响应相关的属性与方法)
5 app.get('请求URL',function(req,res) {/*处理函数*/ })
4.监听post请求
通过app.post( )方法,可以监听客户端的GET请求,语法如下:
1.参数1∶客户端请求的URL 地址
2.参数2:请求对应的处理函数
3.req:请求对象((包含了与请求相关的属性与方法)
4.res:响应对象(包含了与响应相关的属性与方法)
5 app.post('请求URL',function(req,res) {/*处理函数*/ })
5.把内容响应给客户端
通过res.send)方法,可以把处理好的内容,发送给客户端:
app.get( ' /user ' , (req, res) =>{
向客户端发送JSON 对象
res.send({ name: 'zs ',age: 20,gender: '男'})
})
app.post( " /user'. (req,res) =>{
向客户端发送文本内容
res.send( '请求成功')
})
6.获取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)
7.获取URL中的动态参数
通过req.params对象,可以访问到URL中,通过:匹配到的动态参数:
//URL地址中,可以通过:参数名的形式,匹配动态参数值
app.get( '/user/:id',(req,res) => {
//req.params 默认是一个空对象
//里面存放着通过:动态匹配到的参数值
console.log(req. params)
})
8.托管静态资源
1.express.static()
express提供了一个非常好用的函数,叫做express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如,通过如下代码就可以将public目录下的图片、CSS文件JavaScript 文件对外开放访问了:
app.use(express.static('public'))
*如过不生效
const path = require('path');
app.use(express.static(path.join(__dirname, 'public')))
挂载路径前缀
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
app.use( '/public' , express.static( 'public' ))
现在,你就可以通过带有/public 前缀地址来访问public目录中的文件了:
express路由
1.Express中的路由
在Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
Express 中的路由分3部分组成,分别是请求的类型、请求的URL地址、处理函数,格式刻
app.METHOD(PATH,HANDLER)
2.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 ')})
3.路由的匹配过程
1. 每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
2. 在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL同时匹配成功,则 Express 会将这次请求,转交给对应的function 函数进行处理。
3. 路由匹配的注意点:
按照定义的先后顺序进行匹配
请求类型和请求的URL同时匹配成功,才会调用对应的处理函数
4.路由的使用
在Express 中使用路由最简单的方式,就是把路由挂载到app 上,示例代码如下:
const express = require('express')
const app = express()
//挂载路由
app.get('/',(req, res) =>{
res.send("hello word")
})
app.post('/',(req, res) =>{
res.send("post request")
})
app.listen(3000,() => {
console.log('http://localhost:3000');
})
5.模块化路由
为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
将路由抽离为单独模块的步骤如下:创建路由模块对应的.js文件
1.创建路由模块对应的.js文件
2.调用express.Router()函数创建路由对象
3.向路由对象上挂载具体的路由
4.使用module.exports向外共享路由对象
//这是路由模块
// 1.导入express
const express = require('express')
//2.创建路由对象
const router = express.Router()
//3. 挂载具体的路由
router.get('/user/list',(req, res) => {
res.send("get user list")
})
router.post('/user/add',(req, res) => {
res.send('post user add')
})
//4.向外导出路由
module.exports = router
5.使用app.use()函数注册路由模块
const express = require('express');
const app = express();
//1.导入路由模块
const routes = require('./05.router');
//2.注册路由模块 app.use这个函数的作用,就是用来注册全局的中间件
app.use(routes)
app.listen(3000,()=>{
console.log('listening on 3000')
});
给路由添加统一的访问前缀
app.use('/api',routes)
6.EXpress中间件
1.什么是中间件
中间件:特指业务流程的中间处理环节
2.express中间件的调度流程
当请求到达Express的服务之后,可以连续调用多个中间件,从而对这次请求进行预处理
3.Express中间件的格式
Express的中间件,本质上就是一个function处理函数,Express中间件的格式如下:
app.get( '/',(req,res,next) => {
next()
})
app.listen(3000);
*注意:中间件函数的形参列表中,必须包含next参数,而路由器处理函数只包含req和res
4.next()形参的作用
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
7.Express中间件初体验
1.定义中间件
可以通过如下方式定义,定义一个最简单的中间件
//常量 mw所指向的,就是一个中间件函数
const mw = function(req,res,next){
console.log("这是一个最简单的中间件")
// 注意:在当前中间件的业务处理完毕后,必须调用next()函数
// 表示把流转关系交给下一个中间件或路由
next()
}
2.全局生效的中间件
客户端发起任何请求,到达服务器之后,都会触发的中间件叫做全局生效中间件
通过调用app.use()中间件函数,即可定义一个全局中间件,示例代码如下:
const express = require('express')
const app = express()
const port = 3000
// 定义一个最简单的中间件函数
const mw = function (req, res,next) {
console.log("这是最简单的中间件函数");
//把流转关系,转交给下一个中间件
next()
}
// 将mw注册成为全局的中间件
app.use(mw)
app.get('/', (req, res) =>{
console.log("调用了/这个路由");
res.send('Hello World!')
})
app.get('/user', (req, res) => {
console.log(`调用了${req.url}`);
res.send('my is user')
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
3.定义全局中间件的简化形式
app.use((req, res, next) => {
console.log("这是简化的全局中间件");
next();
})
4.中间件的作用
多个中间件之间 共享一份req和res基于这个特性,我们可以在上有的中间件中,统一为req或res对象添加自定义的属性和方法,供下游的中间件或路由使用
5.定义多个全局中间件
可以使用app.use()连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件的先后顺序,依次进行调用,实例代码:
// 定义第一个全局中间件
app.use((req, res, next) =>{
console.log("调用了第一个全局中间件");
next()
})
//定义第二个全局中间件
app.use((req, res, next) =>{
console.log("调用了第二个全局中间件");
next()
})
//定义一个路由
app.get('/', (req, res, next) =>{
res.send(req.url)
})
6.局部生效的中间件
不使用app.use()定义的中间件,叫做局部生效的中间件 示例代码如下
const mw = (req, res, next) => {
console.log("调用局部生效中间件");
next()
}
app.get('/',mw, (req, res) => {
res.send("/")
})
app.get('/user',(req, res) => {
res.send("/user")
})
7.定义多个局部中间件
可以在路由中通过如下两种等价的方式,使用多个局部中间件
//创建局部生效的中间件
const mw1 = (req, res, next) =>{
console.log('调用了第一个局部生效的中间件');
next()
}
const mw2 = (req, res, next) =>{
console.log("调用了第二个局部生效的中间件");
next()
}
// 以下两种写法均为完全等价,可以使用任意一种
app.get('/', mw1,mw2,(req, res) =>{
res.send('/')
})
app.get('/user',[mw1,mw2],(req, res) =>{
res.send('/user')
})
8.了解中间件的5个注意事项
1.一定要在路由注册之前注册中间件
2.客户端发送过来的请求,可以连续调用多个中间件进行处理
3.执行完中间件的业务代码之后,不要忘记调用next()函数
4.为了防止逻辑混乱,盗用next()函数后不要频再写额外的代码
5.连续调用中间键的时候多个中间件之间,共享req和res对象
8.中间件的分类
1.应用级别的中间件 app
通过app.use()或app.get()或app.post()绑定到app实例上的中间件,叫做应用级别的中间件
2.路由级别的中间件 router
绑定到express.Routerl)实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app实例上,路由级别中间件绑定到router实例上
3.错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的function处理函数中,必须有4个形参,形参顺序从前到后,分别是(err, req, res, next)。
app.get( ' /', function (req,res) { // 路由
//抛出一个自定义的错误
throw new Error( '服务器内部发生了错误!')
res.send( 'Home Page.')
})
app.use( function (err,req,res,next) {
//错误级别的中间件
console.log( '发生了错误:' +err.message)
// 在服务器打印错误消息
res.send( 'Error! ' +err.message) // 2.2向客户端响应错误相关的内容
})
** 错误级别中间件,必须注册再所有路由之后
4.Express内置的中间件
自Express 4.16.0版本开始,Express 内置了3个常用的中间件,极大的提高了Express 项目的开发效率和体验
1.express.static快速托管静态资源的内置中间件,例如:HTML文件、图片、CSS样式等(无兼容性)
2.express.json解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)
3.express.urlencoded解析URL-encoded格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)
//配置解析application/json格式数据的内置中间件
app.use(express.json())
//配置解析 application/x-ww-form-urlencoded格式数据的内置中间件
app.use(express.urlencoded( { extended: false }))
app.use(express.json())
app.use(express.urlencoded({extended:false}))
app.post('/book', function (req, res) {
//在客户端可以使用req.body这个属性,来接收客户端发送过来的请求数据
// 默认情况下,如果不配置解析表单的中间件,则req.body 默认等于 undefined
console.log(req.body);
res.send('POST request to the homepage')
})
app.get('/', (req, res) => {
res.send('GET request to the homepage')
})
app.post('/', function (req, res) {
// 在服务器端,可以通过req.body 来获取JSON格式和url-encoded格式的数据
console.log(req.body);
res.send('POST request to the homepage')
})
5.第三方的中间件
非Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。
例如:在[email protected]之前的版本中,经常使用body-parser这个第三方中间件,来解析请求体数据。使用步骤如下:
运行npm install body-parser安装中间件使用require 导入中间件
6.解决跨越问题中间件
cors
cors 是 Express的一个第三方中间件。通过安装和配置cors 中间件,可以很方便地解决跨域问题。
1.运行 npm install cors 安装中间件
2.使用const cors = require('cors') 导入中间件
3.在路由之前调用app.use(cors) 配置中间件
什么是cors
CORS (Cross-Origin Resource Sharing,跨域资源共享)由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制。
CORS 的注意事项
CORS主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了CORS的接口。CORS在浏览器中有兼容性。只有支持XMLHttpRequest Level2的浏览器,才能正常访问开启了CORS的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
CORS 响应头部-Access-Control-Allow-Origin
响应头部中可以携带一个 Access-Control-Allow-Origin字段,其语法如下:
Access-Control-Allow-origin: |*
其中,origin 参数的值指定了允许访问该资源的外域URL。
例如,下面的字段值将只允许来自http://itcast.cn的请求:
res.setHeader ( 'Access-Control-Allow-Origin', 'http:/litcast.cn')
如果指定了Access-Control-Allow-Origin字段的值为通配符*,表示允许来自任何域的请求,示例代码如下:
res.setHeader( 'Access-Control-Allow-Origin', '*')
CORS 响应头部 Access-Control-Allow-Headers
默认情况下,CORS仅支持客户端向服务器发送如下的9个请求头:
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width ,Content-Type (值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败!
允许客户端额外向服务器发送Content-Type请求头和X-Custom-Header 请求头
注意:多个请求头之间使用英文的逗号进行分割
res.setHeader(' Access-Control-Allow-Headers','Content-Type,X-Custom-Header ' )
CORS响应头部– Access-Control-Allow-Methods
默认情况下,CORS仅支持客户端发起GET、POST、HEAD请求。
如果客户端希望通过PUT、DELETE等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Alow-Methods来指明实际请求所允许使用的HTTP方法。
只允许POST、GET、DELETE、HEAD请求方法
res.setHeader( 'Access-Control-Allow-Methods ','POST,GET,DELETE,HEAD')
允许所有的 HTTP请求方法
res.setHeader( ' Access-Control-Allow-Methods ', '*')
简单请求:
同时满足一下两个大条件的请求,就属于简单请求
1.请求方式GET,POST,HEAD 三者之一
2.HTTP头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language,Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type (只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
预检请求
只要符合以下任何一个条件的请求,都需要进行预检请求:
1.请求方式为GET、POST、HEAD之外的请求Method类型
2.请求头中包含自定义头部字段
3.向服务器发送了application/json格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这-次的OPTION请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
简单请求和预检请求的区别
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求。
express+mysql
安装mysql模块
npm install mysql
配置mysql模块
// 1.导入mysql模块
const mysql = require( ' mysql' )
//2.建立与 MySQL数据库的连接
const db = mysql.createPool({
host: '127.0.0.1', //数据库的IP地址
user : 'root',//登录数据库的账号
password: 'admin123',//登录数据库的密码
database: 'my_db_01' //指定要操作哪个数据库
})
测试mysql模块是否正常工作
// 测试mysql 是否正常工作
db.query('SELECT 1', (err, result) => {
if (err) {
return console.log("错误");
}
//只要能打印出{'1':1}的结果,就证明数据库连接成功
console.log(result);
})
查询数据
// 查询list表中的所有数据
const sql = "SELECT * FROM list";
db.query(sql, (err, result) => {
// 查询失败
if (err) { return console.log("查询数据失败"+err.message); }
// 查询失败 得到一个数组
console.log(result);
})
插入数据
// 向user表中插入一条数据
const user = {username:'root',password:'123456'}
const userSql = 'INSERT INTO user (username,password) VALUES (?, ?)'
db.query(userSql,[user.username,user.password],(err, result)=>{
// 插入失败
if (err) { return console.log(err.message); }
// 插入成功
if (result.affectedRows === 1){ console.log("插入成功")}
})
简便的方式:
const user = {username:'root',password:'123456'}
const userSql = 'INSERT INTO user set ?'
db.query(userSql,user,(err, result)=>{
// 插入失败
if (err) { return console.log(err.message); }
// 插入成功
if (result.affectedRows === 1){ console.log("插入成功")}
})
更新数据
// 1.要更新的数据对象
const user = {id:1,username:'lisi',password:'789456'}
const upSql = 'UPDATE user SET username=?,password = ? WHERE id=?'
db.query(upSql,[user.username,user.password,user.id],(err, result)=>{
if (err) { return console.log(err.message); }
if (result.affectedRows === 1){ console.log("更新数据成功")}
})
删除数据:
// 删除user表中的一条数据
const sql = 'DELETE FROM user WHERE id= ?'
db .query(sql,[1], (err, result)=>{
if (err){
return console.log(err.message);
}
if (result.affectedRows === 1){ console.log("删除成功")}
})
标记删除
使用DELETE语句,会把真正的把数据从表中删除掉。为了保险起见,推荐使用标记删除的形式,来模拟删除的动作。
所谓的标记删除,就是在表中设置类似于status这样的状态字段,来标记当前这条数据是否被删除。
当用户执行了删除的动作时,我们并没有执行DELETE语句把数据删除掉,而是执行了UPDATE语句,将这条数据对应的status字段标记为删除即可。
前后端身份认证
web开发模式
目前主流的Web开发模式有两种,分别是:
1基于服务端渲染的传统 Web开发模式
2基于前后端分离的新型Web开发模式
1.服务端渲染的Web开发模式
服务端渲染的概念:服务器发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用Ajax这样的技术额外请求页面的数据.
2.服务端渲染的优缺点
优点:
前端耗时少。因为服务器端负责动态生成HTML内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电。
有利于SEO。因为服务器端响应的是完整的HTML页面内容,所以爬虫更容易爬取获得信息,更有利于SEO。
缺点:
占用服务器端资源。即服务器端完成HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力。
不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效开发。
3.前后端分离的 Web开发模式
前后端分离的概念:前后端分离的开发模式,依赖于Ajax技术的广泛应用。简而言之,前后端分离的Web开发模式,就是后端只负责提供API接口,前端使用Ajax调用接口的开发模式。
4.前后端分离的优缺点
优点:
开发体验好。前端专注于UI页面的开发,后端专注于api的开发,且前端有更多的选择性。
用户体验好。Ajax技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。
减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的。
缺点:
不利于 SEO。因为完整的HTML页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。
(解决方案:利用Vue、React等前端框架的SSR (server side render)技术能够很好的解决SEO问题! )
1.什么是身份认证?
身份认证(Authentication)又称“身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。
2.为什么需要身份认证
身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户。
3.不同开发模式下的身份认证
对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案:
1.服务端渲染推荐使用Session 认证机制
2.前后端分离推荐使用JWT认证机制
Session认证机制
1.HTTP协议的无状态性
了解HTTP协议的无状态性是进一步学习Session认证机制的必要前提。
HTTP协议的无状态性,指的是客户端的每次HTTP请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次HTTP请求的状态。
2.如何突破HTTP无状态的限制
通过Cookie
3.什么是Cookie
Cookie是存储在用户浏览器中的一段不超过4KB的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。
不同域名下的Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。
Cookie的几大特性:
1.自动发送
2.域名独立
3.过期限制
4.4KB限制
4.Cookie在身份认证中的作用
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的Cookie,客户端会自动将Cookie保存在浏览器中。
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。
5.Cookie 不具有安全性
由于Cookie是存储在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器。
注意:千万不要使用Cookie存储重要且隐私的数据!比如用户的身份信息、密码等。
6.提高身份认证的安全性
为了防止客户伪造会员卡,收银员在拿到客户出示的会员卡之后,可以在收银机上进行刷卡认证。只有收银机确认存在的会员卡,才能被正常使用。
这种“会员卡+刷卡认证”的设计理念,就是Session认证机制的精髓。
在express中使用session认证
1.安装express-session中间件
npm i express-session
2.配置session中间件
const session = require('express-session')
app.use(
session({
secret: 'xinghuo',
resave: false,
saveUninitialized: true,
})
)
3.向session中存数据
当express-session中间件配置成功后,即可通过 req.session来访问和使用session对象,从而存储用户的关键信息:
app.post('/api/login',(req, res) => {
if (req.body.username != 'admin' || req.body.password != '123456'){
return res.send(
{
statis:1,
msg: '登录失败'
}
)
}
req.session.user = req.body //存储用户信息
req.session.idlogin = true //存储登录信息
})
4.在session中取数据
直接在req.session对象上获取之前存储的数据
app.post('/', function (req, res) {
if(!req.session.islogin){
return res.send({
status:1,
msg: 'fail'
})
}
res.send({
status:0,
msg: 'success',
username : req.session.user.username
})
})
5.清空session
调用req.session.destroy()函数,即可清空服务器保存的session 信息
6.了解 Session 认证的局限性
Session认证机制需要配合Cookie才能实现。由于Cookie 默认不支持跨域访问,所以当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域Session认证。
注意:
当前端请求后端接口不存在跨域问题的时候,推荐使用Session身份认证机制。
当前端需要跨域请求后端接口的时候,不推荐使用Session身份认证机制,推荐使用JWT认证机制。
JWT认证机制
1.JWT的组成部分
JWT通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
三者之间使用英文的“.”分隔,格式如下:
Header.Payload.Signature
2.JWT的三个部分各自代表的含义
JWT的三个组成部分,从前到后分别是 Header、Payload、Signature。
其中:
Payload部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
Header和Signature是安全性相关的部分,只是为了保证Token的安全性。
3.JWT的使用方式
客户端收到服务器返回的WT之后,通常会将它储存在localStorage或sessionStorage 中。
此后,客户端每次与服务器通信,都要带上这个WT的字符串,从而进行身份认证。推荐的做法是把JWT放在HTTP请求头的Authorization字段中,格式如下:
Authorization: Bearer
4.expresss中使用JWT
1.安装相关的包
npm install jsonwebtoken express-jwt
jsonwebtoken用于生成JWT字符串
express-jwt用于将JWT字符串解析还原成JSON 对象
2.导入JWT相关的包
使用require()函数,分别导入JWT相关的两个包:
// 安装并导入JWT相关的包 分别是 jsionwebtoken 和 express-jwt
const jwt = require('express-jwt')
const jsont = require('jsonwebtoken')
3.定义secret密钥
为了保证JWT字符串的安全性,防止JWT字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的secret密钥:
当生成JWT字符串的时候,需要使用secret密钥对用户的信息进行加密,最终得到加密好的WT字符串当把JWT字符串解析还原成JSON对象的时候,需要使用secret密钥进行解密
4.在登录成功后生成JWT字符串
调用jsonwebtoken包提供sign()方法,将用户的信息加密成JWT字符串,响应给客户端
// 定义一个secret密钥,建议将密钥命名为 secretKey
const secretKey = 'itbnsadklsdbabsdalhnlk ^_^'
//登录接口
app.post('/api/login', function (req, res) {
//将req.body 请求体中的数据,转存为userinfo常量
const userinfo = req.body
// 登录失败
if(userinfo.username != 'admin' || userinfo.password != '123456')
return res.send({
status:400,
message:'登录失败'
})
// 登录成功
// 在登录成功之后调用 jwt.sign()方法生成jwt字符串。并通过token属性发送给客户端
const token = jsont.sign({username: userinfo.username},secretKey,{expiresIn: '30s'})
res.send({
status:200,
message: '登录成功',
token: token
})
res.send('POST request to the homepage')
})
5.将JWT字符串还原为JSON对象
客户端每次东访问那些有权限接口的时候,都需要主动通过请求头中的Authorization字段,将Token字符串发送到服务器进行身份认证。
此时,服务器可以通过express-jwt这个中间件,自动将客户端发送过来的Token解析还原成JSON对象:
// 安装并导入JWT相关的包 分别是 jsionwebtoken 和 express-jwt
const expressjwt = require('express-jwt')
const jsont = require('jsonwebtoken')
// 定义一个secret密钥,建议将密钥命名为 secretKey
const secretKey = 'itbnsadklsdbabsdalhnlk ^_^'
// 初测将JWT字符串解析还原成JSON对象的中间件
app.use(expressjwt({secret: secretKey,algorithms:['HS256']}).unless({path:[/^\/api\//]}))
6.捕获解析JWT失败后产生的错误
当使用express-jwt解析Token字符串时,如果客户端发送过来的Token字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行。我们可以通过 Express的错误中间件,捕获这个错误并进行相关的处理,示例代码如下:
app.use((err, req, res,next) => {
if(err.name === 'UnauthorizedError'){
return res.send({
status:401,
msg:'无效的token'
})
}
res.send({
status:500,
message:'未知的错误'
})
})