# 认识 Node.js

Node.js 是一门服务器编程语言,它也遵循了 ECMAScript 语法规范,在此规范的基础上加入了 Node.js API,包含处理 http 请求、处理文件、socket 编程等。Node.js API 和 ECMAScript 两者结合组成了 Node.js,完成 Server 端的任何操作,为客户端浏览器进行服务。

我们快一起去探索 Node.js 的世界吧~

# 知识点

  • Node.js 的介绍
  • 下载和安装 Node.js

# Node.js 的介绍

在实验介绍中说了,Node.js 是一门服务器编程语言。它发布于 2009 年 5 月,由 Ryan Dahl 开发,也是一个基于 Chrome V8 引擎的 JavaScript 运行环境,使用了一个事件驱动、非阻塞式 I/O 模型,让 JavaScript 运行在服务端的开发平台,它让 JavaScript 成为与 PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。

Node.js 有如下特点和优势:

  • 它是一个 JavaScript 运行环境
  • 依赖于 Chrome V8 引擎进行代码解析
  • 事件驱动(event-driven)
  • 非阻塞 I/O (non-blocking I/O)
  • 轻量、可伸缩,适于实时数据交互应用
  • 单进程,单线程

学到这里有的同学可能有些疑惑,这么多特点都是什么意思呢?Node.js 相对于其他服务器语言,例如 Ruby、Java 又有什么区别和优势呢?别着急,老师马上为大家解答。

传统的服务器开发语言(例如 java)是多线程的,例如我们在淘宝商城购物,如果只有一个顾客发送请求,那显然是没问题的。但是当有成百上千万的用户同时访问的时候,我们肯定不希望别人买完以后才能轮到自己购买,这显然要等待很长时间。但是如果 CPU 只有一个核心的时候或者 CPU 只有一个线程的时候,确实要等待别人购买完毕以后才能购买。这就像在饭馆吃饭,如果只有一个厨师的话,同一时间只能给一个顾客做菜,这样必须要等待这一个厨师为别人做完菜以后才能为自己做菜。

为了解决这个问题,我们可以多增加几个厨师来同时为这些顾客做菜,这就是 java 语言的处理方式:使用多线程并发执行,但是这种方式势必会增加聘请厨师的成本和消耗更大的厨房空间,这无疑是一个资源的浪费,而且这种多线程模型,CPU(厨师) 在为客户服务的时候不能做其他的事,例如使用 I/O 读取文件的时候,只能等待文件系统读取文件完毕以后,才能继续做其他事。

为了解决上述问题 Node.js 应运而生。Node.js 是单线程模型、非阻塞 I/O、采用事件循环机制的原理进行处理。如下图:

1678547067868

非阻塞 I/O 的意思就是,文件系统在进行 I/O 操作的时候,Node.js 这个线程还可以做其他的事,当文件系统读取文件完毕时,通过事件的回调函数告诉 Node.js 线程,然后 Node.js 把读取的内容响应给用户。

Node.js 中的 I/O 操作可以理解成生活中的一个幕后工作者,就像在餐馆点餐这个场景中,饭馆可以直接去把点餐的任务派发给外部擅长做菜、煮饭、酿酒的厂家,这些厂家都是非常擅长这些工作的,Node 中也有各种擅长做某件事的模块。这样当这些幕后的厂家完成这些做菜、饭、酒的事件以后,把做完的饭菜酒等食品交给厨师,然后由厨师统一的让服务员把饭菜交给顾客。这样就大大提高了整个餐馆的运营速度。

1678547079805

如果大家还不明白的话,我们再看下一个例子,Node.js 就像国王,国王每天都在写任务清单,然后派发给大臣。大臣把任务清单交给下面的官员去做,而这个时候国王还是可以继续写任务清单的。当官员完成任务这个事件后,统一的任务结果交给国王(以事件回调函数的形式通知 Node.js)。此事件反复进行,这便是事件循环。所以说除了国王(Node.js)线程以外,每件事都是并行发生的,这便是 Node.js 单线程和事件循环能同时处理多个请求的原理。

# 在 Window 和 Mac 中下载和安装 Node.js

# 下载 Node.js

点击 Node 官网 进入 Node 官方网站,效果如下:

1678547104070

网站自动识别系统为 Windows ,默认给出了 WindowsNode 下载地址,如果使用 Mac 电脑访问则自动识别系统为 Mac 系统,自动给出 Mac 版下载地址。这里给出了两个版本的下载地址:

一.14.17.3 LTS 版(左侧)

即 Long Time Support 版(长时间支持版),即稳定版。

二.16.4.2 Current (右侧)

即最新版,包含最新的特性,但不稳定。

这里推荐下载 LTS 长时间支持版,点击 14.17.3 LTS 进行下载,下载后的文件如下:

1678547119373

另: Mac 下载相应 Mac 版本即可。

# 安装

windowsMac 下都是直接双击安装文件,一路下一步即可。

# 查看是否安装成功

安装完成后,使用 cmd 进入到控制台,输入如下命令测试 Node 是否安装成功。

node - v; // 查看 node 版本
npm - v; // 查看 npm 版本

如果看到如下版本信息,则表示 Node 安装成功。

1678547133917

需要说明的是:本课程是基于 node v14+ 的版本,低于该版本,请自行更新至对应版本中。

安装 Node 的时候会同时帮我们安装 npm (Node Package Manager)Node 包管理工具,用于下载依赖的 node 包。

# linux 版 Node 下载和安装

打开我们的实验环境,在右侧控制台中输入如下命令:

node -v

效果如下:

1678547168258

发现我们并没有安装 Node 就已经出现了 Node 版本,原因是我们的实验环境中已经默认安装了 Node 14.15.1 版。

下面给大家讲解一下 linuxNode.js 的下载和安装。

# 下载

进入 Node 官网 点击下图中的 Downloads

1678547180174

在弹出的界面中显示的 Node 的全部下载版本,这里我们下载 Linux Binaries(x64 版。见下图:

1678547191674

同学们也可以使用下列命令直接下载我们云服务器上的 Linux Node 安装文件。

wget https://labfile.oss.aliyuncs.com/courses/4380/node-v14.17.3-linux-x64.tar.xz

使用上述命令下载后效果如下:

1678547203897

然后使用如下命令进行解压缩:

tar -xvf node-v14.17.3-linux-x64.tar.xz

解压后效果如下

1678547212841

解压完毕后就可以把 node-v14.17.3-linux-x64.tar.xz 文件删除了。

然后使用如下命令对解压后的 node 文件夹进行改名:

mv node-v14.17.3-linux-x64 node

改名后效果如下:

1678547228991

这时再次输入 node -v 查看版本,发现版本仍然是 14.15.1。

1678547239212

说明我们的 Node 安装并没有完成,如果想使用最新安装的 14.17.3 版,需要使用以下命令。

cd node/bin
./node -v

注意这里是使用./node 来使用当前路径下的 node 命令,效果如下:

1678547250037

发现版本为 14.17.3 ,说明我们已经使用了自己下载的最新版本的 Node,但是每次都需要进入到 Node 安装路径的 bin 路径下才能使用 Node 命令,比较麻烦。如何能让我们在系统的任何地方都能使用最新安装的 Node 呢?这里需要修改我们的环境变量 PATH , 把 node/bin 这个路径添加到 PATH 下。

# 修改环境变量 PATH

为了在全局使用最新安装的 Node ,我们还需要修改一个配置文件 ~/.zshrc

PATH 作用:当我们在控制台输入命令的时候(例如 node -v),系统是去 PATH 环境变量下配置的路径中寻找这个命令是否存在,查找的路径顺序为从左到右依次查找(linux 路径分隔符为 :)。如果发现对应的 PATH 路径下有 node 命令就会使用,否则就会报错。

首先使用下列命令查看我们的环境变量:

env | grep node

效果如下:

1678547269422

发现 PATH 中有 /usr/sbin/nodejs/bin ,这个路径就是我们实验环境默认安装的 14.15.1 的 Node 安装路径,如果想让我们新安装的 Node 优先执行的话,只需要把新版本 Node 的安装路径下的 bin 路径放到 /usr/sbin/nodejs/bin 路径前即可。

使用下列命令修改 ~/.zshrc

vim ~/.zshrc

使用 i 进入插入模式,在文件最后加入如下内容:

export PATH=/home/project/node/bin:$PATH

然后输入 ESC 回到普通模式,输入 : 进入命令模式,然后 输入 wq 进行保存。修改完成的文件并不会马上生效,需要使用如下命令让刚才配置的环境变量生效。

source ~/.zshrc

最后输入 node -v 重新查看 node 版本,效果如下:

1678547283869

发现现在全局使用的已经是我们最新安装的 node 了。

# Web 请求

HTTP 协议(Hyper Text Transfer Protocol)是超文本传输协议,它是基于请求 - 响应的一个协议,即客户端浏览器给服务器发送一个 HTTP 请求,然后服务器对客户端的请求作出响应。我们开发的基于浏览器的 B/S 架构的程序都是基于 HTTP 协议(也有 HTTPS)的,例如登录、注册、查询商品等。

当用户在 URL 中输入一个网址到看到页面,大体经过如下几步:

  • DNS 解析,建立 TCP 连接,发送 HTTP 请求。
  • Server 端接收 HTTP 请求,处理,返回结果。
  • 客户端接收到返回数据,进行页面渲染显示内容。

本节实验要给同学介绍的就是发送请求最常用的两种方式,GET 和 POST。

我们快一起去学习吧!

# 知识点

  • GET 请求
  • POST 请求

# 处理 GET 请求

通过浏览器向服务器发送请求的常用方式分为两种,一种是 GET 方式,另一种是 POST 方式,前者请求的安全性不高,常用于查询的操作,如查询用户信息、查询博客信息等。

GET 请求传递参数是在 URL 后面加入一个 "?" ,然后在 "?" 后面加入想要传递的参数,多个参数之间用 "&" 隔开。格式如下:

http://localhost:8000/index?id=1001&name=abc

首先在右侧控制台中输入以下命令来初始化 npm 环境,初始化完成后会生成一个 package.json 文件。

npm init -y

project 文件夹下,新建 index.js 文件,并加入下列代码:

const http = require("http");
const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
  // 通过请求对象获取完整的请求地址并保存在变量 url 中
  let url = req.headers["x-scheme"] + "://" + req.headers.host + req.url;
  // 将变量传入实例化方法中,并实例化一个 URL 对象
  const myURL = new URL(url);
  let params = myURL.searchParams.toString();
  res.write(params);
  res.end();
});
// 服务侦听 8080 端口
server.listen(8080, () => {
  console.log("服务器运行在 8080 端口...");
});

在本示例中代码解析如下:

  • 定义一个变量 url 用于保存 req 对象请求的完整地址,其中 req.headers['x-scheme'] 返回请求协议名称,如 httphttps ,而 req.headers.host 返回请求的域名和端口, req.url 返回请求的详细路径,包含查询字符串。

  • 获取并保存完整的请求地址后,实例化一个 URL 对象,在实例化对象的方法中,可以传递两个参数,第一个参数是必选项,表示要解析的绝对或相对的网址,第二个参数是可选项,表示要解析的基础网址,如果第一个参数是绝对地址,则第二个可以省略,如本示例中的代码;如果第一个对数是相对地址,则第二个参数必须添加请求地址的协议名称、域名和端口。

  • 如果将示例中绝对的请求地址修改成相对地址,那么在实例化 URL 对象时,代码修改如下所示:

    // 通过请求对象获取相对请求路径并保存在变量 url 中
    let url = req.url;
    // 在实例化过程中,通过第二个参数添加请求协议的名称、域名和端口
    const myURL = new URL(
      url,
      req.headers["x-scheme"] + "://" + req.headers.host
    );

    这两种代码实现的方式在使用时,结果都是一样的,但在实际运用时,传入完整的请求地址,使用会更多些。

在控制台中输入以下命令运行该程序。

node index.js

然后点击右侧的 “Web 服务”,并在 simplelab.cn 地址后加入 ?id=1001&name=abc ,刷新页面,效果如下:

1678414567270

页面中显示的内容就是以字符串形式输出的 GET 请求参数,如果需要使用,也可以通过指定参数名称获取对应的值。

它这里不是实时刷新的,当修改了服务器端的代码,就需要重新运行代码。

res.write() 传入的是字符串,没有自动换行。

# 处理 POST 请求

相对于 GET 方式请求, POST 请求在传输数据时,要安全很多,因此,常用用于数据的添加、删除、修改操作,例如新建博客、删除博客、修改博客等就会使用到 POST 请求。

URL 中输入地址直接访问属于 GET 请求,而 POST 请求则常用于表单数据的提交,先在 form 中设置 method=post ,当用户点击提交表单按钮时就会发送一个 POST 请求,并把表单中用户填入的数据传递给服务器。但是手工编写表单的代码显然比较麻烦,有没有一种更加简单的方式发送 POST 请求呢?

答案是有的。可以使用 Postman 这个工具来帮助我们发送 POST 请求。

# Postman 下载安装

首先在右侧桌面环境打开 Firefox 浏览器,然后在 URL 地址栏中输入 http://postman.com/downloads 进入到下载页面。系统自动识别下载 linux 版本的 postman

1678414650452

然后单击 Download the App 按钮进行下载,在弹出的界面中选择 Linux 64-bit 下载(如下图)。

1678414660754

单击” 保存文件” 按钮进行保存,如下图。

1678414733986

下载完毕后右上角会提示 “已完成”。

1678414792019

然后点击 “显示全部下载” 会弹出以下界面。

1678414801050

点击 “打开所在文件夹”,然后右键点击 Postman-linux-x86_64-8.11.1.tar.gz 文件重命名为 postman.tar.gz ,然后使用鼠标把该文件拖拽到左侧 shiyanlou 目录下。然后在实验楼路径中点击右键,选择 “在此打开终端”(如下图)。

1678414811457

然后在终端中输入如下命令进行解压。

sudo tar -zxvf postman.tar.gz

使用如下命令启动 postman

./Postman/Postman

启动后点击左上角的 "File"---"new" 新建一个窗口,效果如下:

1678414822977

然后申请一个 Postman 账号并点击 Sign in 登录(此过程不再截图,同学们自行完成)。登录成功后如下图,然后点击 Create new

1678414833127

在弹出的界面中点击 HTTP Request ,如下图:

1678414848301

如果想发送 HTTP 请求还需要装一个 Postman 客户端才能正常发送请求。使用下列命令下载 Postman 客户端。

cd /home/shiyanlou
wget https://labfile.oss.aliyuncs.com/courses/4380/postmanAgent.tar.gz

注意:如果想复制如上命令到云课桌面环境进行粘贴的话,可以借助右侧 “剪切板” 功能。先复制好想要粘贴的命令,然后点击右侧 “剪切板”,把复制的内容粘贴到剪切板中,然后点击保存即可(如下图),然后就可以在云课桌面环境中进行粘贴了。

1678414863584

下载完毕后截图如下:

1678414872283

然后使用如下命令进行解压安装。

cd /home/shiyanlou
sudo tar -zxvf postmanAgent.tar.gz

# 编写代码处理 POST 请求

然后在 /home/shiyanlou/ 路径下新建 index.js 文件,然后编写代码如下:

const http = require("http");
const server = http.createServer((req, res) => {
  if (req.method === "POST") {
    console.log("content-type:", req.headers["content-type"]); // 获取请求类型 application/json
    // 读 post 数据
    let postData = ""; //postData 用来存储传递给服务器的全部数据
    // 分段循环传输数据,每次传递数据都会执行后面的回调函数
    req.on("data", (chunk) => {
      postData += chunk.toString(); //chunk 是二进制数据 所以要把它转换成字符串
    });
    // 当数据传输完毕会执行 end 事件后的回调函数
    req.on("end", () => {
      console.log("postData:", postData);
      res.end("Hello"); // 在这里返回因为是异步
    });
    console.log("test"); // 这里先被打印 因为上面的代码是异步的
  }
});
server.listen(8080, () => {
  console.log("服务器运行在 8080 端口...");
});

在终端中输入以下命令运行该程序。

cd /home/shiyanlou
node index.js

最终效果如下图所示:

1678414898104

res.end() 可以传入字符串作为参数,也可以不传入,所谓结束返回的标志。

# 使用 Postman 发送 POST 请求

然后打开刚才的 Postman 界面,根据下图依次选择 POST 请求,输入请求地址 http://localhost:8080 。然后点击 Body ,选择 raw<span> </span>JSON ,输入发送的内容后点击 Send 按钮。

1678414908524

当看到上图中的返回内容 Hello 时,说明程序已经调试成功了,控制台输出如下:

1678414918300

# 实验总结

本节实验给大家介绍了两种最常用的 Web 请求,分别是 GET 和 POST。这里我们来总结一下:前者的链接中包含请求参数,安全系数低于 POST 请求。但 GET 请求也有自己的用处,比如查询操作时,显然 GET 请求是不错的选择。

# 初始化路由

# 知识点

  • 认识路由
  • 配置路由
  • 访问路由
  • 路由开发

# 认识路由

路由,顾名思义,它是指路径的指向或由来,即访问一个地址后它的指向,确定地址指向后,将会根据不同的路由地址,编写相应的代码,如下图所示。

1678414985242

一次地址的指向,实质上是一次数据请求的过程,在这种请求的过程中,还可以携带请求的方式,如 POST 或 GET,同时,也可以携带请求的参数,根据这些请求携带的方式和参数,即使是同一个地址,也可以执行不同的代码,如下图所示。

1678414998063

借助路由的这些特性,被广泛地应用于项目中各页面的切换,数据接口的请求,因此,路由是项目开发中必须要掌握的内容。在理解了它的重要性之后,如何去配置一个路由呢?带着这个问题,下面来说路由的配置方法。

# 配置路由

node 中配置路由十分简单,首先,使用以下命令下载搭建好的开发环境项目包,地址如下所示。

wget https://labfile.oss.aliyuncs.com/courses/4380/router.zip && unzip router.zip

然后,解压该项目包至 project 文件夹中,并打开项目包 router 文件夹,找到 bin 文件夹,在该文件夹下,创建一个名称为 reqRouters 的 js 文件,加入如下所示的代码:

const reqRouters = (req, res) => {
  if (req.path === "/aa") {
    return "首页";
  }
  if (req.path === "/bb") {
    return "列表页";
  }
  if (req.path === "/cc") {
    return "详细页";
  }
};
module.exports = reqRouters;

在上述代码中,定义了一个名称为 reqRouters 函数,函数中,参数 req 表示请求时携带的对象,用于创建服务请求时的回调函数使用,在这个 req 对象中,通过 path 可以获取到请求时的路由地址,根据获取的不同地址,返回不同的内容,最后,输出这个名称为 reqRouters 的函数。

接下来,再次找到 bin 文件夹,打开名称为 app 的 js 文件,删除原有的内容,加入如下所示的代码。

const reqRouters = require("./reqRouters");
const serverHandle = (req, res) => {
  res.setHeader("Content-Type", "application/json");
  req.path = req.url.split("?")[0];
  const result = reqRouters(req, res);
  if (result) {
    res.write(result);
    res.end();
    return;
  }
  res.writeHead(404, { "Content-Type": "text/plain" });
  res.write("404 Not Found\n");
  res.end();
};
module.exports = serverHandle;

在上述代码中,首先,使用 require 输入 reqRouters 模块,用于获取请求输出的内容,其次,定义一个名称为 serverHandle 的函数,用于服务创建时的回调,在该函数中,向 reqRouters 函数传入请求的路由地址,获取输出的内容并保存至变量 result 中,最后,判断变量 result 中是否有内容,如果有,则直接输出在页面中,否则,在页面中输出 "404 Not Found" 的信息。

最后,找到 bin 文件夹,打开名称为 index 的 js 文件,删除原有的内容,加入如下所示的代码。

const http = require("http");
const serverHandle = require("./app");
const port = 8080;
const server = http.createServer(serverHandle);
server.listen(port, () => {
  console.log("服务器运行在 8080 端口...");
});

在上述代码中,首先,分别导入 httpserverHandle 模块,前者用于创建一个新的服务,是 node 自带模块, 后者用于创建服务后的回调函数,当服务创建成功后,使用 listen 方法,在指定的 8080 端口下侦听,当服务启动后,就可以在浏览器的地址栏中,根据启动的地址和端口访问这个服务了,由于配置的路由在服务中,这时,就可以按配置的路由访问页面了 🤪

# 访问路由

要访问路由,需要先启动服务,因此,首先,在项目文件夹 router 下,打开终端界面,并输入如下指令。

npm run dev

服务启动后的界面效果如下图所示:

1678415045353

当服务成功启动后,就可以在浏览器中,根据指定的地址和端口,访问相应的页面内容,效果如下所示:

1678415054481

当在浏览器地址栏中输入 localhost:8080/aa 时,它的 url.path 值为 '/a' ,传给 reqRouters 函数后,则返回 "首页" 字符,因此,页面中输出 "首页" 内容,其他输入地址依此类推,根据不同的 url.path 值,向页面输出不同的内容,最终实现路由的功能。

# 路由开发

# 获取请求方式

在发送一次请求时,不同的请求方式,将会返回不同的数据,目前常用的请求方式分为 GETPOST 两种,前者常用于查询请求,后者用于增加、修改和删除的请求,那么,在 node 中,如何获取路由中的请求方式呢?下面通过一个示例来进行说明。

首先,使用以下命令获取搭建好的路由初始化项目包,地址如下所示。

wget https://labfile.oss.aliyuncs.com/courses/4380/router-param.zip && unzip router-param.zip

打开项目包 router-param 中的 bin 文件夹,在该文件夹下,找到并打开名称为 reqRouters 的 js 文件,删除原有的代码,加入如下所示的代码:

const reqRouters = (req, res) => {
  if (req.method === "GET") return "这是一次 GET 方式请求";
  if (req.method === "POST") return "这是一次 POST 方式请求";
};
module.exports = reqRouters;

在上述代码中,首先,通过 req 对象中的 method 属性获取到服务请求的方式,然后根据不同的请求方式,返回不同的文字内容,最后,输出这个名称为 reqRouters 的函数。

修改完成后,在项目文件夹 router-param 下,打开终端界面,并输入如下指令。

npm run dev

服务启动后的界面效果如下图所示:

1678415080554

当服务成功启动后,就可以在浏览器中,根据指定的地址和端口,访问相应的页面内容,由于是以 GETPOST 发送请求,因此,需要借助之前安装好的 Postman 工具,更多与 Postman 相关的内容,请点击 👉参阅资料, 其实现的效果分别如下:

  • 如果是 GET 方式请求,则返回 "这是一次 GET 方式请求" 内容,如下图所示。

1678415091409

  • 如果是 POST 方式请求,则返回 "这是一次 POST 方式请求" 内容,如下图所示。

1678415106929

在上述效果示意图中,我们看到,通过 req 对象中的 method 属性可获取每次请求的方式,除获取每次请求的方式之外,还可以获取路由中传递的参数值,根据请求方式的不同,获取的方式也不一样,接下来,我们先来介绍获取 GET 方式传入的参数值。

# 获取 GET 方式传参

在进行 GET 方式请求时,如果需要传递参数值,通常添加在 url 的后面,使用 ? 进行标记,通过 key=value 的形式进行设置,多个参数值之间使用 & 进行连接,如下图所示:

1678415126101

获取 GET 方式传参的过程实质是根据参数名称,获取对应值的过程,在这个过程中,先将传入的地址作为参数,实例化一个 URL 对象,然后通过该对象获取表示网址查询参数的 searchParams 对象,最后,在 searchParams 对象中,根据传入参数名称,获取对应的值,接下面我们通过一个示例来演示参数值获取的过程。

打开项目包 router-param 中的 bin 文件夹,在该文件夹下,找到并打开名称为 reqRouters 的 js 文件,删除原有的代码,加入如下所示的代码:

// 引入 url 模块
var url = require("url");
const reqRouters = (req, res) => {
  if (req.method === "GET") {
    const myURL = new URL(req.url, req.headers.host);
    let params = myURL.searchParams;
    if (params.get("id") && params.get("name")) {
      return params.get("id") + "," + params.get("name");
    } else {
      return "没有传入相应参数!";
    }
  }
};
module.exports = reqRouters;

在上述代码中,首先判断请求否是为 GET 方式 ,如果是,则实例化一个 URL 对象,在实例化过程中,需要传入两个参数,第一个参数是请求的 url 地址,通过 req.url 获取,第二个参数是 base 表示要解析的基本网址,包括地址的域名和端口,如果 url 地址是相对的,则需要添加第二个参数,如果是绝对的,则可以省略第二个参数。

在上述代码中,通过这个实例化后的 URL 对象,将保存网址查询参数的 searchParams 对象赋值给变量 params ,然后再判断 params 中是否存在对应参数名称的值,如果存在,则返回该对应值,否则,返回 "没有传入相应参数!" 的错误信息。

修改完成后,在项目文件夹 router-param 下,打开终端界面,并输入如下指令。

npm run dev

当服务成功启动后,借助之前安装好的 Postman 工具,使用 GET 方式,在当前域名下,首先请求一个指定参数名称和值的地址,然后请求一个不传参数的地址,在点击 "发送" 按钮后,其实现的效果分别如下图所示:

1678415140910

在上述效果示意图中,我们看到,路由中传入的两项参数都获取成功,相比获取 GET 方式传入的参数值而言,获取 POST 方式传入的参数值就要复杂很多,接下来我们再来说下获取 POST 方式传入参数值。

# 获取 POST 方式传参

相对于使用 GET 方式请求而言,使用 POST 方式更加安全,因此,常用于增加、修改和删除数据的操作,使用 POST 方式传参时,携带的请求数据并不在路由中,而是在请求对象中,因此,需要绑定请求过程的两个事件,一个是 data 事件,在该事件中获取并累加每次请求传入的参数值, 另一个是 end 事件,该事件在请求结束时触发,在事件中处理累加后的请求参数,并输出至页面中,接下面我们通过一个示例来演示参数值获取的过程。

打开项目包 router-param 中的 bin 文件夹,在该文件夹下,找到并打开名称为 index 的 js 文件,删除原有的代码,加入如下所示的代码:

const http = require("http");
const serverHandle = (req, res) => {
  if (req.method === "POST") {
    var strPOST = "";
    // 绑定数据请求事件,每当接受到请求体的数据,就累加到 strPOST 变量中
    req.on("data", function (chunk) {
      strPOST += chunk;
    });
    // 绑定数据请求结束事件,向页面输出指定的参数值
    req.on("end", function () {
      res.setHeader("Content-Type", "application/json");
      let objPOST = new URLSearchParams(strPOST);
      if (objPOST.get("id") && objPOST.get("name")) {
        const result = objPOST.get("id") + "," + objPOST.get("name");
        res.write(result);
      } else {
        res.write("没有传入相应参数!");
      }
      res.end();
      return;
    });
  }
};
const port = 8080;
const server = http.createServer(serverHandle);
server.listen(port, () => {
  console.log("服务器运行在 8080 端口...");
});

在上述代码中,首先判断请求否是为 POST 方式 ,如果是,则分别绑定请求时的两个事件,一个是 data 事件,在该事件中,将每次发送参数值累加并保存在变量 strPOST 中, 另一个是 end 事件,在该事件中 ,当请求结束后,将传入并累加后的参数解析为查询字符串,并使用它来实例化新的 URLSearchParams 对象,最后,在 URLSearchParams 对象中,先判断传入的参数名称是否存在获取对应的值,如果存在,则并输出到页面中,否则,向页面输出 "没有传入相应参数!" 的错误信息。

修改完成后,在项目文件夹 router-param 下,打开终端界面,并输入如下指令。

npm run dev

当服务成功启动后,借助之前安装好的 Postman 工具,使用 POST 方式,请求一个当前域名下指定参数名称和值的地址,点击 "发送" 按钮后,其实现的效果如下图所示:

1678415165507

# Node.js 文件操作

我们知道,在前端在开发时,JavaScript 是一种依赖浏览器解析的语言,在访问文件时,需要依赖浏览器的兼容性,操作文件的能力很弱 ,而 Node 则这方面有很强的能力,那么,它是如何操作一个文件的呢?带着这个问题,我们快速地进入到今天的学习中来吧!

# 知识点

  • 文件读取操作
  • 文件写入操作
  • 删除文件操作

# 文件读取操作

node 中,如果要读取一个文件,就必须引入一个名称为 fs 的模块,而 fsfile-system 的简写,表示文件系统,在该模块中提供了全部操作文件的方法,因此,如果想去读取一个文件,必须先导入该模块,代码如下所示。

const fs = require("fs");

完成模块导入之后,就可以使用该模块中读取文件的方法 fs.readFile() ,该方法是一个异步读取文件的方法,它的调用格式如下所示:

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

在上述格式中,第一个参数 path 表示读取文件的路径,通常是一个相对路径,如 "./public/msg.txt",第二个参数 options 是一个可选项,在该项中可以设置读取文件时的编码格式 encoding ,文件打开的行为 flag ,允许中止正在进行的读取文件 signal ,其中 flag 包含多种方式,具体如下表所示:

(诶?这都已经是 readFile 了,那么为什么要写这些 flag 呢?不就是 r 了吗)

flag 名称描述
a打开文件进行追加。 如果文件不存在,则创建该文件。
ax类似于 a 但如果路径存在则失败。
a+打开文件进行读取和追加。 如果文件不存在,则创建该文件。
ax+类似于 a+ 但如果路径存在则失败。
as以同步模式打开文件进行追加。 如果文件不存在,则创建该文件。
as+以同步模式打开文件进行读取和追加。 如果文件不存在,则创建该文件。
r打开文件进行读取。 如果文件不存在,则会发生异常。
r+打开文件进行读写。 如果文件不存在,则会发生异常。
rs+以同步模式打开文件进行读写。 指示操作系统绕过本地文件系统缓存。
w打开文件进行写入。 如果它不存在则创建,如果它存在则截断该文件。
wx类似于 w 但如果路径存在则失败。
w+打开文件进行读写。 如果它不存在则创建,如果它存在则截断该文件。
wx+类似于 w+ 但如果路径存在则失败。

第三个参数 callback 是一个回调函数, 在回调函数中,传入了两个参数,一个参数的名称为 error ,表示文件未读取成功时的错误信息,另一个参数的名称为 data ,表示文件读取成功时的内容信息,接下来通过一个示例来演示文件读取的过程。

找到并打开 project 文件夹,在该文件夹下,创建一个名称为 public 的文件夹,在 public 文件夹中,新建一个名称为 msg 的记事本文件,打开该文件,添加下列文字内容并保存。

蓝桥,一个学习知识的好地方!
          --程序员寄语--

再次找到并打开 project 文件夹,在该文件夹下,新建一个名称为 readfile 的 js 文件,打开该文件,并添加下列代码:

// 导入 fs 模块
const fs = require("fs");
// 调用模块中异步读取文件的方法
fs.readFile("./public/msg.txt", "utf-8", (error, data) => {
  if (error) throw error;
  console.log(data);
});

在上述代码中,先导入 fs 模块,再调用模块中的异步读取文件的方法 readFile ,以 "utf-8" 的编码格式,读取记事本 msg 文件中的内容,在方法的回调函数中,如果出现错误,则返回相关的错误信息,如果没有出现错误,在控制台输出读取到的文件内容。最后,在 project 文件夹下,打开终端,并使用 node 执行名称为 readfile 的 js 文件,最终的效果如下图所示:

1678415216894

从最终读取出的内容效果看,使用 fs 模块中的 readFile 方法,可以很方便地读取到文件中的内容,

# 文件写入操作

fs 模块中,与文件读取方法 readFile 相对应, writeFile 方法则是用于文件的写入,该方法也是一个异步写入文件的方法,它的调用格式如下所示:

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

在上述格式中,方法中第一个参数 file 表示要写入的文件名,第二个参数 data 表示需要写入的内容,第三个参数 options 是一个可选项,它与文件读取方法中的功能一样,在此不再赘述,第四个参数 callback 是一个回调函数, 在回调函数中,传入了一个名称为 error 的参数,表示文件没有写入成功时的错误信息,接下来通过一个示例来演示文件写入的过程。

找到并打开 project 文件夹,在该文件夹下,新建一个名称为 writefile 的 js 文件,打开该文件,并添加下列代码:

// 导入 fs 模块
const fs = require("fs");
// 调用模块中异步写入文件的方法
fs.writeFile("./public/msg.txt", "今天是一个好日子", (error) => {
  if (error) throw error;
  console.log("写入成功!");
});

在上述代码中,先导入 fs 模块,再调用模块中的异步写入文件的方法 writeFile ,将字符串 "今天是一个好日子" 写入 msg 文件中,由于该文件已存在,因此,写入的内容将会覆盖文件中原有的内容,同时,在方法的回调函数中,如果出现错误,则返回相关的错误信息,如果写入没有出错,则在控制台输出 "写入成功!" 的字样。最后,在 project 文件夹下,打开终端,并使用 node 执行名称为 writefile 的 js 文件,效果如下图所示:

1678415249698

当调用 fs 模块中的 writeFile 方法在指定的文件中写入内容后,文件中的原有内容默认将会被覆盖,只保留最新写入的内容,也可以借助 options 对象中的 flag 属性值,改变写入文件时的方式,由覆盖变为追加内容,接下来通过一个示例来演示它实现的方式。(或者先读取保存,然后字符串拼接再写)

找到并打开 project 文件夹,在该文件夹下,新建一个名称为 writefile2 的 js 文件,打开该文件,并添加下列代码:

// 导入 fs 模块
const fs = require("fs");
// 调用模块中异步写入文件的方法
fs.writeFile(
  "./public/msg.txt",
  ",明天又是一个大晴天。",
  { flag: "a", encoding: "utf-8" },
  (error) => {
    if (error) throw error;
    console.log("写入成功!");
  }
);

打开终端,并使用 node 执行名称为 writefile2 的 js 文件,效果如下图所示:

1678415271704

在上述实现的代码中, flag 值为 "a" 时,表示以追加的方式向文件中写入内容,其他方式大家可以根据这种方式进行变更。

# 删除文件操作

fs 模块中,可以使用 writeFile 方法增加一个指定名称的文件,如果不希望使用某个文件,也可以调用 unlink 方法,删除这个文件, unlink 方法也是一个异步的方法,它的调用格式如下所示:

fs.unlink(path, callback);

在上述格式中,第一个参数 path 表示删除文件的路径,第二个参数 callback 是一个回调函数, 在回调函数中,传入了一个名称为 error 的参数,表示文件没有删除成功时的错误信息,接下来通过一个示例来演示文件删除的过程。

首先找到并打开 project 文件夹,在该文件夹下,再打开 public 文件夹,新建一个文件为 temp 的记事本文件,结构如下图所示:

1678415293633

最后,在 project 文件夹下,新建一个名称为 delefile 的 js 文件,打开该文件,并添加下列代码:

// 导入 fs 模块
const fs = require("fs");
// 调用模块中异步删除文件的方法
fs.unlink("./public/temp.txt", (error) => {
  if (error) throw error;
  console.log("删除成功!");
});

在上述代码中,先导入 fs 模块,再调用模块中的异步删除文件的方法 unlink ,在方法的回调函数中,如果出现错误,则返回相关的错误信息,如果删除没有出错,则在控制台输出 "删除成功!" 的字样。最后,在 project 文件夹下,打开终端,并使用 node 执行名称为 delefile 的 js 文件,效果如下图所示:

1678415306073

注意:在 fs 模块中的 unlink 方法,只能删除文件,不适用于删除目录,即使是一个空目录,如果要删除目录,需要使用另外一个名称为 rmdir 的方法,关于 rmdir 更的资料,请查看 👉 rmdir 使用方法