Node.js-1

Node.js 简介

定义Node.js 是一个跨平台 JavaScript 运行环境,使开发者可以搭建服务器端的 JavaScript 应用程序

为什么 Node.js 能执行 JS 代码

  • Chrome 浏览器能执行 JS 代码,依靠的是内核中的 V8引擎(即:C++程序)

Node.js-1_第1张图片

  • Node.js 是基于 Chrome V8 引擎进行封装的
  • 注意:Node.js 环境没有 DOM 和 BOM 等内容,但是它也有自己独立的 API

模块化

基本认知

Node.js 中根据模块来源不同,将模块分为了3大类,分别是:

  • 内置模块(内置模块是由 Node.js 官方提供的,例如fs、path、http等)
  • 自定义模块(用户创建的每一个 .js 文件,都是自定义模块)
  • 第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要提前下载)

加载模块

使用强大的 require() 方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用(注意:使用 require() 方法加载其他模块时,会执行被加载模块中的代码)

// 加载内置模块
const fs = require('fs')

// 加载用户自定义的模块
const myModule = require('./myModule.js')

// 加载第三方模块
const moment = require('moment')

module 对象

  • module 对象

每个自定义的模块中都有一个 module 对象,它里面存储了和当前模块有关的信息(打印结果,如下所示)

Module {
  id: '.',
  path: 'E:\\Desktop\\jsProject',
  exports: {},
  filename: 'E:\\Desktop\\jsProject\\test.js',
  loaded: false,
  children: [],
  paths: [
    'E:\\Desktop\\jsProject\\node_modules',
    'E:\\Desktop\\node_modules',
    'E:\\node_modules'
  ]
}
  • module.exports 对象
在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用

外界用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象
【myModule.js文件】

module.exports.username = 'Jack'
module.exports.sayHello = () => { console.log('Hello') }
【text.js文件】

const m = require('./myModule')
console.log(m)	// { username: 'Jack', sayHello: [Function (anonymous)] }

模块化规范

Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性和各模块之间如何相互依赖

CommonJS 规定:

  • 每个模块内部,module 变量代表当前模块
  • module 变量是一个对象,它的 exports 属性(即:module.exports)是对外的接口
  • 加载某个模块,其实是加载该模块的 module.exports 属性。require() 方法用于加载模块

npm与包

基本介绍

  • Node.js 中的第三方模块又叫做包
  • npm 公司提供了一个包管理工具,名字叫做 Node Package Manager(简称 npm 包管理工具),这个工具随着 Node.js 的安装被一起安装到了用户的电脑上
  • npm 包管理工具能够从 https://registry.npmjs.org/ 服务器把用户需要的包(第三方模块)下载到本地使用
  • 可以在终端执行 npm -v 命令,来查看自己电脑上所安装的 npm 包管理工具的版本号
  • 如果有需要的话,可以从"全球最大的包共享平台"搜索自己想要的包:https://www.npmjs.com/

npm 体验

目标:通过npm包管理工具,下载 moment 包,并输出格式化时间

  • 在终端输入npm命令,下载 moment 包
npm install moment
  • 简写
npm i moment
  • 指定版本号(不指定版本号,默认安装最新版本的包)
npm i [email protected]
  • 输出格式化时间(详细操作,自行查阅官方文档)
// 1. 导入 moment 包
const moment = require('moment')

// 2. 使用包里面的方法
const dt = moment.format('YYYY-MM-DD HH:mm:ss')
console.log(dt)	// 输出格式化时间

注意事项

  • 初次装包完成后,在项目文件夹下多一个叫做 node_modules 的文件夹和 package-lock.json 的配置文件

  • node_modules 文件夹用来存放所有已安装到项目中的包。require() 导入第三方包时,就是从这个目录中查找并加载包

  • package-lock.json 配置文件用来记录 node_modules 目录下的每一个包的下载信息,例如包的名字、版本号、下载地址

  • 注意:不要手动修改 node_modules 或 package-lock.json 文件中的任何代码,npm 包管理工具会自动维护它们

  • npm i [email protected] 中的版本号由三位数字组成

每一位数字所代表的含义:
    第一位数字:大版本(例如:底层重构)
    第二位数字:功能版本(例如:新功能的推出)
    第三位数字:Bug修复版本(例如:修复某个Bug)

版本号提升的规则:
	只要前面的版本号增长了,后面的版本号"归零"

包管理配置文件

基本认知

npm 规定,在项目根目录中,必须提供一个叫做 package.json 的包管理配置文件。用来记录与项目有关的一些配置信息,例如:
	- 项目的名称、版本号、描述等
	- 项目中都用到了哪些包
	- 哪些包只在开发期间会用到
	- 哪些包在开发和部署时都需要用到
问题:
	在多人协作时,第三方包的体积过大,不方便团队成员之间共享项目源代码

解决方案:
	共享时剔除 node_modules 目录,让其他成员根据 package.json 配置文件,自行下载第三方模块(注意:今后在项目开发中,一定要把 node_modules 文件夹,添加到 .gitignore 忽略文件中)

快速创建

  • npm 包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建 package.json 配置文件
  • 该命令只能在英文的目录下成功运行
  • 当我们后续运行 npm install 命令去安装其他的包时,npm 包管理工具会自动更新配置文件的信息,我们不需要手动修改它
npm init -y

一次性安装所有包

  • 执行 npm install 命令时,npm 包管理工具会先自动读取 package.json 配置文件中的 dependencies 节点
  • 读取到记录的所有依赖包名称和版本号之后,npm 包管理工具会把这些包一次性下载到项目中
npm install

卸载包

  • 命令执行成功后,会把卸载的包,自动从 package.json 的 dependencies 中移除掉
npm uninstall 包的名称

devDependencies 节点

  • 如果某些包只在项目开发阶段用到,在项目上线之后不会使用,建议把这些包记录到 devDependencies 节点中
npm i 包的名称 -D

或者

npm i 包的名称 --save-dev

npm 镜像源

# 查看当前的下包镜像源
npm config get registry

# 将下包的镜像源切换成淘宝镜像源
npm config set registry https://registry.npmmirror.com

# 检查镜像源是否下载成功
npm config get registry

Node.js 内置模块

fs 文件系统模块

// 1. 导入 fs 模块对象
const fs = require('fs')

// 2. 写入文件
//      参数1:要写入内容的文件的存放路径
//      参数2:表示要写入的内容
//      参数3:写入的格式(默认utf8)
//      参数3:回调函数
const info = 'Hello World'
fs.writeFile('./test.txt', info, 'utf8', function (err) {
    if (err) console.log(`错误信息${err}`)
    else console.log(`写入数据成功(数据信息:${info}`)

    // 3. 读取文件
    //      参数1:读取文件的存放路径
    //      参数2:读取文件时候采用的编码格式(默认utf8)
    //      参数3:回调函数,拿到读取失败和成功的结果
    fs.readFile('./test.txt', 'utf8', (err, data) => {
        if (err) console.log(`错误信息${err}`)
        else console.log(`读取数据成功(数据信息:${data.toString()}`) // data 是 buffer 16 进制数据流对象,需要转换为字符串格式
    })
})

path 路径模块

  • 获取当前文件所处的目录
// __dirname 表示获取当前文件所处的目录(文件夹)
console.log(__dirname)
  • 路径拼接
// 导入 path 模块
const path = require('path')

// 注意: ../会抵消前面的路径
const pathStr = path.join('/a', '/b/c', '../', '/d', '/e')
console.log(pathStr)    // 输出路径为:\a\b\d\e
  • 获取文件名、扩展名
// 导入 path 模块
const path = require('path')

// 定义文件的"存放路径"
const filePath = '/a/b/c/index.html'

// 从"存放路径"中获取文件名(包含扩展名部分)
const fileName1 = path.basename(filePath)
console.log(fileName1)   // 输出结果:index.html

// 从"存放路径"中获取文件名(不包含扩展名部分)
const fileName2 = path.basename(filePath, '.html')
console.log(fileName2)   // 输出结果:index

// 从"存放路径"中获取文件扩展名
const extName = path.extname(filePath)
console.log(extName)    // 输出结果:.html

http 模块

// 1. 导入 http 模块
const http = require('http')
const { resolve } = require('path')

// 2. 创建 web 服务器实例
const server = http.createServer()

// 3. 为服务器实例绑定 request 事件
// req 是请求对象,包含了与客户端相关的数据和属性
// res 是响应对象
// res.end() 方法用于向客户端发送指定的内容,并结束本次请求的处理过程
server.on('request', (req, res) => {
    // req.url 是客户端请求的 URL 地址
    const url = req.url
    // req.method 是客户端请求的 method 类型
    const method = req.method
    // 为了防止中文乱码的问题,需要设置响应头
    res.setHeader('Content-Type', 'text/html; charset=utf-8')
    // res.end() 方法
    res.end(`

您的请求方式为:${method} 请求

`
) }) // 4. 启动服务器 server.listen(80, () => { console.log('server running at http://127.0.0.1:80') })

第三方 API 模块

express

基本介绍

  • Express 是基于 Node.js 平台的 Web 开发框架,专门用来创建 Web 服务器
  • Express 本质上就是一个第三方模块,相较与 Node.js 内置的 http 模块,它使用起来更简单,开发效率更高(Express 是基于 http 模块进一步封装出来的)
  • Express 中文官网:https://www.expressjs.com.cn/

模块使用

  • 安装
npm i [email protected]
  • 创建 Web 服务器
// 1. 导入 express
const express = require('express')

// 2. 创建 web 服务器
const app = express()

// 4. 监听客户端的 GET 和 POST 请求
// GET      app.get('请求URL', (请求对象, 响应对象) => { 处理函数 })
// POST     app.post('请求URL', (请求对象, 响应对象) => { 处理函数 })
app.get('/user', (req, res) => {
    res.send({ name: 'Jack', age: 20, gender: '男' })    // 响应一个 JSON 对象
})

app.post('/user', (req, res) => {
    res.send('订单提交成功,请等待商家发货')    // 响应
})

// 5. 获取URL携带的查询参数
app.get('/', (req, res) => {
    // 默认情况下,req.query 是一个空对象
    // 查询参数 ?name=Jack&age=20
    // 可通过 req.query.name 与 req.query.age 获得其值
    console.log(req.query)
    res.send(req.query)
})

// 6. 获取URL中的动态参数
app.get('/user/:id/:name', (req, res) => {
    // req.params 默认是一个空对象,里面存放着,通过冒号识别出来的参数值
    res.send(req.params)
})

// 3. 调用 app.listen(端口号, 启动成功后的回调函数)
app.listen(80, () => {
    console.log('Server started successfully at http://127.0.0.1/')
})

托管静态资源

  • 认识 express.static()
介绍:
    express 提供了一个非常好用的函数,叫做 express.static()
    通过它,我们可以非常方便地创建一个静态资源服务器

例如:
	项目目录下有一个 public 文件夹,里面存放了图片、html、css、js文件,通过下面的代码就可以将它们对外开放访问了

注意:
	express 在指定的静态文件中查找文件,并对外提供资源的访问路径,因此,存放静态文件的目录名(本例中为public)是不会出现在URL中的
    http://127.0.0.1/images/img0.jpg
    http://127.0.0.1/html/index.html
    http://127.0.0.1/css/index.css
    http://127.0.0.1/js/index.js
app.use(express.static('public'))
  • 托管多个静态资源目录
app.use(express.static('public'))
app.use(express.static('files'))
......
  • 挂载路径前缀
如果希望在托管的静态资源访问路径之前,挂载路径前缀,可以使用下面的代码
	http://127.0.0.1/public/images/img0.jpg
    http://127.0.0.1/public/html/index.html
    http://127.0.0.1/public/css/index.css
    http://127.0.0.1/public/js/index.js
app.use('public', express.static('public'))

nodemon

  • 为什么要使用 nodemon
在编写调试 Node.js 项目的时候,如果修改了项目的代码,则需要频繁的手动 close 掉,然后再重新启动,非常繁琐

为了解决这个问题,我们可以使用 nodemon 这个工具,它能够监听项目文件的变动,当代码修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试
  • 安装 nodemon
在终端中运行下面命令,即可将 nodemon 安装为全局可用的工具

npm install -g nodemon
  • 使用 nodemon
nodemon 要执行的js文件的路径
例如:nodemon E:\Desktop\jsProject\test.js

路由

直接挂载路由

const express = require('express')
const app = express()

// 直接在app上挂载路由
app.get('/', (req, res) => { res.send('this is get') })
app.post('/', (req, res) => { res.send('this is post') })

app.listen(80, () => {
    console.log('Server is running at http://127.0.0.1/')
})

模块化的方式,挂载路由,实现解耦合

  • 文件名:routerModule
// 这是路由模块

// 1. 导入 express 模块
const express = require('express')

// 2. 创建路由对象
const router = express.Router()

// 3. 挂载具体的路由
router.get('/list', (req, res) => { res.send('Get user list.') })
router.post('/add', (req, res) => { res.send('Add new user.') })

// 4. 向外导出路由对象
module.exports = router
  • 文件名:myApplication
// 这是 app (application-应用)

// 1. 导入 express 模块
const express = require('express')

// 2. 创建应用
const app = express()

// 4. 导入路由模块
const router = require('./routerModule')

// 5. "注册"路由模块
app.use('/user', router)
// app.use(express.static('public'))
// 注意:app.use() 函数的作用,就是用来"注册"全局中间件

// 3. 运行服务器,监听应用的80端口
app.listen(80, () => { console.log('Server is running at http://127.0.0.1/') })

中间键

  • 基本认识
// 1. 导入 express 模块
const express = require('express')

// 2. 创建应用
const app = express()

// 4. 定义一个中间件函数
const mw = (req, res, next) => {
    console.log('这是最简单的中间件函数')
    next()      // 把流转关系,转交给下一个中间件或路由
}

// 5. 将 mw 注册为全局生效的中间件
// 当注册成功后,用户每次请求服务时,都会先执行中间键函数里面的内容,再执行路由函数里面的内容
app.use(mw)

// 6. 创建路由
app.get('/', (req, res) => { res.send('home page') })
app.get('/user', (req, res) => { res.send('user page') })

// 3. 运行服务器,监听应用的80端口
app.listen(80, () => { console.log('Server is running at http://127.0.0.1/') })
  • 体验它的作用
// 1. 导入 express 模块
const express = require('express')

// 2. 创建应用
const app = express()

// 4. 注册全局中间件
app.use((req, res, next) => {
    // 获取请求到达服务器的时间
    const time = Date.now()
    // 为req对象,挂载自定义属性,从而把时间共享给后面的所有路由
    req.startTime = time
    next()
})

// 5. 创建路由
app.get('/', (req, res) => { res.send('home page ' + req.startTime) })
app.get('/user', (req, res) => { res.send('user page ' + req.startTime) })

// 3. 运行服务器,监听应用的80端口
app.listen(80, () => { console.log('Server is running at http://127.0.0.1/') })
  • 定义多个全局中间件
// 1. 导入 express 模块
const express = require('express')

// 2. 创建应用
const app = express()

// 4. 注册全局中间件
// 4.1 注册第一个中间件
app.use((req, res, next) => {
    console.log('调用了第1个中间件')
    next()
})

// 4.2 注册第二个中间件
app.use((req, res, next) => {
    console.log('调用了第2个中间件')
    next()
})

// 4.3 注册第三个中间件
app.use((req, res, next) => {
    console.log('调用了第3个中间件')
    next()
})

// 5. 创建路由
app.get('/user', (req, res) => { res.send('user page') })

// 3. 运行服务器,监听应用的80端口
app.listen(80, () => { console.log('Server is running at http://127.0.0.1/') })
  • 局部生效的中间件
// 1. 导入 express 模块
const express = require('express')

// 2. 创建应用
const app = express()

// 4. 定义一个中间件函数
const mw = (req, res, next) => {
    console.log('调用了局部生效的中间件')
    next()
}

// 5. 创建路由
app.get('/', mw, (req, res) => { res.send('home page') })  // 使用了局部中间件
app.get('/user', (req, res) => { res.send('user page') })   // 没有使用局部中间件

// 3. 运行服务器,监听应用的80端口
app.listen(80, () => { console.log('Server is running at http://127.0.0.1/') })
  • 定义多个局部中间件
// 以下两种写法是等效的
app.get('/', mw1, mw2, (req, res)=>{ res.send('home page') })
app.get('/', [mw1, mw2], (req, res)=>{ res.send('home page') })
  • 错误级别的中间件
// 1. 导入 express 模块
const express = require('express')

// 2. 创建应用
const app = express()

// 4. 定义路由
app.get('/', (req, res) => {
    throw new Error('The Error made by People.')     // 人为制造错误
    res.send('home page')   // 此处无法执行
})

// 5. 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err, req, res, next) => {
    console.log('警告!服务器发生了错误。\n错误信息:' + err.message)
    res.send('尊敬的用户,很抱歉,服务器出错了~')
})	// 错误级别的中间件一定要放在所有路由之后

// 3. 运行服务器,监听应用的80端口
app.listen(80, () => { console.log('Server is running at http://127.0.0.1/') })
  • 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+ 版本可用)
// 1. 导入 express 模块
const express = require('express')

// 2. 创建应用
const app = express()

// 4.1 通过express.json()这个中间件,解析表单中的JSON
app.use(express.json())
// 4.2 通过express.urlencoded()这个中间件,来解析表单中的url-encoded格式的数据
app.use(express.urlencoded({ extended: false }))

// 5. 创建路由
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')
})

// 3. 运行服务器,监听应用的80端口
app.listen(80, () => { console.log('Server is running at http://127.0.0.1/') })

跨域访问

  • 下载并安装 cors 中间件
npm i cors@2.8.5
  • 使用 cors 中间件
// 导入 cors 中间件
const cors = require('cors')
// 将 cors 注册为全局中间件
app.use(cors())

跨域资源共享

点击链接:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS

mysql

基本内容

  • 安装
npm install mysql
  • 配置
// 1. 导入 mysql 模块
const mysql = require('mysql')

// 2. 建立与 MySQL 数据库的连接
const db = mysql.createPool({
    host: '127.0.0.1',      // 数据库的IP地址
    user: 'root',           // 登录数据库的账号
    password: 'admin123',   // 登录数据库的密码
    database: 'myDatabase'  // 指定要操作哪个数据库
})
  • 测试
// 检测 mysql 模块能否正常工作
db.query('select 1', (err, results) => {
    if (err) return console.log(err.message)    // mysql 模块工作期间报错了
    console.log(results)    // 能够成功的执行SQL语句
})

查询与插入数据

  • 查询数据
// 1. 导入 mysql 模块
const mysql = require('mysql')

// 2. 建立与 MySQL 数据库的连接
const db = mysql.createPool({
    host: '127.0.0.1',      // 数据库的IP地址
    user: 'root',           // 登录数据库的账号
    password: 'admin123',   // 登录数据库的密码
    database: 'myDatabase'  // 指定要操作哪个数据库
})

// 3. 查询 users 表中所有的数据
const sqlStr = 'select * from users'
db.query(sqlStr, (err, results) => {
    // 查询数据失败
    if (err) return console.log(err.message)
    // 查询数据成功
    console.log(results)
})
  • 插入数据
// 1. 导入 mysql 模块
const mysql = require('mysql')

// 2. 建立与 MySQL 数据库的连接
const db = mysql.createPool({
    host: '127.0.0.1',      // 数据库的IP地址
    user: 'root',           // 登录数据库的账号
    password: 'admin123',   // 登录数据库的密码
    database: 'myDatabase'  // 指定要操作哪个数据库
})

// 3. 向 users 表中插入数据
const user = { username: 'Jack', password: '123456' }
const sqlStr = 'insert into users set ?'
db.query(sqlStr, user, (err, results) => {
    if (err) return console.log(err.message)    // 执行SQL语句失败了
    if (results.affectedRows === 1) { console.log('插入数据成功!') }
})

更新与删除数据

  • 更新数据
// 1. 导入 mysql 模块
const mysql = require('mysql')

// 2. 建立与 MySQL 数据库的连接
const db = mysql.createPool({
    host: '127.0.0.1',      // 数据库的IP地址
    user: 'root',           // 登录数据库的账号
    password: 'admin123',   // 登录数据库的密码
    database: 'myDatabase'  // 指定要操作哪个数据库
})

// 3. 更新 users 表中数据
const user = { id: 6, username: 'Jack', password: '123456' }
const sqlStr = 'update users set ? where id=?'
db.query(sqlStr, [user, user.id], (err, results) => {
    if (err) return console.log(err.message)    // 执行SQL语句失败了
    if (results.affectedRows === 1) { console.log('更新数据成功!') }
})
  • 删除数据
// 1. 导入 mysql 模块
const mysql = require('mysql')

// 2. 建立与 MySQL 数据库的连接
const db = mysql.createPool({
    host: '127.0.0.1',      // 数据库的IP地址
    user: 'root',           // 登录数据库的账号
    password: 'admin123',   // 登录数据库的密码
    database: 'myDatabase'  // 指定要操作哪个数据库
})

// 3. 删除 users 表中的数据(删除id=5的用户数据)
const sqlStr = 'delete from users where id=?'
db.query(sqlStr, 5, (err, results) => {
    if (err) return console.log(err.message)    // 执行SQL语句失败了
    if (results.affectedRows === 1) { console.log('删除数据成功!') }
})
标记删除法:(扩展)
	使用 delete 语句,是真的会把数据从表中抹除掉。为了保险起见,推荐使用标记删除法,来模拟删除的动作。
	什么是标记删除法呢?就是在创建表的时候,提前建立好一个 status 状态字段,用来表示当前这条数据是否被删除掉,当我们使用标记删除法后,哪怕用户删除数据了,有一天用户需要找回来,也能找到这条数据。

Web 开发模式

目前主流的开发模式有两种

  • 基于服务端渲染的传统 Web 开发模式
  • 基于前后端分离的新型 Web 开发模式

服务端渲染

概念

服务端发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用Ajax这样的技术额外请求页面的数据。

优点

  • 前端耗时少(因为服务器端负责动态生成HTML内容,浏览器只需要直接渲染即可,尤其是移动端,更省电)
  • 有利于SEO(因为服务器端响应的是完整的HTML页面内容,所以爬虫更容易获取信息,更有利于SEO)

缺点

  • 占用服务器资源(服务器端完成HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力)
  • 不利于前后端分离,开发效率低(使用服务器端渲染,则无法分工合作,尤其对于前端项目复杂度高的项目,不利于项目高效开发)

前后端分离

概念

前后端分离的开发模式,依赖于Ajax技术的广泛应用。简而言之,前后端分离的Web开发模式,就是后端只负责提供API接口,前端使用Ajax调用接口的开发模式。

优点

  • 开发体验好。(前端专注于 UI 页面的开发,后端专注于 API 的开发,而且前端有更多的选择性)
  • 用户体验好。(Ajax 技术的广泛应用,极大的提高了用户的体验 ,可以轻松实现页面的局部刷新)
  • 减轻了服务器的压力(页面最终是在每个用户的浏览器中生成的)

缺点

  • 不利于 SEO (因为完整的HTML页面需要在客户端动态拼接完成,所以爬虫难以爬取页面的有效信息)
解决方案:
	利用 Vue、React 等前端框架的 SSR (server side render) 技术能够很好的解决 SEO 问题

身份认证

基本认识

介绍:
	身份认证(Authoritarian)又称"身份验证"、"鉴权",它是指通过一定的手段,完成对用户身份的确认

例如:
	手机验证码登录、邮箱密码登录、二维码登录等
不同开发模式下的身份认证
	1.服务端渲染推荐使用 Session 认证机制
	2.前后端分离推荐使用 JWT 认证机制

Session 认证机制

Cookie

  • HTTP 无状态性质
HTTP 协议的无状态性质,指的是客户端每次的 HTTP 请求都是独立的,连续多个请求之前没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态
  • 突破 HTTP 无状态的限制
使用 Cookie 可以弥补 HTTP 无法记录状态的问题
  • 什么是 Cookie
Cookie 是存储在用户浏览器中的一段不超过 4KB 的字符串
它由一个名称(Name)、一个值(value)和其他几个用于控罪 Cookie 有效期、安全性、使用范围的可选属性组成
不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器
  • Cookie 的几大特性
1.自动发送
2.域名独立
3.过期时限
4.4KB限制
  • Cookie 在身份认证中的作用
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给服务器,服务器即可验证客户端的身份
  • Cookie 不具有安全性
由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器

Session

  • Session = Cookie + 认证
  • Session 的工作原理

Node.js-1_第2张图片

实现 Session 认证

  • 安装 express-session 中间件
npm install express-session
  • 配置 express-session 中间件
// 1. 导入 session 中间件
const session = require('express-session')

// 2. 配置 Session 中间件
app.use(session({
    secret: 'keyboard cat',     // secret 属性的值可以为任意字符串
    resave: false,              // 固定写法
    saveUninitialized: true     // 固定写法
}))
  • session 操作
当 express-session 中间件配置成功后,即可通过 req.session 来访问和使用 session 对象,从而存储用户的关键信息
// 1. 创建服务器实例
const express = require('express')
const app = express()

// 3. 配置 express-session 中间件
const session = require('express-session')
app.use(session({
    secret: 'keyboard cat',     // secret 属性的值可以为任意字符串
    resave: false,              // 固定写法
    saveUninitialized: true     // 固定写法
}))

// 4. 创建路由

// 4.1 登录接口
app.post('api/login', (req, res) => {
    // 判断用户提交的登录信息是否正确
    if (req.body.username !== 'admin' || req.body.password !== '123456') {
        return res.send({ status: 1, msg: '登录失败' })
    }
    req.session.user = req.body     // 将用户的信息,存储到 Session 中
    req.session.isLogin = true      // 将用户的登录状态,存储到 Session 中
    req.send({ status: 0, msg: '登录成功' })
})

// 4.2 获取用户姓名的接口
app.get('/api/username', (req, res) => {
    // 从 Session 中获取用户的名称,响应给客户端
    if (!req.session.isLogin) {
        return res.send({ status: 1, msg: 'fail' })
    }
    res.send({
        status: 0,
        msg: 'success',
        username: req.session.user.username
    })
})

// 4.3 退出登录的接口
app.post('/api/logout', (req, res) => {
    // 清空当前客户端对应的 Session 信息
    req.session.destory()
    res.send({
        status: 0,
        msg: '退出登录成功'
    })
})

// 2. 启动服务器
app.listen(80, (req, res) => {
    console.log('Server is running at http://127.0.0.1/')
})
  • Session 认证的局限性
Session 认证机制需要配合 Cookie 才能实现。
由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口时,需要做很多额外配置,才能实现跨域 Session 认证
当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制
当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制

JWT 认证机制

简介

  • JWT(JSON Web Token)是目前最流行的跨域认证解决方案
  • 工作原理

Node.js-1_第3张图片

  • **JWT **通常由三部分组成
Header		(头部)
Payload		(有效载荷)
Signature	(签名)
三者之间使用英文的"."进行分隔
Payload 部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串
Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性
  • JWT 的使用方式
客户端收到服务器返回的 JWT 之后,通常会将它存储在 localStorage 或 sessionStorage 中
此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证
推荐的做法是把 JWT 放在 HTTP 请求头的 Authoritarian 字段中
格式如下:
Authorization: Bearer 

实现 JWT 认证

  • 安装 JWT 相关的包
jsonwebtoken 用于生成 JWT 字符串
express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
npm install jsonwebtoken express-jwt
  • 使用 JWT
// 1. 创建服务器实例
const express = require('express')
const app = express()

// 3. 使用 JWT
// 3.1 安装并导入 JWT 相关的两个包
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')

// 3.2 设置 secret 密钥,保证 JWT 字符串的安全
const secretKey = 'text use ^_^'

// 3.4 将 JWT 字符串解析还原成 JSON 对象
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
const mw = expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] })
app.use(mw)     // 将这个中间件注册为全局中间件

// 获取用户信息的接口
app.get('/admin/getInfo', (req, res) => {
    // 3.5 使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
    console.log(req.user)
    res.send({
        status: 200,
        message: "获取用户信息成功",
        data: req.user
    })
})

// 登录接口
app.post('api/login', (req, res) => {
    const userinfo = req.body
    // 登录失败
    if (userinfo.username !== 'admin' || userinfo.password !== '123456') {
        return res.send({ status: 400, message: '登录失败' })
    }
    // 登录成功

    // 3.3 登录成功后,生成 JWT 字符串,并通过 token 属性发送给客户端
    // 参数1:用户的信息对象
    // 参数2:加密的密钥
    // 参数3:配置对象,可以配置当前的 token 的有效期
    const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '60s' })
    req.send({ status: 200, message: '登录成功', token: tokenStr })
})

// 3.6 使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res) => {
    // 判断错误是否为 token 解析失败导致
    if (err.name === 'UnauthorizedError') return res.send({ status: 401, message: '无效的token' })
    res.send({ status: 500, message: '未知的错误' })
})

// 2. 启动服务器
app.listen(80, (req, res) => {
    console.log('Server is running at http://127.0.0.1/')
})

你可能感兴趣的:(#,Node.js,node.js)