Node学习笔记

文章目录

  • 初始Node.js与内置模块
    • 1. 初始Node.js
      • 01-回顾与思考
      • 02-Node.js简介
        • 1. 官网
        • 2. JS运行环境注意点
        • 3. Node.js可以做什么
        • 4. Node.js学习路径
      • 03-Node.js环境安装
        • 1. 区分LTS和Current版本不同
        • 2. 查看已安装的Node.js的版本号
        • 3. 终端概念
        • 4. Node.js环境中执行JS代码
    • 2. fs文件系统模块
      • 01-fs.readFile()
      • 02-fs.writeFile()
      • 03-路径动态拼接问题
    • 3. path路径模块
      • 01-概念
      • 02-路径拼接
        • 1. path.join()
        • 2. path.basename()
        • 3. path.extname()
      • 03-注意点
    • 4. http模块
      • 01-概念
      • 02-进一步理解http模块作用
      • 03-服务器相关的概念
        • 1. IP地址
        • 2. 域名和域名服务器
        • 3. 端口号
      • 04-创建最基本的web服务器
        • 1. 创建web服务器基本步骤
        • 2. 导入http模块
        • 3. 创建web服务器实例
        • 4. 绑定request事件
        • 5. 启动服务器
        • 6. req请求对象
        • 7. res响应对象
        • 8. 解决中文乱码问题
      • 05-根据不同url响应不同的html内容
        • 核心步骤
      • 06-实现clock时钟web服务器
        • 1. 核心思路
        • 2. 实现步骤
  • 模块化
    • 1. 模块化的基本概念
      • 01-概念
        • 1. 编程领域的模块化
      • 02-模块化规范
    • 2. Node.js中模块的分类
      • 01-模块分类
      • 02-模块加载
      • 03-Node.js中模块的作用域
        • 1. 概念
        • 2. 好处
      • 04-向外共享模块作用域中的成员
        • 1. module对象
        • 2. module.exports对象
        • 3. 共享成员的注意点
        • 4. exports对象
        • 5. Node.js中模块化规范
    • 3. npm与包
      • 01-概念
      • 02-包的来源
      • 03-为什么需要包
      • 04-从哪里下载包
      • 05-如何下载包
      • 06-npm初体验
        • 1. 格式化时间的传统做法
        • 2. 格式化事件的高级做法
        • 3. 在项目中安装包的命令
        • 4. 初次装包后多了哪些文件
        • 5. 安装指定版本的包
        • 6. 包的语义化版本规范
      • 07-包管理配置文件
        • 1. 多人协作的问题
        • 2. 如何记录项目中安装了哪些包
        • 3. 快速创建爱你package.json
        • 4. dependencies节点
        • 5. 一次性安装所有包
        • 6. 卸载包
        • 7. devDependencies节点
      • 08-解决下包速度慢的问题
        • 1. 为什么下包慢
        • 2. 淘宝NPM镜像服务器
        • 3. 切换npm的下包镜像源
        • 4. nrm
      • 09-包的分类
        • 1. 项目包
        • 2. 全局包
        • 3. i5tign_toc
      • 10-规范的包结构
      • 11-开发属于自己的包
        • 1. 需要实现的功能
        • 2. 初始化包的基本结构
        • 3. 初始化 package.json
        • 4. 在index.js中定义格式化时间的方法
        • 5. index.js中定义html转义方法
        • 6. index.js中定义html还原方法
        • 7. 将不同功能进行模块化拆分
        • 8. 编写包的说明文档
      • 12-发布包
        • 1. 注册npm账号
        • 2. 登录npm账号
        • 3. 把包发布到npm上
        • 4. 删除已发布的包
    • 4. 模块的加载机制
      • 01-优先从缓存中加载
      • 02-内置模块的加载机制
      • 03-自定义模块的加载机制
      • 04-第三方模块的加载机制
      • 05-目录作为模块
  • Express
    • 1. 初始Express
      • 1.1 概念
        • 1. 进一步理解Express
        • 2. Express能做什么
      • 1.2 Express基本使用
        • 1. 安装
        • 2. 创建基本的Web服务器
        • 3. 监听GET请求
        • 4. 监听POST请求
        • 5. 把内容响应给客户端
        • 6. 获取URL中携带的查询参数
        • 7. 获取URL中的动态参数
      • 1.3 托管静态资源
        • 1. express.static()
        • 2. 托管多个静态资源目录
        • 3. 挂载路径前缀
      • 1.4 nodemon
        • 1. 为什么使用nodemon
        • 2. 安装nodemon
        • 3. 使用nodemon
    • 2. Express 路由
      • 2.1 路由的概念
        • 1. Express中的路由
        • 2. 路由的匹配过程
      • 2.2 路由的使用
        • 1. 最简单的用法
        • 2. 模块化路由
        • 3. 为路由模块添加前缀
    • 3. Express 中间件
      • 3.1 概念
        • 1. Express中间件的调用流程
        • 2. Express中间件的格式
        • 3. next函数的作用
      • 3.2 Express中间件初体验
        • 1. 定义中间件函数
        • 2. 全局生效的中间件
        • 3. 定义全局中间件简化形式
        • 4. 中间件的作用
        • 5. 定义多个全局中间件
        • 6. 局部生效的中间件
        • 7. 定义多个局部的中间件
        • 8. 了解中间件的5个生效方式
      • 3.3 中间件的分类
        • 1. 应用级别的中间件
        • 2. 路由级别的中间件
        • 3. 错误级别的中间件
        • 4. Express内置的中间件
        • 5. 第三方中间件
      • 3.4 自定义中间件
        • 1. 需求描述与实现步骤
        • 2. 监听req的 data 事件
        • 3. 监听 req 的 end 事件
        • 4. 使用 querystring 模块解析请求体数据
        • 5. 将解析出来的数据对象挂载为req.body
        • 6. 将自定义中间件封装为模块
    • 4. 使用Express 写接口
      • 4.1 创建基本的服务器
      • 4.2 创建 API 路由模块
      • 4.3 编写GET接口
      • 4.4 编写POST接口
      • 4.5 CORS 跨域资源共享
        • 1. 接口跨域问题
        • 2. 使用cors中简介解决跨域问题
        • 3. 什么是cors
        • 4. cors注意事项
        • 5. CORS 响应头部 Access-Control-Allow-Origin
        • 6. CORS 响应头部 Access-Control-Allow-Headers
        • 7.CORS响应头部 -Access-Control-Allow-Methods
        • 8. CORS请求的分类
        • 9. 简单请求
        • 10. 预检请求
        • 11. 简单请求和预检请求的区别
      • 4.6 JSONP 接口
        • 1. 回顾JSONP 的概念与特点
        • 2. 创建JSONP接口注意事项
        • 3. 实现JSONP 接口的步骤
  • 数据库与身份证
    • 1. 数据库的基本概念
      • 1.1 什么是数据库
        • 1. 常见数据库及分类
        • 2. 传统型数据库数据组织结构
        • 3. 实际开发中 库、表、行、字段的关系
    • 2. 安装并配置MySQL
    • 3. MySQL基本使用
      • 3.1 使用SQL管理数据库
        • 1. 什么是SQL
        • 2. SQL能做什么
        • 3. SQL学习目标
      • 3.2 SQL的SELECT语法
      • 3.3 SQL的INSERT INTO语句
      • 3.4 SQL的UPDATA 语句
      • 3.5 SQL的 DELECT 语句
      • 3.6 SQL的 WHERE子句
      • 3.7 SQL的 ORDER BY子句
      • 3.8 SQL的COUNT(*) 函数
    • 4. 在Express中操作MySQL
      • 4.1 在项目中操作数据库步骤
      • 4.2 安装与配置mysql模块
        • 1. 安装mysql模块
        • 2. 配置mysql 模块
        • 3. 测试mysql 模块能否正常工作
      • 4.3 使用mysql 模块操作MySQL数据库
        • 1. 查询数据
        • 2. 插入数据
        • 3. 插入数据的便捷方式
        • 4. 更新数据表
        • 5. 更新数据的便捷方式
        • 6. 删除数据
        • 7. 标记删除
    • 5. 前后端的身份认证
      • 5.1 Web开发模式
        • 1. 服务器渲染的Web开发模式
        • 2. 优缺点
        • 3. 前后端分离的Web开发模式
        • 4. 前后端分离的优缺点
        • 5. 如何选择web 开发模式
      • 5.2 身份认证
        • 1. 什么是身份认证
        • 2. 为什么需要身份认证
        • 3. 不同开发模式下的身份认证
      • 5.3 Session 认证机制
        • 1. HTTP 协议的无状态性
        • 2. 如何突破 HTTP 无状态限制
        • 3. Cookie
        • 4. Cookie 在身份认证中的作用
        • 5. Cookie 不具有安全性
        • 6. 提高身份认证的安全性
        • 7. Session 的工作机制
      • 5.4 在Express 中使用 Session 认证
        • 1. 安装express-session中间件
        • 2. 配置express-session 中间件
        • 3. 向session 中存数据
        • 4. 从session中取数据
        • 5. 清空session
      • 5.5 JWT 认证机制
        • 1. 了解 Session 认证的局限性
        • 2. JWT概念
        • 3. JWT 的工作原理
        • 4. JWT组成部分
        • 5. JWT的三部分各自代表的含义
        • 6. JWT 的使用方式
      • 5.6 在Express中使用 JWT
        • 1. 安装 JWT 相关的包
        • 2. 导入相关包
        • 3. 定义secret 秘钥
        • 4. 在登录成功后生成 JWT 字符串
        • 5. 将 JWT 字符串还原为 JSON 对象
        • 6. 使用 req.user 获取用户信息
        • 7. 捕获解析 JWT 失败后产生的错误
        • 5. 清空session
      • 5.5 JWT 认证机制
        • 1. 了解 Session 认证的局限性
        • 2. JWT概念
        • 3. JWT 的工作原理
        • 4. JWT组成部分
        • 5. JWT的三部分各自代表的含义
        • 6. JWT 的使用方式
      • 5.6 在Express中使用 JWT
        • 1. 安装 JWT 相关的包
        • 2. 导入相关包
        • 3. 定义secret 秘钥
        • 4. 在登录成功后生成 JWT 字符串
        • 5. 将 JWT 字符串还原为 JSON 对象
        • 6. 使用 req.user 获取用户信息
        • 7. 捕获解析 JWT 失败后产生的错误


初始Node.js与内置模块

1. 初始Node.js

01-回顾与思考

浏览器中JS组成

  • JS核心语法
    • 变量、数据类型、循环、分支、判断
    • 函数、作用、this、etc…
  • WebAPI
    • DOM、BOM操作
    • 基于XMLHttpRequest 的 Ajax操作
    • etc…
  • Chrome => V8

Chrome 浏览器的 V8 解析引擎性能最好

  • 每个浏览器都内置了DOM、BOM的API,所以js可以操作DOM和BOM
  • 内置API是由运行环境提供的特殊接口,只能在所属的运行环境中被调用
  • Node.js是运行环境,通过Node.js可以使JS做后端开发

02-Node.js简介

Node.js是一个基于 Chrome V8引擎的 JavaScript运行环境

1. 官网

http://nodejs.cn/

2. JS运行环境注意点
  • 浏览器是JS的前端运行环境
  • Node.js是JS的后端运行环境
  • Node.js中无法调用DOM和BOM等浏览器内置API
3. Node.js可以做什么
  • 提供了基础的功能和API,还有很强大的工具和框架
  • 基于Express框架,可以快速搭建web应用
  • 基于Electron框架,可以搭建平台的桌面应用
  • 基于restify框架,可以快速搭建API接口项目
  • 读写和操作数据库,创建使用的命令行工具辅助前端开发,etc…
4. Node.js学习路径

JS基础语法 + Node.js内置API模块(fs、path、http等) + 第三方API模块(express、mysql等)

03-Node.js环境安装

1. 区分LTS和Current版本不同
  • LTS:为长期稳定版,对追求稳定的企业项目来说推荐安装
  • Current:为新特性尝鲜版
2. 查看已安装的Node.js的版本号

打开终端,终端输入命令 node -v 后,回车即可

3. 终端概念

终端:专门为开发人员设计的,用于实现人机交互的一种方式,需要记一些常用的终端命令

4. Node.js环境中执行JS代码
  • 打开终端
  • 输入node 要执行的js文件路径

快捷方式

  • 直接在文件目录下 shift + 右键 打开powershell窗口

终端中的快捷键

  • 可以快速定位到上一次执行的命令
  • tab键 能够快速补全路径
  • esc 键 能够清空当前已输入的命令
  • cls 命令 能够清空当前的终端

2. fs文件系统模块

fs模块是Node.js官方提供的、用来操作文件的模块。提供了一系列的方法属性,用来满足用户对文件的操作需求

  • fs.readFile()方法,用来读取指定文件的内容
  • fs.writeFile()方法,用来向指定的文件中写入内容

如果在JS代码中,使用fs模块操作文件,需要导入

let fs = require('fs')

01-fs.readFile()

可以读取指定文件内容,语法格式如下

fs.readFile(path[,options],callback)

参数

  • 参数1:必选参数,字符串,表示文件路径
  • 参数2:可选,表示以什么编码格式读取文件
  • 参数3:必须参数,文件读取完成后,通过回调函数拿到读取结果
//导入fs模块,来操作文件
const fs = require('fs')

//调用方法

//参数1:读取读取存放路径
//参数2:读取文件时候采用的编码格式,一般默认指定utf8
//参数3:回调函数,拿失败和成功的结果, err  dataStr

fs.readFile('./files/1.txt', 'utf8', function(err, dataStr) {
    //失败的结果
    //如果读取成功 err 为 NULL
    //如果读取失败 err 为 错误对象,dataStr 为 undefined
    console.log(err);
    console.log('--------------');
    //成功的结果
    console.log(dataStr);
})

判断文件是否读取成功

fs.readFile('./files/1.txt', 'utf8', function(err, dataStr) {
    if (err) {
        return console.log('读取失败!' + err.message);
    }

    console.log('读取成功' + dataStr);
})

02-fs.writeFile()

可以向指定文件中写入内容

fs.writeFile(file, data[,options],callback)

参数

  • 参数1:必选参数,指定一个文件路径的字符串,表示文件存放路径
  • 参数2:必选参数,表示写入内容
  • 参数3:可选参数,表示以什么格式写入文件内容,默认值是utf8
  • 参数4:必选参数,文件写入完成后的回调函数
//导入fs文件
const fs = require('fs')

//  调用方法写入文件内容
//  参数1:文件存放路径
//  参数2:写入内容
//  参数3:回调函数

fs.writeFile('./files/2.txt', 'abcd', function(err) {
    // 如果文件写入成功 err值为NULL
    //  写入失败    err 返回一个错误对象
    console.log(err);
})

判断文件是否写入成功

fs.writeFile('./files/2.txt', 'abcd', function(err) {
    if (err) {
        return console.log('写入失败' + err.message);
    }
    console.log(err);
})

03-路径动态拼接问题

fs操作文件时,提供的操作路径是以./ 或 …/开头的相对路径时,很容易出现路径动态拼接错误问题

原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径

解决方法:在readFIle()中的path参数提供一个完整的路径

问题:移植性很差,不利于维护

fs.readFile('D:\\A_code\\Vs_Code\\study\\Node\\files\\1.txt', 'utf8', function(err, dataStr) {
    // console.log(dataStr);

    if (err) {
        return console.log('读取失败!' + err.message);
    }

    console.log('读取成功' + dataStr);
})
  • __dirname 表示当前文件所处的目录
fs.readFile(__dirname + '/files/1.txt', 'utf8', function(err, dataStr) {
    if (err) {
        return console.log('读取失败!' + err.message);
    }

    console.log('读取成功' + dataStr);
})

3. path路径模块

01-概念

path模块是Node.js官方提供、用来处理路径的模块

  • path.join()方法,用来将多个路径片段拼接成一个完成的路径字符串
  • path.basename()方法,用来从路径字符串中,将文件名解析出来

如果在JS代码中,使用path模块操作文件,需要导入

const fs = require('fs')

02-路径拼接

1. path.join()

用来将多个路径片段拼接成一个完成的路径字符串,语法如下

path.join([..paths])

参数

  • …paths 路径片段序列
  • 返回值:string
  • 注意:不建议使用+号拼接路径,可能会出现问题,使用path.join进行路径拼接
const path = require('path')

//注意 ../会抵消前面的路径
const pathStr = path.join('/a', '/b/c', '../', './d', 'e')
console.log(pathStr); // \a\b\d\e
2. path.basename()

用来从路径字符串中,将文件名解析出来,语法如下

path.basename(path[,ext])

参数

  • path:必选参数,表示一个路径的字符串
  • ext:可选参数 string 表示文件扩展名
  • 返回值:string,表示路径最后一部分
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
3. path.extname()

用来获取路径中的扩展名部分,语法如下

path.extname(path)

参数

  • path:必选参数,表示一个路径的字符串
  • 返回值:string,表示得到的扩展名
const path = require('path')

// 定义路径的存放路径
const fpath = '/a/b/c/index.html'

const fext = path.extname(fpath)
console.log(fext); // .html

03-注意点

  • fs.writeFile()方法 只能用来创建文件,不能用来创建路径
  • 重复调用fs.writeFile()方法写入同一个文件,新写入的内容会覆盖之前的旧内容

4. http模块

01-概念

http模块是Node.js官方提供的、用来创建web服务器的模块,通过http模块提供的http.createServer() 方法能方便的把一台普通电脑,变成一台Web服务器,从而对外提供Web资源服务

如果在JS代码中,使用http模块操作文件,需要导入

const http = require('http')

02-进一步理解http模块作用

服务器和普通电脑去呗在于,服务器上安装了web服务器软件,例如IIS、Apache

通过安装这些服务器软件,就能把一台普通电脑变成一台web服务器

在Node.js中不需要这些第三方web服务器软件,可以基于Node.js提供http模块,通过几行代码,可以写一个服务器软件,从而对外提供web服务

03-服务器相关的概念

1. IP地址

IP地址就是每台计算机的唯一地址

IP地址的格式:通常用“点分十进制”表示(a.b.c.d)的形式,a,b,c,d都是0~255之间的十进制整数

  • 互联网每台Web服务器,都有自己的IP地址
  • 开发期间,自己电脑是一台服务器又是一个客户端,为了方便测试,可以在自己浏览器上输入 127.0.0.1这个IP地址,就能把自己电脑作为一个服务器进行访问。127.0.0.1只能用于自己测试,别人访问使用
2. 域名和域名服务器

IP地址是一长串数字,不直观所以发明一套字符型地址方案,即域名

IP地址与域名的对应关系:这份对应关系存放在一种叫做域名服务器DNS

域名服务器:就是提供IP地址和域名之间的转换服务的服务器

  • 127.0.0.1 对应的域名是 localhost
3. 端口号

计算机的端口号,就像现实生蚝中的门牌号

一台电脑上,可以运行成百上千个web服务,每个web服务对应一个唯一端口号,客户端发送过来的网络请求通过端口号,可以准确地交给对应web服务器进行处理

  • 每个端口号不能同时被多个web服务占用
  • 实际应用中,URL中的80端口可以被省略

04-创建最基本的web服务器

//  导入http模块

const http = require('http')

//  创建web服务器实例

const server = http.createServer()

//  为服务器实例绑定request事件,监听客户端请求

server.on('request', function(req, res) {
    console.log('Some visit our web server');
})

//  启动服务器
server.listen(8080, function() {
    console.log('http server running at http://127.0.0.1:8080')
})
1. 创建web服务器基本步骤
  • 导入http模块
  • 创建web服务器实例
  • 为服务器实例绑定request事件,监听客户端请求
  • 启动服务器
2. 导入http模块
const http = require('http')
3. 创建web服务器实例
const server = http.createServer()
4. 绑定request事件
server.on('request', function(req, res) {
    console.log('Some visit our web server');
})
5. 启动服务器
server.listen(8080, function() {
    console.log('http server running at http://127.0.0.1:8080')
})
6. req请求对象

server.on()为服务器绑定的request事件处理函数

访问与客户端相关的数据或属性

// req 是请求对象,包含了客户端相关的数据和属性
server.on('request', (req, res) => {
    //req.url 是客户端请求的url地址
    const url = req.url
    // req.method 是客户端请求的method方法
    const method = req.method

    const str = `Your request url is ${url},and request method is ${method}`
    console.log(str)
})
7. res响应对象

服务器request事件处理函数中,访问与服务器相关的数据或属性

// req 是请求对象,包含了客户端相关的数据和属性
server.on('request', (req, res) => {
    //req.url 是客户端请求的url地址
    const url = req.url
        // req.method 是客户端请求的method方法
    const method = req.method

    const str = `Your request url is ${url},and request method is ${method}`
    console.log(str)

    // 调用res.end()方法,向客户端响应一些内容
    res.end(str)
})
8. 解决中文乱码问题

调用res.end()方法,向客户端发送中文内容时候,会出现乱码问题,需要手动设置内容编码格式

// req 是请求对象,包含了客户端相关的数据和属性
server.on('request', (req, res) => {
    const str = `您请求的URL地址是 ${req.url},请求的 method 类型是 ${req.method}`;
    //调用setHeader方法,设置Content-Type响应头,解决中文乱码问题
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    // 调用res.end()方法,向客户端响应一些内容
    res.end(str)
})

05-根据不同url响应不同的html内容

核心步骤
  • 获取请求的url地址
  • 设置默认的响应内容为404 Not found
  • 判断用户请求是否为/ 或 /index.html
  • 判断用户请求是否为/about.html关于页面
  • 设置Content-Type响应头,防止中文乱码
  • 使用res.end() 把内容响应给客户端
const http = require('http')

const server = http.createServer()

server.on('request', (req, res) => {
    // 获取请求的url地址
    const url = req.url

    // 设置默认的响应内容为404 Not found
    let content = '

404 Not found

' // 判断用户请求是否为/ 或 /index.html // 判断用户请求是否为/about.html关于页面 if (url === '/' || url === '/index.html') { content = '

首页

' } else if (url === '/about.html') { content = '

关于

' } // 设置Content-Type响应头,防止中文乱码 res.setHeader('Content-Type', 'text/html; charset=utf-8;') // 使用res.end() 把内容响应给客户端 res.end(content) }) server.listen(80, () => { console.log('server running at http://127.0.0.1'); })

06-实现clock时钟web服务器

1. 核心思路

把文件的实际存放路径,作为每个资源的请求url地址

2. 实现步骤
  • 导入需要的模块
  • 创建基本的web服务器
  • 将资源的请求url地址映射为文件的存放路径
  • 读取文件内容并响应给客户端
  • 优化资源的请求路径
// 导入需要的模块

const http = require('http')
const path = require('path')
const fs = require('fs')

// 创建基本的web服务器

const server = http.createServer()

// 将资源的请求url地址映射为文件的存放路径
server.on('request', function(req, res) {
    const url = req.url;

    // const fpath = path.join(__dirname, url)
    let fpath = ''
        // 优化资源的请求路径
    if (url === '/') {
        fpath = path.join(__dirname, './clock/index.html')
    } else {
        fpath = path.join(__dirname, '/clock', url)
    }


    fs.readFile(fpath, 'utf8', (err, dataStr) => {
        if (err) return res.end('404 Not found')
        res.end(dataStr)
    })
})

// 读取文件内容并响应给客户端

server.listen(80, () => {
    console.log('server runing at http://127.0.0.1');
})

模块化

1. 模块化的基本概念

01-概念

模块化是指解决 复杂问题 时,自顶向下把系统划分成若干模块的过程。对整个系统来说,模块是可组合、分解和更换的单元

1. 编程领域的模块化

模块化就是遵循固定的规则,把一个大文件拆成独立并互相依赖的多个小模块

好处

  • 提高了代码的复用性
  • 提高了代码的可维护性
  • 可以实现按需加载

02-模块化规范

就是对代码进行模块化的拆分与组合时,需要遵守的那些规则

  • 使用什么样的语法格式引用模块
  • 在模块中使用什么样的语法格式向外暴露成员

2. Node.js中模块的分类

01-模块分类

来源不同,分为3大类

  • 内置模块(由Node.js提供的,fs、path、http等)
  • 自定义模块(用户自己定义的每个.js文件)
  • 第三方模块(由第三方开发出来的模块,使用前需要下载)

02-模块加载

使用强大的require()方法,可以加载需要的内置模块,用户自定义模块,第三方模块进行使用

// 加载需要的内置模块
const fs = require('fs')
// 加载用户自定义模块	可以省略后缀名
const custom = require('./custom.js')
// 加载第三方模块进行使用
const moment = require(moment)

注意:使用require() 方法加载其它模块时,会执行被加载模块中的代码

03-Node.js中模块的作用域

1. 概念

和函数作用域类似,在自定义模块定义的变量、方法等成员,只能能在当前模块被访问,这种模块级别访问限制,叫做模块作用域

2. 好处

防止全局变量污染的问题

04-向外共享模块作用域中的成员

1. module对象

每个.js自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息

2. module.exports对象

自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用

require() 方法导入自定义模块时,得到的就是module.exports 所指向的对象

3. 共享成员的注意点

使用require() 方法导入模块时,导入的结束,永远以module.exports 指向的对象为准

4. exports对象

Node提供了exports对象,默认情况下,exports和module.exports只向同一个对象,最终共享的结果以module.exports 指向的对象为准

误区

  • require最终的结果以module.exports 指向的对象为准
5. Node.js中模块化规范

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

规定

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

3. npm与包

01-概念

Node,js中的第三方模块又叫做包,也是第三方模块,只是叫法不同

02-包的来源

不同的Node.js中的内置模块和自定义模块,包时由第三方个人或团队开发出来的,免费供所有人使用

03-为什么需要包

Node.js内置模块仅提供了一些底层API,导致基于内置模块进行项目开发效率低

包时基于内置模块出来的,提供了更高级、更方便的API,极大提高了开发效率

包和内置模块之间的关系,类似于jQuery 和浏览器内置API 之间的关系

04-从哪里下载包

npm,Inc公司提供了网站:https://www.npmjs.com/ ,它是全球最大的包共享平台

npm,Inc公司提供了服务器:https://registry.npmjs.org ,来对外共享所有包

05-如何下载包

npm,Inc公司提供了包管理工具,从https://registry.npmjs.org/

这个包管理工具简称npm,随着Node.js的安装包一起被安装到电脑上

06-npm初体验

1. 格式化时间的传统做法
  • 创建格式化时间的自定义模块
  • 定义格式化时间的方法
  • 创建补零函数
  • 自定义模块中到处格式化时间的函数
  • 导入格式化时间的自定义模块
  • 调用格式化时间的函数
  1. 自定义模板
//  创建格式化时间的自定义模块
//  定义格式化时间的方法
//  创建补零函数
//  自定义模块中到处格式化时间的函数
//  导入格式化时间的自定义模块
//  调用格式化时间的函数

function dataFormat(dtStr) {
    const dt = new Date(dtStr)

    const y = dt.getFullYear()
    const m = padZero(dt.getMonth() + 1)
    const d = padZero(dt.getDate())

    const hh = padZero(dt.getHours())
    const mm = padZero(dt.getMinutes())
    const ss = padZero(dt.getSeconds())

    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

function padZero(n) {
    return n > 9 ? n : '0' + n
}

module.exports = {
    dataFormat
}
  1. 测试
const TIME = require('./05.dataFormat')

const dt = new Date()

const newDT = TIME.dataFormat(dt)
console.log(newDT);
2. 格式化事件的高级做法
  • 使用npm包管理工具,项目中安装格式化事件的包 moment
  • 使用require() 导入格式化时间的包
  • 参考moment的官方 API文档对事件进行格式化
3. 在项目中安装包的命令

在项目中安装指定名称的包,需要运行如下的命令

npm install 包的完整名称

//简写

npm i 包的完整名称

查看文档:https://www.npmjs.com/

4. 初次装包后多了哪些文件

多了一个node_modules的文件夹和package-lock.json的配置文件

  • node_modules文件夹用来存放所有已安装的项目中的包
  • package-lock.json 用来记录node_modules 目录下的每一个包的下载信息
5. 安装指定版本的包

默认情况下,使用npm install 命令安装包的时候,会自动安装最新版本的包。

可以通过@符号指定具体版本

npm i [email protected]
6. 包的语义化版本规范

包的版本号是以“点分十进制” 形式进行定义的,总共有三位数字

  • 第一位:大版本
  • 第二位:功能版本
  • 第三位:Bug修复版本

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

07-包管理配置文件

npm规定,在根目录中必须提供一个叫做package.json 的包管理配置文件,用来记录有关的一些配置信息

  • 项目的名称、版本号、描述等
  • 项目中都用到那些包
  • 那些包只在开发期间会用到
  • 那些包在开发和部署都需要用到
1. 多人协作的问题

遇到的问题:第三方包的体积过大,不方便团队成员之间共享项目源代码

解决方案:共享时剔除node_modules

2. 如何记录项目中安装了哪些包

在项目根目录中,创建一个叫做package.json 的配置文件,用来记录项目中安装了哪些包

注意:一定要把node_modules文件夹,添加到.gitignore忽略文件中

3. 快速创建爱你package.json

npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建package.json文件

npm init -y

注意:

  • 上述命令只能英文目录下成功运行,所以,项目文件夹的名称一定使用英文。不要使用中文,不要有空格
  • 运行 npm install 命令安装包时候,npm管理工具自动把报的名称和版本号,记录到package.json中
4. dependencies节点

package.json文件中,有一个dependencies节点,专门用来记录npm install 命令安装了哪些包

5. 一次性安装所有包

拿到一个剔除了node_modules项目之后,需要把所有的包下载到项目中

可以执行npm install 命令(或 npm i) 一次性安装所有的依赖包

6. 卸载包

可以运行npm uninstall 命令

卸载成功后,会把卸载的包自动从package.json中移除掉

7. devDependencies节点

某些包只在项目开发阶段会用到,在项目上线之后不会用到,记录到devDependencies节点下

开发阶段,项目上线之后还会用到放在dependencies节点中

使用如下命令

npm install 包名 --save-dev

// 简写

npm install 包名 -D

08-解决下包速度慢的问题

1. 为什么下包慢

npm下包时候,默认从国外http://registry.npmjs.org/服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢

2. 淘宝NPM镜像服务器

淘宝在国内搭建了一个服务器,专门把国外官方服务器中的包同步到国内服务器,然后在国内提供下包服务,从而极大的提高了下包速度

镜像:一种文件存储形式,一个磁盘上的数据在两一个磁盘上存在一个完全相同的副本即为镜像

3. 切换npm的下包镜像源

指的就是下包的服务器地址

#查看当前下包的镜像源
npm config get registry		//https://registry.npmjs.org/

#将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobao.org/
4. nrm

为了更方便切换下包的镜像源,可以安装nrm小工具,利用nrm提供的终端命令,可以快速查看和切换下包的镜像源

#通过npm包管理器,将nrm安装为全局可用的工具
npm i nrm -g

#查看所有可用的镜像源
nrm ls

#将下包的镜像源切换为 taobao 镜像
nrm use taobao

09-包的分类

1. 项目包

那些被安装到项目的node_modules目录中的包,都是项目包

  • 开发依赖包,被记录到devDependencies节点中的包
  • 核心依赖包,被记录在dependencies节点中的包
2. 全局包

在执行npm install命令时,如果提供了-g参数,则会把包安装为全局包

全局包为被安装到C:\Users\ 用户\AppData\Roaming\npm\node_modules 目录下

npm i 包名 -g		#全局安装指定的包

npm uninstall 包名 -g		#卸载全局安装的包

注意:

  • 只有工具性质的包,才能全局安装的必要性,因为它们提供了好用的终端命令
  • 判断某个包是否需要安装到全局后才能使用,可以参考官方提供的使用说明即可
3. i5tign_toc

i5tign_toc是一个可以把md文档转换成html页面的小工具

#下载
npm install -g i5ting_toc

#调用i5ting_toc,轻松实现md 转 html的功能
i5ting_toc -f 要转换的md文件路径 -o

10-规范的包结构

清楚了包的概念,以及下载和使用包,了解一下包结构

一个规范的包,它的组成结构,必须符合一下3点要求:

  • 包必须以单独的目录存在
  • 包的顶级目录下必须包含package.json这个包管理配置文件
  • package.json 中必须包含name,version,main这三个属性,分别为名字,版本号,包的入口

11-开发属于自己的包

1. 需要实现的功能
  • 格式化时间
  • 转义HTML特殊字符
  • 还原HTML特殊字符
2. 初始化包的基本结构
  • 新建itheima-tools文件夹,作为包的根目录
  • 在itheima-tools文件夹中,新建三个文件
    • package.json (包管理配置文件)
    • index.js (报的入口文件)
    • README.md (包的说明文档)
3. 初始化 package.json
{
  "name": "itheima-tools",
  "version": "1.0.0",
  "main": "index.js",
  "description": "提供了格式化事件,HtmlFscape的功能",
  "keywords": ["itheima", "dataFormat", "escape"],
  "license": "ISC"
}
4. 在index.js中定义格式化时间的方法
//  包的入口文件

//  定义格式化时间的方法

function dataFormat(dateStr) {
    const dt = new Date(dateStr)

    const y = dt.getFullYear()
    const m = padZero(dt.getMonth() + 1)
    const d = padZero(dt.getDate())

    const hh = padZero(dt.getHours())
    const mm = padZero(dt.getMinutes())
    const ss = padZero(dt.getSeconds())

    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

//  定义一个补零函数
function padZero(n) {
    return n > 9 ? n : '0' + n
}


//  向外暴露需要的成员
module.exports = {
    dataFormat
}
5. index.js中定义html转义方法
//  定义转义html特殊字符
function htmlEscape(htmlstr) {
    return htmlstr.replace(/<|>|"|&/g, (match) => {
        switch (match) {
            case '<':
                return '<'
                break;
            case '>':
                return '>'
                break;
            case '"':
                return '"'
                break;
            case '&':
                return '&'
                break;
        }
    })
}
6. index.js中定义html还原方法
//  定义还原方法
function htmlUnEscape(htmlstr) {
    return htmlstr.replace(/<|>|"|&/g, (match) => {
        switch (match) {
            case '<':
                return '<'
                break;
            case '>':
                return '>'
                break;
            case '"':
                return '"'
                break;
            case '&':
                return '&'
                break;
        }
    })
}
7. 将不同功能进行模块化拆分
  • 将格式化时间功能,拆分到src->dateFormat.js中
  • 将处理HTML字符串功能,拆分到src->htmlEscape.js中
  • 在index.js中,导入两个模块,得到需要向外共享的方法
  • 在index.js中,使用module.exports 把对应的方法共享出去
8. 编写包的说明文档

包根目录中的README.md文件,是包的使用说明文档,通过它,可以事先把包的使用说明,以Markdown的格式写出来,方便用户参考

6项内容:安装方式、导入方式、格式化时间、转义HTML的特殊字符、还原HTML中的特殊字符、开源协议

12-发布包

1. 注册npm账号
  • 访问https://www.npmjs.com/,sign up,进行注册
  • 填写信息
  • 点击Create an Account,注册账户
  • 登录邮箱,点击验证链接,进行账号验证
2. 登录npm账号

在终端执行 npm login命令,依次输入用户名、密码、邮箱后即可

注意:必须把下包服务器切换到npm官方服务器,否则会导致发布包失败

3. 把包发布到npm上

将终端切换到包的根目录之后,运行npm publish命令即可(注意:包名不能雷同)

4. 删除已发布的包

运行 npm unpublish 包名 --force 命令即可

  • npm unpublish 只能删除72小时以内发布的包
  • npm unpublish 删除的包,在24小时内不允许重复发布
  • 发布包的时候要慎重,尽量不要往 npm 上发布没有意义的包

4. 模块的加载机制

01-优先从缓存中加载

模块在第一次加载后会被缓存。这就意味着多次调用require() 不会导致模块的代码被执行多次

注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会有限从缓存中加载,从而提高模块加载效率

02-内置模块的加载机制

内置模块是由Node.js官方提供的,内置模块的加载优先级最高

node_modules目录下的包和内置模块重名, require的是内置模块

03-自定义模块的加载机制

使用require() 加载自定义模块时,必须指定以 ./ 或 …/ 开头的路径标识符,如果没有指定,则node会把它当作内置模块或第三方模块进行加载

同时,如果导入自定义模块时,省略了文件的扩展名,Node.js会按顺序分别尝试加载以下文件

  • 按照确切的文件名进行加载
  • 补全.js扩展名进行加载
  • 补全.json扩展名进行加载
  • 补全.node扩展名进行加载
  • 加载失败,终端报错

04-第三方模块的加载机制

如果传递给require() 的模块标识符不是一个内置模块,也没有以 ./ 或 …/ 开头,则Node.js会以当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块

如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录

05-目录作为模块

三种加载方式

  • 在被加载目录下查找一个叫做package.json 的文件,并寻找main属性,作为require() 加载入口
  • 没有package.json 的文件,或者main入口不存在或无法解析,则Node.js将会试图加载目录下的index.js文件
  • 两者都失败,则Node.js会在终端打印错误

Express

1. 初始Express

1.1 概念

官方概念:Express是基于 Node.js 平台,快速、开放、极简的Web开发框架

通俗理解:作用类似于http模块,是专门用来创建Web服务器

Express本质:就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法

中文网:https://www.expressjs.com.cn/

1. 进一步理解Express

http内置模块用起来很复杂,开发效率低;Express是基于内置http模块进一步封装出来的,提高开发效率

2. Express能做什么

对于前端程序员来说,常见两种服务器

  • Web网站服务器:专门对外提供Web网页资源的服务器
  • API接口服务器:专门对外提供API接口的服务器

可以方便、快速的创建Web网站的服务器或API接口服务器

1.2 Express基本使用

1. 安装

在项目所处的目录中,运行如下终端命令,即可将express安装到项目使用

npm i [email protected]
2. 创建基本的Web服务器
// 导入express
const express = require('express')

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

// 启动web服务器
app.listen(80, () => {
    console.log('express server running at http://127.0.0.1');
})
3. 监听GET请求

通过app.get() 方法,可以监听客户端GET请求

app.get('/user', (req, res) => {
    // 调用 espress 提供的 res.send() 方法,向客户端提供一个JSON对象
    res.send({ name: 'zs', age: 20, gender: '男' })
})
4. 监听POST请求

通过app.get() 方法,可以监听客户端POST请求

app.post('/user', (req, res) => {
    // 调用 express 提供 res.send() 方法,向客户端提供一个 文本字符串
    res.send('请求成功')
})
5. 把内容响应给客户端

通过res.send() 方法,可以处理好内容,发送给客户端

// get()
app.get('/user', (req, res) => {
    // 调用 espress 提供的 res.send() 方法,向客户端提供一个JSON对象
    res.send({ name: 'zs', age: 20, gender: '男' })
})

// post()
app.post('/user', (req, res) => {
    // 调用 express 提供 res.send() 方法,向客户端提供一个 文本字符串
    res.send('请求成功')
})
6. 获取URL中携带的查询参数

通过req.query对象,可以访问到客户端通过查询字符串的形式,发送给服务器的参数

app.get('/', (req, res) => {
    // 通过req.query可以获取客户端发过来的查询参数
    // 默认情况下,req.query 是一个空对象
    console.log(req.query);
    res.send(req.query)
})
7. 获取URL中的动态参数

通过req.params 对象,可以访问到URL中,通过:匹配到的动态参数

// 注意:这里的 :id 是一个动态参数
app.get('/user/:id', (req, res) => {
    // req.params 是动态匹配到的 URL 参数,默认也是一个空对象
    console.log(req.params)
    res.send(req.params)
})

1.3 托管静态资源

1. express.static()

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

注意:Express在指定静态目录中查询文件,并对外提供资源的访问路径,因此存放静态文件的目录名不会出现在url中

const express = require('express')

const app = express()

// 在这里,调用express.static()方法,快速对外提供静态资源
app.use(express.static('./clock'))

app.listen(80, () => {
    console.log('express server running at http://127.0.0.1');
})
2. 托管多个静态资源目录

多次调用express.static() 函数就行

访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件

app.use(express.static('./clock'))
app.use(express.static('./public'))
3. 挂载路径前缀

express.static 要为函数服务的文件创建虚拟路径前缀(该路径实际上并不存在于文件系统中) ,请指定静态目录的挂载路径,如下所示:

app.use('/static', express.static('public'))

1.4 nodemon

1. 为什么使用nodemon

在调试Node.js项目时候,如果修改了项目代码,则需要手动关掉,再重新启动,非常繁琐

可以使用nodemon,能够监听项目文件变换,自动重启项目

2. 安装nodemon
npm install -g nodemon
3. 使用nodemon

传统运行node app.js命令,启动项目,坏处是代码修改之后,需要手动重启项目

现在使用 nodemon app.js命令启动项目,好处:代码修改之后,会被nodemon监听到,从而实现自动重启效果

2. Express 路由

2.1 路由的概念

广义上:路由就是映射关系,是按键与服务之间的映射关系

路由:是指应用程序的端点 (URI) 如何响应客户端请求。该端点是 URI和特定的 HTTP 请求方法。

1. Express中的路由

路由是指:客户端请求与服务器处理函数之间的映射关系

3部分组成:请求类型请求URL地址处理函数

app.METHOD(PATH, HANDLER)

//eg

app.get('/', function (req, res) {
  res.send('Hello World!')
})

参数

  • METHOD是一个HTTP 请求方法,小写。

  • PATH是服务器上的路径。

  • HANDLER是路由匹配时执行的函数。

2. 路由的匹配过程

每当一次请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数

匹配时,按照路由顺序进行匹配,如果请求类型和请求url同时匹配成功。才会调用函数

注意

  • 按照定义的先后顺序进行匹配
  • 请求类型和请求URL同时匹配成功,才会调用对应的处理函数

2.2 路由的使用

1. 最简单的用法

挂载到app上

// GET method route
app.get('/', function (req, res) {
  res.send('GET request to the homepage')
})

// POST method route
app.post('/', function (req, res) {
  res.send('POST request to the homepage')
})
2. 模块化路由

为了方便对路由进行模块化管理。将路由抽离为单独的模块,实现步骤如下

  • 创建路由模块对应js文件
  • 调用express.Router() 函数创建路由对象
  • 向路由对象挂载具体的路由
  • 使用module.exports 向外共享路由对象
  • 使用app.use() 函数注册路由模块
// 路由模块
// 导入Express
const express = require("express");

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

// 挂载具体路由

router.get('/user/list', (req, res) => {
    res.send('Get user list.')
})

router.post('/user/add', (req, res) => {
    res.send('POST user add.')
})

// 向外导出路由对象
module.exports = router


// 在新的服务器.js中
// 导入路由模块
const router = require('./12.router')

// 注册路由模块
// app.use() 函数作用,就是来注册全局中间件
app.use(router)
3. 为路由模块添加前缀

类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀一样

// app.use() 函数作用,就是来注册全局中间件
app.use('/api', router)

3. Express 中间件

3.1 概念

中间件:特指业务流程的中间处理环节

1. Express中间件的调用流程

当一个请求到达Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ureDpv3-1650958673647)(D:\A_study\Node\study_image\1.中间件流程.jpg)]

2. Express中间件的格式

本质上就是一个function处理函数

app.get('/user/:id', function (req, res, next) {
  res.send('USER')
})

注意:中间件函数的形参列表中,必须包含next函数,而路由处理函数只包含req 和 res

3. next函数的作用

next函数 是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由

3.2 Express中间件初体验

1. 定义中间件函数
// 定义一个中间件函数
const mw = function(req, res, next) {
    console.log('简单的中间件函数');

    // 把流转关系,转交给下一个中间件函数
    next()
}
2. 全局生效的中间件

客户端发起的任何请求,到达服务器之后,都会触发中间件,叫做全局生效的中间件

通过 app.use(中间件函数) ,即可定义一个全局生效的中间件

// 将mw 注册为全局生效的中间件
app.use(mw)
3. 定义全局中间件简化形式
app.use(function(req, res, next) {
    console.log('这是简单的中间件函数');

    next()
})

4. 中间件的作用

多个中间件之间,共享一份req 和 res,基于这样特性,可以在上游的中间件中,统一req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用

app.use(function(req, res, next) {
    // 获取到请求到达服务器的时间
    const time = Date.now()

    // 为req对象,挂在自定义属性,从而把时间共享给后面所有路由
    req.startTime = time

    next()
})

app.get('/', (req, res) => {

    res.send('Home page.' + req.startTime)
})

app.get('/user', (req, res) => {
    res.send('User page.' + req.startTime)
})

5. 定义多个全局中间件

可以使用app.use() 连续定义多个全局中间件,客户端请求到达服务器之后,会按照中间件定义的先后顺序一次进行调用

// 第一个全局中间件
app.use(function(req, res, next) {
    console.log('调用了第一个全局中间件');
    next()
})

// 第二个全局中间件
app.use(function(req, res, next) {
    // 获取到请求到达服务器的时间
    console.log('调用了第二个全局中间件');
    next()
})

// 结果
http:/127.0.0.1
调用了第一个全局中间件
调用了第二个全局中间件
6. 局部生效的中间件

不适用app.use() 定义的中间件,叫做局部生效的中间件

const mw1 = (req, res, next) => {
    console.log('调用了局部生效的中间件');
    next()
}

// 只在当前路由生效
app.get('/', mw1, (req, res) => {

    res.send('Home page.')
})

7. 定义多个局部的中间件
app.get('/', mw1,mw2, (req, res) => {res.send('Home page.')})
// 等价
app.get('/', [mw1,mw2], (req, res) => {res.send('Home page.')})
8. 了解中间件的5个生效方式
  • 一定要在路由之前 注册中间件
  • 客户端发送过来的请求,可以连续调用多个中间件进行处理
  • 执行完中间件的业务代码后,不要忘记next() 函数
  • 为了防止代码逻辑混乱,调用next() 函数后不要再写额外的代码
  • 连续调用多个中间件时,多个中间件之间,共享req 和 res对象

3.3 中间件的分类

为了方便理解记忆中间件使用,分为5大类

  • 应用及的中间件
  • 路由级别的中间件
  • 错误级别的中间件
  • Express 内置的中间件
  • 第三方的中间件
1. 应用级别的中间件

通过app.use() 或 app.get() 或 app.post(),绑定到app实例上的中间件,叫做应用级别的中间件

2. 路由级别的中间件

绑定到express.Route() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别的没有任何区别,只是一个绑定在app实例上,一个绑定在router实例上

var express = require('express')
var app = express()
var router = express.Router()

router.use(function (req, res, next) {
  console.log('Time:', Date.now())
  next()
})
3. 错误级别的中间件

错误处理中间件总是需要四个参数。必须提供四个参数以将其标识为错误处理中间件函数。即使您不需要使用该next对象,您也必须指定它来维护签名。否则,该next对象将被解释为常规中间件并且无法处理错误。

作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题

app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

//eg

// 只在当前路由生效
app.get('/', mw1, (req, res) => {
    throw new Error('服务器内部方式错误');
    res.send('Home page.')
})

app.get('/user', (req, res) => {
    res.send('User page.')
})

app.use((err, req, res, next) => {
    console.log('发生了错误 ' + err.message);
    res.send('Error' + err.message)
})

注意:错误级别的中间件,必须注册在所有路由之后

4. Express内置的中间件

Express内置3个常用的中间件,极大提高Express项目的开发效率和体验

  • express.static:快速托管静态资源的内置中间件(无兼容性)
  • express.json:解析json格式的请求体数据(有兼容性,仅在4.16.0+版本中是使用)
  • express.urlencoded:解析URL -encoded格式的请求体数据(有兼容性,仅在4.16.0+版本中是使用)
// 配置解析 application/json 格式数据的内置中间件
app.use(express.json())

//配置解析 application/x-www-form-urlencoded 格式化数据的内置中间件
app.use(express.urlencoded({ extended:false }))
  • express.json的使用
const express = require('express')

const app = express()

// 注意:除了错误级别的中间件,其他的中间件必须在路由之前配置
// 通过 express.json() 解析表单中的 JSON 格式 的数据
app.use(express.json())

app.post('/user', (req, res) => {
    // 在服务器,可以使用req.body这个属性,来接受客户端发送过来的请求数据
    // 默认情况下,如果不配置解析表单数据的中间件,则req.body默认等于 underfined
    console.log(req.body);
    res.send('ok')
})

app.listen(80, () => {
    console.log('http:/127.0.0.1');
})
5. 第三方中间件

非Express官方内置的,叫做第三方中间件,可以按需下载并配置第三方中间件

经常使用body-parser,来解析请求体数据,使用步骤如下

  • 运行npm install body-parser 安装中间件
  • 使用require 导入中间件
  • 调用 app.use() 注册并使用中间件
const express = require('express')

const app = express()

const parser = require('body-parser')

app.use(parser.urlencoded({ extended: false }))

app.post('/user', (req, res) => {
    // 如果没有配置任何解析数据的中间件,则req.body 默认等于 undefined
    console.log(req.body);
    res.send('ok')
})

app.listen(80, () => {
    console.log('http:/127.0.0.1');
})

注意:Express内置的express.urlencoded 中间件,就是基于 body-parser 这个第三方中间件进一步封装出来的

3.4 自定义中间件

1. 需求描述与实现步骤

自己手动迷你一个类似express.urlencoded 这样的中间件,来解析POST 提交的服务器表单数据

实现步骤

  • 定义中间件
  • 监听req的 data 事件
  • 监听 req 的 end 事件
  • 使用 querystring 模块解析请求体数据
  • 将解析出来的数据对象挂载为req.body
  • 将自定义中间件封装为模块
2. 监听req的 data 事件

需要监听req 对象的data 事件,来获取客户端发送到服务器的数据

如果数据量比较大,无法一次发送完毕,则客户端会把数据切割后,分批发送给服务器

data事件可能会触发多次

let str = '';
// 监听 req 的 data 事件
req.on('data', (chunk) => {
	str += chunk
})
3. 监听 req 的 end 事件

当请求体数据接收完毕之后,自动出发 req 的 end 事件,然后拿到并处理完整的请求体数据

req.on('end', () => {
	// 在str中存放的完整的请求体数据
	console.log(str);
	// TODO: 把字符串格式的请求体数据,解析成对象格式
})
4. 使用 querystring 模块解析请求体数据

Node.js 内置一个querystring模块,专门用来处理查询字符串,通过这个模块 parse() 函数,可以轻松查询字符串,解析成对象格式

5. 将解析出来的数据对象挂载为req.body

上游的中间件和下游的中间件及路由之间,共享同一分req 和 res。因此可以挂载

// 监听 req 的 end 事件
    req.on('end', () => {
        // 在str中存放的完整的请求体数据
        // console.log(str);
        // TODO: 把字符串格式的请求体数据,解析成对象格式
        const body = qs.parse(str)
        req.body = body
        next()
    })
6. 将自定义中间件封装为模块
// test.js
const express = require('express')

const app = express()

// 导入Node.js内置的querystring模块
const qs = require('querystring')

//这是解析表单数据的中间件
const customBodyParser = require('./20.custom-body-parser')
app.use(customBodyParser)

app.post('/user', (req, res) => {
    // 如果没有配置任何解析数据的中间件,则req.body 默认等于 undefined
    console.log(req.body);
    res.send(req.body)
})

app.listen(80, () => {
    console.log('http:/127.0.0.1');
})

// 20.custom-body-parser.js
const qs = require('querystring')

const bodyParser = (req, res, next) => {
    // 定义中间件具体的业务逻辑

    let str = '';
    // 监听 req 的 data 事件
    req.on('data', (chunk) => {
        str += chunk
    })

    // 监听 req 的 end 事件
    req.on('end', () => {
        // 在str中存放的完整的请求体数据
        // console.log(str);
        // TODO: 把字符串格式的请求体数据,解析成对象格式
        const body = qs.parse(str)
        req.body = body
        next()
    })
}
module.exports = bodyParser

4. 使用Express 写接口

  1. ​ // req.query 查询字符串解析出来的对象 username=zhangsan&password=123 { username:zhangsan }
  2. ​ // req.route 当前匹配的路由 正则表达式
  3. ​ // req.params 获取路由匹配的参数
  4. ​ // req.body post发送的数据解析出来的对象

4.1 创建基本的服务器

// 导入express
const express = require('express')

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

// 可以定义接口

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

4.2 创建 API 路由模块

// apiRouter.js
const express = require('express')

const router = express.Router

module.exports = router

//使用express写接口.js
// 可以定义接口
const router = require('./23.apiRouter')
app.use('/api', router)

4.3 编写GET接口

router.get('/get', (req, res) => {
    // 通过req.query    获取客户端通过查询字符串,发送给服务器的数据
    const query = req.query
        //调用send() 方法,向客户端相应处理的结果

    res.send({
        status: 0, // 0 表示成功,1 表示失败
        msg: 'GET 请求成功',
        data: query // 相应给客户端的数据
    })
})

4.4 编写POST接口

// apiRouter.js
// 定义POST接口
router.post('/post', (req, res) => {
    // 通过req.body 获取请求体中包含的 URL-encoded 格式的数据
    const body = req.body

    // 调用res.send() ,向客户端响应结果
    res.send({
        status: 0,
        msg: 'POST 请求成功!',
        data: body
    })
})

// 使用express写接口.js
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))

注意:如果获取URL-encoded格式的请求体数据,必须配置中间件app.use(express.urlencoded({extend:false}))

4.5 CORS 跨域资源共享

1. 接口跨域问题

上述编写的GET、POST接口,一个严重的问题就是不能跨域

Access to XMLHttpRequest at 'http://127.0.0.1/api/get?name=zs&age=20' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

访问由如上错误

解决接口跨域问题的解决方案主要有两种:

  • CORS(主流的解决方案,推荐使用)
  • JOSONP(有缺陷的解决方案:只支持GET请求)
2. 使用cors中简介解决跨域问题

cors时Express的一个第三方中简件,通过安装和配置cors中简件,可以很方便解决跨域问题

使用步骤分为如下3步:

  • 运行 npm install cors //安装中间件
  • 使用 const cors = require(‘cors’) //导入中简介
  • 在路由之前调用 app.use(cors()) //配置中简件
// 一定要在路由之前配置 cors 这个中间件,从而解决接口跨域问题
const cors = require('cors')
app.use(cors())
3. 什么是cors

cors(跨域资源共享)由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端js带啊吗跨域获取资源,浏览器的同源策略默认会组织网页 ”跨域“ 获取资源

如果配置了CORS 相关的 HTTP 响应头,可以接触浏览器端的跨域访问限制

4. cors注意事项
  • CORS主要在服务器端进行配置,客户端浏览器无法做任何额外的配置,即可请求开启CORS的接口
  • CORS在浏览器由兼容性,只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问CORES的服务器端口
5. CORS 响应头部 Access-Control-Allow-Origin

响应头部可以携带一个 Access-Control-Allow-Origin 字段,其语法如下

Access-Control-Allow-Origin: | *

origin参数的值指定了允许访问该资源的外域URL

eg,下面字段将只允许来自http://itheima.cn的请求

res.setHeader('Access-Control-Allow-Origin','http://itheima.cn')

如果值为 通配符*,表示允许来自任何域的请求

res.setHeader('Access-Control-Allow-Origin','*')
6. CORS 响应头部 Access-Control-Allow-Headers

默认情况下 cors仅支持客户端向服务器发送如下9个请求头:
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Tyoe(值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一)

如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败

res.setHeader('Access-Control-Allow-Headers','Content-Type,X-Custom-Header')
7.CORS响应头部 -Access-Control-Allow-Methods

默认情况下 CORS仅支持客户端发起GET、POST、HEAD请求
如果客户端希望通过PUTDELETE等方式请求服务器的资源、则需要在服务器端、通过Access-Control-Allow-Methods来指明实际请求所允许使用的HTTP方法

res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
// 允许所有的 HTTP 的请求方式
res.setHeader('Access-Control-Allow-Methods','*')
8. CORS请求的分类

客户端在请求CORS接口时,根据请求方式和请求头的不同,可以将CORS的请求分为两大类,分别是

  • 简单请求
  • 预检请求
9. 简单请求

满足两大条件,就属于简单请求

  • 请求方式:GET、POST、HEAD三者之一
  • HTTP头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
10. 预检请求

只要符合以下任何一个条件的请求,都需要进行预检请求

  • 请求方式:GET、POST、HEAD之外的请求Method类型
  • 请求头中包含自定义头部
  • 向服务器发送了 application/json 格式的数据

在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为“预检请求” 。服务器成功响应预检请求后,才会发送真正的请求,并且携带真是数据

11. 简单请求和预检请求的区别
  • 简单请求的特点:客户端和服务器之间只会发生一次请求
  • 预检请求的特点:客户端和服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求

4.6 JSONP 接口

1. 回顾JSONP 的概念与特点

概念:浏览器通过

你可能感兴趣的:(前端)