一、内置方法
***fs模块
方法 **
-
fs.readFile()方法
读取指定文件中的内容
const fs = require('fs') fs.readFile('文件的路径','utf8',function(err,dataStr) {})
-
fs.writeFile()方法
指定文件中写入的内容
const fs = require('fs') fs.writeFile('111.txt', 'abcd', function(err) { console.log(err); //写入成功err为空 })
-
路径问题
路径拼接错误 是因为提供了相对路径
路径相对于执行文件的终端的所在路径
##解决
- 绝对路径:拼接一个带盘符的路径(移植性差,不利于维护)
- __dirname 当前文件所处的目录 拼接要执行的js文件的名称
***path模块
**方法
-
path.join()方法
将多个路径片段拼接成一个完整的路径字符串
const path = require('path')
const pathStr = path.join('/a', '/b/c', '../', '/d', '/e')
console.log(pathStr);
-
path.basename()方法
获取文件名称
-
path.extname()方法
获取文件的后缀名
*replace字符串的替换 可以跟正则表达式来替换正则表达式匹配的字符串
**注意
- fs.writeFile() 只能用来创建文件,不能用来创建路径
- 重复调用fs.writeFile() 新写的内容会覆盖之前的旧内容
***http模块
**ip地址
- 每台计算机唯一确定的地址
- ping xxx地址 ——查看某个地址的ip地址
- 以192.168开头的是私有地址
- 最大是255
- 127.0.0.1 / localhost ——表示自己的电脑(都能访问自己的电脑)
**域名和域名服务器
- 域名:帮助我们记忆
- 域名服务器:提供ip地址和域名之间的转换的服务器
- DNS域名解析服务器
**端口号
客户端发来的网络请求 通过端口号 可以被准确的交给对应的web服务进行处理
##http协议 默认端口为80 https默认端口 443
**创建web服务器的模块基本步骤
- 导入http
- 创建 web 服务器实例
- 为服务器实例绑定 request 事件,监听客户端的请求
- 启动服务器
- node命令访问服务器
//导入http
const http = require('http')
//创建 web 服务器实例
const server = http.createServer()
//为服务器实例绑定 request 事件,监听客户端的请求
server.on('request',function(req,res) {
//有请求 就会触发 request 事件
console.log('Someone visit our web server')
})
//启动服务器(端口的绑定)
server.listen(8080,function() {
//第一个参数是 端口号
console.log('服务器启动了')
})
**req 请求对象
可以得到请求相关的信息
- req 是请求对象 包含与客户端相关的数据和属性
- req.url 是客户端请求的 URL 地址
- req.method 客户端的请求类型
**res 响应对象
- res.end() 向客户端响应一些内容 并结束这次请求的处理过程
** 解决中文乱码的问题
设置 Content-Type 响应头
//用utf-8的形式对代码进行解析
res.setHeader('Content-Type','text/html;charset = utf-8')
- 时钟案例
const http = require('http') const server = http.createServer() const fs = require('fs') const path = require('path') server.on('request', function(req, res) { var fileName = req.url if (fileName === '/') { fileName = 'index.html' } //简化代码 fs.readFile(path.join(__dirname, fileName), 'utf8', function(err, dataStr) { if (err) { return res.end('Not Found') } res.end(dataStr) }) //分开写 加深理解 // if (req.url === '/' || req.url == '/index.html') { // fs.readFile(path.join(__dirname, '/index.html'), 'utf8', function(err, dataStr) { // res.end(dataStr) // }) // } else if (req.url === '/index.css') { // fs.readFile(path.join(__dirname, '/index.css'), 'utf8', function(err, dataStr) { // res.end(dataStr) // }) // } else if (req.url === '/index.js') { // fs.readFile(path.join(__dirname, '/index.js'), 'utf8', function(err, dataStr) { // res.end(dataStr) // }) // } }) server.listen(9919, function() { console.log('启动了'); })
二、模块化
***加载模块
//加载内置的 fs 模块
const fs = require('fs')
//加载用户的自定义模块
const custom = require('./相对路径')
//加载第三方模块
const moment = require('moment')
##require
-
帮助我们引入一个js文件(得到别人提供的东西 就是一个对象)
得到的是引入模块的 exports 属性
-
可以使加载模块的代码立即执行
-
后面不写要加载文件的后缀名 对应文件也能被加载
***模块作用域
- 在一个模块中定义的变量 不能在另一个模块中使用
##向外共享模块作用域的成员
- module 对象
- .js 自定义模块中都有一个** module **对象,它里面存储了和当前模块有关的信息,
- require()方法 导入自定义模块 得到的是 module.exports 所指向的对象
- 导入的是 module.exports最后指向的对象为准
module.exports.obj = obj module.exports.fn = fn //导入的两种形式 module.exports = { obj: obj, fn: fn }
- exports对象(只是写法更简单了)
- exports 和module.exports 指向同一个对象。
- exports 和 module.exports 使用是相同的 exports用法更简单
**CommonJS 规范
- module变量——当前的文件
- module.exports——是对外的接口
- require()方法用于加载模块
三、包
***安装包
- npm install 包的完整名称————安装指定的包
- npm i 完整的包的名称————简写
- npm i moment@+版本号————安装指定版本的包
//导入指需要的包
const moment = require('moment')
//查看官方文档 使用包
moment().format('YYYY-MM-DD HH:mm:ss')
***包管理配置文件
- package.json——记录项目开发要用到的包
- 项目开发中,一定要把 node_modules 文件夹 添加到 .gitignore 忽略文件中
##快速创建 package.json 文件
//在执行命令所处的目录中,快速新建 package.json 文件
npm init -y
//1. 只能在英文的目录下成功运行 不能出现中文和空格
// 2. npm install 命令安装包的时候,npm 包管理工具会自动把包的名称和版本号,记录到package.json中。
##dependencies 节点
记录使用 npm install 命令安装了哪些包
##一次性安装所有的包
npm install 或 npm i
##卸载指定的包
npm uninstall 包的名称
卸载完成之后 package.json 的 dependencies 中会自动移除掉
##devDependencies 节点
在项目开发阶段会用到 项目上线之后不会用到 就把包放到这个节点
##安装指定的包 并记录到 devDependencies 节点中
//简写
npm i 包名 -D
//完整写法
npm install 包名 --save-dev
***解决下载包慢的问题
-
切换 npm 的下包的服务器地址
# 查看当前的下包镜像源 npm config get registry #将下包的镜像源切换为淘宝镜像 npm config set registry=https://registry.npm.taobao.org/ #检查镜像源是否下载成功 npm config get registry
-
nrm
快速查看和切换下包的镜像源
#通过 npm 将 nrm 安装为全局可用的工具 npm i nrm -g #查看所有可用的镜像源 nrm ls #切换为 taobao 镜像 nrm use taobao
-
解决nrm无法加载的问题
在命令行输入命令Set-ExecutionPolicy RemoteSigned -Scope Process 即可解决
-
***包的分类
-
项目包
- 开发依赖包:被记录到devDependencies 节点中的包,只在开发期间会用到
- 运行依赖包:被记录到dependencies 节点中的包,在开发期间和项目上线之后都会用到
-
全局包
工具类型的包才会全局安装
#全局安装指定的包 npm i 包名 -g #卸载全局安装的包 npm uninstall 包名 -g
## i5ting_toc 包
可以把md 文档转为 html 页面的小工具
#将 i5ting_toc 安装为全局包
npm install i5ting_toc -g
# 调用 i5ting_toc 实现 md 转 html 的功能
i5ting_toc -f 要转换的 md 文件的路径 -o
## 规范的包结构
- 包必须以单独的目录而存在
- 顶级目录必须包含 package.json 这个包管理配置文件
- package.json 中必须包含 name,version,main 这三个属性 分别代表 包的名字 版本号 包的入口
***express框架
快速的创建web网站服务器或API接口服务器
##监听GET请求
app.get('请求URL',function(req,res) {/*处理函数*/})
##监听POST请求
app.post('请求URL',function(req,res) {/*处理函数*/})
##内容响应给客户端
- res.send(“要发送的内容”) 可以把处理好的内容,发送给客户端
const express = require('express')
const app = express()
app.get('/user', function(req, res) {
// user/:aa 可以监听到user后所有变化的参数
// 冒号后面的值不是固定的 但是冒号必须写
//可以后面跟多个动态参数 user/:id/:name
res.send('要发送的内容')
//可以传递对象
})
app.listen(80, () => {
console.log('开启');
})
##访问 查询字符串形式 发送到服务器的参数
查询参数
-
req.query
客户端使用 ?name=zs&age=20
req.query.name req.query.age
##获取URL 的动态参数
路径参数
- req.params 默认是一个空对象 里面存放着通过动态匹配到的参数值
##注意
- 冒号后面的值不是固定的 但是冒号必须写
- 可以后面跟多个动态参数 user/:id/:name
## 托管静态资源
- express.static()——创建一个静态资源服务器
//快速的对外提供静态资源 app.use(express.static('指定文件夹的路径'))
- 托管多个静态资源目录
//只需要多次调用express.static 先调用谁就先显示谁 app.use(express.static('指定文件夹的路径')) app.use(express.static('指定文件夹的路径'))
- 挂载路径前缀
app.use('前缀地址路径 ',express.static('指定文件的路径'))
*** nodemon
监听项目文件的变动,当代码被修改后 nodemon 会自动帮我们重启项目
##安装
npm install -g nodemon
##使用
//传统方式
node app.js
//可以实现自动重启项目的效果
nodemon app.js
***路由
就是映射关系
##Express 路由
- 客户端的请求与服务器处理函数之间的映射关系
- 组成:请求的类型 请求的URL地址 处理函数
##创建路由模块
//1. 导入 express
var express = require('express')
//2. 创建路由对象
var router = express.Router()
//3. 挂载获取用户列表的路由
router.get('/user/list',function(req,res){
res.send('Get user list')
})
//4. 挂载添加用户的路由
router.post('/user/add',function(req,res){
res.send('Add new user')
})
//5. 向外导出路由对象
module.exports = router
##注册路由
//1. 导入路由模块
const userRouter = require('./router/user.js')
//2. 使用 app.user() 注册路由模块
app.use(userRouter)
// app.use() 函数的作用 就是来注册全局 中间件
app.listen(8899,function() {
console.log('http://127.0.0.1')
})
## 为路由模块添加前缀
//1. 导入路由模块
const userRouter = require('./router/user.js')
//2. 使用 app.user() 注册路由模块,并添加统一的访问前缀 /api
app.use('/api',userRouter)
**Express 中间件
特指业务流程的中间处理环节
作用:对请求进行预处理
##中间件的格式
中间件函数的形参列表中,必须包含 next 参数 (next是函数 所以要调用)
路由处理函数中只包含req和res
##next 函数的作用
next 函数是实现多个中间件连续调用的关键
##定义中间件函数
const express = require('express')
const app = express()
const mw = function(req,res,next) {
console.log('最简单的中间件')
//把流转关系,转交给下一个中间件或路由
next()
}
app.listen(80,()=>{
console.log('1')
})
##全局生效的中间件
通过调用 app.use(中间件函数),定义一个全局生效的中间件
const express = require('express')
const app = express()
const mw = function(req,res,next) {
console.log('最简单的中间件')
//把流转关系,转交给下一个中间件或路由
next()
}
app.use(mw)
//挂载路由
app.get('/',function(req,res) {
res.send('hello world')
})
app.listen(80,()=>{
console.log('1')
})
##中间件的作用
共享同一份req 和 res
app.use((req,res,next)=>{
req.newTime = 'time'
next()
})
//挂载路由
app.get('/',function(req,res) {
res.send('hello world' + req.newTime)
})
##局部生效的中间件
不使用 app.use() 定义的中间件
只针对某个路由产生作用
//定义中间件函数 mw1
const mw1 = function(req,res,next) {
console.log('这是中间件函数')
next()
}
//mw1 这个中间件只在‘当前路由中生效’,这种用法属于‘局部生效的中间件’
app.get('/',mw1,function(req,res) {
res.send('hello world ')
})
##定义多个局部的中间件
//两种方式完全是等价的 任选其一
app.get('/',mw1,mw2,(req,res)=> { res.send('hellow world')})
app.get('/',[mw1,mw2],(req,res)=> { res.send('hellow world')})
**中间件的注意
- 必须在路由之前定义中间件
- 客户端发送过来的请求 可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后 不要忘记调用 next() 函数
- 防止代码逻辑混乱 调用 next() 函数后不要写额外代码
- 多个中间件之间 共享 req 和 res 对象
##错误级别中间件
必须有4个参数 前后顺序分别是(err,req,res,next)
//1. 定义一个路由
app.get('/',(req,res)=>{
//抛出一个自定义错误
throw new Error('服务器内部发生了错误')
res.send('hello world')
})
app.use(function(err,req,res,next) { //错误级别的中间件
console.log('发生了错误',err.message)//打印错误消息
res.send('Error'+ err.message)// 向客户端响应错误相关的内容
})
app.listen(80,function() {
console.log('11')
})
注意:错误级别的中间件必须定义在所有路由之后
##内置中间件
- express.static 快速托管静态资源的内置中间件
- express.json 解析json 格式的请求体数据(4.16.0+)
- express.urllencoded 解析 URL - encoded 格式的请求体数据(4.16.0+)
//配置解析 application/json 格式数据的内置中间件 app.use(express.json()) //配置解析 application/x-www-form-urlencoded 格式数据的内置中间件 app.use(express.urlencoded({extended:false}))
注意:
- 在服务器,可以使用 req.body 这个属性 ,来接受客户端发送过来的请求体数据
- 默认情况下,如果不配置解析表单数据的中间件 则 req.body 默认等于 undefined
##自定义中间件
-
定义中间件
app.use(function(req,res,next) { //中间业务逻辑 })
-
监听req的data事件
数据量比较大,会把数据切割后,分批发送到服务器 data事件可能会触发多次
//1. 定义变量 来存储客户端发送过来的请求数据 let str = '' //2. 监听 req 对象的 data 事件(客户端发送过来的请求体数据) req.on('data',(chunk)=>{ //拼接请求体数据 隐式转换为字符串 str += chunk })
-
监听req 的end事件
//1. 定义变量 来存储客户端发送过来的请求数据 let str = '' //2. 监听 req 对象的 data 事件(客户端发送过来的请求体数据) req.on('data',(chunk)=>{ //拼接请求体数据 隐式转换为字符串 str += chunk }) //3. 监听 req 的 end 事件 req.on('end',()=>{ //打印完整的请求体数据 console.log(str) //把字符串格式的请求体数据 解析成对象格式 })
-
使用 querystring 模块解析请求体数据
// 导入处理 querystring 的 Node.js 内置模块 const qs = require('querystring') //4. 调用 qs.parse() 方法 把查询字符串解析为对象 const body = qs.parse(str)
-
解析出来的数据挂载为 req.body
req.on('end',()=>{ const body = qs.parse(str) req.body = body next() })
**定义JSONP接口
##实现JSONP的接口步骤
const express = require('express')
const app = express()
app.get('/api/get', function(req, res) {
// 1. 获取客户端发送过来的回调函数的名字
const fnName = req.query.callback
//2. 得到 JSONP 形式发送给客户端的数据
const data = { name: 'zs', age: '22' }
//3. 函数调用
// const fn = `${fnName}(${JSON.stringify(data)})`
// 响应给客户端的 script 标签进行解析
// res.send(fn)
const jsonStr = JSON.stringify(data)
res.send(fnName + '(' + jsonStr + ')')
//本质就是函数的调用 数据是经过函数的参数来传递
})
app.listen(80, () => {
console.log('开启');
})
##CORS资源共享
- 发起的是一个get的跨域请求,为了能够让浏览器不拦截这项数据,服务端需要在响应头设置Access-Control-Allow-Orgin
- 发起的是get的跨域请求 并且在请求头中带上了自定义字段 ,服务端需要在响应头设置 Access-Control-Allow-Orgin Access-Control-Allow-Headers
- 发起的是一个put的跨域请求,为了能够让浏览器不拦截这项数据,服务端需要在响应头设置 Access-Control-Allow-Orgin Access-Control-Allow-Methods
- 发起的是一个put的跨域请求 并且携带了自定义请求头 服务端需要在响应头设置 Access-Control-Allow-Orgin Access-Control-Allow-Headers Access-Control-Allow-Methods
四、数据库
***数据库的基本使用
##创建数据库
- 点击新建数据库按钮
- 填写数据库的名称
- 点击Apply 按钮,创建数据库
##创建数据表
- 数据类型
- int整数
- varchar(len)字符串
- tinyint(1)布尔值
- 字段的特殊标识
- PK 主键 唯一标识
- NN 值不允许为空
- UQ 值唯一
- AI 值自动增长
**SQL
- SQL语言只能在关系型数据库中使用
##SQL做的事情
对数据库的数据进行 增删改查
- 查询数据(select)
- 插入数据(insert into)
- 更新数据(update)
- 删除数据(delete)
##SELECT语句
SELECT 语句用于从表中查询数据
--从 FROM 指定的 【表中】 查询出 【所有的数据】 * 表示【所有的列】
SELECT * FROM 表名称
--从 FROM 指定的表中 查询指定列名称(字段) 的数据
SELECT 列名称 FROM 表名称
注意:关键字对 大小写不敏感
##INSERT INTO 语句
INSERT INTO 语句用于向数据表中插入新的数据行
--列和值之间是一一对应 ,多个列和多个值之间,使用英文逗号分隔
--insert into 表名(列1,列2 ...) values(值1,值2 ...)
insert into users(username,password) values('tony stark','098123')
##UPDATE语句
Update 语句用于修改表中的数据。
--update 表名 set 列名称 = 新值 where 列名称=某值
--更新一个列
update users set password='88888888' where id=7
--更新多个列
update users set password='admin123',status=1 where id=2
##DELETE语句
DELETE 语句用于删除表中的行
delete from 表名称 where 列名称=值
delete from users where id=1
##where 子句
WHERE 子句用于 限定选择的标准
运算符:
- <> 、 != 不等于
- BETWEEN 在某和范围内
- LIKE 搜索某种模式
--查询 username 不等于 admin的所有用户
SELECT * FROM users WHERE username<>'admin'
##AND 和 OR运算符
- AND 表示必须同时满足多个条件
- OR 表示只要满足任意一个条件即可
--使用 and 来显示所有状态为0 且 id小于3 的用户
select * from users where status=0 and id<3
-- 使用 or 来显示所有状态为1 或者 username 为 zs 的用户
select * from users where status=1 or username='zs'
##ORDER BY 子句
ORDER BY 语句用于根据指定的列对结果集进行排序(默认是升序 要想降序 加 desc)
--对 users 表中的数据 按照 status 字段进行 升序 排序
select * from users order by id asc
-- 降序
select * from users order by id desc
##ORDER BY 子句 – 多重排序
-- 对 users 表中的数据 先按照status 字段进行降序排序,在按照 username 的字母顺序,进行升序排序(后面用逗号进行罗列就好了)
select * from users order by status desc,username asc
##COUNT( *)函数
COUNT( *) 函数用于返回查询结果的总数据条数
select count(*) from 表名 --返回表中有多少条数据
select count(*) from users where status=0
-- 返回的是符合条件的数据的个数
- 使用AS 为列设置别名
--将列名称从 count(*) 修改为 total select count(*) as total from users where status=0
**操作mysql 数据库的步骤
- 安装 操作 MySQL 数据库的第三方模块
- 连接到MySQL 数据库(都需要账号密码)
- 执行sql 语句
-
安装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 模块能否正常工作
调用 db.query() 函数
//检测 MySQL 模块能否正常工作 db.query('select 1',(err,results)=>{ 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)
//查询成功
console.log(results)
})
##插入数据
判断 results.affectedRows ===1 说明插入成功
//要插入 users 的数据
const user = { username:'gg',password:'999999'}
//待执行的 sql 语句 ? 表示占位符
const sqlStr = 'insert into users (username,password) values(?,?)'
//使用数组 依次为 ? 占位符 指定具体的值
db.query(sqlStr,[user.username,user.password],(err,results) => {
if(err) return console.log(err.message)//失败
if(results.affectedRows === 1) {
console.log('插入数据成功')//成功
}
})
-
插入数据便捷方式
如果对象的 每一个属性 和 数据表的字段一一对应
const sqlStr = 'insert into users set ? '
##更新数据
//1. 要更新的数据对象
const user = {id:7,username:'aaa',password:'000'}
//2.要执行的 sql 语句
const sqlStr = 'update users set username=?,password=? where id=?'
//3. 调用 db.query() 执行 sql 语句的同时 使用数组依次为占位符指定具体的值
db.query(sqlStr,[user.username,user.password,user.id],(err,resullts)=>{
if(results.affectedRows === 1) {
console.log('插入数据成功')//成功
})
- 更新数据的便捷方式
const sqlStr = 'update users set ? where id=?'
##删除数据
const sqlStr = 'delete from users where id=?'
//如果 sql 语句中只有 一个占位符(也就是一个 ? )则可以省略数组
db.query(sqlStr,1,(err,results)=>{
if(results.affectedRows === 1) {
console.log('删除数据成功')//成功
})
- 标记删除
//使用 update 语句代替 delete 语句 只更新数据的状态 并没有真正的删除
***前后端身份认证
状态保持
通过一定手段完成对用户身份的确认(手机验证码登录,邮箱登录 ,二维码登录)
##身份认证方式
- 服务端渲染推荐使用Session 认证机制
- 前后端分离推荐使用 JWT认证机制
##Session认证机制
-
HTTP协议的无状态性
每次的http 请求都是独立的,连续多个http 请求之间没有直接关系,服务器不会主动保留每次 http 请求的状态
-
http无状态的突破
cookie存储方式
- 自动存储
- 自动携带(不用在写js代码)
使用方式
res.cookie(‘userid’,user.id)
userid:表示存储在cookie中的名称
user.id:表示存储在cookie中的值
##cookie数据不具有安全性
不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等
##使用Session 认证
- 安装 express-session 中间件
npm install express-session
2. 配置express-session 中间件
//导入
const session = require('express-session')
//配置
app.use(session({
secret:'',//secret 属性的值可以是任意字符串
resave:false,//固定写法
saveUninitialized:true//固定写法
}))
3. 在session 中存数据(存储在服务器中)
当 express-session 中间件配置成功后,即可通过req.session 来访问和使用session 对象,存储用户的关键信息
4. 从 session 中取数据
req.session
5. 清空session
调用 req.session.destroy()函数
##JWT认证机制
- 当前端请求后端接口 不存在跨域问题的时候 使用Session 身份认证机制
- 跨域请求 推荐JWT 认证机制
- 最流行的 跨域认证的解决方案
##JWT工作原理
用户的信息通过 Token 字符串的形式,保存客户端的浏览器,通过还原 Token 字符串的形式来认证用户身份
##JWT组成部分
- Header 头部(保证token的安全性)
- Payload 有效荷载(真正的用户信息)
- Signature 签名(保证token的 安全性) ——>保证信息不会被篡改
***在 Express 中使用 JWT
如何在服务器生成 token的字符串
##安装 JWT 相关的包
npm install jsonwebtoken express-jwt
// jsonwebtoken 生成 JWT 字符串
// express-jwt 将JWT 字符串解析还原成 JSON 对像
##导入 包
##定义secret 密钥
保证JWT 字符串的安全性 (字符串的加密和解密)
//帮助我们生成第三部分的字符串(也就是Signature)
const secretKey = ‘xxx’
##生成JWT 字符串
jwt.sign({参数1},参数2,{参数3})
// 参数1 : 用户的信息对象
// 参数2 : 加密的密钥
// 参数3 : 配置对像 可以配置当前 token 的有效期{expiresIn:'30S'} expiresIn 属性名固定
##将JWT 字符串还原为 JSON 对象
//注册转换为JSON对像中间件
//expressJWT({ secret: secretKey}) 用来解析 token 的中间件
//unless({ path:[/^\/api\//] }) 指定哪些接口不需要访问权限
app.use(expressJWT({ seret: secreKey }).unless({ path:[/^\/api\//]}))
***状态保持的三种方式
http的无状态性
-
Cookie
自动存数据
自动携带数据
-
Session
本质上依赖 Cookie
Session 的方案中,真实的是存储在服务器的,给浏览器的cookie 存的只是一个sid
-
Token
Session 的局限性:
cookie的数据,在跨域的情况下,不会自动携带
Session会占用服务端的资源
Token是由后端生成,由浏览器来存储
手动的存—> LocalStorage
手动的带到请求中
评论区