Node.js-1

Node.js 简介

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

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

  • Chrome 浏览器能执行 JS 代码,依靠的是内核中的 V8引擎(即:C++程序)
  • Node.js 是基于 Chrome V8 引擎进行封装的
  • 注意:Node.js 环境没有 DOM 和 BOM 等内容,但是它也有自己独立的 API

模块化

基本认知

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

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

加载模块

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

javascript 复制代码
// 加载内置模块
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') }

javascript 复制代码
【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 包
javascript 复制代码
npm install moment
  • 简写

    npm i moment

  • 指定版本号(不指定版本号,默认安装最新版本的包)

    npm i moment@2.22.2

  • 输出格式化时间(详细操作,自行查阅官方文档)

    // 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 moment@2.22.2 中的版本号由三位数字组成

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

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

包管理配置文件

基本认知

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

解决方案:
	共享时剔除 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 文件系统模块

javascript 复制代码
// 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 路径模块

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

// 注意: ../会抵消前面的路径
const pathStr = path.join('/a', '/b/c', '../', '/d', '/e')
console.log(pathStr)    // 输出路径为:\a\b\d\e
  • 获取文件名、扩展名
javascript 复制代码
// 导入 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 模块

javascript 复制代码
// 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(`<h1>您的请求方式为:${method} 请求</h1>`)
})

// 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 express@4.17.1

  • 创建 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

javascript 复制代码
app.use(express.static('public'))
  • 托管多个静态资源目录
javascript 复制代码
app.use(express.static('public'))
app.use(express.static('files'))
......

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
javascript 复制代码
// 这是路由模块

// 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
javascript 复制代码
// 这是 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/') })

中间键

  • 基本认识
javascript 复制代码
// 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/') })

  • 定义多个局部中间件

javascript 复制代码
// 以下两种写法是等效的
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 中间件
javascript 复制代码
npm i cors@2.8.5
  • 使用 cors 中间件
javascript 复制代码
// 导入 cors 中间件
const cors = require('cors')
// 将 cors 注册为全局中间件
app.use(cors())

跨域资源共享

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

mysql

基本内容

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

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

查询与插入数据

  • 查询数据
javascript 复制代码
// 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)
})
  • 插入数据
javascript 复制代码
// 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('插入数据成功!') }
})

更新与删除数据

  • 更新数据
javascript 复制代码
// 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('更新数据成功!') }
})
  • 删除数据
javascript 复制代码
// 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 认证机制

  • HTTP 无状态性质

    HTTP 协议的无状态性质,指的是客户端每次的 HTTP 请求都是独立的,连续多个请求之前没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态

  • 突破 HTTP 无状态的限制

    使用 Cookie 可以弥补 HTTP 无法记录状态的问题

  • 什么是 Cookie

    Cookie 是存储在用户浏览器中的一段不超过 4KB 的字符串

    它由一个名称(Name)、一个值(value)和其他几个用于控罪 Cookie 有效期、安全性、使用范围的可选属性组成

    不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器

  • Cookie 的几大特性

javascript 复制代码
1.自动发送
2.域名独立
3.过期时限
4.4KB限制
  • Cookie 在身份认证中的作用

    客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中
    随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给服务器,服务器即可验证客户端的身份

  • Cookie 不具有安全性

    由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器

Session

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

实现 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)是目前最流行的跨域认证解决方案
  • 工作原理
  • **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 对象

javascript 复制代码
npm install jsonwebtoken express-jwt
  • 使用 JWT
javascript 复制代码
// 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/')
})
相关推荐
Python私教5 小时前
把开源 Agent 打包成"解压双击即用"的 Windows 便携包:一条命令的完整实现
node.js
没事别瞎琢磨7 小时前
十一、审计与 Run Session——每一步操作都被记录
人工智能·node.js
没事别瞎琢磨7 小时前
十六、AgentSandbox——把所有模块串起来的编排类
人工智能·node.js
没事别瞎琢磨7 小时前
十二、网络代理与白名单规则引擎
人工智能·node.js
没事别瞎琢磨7 小时前
十四、Git Worktree 隔离执行
人工智能·node.js
没事别瞎琢磨8 小时前
十、统一 Runner 入口——能力检测与模式回退
人工智能·node.js
没事别瞎琢磨8 小时前
八、环境隔离——构建安全的子进程环境
人工智能·node.js
没事别瞎琢磨9 小时前
六、输出捕获与截断
人工智能·node.js
没事别瞎琢磨9 小时前
七、敏感路径预检——Protected Paths
人工智能·node.js
没事别瞎琢磨10 小时前
五、进程执行——spawn、超时与进程树清理
人工智能·node.js