• Node 入门


    Node.js

    一、模块化介绍

    1.1 CommonJS 规范

    • CommonJS规范的提出,主要是为了弥补当前JavaScript没有标准的缺陷
    • 希望JS能够在任何地方运行
    • 对模块的定义:
      1. 模块应用
      2. 模块定义
      3. 模块标识
    1.1.1 模块应用–require API

    ​ 在node中,通过require()函数来引入外部的模块,返回一个对象。参数是一个文件的路径。如果使用相对路径,必须以 . 或 .. 开头

    let md = require('./xxx.js')
    
    • 1
    1.1.2 模块定义–exports
    • 在Node中,一个js文件就是一个模块。

    • 在Node中,每一个js文件中的js代码都是独立运行在一个函数中,而不是全局作用域。一个模块无法访问其他模块的变量或函数。

      function (exports, require, module, __filename, __dirname) {
          /*
      	exports:将变量或函数暴露到外部;
      	require:函数,引入外部的模块;
      	module:代表当前模块本身; 
      	module.exports===exports(true)
      	__filename:当前模块的路径;
      	__dirname:当前模块所在文件夹的路径;
      	*/
          const math = require("./math");
          let x = 1,
              y = 2;
          console.log(math.add(x, y));
          console.log(arguments.callee + "");
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • 可以使用exports向外部暴露属性或方法。

    1.1.3 模块标识
    • 可以通过模块标识找到某一个模块,路径是其中一种标识。

    • 模块:

      1. 核心模块

        node引擎提供的模块,直接引用 模块名字。

      2. 文件模块

        用户自己创建的。

    1.1.4 exports 和 module.exports

    二、包 package

    • CommonJS的包规范允许我们将一组相关的模块组合到一起,形成一组完整的工具。

    • CommonJS的包规范由 包结构包描述文件 两个部分组成。

    • 包结构

      用于组织包中的各种文件。

      包实际上就是一个压缩文件,解压以后还原为目录。符合规范的目录,应该包含如下文件︰

      1. package.json 描述文件

        npm init -y // 快速创建package.json文件
        
        • 1
      2. package-lock.json 记录node_modules 目录下每个包的下载信息,例如名字、版本号、下载地址等;

      3. bin 可执行二进制文件;

      4. lib js代码;

      5. doc 文档;

      6. test 单元测试;

    • 包描述文件

      描述包的相关信息,以供外部读取分析。

      2.1 开发属于自己的包

      步骤:

      1. 新建包文件夹,作为包的根目录

      2. 在文件夹中创建

        package.json 包管理配置文件

        {
            "name": "szh-utils",
            "version": "1.0.1",
            "main": "index.js",
            "description": "我的第一个工具包",
            "keywords": ["szh", "format", "escape"],
            "license": "ICS"
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8

        index.js 入口文件

        const dateFormat = require('./dateFormat')
        const escape = require('./htmlEscape')
        
        module.exports = {
            ...dateFormat,
            ...escape
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7

        README.md 包的说明文档

        #安装
        ```
        npm install szh-utils
        ```
        
        #导入包
        ```js
        const szh_utils = require('szh-utils') 
        ```
        
        #格式化时间
        ```js
        const dateStr = new Date()
        const formatStr = szh_utils.dateFormat(dateStr)
        console.log(formatStr);
        ```
        
        #转义特殊字符
        ```js
        const htmlStr = '

        测试 转义特殊字符

        ' const escapeStr = szh_utils.htmlEscape(htmlStr) console.log(escapeStr) const unescapeStr = utils.unHtmlEscape(escapeStr) console.log(unescapeStr) ```
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
      3. 发布包

        到官网注册一个账号

        https://www.npmjs.com/

        在终端进行登录

        npm login
        # 依次输入用户名、密码、邮箱
        
        • 1
        • 2

        把包发布到npm上

        npm publish
        
        • 1

        删除已发布的包

        npm unpublish 包名 --force
        
        • 1
        • 只能删除72小时以内发布的包
        • 24小时内不能重复发布相同的包

    三、NPM (Node Package Manager)

    ​ 对于Node而言,NPM帮助其完成了第三方模块的发布安装依赖等。借助NPM,Node与第三方模之间形成了很好的一个生态系统。

    • NPM命令

      npm – 帮助说明

      npm search 包名 – 搜索模块包

      npm remove/r 包名 --删除包

      npm install/i 包名 --save --安装并添加到依赖中

      npm install --根据package.json的配置下载所需要的包

      npm install -g --全局安装包 (不止在项目里用的)

    • cnpm

      淘宝镜像安装

      npm install -g cnpm --registry=https://taobao.org
      
      • 1
      cnpm install xxx
      
      • 1
    • 切换npm的下包镜像源

      # 查看当前的下包镜像源
      npm config get registry
      # 切换为淘宝的
      npm config set registry = https://registry.npm.taobao.org/
      
      • 1
      • 2
      • 3
      • 4
    • nrm

      # nrm 可以方便的进行镜像源的切换
      # 安装
      npm i nrm -g
      # 查看
      nrm ls
      # 切换
      nrm use taobao
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • i5ting_toc

      可以将 md 文档转换成 html 页面的小工具。

      npm install -g i5ting_toc
      i5ting_toc -f md文档的路径 -o
      
      • 1
      • 2

    四、Node 搜索包的流程

    ​ node在使用模块名字来引入模块时,它会首先在当前目录的node_modules中寻找是否含有模块。

    ​ 如果有则直接使用,没有则去上一级目录的node_modules中寻找,直至找到为止。如果在最后(磁盘根目录)依然没有找到,则会报错。

    五、Buffer缓冲区

    /*
     *  Buffer (缓冲区)
     *      1.结构和操作方法和数组类似
     *      2.数组不能存储二进制的文件,而buffer则是专门用来存储二进制数据的
     *      3.它的元素为一个16进制的两位数,存储是二进制数据,显示是16进制形式
     *      4.实际上一个元素就表示内存中的一个字节
     *      5.实际上buffer中的内存不是通过JavaScript分配的,而是在底层通过C++申请		   的
     *      6.我们可以直接通过buffer来创建内存中的空间
     *      7.使用buffer不需要引入模块,直接使用
     *      8.buffer大小确定后不能重新修改,是对底层内存的直接操作
     * */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • Buffer.from(string): 存储字符串 string 到buffer中;
    • Buffer.alloc(size): 创建固定size大小的内存空间并初始化
    • Buffer.allocUnsafe: 创建固定size大小的内存空间,不初始化,内存中可能包含敏感数据;
    • buf.fill(0): 用0填充buffer;

    六、文件系统模块

    6.1 文件写入

    const fs = require("fs");
    /*
        文件的写入:
            1-打开文件
            2-写入内容
            3-保存关闭文件
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    6.1.1 同步文件写入
    /*
     * fs.openSync(path, flags[, mode]) 同步文件写入
     *   返回一个表示文件描述符的整数
     *      path: 路径
     *      flags: 文件类型 r 只读 w 可写
     * */
    let fd1 = fs.openSync("hello.txt", "w");
    // console.log(fd1);
    /*
     * fs.writeSync(fd, string[, position[, encoding]])
     *   返回写入的字节数。
     *      fd: 文件对象
     *      string: 写入内容
     *      position: 可选 写入的起始位置
     *      encoding: 可选 默认utf-8
     * */
    let fw1 = fs.writeSync(fd1, "hello node");
    // console.log(fw1);
    /*
     *  fs.closeSync(fd)
     *      返回 undefined。
     *      fd: 要关闭的文件
     * */
    let fc1 = fs.closeSync(fd1);
    // console.log(fc1);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    6.1.2 异步文件写入
    /*
     * 异步文件写入
     *  打开文件: fs.open(path, flags[, mode], callback)
     *      没有返回值(异步方法没有返回值,结果在回调函数中返回)
     *      callback: 回调函数
     *          err: 错误对象,没有错误则为null
     *          fd: 文件描述符
     *      flags: 'a' 以追加模式打开文件
     *  写入文件: fs.write(fd, string[, position[, encoding]], callback)
     *      callback 有三个参数 (err, written, string),
     *          written 指定传入的字符串被写入多少字节.
     *  关闭文件: fs.close(fd, callback)
     *      callback 只有 err 一个参数
     * */
    fs.open("hello2.txt", "a", function (err, fd) {
      console.log(arguments);
      if (!err) {
        console.log(fd);
        fs.write(fd, "异步写入", function (err, written, string) {
          if (!err) {
            console.log(written);
            console.log(string);
          }
          fs.close(fd, function (err) {
            if (!err) {
              console.log("异步文件写入已关闭...");
            }
          });
        });
      }
    });
    
    /*
     * 同步任务不存在异常处理,如果有一个地方出现错误,则整个程序都会出错
     *   异步任务可以去进行异常判断,避免因某个地方出错而影响整个程序
     * */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    6.1.3 简单文件写入
    /*
     * 简单文件写入
     *  fs.writeFileSync(file, data[, options])
     *  fs.writeFile(file, data[, options], callback)
     *    file: 文件路径
     *    data: 写入数据
     *    options: 可选,对写入进行配置
     *        encoding: 默认utf-8
     *        mode: 默认0o666
     *        flag: 默认 w
     *    callback: 回调函数
     * */
    fs.writeFile("hello3.txt", "简单文件写入", { flag: "a" }, function (err) {
        if (err) throw err;
        console.log("The file has been saved!");
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    6.1.4 流式文件写入
    /*
     * 流式文件写入
     *    同步、异步、简单文件写入不适合大文件写入,性能较差,容易导致内存溢出
     *    1-创建一个可写流 fs.createWriteStream(path[, options])
     *      返回一个新建的 WriteStream 对象。
     *        path: 文件路径
     *        options: 可选
     *    2-监听流的打开和关闭状态
     *      WriteStream对象.once("open",function(){}),once只在打开时执行一次
     *      WriteStream对象.once("close",function(){})
     *      WriteStream对象.on("open",function(){}),on 持续事件,过于浪费
     * */
    //创建
    let ws = fs.createWriteStream("hello4.txt");
    //监听
    ws.once("open", function () {
        console.log("监听写入流的打开");
    });
    ws.once("close", function () {
        console.log("监听写入流的关闭");
    });
    // 持续写入
    ws.write("通过写入流进行文件内容的写入...");
    ws.write("111");
    ws.write("222");
    ws.write("333");
    //关闭
    ws.end();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    6.2 文件读取

    6.2.1 简单文件读取
    const fs = require("fs");
    /*
     * 简单文件读取
     *  fs.readFileSync(path[, options])
     *      如果指定了 encoding 选项,则该函数返回一个字符串,否则返回一个 buffer。
     *  fs.readFile(path[, options], callback)
     * */
    let fr1 = fs.readFileSync("hello4.txt");
    console.log(fr1);
    let fr2 = fs.readFileSync("hello3.txt", "utf-8");
    console.log(fr2);
    
    fs.readFile("hello.txt", (err, data) => {
        if (!err) {
            console.log(data.toString()); // 没有指定编码类型则返回 buffer类型
        } else {
            throw err;
        }
    });
    
    fs.readFile("moon.png", (err, data) => {
        if (!err) {
            console.log(data); // 没有指定编码类型则返回 buffer类型
            fs.writeFile("moon2.png", data, (err) => {
                if (!err) {
                    console.log("复制图片成功");
                }
            });
        } else {
            throw err;
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    6.2.2 流式文件读取
    /*
     * 流式读取
     *   适合大文件,分多次读取
     *    fs.createReadeStream() 创建可读流
     *    rs.on("data",(data)=>{}) 绑定data事件
     *      事件绑定完毕,自动开始读取数据
     * */
    let rs = fs.createReadStream("moon.png");
    let ws = fs.createWriteStream("moon3.png");
    rs.once("open", function () {
        console.log("可读流打开");
    });
    rs.once("close", function () {
        console.log("可读流关闭");
        // 数据读取完毕,关闭可写流
        ws.end();
    });
    ws.once("open", function () {
        console.log("可写流打开");
    });
    ws.once("close", function () {
        console.log("可写流关闭");
    });
    rs.on("data", (data) => {
        console.log(data);
        console.log(data.length);
        ws.write(data);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    /* pipe() 可以将可读流数据直接输出到可写流中 */
    rs.pipe(ws);
    
    • 1
    • 2
    6.2.3 练习
    //成绩.txt
    小红=99 小白=100 小黄=70 小黑=66 小绿=88
    
    • 1
    • 2
    //成绩_copy.txt
    小红:99
    小白:100
    小黄:70
    小黑:66
    小绿:88
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    const fs = require("fs");
    
    fs.readFile("./file/成绩.txt", "utf-8", (err, data) => {
        if (err) throw err;
        let str = data.replace(/\s/g, "\r\n");
        str = str.replace(/=/g, ":");
        fs.writeFile("./file/成绩_copy", str, (err) => {
            if (err) throw err;
            console.log("转换成功~");
        });
    });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    6.2.4 其他操作
    • 验证路径是否存在

      fs.existsSync(path)
      
      • 1
    • 获取文件信息

      fs.stat(path,callback)
      fs.statSync(path)
      
      • 1
      • 2
    • 删除文件

      fs.unlink(path,callback)
      fs.unlinkSync(path)
      
      • 1
      • 2

    6.3 路径动态拼接问题

    ​ 在代码运行的时候,会以执行node命令时所处的目录,动态拼接处被操作文件的完成路径。

    ​ 解决方法:使用绝对路径

    // __dirname表示当前文件所处的目录
    fs.readFile(__dirname + '/file/成绩.txt',(err,data)=>{})
    
    • 1
    • 2

    七、path 路径模块

    7.1 定义

    path模块是 Node.js 官方提供的,用来处理路径的模块,它提供了一系列的方法和数学。

    7.2 使用

    const path = require("path")
    
    • 1
    • path.join(): 把多个路径片段拼接为完整的路径字符串;

      const pathStr = path.join(__dirname + "./file/hello.txt")
      
      • 1

      注意: ../会抵消上一层路径。

    • path.basename(): 获取路径的最后一部分;

    • path.extname():获取路径中的文件扩展名;

    7.3 时钟案例

    实现思路:

    1. 导入fs``path模块;
    2. 定义正则表达式,匹配style、script标签;
    3. 调用模块fs的readFile()读取html文件;
    4. 自定义提取方法,分别写入到各自文件中;
    /* 引入模块 */
    const fs = require("fs");
    const path = require("path");
    
    /* 定义正则表达式,匹配style、script标签; */
    const regStyle = /", "")
        if (data) {
            fs.writeFile(path.join(__dirname, "/clock/clock.css"), data, err => {
                if (err) return console.log('css文件写入错误' + err.message);
                console.log('css文件写入成功');
            })
        }
    }
    
    function resolveJs(model) {
        const jsModel = regScript.exec(model)
        const data = jsModel[0].replace("", "")
        if (data) {
            fs.writeFile(path.join(__dirname, "/clock/clock.js"), data, err => {
                if (err) return console.log('js文件写入错误' + err.message);
                console.log('js文件写入成功');
            })
        }
    }
    
    /* 调用模块fs的` readFile() `读取html文件 */
    fs.readFile(__dirname + "/index.html", "utf-8", (err, data) => {
        if (err) return console.log(err.message);
        resolveHtml(data)
        resolveCss(data)
        resolveJs(data)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    注意writeFile()只会创建文件,不会创建路径

    八、HTTP模块

    8.1 什么是HTTP模块

    ​ 在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器。

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

    ​ 修改XAMPP Apache路径:

    /*将下面两个改成想要存放网站的路径*/
    DocumentRoot "D:/XAMPP/htdocs"
    <Directory "D:/XAMPP/htdocs">
    
    • 1
    • 2
    • 3

    8.2 作用

    ​ 在 Node.js 中,我们不需要使用 IIS、Apache 等这些第三方 web 服务器软件。因为我们可以基于 Node.js 提供的 http 模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供 web 服务。

    8.3 服务器相关概念

    • IP地址

      IP 地址就是互联网上每台计算机的唯一地址,因此 IP 地址具有唯一性。

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

      注意

      1. 互联网中每台 Web 服务器,都有自己的 IP 地址,例如:大家可以在 Windows 的终端中运行 ping www.baidu.com 命令,即可查看到百度服务器的 IP 地址;
      2. 在开发期间,自己的电脑既是一台服务器,也是一个客户端,为了方便测试,可以在自己的浏览器中输入 127.0.0.1 这个 IP 地址,就能把自己的电脑当做一台服务器进行访问了。
    • 域名域名服务器

      ​ 尽管 IP 地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名(Domain Name)地址

      ​ IP地址和域名是一一对应的关系,这份对应关系存放在一种叫做域名服务器(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供 IP 地址和域名之间的转换服务的服务器

      注意

      ​ 在开发测试期间, 127.0.0.1 对应的域名是 localhost,它们都代表我们自己的这台电脑,在使用效果上没有任何区别

    • 端口号

    在这里插入图片描述

    8.4 创建最基本的web服务

    步骤

    1. 导入http模块
    2. 创建web服务器实例
    3. 给服务器实例绑定request事件,监听客户端的请求
    4. 启动服务器
    const http = require("http")
    
    // 创建实例
    const server = http.createServer()
    
    // 绑定request
    server.on('request', (req, res) => {
        console.log('访问 web 服务器');
    })
    
    // 启动
    server.listen(80, () => {
        console.log('running at 80端口');
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    8.5 实现clock时钟的web服务器

    核心思路:把文件的实际存放路径,作为每个资源的请求URL地址

    九、Express

    9.1 简介

    • 是什么?

    Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。

    通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的。

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

    https://www.expressjs.com.cn/

    • 能做什么?

      使用 Express,我们可以方便、快速的创建 Web 网站的服务器或 API 接口的服务器。

      Web 网站服务器:专门对外提供 Web 网页资源的服务器。

      API 接口服务器:专门对外提供 API 接口的服务器。

    9.2 使用

    9.2.1 托管静态资源

    express.static()可以非常方便地创建一个静态资源服务器, 例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了。

    app.use(express.static('public'))
    
    • 1
    9.2.2 nodemon

    https://www.npmjs.com/package/nodemon

    ​ 使用 nodemon 这个工具,它能够监听项目文件 的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。

    9.3 路由

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

    ​ 路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数。

    9.3.1 路由的使用
    9.3.2 模块化路由
    1. 创建路由模块对应的.js文件

    2. 调用express.Router()函数创建路由对象

      const router = express.Router()
      
      • 1
    3. 向路由对象上挂载具体的路由

      router.get('/user/list', (req, res) => {
          res.send('获取用户列表')
      })
      router.all('/user/add', (req, res) => {
          res.send('添加用户')
      })
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    4. 使用module.exports向外共享路由对象

      module.exports = router
      
      • 1
    5. 使用app.use()函数注册路由模块

      // 导入路由模块
      const userRouter = require('./router')
      // 注册路由模块
      app.use('/api', userRouter)
      
      • 1
      • 2
      • 3
      • 4
    6. 完整代码

      /*router.js*/
      const express = require('express')
      const router = express.Router()
      
      router.get('/user/list', (req, res) => {
          res.send('获取用户列表')
      })
      router.all('/user/add', (req, res) => {
          res.send('添加用户')
      })
      
      module.exports = router
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      /*index.js*/
      const express = require('express')
      const app = express()
      
      // 导入路由模块
      const userRouter = require('./router')
      
      // 注册路由模块
      app.use('/api', userRouter)
      
      app.listen(8080, () => {
          console.log('监听8080端口,学习express路由模块');
      })
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

    9.4 中间件

    9.4.1 什么是中间件?
    • 中间件(Middleware),特指业务流程中间处理环节

    • 作用

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

      • 当一个请求到达Express的服务器后,可以连续调用多个中间件,从而这次请求进行预处理
    • 格式
      在这里插入图片描述

    • next()函数的作用

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

    9.4.2 中间件的使用
    • 简单中间件函数

      /**
       * 简单中间件
       */
      const mw1 = function(req, res, next) {
          console.log('简单中间件');
          next()
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 全局中间件

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

      /**
       * 全局中间件
       */
      const mw2 = function(req, res, next) {
          console.log('全局中间件')
          next()
      }
      app.use(mw2)
      /**
       * 简化形式
       */
      app.use(function(req, res, next) {
          console.log('简化形式')
          next()
      })
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

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

    • 局部中间件

      /**
       * 局部中间件
       */
      const mw3 = function(req, res, next) {
          console.log('局部中间件')
          next()
      }
      app.get('/', mw3, function(req, res) {
          // 只在当前路由中生效
          res.send('111')
      })
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    9.4.3 中间件的分类
    • 应用级别

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

      /**
       * 应用级别
       */
      app.use((req, res, next) => {
          next()
      })
      app.get('/', mw, (req, res) => {
          res.send('局部')
      })
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 路由级别

      绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。

      /**
       * 路由级别
       */
      const router = express.Router()
      router.use((req, res, next) => {
          next()
      })
      app.use('/', router)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 错误级别

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

      /**
       * 错误级别
       */
      app.get('/', (req, res) => {
          throw new Error('服务器内部发生了错误!')
          res.send('111')
      })
      app.use((err, req, res, next) => {
          console.log(err.message)
      })
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

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

    • Express内置的中间件

      自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:

      • express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)

      • express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)

      • express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)

        url-encoded格式也叫form 格式或 x-www-form-urlencoded格式;

    • 第三方中间件

    • 自定义中间件

      需求:实现类似express.urlencoded中间件,用于解析POST提交的表单数据

      思路:

      • 定义中间件;
      • 监听reqdata事件,将请求的数据保存到自定义变量str中;
      • 监听reqend事件,在请求体发送完毕时触发,将变量str中的数据使用express内置模块querystring(现在已经被弃用了,但是还是可以使用)进行解析(可以将查询字符串=>对象格式),并将解析结果挂载回req.body上;
      • 将自定义中间件封装为模块

      实现:

      /*自定义中间件.js*/
      /**
       * 内置模块:
       *  用来处理查询字符串,
       *  通过parse()函数,可以将查询字符串解析成对象格式
       */
      const qs = require('querystring')
      
      function myUrlencoded(req, res, next) {
          let str = '' //用来保存请求体中的数据 
          req.on('data', chunk => {
              str += chunk
          })
          req.on('end', function() {
              // console.log(argument);
              req.body = qs.parse(str)
              next()
          })
      }
      module.exports = myUrlencoded
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      /*router.js 中间件的路由模块*/
      const express = require('express')
      const router = express.Router()
      
      router.all('/mw', (req, res) => {
          res.setHeader('Access-Control-Allow-Origin', '*')
          res.setHeader('Access-Control-Allow-Headers', '*')
          res.send('OK')
      })
      
      module.exports = router
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      /*test.js 测试模块*/
      const express = require('express')
      const app = express()
      const mwRouter = require('./router')
      const myUrlencoded = require('./自定义中间件')
      
      app.use(myUrlencoded) // 中间件需要在路由之前注册
      app.use(mwRouter)
      
      
      app.listen(80, () => {
          console.log('running at 80...');
      })
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
  • 相关阅读:
    C语言K&R圣经笔记 2.4声明 2.5算术操作符 2.6关系和逻辑操作符
    如何正确使用 WEB 接口的 HTTP 状态码和业务状态码?
    电子统计台账:设置能自动合并数据的垂直过滤模板
    EasyExcel动态表头导出
    Go语言集成开发环境(IDE):GoLand 2023中文
    SQL注入漏洞代码分析
    Vue+iview 组件中通过v-for循环动态生成form表单进行表单校验
    【Linux】部署单机OA项目及搭建spa前后端分离项目
    Java#28(集合进阶1---单列集合)
    蓝绿部署:实现无缝可靠的软件发布
  • 原文地址:https://blog.csdn.net/SUZEHUI9636/article/details/127757117