Node.js 中文网
Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine
Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境
语法:
fs.readFile(path[, options], callback)
语法:
fs.writeFile(file, data[, options], callback)
原数据:
目标处理结果:
//读取文件
fs.readFile('./1.txt', 'utf-8', (err, dataStr) => {
//如果读取失败,则返回err,默认值为null,故可直接用于if判断
if (err) {
//直接return,不再输出后续代码
return console.log(err)
}
//dataStr为成功读取到的数据
console.log('读取成功' + dataStr)
//处理数据
//对dataStr做空格换行分隔,赋到Old数组上,并创建空的New数组
const arrOld = dataStr.split('\r\n')
const arrNew = []
//对Old数组的每一项,将'='替换为':',并push到新数组中
arrOld.forEach(item => {
arrNew.push(item.replace('=', ':'))
})
//以回车分隔将数组串联成一字符串
const newStr= arrNew.join('\r\n')
// console.log(typeof(newStr)) 可以看到输出类型为字符串
// console.log(newStr) 输出串联后的字符串
//写入文件
fs.writeFile('./1.txt',newStr , (err) => {
//写入失败返回的err默认为null,因此可作为判断条件
if (err) {
//err.message是错误信息
return console.log(err.message);
}
console.log('写入成功');
});
})
在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 …/ 开头的相对路径时,容易出现路径动态拼接错误的问题
原因: 代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径
解决方案:在使用 fs 模块操作文件时,直接提供完整的路径,从而防止路径动态拼接的问题
__dirname 获取文件所处的绝对路径
fs.readFile(__dirname + '/files/1.txt', 'utf8', function(err, data) {
...
})
path 模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。
path.join('','')
const path = require('path')
const fs = require('fs')
// 注意 ../ 会抵消前面的路径
// ./ 会被忽略
const pathStr = path.join('/a', '/b/c', '../../', './d', 'e')
console.log(pathStr) // \a\d\e
fs.readFile(path.join(__dirname, './files/1.txt'), 'utf8', function (err, dataStr) {
if (err) {
return console.log(err.message)
}
console.log(dataStr)
})
使用 path.basename() 方法,可以获取路径中的最后一部分,常通过该方法获取路径中的文件名
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
const path = require('path')
const fpath = '/a/b/c/index.html'
const fext = path.extname(fpath)
console.log(fext) // .html
http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。
const http = require('http')
// 创建 web 服务器实例
const server = http.createServer()
// 为服务器实例绑定 request 事件,监听客户端的请求
server.on('request', function (req, res) {
const url = req.url
const method = req.method
const str = `Your request url is ${url}, and request method is ${method}`
console.log(str)
// 设置 Content-Type 响应头,解决中文乱码的问题
res.setHeader('Content-Type', 'text/html; charset=utf-8')
// 向客户端响应内容
res.end(str)
})
//注:要是报错可能是端口被mysql或其他软件占用,改个端口即可
server.listen(8080, function () {
console.log('server running at http://127.0.0.1:8080')
})
const http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {
const url = req.url
// 设置默认的响应内容为 404 Not found
let content = '404 Not found!
'
// 判断用户请求的是否为 / 或 /index.html 首页
if (url === '/' || url === '/index.html') {
content = '首页
'
}
// 判断用户请求的是否为 /about.html 关于页面
else if (url === '/about.html') {
content = '关于页面
'
}
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(content)
})
server.listen(80, () => {
console.log('server running at http://127.0.0.1')
})
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程,模块是可组合、分解和更换的单元
模块化作用:
模块化规范是对代码进行模块化拆分和组合时需要遵守的规则,如使用何种语法格式引用模块和向外暴露成员
const fs = require('fs')
const custon = require('./custom')
const moment = require('monent')
模块作用域:和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,无法被外界访问,这种模块级别的访问限制叫做模块作用域
模块作用域作用:防止全局变量污染
//自定义模块.js
const age = 20
//向module.exports对象上挂载 属性 / 方法 / 变量
module.exports.username = 'zs'
module.exports.sayHi = function(){
console.log('hi')
}
module.exports.age = age
//使用模块.js
const m = require('./自定义模块')
console.log(m)
输出:
//自定义模块.js
const age = 20
//向exports对象上挂载 属性 / 方法 / 变量
exports.username = 'zs'
exports.sayHi = function(){
console.log('hi')
}
module.exports.age = age
//以module.exports最终指向为准(相当于开辟了一个新对象)
module.exports = {
nickName: '做一只猫',
sayHello(){
console.log('hello')
}
}
//即便这时候再改exports指向不影响
exports = {
nickName: '做一只猫',
sayHello(){
console.log('hello')
}
}
//使用模块.js
const m = require('./自定义模块')
console.log(m)
模块第一次加载后会被缓存,即多次调用 require() 不会导致模块的代码被执行多次,提高模块加载效率。
内置模块加载
内置模块加载优先级最高。
自定义模块加载
加载自定义模块时,路径要以 ./ 或 …/ 开头,否则会作为内置模块或第三方模块加载(会报错)
导入自定义模块时,若省略文件扩展名,则 Node.js 会按顺序尝试加载文件:
第三方模块加载
目录作为模块加载
当把目录作为模块标识符进行加载的时候,有三种加载方式:
例:
1 新建test文件夹,在文件夹中放 a.js , index.js 和 package.json
//a.js中
console.log('调用了a.js')
//index.js中
console.log('调用了index.js')
//package.json中
{
"main": "./a.js"
}
3 新建1.js 并调用
//try.js
require('./test')
4 删去package.json 再次调用1.js
5 删去index.js 再次调用1.js
npm -v
查看npm的版本//在终端中(vs终端也可)@版本号可不加
npm install 完整的包名@版本号
//简写:
npm i 完整的包名
//例:用moment实例化时间
//终端中
npm i moment
//代码区中
const moment = require('moment')
const dt = moment().format('YYYY-MM-DD HH:mm:ss')
console.log(dt)
//这个包停止维护了,但还可以使用
作用: 团队开发中第三方的体积过大,因为联网即可下载第三方包,所以不需要将包也上传到项目中,而只用上传核心文件
创建packge.json
"dependencies": {
"moment": "^2.29.4"
}
一次性安装所有需要的包
npm install //能安装dependencies节点中所有的包
卸载包
npm uninstall 要卸载的包名
deDependencies节点
如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到 devDependencies 节点中
与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies 节点中
可使用如下的命令,将包记录到 devDependencies 节点中:
npm i 包名 -D
//完整写法如下
npm install 包名 --save-dev
解决下载速度慢的问题
为了更方便的切换下包的镜像源,我们可以安装 nrm 这个小工具,利用 nrm 提供的终端命令,可以快速查看和切换下包的镜像源
//将nrm安装为全局可用的工具
npm i nrm -g
//查看所有可用的镜像源
nrm ls
//将下包的镜像源切换为taobao 镜像
nrm use taobao
1. 项目包
那些被安装到项目的 node_modules 目录中的包,都是项目包。
项目包又分为两类,分别是:
npm i 包名 -D //开发依赖包(会被记录到devDependencies节点下)
npm i 包名 //核心依赖包(会被记录到dependencies节点下)
2.全局包
npm i 包名 -g //全局安装指定的包
npm uninstall 包名 -g //卸载全局安装的包
3.i5ting_toc
i5ting_toc 是一个可以把 md 文档转为 html 页面的小工具,使用步骤如下:
//将 i5ting_toc 安装为全局包
npm install -g i5ting_toc
//调用 i5ting_toc 实现md转html的功能
i5ting_toc -f 要转换的md文件路径 -o
一个规范的包,它的组成结构,必须符合以下 3 点要求:
注意: 以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:
https://yarnpkg.com/zh-Hans/docs/package-json
Express中文网
基于 Node.js 平台,快速、开放、极简的 Web 开发框架
Express 是用于快速创建服务器的第三方模块。
安装 Express:
npm install express
//黑马课程学习建议使用@4.17.1
const express = require('express')
// 创建 web 服务器
const app = express()
// 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
// 注意这里是向客户端发送相应了!
app.get('/user', (req, res) => {
//向客户端发送 JSON 对象
res.send({ name: 'zs', age: 20, gender: '男' })
})
app.post('/user', (req, res) => {
//向客户端发送文本内容
res.send('请求成功')
})
//启动web服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
const express = require('express')
// 创建 web 服务器
const app = express()
//查询参数
app.get('/', (req, res) => {
// 通过 req.query 可以获取到客户端发送过来的查询参数
// req.query默认是一个空对象
// 客户端使用 http://127.0.0.1?name=zs&age=20这种查询字符串形式,发送到服务器的参数
// 可通过req.query对象访问到,如:req.query.name
console.log(req.query)
res.send(req.query)
})
// 这里的 :id 是一个动态的参数 id可以是其他任意合法的值
app.get('/user/:ids/:username', (req, res) => {
// req.params 是动态匹配到的 URL 参数,默认是一个空对象
console.log(req.params)
res.send(req.params)
})
//启动web服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
1. .use(express.static())
express 提供了一个非常好用的函数,叫做 express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了:
app.use(express.static('public'))
现在就可以访问public目录中的所有文件了:
http://localhost:3000/images/bg.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/login.js
注意: Express 在指定的静态目录中查找文件,并对外提供资源的访问路径,因此,存放静态文件的目录名不会出现在 URL 中,即public不会出现在上述http://...
的URL中
2. 托管多个静态资源目录
访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件
app.use(express.static('public'))
app.use(express.static('files'))
当public和files中都有index.js,会优先访问public中的
3. 挂载路径前缀
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
app.use('/public, express.static('public'))
现在,你就可以通过带有 /public 前缀地址来访问 public 目录中的文件了:
http://localhost:3000/public/images/kitten.jpg
http://localhost:3000/public/css/style.css
http://localhost:3000/public/js/app.js
作用:
安装nodemon
在终端中,运行如下命令,即可将 nodemon 安装为全局可用的工具:
npm install -g nodemon
使用 nodemon
将 node 命令替换为 nodemon 命令,使用 nodemon app.js 来启动项目。使得代码被修改之后,会被 nodemon 监听到,从而实现自动重启项目的效果。
node 1.js
nodemon 1.js
//若无法使用nodemon,管理员权限打开终端输入:set-ExecutionPolicy RemoteSigned 之后按Y
路由匹配的注意点:
创建路由模块:
// router.js
// 导入express
const express = require('express')
// 创建路由对象
const router = express.Router()
// 挂载具体路由
// /api是统一挂载的访问前缀
router.get('/api/user/list', (req, res) => {
res.send('Get user list.')
})
router.post('/api/user/add', (req, res) => {
res.send('Add new user.')
})
// 向外导出路由对象
module.exports = router
注册路由模块:
const express = require('express')
const router = require('./router')
const app = express()
// 注册路由模块,添加访问前缀
//app.use(expresss.static('./files'))
//app.use()的作用是注册全局中间件 expresss.static和router都是中间件
// '/api', 是统一挂载的访问前缀
app.use('/api', router)
app.listen(80, () => {
console.log('http://127.0.0.1')
})
//包含 req, res, next 三个参数,next() 参数把流转关系交给下一个中间件或路由
const mw = function(req, res, next){
next()
}
中间件注意事项:
在注册路由之前注册中间件(错误级别中间件除外)
中间件可连续调用多个
执行完中间件的业务代码之后,不要忘记调用 next() 函数
为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象
通过 app.use() 定义的中间件为全局中间件
const express = require('express')
const app = express()
//1 常规写法
const mw = function(req, res, next){
console.log('这是一个中间件')
next()
}
app.use(mw)
//2 简化形式
app.use((req, res, next) => {
console.log('这是一个中间件')
next()
})
app.use((req, res, next) => {
req.startTime = Date.now()
next()
})
app.get('/',(req, res) =>{
res.send('hi' + startTime)
})
app.get('/',(req, res) =>{
res.send('hello' + startTime)
})
//定义第一个全局中间件
app.use((req, res, next) => {
console.log('调用了第1个全局中间件')
next()
})
// 定义第二个全局中间件
app.use((req, res, next) => {
console.log('调用了第2个全局中间件')
next()
})
app.get('/user', (req, res) => {
res.send('User page.')
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
不使用 app.use() 定义的中间件,叫做局部生效的中间件
const express = require('express')
const app = express()
// 定义中间件函数
const mw1 = (req, res, next) => {
console.log('调用了第一个局部生效的中间件')
next()
}
const mw2 = (req, res, next) => {
console.log('调用了第二个局部生效的中间件')
next()
}
//1. 调用一个中间件
app.get('/one', mw1, (req, res) => res.send('one page.'))
// 2. 调用多个中间件的两种方式
app.get('/hello', mw2, mw1, (req, res) => res.send('hello page.'))
app.get('/about', [mw1, mw2], (req, res) => res.send('about page.'))
app.get('/user', (req, res) => res.send('User page.'))
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
1.应用级别的中间件
通过 app.use() 或 app.get() 或 app.post() ,绑定到 app 实例上的中间件
2.路由级别的中间件
const app = express()
const router = express.Router()
router.use(function (req, res, next) {
console.log(1)
next()
})
app.use('/', router)
3.错误级别的中间件
const express = require('express')
const app = express()
app.get('/', (req, res) => {
throw new Error('服务器内部发生了错误!')
res.send('Home page.')
})
// 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err, req, res, next) => {
console.log('发生了错误!' + err.message)
res.send('Error:' + err.message)
})
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
4.Express 内置中间件
自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
演示express.json 和 express.urlencoded
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())
// 通过 express.urlencoded() 这个中间件,来解析 表单中的 url-encoded 格式的数据
app.use(express.urlencoded({ extended: false }))
app.post('/user', (req, res) => {
// 在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined
console.log(req.body)
res.send('ok')
})
app.post('/book', (req, res) => {
// 在服务器端,可以通过 req,body 来获取 JSON 格式的表单数据和 url-encoded 格式的数据
console.log(req.body)
res.send('ok')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
5.第三方中间件
演示body-parser,仅作为演示作用,该中间件和之前express.json 和 express.urlencoded的用法没区别
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 1. 导入解析表单数据的中间件 body-parser
const parser = require('body-parser')
// 2. 使用 app.use() 注册中间件
app.use(parser.urlencoded({ extended: false }))
// app.use(express.urlencoded({ extended: false }))
app.post('/user', (req, res) => {
// 如果没有配置任何解析表单数据的中间件,则 req.body 默认等于 undefined
console.log(req.body)
res.send('ok')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
5.第三方中间件
注意:querystring 模块已被弃用,可改用JSON.parse
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 导入 Node.js 内置的 querystring 模块
const qs = require('querystring')
// 这是解析表单数据的中间件
app.use((req, res, next) => {
// 定义中间件具体的业务逻辑
// 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
// 2. 监听 req 的 data 事件
req.on('data', (chunk) => {
str += chunk
})
// 3. 监听 req 的 end 事件
req.on('end', () => {
// 在 str 中存放的是完整的请求体数据
// console.log(str)
// TODO: 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
req.body = body
next()
})
})
app.post('/user', (req, res) => {
res.send(req.body)
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
编写接口
//exp.js
const express = require('express')
const router = express.Router()
// 在这里挂载对应的路由
router.get('/get', (req, res) => {
// 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据
const query = req.query
// 调用 res.send() 方法,向客户端响应处理的结果
res.send({
status: 0, // 0 表示处理成功,1 表示处理失败
msg: 'GET 请求成功!', // 状态的描述
data: query, // 需要响应给客户端的数据
})
})
// 定义 POST 接口
router.post('/post', (req, res) => {
// 通过 req.body 获取请求体中包含的 url-encoded 格式的数据
const body = req.body
// 调用 res.send() 方法,向客户端响应结果
res.send({
status: 0,
msg: 'POST 请求成功!',
data: body,
})
})
// 定义 DELETE 接口(用于验证后面发送两次请求,即OPTION 请求和正常请求)
router.delete('/delete', (req, res) => {
res.send({
status: 0,
msg: 'DELETE请求成功',
})
})
module.exports = router
使用接口
//use.js
// 导入 express
const express = require('express')
// 创建服务器实例
const app = express()
// 导入路由模块
const router = require('./exp.js')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
CORS 中间件解决跨域
CORS
// 导入 express
const express = require('express')
// 创建服务器实例
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())
// 导入路由模块
const router = require('./exp.js')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
CORS 常见响应头
//允许百度访问
res.setHeader('Access-Control-Allow-Origin', 'http://www.baidu.com')
//允许所有URL访问
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')
res.setHEader('Access-Control-Allow-Methods', '*')
CORS 请求分类
简单请求
预检请求
*编写JSON接口
// 导入 express
const express = require('express')
// 创建服务器实例
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
// 必须在配置 cors 中间件之前,配置 JSONP 的接口
app.get('/api/jsonp', (req, res) => {
// TODO: 定义 JSONP 接口具体的实现过程
// 1. 得到函数的名称
const funcName = req.query.callback
// 2. 定义要发送到客户端的数据对象
const data = { name: 'zs', age: 22 }
// 3. 拼接出一个函数的调用
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4. 把拼接的字符串,响应给客户端
res.send(scriptStr)
})
// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())
// 导入路由模块
const router = require('./16.apiRouter')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
配置 mysql 模块
npm install mysql
const mysql = require('mysql')
const db = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'test',
})
db.query('select 1', (err, results) => {
if (err) return console.log(err.message)
console.log(results)
})
db.query('select * from users', (err, results) => {
if (err) return console.log(err.message)
console.log(results)
})
const user = {username: '做一只猫', password:'111111'}
// ? 表示占位符
const sql = 'insert into users values(?, ?)'
// 使用数组的形式为占位符指定具体的值
db.query(sql, [user.username, user.password], (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('插入成功')
})
const user = { username: '做一只猫', password: '111111' }
const sql = 'insert into users set ?'
db.query(sql, user, (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('插入成功')
})
const user = { username: '做一只猫', password: '111111' }
const sql = 'update users set username=?, password=? where id=?'
db.query(sql, [user.username, user.password, user.id], (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('更新数据成功')
})
const user = {id: 4, username: '做一只猫', password: '111111' }
const sql = 'update users set ? where id=?'
db.query(sql, [user, user.id], (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('更新数据成功')
})
const sql = 'delete from users where id=?'
//4代表user.id
//若SQL语句中有多个占位符,则必须使用数组为每个占位符指定具体的值
//若SQL语句中只有一个占位符,则可以省略
db.query(sql, 4, (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('删除数据成功')
})
db.query('update users set status=1 where id=?', 7, (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('删除数据成功')
})
服务端渲染
服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接动态生成的。因此客户端不需要使用 Ajax 额外请求页面的数据。
app.get('/index.html', (req, res) => {
const user = { name: 'Bruce', age: 29 }
const html = `username:
${user.name}, age:${user.age}`
res.send(html)
})
前后端分离
前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。后端只负责提供 API 接口,前端使用 Ajax 调用接口。
缺点:
不利于 SEO。完整的 HTML 页面在浏览器拼接完成,因此爬虫无法爬取页面的有效信息。
Vue、React 等框架的 SSR(server side render)技术能解决 SEO 问题。
如何选择
Session认证机制
JWT认证机制
服务端渲染推荐使用 Session 认证机制
会员卡例子详解:
1. HTTP 协议的无状态性
对于超市来说,为了方便收银员在进行结算时给 VIP 用户打折,超市可以为每个 VIP 用户发放会员卡
注意: 现实生活中的会员卡身份认证方式,在 Web 开发中的专业术语叫做 Cookie。
3. 关于 Cookie
4. Cookie 在身份认证中的作用
5. Cookie 不具有安全性
6. 提高身份认证的安全性
这种 “会员卡 + 刷卡认证” 的设计理念,就是 Session 认证机制的精髓。
安装 express-session 中间件
npm install express-session
配置中间件
const session = require('express-session')
app.use(
session({
secret: '做一只猫', // secret 属性的值为任意字符串
resave: false, //固定写法
saveUninitalized: true //固定写法
})
)
向 session 中存数据
中间件配置成功后,可通过 req.session 访问 session 对象,存储用户信息
app.post('/api/login', (req, res) => {
//判断用户提交的登录信息是否正确
if(req.body.username !== 'admin' || req.body.password !== '000000'){
return res.send({status: 1, msg: '登录失败'})
}
req.session.user = req.body //将用户的信息存储到S
req.session.isLogin = true
res.send({ status: 0, msg: 'login done' })
})
从 session 取数据
app.get('/api/username', (req, res) => {
//判断用户是否登录
if (!req.session.isLogin) {
return res.send({ status: 1, msg: 'fail' })
}
res.send({ status: 0, msg: 'success', username: req.session.user.username })
})
清空 session
app.post('/api/logout', (req, res) => {
// 清空当前客户端的session信息
req.session.destroy()
res.send({ status: 0, msg: '退出登录成功' })
})
注意:
JWT 的工作原理
用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份
JWT 组成部分:
Header、Payload、Signature
Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTcsInVzZXJuYW1lIjoiQnJ1Y2UiLCJwYXNzd29yZCI6IiIsIm5pY2tuYW1lIjoiaGVsbG8iLCJlbWFpbCI6InNjdXRAcXEuY29tIiwidXNlcl9waWMiOiIiLCJpYXQiOjE2NDE4NjU3MzEsImV4cCI6MTY0MTkwMTczMX0.bmqzAkNSZgD8IZxRGGyVlVwGl7EGMtWitvjGD-a5U5c
JWT 使用方式:
Authorization: Bearer <token>
安装
//安装多个包中间用空格隔开
npm install jsonwebtoken express-jwt
定义 secret 密钥
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
// 密钥可以为任意字符串
const secretKey = '做一只猫No.1'
生成 JWT 字符串
app.post('/api/login', (req, res) => {
...
res.send({
status: 200,
message: '登录成功',
// jwt.sign() 生成 JWT 字符串
// 参数:用户信息对象、加密密钥、配置对象-token有效期
// 尽量不保存敏感信息,因此只有用户名,没有密码
token: jwt.sign({username: userInfo.username}, secretKey, {expiresIn: '10h'})
})
})
JWT 字符串还原为 JSON 对象
// unless({ path: [/^\/api\//] }) 指定哪些接口无需访问权限
app.use(expressJWT({
secret: secretKey,
//6.0版本之后要加上这个
algorithms: [‘HS256’]
}).unless({ path: [/^\/api\//] }))
获取用户信息
app.get('/admin/getinfo', (req, res) => {
console.log(req.user)
res.send({
status: 200,
message: '获取信息成功',
//data: req.user
//6.0版本后改用req.auth
data: req.auth
})
})
捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
return res.send({ status: 401, message: 'Invalid token' })
}
res.send({ status: 500, message: 'Unknown error' })
})
注: 本篇博客不包含时钟案例(P11-P13),时钟web案例(P18),大事件项目(P77-P96),仅包含基础知识内容,供搜索备忘。