node基础使用
本文最后更新于:8 个月前
说明
此文档是之前学习前端记录,后续我将进行格式修改。
初始node.js
v8解析引擎负责解析执行javascript代码;
内置API是由运行环境提供的特殊接口,只能在所在的运行环境中调用。
什么是node.js
node.js是基于Chrome的V8引擎的javascript的运行环境
注意:
浏览器是javascript的前端运行环境;
node.js是javascript的后端运行环境
node.js无法调用DOM、bom等操作
node.js可以做什么
node.js作为一个javascript的运行环境,仅仅提供了基础的功能和API,然而基于node.js的工具和框架层出不穷。
可以基于Express框架,快速构建WEB应用
可以基于Electron框架,可以构建跨平台桌面应用
可以基于restify框架,快速构建API接口项目
可以读写数据库和操作数据库、创建实用命令行工具辅助前端开发
在node.js环境中执行javascript代码
打开终端,输入node+执行代码文件位置
fs文件系统模块
fs模块是node官方提供用来操作文件的模块。它提供一系列方法和属性,用来满足用户对文件的操作需求
使用步骤:
首先导入fs模块文件
const fs=rquire('fs")
读取文件fs.readFile()方法
写入文件fs.readFile()方法
读取文件
读取文件会传入三个参数:
第一个参数为文件所在位置
第二个为读取文件时采用的编码格式,一般默认指定为UTF-8
第三个参数为回调函数,拿到读取失败和成功的结果 err datastr
读取成功err参数就为null,读取失败datastr就为undefind
例如:fs.readFile('./file.txt','utf-8',(err,datastr)=>{
console.log(err)
console.log('------------')
//打印成功读取文件的结果
console.log(datastr)
})
写入文件
该方法会传入三个参数
第一个参数为要写入文件的位置
第二个参数为要写入的内容
第三个参数为回调函数
回调函数会传入一个参数err
如果文件写入 成功,则err参数的值为null
如果文件写入失败,则err的值等于一个错误对象
例如:
fs.writeFile('./file.txt','我想学习',(err)=>{
console.log(err)
})
注意:
这个方法只能用来创建文件,不能用来创建路径
新写入内容会覆盖旧内容
fs文件模块路径
fs文件模块会根据命令终端所在的目录动态拼接出操作文件的完整路径
那么这样就会出现问题:
当我们文件路径以./或者在../的相对路径形式,那么在不同文件下执行命令就会出现路径问题
解决方法:1、使用绝对路径(可移植性差)
2、使用__dirname 来表示当前文件目录 例如用 __dirname+'/file.txt'
path路径模块
path模块是node.js官方提供的、用来处理路径的模块,他提供的一系列模块可以满足用户对路径处理的需求
例如:path.join(),用来拼接成一个完整的路径字符串
path.basename()。用来从路径字符串中将文件名解析出来
path.extname(),获取路径扩展路径
以后文件路径拼接时,就要使用path.join() 例如:const pathStr=path.join(__dirname,'./111')
获取路径中的名字部分
const pathBasename=path.basename('/a/b/d/c') 打印pathBasename为c
什么是http模块
在网络节点中,负责消费资源的交=叫客户端,负责对外提供网络资源的电脑叫服务器
http模块是node.js官方提供的、用来创建web服务器的模块。通过http模块提供的方法,就能方便的把一台普通的电脑
变成一台web服务器,从而向外界提供web资源
服务器与普通电脑的区别在于,服务器上安装了web服务器软件,例如apache等,通过这些服务器软件,就能把一台普通的电脑
变为一台web服务器
在node.js中,不需要第三方服务器软件,我们可以使用node.js提供的http模块
服务器相关概念
#ip地址
IP地址具有唯一性;
互联网中每个web服务器,都有自己的IP地址,都可以通过ip地址找到相应的网站
#域名和域名服务器
IP地址能够唯一标识在网络中的计算机,但是IP地址是一长串数字,不直观,不便于记忆,于是域名地址解决这一问题
ip地址和域名是一一对应的关系,这份对应关系保存在域名服务器中(DNS)
注意:
单纯使用IP地址,在互联网中的电脑也能够正常使用,互联网中的电脑有了域名的加持,变得更加方便
在开发测试中,127.0.0.1对应的域名是localhost
#端口号
在一台电脑中可以运行成千上万个服务器,但是每个服务器对应唯一个端口号,通过端口号可以准确交到指定
服务器处理
创建最基本的web服务器步骤
第一步导入http模块
第二步创建web服务器实例
第三步为服务器绑定request事件,监听客户端请求
第四步启动服务器
const http=require('http')
//创建web服务器实例
const server=http.createServer()
//为服务器绑定request事件,监听客户端请求
server.on('request',(req,res)=>{
console.log('服务器被调用')
})
//启动服务器
server.listen(9000,()=>{
console.log('服务启动成功')
})
req对象
只要服务器接收到了客户端的请求,就会通过server.on()为服务器绑定request事件处理函数
req是请求对象,他包含了与客户端相关的属性与数据
req.url是客户端请求的地址
req.method是客户端的method请求类型
res对象
res是应答对象
模块化
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干个模块的过程。对于整个系统来说,模块是可组合、
可分解和更换的单元
好处:
提高了代码的复用性
提高了代码的可维护性
可以实现按需加载
模块的分类
内置模块 (内置模块是由node.js官方提供的如fs文件模块、path路径模块、http模块)
自定义模块
第三方模块
加载模块
使用require()方法可以加载需要的内置模块、用户自定义模块、第三方模块使用
加载自定义模块时,需要加路径
模块作用域
向外共享模块作用域的成员
使用require方法导入模块时,导入的结果永远以module.exports指向的对象为准
由于module.exports单词写起来比较复杂,为了简化向外共享成员代码时,node提供
了exports对象。默认情况下,exports和module.exports指向同一个对象,最终指向的结果还是以
module.exports指向的对象为准
module.exports与export使用误区
require方法导入模块时,导入的结果永远以module.exports指向的对象为准
node.js中的模块化规范
node.js遵循了commonJs规范,commonJs规定了模块的特性和各个模块之间相互依赖
commonJs规定:
1.每个模块内部,module变量代表当前模块
2.module变量是一个对象,他的export属性是对外的接口
3.加载某个模块时,其实是加载该模块module.exports的属性,require()方法用于加载模块
node.js中的第三方模块又叫做包
就像电脑与计算机是相同的东西
由于node.js的内置模块仅仅提供了一些底层的API,导致在基于内置模块进行项目开发时,效率很低
包是基于内置模块封装出来的、提供了更高级、更方便的API、极大的提高了开发效率
包管理配置文件
在npm中规定,在项目根目录中,必须提供一个叫做package.json的包管理配置文件,用来记录
与项目有关的一些配置信息,例如:
项目的名称、版本号、描述
项目中用到了那些包
那些包在开发中会用到
那些包在开发和部署时都会用到
创建包管理配置文件
--npm提供了一个快捷命令,可以执行命令在所处目录中,快速创建package.json包管理配置文件
npm init -y
--package.json包管理文件中的dependencies节点表示使用npm i命令装了那些包
devDependencies节点表示某些包只会在开发阶段使用的包,,而在项目上线之后不会用到,则建议将这些包记录到 devDependencies
节点中,如果包在开发和上线之后都需要使用时,建议把这些包记录到dependencies节点中
--安装包npm i 卸载包npm uninstall
安装指定的包,并记录到devDependencies节点中 npm i 包名 --save--dev(简写npm i 包名 -D)
包的分类
包分为项目开发包,全局包
全局包:
只有工具性质的包,才有全局安装的必要性
包会安装到C:\Users\asus\AppData\Roaming\npm\node_modules下
npm i 包名 -g
项目开发包:
开发依赖包
核心依赖包
发布npm包
把终端切换到包的根目录中时,运行npm publish命令。即可发布到npm上(包名不能雷同)
运行npm unpublish 包名 --force命令,即可从npm 删除已发布的包(仅可删除上传72小时以内的包)
删除的包24小时内不能重新上传
模块加载机制
优先从缓存中加载,模块第一次加载之后会被后台缓存,这也意味着多次调用require()不会导致模块代码执行
多次,注意,无论是内置模块、用户自定义模块、还是第三方模块,他们都会优先从缓存中加载,从而提高加载效率
内置模块是node.js官方提供的,所以内置模块的优先级最高
使用自定义模块时,必须加./路径标识符,否则node将会当作内置模块或第三方模块
如果是第三方模块会尝试从node_modules文件夹中寻找,没有找到就会移动到上一层目录中,直至文件根目录
express
express是基于node.js平台,快速、开放、极简的web开发框架
express的作用与node.js内置模块类似,专门用来创建web服务器
express本质:就是一个npm上的第三方包,提供了快速创建web服务器的便捷方法
express能做什么
使用express,我们可以方便、快速创建web网站服务器或API接口的服务器
express基本使用
//导入express
//创建web服务器实例
//启动web服务器
//导入express
const express=require('express')
//创建web服务器实例
const app=express()
//启动web服务器
app.listen(9000,()=>{
console.log('express搭建的服务器好了')
})
监听客户端GET请求,post请求
调用app.get()方法,使用res.send()发送应答
//监听客户端GET请求
app.get('/user',(req,res)=>{
//向客户端发送json对象
res.send({name:'hezhixing'})
})
调用app.post()方法,使用res.send()发送应答
//监听客户端POST请求
app.post('/user',(req,res)=>{
res.send('请求成功')
})
一些方法
res.send()应答响应
req.query请求参数
req.params对象,可以访问到url,通过:匹配到动态参数
//注意这里的:id是一个动态的id
app.get('/user/:id',(req,res)=>{
console.log(req.params)
res.send(req.params)
})
express.staic
静态资源,express提供了非常好用的函数,叫做express.static(),通过它,我们可以非常
方便地创建一个静态资源服务器,例如,通过如下代码就可以将public目录下的图片、css文件
js文件对外开放访问:
app.use(express.static('public'))
注意:express在指定静态目录下查找文件,存放静态文件的目录名不会出现在url中
//导入express
const express=require('express')
//创建web服务器实例
const app=express()
//调用express.static()方法,来对外提供那个静态资源
app.use(express.static('./clock'))
//启动web服务器实例
app.listen(9000,()=>{
console.log('web服务器启动成功!')
})
托管多个静态资源目录
调用多次express.static方法 ,但是该方法会根据顺序查找,
app.use('file',express.static('./files'))
app.use('clock',express.static('./clock'))
加载时,要加访问前缀
express路由
路由广义上来讲,就是映射关系
在express中,路由指的是客户端请求与服务器处理函数之间的映射关系。
Express的路由由三部分组成,分别是请求的类型、请求的url地址、处理函数,格式如下:
app.METHOD(PATH,HANDLER)
例子:
//引入express实例
const express=require('express')
//创建expressweb服务
const app=express()
app.get('/',(req,res)=>{
console.log('/路由下被请求')
res.send('hello nodejs')
})
app.get('/user',(req,res)=>{
console.log('user路由被请求')
res.send('我是user路由下的请求')
})
//启动web服务器
app.listen(9000,()=>{
console.log('服务器启动成功')
})
路由的匹配过程
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用相应的处理函数
在匹配时,按路由的顺序进行匹配,如果请求类型和请求的url地址同时匹配成功,则express就会将这次
的请求,转交给对应的function函数进行处理。
按定义的先后顺序进行匹配,如匹配成功就不会再进行匹配
模块化路由
为了方便对路由进行模块化管理,exprss不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块
将路由抽离为单独的模块
1、创建路由,模块对应的js文件
2、调用express.Router()函数创建路由对象
3、向路由对象上挂载具体的路由
4、使用module.exports向外共享路由对象
5、使用app.use()函数注册路由模块
app.use()作用就是注册全局中间件,还可以在此加入访问前缀
中间件
中间件(middleware), 特指业务流程的中间处理环节
express中间件的调用流程:当请求到达express的服务器之后,
可以调用多个中间件,对这次请求进行预处理
express中间件的格式
express中间件,本质上就是一个function处理函数,express中间件的格式如下:
app.get(path,(req,res,next)=>{
next()
})
注意:中间件函数的形参列表中,必须包含next函数,而路由处理函数中只包含req,res
next函数的作用
next函数是实现多个中间件连续调用的关键,它表示流转关系转交给下一个中间件或路由,有放行的意思
//引入express模块
const express=require('express')
const app=express()
//定义中间件
const mw=(req,res,next)=>{
console.log('这是一个最简单的中间件函数')
//把流转关系转交给下一个中间件或路由
next()
}
//注册全局中间件
app.use(mw)
//使用中间件
app.get('/',(req,res)=>{
res.send('HOME page')
})
app.get('/user',(req,res)=>{
res.send('User page')
})
app.listen(9000,()=>{
console.log('定义中间件服务器启动')
})
中间件的作用
多个中间件之间,共享同一份req和res,基于这样的特性,我们可以在上游的中间件统一为req与res对象添加
自定义的属性或方法。供下游中间件或路由进行使用
例如:可以设置req参数,向下游路由或中间件共享该参数,下游可以使用req来访问中间件的设置
//全局中间件简化格式
app.use((req,res,next)=>{
//为req对象挂载,自定义属性,从而把时间共享传递给后面的所有路由
req.starTime=moment().format('YYYY-MM-DD HH:mm:ss')
console.log('这是定义注册全局中间件')
next()
})
定义多个中间件
通过app.use来连续定义多个全局中间件,客户端请求到达服务器以后,会按照中间件定义先后顺序依次进行调用
局部生效的中间件
不使用app.use()注册的中间件,叫做局部生效的中间件
//使用中间件
app.get('/',mw,(req,res)=>{
res.send('HOME page')
})
定义多个中间件
在路由中有两种等价的方式
1、app.get('/',mw1,mw2,mw3,handler())
2、app.get('/',[mw1,mw2,mw3],handler())
中间件使用的5个注意事项
1、一定要在路由之前注册中间件
2、客户端发送过来的请求,可以连续调用多个中间件来进行处理
3、定义中间件时,不要忘记调用next函数,进行放行
4、调用完next函数后,不要在其编写其他代码
5、连续调用多个中间件时,中间件之间是共享req与res对象
中间件的分类
express官方将中间件分为了五大类:
1、通过app.use()、app.get()、app.post(),绑定到app实例上的中间件,叫做应用级别中间件
2、路由级别中间件:绑定到express.Router()实例上的中间件,他的用法与应用级别中间件没有任何区别
只不过、应用级别中间件是绑定到app实例上的,路由级别中间件是绑定到router上的
3、错误级别中间件:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
格式:错误级别的中间件的function的处理函数中,必须要有四个形参,分别是(err,req,res,next)
例如:
app.get('/',(req,res)=>{
throw new Error('服务器内部发生了错误') //抛出自定义错误
res.send('hello')
})
app.use((err,req,res,next)=>{ //错误级别的中间件
console.log('发生了错误',err.message)
res.send('Error'+err.message) //将收集的异常错误发送给客户端,防止程序异常崩溃
})
注意错误级别中间件要注册到路由之后,才能捕获异常错误
4、express内置中间件:4.16.0内置了3个内置中间件
express.static()快速托管静态资源的内置中间件,例如HTML、css等等
express.json()解析json格式的请求体数据(4.16后可用)
express.urlencoded()解析url-encoded格式的请求体数据(4.16后可用)
使用内置中间件方法:app.use(express.json())
5、第三方中间件
非express官方内置的中间件,而是由第三方开发出来的中间件叫做第三方中间件,在项目中,大家可以按需下载并配置
例如在express4.16.0之前的版本,经常使用body-parser这个第三方中间件,来解析请求体数据:
1、运行npm i body-parser安装这个中间件
2、使用require导入这个中间件
3、调用app.use()注册并使用中间件
自定义中间件
自己手动模拟一个类似于express.urlencoded这样的中间件,来解析POST提交到服务器的表单数据
实现步骤:
1、定义中间件
2、监听req的data请求
3、监听req的end事件
4、使用querystring模块解析请求体数据
5、将解析好的数据对象挂载到req.body
使用express写接口
1、创建基本的服务器
2、创建api路由模块
3、接口编写
4、跨域问题解决:
有:cors、jsonp(这种有缺陷,只支持get请求)
跨域问题解决
1、cors跨域问题:
cors是express的一个第三方中间件,通过安装和配置cors中间件,可以很方便的解决跨域问题
使用步骤有三步:
1.运行npm i cors安装中间件
2.使用const cors=require('cors')导入中间件
3.在路由之前调用app.use(cors())
cors
什么是cors:英文名称(Cross-origin Resource Sharing)跨域资源共享由一系列http响应头组成,
这些http响应头决定浏览器是否阻止前端js代码跨域获取资源
浏览器同源安全策略会默认阻止网页“跨域”获取资源,但是如果接口服务器配置了cors相关的http响应头
就可以解决跨域问题访问限制。
注意:cors主要在服务器端进行配置,客户端浏览器无需做任何额外的配置,即可请求开启了cors的接口
cors在浏览器中有兼容性,只支持 XMLHttpRequest Level2的浏览器才能正常访问
cors响应头
3个相关的响应头部:
Access-Contorl-Allow-Origin 可以控制那些网站的请求
例如:res.setHeader('Access-Contorl-Allow-Origin','*') //允许所有网站的请求
Access-Contorl-Allow-Headers 默认情况下,cors仅支持客户端发送9个请求头
通过这个可以声明额外的请求头:
res.setHeader('Access-Contorl-Allow-Headers','Content-Type,X-Custom-Header')
Access-Contorl-Allow-Methods 默认情况下cors仅支持客户端发起get、post、HEAD请求
如果客户端想要通过PUT、DELELT等方式请求服务器的资源,则需要在服务器端设置指明实际需要的http方法
res.setHeader('Access-Contorl-Allow-Methods','*') //允许所有请求方法
cors请求的分类
在客户端请求cors接口时,根据请求方式和请求头的不同,可以将cors请求分为两大类:
1、简单请求
请求方式属于get、post、head三者之一,同时请求头在9个范围之内
2、预检请求
只要包含一项就是预检请求
请求方式是get、post、head三者之外
请求头包含自定义头部字段
向服务器发送了application/json格式的请求
什么是预检请求:在浏览器与服务器正式通信之前,浏览器会发送一个OPTION的请求进行预检,以获知
服务器是否允许实际请求,所以这个OPTION的请求称为预检请求。服务器成功响应预检请求后,
才会发送真正的请求
简单请求与预检请求的区别:
简单请求:客户端与服务器端之间只会发生一次请求
预检请求:客户端与服务器端会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求
jsonp接口
概念:浏览器端通过<script>标签的src属性,请求服务器上的数据,同时服务器返回一个函数的调用。这种请求数据的方式叫做jsonp
特点:JSONP不属于真正的AJAX请求,因为它没有使用XMLHttpRequest对象
JSONP仅支持GET请求,不支持POST、PUT等请求
创建JSONP接口:
如果项目中已经配置了cors跨域资源共享,为了防止冲突,必须要配置cors中间件之前声明JSONP接口,否则JSONP接口会被处理
成开启了CORS的接口
实现jsonp接口的步骤:
1、获取客户端发送过来的回调函数的名字
2、得到通过jsonp形式发送给客户端的数据
3、根据前两步得到的数据,拼接一个函数调用的字符串
4、把上一步拼接得到的字符串,响应给客户端的<script>标签解析
//配置JSONP接口,必须要在cors之前配置
app.get('/api/jsonp',(req,res)=>{
//定义JSONP接口具体实现过程
//1、获取客户端发送过来的回调函数的名字
const funcName=req.query.callback
//2、获取通过jsonp格式发送给客户端的数据
const data={name:'zs',age:19}
//3、根据前两步得到的数据、拼接出一个函数调用的字符串
const scriptStr=`${funcName}(${JSON.stringify(data)})`
//4、把上一步拼接的字符串,响应给客户端的<script>标签解析执行
res.send(scriptStr)
})
//jsonp请求
$("#jsonp").on('click',function(){
$.ajax({
type:'GET',
url:"http://127.0.0.1:9000/api/jsonp",
dataType:'JSONP',
success:function(res){
console.log(res)
}
})
})
数据库
数据库的基本概念
数据库(database)是用来阻组织、存储和管理数据的仓库
为了方便管理互联网数据,就有了数据库管理系统的概念,简称数据库
传统型数据库中,数据的组织结构分为数据库、数据表、数据行、数据列4大部分组成
之间的关系:
1、在实际的开发过程中,一般情况下,每个项目对应独立的数据库
2、在不同的数据,要存储到数据库的不同表中,例如:用户数据存到users表中,图书信息存到books表中
3、每个表具体要存储那些信息,由字段来决定
安装Mysql
.......
sql增删改查基本使用
1、SQL的SELECT语句:
SELECT语句用于从表中查询数据,执行的结果被存储在一个结构表中,格式如下:
SELECT *FROM 表名称 //查询指定表
SELECT 列名称 FROM 表名称 //从指定表中,查询出指定列的名称的数据
2、SQL的INSERT INTO 语句
INSERT INTO 语句用于向数据表中插入新的数据行,语法格式
INSERT INTO table_name(列1,列2....)VALUES(值1,值2....)
3、SQL中Update语句
Update语句主要运用于修改数据表中的数据,语法格式如下:
UPDATE 表名称 SET 列名=新值 WHERE 列名称=某值 //修改一列
UPDATE 表名称 SET 列名=新值, 列名=新值 WHERE 列名称=某值 //修改多个列
4、SQL中DELETE语句
DELETE语句用于删除表中的行,语法格式:
DELETE FROM 表名称 WHERE 列名称=值
注意:在删除、修改时一定要注意加上WHERE条件
SQL中WHERE子句
WHERE用于限定选择的标准,在SELECT、DELETE、UPDATE语句中均可使用WHERE子句来限定选择的标准
SELECT 列名称 FROM 表名 WHERE 列 运算符 值 //查询语句中的where条件
UPDATE 表名称 SET 列=新值,列=新值 WHERE 列 运算符 值 //更新满足WHERE条件
DELECT FROM 表名称 WHERE 列 运算符 值 //删除满足WHERE条件的行
其中运算符有=,<,>,<>(不等于),BETWEEN
SQL语句的ADD与OR运算符
AND与OR可以在WHERE子句中把两个多个条件结合起来
AND表示必须同时满足多个条件,相当于javascript中的&&运算符
OR表示只要满足一个条件就可以,相当于javascript中的||运算符
SQL语句中的ORDER BY子句
ORDER BY子句用于根据指定的列对结果集进行排序
ORDER BY子句默认按照升序记录进行排序
如果希望降序排列,可以使用DESC关键字
例如:select * from node_user order by id DESC;
ORDER BY子句多重排序:
例如先按照id字段降序排列,再按name升序排序:select * from node_user order by id DESC,name asc;
SQL语句中的count函数与as关键字
count(*)函数用于返回查询结果总数居条数,语法格式:
SELECT COUNT(*) FROM 表名称
可以使用AS关键字来为列设置别名
例如:查询出来的列的名称设置别名 SELECT COUNT(*) AS total FROM node_user WHERE id>2
在项目中操作数据库
1、安装操作数据库的第三方模块mysql
2、通过mysql模块连接到mysql数据库
//建立与mysql数据库的连接关系
const db=mysql.createPool({
host:'127.0.0.1', //数据库地址
port:'3306', //端口
user:'root', //登录数据账号
password:'123456', //密码
database:'node_test' //数据库
})
3、通过mysql模块执行SQL语句
//查询数据库
db.query('SELECT * FROM node_user',(err, results)=>{
if(err) return console.log('数据库连接失败:',err.message)
//打印出结果
console.log(results)
})
//向user表中插入的数据
const user ={id:5,name:'hzx',password:897}
//将执行的SQL语句,?表示占位符
const sqlStr='insert into node_user (id,name,password) values(?,?,?) '
//插入数据
db.query(sqlStr,[user.id,user.name,user.password],(err,results)=>{
if(err) return console.log('数据库连接失败:',err.message)
//打印插入的结果
if(results.affectedRows ===1){console.log('插入成功')}
})
注意:插入数据时,插入字段更多时,可以使用该SQL语句:insert into 表名 set ?
修改数据时,修改字段很多时,可以使用update 表名 set ? where id=?
web开发模式
1、基于服务端渲染的开发模式
优点:前端耗时少,因为服务器端负责动态生成html的内容,浏览器只需要渲染页面即可,尤其是移动端,更省电
有利于SEO,因为服务器端响应的是完整的html内容,所以爬虫更容易获取信息,更有利于SEO
缺点:
占用服务器端资源
不利于的前后端分离,开发效率低
2、前后端分离的开发模式
概念:前后端分离的开发模式,依赖于AJAX技术的广泛应用。简而言之,
就是后端只负责API接口、前端使用AJAX调用接口的开发模式
优点:
开发体验好
用户体验好
减轻了服务器端压力
缺点:
不利于SEO
身份认证
身份认证又称身份验证、鉴权是指通过一定手段,完成对用户身份的确认。
在web开发中,也涉及用户身份的验证,例如各大网站的手机号验证码登录、邮箱登录、二维码登录等。
1、服务器渲染开发模式推荐使用session认证机制
2、前后端分离推荐使用JWT认证机制
session认证机制
http协议的无状态性:指的是客户端的每次http请求都是独立的,连接多个请求没有直接的关系,服务器不会主动保留http请求的状态
突破http无状态性:cookie
什么是cookie
cookie是存储在用户浏览器中一段不超过4kb的字符串,他是由一个名称、一个值和其他几个用于控制cookie有效期、安全性、使用范围组成
不同域名的cookie是独立存在的,每当客户端发起请求时,会自动把当前域名下所有未过期的cookie一同发送给服务器
cookie特性:
1、自动发送
2、域名独立
3、过期时限
4、4kb限制
cookie在身份认证的作用
客户端在第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的cookie,客户端会将cookie保存在浏览器中
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的cookie、通过请求头形式发送给浏览器,服务器即可验证客户端身份。
cookie不具有安全性,cookie是储存在浏览器中,而浏览器提供了cookie的读写API,因此cookie极易被伪造,不要存放用户隐私及密码
session工作原理
.....
在express中使用session认证
// TODO_01:请配置 Session 中间件
const session=require('express-session')
app.use(session({
secret:'hezhixing',
resave:false,
saveUninitialized:true,
}))
JWT认证机制
session认证机制需要配合cookie才能使用,由于cookie不支持跨域访问,所以当涉及到前端跨域请求后后端连接
接口的时候,需要做很多额外的配置,才能实现seesion认证
注意:当前端请求后端接口不存在跨域问题时,推荐使用session认证机制
需要跨域请求后端接口时,推荐使用JWT认证机制
JWT(JSON web token)是目前最流行的跨域认证方案
JWT工作原理
....
JWT组成部分
JWT通常由三部分组成,分别是Header头部、payload有效荷载、signature签名。三者采用英文“.”分割,格式:
Header.payload.signature
payload才是真正的用户信息,他是经过加密之后生成的字符串
header和signature是为了保证token安全
JWT使用方式
客户端收到服务器返回的JWT之后,通常会将它储存在localStorage或sessionStorage中
此后,客户端每次与服务器端通信都要带上这个JWT的字符串,从而进行身份认证。
推荐的做法是将JWT放在http请求头的Authorization字段中,格式:
Authorization:Bearer <token>
生成token的步骤
1、使用npm i express-jwt jsonwebtoken命令下载第三方包
2、引入这两个包
const jsonwebtoken=require('jsonwebtoken')
const expressJWT=require('express-jwt')
3、定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey='hezhixing No1'
4、在登录成功之后,调用jsonwebtoken.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
//该方法会传递三个参数,参数1:用户信息对象,参数2:加密的密钥,参数3:配置对象,可以配置token的有效期
//记住千万不要把密码加密到token中
const jwtStr=jsonwebtoken.sign({username:userinfo.username},secretKey,{expiresIn:'30s'})
res.send({
status: 200,
message: '登录成功!',
token: jwtStr // 要发送给客户端的 token 字符串
})
5、注册将 JWT 字符串解析还原成 JSON 对象的中间件
//配置成功了express-jwt中间件,就可以将解析出来的用户信息,挂载到req.user(req.auth)上
app.use(expressJWT({algorithms:['HS256'],secret:secretKey}).unless({path:[/^\/api\//]}))
6、使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
console.log(req.user)
res.send({
status: 200,
message: '获取用户信息成功!',
data: req.user // 要发送给客户端的用户信息
})
7、使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err,req,res,next)=>{ //错误级别的中间件
//这次错误是由token解析失败导致的
if(err.name==='UnauthorizedError'){
return res.send({
status:0,
msg:'无效的token'
})
}
致谢
此文档是学习黑马程序员B站视频记录。学习请搜索黑马程序员node课程
本博客所有文章除特别声明外,如需转载或引用,请注明出处!