+
+### 关于 HTTP 请求类型
-### About HTTP request types
-
-
- [HTTP 标准](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) 谈到了与请求类型有关的两个属性:**安全** 和 **空闲**。
+
+[HTTP 标准](https://www.rfc-editor.org/rfc/rfc9110.html#name-common-method-properties)提到了关于请求类型的两个属性:**安全性**和**幂等性**。
- HTTP GET 请求应该是 安全的 。
+HTTP GET 请求应该是安全的:
- > 特别是,约定俗成的是,GET 和 HEAD 方法不应具有除检索以外的行动意义。这些方法应该被认为是 " 安全的 "。。
-
+> 特别是已经约定俗成的,GET 和 HEAD 方法不应具有除获取数据以外的任何意义。GET 和 HEAD 方法应被认为是“安全的”。
-
- 安全意味着执行的请求不能在服务器中引起任何 副作用 。我们所说的副作用是指数据库的状态不能因为请求而改变,而且响应必须只返回服务器上已经存在的数据。
+
+安全性意味着执行这类请求不能在服务端引起任何副作用。这里的副作用是指数据库的状态不能因请求而改变,而且响应必须只返回服务端上已有的数据。
+
+还没有办法能保证 GET 请求是安全的,这只是 HTTP 标准定义的一个建议。通过在 API 中遵守 RESTful 原则,就总能以安全的方式使用 GET 请求。
-
- 没有什么能保证 GET 请求实际上是 安全的 ,这实际上只是 HTTP 标准中定义的一个建议。通过在我们的 API 中坚持 RESTful 原则,GET 请求实际上总是以一种 安全 的方式被使用。
-
-
-
- HTTP 标准还定义了请求类型 [HEAD](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4),这应该是安全的。在实践中,HEAD 的工作方式应该与 GET 完全一样,但它不返回任何东西,只返回状态代码和响应头。当你发出 HEAD 请求时,响应体将不会被返回。
-
+
+HTTP 标准定义的请求类型中,应该是安全的还有 [HEAD](https://www.rfc-editor.org/rfc/rfc9110.html#name-head)。实际上,HEAD 的效果应该与 GET 完全一样,但不返回除状态码和响应标头外的任何信息。当你发送 HEAD 请求时,不会有响应体返回。
- 除了 POST,所有的 HTTP 请求都应该是 idempotent。
+除了 POST,所有 HTTP 请求都应该是幂等的。
- > 方法也可以具有 " 同位素 " 的属性,即(除了错误或过期问题)N>0 个相同的请求的副作用与单个请求相同。GET、HEAD、PUT 和 DELETE 等方法都有这个属性 。
-
-
-
- 这意味着,如果一个请求有副作用,那么无论这个请求被发送多少次,结果都应该是一样的。
-
+> 方法也可以具有“幂等性”,即(除了错误或过期的问题) N > 0 个相同的请求的副作用与单个请求的副作用相同。GET、HEAD、PUT 和 DELETE 方法都有这一属性
-
- 如果我们向 url /api/notes/10 发出 HTTP PUT 请求,并随请求发送数据 { content:"no side effects!", important: true },无论发送多少次请求,结果都是一样的。
+
+这意味着如果一个请求确实会产生副作用,那么无论发送多少次该请求,结果都应该是一样的。
+
+如果我们向 URL /api/notes/10 发送 HTTP PUT 请求,并随请求发送数据 { content:"no side effects!", important: true },那么无论发送多少次请求,结果都应该是一样的。
- 就像 GET 请求的 安全 一样, 空闲 也只是 HTTP 标准中的一个建议,并不是简单地基于请求类型就能保证的。然而,当我们的 API 遵守 RESTful 原则时,那么 GET、HEAD、PUT 和 DELETE 请求的使用方式就是 idempotent。
-
+和 GET 请求的安全性一样,幂等性也只是 HTTP 标准中的一个建议,并不是简单根据请求类型就能保证的。然而,当我们的 API 遵守 RESTful 原则时,就能以幂等的方式使用 GET、HEAD、PUT 和 DELETE 请求。
- POST 是唯一的 HTTP 请求类型,既不 安全 也不 空闲 。如果我们向 /api/notes 发送 5 个不同的 HTTP POST 请求,其正文为 {content:"many same", important: true},服务器上产生的 5 个笔记都会有相同的内容。
-
+POST 是唯一的既不安全也不幂等的 HTTP 请求类型。如果我们向 /api/notes 发送 5 个 HTTP POST 请求,每个请求的请求体都是 {content:"many same", important: true},那么服务端会产生 5 个相同内容的笔记。
-### Middleware
-
-
- 我们之前使用的快递 [json-parser](https://expressjs.com/en/api.html) 是一个所谓的 [中间件](http://expressjs.com/en/guide/using-middleware.html)。
+
+### 中间件
+
+之前使用的 Express [json-parser](https://expressjs.com/en/api.html) 是一个[中间件](http://expressjs.com/en/guide/using-middleware.html)。
- 中间件是可以用来处理 _request_ 和 _response_ 对象的函数。
-
-
- 我们之前使用的 json-parser 从请求中获取原始数据,这些数据存储在 _request_ 对象中,将其解析为一个 JavaScript 对象,并将其作为一个新的属性 body 分配给 _request_ 对象。
+中间件是可以用来处理 _request_ 和 _response_ 对象的函数。
+
+我们之前使用的 json-parser 从请求中获取存储在 _request_ 对象中的原始数据,将其解析为一个 JavaScript 对象,并将其赋值给 _request_ 对象作为其新属性 body。
-
- 在实践中,你可以同时使用几个中间件。当你有多个中间件时,它们会按照在 Express 中被使用的顺序一个一个地被执行。
-
+
+实际上,你可以同时使用多个中间件。当你有多个中间件时,它们会按照应用代码中的顺序一个一个地执行。
- 让我们来实现我们自己的中间件,它可以打印出发送到服务器的每个请求的信息。
-
+让我们来实现我们自己的中间件,来打印出发送到服务端的每个请求的信息。
- 中间件是一个接收三个参数的函数。
+中间件是一个接收三个参数的函数:
```js
const requestLogger = (request, response, next) => {
@@ -1320,25 +1098,23 @@ const requestLogger = (request, response, next) => {
```
- 在函数体的最后,调用作为参数传递的 _next_ 函数。这个 _next_ 函数将控制权交给下一个中间件。
+函数体在最后调用了作为参数传递的 _next_ 函数。_next_ 函数将控制权交给下一个中间件。
- 中间件是这样被使用的。
+中间件是这样用的:
```js
app.use(requestLogger)
```
-
- 中间件函数的调用顺序是它们被 Express 服务器对象的 _use_ 方法所使用的顺序。请注意,json-parser 是在 _requestLogger_ 中间件之前被使用的,因为否则在执行记录器的时候,request.body 将不会被初始化。
-
+
+记住,中间件函数是按照 JavaScript 引擎遇到它们的顺序调用的。注意 _json-parser_ 要放在 _requestLogger_ 之前,否则在执行 _requestLogger_ 的时候,request.body 还没有初始化!
-
- 如果我们想让中间件函数在路由事件处理程序被调用前执行,那么就必须在路由之前使用这些中间件函数。也有一些情况,我们想在路由之后定义中间件函数。在实践中,这意味着我们要定义的中间件函数只有在没有路由处理 HTTP 请求时才会被调用。
+
+如果我们想让路由事件处理函数执行中间件函数,那么就必须在路由之前使用这些中间件函数。有时,我们想在路由之后才使用中间件函数。我们只在没有路由处理函数处理 HTTP 请求时才会调用路由之后的中间件函数。
-
-
- 让我们在路由之后添加以下中间件,用于捕捉向不存在的路由发出的请求。对于这些请求,中间件将返回一个 JSON 格式的错误信息。
+
+让我们在路由之后添加以下中间件,用于捕捉向不存在的路由发出的请求。对于这些请求,中间件将返回一个 JSON 格式的错误信息。
```js
const unknownEndpoint = (request, response) => {
@@ -1348,49 +1124,47 @@ const unknownEndpoint = (request, response) => {
app.use(unknownEndpoint)
```
-
- 你可以在 [这个 github 仓库](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-2) 的 part3-2 分支中找到我们当前应用的全部代码。
+你可以在[这个 GitHub 仓库](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-2)的 part3-2 分支中找到我们当前应用的全部代码。
-### Exercises 3.7.-3.8.
+
+### 练习 3.7.~3.8.
-#### 3.7: Phonebook backend step7
+
+#### 3.7:电话簿后端 第 7 步
- 将 [morgan](https://github.com/expressjs/morgan) 中间件添加到你的应用中进行记录。根据 tiny 配置,将信息记录到你的控制台。
+将 [morgan](https://github.com/expressjs/morgan) 中间件添加到你的应用中来记录日志。将 morgan 配置成按 tiny 配置项将信息记录到你的控制台。
- Morgan 的文档不是最好的,你可能需要花一些时间来弄清楚如何正确地配置它。然而,世界上的大多数文档都属于同一类别,所以无论如何,学会破译和解释神秘的文档是件好事。
-
+morgan 的文档并非最好,你可能需要花一些时间来弄清楚如何正确配置它。然而,世界上的大多数文档都是这个样子,所以无论如何,学会理解和解释难解的文档都是件好事。
- 摩根和其他所有的库一样,通过 _npm install_ 命令来安装。使用 Morgan 的方式和配置其他中间件一样,都是使用 _app.use_ 命令。
-
-
-#### 3.8*: Phonebook backend step8
+安装 morgan 的方式和其他所有库一样,都是用 _npm install_ 命令。使用 morgan 的方式和其他所有中间件一样,都是用 _app.use_ 命令。
+
+#### 3.8*:电话簿后端 第 8 步
- 配置 morgan,使它也显示 HTTP POST 请求中发送的数据。
+配置 morgan,使它也显示 HTTP POST 请求中发送的数据:

- 注意,即使在控制台中记录数据也是危险的,因为它可能包含敏感数据,并可能违反当地的隐私法(如欧盟的 GDPR)或商业标准。在这个练习中,你不必担心隐私问题,但在实践中,尽量不要记录任何敏感数据。
+注意记录数据是危险的,即使是记录到控制台中也如此,因为数据中可能包含敏感数据,从而可能违反当地的隐私法规(如欧盟的 GDPR)或商业标准。在这道练习中,你不必担心隐私问题,但在工作中,尽量不要记录任何敏感数据。
- 这个练习可能相当有挑战性,尽管解决方案不需要大量的代码。
-
+这个练习可能相当有挑战性,尽管答案不需要大量的代码。
- 这个练习可以用几种不同的方式完成。其中一个可能的解决方案是利用这两种技术。
+这道练习可以用几种不同的方式完成。一种解法是利用这两项技巧:
- - [创建新令牌](https://github.com/expressjs/morgan#creating-new-tokens)
+- [新建词法单元](https://github.com/expressjs/morgan#creating-new-tokens)
- - [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)
+- [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)
diff --git a/src/content/3/zh/part3b.md b/src/content/3/zh/part3b.md
index e7532be4a4c..39d79233ee5 100644
--- a/src/content/3/zh/part3b.md
+++ b/src/content/3/zh/part3b.md
@@ -8,12 +8,12 @@ lang: zh
-### Exercises 3.9.-3.11.
+
+### 练习 3.9.~3.11
- 下面的练习不需要很多行的代码。然而,它们可能是具有挑战性的,因为你必须确切地了解正在发生什么和在哪里发生,而且配置必须恰到好处。
+下面的练习不需要很多行代码。但这些练习可能具有挑战性,因为你必须准确理解发生了什么事情,以及事情在哪里发生,并且配置必须正好正确。
-#### 3.9 phonebook backend step9
+
+#### 3.9 电话簿后端 第 9 步
- 使后端与上一部分练习中的电话簿前端一起工作。先不要实现对电话号码进行修改的功能,这将在练习3.17中实现。
+使后端与上一章节练习中的电话簿前端一起运行。先不要实现更改电话号码的功能,我们会在练习 3.17 中实现。
- 你可能需要对前端做一些小的改动,至少要对后端的URL做一些改动。记得在你的浏览器中保持开放的开发者控制台。如果一些HTTP请求失败了,你应该从Network-tab中检查发生了什么。也要注意后端的控制台。如果你没有做前面的练习,在负责POST请求的事件处理程序中把请求数据或request.body打印到控制台是值得的。
+你可能要对前端做一些小的改动,至少要更改后端的 URL。记得在浏览器中保持开发者控制台打开。如果一些 HTTP 请求失败了,你应该在网络标签页中检查发生了什么。也要留意后端的控制台。如果你还没有做前面的练习,那么值得在负责 POST 请求的事件处理函数中把请求数据或 request.body 打印到控制台。
-#### 3.10 phonebook backend step10
+
+#### 3.10 电话簿后端 第 10 步
-
- 将后端部署到互联网上,例如部署到Heroku。
+
+将后端部署到互联网上,比如部署到 Fly.io 或 Render。如果你用的是 Fly.io,那么应该在后端的根目录(也就是在和后端 package.json 所在的同一个目录下)运行命令。
-
- **NB**命令_heroku_在系里的电脑和新生的笔记本上都可以使用。如果由于某些原因你不能[安装](https://devcenter.heroku.com/articles/heroku-cli)Heroku到你的电脑上,你可以使用命令[npx heroku](https://www.npmjs.com/package/heroku)。
+
+**专业提示:**当你将应用部署到互联网时,至少在一开始时应**始终**留意应用的日志。
- 用浏览器和Postman或VS Code REST客户端测试已部署的后端,以确保其工作。
-
-
- **专业提示:**当你将你的应用部署到Heroku时,至少在开始时值得用命令heroku logs -t来关注heroku应用的日志,**在任何时候。
-
-
- 下面是一个典型问题的日志。Heroku无法找到应用的依赖项express。
-
-
-
-
- 原因是express包没有被npm install express命令安装,所以关于这个依赖的信息没有被保存到package.json文件中。
-
-
- 另一个典型的问题是,应用没有被配置为使用设置在环境变量PORT中的端口。
-
-
+用浏览器和 Postman 或 VS Code REST Client 测试部署好的后端,确保其正常运行。
- 在你的版本库根部创建一个README.md,并在其中添加一个在线应用的链接。
+在你的仓库的根目录创建一个 README.md,并在其中添加在线应用的链接。
-#### 3.11 phonebook full stack
+
+#### 3.11 全栈电话簿
- 为你的前端生成一个生产版本,并使用本章节介绍的方法将其添加到互联网应用中。
+为你的前端构建一个生产版本,并使用本章节介绍的方法将其添加到部署到互联网的应用中。
-
- **NB** 确保目录build没有被gitignored
+
+同时,确保前端在本地(用命令 _npm run dev_ 启动的开发模式中)仍然可以运行。
-
- 还要确保前端在本地仍然可以工作(在开发模式下,用_npm start_命令启动)。
+
+如果你使用的是 Render,确保后端的 dist 目录没有在 .gitignore 中。
-
- 如果你有问题让应用工作,请确保你的目录结构与[示例应用](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-3)的结构一致。
+
+**注:**在本章节的任何阶段,你都**不**应直接部署前端。整个章节都只部署后端。将前端构建的生产版本添加到后端仓库中,然后让后端来提供,如同[由后端提供静态文件](/zh/part3/把应用部署到互联网上#由后端提供静态文件)一节中所描述的那样。
diff --git a/src/content/3/zh/part3c.md b/src/content/3/zh/part3c.md
index 180cf742385..01aa2bd8f94 100644
--- a/src/content/3/zh/part3c.md
+++ b/src/content/3/zh/part3c.md
@@ -8,33 +8,34 @@ lang: zh
-在我们进入关于在数据库中持久化数据的主题之前,我们先来看一下调试 Node 应用程序的几种不同方法。
+在我们开始主题——在数据库中持久化保存数据之前,我们先来看一下调试 Node 应用的几种不同方法。
-### Debugging Node applications
+
+### 调试 Node 程序
-调试 Node 应用程序比调试在浏览器中运行的 JavaScript 稍微困难一些。打印到控制台是一种经过验证的方法,值得一试。有些人认为应该使用更复杂的方法,但我不同意。即使是世界上顶级的开源开发人员也会使用这种方法。
+调试 Node 应用比调试浏览器中运行的 JavaScript 稍微困难一些。打印到控制台是一种经实践检验的方法,永远值得这么做。有些人认为应该使用更优雅的方法,但我不同意。即使是世界上的精英开源开发者也[使用](https://tenderlovemaking.com/2016/02/05/i-am-a-puts-debuggerer.html)这种[方法](https://swizec.com/blog/javascript-debugging-slightly-beyond-consolelog/)。
#### Visual Studio Code
-在某些情况下,Visual Studio Code 的调试器可能很有用。您可以像这样以调试模式启动应用程序(在这个和接下来的几个图像中,注释中有一个名为“日期”的字段,在当前版本的应用程序中已被删除):
+在某些情况下,Visual Studio Code 的调试器会很有用。你可以像这样以调试模式启动应用(在这张图片和接下来几张图片中,笔记有一个在当前版本的应用中已删除的 _date_ 字段):

-请注意,应用程序不应该在另一个控制台中运行,否则端口将已经被占用。
+注意,不应该在另一个控制台中运行应用,否则会占用端口。
-__注意__:Visual Studio Code 的较新版本可能会将“Debug”更改为“Run”。此外,您可能需要配置您的 _launch.json_ 文件来开始调试。您可以通过选择下拉菜单上方的绿色播放按钮旁边的 _Add Configuration..._,然后选择 _Run "npm start" in a debug terminal_ 来进行配置。有关更详细的设置说明,请访问 Visual Studio Code 的[调试文档](https://code.visualstudio.com/docs/editor/debugging)。
+__注__ 新版的 Visual Studio Code 可能用的是_运行_而非_调试_。此外,你可能需要配置 _launch.json_ 文件来开始调试。你可以通过在绿色播放按钮旁边,_变量_菜单上方的下拉菜单中选择_添加配置…_,然后选择_在调试终端运行“npm start”_来进行配置。更详细的设置说明参见 Visual Studio Code 的[调试文档](https://code.visualstudio.com/docs/editor/debugging)。
-下面是一张截图,显示代码执行在保存新笔记的过程中被暂停:
+下面的截图显示代码在保存新笔记的中途已暂停执行:

-代码执行在第 69 行的断点处停止。在控制台中,您可以看到
note 变量的值。在左上角的窗口中,您可以看到与应用程序状态相关的其他信息。
+代码执行在第 69 行的
断点处停止。在控制台中,你可以看到
note 变量的值。在左上角的窗口中,你可以看到应用状态的其他相关信息。
顶部的箭头可以用于控制调试器的流程。
@@ -45,86 +46,75 @@ __注意__:Visual Studio Code 的较新版本可能会将“Debug”更改为
#### Chrome dev tools
-您也可以通过在命令中启动应用程序来使用 Chrome 开发者控制台进行调试:
+通过以下命令启动应用,也可以使用 Chrome 开发者控制台进行调试:
```bash
node --inspect index.js
```
-
-您还可以将 `--inspect` 标志传递给 `nodemon`:
-
-```bash
-nodemon --inspect index.js
-```
-
-您可以通过点击 Chrome 开发者控制台中出现的绿色图标(node logo)来访问调试器:
+你可以通过点击 Chrome 开发者控制台中的绿色图标——Node 的 logo——来访问调试器:

-调试器的界面与在 React 应用程序中的使用方式相同。可以使用
Sources选项卡设置断点,代码执行将在断点处暂停。
+调试界面的用法与调试 React 应用时的用法相同。可以在
源代码选项卡中设置断点,代码将在断点处暂停执行。

-应用程序的所有
console.log消息都将出现在调试器的
Console选项卡中。您还可以检查变量的值并执行自己的 JavaScript 代码。
+应用所有的
console.log 消息都将出现在调试器的
控制台选项卡中。你还可以检查变量的值并执行自己的 JavaScript 代码。

-#### Question everything
+
+#### 怀疑一切
-调试全栈应用程序可能一开始看起来很棘手。很快,我们的应用程序除了前端和后端之外还将有一个数据库,而应用程序中可能存在许多潜在的错误。
+调试全栈应用一开始可能看起来很棘手。我们的应用除了前端和后端之外,很快还将又有一个数据库,应用中将有许多可能出现问题的地方。
-当应用程序"无法工作"时,我们首先必须找出问题实际发生在哪里。问题往往存在于您意想不到的地方,可能需要几分钟、几小时甚至几天才能找到问题的根源。
+当应用“无法运行”时,我们首先必须找出问题实际发生在哪里。问题往往存在于你意想不到的地方,并且可能要几分钟、几小时甚至几天才能找到问题的根源。
-关键是要有系统性。由于问题可能存在于任何地方,
您必须对所有事物提出质疑,逐个排除所有可能性。记录到控制台、使用 Postman、调试器和经验都会有所帮助。
+关键是要有条不紊。由于问题可能存在于任何地方,
你必须怀疑一切,逐个排除所有可能性。记录到控制台,借助 Postman、调试器和经验都会有所帮助。
-当出现错误时,
最糟糕的策略就是继续编写代码。这将确保您的代码很快会有更多的错误,并且调试它们将变得更加困难。丰田生产系统的 [Jidoka](https://leanscape.io/principles-of-lean-13-jidoka/)(停止和修复)原则 在这种情况下也非常有效。
+当出现错误时,
所有策略中最差的就是继续编写代码。这么做保证会让你的代码很快有更多的错误,并且更难以调试。丰田生产体系的[自动化](https://leanscape.io/principles-of-lean-13-jidoka/)(停止和修复)原则在这种情况下也非常有效。
### MongoDB
-
-为了永久存储我们保存的笔记,我们需要一个数据库。赫尔辛基大学的大多数课程使用关系数据库。在本课程的大部分内容中,我们将使用 [MongoDB](https://www.mongodb.com/),这是一种所谓的 [文档数据库](https://en.wikipedia.org/wiki/Document-oriented_database)。
+
+为了永久存储我们保存的笔记,我们需要一个数据库。赫尔辛基大学教授的大多数课程使用的都是关系数据库。在本课程的大部分章节中,我们将使用 [MongoDB](https://www.mongodb.com/),这是一种[文档数据库](https://en.wikipedia.org/wiki/Document-oriented_database)。
-选择使用 Mongo 作为数据库的原因是它相对于关系数据库来说更简单。本课程的 [第13部分](/zh/part13) 展示了如何构建使用关系数据库的 Node.js 后端。
+使用 Mongo 作为数据库的原因是它相对于关系数据库来说更简单。本课程的[第 13 章节](/zh/part13)展示了如何构建使用关系数据库的 Node.js 后端。
文档数据库与关系数据库在数据组织方式和支持的查询语言方面有所不同。文档数据库通常被归类为 [NoSQL](https://en.wikipedia.org/wiki/NoSQL) 的范畴。
-您可以从 [数据库导论课程](https://tikape-s18.mooc.fi/part7/) 的 [part7](https://tikape-s18.mooc.fi/part7/) 材料中了解有关文档数据库和 NoSQL 的更多信息。不幸的是,该材料目前仅提供芬兰语版本。
+你可以从 Introduction to Databases 课程[第 7 周](https://tikape-s18.mooc.fi/part7/)的教材中了解更多文档数据库和 NoSQL 的信息。不幸的是,该教材目前只有芬兰语。
-现在,请阅读 MongoDB 手册中关于 [集合(collections)](https://docs.mongodb.com/manual/core/databases-and-collections/) 和 [文档(documents)](https://docs.mongodb.com/manual/core/document/) 的章节,以了解文档数据库如何存储数据的基本概念。
+现在阅读 MongoDB 手册中关于[集合](https://docs.mongodb.com/manual/core/databases-and-collections/)和[文档](https://docs.mongodb.com/manual/core/document/)的章节,对文档数据库是如何存储数据的有一个基本概念。
-当然,您可以在计算机上安装和运行 MongoDB。然而,互联网上也有许多可用的 Mongo 数据库服务。在本课程中,我们首选的 MongoDB 提供商将是 [MongoDB Atlas](https://www.mongodb.com/atlas/database)。
-
-
-创建并登录到您的帐户后,让我们首先选择免费选项:
-
-
+当然,你可以在你自己的电脑上安装并运行 MongoDB。然而,互联网上也有许多可以利用的 Mongo 数据库服务。在本课程中,我们首选的 MongoDB 提供商是 [MongoDB Atlas](https://www.mongodb.com/atlas/database)。
-
-选择云提供商和位置,并创建集群:
+
+创建你的帐户并登录后,让我们用首页上的按钮新建一个集群。在打开的页面中,选择免费计划,决定云服务提供商和数据中心,然后创建集群:

-
-让我们等待集群准备就绪。这可能需要几分钟时间。
+
+这里选择的云服务提供商是
AWS,地区是
斯德哥尔摩(eu-north-1)。注意如果你选择了其他选项,你的数据库连接字符串会与本例中的略有不同。等待集群准备就绪。这可能需要几分钟时间。
-**注意**:在集群准备就绪之前,请不要继续进行。
+**注** 在集群准备就绪之前,先不要继续阅读。
-让我们使用
security(安全)选项卡为数据库创建用户凭据。请注意,这些凭据与您用于登录 MongoDB Atlas 的凭据不同。这些凭据将用于您的应用程序连接到数据库。
+让我们使用
security 选项卡创建数据库的用户凭据。请注意,这些凭据不同于登录 MongoDB Atlas 的凭据。这些凭据是用来将你的应用连接到数据库的。

@@ -134,59 +124,55 @@ nodemon --inspect index.js

-注意:如果对话框菜单对您而言不同,根据 MongoDB 文档,将 0.0.0.0 添加为 IP 地址也允许从任何地方访问。
+注:如果你的对话框菜单不同,根据 MongoDB 文档,将 0.0.0.0 添加为 IP 地址也会允许从任何地方访问。
-
-最后,我们准备好连接到我们的数据库了。首先点击
connect:
+
+终于,我们准备好连接到我们的数据库了。要连接到数据库,我们需要数据库连接字符串,在界面中选择
Connect,然后选择
Drivers,数据库连接字符串就在
Connect to your application 一节中:

-
-然后选择:
Connect to your application
-
-
-
-视图显示了
MongoDB URI,这是我们将提供给我们的应用程序的 MongoDB 客户端库的数据库地址。
+界面显示了
MongoDB URI,这是我们要在应用中提供给 MongoDB 客户端库的数据库地址:
+
+
-地址看起来是这样子的:
+地址类似这样:
```js
-mongodb+srv://fullstack:thepasswordishere@cluster0.o1opl.mongodb.net/?retryWrites=true&w=majority
+mongodb+srv://fullstack:thepasswordishere@cluster0.a5qfl.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0
```
我们现在已经准备好使用数据库了。
-我们可以直接从我们的 JavaScript 代码中使用数据库,使用[官方的 MongoDB Node.js 驱动程序](https://mongodb.github.io/node-mongodb-native/),但是使用起来相当麻烦。我们将使用[Mongoose](http://mongoosejs.com/index.html)库,它提供了一个更高级的 API。
+我们可以在我们的 JavaScript 代码中用 [MongoDB 官方 Node.js 驱动](https://mongodb.github.io/node-mongodb-native/)来直接使用数据库,但是这个驱动用起来相当麻烦。因此我们将使用 [Mongoose](http://mongoosejs.com/index.html) 库,它提供了一个更高级的 API。
-Mongoose可以被描述为一个
对象文档映射器(ODM),使用这个库将JavaScript对象保存为Mongo文档非常简单。
+Mongoose 可以当作一个
对象文档映射器(ODM,Object Document Mapper),使用这个库后,将 JavaScript 对象保存为 Mongo 文档就简单了。
-让我们在笔记项目的后端中安装Mongoose:
+让我们在笔记项目的后端中安装 Mongoose:
```bash
npm install mongoose
```
-暂时先不要在后端添加任何与Mongo相关的代码。相反,我们可以通过在笔记后端应用程序的根目录下创建一个新文件
mongo.js来创建一个练习应用程序:
+暂时先不要在后端添加任何与 Mongo 相关的代码。让我们先在笔记后端应用的根目录下新建一个文件
mongo.js来创建一个练习应用:
```js
const mongoose = require('mongoose')
-if (process.argv.length<3) {
+if (process.argv.length < 3) {
console.log('give password as argument')
process.exit(1)
}
const password = process.argv[2]
-const url =
- `mongodb+srv://fullstack:${password}@cluster0.o1opl.mongodb.net/?retryWrites=true&w=majority`
+const url = `mongodb+srv://fullstack:${password}@cluster0.a5qfl.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0`
mongoose.set('strictQuery',false)
@@ -211,37 +197,36 @@ note.save().then(result => {
```
-**注意**:根据您在构建集群时选择的区域,
MongoDB URI可能与上面提供的示例不同。您应该验证并使用从MongoDB Atlas生成的正确URI。
+**注:**根据你在构建集群时选择的地区,
MongoDB URI 可能会与上面提供的示例不同。你应该验证并使用从 MongoDB Atlas 生成的正确 URI。
-代码还假设它将通过命令行参数传递从我们在MongoDB Atlas中创建的凭据中生成的密码。我们可以像这样访问命令行参数:
+代码还假定我们在 MongoDB Atlas 中创建的凭据的密码将通过命令行参数传入。我们可以像这样访问命令行参数:
```js
const password = process.argv[2]
```
-当使用命令
node mongo.js yourPassword运行代码时,Mongo将向数据库添加一个新文档。
+当使用命令
node mongo.js yourPassword 运行代码时,Mongo 将向数据库添加一个新文档。
-**注意**:请注意,密码是为数据库用户创建的密码,而不是您的MongoDB Atlas密码。此外,如果您创建了一个带有特殊字符的密码,那么您需要对该密码进行[URL编码](https://docs.atlas.mongodb.com/troubleshoot-connection/#special-characters-in-connection-string-password)。
+**注:**请注意这里的密码是为数据库用户创建的密码,而不是 MongoDB Atlas 的密码。此外,如果你创建的密码带有特殊字符,那么你需要[用 URL 编码该密码](https://docs.atlas.mongodb.com/troubleshoot-connection/#special-characters-in-connection-string-password)。
-我们可以从MongoDB Atlas的
浏览集合选项卡中查看数据库的当前状态。
+我们可以从 MongoDB Atlas 的 Database 选项卡的
Browse Collections 中查看数据库的当前状态。

-正如视图所示,与笔记匹配的
文档已添加到
myFirstDatabase数据库中的
notes集合中。
+正如视图所示,与笔记匹配的
文档已添加到
myFirstDatabase 数据库中的
notes 集合中。

-让我们销毁默认数据库
test,并通过修改URI中引用的数据库名称将其更改为
noteApp:
+让我们删除默认的数据库
test,并将连接字符串引用的数据库的名称更改为
noteApp,将 URI 改成:
```js
-const url =
- `mongodb+srv://fullstack:${password}@cluster0.o1opl.mongodb.net/noteApp?retryWrites=true&w=majority`
+const url = `mongodb+srv://fullstack:${password}@cluster0.a5qfl.mongodb.net/noteApp?retryWrites=true&w=majority&appName=Cluster0`
```
@@ -250,12 +235,13 @@ const url =

-数据现在存储在正确的数据库中。该视图还提供了
create database(创建数据库)功能,可以用于从网站创建新数据库。这样创建数据库是不必要的,因为当应用程序尝试连接到尚不存在的数据库时,MongoDB Atlas会自动创建一个新数据库。
+数据现在存储在正确的数据库中。该界面还提供了
create database 功能,用于从网站新建数据库。没有必要这样新建数据库,因为当应用尝试连接到一个尚不存在的数据库时,MongoDB Atlas 会自动新建一个数据库。
-### Schema
+
+### 模式
-在与数据库建立连接后,我们为笔记定义了[schema](http://mongoosejs.com/docs/guide.html),并创建了相应的[model](http://mongoosejs.com/docs/models.html):
+在建立数据库的连接后,我们定义了笔记的[模式](http://mongoosejs.com/docs/guide.html)和对应的[模型](http://mongoosejs.com/docs/models.html):
```js
const noteSchema = new mongoose.Schema({
@@ -267,21 +253,22 @@ const Note = mongoose.model('Note', noteSchema)
```
-首先,我们定义了存储在 _noteSchema_ 变量中的笔记的[schema](http://mongoosejs.com/docs/guide.html)。该schema告诉 Mongoose 如何将笔记对象存储在数据库中。
+首先,我们定义了笔记的[模式](http://mongoosejs.com/docs/guide.html)并存储在 _noteSchema_ 变量中。该模式告诉 Mongoose 笔记对象是怎么存储在数据库中的。
-在 _Note_ 模型定义中,第一个
"Note"参数是模型的单数名称。集合的名称将是小写复数形式的
notes,因为[Mongoose的惯例](http://mongoosejs.com/docs/models.html)是自动将集合命名为复数形式(例如
notes),当schema以单数形式(例如
Note)引用它们时。
+在 _Note_ 模型定义中,第一个
“Note”参数是模型单数形式的名称。集合的名称会是小写复数形式的
notes。这是因为 [Mongoose 的习惯](http://mongoosejs.com/docs/models.html)是当模式以单数形式(如
Note)引用集合时,会自动将集合命名为其复数形式(如
notes)。
-像Mongo这样的文档数据库是
schemaless,这意味着数据库本身并不关心存储在数据库中的数据的结构。可以在同一集合中存储具有完全不同字段的文档。
+像 Mongo 这样的文档数据库是
无模式的,这意味着数据库本身并不关心数据库中存储的数据的结构。可以在同一集合中存储字段完全不同的文档。
-Mongoose的思想是,存储在数据库中的数据在应用程序级别被赋予一个
schema,该schema定义了存储在任何给定集合中的文档的形状。
+Mongoose 的思想是,
在应用层面给定数据库中存储的数据的
模式,来定义存储在任何给定集合中的文档的形状。
-### Creating and saving objects
+
+### 创建和保存对象
-
-接下来,应用程序使用
Note[model](http://mongoosejs.com/docs/models.html)创建一个新的笔记对象:
+
+接下来,应用借助
Note [模型](https://mongoosejs.com/docs/models.html)创建一个新的笔记对象:
```js
const note = new Note({
@@ -290,11 +277,11 @@ const note = new Note({
})
```
-
-模型(Models)是所谓的
构造函数,它根据提供的参数创建新的JavaScript对象。由于对象是用模型的构造函数创建的,因此它们具有模型的所有属性,这包括用于将对象保存到数据库的方法。
+
+模型是根据提供的参数创建新的 JavaScript 对象的
构造函数。由于对象是用模型的构造函数创建的,因此它们具有模型的所有属性,包括将对象保存到数据库的方法。
-将对象保存到数据库使用的是适当命名的 _save_ 方法,可以通过 _then_ 方法提供一个事件处理程序:
+将对象保存到数据库使用顾名思义的 _save_ 方法,可以通过 _then_ 方法为 _save_ 方法提供一个事件处理函数:
```js
note.save().then(result => {
@@ -303,19 +290,20 @@ note.save().then(result => {
})
```
-
-当对象保存到数据库时,提供给 _then_ 的事件处理程序会被调用。事件处理程序使用命令
mongoose.connection.close() 关闭数据库连接。如果不关闭连接,程序将永远不会结束执行。
+
+当对象保存到数据库时,会调用提供给 _then_ 的事件处理函数。事件处理函数使用命令
mongoose.connection.close() 关闭数据库连接。如果不关闭连接,那么在程序结束之前,连接将一直打开。
-保存操作的结果在事件处理程序的 _result_ 参数中。当我们在数据库中存储一个对象时,结果并不那么有趣。如果你想在实现应用程序或在调试期间仔细查看它,你可以将对象打印到控制台。
+保存操作的结果在事件处理函数的 _result_ 参数中。当我们在数据库中存储一个对象时,保存操作的结果没什么有趣的。如果你想在实现应用或在调试时仔细查看它,可以将对象打印到控制台。
-我们也可以通过修改代码中的数据并再次执行程序来保存更多的笔记。
+让我们修改代码中的数据并再次执行程序来保存更多的笔记。
-**注意:**不幸的是,Mongoose的文档并不非常一致,部分文档在其示例中使用回调,其他部分使用其他样式,因此不建议直接从那里复制和粘贴代码。不建议在同一代码中混合使用promise和旧式的回调。
+**注:**不幸的是,Mongoose 的文档并不非常一致,部分文档在示例中使用回调函数,其他部分使用其他风格,因此不建议直接从那里复制和粘贴代码。不建议在同一份代码中混合使用 Promise 和传统的回调。
-### Fetching objects from the database
+
+### 从数据库中获取对象
让我们注释掉生成新笔记的代码,并用以下内容替换它:
@@ -335,13 +323,13 @@ Note.find({}).then(result => {

-通过_Note_模型的[find](https://mongoosejs.com/docs/api/model.html#model_Model-find)方法从数据库中检索对象。该方法的参数是一个表示搜索条件的对象。由于参数是一个空对象
{},我们得到了_notes_集合中存储的所有笔记。
+对象是通过 _Note_ 模型的 [find](https://mongoosejs.com/docs/api/model.html#model_Model-find) 方法从数据库中获取的。find 方法的参数是一个表示搜索条件的对象。由于参数是一个空对象
{},我们得到了 _notes_ 集合中存储的所有笔记。
-搜索条件遵循Mongo搜索查询[syntax](https://docs.mongodb.com/manual/reference/operator/)。
+搜索条件遵循 Mongo 的搜索查询[语法](https://docs.mongodb.com/manual/reference/operator/)。
-我们可以限制我们的搜索只包括重要的笔记,像这样:
+我们可以这么限制我们的搜索,使其只包含重要的笔记:
```js
Note.find({ important: true }).then(result => {
@@ -353,49 +341,51 @@ Note.find({ important: true }).then(result => {
-### Exercise 3.12.
+
+### 练习 3.12.
-#### 3.12: Command-line database
+
+#### 3.12:命令行数据库
-使用MongoDB Atlas为电话簿应用程序创建一个基于云的MongoDB数据库。
+使用 MongoDB Atlas 为电话簿应用创建一个基于云的 MongoDB 数据库。
-在项目目录中创建一个mongo.js文件,该文件可以用于向电话簿添加条目,以及列出电话簿中所有已有的条目。
+在项目目录中创建一个mongo.js文件,用于向电话簿添加记录,以及列出电话簿中所有已有的记录。
-**注意:** 不要在你提交和推送到GitHub的文件中包含密码!
+**注意:**不要在你提交和推送到 GitHub 的文件中包含密码!
-应用程序应该如下工作。你通过传递三个命令行参数(第一个是密码)来使用程序,例如:
+应用应该实现下列功能。你通过传递三个命令行参数(第一个是密码)来使用程序,例如:
```bash
node mongo.js yourpassword Anna 040-1234556
```
-因此,应用程序将打印:
+运行后,应用将打印:
```bash
added Anna number 040-1234556 to phonebook
```
-新的电话簿条目将被保存到数据库中。注意,如果名字包含空格字符,它必须被包含在引号中:
+新的电话簿记录将被保存到数据库中。注意如果名字包含空格字符,就必须用引号引起来:
```bash
node mongo.js yourpassword "Arto Vihavainen" 045-1232456
```
-如果密码是给程序的唯一参数,意味着它像这样被调用:
+如果密码是给程序的唯一参数,也就是这样调用:
```bash
node mongo.js yourpassword
```
-那么程序应该显示电话簿中的所有条目:
+那么程序应该显示电话簿中的所有记录:
```
phonebook:
@@ -405,10 +395,10 @@ Ada Lovelace 040-1231236
```
-你可以从[process.argv](https://nodejs.org/docs/latest-v18.x/api/process.html#process_process_argv)变量获取命令行参数。
+你可以从 [process.argv](https://nodejs.org/docs/latest-v18.x/api/process.html#process_process_argv) 变量获取命令行参数。
-**注意:不要在错误的地方关闭连接**。例如,以下代码将无法工作:
+**注意:不要在错误的地方关闭连接**。例如,以下代码将无法正确运行:
```js
Person
@@ -421,7 +411,7 @@ mongoose.connection.close()
```
-在上面的代码中,mongoose.connection.close()命令将在Person.find操作开始后立即执行。这意味着数据库连接将立即关闭,执行永远不会到达Person.find操作完成和callback函数被调用的地方。
+在上面的代码中,mongoose.connection.close() 命令将在 Person.find 操作开始后立即执行。这会立即关闭数据库连接,从而永远不会执行到 Person.find 操作完成并调用回调函数的地方。
关闭数据库连接的正确位置是在回调函数的末尾:
@@ -436,28 +426,27 @@ Person
```
-**注意:** 如果你用Person这个名字定义一个模型,mongoose会自动将关联的集合命名为people。
+**注意:**如果你将模型的名字定义为 Person,mongoose 会自动将关联的集合命名为 people。
-### Connecting the backend to a database
+
+### 将后端连接到数据库
-现在我们已经有足够的知识开始在我们的笔记应用程序后端中使用Mongo。
+现在我们已经有足够的知识来在我们笔记应用的后端开始使用 Mongo。
-让我们通过复制粘贴Mongoose定义到index.js文件来快速开始:
+让我们通过将 Mongoose 的定义复制粘贴到 index.js 文件来快速开始:
```js
const mongoose = require('mongoose')
-const password = process.argv[2]
-
// DO NOT SAVE YOUR PASSWORD TO GITHUB!!
-const url =
- `mongodb+srv://fullstack:${password}@cluster0.o1opl.mongodb.net/?retryWrites=true&w=majority`
+const password = process.argv[2]
+const url = `mongodb+srv://fullstack:${password}@cluster0.a5qfl.mongodb.net/noteApp?retryWrites=true&w=majority&appName=Cluster0`
mongoose.set('strictQuery',false)
mongoose.connect(url)
@@ -471,7 +460,7 @@ const Note = mongoose.model('Note', noteSchema)
```
-让我们将获取所有笔记的处理器更改为以下形式:
+让我们将处理获取所有笔记的函数改成:
```js
app.get('/api/notes', (request, response) => {
@@ -481,24 +470,17 @@ app.get('/api/notes', (request, response) => {
})
```
-
-我们可以在浏览器中验证后端是否可以显示所有的文档:
+
+让我们用命令 node --watch index.js yourpassword 启动后端,于是我们可以在浏览器中验证后端是否正确显示所有保存到数据库中的笔记:

-应用程序几乎完美地工作。前端假设每个对象在id字段中都有一个唯一的id。我们也不想将mongo版本控制字段\_\_v返回给前端。
-
-
-格式化Mongoose返回的对象的一种方法是[modify(修改)](https://stackoverflow.com/questions/7034848/mongodb-output-id-instead-of-id)模式的 _toJSON_ 方法,该方法在用该模式产生的模型的所有实例上使用。
-
-
-要modify(修改)该方法,我们需要更改模式的可配置选项,可以使用模式的set方法更改选项,更多关于此方法的信息请参见: 。有关 _toJSON_ 选项的更多信息,请参阅 和 。
+应用几乎完美地运行。只是前端假定每个对象都有的一个唯一 id 是在 id 字段中。我们也不想将 mongo 的版本控制字段 \_\_v 返回给前端。
+
+更改 Mongoose 返回对象的格式的一种方法是[修改](https://stackoverflow.com/questions/7034848/mongodb-output-id-instead-of-id)模式的 _toJSON_ 方法,该方法应用于该模式产生的所有模型的所有实例。可以这么修改:
-
-有关 _transform_ 函数的更多信息,请参阅 。
-
```js
noteSchema.set('toJSON', {
transform: (document, returnedObject) => {
@@ -510,10 +492,10 @@ noteSchema.set('toJSON', {
```
-尽管Mongoose对象的\_id属性看起来像一个字符串,但实际上它是一个对象。我们定义的 _toJSON_ 方法将其转换为字符串以确保安全。如果我们不做这个改变,一旦我们开始编写测试,它将在未来对我们造成更大的麻烦。
+尽管 Mongoose 对象的 \_id 属性看起来像一个字符串,但它实际上是一个对象。我们定义的 _toJSON_ 方法将其转换成字符串以确保安全。如果我们不这么改的话,一旦将来我们开始编写测试,对象形式的 *\_id* 属性就会给我们带来更大的危害。
-在处理器中不需要做任何改变:
+处理函数不需要更改:
```js
app.get('/api/notes', (request, response) => {
@@ -524,14 +506,16 @@ app.get('/api/notes', (request, response) => {
```
-代码在格式化响应的笔记时将自动使用定义的 _toJSON_ 。
-### Database configuration into its own module
+代码在将笔记格式化为响应的格式时会自动使用定义的 _toJSON_ 方法。
+
+
+### 将数据库配置移到自己的模块
-在我们将后端的其余部分重构为使用数据库之前,让我们将Mongoose特定的代码提取到它自己的模块中。
+在我们将后端的其余部分重构为使用数据库之前,让我们先将 Mongoose 特定的代码提取到它自己的模块中。
-让我们为模块创建一个名为models的新目录,并添加一个名为note.js的文件:
+让我们为模块新建一个名为 models 的目录,并添加一个名为 note.js 的文件:
```js
const mongoose = require('mongoose')
@@ -540,8 +524,7 @@ mongoose.set('strictQuery', false)
const url = process.env.MONGODB_URI // highlight-line
-console.log('connecting to', url) // highlight-line
-
+console.log('connecting to', url)
mongoose.connect(url)
// highlight-start
.then(result => {
@@ -568,30 +551,27 @@ noteSchema.set('toJSON', {
module.exports = mongoose.model('Note', noteSchema) // highlight-line
```
-
-定义Node [modules(模块)](https://nodejs.org/docs/latest-v18.x/api/modules.html)的方式与在第2部分中定义[ES6 modules](/zh/part2/从渲染集合到模块学习#refactoring-modules)的方式略有不同。
+
+代码与之前相比有一些变化。数据库连接 URL 现在通过 MONGODB_URI 环境变量传递给应用,因为将其硬编码到应用中并不明智:
-
-modules(模块)的公共接口是通过为 _module.exports_ 变量设置一个值来定义的。我们将值设置为Note模型。在模块内部定义的其他东西,如变量 _mongoose_ 和 _url_ ,对模块的用户来说将不可访问或不可见。
+```js
+const url = process.env.MONGODB_URI
+```
-
-导入模块是通过在index.js中添加以下行来实现的:
+
+有许多方法定义环境变量的值。例如,我们可以在启动应用时定义:
-```js
-const Note = require('./models/note')
+```bash
+MONGODB_URI="your_connection_string_here" npm run dev
```
-
-这样,_Note_ 变量将被赋值为模块定义的同一个对象。
+
+我们稍后会学习一种更优雅的定义环境变量的方法。
-建立连接的方式有所改变:
+建立连接的方式略有变化:
```js
-const url = process.env.MONGODB_URI
-
-console.log('connecting to', url)
-
mongoose.connect(url)
.then(result => {
console.log('connected to MongoDB')
@@ -601,56 +581,65 @@ mongoose.connect(url)
})
```
-
-将数据库的地址硬编码到代码中并不是一个好主意,所以我们通过MONGODB_URI环境变量将数据库的地址传递给应用程序。
-
-建立连接的方法现在被赋予了处理成功和失败的连接尝试的函数。两个函数只是将成功状态的消息记录到控制台:
+建立连接的方法现在有了处理连接成功和失败的函数。两个函数都只是将连接成功与否的消息记录到控制台:

-
-有许多方法可以定义环境变量的值。一种方法是在启动应用程序时定义它:
+
+定义 Node [模块](https://nodejs.org/docs/latest-v18.x/api/modules.html)的方式与在第 2 章节中定义 [ES6 模块](/zh/part2/渲染集合与模块#重构模块)的方式略有不同。
-```bash
-MONGODB_URI=address_here npm run dev
+
+模块的公共接口是通过设定 _module.exports_ 变量的值来定义的。我们将值设置为 Note 模型。在模块内部定义的其他东西,如变量 _mongoose_ 和 _url_,对模块的用户而言将不可访问,也不可见。
+
+
+导入模块是通过在 index.js 中添加下面这一行来实现的:
+
+```js
+const Note = require('./models/note')
```
-
-更聪明的方法是使用[dotenv](https://github.com/motdotla/dotenv#readme)库。你可以用以下命令安装这个库:
+
+这样,_Note_ 变量将被赋值为模块定义的同一个对象。
+
+
+### 使用 dotenv 库定义环境变量
+
+
+一种更优雅地定义环境变量的方法是使用 [dotenv](https://github.com/motdotla/dotenv#readme) 库。你可以用以下命令安装这个库:
```bash
npm install dotenv
```
-要使用这个库,我们在项目的根目录下创建一个.env文件。环境变量在文件内部定义,它可以像这样:
+要使用这个库,我们要在项目的根目录下创建一个 .env 文件。环境变量在这个文件内定义,可以类似这样:
```bash
-MONGODB_URI=mongodb+srv://fullstack:thepasswordishere@cluster0.o1opl.mongodb.net/noteApp?retryWrites=true&w=majority
+MONGODB_URI=mongodb+srv://fullstack:thepasswordishere@cluster0.a5qfl.mongodb.net/noteApp?retryWrites=true&w=majority&appName=Cluster0
PORT=3001
```
-我们也将服务器的硬编码端口添加到PORT环境变量中。
+我们也将硬编码的服务端端口添加到 PORT 环境变量中。
-**我们应该立即将.env文件添加到gitignore中,因为我们不希望公开发布任何机密信息!**
+**应当立即将 .env 文件添加到 .gitignore 中,不要把任何秘密信息发布到网上!**

-在.env文件中定义的环境变量可以通过表达式require('dotenv').config()引入,你可以像引用普通环境变量一样在代码中引用它们,使用process.env.MONGODB_URI语法。
+.env 文件中定义的环境变量可以通过表达式 require('dotenv').config() 导入,然后你在代码中就可以像引用普通环境变量一样,用 process.env.MONGODB_URI 语法引用它们。
-
-让我们以以下方式更改index.js文件:
+
+让我们在 index.js 文件的开头导入环境变量,这样就可以在整个应用中使用环境变量了。让我们将 index.js 文件更改为:
```js
require('dotenv').config() // highlight-line
const express = require('express')
-const app = express()
const Note = require('./models/note') // highlight-line
+const app = express()
// ..
const PORT = process.env.PORT // highlight-line
@@ -660,39 +649,38 @@ app.listen(PORT, () => {
```
-在导入note模型之前导入dotenv非常重要。这确保了在导入其他模块的代码之前,.env文件中的环境变量在全局范围内可用。
+重要的是 dotenv 要在 note 模型之前导入。这能确保在导入其他模块的代码之前,.env 文件中的环境变量在全局范围内(译注:包括其他导入的模块内)都可用。
-### Important note to Fly.io users
+
+#### 关于在 Fly.io 和 Render 中定义环境变量的重要注意事项
-
-因为GitHub不是与Fly.io一起使用的,所以当应用程序被部署时,.env文件也会被传到Fly.io服务器。因此,文件中定义的环境变量将在那里可用。
+
+**Fly.io 用户:**因为 Fly.io 不与 GitHub 一起使用,所以当部署应用时,.env 文件也会传到 Fly.io 服务器上。因此,文件中定义的环境变量在 Fly.io 也可用。
-然而,[更好的选择](https://community.fly.io/t/clarification-on-environment-variables/6309)是通过在项目根目录创建 _.dockerignore_ 文件,内容如下
+然而,[更好的选择](https://community.fly.io/t/clarification-on-environment-variables/6309)是通过在项目根目录创建 _.dockerignore_ 文件来防止 .env 被复制到 Fly.io,*.dockerignore* 的内容如下
```bash
.env
```
-并使用以下命令从命令行设置环境值:
+并在命令行用以下命令设置环境变量的值:
```bash
-fly secrets set MONGODB_URI="mongodb+srv://fullstack:thepasswordishere@cluster0.o1opl.mongodb.net/noteApp?retryWrites=true&w=majority"
+fly secrets set MONGODB_URI="mongodb+srv://fullstack:thepasswordishere@cluster0.a5qfl.mongodb.net/noteApp?retryWrites=true&w=majority&appName=Cluster0"
```
-
-由于PORT也在我们的.env中定义,所以实际上在Fly.io中忽略该文件是至关重要的,否则应用程序将在错误的端口启动。
-
-
-在使用Render时,通过在仪表板中定义适当的环境变量给出数据库url:
+
+**Render 用户:**在使用Render时,数据库 URL 通过在仪表板中定义适当的环境变量提供:

-
-只需将以mongodb+srv://开头的URL设置到_value_字段。
+
+只需将 _value_ 字段设为以 mongodb+srv:// 开头的 URL。
-### Using database in route handlers
+
+### 在路由处理函数中使用数据库
接下来,让我们将后端的其余功能更改为使用数据库。
@@ -704,7 +692,7 @@ fly secrets set MONGODB_URI="mongodb+srv://fullstack:thepasswordishere@cluster0.
app.post('/api/notes', (request, response) => {
const body = request.body
- if (body.content === undefined) {
+ if (!body.content) {
return response.status(400).json({ error: 'content missing' })
}
@@ -720,7 +708,7 @@ app.post('/api/notes', (request, response) => {
```
-笔记对象是用 _Note_ 构造函数创建的。响应在 _save_ 操作的回调函数内部发送。这确保只有在操作成功时才发送响应。我们稍后会讨论错误处理。
+笔记对象是用 _Note_ 构造函数创建的。响应是在 _save_ 操作的回调函数内发送的。这确保只有在操作成功时才发送响应。我们稍后会讨论如何处理错误。
回调函数中的 _savedNote_ 参数是保存的新创建的笔记。响应中发送回来的数据是用 _toJSON_ 方法自动创建的格式化版本:
@@ -730,7 +718,7 @@ response.json(savedNote)
```
-使用Mongoose的[findById](https://mongoosejs.com/docs/api/model.html#model_Model-findById)方法,获取单个笔记的操作变为以下形式:
+通过使用 Mongoose 的 [findById](https://mongoosejs.com/docs/api/model.html#model_Model-findById) 方法,获取单个笔记的操作变为以下形式:
```js
app.get('/api/notes/:id', (request, response) => {
@@ -740,64 +728,100 @@ app.get('/api/notes/:id', (request, response) => {
})
```
-### Verifying frontend and backend integration
+
+### 验证前后端的整合
-当后端的功能被扩展时,首先使用**浏览器、Postman或VS Code REST客户端**测试后端是个好主意。接下来,让我们在启用数据库后尝试创建一个新的笔记:
+当后端扩展后,首先使用**浏览器、Postman 或 VS Code REST Client** 测试后端是明智的。接下来,让我们在启用数据库后尝试创建一个新的笔记:

-只有在后端的所有内容都经过验证并正常工作后,才是测试前端与后端是否协同工作的好时机。仅通过前端进行测试效率极低。
+只有在后端的所有内容都经过验证并正确运行后,才是测试前端与后端是否协同工作的时候。仅通过前端进行测试效率极低。
-逐个集成前端和后端的功能可能是个好主意。首先,我们可以实现从数据库获取所有笔记的功能,并通过浏览器中的后端端点进行测试。然后,我们可以验证前端是否能与新的后端一起工作。一旦所有东西看起来都在工作,我们就会转向下一个功能。
+逐个集成前后端的功能可能是个好主意。首先,我们可以实现从数据库获取所有笔记的功能,然后在浏览器中通过后端端点进行测试。然后,我们可以验证前端是否能与新的后端一起正确运行。一旦所有东西看起来都正确,我们就可以继续下一个功能。
-一旦我们引入数据库,查看数据库中持久化的状态是非常有用的,例如,从MongoDB Atlas的控制面板中查看。在开发过程中,像我们之前写的mongo.js这样的小型Node助手程序往往非常有帮助。
+一旦我们引入数据库,查看数据库中持久化的状态是非常有用的,比如通过 MongoDB Atlas 的控制面板查看。在开发过程中,小型 Node 辅助程序,比如我们之前写的 mongo.js,往往非常有帮助。
-你可以在part3-4分支的[这个GitHub仓库](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-4)中找到我们当前应用程序的完整代码。
+你可以在[这个 GitHub 仓库](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-4)的 part3-4 分支中找到我们当前应用的完整代码。
+
+
+### 真正的全栈开发者的誓言
+
+
+现在又是练习的时候了。我们的应用的复杂性现在又上升了一个阶段,因为除了前端和后端,我们还有一个数据库。
+
+的确有很多可能的错误来源。
+
+
+所以我们应该再次扩展我们的誓言:
+
+
+全栈开发极其困难,因此我会尽一切可能使其变得更容易
+
+
+- 我会始终打开浏览器的开发者控制台
+
+- 我会使用浏览器开发者工具的网络标签页,确保前后端按预期通信
+
+- 我会持续留意服务端的状态,确保前端发送的数据按预期保存
+
+- 我会留意数据库:后端是否以正确的格式保存数据
+
+- 我小步前进
+
+- 我会写大量的 _console.log_ 语句,以确保我理解代码的行为,并借此定位问题
+
+- 如果我的代码不能正确运行,我不会写更多的代码。相反,我会开始删除代码直到它能正确运行,或者直接回到一切都还正常的状态
+
+- 当我在课程的 Discord 频道或其他地方寻求帮助时,我会恰当地陈述我的问题,参见[这里](https://fullstackopen.com/en/part0/general_info#how-to-get-help-in-discord)了解如何寻求帮助
-### Exercises 3.13.-3.14.
+
+### 练习 3.13.~3.14.
-以下练习相当简单,但如果你的前端停止与后端一起工作,那么找出并修复错误可能会相当有趣。
+以下练习相当简单,但如果你的前端无法与后端一起运行,那么寻找并修复错误的过程可能会相当有趣。
-#### 3.13: Phonebook database, step 1
+
+#### 3.13:电话簿数据库,第 1 步
-更改所有电话簿条目的获取方式,使数据从数据库中获取。
+将获取所有电话簿记录的方式更改为从数据库中获取数据。
-在更改后,验证前端是否正常工作。
+在更改后,验证前端是否依然正确运行。
-
-在接下来的练习中,将所有Mongoose特定的代码写入其自己的模块,就像我们在[数据库配置到自己的模块](/zh/part3/将数据存入_mongo_db#database-configuration-into-its-own-module)一章中做的那样。
+
+在接下来的练习中,将所有 Mongoose 特定的代码写入其自己的模块,就像我们在[将数据库配置到自己的模块](/zh/part3/将数据存入_mongo_db#将数据库配置移到自己的模块)一章中做的那样。
-#### 3.14: Phonebook database, step 2
+
+#### 3.14:电话簿数据库,第 2 步
-更改后端,使新的号码保存到数据库中。验证更改后你的前端是否仍然工作。
+更改后端,使新的号码保存到数据库中。在更改后,验证前端是否依然正确运行。
-在这个阶段,你可以忽略是否已经有一个人在数据库中与你要添加的人同名。
+在这个阶段,你可以先不考虑数据库中是否已经有一个人与你要添加的人同名的情况。
-### Error handling
+
+### 错误处理
-如果我们尝试访问一个不存在的笔记的URL,例如,其中5c41c90e84d891c15dfa3431不是存储在数据库中的id,那么响应将为 _null_ 。
+如果我们尝试访问一个不存在的笔记的 URL,例如 ,其中 5c41c90e84d891c15dfa3431 不是存储在数据库中的 id,那么响应将为 _null_ 。
-让我们改变这种行为,如果给定id的笔记不存在,服务器将以HTTP状态码404未找到来响应请求。此外,让我们实现一个简单的catch块来处理findById方法返回的promise被拒绝的情况:
+让我们改变这种行为,如果给定 id 的笔记不存在,服务器将以 HTTP 状态码 404 not found 来响应请求。此外,让我们实现一个简单的 catch 块来处理 findById 方法返回的 Promise 被拒绝的情况:
```js
app.get('/api/notes/:id', (request, response) => {
@@ -821,10 +845,10 @@ app.get('/api/notes/:id', (request, response) => {
```
-如果在数据库中没有找到匹配的对象, _note_ 的值将为 _null_ ,并执行 _else_ 块。这将导致一个带有状态码404 not found的响应。如果 findById 方法返回的 promise 被拒绝,响应将有状态码500内部服务器错误。控制台会显示关于错误的更详细的信息。
+如果数据库中没有找到匹配的对象,_note_ 的值将为 _null_ 并执行 _else_ 块。这将导致响应状态码 404 not found。如果 findById 方法返回的 Promise 被拒绝,响应的状态码将是 500 internal server error。控制台会显示更详细的错误信息。
-除了不存在的笔记,还有一个需要处理的错误情况。在这种情况下,我们试图获取一个错误类型的_id_,也就是说,_id_与Mongo标识符格式不匹配。
+除了不存在的笔记,还有一个需要处理的错误情况。在这种情况下,我们试图获取一个错误类型的 _id_,也就是说,_id_ 与 Mongo 的标识符 *_id* 的格式不匹配。
如果我们发出以下请求,我们将得到下面的错误消息:
@@ -841,10 +865,10 @@ Body: {}
```
-给出一个格式错误的id作为参数,findById方法将抛出错误,导致返回的promise被拒绝。这将导致在catch块中定义的回调函数被调用。
+如果给出一个格式错误的 id 作为参数,findById 方法将抛出错误,导致返回的 Promise 被拒绝。这会调用 catch 块中定义的回调函数。
-让我们对catch块中的响应做一些小的调整:
+让我们对 catch 块中的响应做一些小调整:
```js
app.get('/api/notes/:id', (request, response) => {
@@ -864,19 +888,19 @@ app.get('/api/notes/:id', (request, response) => {
```
-如果id的格式不正确,那么我们将进入在_catch_块中定义的错误处理程序。适合这种情况的状态码是[400 Bad Request](https://www.rfc-editor.org/rfc/rfc9110.html#name-400-bad-request),因为这种情况完全符合描述:
+如果 id 的格式不正确,那么我们会以在 _catch_ 块中定义的错误处理程序结束。适合这种情况的状态码是 [400 Bad Request](https://www.rfc-editor.org/rfc/rfc9110.html#name-400-bad-request),因为这种情况完全符合其描述:
-> 400 (Bad Request) 状态码表示服务器不能或不会处理请求,因为有些东西被认为是客户端错误(例如,请求语法格式错误,请求消息帧格式无效,或请求路由欺骗)。
+> 400(Bad Request)状态码表示服务器不能或不会处理请求,因为服务端认为某些东西是客户端错误(例如,请求语法格式错误、请求消息帧格式无效,或请求路由欺骗)。
我们还在响应中添加了一些数据,以便解释错误的原因。
-在处理Promises时,几乎总是添加错误和异常处理的好主意。否则,你会发现自己在处理奇怪的错误。
+在处理 Promise 时,添加错误和异常处理几乎总是明智的。否则,你会发现自己在处理奇怪的错误。
-在错误处理程序中打印引发异常的对象永远不是个坏主意:
+在错误处理程序中打印引发异常的对象永远不会错:
```js
.catch(error => {
@@ -886,20 +910,21 @@ app.get('/api/notes/:id', (request, response) => {
```
-错误处理程序被调用的原因可能完全不同于你预期的。如果你将错误记录到控制台,你可能会从长时间和令人沮丧的调试会话中解救出来。此外,大多数现代服务在你部署应用程序时都支持某种形式的日志系统,你可以用来检查这些日志。如前所述,Fly.io就是其中之一。
+导致错误处理程序被调用的原因可能完全不同于你所预期的。如果你将错误记录到控制台,你可能会从长时间令人沮丧的调试会话中解救出来。此外,大多数你部署应用的现代服务都支持某种形式的日志系统,你可以借此来检查这些日志。如前所述,Fly.io 就是其中之一。
-每次你在一个有后端的项目上工作时,关注后端的控制台输出是至关重要的。如果你在一个小屏幕上工作,只需要在背景中看到一小部分输出就足够了。任何错误消息都会引起你的注意,即使控制台在后端很远:
+每次你在处理一个有后端的项目时,关注后端的控制台输出至关重要。如果你的屏幕比较小,只需要在背景中看到一小部分输出就足够了。任何错误消息都会引起你的注意,即使控制台在很后面也如此:

-### Moving error handling into middleware
+
+### 将错误处理移至中间件
-我们在其他代码中编写了错误处理程序的代码。有时这可能是一个合理的解决方案,但有些情况下,最好在一个地方实现所有的错误处理。如果我们稍后想向像[Sentry](https://sentry.io/welcome/)这样的外部错误跟踪系统报告与错误相关的数据,这可能特别有用。
+我们在其他代码中编写了错误处理函数的代码。有些情况下这可能是一个合理的解决方案,但有些情况下,最好在一个地方实现所有的错误处理。如果我们后面想向诸如 [Sentry](https://sentry.io/welcome/) 这样的外部错误跟踪系统报告与错误相关的数据,这可能特别有用。
-让我们更改/api/notes/:id路由的处理程序,使其使用next函数将错误传递下去。下一个函数作为第三个参数传递给处理程序:
+让我们更改 /api/notes/:id 路由的处理函数,使用 next 函数来传递错误。next 函数作为第三个参数传递给处理函数:
```js
app.get('/api/notes/:id', (request, response, next) => { // highlight-line
@@ -915,11 +940,11 @@ app.get('/api/notes/:id', (request, response, next) => { // highlight-line
})
```
-
-向前传递的错误作为一个参数给到next函数。如果next没有参数被调用,那么执行将简单地移动到下一个路由或中间件。如果next函数带有参数被调用,那么执行将继续到错误处理中间件。
+
+传递的错误作为参数传给 next 函数。如果调用 next 时没有传递参数,那么就将简单继续执行下一个路由或中间件。如果调用 next 函数时有一个参数,那么将继续执行错误处理中间件。
-Express的[(error handlers)错误处理器](https://expressjs.com/en/guide/error-handling.html)是定义了一个接受四个参数的函数的中间件。我们的错误处理器看起来像这样:
+Express 的[错误处理函数](https://expressjs.com/en/guide/error-handling.html)是一个定义为接受四个参数的函数的中间件。我们的错误处理函数类似这样:
```js
const errorHandler = (error, request, response, next) => {
@@ -937,15 +962,16 @@ app.use(errorHandler)
```
-错误处理器检查错误是否为CastError异常,如果是,我们知道错误是由Mongo的无效对象id引起的。在这种情况下,错误处理器将使用作为参数传递的响应对象向浏览器发送响应。在所有其他错误情况下,中间件将错误传递给默认的Express错误处理器。
+错误处理函数检查错误是否为 CastError 异常,我们知道这个错误是由 Mongo 的无效对象 id 引起的。在这种情况下,错误处理函数将使用作为参数传递的 response 对象向浏览器发送响应。对于其他所有错误情况,中间件将错误传递给默认的 Express 错误处理函数。
-注意,错误处理中间件必须是最后加载的中间件,所有的路由都应该在错误处理器之前注册!
+注意,错误处理中间件必须是最后加载的中间件,并且所有的路由都应该在错误处理函数之前注册!
-### The order of middleware loading
+
+### 加载中间件的顺序
-
-中间件的执行顺序与它们被加载到express的app.use函数的顺序相同。因此,定义中间件时需要小心。
+
+中间件的执行顺序与它们通过 _app.use_ 函数被加载到 Express 的顺序相同。因此,定义中间件时需要小心。
正确的顺序是:
@@ -976,7 +1002,8 @@ app.use(errorHandler)
```
-json-parser中间件应该是加载到Express中的第一个中间件。如果顺序是以下的:
+json-parser 中间件应该是最先加载到 Express 中的中间件。如果顺序是这样的话:
+
```js
app.use(requestLogger) // request.body is undefined!
@@ -990,13 +1017,10 @@ app.use(express.json())
```
-那么,HTTP请求发送的JSON数据在logger中间件或POST路由处理器中将不可用,因为在这个点上 _request.body_ 将是 _undefined_。
-
-
-同样重要的是,处理不支持的路由的中间件是加载到Express中的最后一个中间件,就在错误处理器之前。
+那么,HTTP 请求发送的 JSON 数据在 logger 中间件和 POST 路由处理函数中都将不可用,因为这时 _request.body_ 还是 _undefined_。
-
-例如,以下加载顺序会导致问题:
+
+同样重要的是,处理不支持的路由的中间件应当在定义完所有端点之后才加载,只在错误处理函数之前。例如,以下加载顺序会导致问题:
```js
const unknownEndpoint = (request, response) => {
@@ -1012,15 +1036,16 @@ app.get('/api/notes', (request, response) => {
```
-现在,未知端点的处理是在HTTP请求处理器之前进行的。由于未知端点处理器对所有请求都以404 unknown endpoint响应,所以在未知端点中间件发送响应后,不会调用任何路由或中间件。唯一的例外是错误处理器,它需要在未知端点处理器之后,放在最后。
+现在,未知端点的处理是在 HTTP 请求处理函数之前进行的。由于未知端点处理函数对所有请求都以 404 unknown endpoint 响应,所以在未知端点中间件发送响应后,不会调用任何路由或中间件。唯一的例外是错误处理函数需要放在最后,在未知端点处理函数之后。
-### Other operations
+
+### 其他操作
-让我们为我们的应用程序添加一些缺失的功能,包括删除和更新单个笔记。
+让我们为我们的应用添加一些缺失的功能,包括删除和更新单个笔记。
-从数据库删除笔记的最简单方法是使用[findByIdAndDelete](https://mongoosejs.com/docs/api/model.html#Model.findByIdAndDelete())方法:
+从数据库删除笔记最简单的方法是使用 [findByIdAndDelete](https://mongoosejs.com/docs/api/model.html#Model.findByIdAndDelete()) 方法:
```js
app.delete('/api/notes/:id', (request, response, next) => {
@@ -1033,111 +1058,108 @@ app.delete('/api/notes/:id', (request, response, next) => {
```
-在删除资源的两种"成功"情况下,后端都以 204 no content 的状态码响应。这两种不同的情况是删除存在的笔记,和删除数据库中不存在的笔记 _result_ 回调参数可以用于检查是否实际删除了资源,如果我们认为有必要,我们可以使用这个信息为这两种情况返回不同的状态码。任何发生的异常都会传递给错误处理器。
+对于删除资源的两种“成功”情况,后端都以 204 no content 的状态码响应。这两种不同的情况是删除存在的笔记,和删除数据库中不存在的笔记。_result_ 回调参数可以用于检查是否实际删除了资源,并且如果我们认为有必要的话,我们也可以根据 _result_ 的信息为两种情况返回不同的状态码。任何发生的异常都会传递给错误处理函数。
-
-使用[findByIdAndUpdate](https://mongoosejs.com/docs/api/model.html#model_Model-findByIdAndUpdate)方法可以轻松地切换笔记的重要性。
+
+让我们实现更新单条笔记的功能,允许更改笔记的重要性。更新笔记的功能实现如下:
```js
app.put('/api/notes/:id', (request, response, next) => {
- const body = request.body
+ const { content, important } = request.body
- const note = {
- content: body.content,
- important: body.important,
- }
+ Note.findById(request.params.id)
+ .then(note => {
+ if (!note) {
+ return response.status(404).end()
+ }
+
+ note.content = content
+ note.important = important
- Note.findByIdAndUpdate(request.params.id, note, { new: true })
- .then(updatedNote => {
- response.json(updatedNote)
+ return note.save().then((updatedNote) => {
+ response.json(updatedNote)
+ })
})
.catch(error => next(error))
})
```
-
-在上面的代码中,我们还允许编辑笔记的内容。
+
+要更新的笔记首先通过 _findById_ 方法从数据库中获取。如果数据库中没有具有给定 id 的对象,变量 _note_ 的值将为 _null_,会以状态码 404 Not Found 响应查询。
-
-注意,findByIdAndUpdate方法接收的是一个常规的JavaScript对象作为参数,而不是一个用Note构造函数创建的新笔记对象。
+
+如果找到了具有给定 id 的对象,会用请求中提供的数据更新对象的 _content_ 和 _important_ 字段,然后用 _save()_ 方法将修改后的笔记保存到数据库。最后发送更新后的笔记来响应 HTTP 请求。
-
-关于使用findByIdAndUpdate方法有一个重要的细节。默认情况下,事件处理器的updatedNote参数接收的是[没有修改的](https://mongoosejs.com/docs/api/model.html#model_Model-findByIdAndUpdate)原始文档。我们添加了可选的{ new: true }参数,这将导致我们的事件处理器被调用时,使用新的修改过的文档而不是原始文档。
+
+值得注意的一点是,代码现在包含嵌套的 Promise,在外层的 _.then_ 方法内部又定义了另一个 [Promise 链](https://javascript.info/promise-chaining):
-
-在直接使用Postman或VS Code REST客户端测试后端后,我们可以验证它似乎是工作的。前端也似乎能够使用数据库与后端一起工作。
+```js
+ .then(note => {
+ if (!note) {
+ return response.status(404).end()
+ }
-
-你可以在part3-5分支的[这个GitHub仓库](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-5)中找到我们当前应用程序的完整代码。
-### A true full stack developer's oath
+ note.content = content
+ note.important = important
-
-现在又是练习的时候了。我们的应用程序的复杂性现在又上升了一个阶段,因为除了前端和后端,我们还有一个数据库。
+ // highlight-start
+ return note.save().then((updatedNote) => {
+ response.json(updatedNote)
+ })
+ // highlight-end
+```
-
-的确,有很多可能的错误来源。
+
+通常不推荐这样做,这会使代码难以阅读。然而这样做至少在本例中能正确运行,因为这样做能确保 _save()_ 方法之后的 _.then_ 块只有在数据库中找到了具有给定 id 的笔记并调用了 _save()_ 方法时才会执行。在本课程的第四章节,我们将学习 async/await 语法,来更简单、更清晰地处理这类情况。
-
-所以我们应该再次扩展我们的誓言:
+
+在直接使用 Postman 或 VS Code REST Client 测试后端后,我们可以验证它似乎能正确运行。前端也显示能与使用数据库的后端一起正确运行。
-
-全栈开发是极其困难的,这就是为什么我会使用所有可能的手段来使它变得更容易
-
-
-
-- 我会一直打开浏览器开发者控制台
-- 我会使用浏览器开发工具的网络标签,确保前端和后端的通信符合我的预期
-- 我会不断关注服务器的状态,确保前端发送到那里的数据按我预期的方式保存
-- 我会关注数据库:后端是否以正确的格式保存数据
-- 我会以小步骤前进
-- 我会写很多的_console.log_语句,以确保我理解代码的行为,并帮助定位问题
-- 如果我的代码不能工作,我不会写更多的代码。相反,我开始删除代码,直到它工作,或者只是返回到一切都还在工作的状态
-- 当我在课程的Discord频道或其他地方寻求帮助时,我会合适地提出我的问题,看[这里](https://fullstackopen.com/en/part0/general_info#how-to-get-help-in-discord)了解如何寻求帮助。
+
+你可以在[这个 GitHub 仓库](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-5)的 part3-5 分支中找到我们当前应用的完整代码。
-### Exercises 3.15.-3.18.
+
+### 练习 3.15.~3.18.
-#### 3.15: Phonebook database, step 3
+
+#### 3.15:电话簿数据库,第 3 步
-更改后端,使得删除电话簿条目在数据库中得到反映。
+更改后端,使得删除电话簿记录能在数据库中显示。
-在进行更改后,验证前端是否仍然工作。
+在进行更改后,验证前端是否仍然正确运行。
-#### 3.16: Phonebook database, step 4
+
+#### 3.16:电话簿数据库,第 4 步
-将应用程序的错误处理移动到新的错误处理中间件。
+将应用的错误处理移到新的错误处理中间件。
-#### 3.17*: Phonebook database, step 5
+
+#### 3.17*:电话簿数据库,第 5 步
-如果用户试图为电话簿中已有姓名的人创建新的电话簿条目,前端将尝试通过向条目的唯一URL发送HTTP PUT请求来更新现有条目的电话号码。
+如果用户试图为姓名已存在于电话簿中的人创建新的电话簿记录,前端将尝试通过向记录的唯一 URL 发送 HTTP PUT 请求来更新现有记录的电话号码。
-修改后端以支持这个请求。
+修改后端以支持这类请求。
-在进行更改后,验证前端是否工作。
+在进行更改后,验证前端是否正确运行。
-#### 3.18*: Phonebook database step 6
+
+#### 3.18*:电话簿数据库 第 6 步
-也试试更新
api/persons/:id和
info路由的处理,以使用数据库,并验证它们是否可以直接使用浏览器、Postman或VS Code REST客户端工作。
+将
api/persons/:id 和
info 路由的处理也更新为使用数据库,并验证它们是否可以直接在浏览器、Postman 或 VS Code REST Client 上正确运行。
-从浏览器查看单个电话簿条目应该是这样的:
+从浏览器查看单个电话簿记录应该类似这样:

diff --git a/src/content/3/zh/part3d.md b/src/content/3/zh/part3d.md
index 1e69b10cdb7..e0124d66a9e 100644
--- a/src/content/3/zh/part3d.md
+++ b/src/content/3/zh/part3d.md
@@ -8,13 +8,13 @@ lang: zh
-我们通常希望对存储在应用程序数据库中的数据应用一些约束。我们的应用程序不应接受缺少或空的content属性的笔记。在路由处理器中检查笔记的有效性:
+我们通常希望对存储在应用程序数据库中的数据应用一些约束。我们的应用程序不应接受 content 属性缺失或为空的笔记。现在笔记的有效性在路由处理函数中检查:
```js
app.post('/api/notes', (request, response) => {
const body = request.body
// highlight-start
- if (body.content === undefined) {
+ if (!body.content) {
return response.status(400).json({ error: 'content missing' })
}
// highlight-end
@@ -24,10 +24,10 @@ app.post('/api/notes', (request, response) => {
```
-如果笔记没有content属性,我们将以400 bad request的状态码响应请求。
+如果笔记没有 content 属性,我们就以 400 bad request 的状态码响应请求。
-在数据存储到数据库之前验证数据格式的一种更智能的方法是使用Mongoose提供的[验证](https://mongoosejs.com/docs/validation.html)功能。
+在数据存储到数据库之前验证数据格式的一种更智能的方法是使用 Mongoose 提供的[验证](https://mongoosejs.com/docs/validation.html)功能。
我们可以为模式中的每个字段定义特定的验证规则:
@@ -46,13 +46,13 @@ const noteSchema = new mongoose.Schema({
```
-现在,content字段要求至少为五个字符长,并且被设置为必需,意味着它不能缺失。我们没有对important字段添加任何约束,所以它在模式中的定义没有改变。
+现在 content 字段要求至少五个字符长,并且被设为 required,意味着字段不能缺失。我们没有对 important 字段添加任何约束,所以它在模式中的定义没有改变。
-minLength和required验证器是Mongoose提供的[内置](https://mongoosejs.com/docs/validation.html#built-in-validators)验证器。如果没有一个内置的验证器能满足我们的需求,Mongoose的[自定义验证器](https://mongoosejs.com/docs/validation.html#custom-validators)功能允许我们创建新的验证器。
+minLength 和 required 验证器是 Mongoose 提供的[内置](https://mongoosejs.com/docs/validation.html#built-in-validators)的验证器。如果内置的验证器全都无法满足我们的需求,我们还可以用 Mongoose 的[自定义验证器](https://mongoosejs.com/docs/validation.html#custom-validators)功能创建新的验证器。
-如果我们试图在数据库中存储一个违反了某些约束的对象,操作将会抛出异常。让我们改变我们创建新笔记的处理器,使其将任何可能的异常传递给错误处理中间件:
+如果我们试图在数据库中存储一个违反了某些约束的对象,操作将会抛出异常。让我们更改创建新笔记的处理函数,来将任何可能的异常传递给错误处理中间件:
```js
app.post('/api/notes', (request, response, next) => { // highlight-line
@@ -72,7 +72,7 @@ app.post('/api/notes', (request, response, next) => { // highlight-line
```
-让我们扩展错误处理器以处理这些验证错误:
+让我们扩展错误处理函数来处理这些验证错误:
```js
const errorHandler = (error, request, response, next) => {
@@ -89,94 +89,79 @@ const errorHandler = (error, request, response, next) => {
```
-当对象验证失败时,我们从Mongoose返回以下默认错误消息:
+当对象验证失败时,我们返回以下 Mongoose 的默认错误消息:

-
-我们注意到后端现在有一个问题:在编辑笔记时没有进行验证。
-这个问题可以解决,[update-validators 文档](https://mongoosejs.com/docs/validation.html#update-validators)解释说,在执行findOneAndUpdate和相关方法时,默认不会运行验证。
-
-
-但是要修复这个问题很简单。让我们也稍微改写一下路由代码:
-
-```js
-app.put('/api/notes/:id', (request, response, next) => {
- const { content, important } = request.body // highlight-line
-
- Note.findByIdAndUpdate(
- request.params.id,
- { content, important }, // highlight-line
- { new: true, runValidators: true, context: 'query' } // highlight-line
- )
- .then(updatedNote => {
- response.json(updatedNote)
- })
- .catch(error => next(error))
-})
-```
-
-### Deploying the database backend to production
+
+### 将数据库后端部署到生产环境
-该应用程序应该能在Fly.io/Render上按原样工作。由于到目前为止我们只对后端进行了修改,所以我们不需要生成前端的新生产构建。
+该应用程序应该能在 Fly.io/Render 上按原样工作。由于到目前为止我们只对后端进行了修改,所以我们不需要构建前端的新生产版本。
-在dotenv中定义的环境变量只会在后端不处于生产模式时使用,即在Fly.io或Render中。
+dotenv 中定义的环境变量只会在后端不处于生产模式,即不在 Fly.io 或 Render 中时使用。
-对于生产环境,我们需要在托管我们应用的服务中设置数据库URL。
+对于生产环境,我们需要在托管我们应用的服务中设置数据库 URL。
-在Fly.io中,可以通过_fly secrets set_命令来完成:
+在 Fly.io 中,可以通过 _fly secrets set_ 命令来完成:
```bash
-fly secrets set MONGODB_URI='mongodb+srv://fullstack:thepasswordishere@cluster0.o1opl.mongodb.net/noteApp?retryWrites=true&w=majority'
+fly secrets set MONGODB_URI='mongodb+srv://fullstack:thepasswordishere@cluster0.a5qfl.mongodb.net/noteApp?retryWrites=true&w=majority'
```
-当应用正在开发过程中,很可能会出现一些失败的情况。例如,当我第一次部署带有数据库的应用时,一个笔记都没有看到:
+在开发应用的过程中,很可能会出现一些失败的情况。例如,当我第一次部署带有数据库的应用时,一个笔记都没有看到:

-浏览器控制台的网络标签页显示,获取笔记的请求并未成功,请求只是在_pending_状态下停留了很长时间,直到最后以502状态码失败。
+浏览器控制台的网络标签页显示并未成功获取笔记,请求只是在 _pending_ 状态下停留了很长时间,最后以 502 状态码失败。
-浏览器控制台必须始终保持打开状态!
+必须始终打开浏览器控制台!
-同时,持续关注服务器日志也非常重要。当我打开 _fly logs_ 查看日志时,问题就显而易见了:
+同时,持续关注服务端日志也非常重要。当我用 _fly logs_ 打开日志时,问题就显而易见了:

-数据库URL是 _undefined_ ,所以忘记了执行 *fly secrets set MONGODB\_URI* 命令。
+数据库 URL 是 _undefined_ ,所以是忘记执行 *fly secrets set MONGODB\_URI* 命令了。
+
+
+你还需要在 MongoDB Atlas 中将 fly.io 应用的 IP 地址添加到白名单中。否则 MongoDB 会拒绝连接。
+
+
+遗憾的是,fly.io 不会给你的应用提供一个专门的 IPv4 地址,所以你需要在 MongoDB Atlas 中允许所有的 IP 地址。
-在使用Render时,可以通过在仪表板中定义适当的环境变量来提供数据库URL:
+在使用 Render 时,可以通过在仪表板中定义适当的环境变量来提供数据库 URL:

-Render仪表板显示服务器日志:
+Render 仪表板显示服务端日志:

-你可以在[此GitHub仓库](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-6)的part3-6分支中找到我们当前应用的完整代码。
+你可以在[这个 GitHub 仓库](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-6)的 part3-6 分支中找到我们当前应用的完整代码。
-### Exercises 3.19.-3.21.
+
+### 练习 3.19.~3.21.
-#### 3.19*: Phonebook database, step 7
+
+#### 3.19*:电话簿数据库,第 7 步
-将验证扩展,使得存储在数据库中的名称至少需要三个字符长。
+扩展验证,使存储在数据库中的名字需要至少三个字符长。
扩展前端,使其在发生验证错误时显示某种形式的错误消息。可以通过添加一个 catch 块来实现错误处理,如下所示:
@@ -194,13 +179,14 @@ personService
```
-你可以显示由Mongoose返回的默认错误消息,尽管它们并不像它们可能的那样易读:
+你可以显示 Mongoose 返回的默认错误消息,尽管这些错误信息并没有那么易读:

-**注意:**在 _update_ 操作中,mongoose验证器默认是关闭的。[阅读文档](https://mongoosejs.com/docs/validation.html)以确定如何启用它们。
+**注意:**在 update 操作中,mongoose 验证器默认是关闭的。[阅读文档](https://mongoosejs.com/docs/validation.html)来确定如何启用它们。
-#### 3.20*: Phonebook database, step 8
+
+#### 3.20*:电话簿数据库,第 8 步
为你的电话簿应用添加验证,确保电话号码的格式正确。电话号码必须:
@@ -210,58 +196,81 @@ personService
- eg. 09-1234556 and 040-22334455 are valid phone numbers
- eg. 1234556, 1-22334455 and 10-22-334455 are invalid -->
-- 长度为8或更多
-- 由两部分组成,两部分由-分隔,第一部分有两个或三个数字,第二部分也由数字组成
- - 例如,09-1234556和040-22334455是有效的电话号码
- - 例如,1234556,1-22334455和10-22-334455是无效的电话号码
+- 8 个字符或更长
+- 由两部分组成,由“-”分隔,第一部分有两个或三个数字,第二部分也都由数字组成
+ - 例如,09-1234556 和 040-22334455 是有效的电话号码
+ - 例如,1234556、1-22334455 和 10-22-334455 是无效的电话号码
使用[自定义验证器](https://mongoosejs.com/docs/validation.html#custom-validators)来实现验证的第二部分。
-如果HTTP POST请求试图添加一个电话号码无效的人,服务器应该以适当的状态码和错误消息响应。
+如果 HTTP POST 请求试图添加一个电话号码无效的人,服务端应该响应合适的状态码和错误消息。
-#### 3.21 Deploying the database backend to production
+
+#### 3.21 将数据库后端部署到生产环境
-Generate a new "full stack" version of the application by creating a new production build of the frontend, and copying it to the backend repository. Verify that everything works locally by using the entire application from the address .
-通过创建前端的新的生产构建,生成应用程序的新的"全栈"版本,并将其复制到后端仓库。验证本地的所有操作是否正常,通过从地址使用整个应用程序。
+
+通过构建前端的新生产版本,并复制到后端仓库,来生成应用程序的新“全栈”版本。通过在地址 使用整个应用程序,验证所有操作在本地是否正常。
将最新版本推送到 Fly.io/Render,并验证那里的所有操作是否也正常。
-
-**NOTE**: 你应该将后端部署到云服务。如果你使用的是Fly.io,命令应该在后端的根目录中运行(也就是在后端的package.json所在的目录中)。如果使用的是Render,后端必须位于你的仓库的根目录中。
-
-
-在这一部分的任何阶段,你都不应该直接部署前端。整个部分都是部署后端仓库,没有其他的。
+
+**注:**在本章节的任何阶段,你都**不**应直接部署前端。整个章节都只部署后端。将前端构建的生产版本添加到后端仓库中,然后让后端来提供,如同[由后端提供静态文件](/zh/part3/把应用部署到互联网上#由后端提供静态文件)一节中所描述的那样。
-### Lint
+
+### lint
-在我们进入下一部分之前,我们介绍一个重要的工具,叫做[lint]()。维基百科对lint的描述如下:
+在我们进入下一章节之前,我们来介绍一个重要的工具,叫做 [lint]()。维基百科对 lint 的描述如下:
-> 一般来说,lint或者linter是任何检测和标记编程语言中错误的工具,包括样式错误。术语 _lint-like behavior_ 有时用于标记可疑语言使用的过程。Lint类 的工具通常对源代码进行静态分析。
+> 一般来说,lint 或 linter 是任何检测并标记编程语言中的错误,包括样式错误的工具。术语“lint 类行为”有时用于标记语言的可疑用法的过程。lint 类工具通常对源代码进行静态分析。
-在编译的静态类型语言如Java中,像NetBeans这样的IDE可以指出代码中的错误,甚至是编译错误之外的错误。像[checkstyle](https://checkstyle.sourceforge.io)这样的用于执行[静态分析](https://en.wikipedia.org/wiki/Static_program_analysis)的附加工具,可以用来扩展IDE的能力,也可以指出与样式相关的问题,如缩进。
+对于编译型静态类型语言,比如 Java,NetBeans 等 IDE 可以指出代码中的错误,甚至不止是编译错误。像 [checkstyle](https://checkstyle.sourceforge.io) 这样用于执行[静态分析](https://en.wikipedia.org/wiki/Static_program_analysis)的附加工具,可以用来扩展 IDE 的能力,来指出与样式相关的问题,如缩进。
-在JavaScript领域,目前主导的静态分析(又名"linting")工具是[ESlint](https://eslint.org/)。
+在 JavaScript 领域,目前领头的静态分析工具(又称“linting”)是[ESlint](https://eslint.org/)。
-
-让我们使用以下命令将ESlint作为开发依赖项安装到notes后端项目中:
+
+让我们将 ESLint 添加为后端的开发依赖项。开发依赖项是只在开发应用的过程中需要的工具。比如,和测试有关的工具就是开发依赖项。当应用以生产模式运行时,就不需要开发依赖项了。
+
+
+使用以下命令将 ESlint 作为开发依赖项安装到后端项目中:
```bash
-npm install eslint --save-dev
+npm install eslint @eslint/js --save-dev
+```
+
+
+package.json 文件的内容会这么变化:
+
+```js
+{
+ //...
+ "dependencies": {
+ "dotenv": "^16.4.7",
+ "express": "^5.1.0",
+ "mongoose": "^8.11.0"
+ },
+ "devDependencies": { // highlight-line
+ "@eslint/js": "^9.22.0", // highlight-line
+ "eslint": "^9.22.0" // highlight-line
+ }
+}
```
+
+该命令在文件中添加了 devDependencies 一节,并在其中添加了 eslint 和 @eslint/js,同时在 node_modules 目录中安装了所需的库。
+
-之后我们可以用以下命令初始化一个默认的ESlint配置:
+之后我们可以用以下命令初始化默认的 ESlint 配置:
```bash
npx eslint --init
@@ -272,87 +281,106 @@ npx eslint --init

-
-配置将会保存在 _.eslintrc.js_ 文件中。我们将在 _env_ 配置中将 _browser_ 改为 _node_:
+
+配置将会保存在 _eslint.config.mjs_ 文件中。
+
+
+### 格式化配置文件
+
+
+让我们重设配置文件 _eslint.config.mjs_ 的格式,将它从当前的格式改为:
```js
-module.exports = {
- "env": {
- "commonjs": true,
- "es2021": true,
- "node": true // highlight-line
+import globals from 'globals'
+
+export default [
+ {
+ files: ['**/*.js'],
+ languageOptions: {
+ sourceType: 'commonjs',
+ globals: { ...globals.node },
+ ecmaVersion: 'latest',
},
- "overrides": [
- {
- "env": {
- "node": true
- },
- "files": [
- ".eslintrc.{js,cjs}"
- ],
- "parserOptions": {
- "sourceType": "script"
- }
- }
- ],
- "parserOptions": {
- "ecmaVersion": "latest"
- },
- "rules": {
- }
-}
+ },
+]
```
-
-让我们稍微修改一下配置。安装一个[插件](https://eslint.style/packages/js),该插件定义了一套与代码风格相关的规则:
+
+到目前为止,ESLint 配置文件在 _files_ 选项中定义了 _["\*\*/\*.js"]_,告诉 ESLint 要检查项目目录中的所有 JavaScript 文件。_languageOptions_ 属性指定 ESLint 应支持的语言特性相关的选项,其中我们将 _sourceType_ 设为“commonjs”。这表示我们项目中的 JavaScript 代码使用 CommonJS 模块系统,从而使 ESLint 能以相应的方法分析代码。
+
+
+_globals_ 属性指定预定义的全局变量。这里使用的展开运算符告诉 ESLint 包含 _globals.node_ 设定中定义的所有全局变量,比如 _process_。对于浏览器端代码,我们会在这里定义 _globals.browser_,来允许浏览器特有的全局变量,比如 _window_ 和 _document_。
+
+最后,_ecmaVersion_ 属性设为“latest”。这将 ECMAScript 版本设为最新可用版本,意味着 ESLint 能理解并正确检查最新的 JavaScript 语法和特性。
+我们希望同时使用 [ESLint 推荐的](https://eslint.org/docs/latest/use/configure/configuration-files#using-predefined-configurations)和自定义的设置。之前安装的 _@eslint/js_ 包提供了 ESLint 的预定义配置。我们可以在配置文件中导入并启用:
+
+```js
+import globals from 'globals'
+import js from '@eslint/js' // highlight-line
+// ...
+
+export default [
+ js.configs.recommended, // highlight-line
+ {
+ // ...
+ },
+]
```
+
+
+我们已将 _js.configs.recommended_ 添加到配置数组的最上面,这确保 ESLint 的推荐设置会在我们自定义的选项之前先应用。
+
+
+让我们继续构建配置文件。安装一个定义了一套与代码样式相关的规则的[插件](https://eslint.style/packages/js):
+
+```bash
npm install --save-dev @stylistic/eslint-plugin-js
```
-
-启用插件并添加一个扩展定义和四个代码风格规则:
+
+导入并启用插件,并添加这四条代码样式规则:
```js
-module.exports = {
+import globals from 'globals'
+import js from '@eslint/js'
+import stylisticJs from '@stylistic/eslint-plugin-js' // highlight-line
+
+export default [
+ {
// ...
- 'plugins': [
- '@stylistic/js'
- ],
- 'extends': 'eslint:recommended',
- 'rules': {
- '@stylistic/js/indent': [
- 'error',
- 2
- ],
- '@stylistic/js/linebreak-style': [
- 'error',
- 'unix'
- ],
- '@stylistic/js/quotes': [
- 'error',
- 'single'
- ],
- '@stylistic/js/semi': [
- 'error',
- 'never'
- ],
- }
-}
+ // highlight-start
+ plugins: {
+ '@stylistic/js': stylisticJs,
+ },
+ rules: {
+ '@stylistic/js/indent': ['error', 2],
+ '@stylistic/js/linebreak-style': ['error', 'unix'],
+ '@stylistic/js/quotes': ['error', 'single'],
+ '@stylistic/js/semi': ['error', 'never'],
+ },
+ // highlight-end
+ },
+]
```
-
-扩展 _eslint:recommended_ 将一套[推荐的规则](https://eslint.org/docs/latest/rules/)添加到项目中。此外,还添加了关于缩进、换行、连字符和分号的规则。这四条规则都在[Eslint样式插件](https://eslint.style/packages/js)中定义了。
+
+[plugins](https://eslint.org/docs/latest/use/configure/plugins) 属性提供了一种可以通过添加自定义规则、配置以及其他核心 ESLint 库中没有的功能来扩展 ESLint 功能的方法。我们已安装并启用了 _@stylistic/eslint-plugin-js_,它为 ESLint 添加了 JavaScript 样式的规则。此外,还添加了关于缩进、换行、引号和分号的规则,这四条规则都是在 [Eslint styles plugin](https://eslint.style/packages/js) 中定义的。
+
+**Windows 用户注意事项:**样式规则中将换行样式设为了 _unix_。建议无论使用什么操作系统都使用 Unix 样式的换行符(_\n_),这样文件可以兼容大多数现代操作系统,并且在多人处理同一文件时更方便协作。如果使用 Windows 样式的换行符,ESLint 会产生如下错误:Expected linebreaks to be 'LF' but found 'CRLF'。遇到这种情况时,按照[这份指南](https://stackoverflow.com/questions/48692741/how-can-i-make-all-line-endings-eols-in-all-files-in-visual-studio-code-unix)将 Visual Studio Code 配置为使用 Unix 样式换行。
+
+
+### 运行 linter
-可以使用以下命令检查和验证像 _index.js_ 这样的文件:
+要检查和验证某个文件,比如 _index.js_,可以使用以下命令:
```bash
npx eslint index.js
```
-我们建议为linting创建一个单独的 _npm script_:
+建议为 linting 创建一个专门的 _npm 脚本_:
```json
{
@@ -370,126 +398,182 @@ npx eslint index.js
现在,_npm run lint_ 命令将检查项目中的每个文件。
-
-当运行命令时,dist 目录中的文件也会被检查,我们不希望这种情况发生、我们可以通过在项目的根目录中创建一个[.eslintignore](https://eslint.org/docs/latest/use/configure/ignore#the-eslintignore-file) 文件来实现这一点,文件的内容如下:
+
+当运行命令时,dist 目录中的文件也会被检查。我们不希望这种情况发生,我们可以通过添加一个对象,并在其 [ignores](https://eslint.org/docs/latest/use/configure/ignore) 属性声明一个我们要忽略的目录和文件的数组来实现这一点。
-```bash
-dist
+```js
+// ...
+export default [
+ js.configs.recommended,
+ {
+ files: ['**/*.js'],
+ // ...
+ },
+ // highlight-start
+ {
+ ignores: ['dist/**'],
+ },
+ // highlight-end
+]
```
-这将导致整个dist目录不被ESlint检查。
+这会使得整个 dist 目录都不被 ESlint 检查。
-Lint对我们的代码有很多意见:
+lint 对我们的代码有很多意见:

-
-我们暂时不去修复这些问题。
-
-
-从命令行执行linter的更好替代方案是将eslint-plugin配置到编辑器中,这将连续运行linter。通过使用插件,你将立即在代码中看到错误。你可以在[这里](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)找到更多关于Visual Studio ESLint插件的信息。
+
+相比于从命令行执行 linter,更好的替代方案是给编辑器配置 _eslint 插件_,来持续不断地运行 linter。使用这个插件后,你将立即在代码中看到错误。你可以在[这里](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)找到更多关于 Visual Studio ESLint 插件的信息。
-VS Code的ESlint插件会用红线下划出风格违规:
+VS Code ESlint 插件会用红线划出违反样式的地方:

-这使得错误很容易被发现并立即修复。
+这会让错误很容易发现,从而立即修复。
+
+
+### 添加更多样式规则
-
-ESlint有大量的[规则](https://eslint.org/docs/rules/),这些规则通过编辑 .eslintrc.js 文件就可以很容易地使用。
+
+ESlint 有大量易于使用的[规则](https://eslint.org/docs/rules/),只要编辑 _eslint.config.mjs_ 文件即可使用。
-
-让我们添加[eqeqeq](https://eslint.org/docs/rules/eqeqeq)规则,如果用非三等号运算符检查等式,它会发出警告。该规则是在配置文件的rules字段下添加的。
+
+让我们添加 [eqeqeq](https://eslint.org/docs/rules/eqeqeq) 规则,如果相等不是用三等号*===*检查的,eqeqeq 规则就会警告我们。将该规则添加到配置文件的 rules 字段下。
```js
-{
+export default [
// ...
- 'rules': {
+ rules: {
// ...
- 'eqeqeq': 'error',
+ eqeqeq: 'error', // highlight-line
},
-}
+ // ...
+]
```
-在我们进行这项工作的同时,让我们对规则进行一些其他更改。
+让我们顺便对规则进行一些其他更改。
-
+
让我们阻止行尾的不必要的[尾随空格](https://eslint.org/docs/rules/no-trailing-spaces),要求[大括号前后始终有一个空格](https://eslint.org/docs/rules/object-curly-spacing),并且也要求箭头函数的函数参数中一致使用空格。
```js
-{
+export default [
// ...
- 'rules': {
+ rules: {
// ...
- 'eqeqeq': 'error',
+ eqeqeq: 'error',
+ // highlight-start
'no-trailing-spaces': 'error',
- 'object-curly-spacing': [
- 'error', 'always'
- ],
- 'arrow-spacing': [
- 'error', { 'before': true, 'after': true }
- ]
+ 'object-curly-spacing': ['error', 'always'],
+ 'arrow-spacing': ['error', { before: true, after: true }],
+ // highlight-end
},
-}
+]
```
-
-我们的默认配置从eslint:recommended中使用了一堆预定的规则:
+
+我们的默认配置使用了这里预定义的一串规则:
-```bash
-'extends': 'eslint:recommended',
+```js
+// ...
+
+export default [
+ js.configs.recommended,
+ // ...
+]
```
-
-这包括一个关于 _console.log_ 命令的警告规则。可以通过在配置文件中将其"值"定义为0来[禁用](https://eslint.org/docs/latest/use/configure/rules)一条规则。我们暂时为no-console规则这样做。
+
+这里包括一条警告 _console.log_ 命令的规则,而我们不想使用这条规则。禁用一条规则可以通过在配置文件中定义其“value”为 0 或 _off_ 来完成。让我们在此期间对 _no-console_ 规则这样做。
```js
-{
- // ...
- 'rules': {
+[
+ {
// ...
- 'eqeqeq': 'error',
- 'no-trailing-spaces': 'error',
- 'object-curly-spacing': [
- 'error', 'always'
- ],
- 'arrow-spacing': [
- 'error', { 'before': true, 'after': true }
- ],
- 'no-console': 0 // highlight-line
+ rules: {
+ // ...
+ eqeqeq: 'error',
+ 'no-trailing-spaces': 'error',
+ 'object-curly-spacing': ['error', 'always'],
+ 'arrow-spacing': ['error', { before: true, after: true }],
+ 'no-console': 'off', // highlight-line
+ },
},
-}
+]
```
-
-**注意** 当你对.eslintrc.js文件进行更改时,建议从命令行运行linter。这将验证配置文件是否正确格式化:
+
+禁用 no-console 规则使我们能够使用 console.log 语句而不使 ESLint 将其标记为问题。在开发期间需要调试代码时这尤其有用。下面是包含迄今为止我们所做的所有修改的完整配置文件:
+
+```js
+import globals from 'globals'
+import js from '@eslint/js'
+import stylisticJs from '@stylistic/eslint-plugin-js'
+
+export default [
+ js.configs.recommended,
+ {
+ files: ['**/*.js'],
+ languageOptions: {
+ sourceType: 'commonjs',
+ globals: { ...globals.node },
+ ecmaVersion: 'latest',
+ },
+ plugins: {
+ '@stylistic/js': stylisticJs,
+ },
+ rules: {
+ '@stylistic/js/indent': ['error', 2],
+ '@stylistic/js/linebreak-style': ['error', 'unix'],
+ '@stylistic/js/quotes': ['error', 'single'],
+ '@stylistic/js/semi': ['error', 'never'],
+ eqeqeq: 'error',
+ 'no-trailing-spaces': 'error',
+ 'object-curly-spacing': ['error', 'always'],
+ 'arrow-spacing': ['error', { before: true, after: true }],
+ 'no-console': 'off',
+ },
+ },
+ {
+ ignores: ['dist/**'],
+ },
+]
+```
+
+
+**注** 当你对 _eslint.config.mjs_ 文件进行更改时,推荐从命令行运行 linter。这将验证配置文件的格式是否正确:
+
+
-如果你的配置文件中有什么错误,lint插件可能会表现得相当不稳定。
+如果你的配置文件中有什么错误,lint 插件可能会表现得相当不稳定。
-许多公司定义编码标准,这些标准通过ESlint配置文件在整个组织中强制执行。不建议反复重新发明轮子,采用别人项目中的现成配置可能是个好主意。最近,许多项目通过采用Airbnb的[ESlint](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb)配置,采纳了Airbnb的[Javascript风格指南](https://github.com/airbnb/javascript)。
+许多公司都会定义编码标准,并通过 ESlint 配置文件在整个组织中强制实施。不推荐反复重新发明轮子,采用别人项目中现成的配置是个明智的选择。最近,许多项目都采用了 Airbnb 的[Javascript 样式指南](https://github.com/airbnb/javascript),并使用了 Airbnb 的 [ESlint](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb) 配置。
-你可以在 part3-7 分支的[这个GitHub仓库](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-7)中找到我们当前应用程序的全部代码。
+你可以在[这个 GitHub 仓库](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-7)的 part3-7 分支中找到我们当前应用程序的全部代码。
-### Exercise 3.22.
+
+### 练习 3.22.
-#### 3.22: Lint configuration
+
+#### 3.22:lint 配置
-将ESlint添加到你的应用程序中,并修复所有的警告。
+将 ESlint 添加到你的应用程序中,并修复所有警告。
-这是课程的这一部分的最后一个练习。现在是时候将你的代码推送到GitHub,并在[练习提交系统](https://studies.cs.helsinki.fi/stats/courses/fullstackopen)中标记你已完成的所有练习了。
+这是课程这一章节的最后一道练习。现在是时候将你的代码推送到 GitHub,并在[练习上交系统](https://studies.cs.helsinki.fi/stats/courses/fullstackopen)中标记你完成的所有练习了。
diff --git a/src/content/partnavigation/partnavigation.js b/src/content/partnavigation/partnavigation.js
index 9afba370eac..92a3976724c 100644
--- a/src/content/partnavigation/partnavigation.js
+++ b/src/content/partnavigation/partnavigation.js
@@ -176,9 +176,9 @@ module.exports = {
},
3: {
a: 'Node.js 与 Express',
- b: '把应用部署到网上',
- c: '将数据存入MongoDB',
- d: 'ESLint与代码检查',
+ b: '把应用部署到互联网上',
+ c: '将数据存入 MongoDB',
+ d: '验证与 ESLint',
},
4: {
a: '从后端结构到测试入门',