diff --git a/docs/cli/index.md b/docs/cli/index.md index a16d77d..9852ac1 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -20,7 +20,11 @@ $ npm install -g @nestjs/cli ``` -> **提示** 或者,您可以使用 `npx @nestjs/cli@latest` 命令而无需全局安装 CLI。 +:::info 提示 +或者,您可以使用 `npx @nestjs/cli@latest` 命令而无需全局安装 CLI。 +::: + + ## 基本工作流程 @@ -46,7 +50,11 @@ $ npm run start:dev 在浏览器中,打开 [http://localhost:3000](http://localhost:3000) 以查看正在运行的新应用程序。当您更改任何源文件时,应用程序将自动重新编译和重新加载。 -> **提示** 我们推荐使用 [SWC 构建器](../recipes/swc.md)来获得更快的构建速度(比默认的 TypeScript 编译器快 10 倍)。 +:::info 提示 +我们推荐使用 [SWC 构建器](../recipes/swc.md)来获得更快的构建速度(比默认的 TypeScript 编译器快 10 倍)。 +::: + + ## 项目结构 diff --git a/docs/cli/overview.md b/docs/cli/overview.md index fef1a62..31b60d9 100644 --- a/docs/cli/overview.md +++ b/docs/cli/overview.md @@ -12,7 +12,11 @@ $ npm install -g @nestjs/cli ``` -> **提示** 你也可以在不全局安装 CLI 的情况下使用此命令 `npx @nestjs/cli@latest`。 +:::info 提示 +你也可以在不全局安装 CLI 的情况下使用此命令 `npx @nestjs/cli@latest`。 +::: + + #### 基本工作流程 @@ -38,7 +42,9 @@ $ npm run start:dev 在浏览器中打开 [http://localhost:3000](http://localhost:3000) 即可查看运行中的新应用。当您修改任何源文件时,应用程序会自动重新编译并加载。 -> info **提示** 我们推荐使用 [SWC 构建器](/recipes/swc)以获得更快的构建速度(性能比默认 TypeScript 编译器快 10 倍)。 +:::info 提示 +我们推荐使用 [SWC 构建器](/recipes/swc)以获得更快的构建速度(性能比默认 TypeScript 编译器快 10 倍)。 +::: #### 项目结构 diff --git a/docs/cli/workspaces.md b/docs/cli/workspaces.md index 013659c..246b207 100644 --- a/docs/cli/workspaces.md +++ b/docs/cli/workspaces.md @@ -31,25 +31,20 @@ $ nest new my-project 我们已经构建了一个*标准模式*结构,其文件夹结构如下所示: -node_modules - -src - -app.controller.ts - -app.module.ts - -app.service.ts - -main.ts - -nest-cli.json - -package.json - -tsconfig.json - -eslint.config.mjs +
+
node_modules
+
src
+
+
app.controller.ts
+
app.module.ts
+
app.service.ts
+
main.ts
+
+
nest-cli.json
+
package.json
+
tsconfig.json
+
eslint.config.mjs
+
我们可以将其转换为如下所示的 monorepo 模式结构: @@ -60,47 +55,43 @@ $ nest generate app my-app 此时,`nest` 将现有结构转换为 **monorepo 模式**结构。这会导致几项重要变化。现在的文件夹结构如下所示: -apps - -my-app - -src - -app.controller.ts - -app.module.ts - -app.service.ts - -main.ts - -tsconfig.app.json - -my-project - -src - -app.controller.ts - -app.module.ts - -app.service.ts - -main.ts - -tsconfig.app.json - -nest-cli.json - -package.json - -tsconfig.json - -eslint.config.mjs +
+
apps
+
+
my-app
+
+
src
+
+
app.controller.ts
+
app.module.ts
+
app.service.ts
+
main.ts
+
+
tsconfig.app.json
+
+
my-project
+
+
src
+
+
app.controller.ts
+
app.module.ts
+
app.service.ts
+
main.ts
+
+
tsconfig.app.json
+
+
+
nest-cli.json
+
package.json
+
tsconfig.json
+
eslint.config.mjs
+
`generate app` 原理图已重新组织代码 - 将每个**应用**项目移至 `apps` 文件夹下,并在每个项目的根目录中添加项目特定的 `tsconfig.app.json` 文件。我们原来的 `my-project` 应用已成为该 monorepo 的**默认项目** ,现在与刚添加的 `my-app` 并列位于 `apps` 文件夹下。我们将在下文讨论默认项目。 -> error **警告** 将标准模式结构转换为 monorepo 仅适用于遵循标准 Nest 项目结构的项目。具体来说,在转换过程中,原理图会尝试将 `src` 和 `test` 文件夹重新定位到根目录下 `apps` 文件夹内的项目文件夹中。如果项目未使用此结构,转换将失败或产生不可靠的结果。 +:::warning 警告 +将标准模式结构转换为 monorepo 仅适用于遵循标准 Nest 项目结构的项目。具体来说,在转换过程中,原理图会尝试将 `src` 和 `test` 文件夹重新定位到根目录下 `apps` 文件夹内的项目文件夹中。如果项目未使用此结构,转换将失败或产生不可靠的结果。 +::: #### 工作区项目 @@ -250,19 +241,21 @@ Nest 将组织和构建标准项目及 monorepo 结构项目所需的元数据 } ``` -> warning **警告** 当将 `spec` 指定为对象时,生成原理图的键目前不支持自动别名处理。这意味着如果将键指定为例如 `service: false` 并尝试通过别名 `s` 生成服务,规范文件仍会被生成。为确保正常原理图名称和别名都能按预期工作,请同时指定常规命令名称和别名,如下所示。 -> -> ```javascript -> { -> "generateOptions": { -> "spec": { -> "service": false, -> "s": false -> } -> }, -> ... -> } -> ``` +:::warning 警告 +当将 `spec` 指定为对象时,生成原理图的键目前不支持自动别名处理。这意味着如果将键指定为例如 `service: false` 并尝试通过别名 `s` 生成服务,规范文件仍会被生成。为确保正常原理图名称和别名都能按预期工作,请同时指定常规命令名称和别名,如下所示。 +::: + + +```javascript +{ + "generateOptions": { + "spec": { + "service": false, + "s": false + } + }, + ... +} ``` #### 项目专属生成选项 @@ -287,7 +280,9 @@ Nest 将组织和构建标准项目及 monorepo 结构项目所需的元数据 } ``` -> warning **注意** 生成选项的优先级顺序如下:命令行界面(CLI)指定的选项优先于项目专属选项,项目专属选项会覆盖全局选项。 +:::warning 注意 +生成选项的优先级顺序如下:命令行界面(CLI)指定的选项优先于项目专属选项,项目专属选项会覆盖全局选项。 +::: #### 指定编译器 @@ -340,7 +335,11 @@ TypeScript 编译会自动将编译器输出(`.js` 和 `.d.ts` 文件)分发 ] ``` -> **警告** 在顶层 `compilerOptions` 属性中设置 `watchAssets` 将覆盖 `assets` 属性内的所有 `watchAssets` 配置 +:::warning 警告 +在顶层 `compilerOptions` 属性中设置 `watchAssets` 将覆盖 `assets` 属性内的所有 `watchAssets` 配置 +::: + + #### 项目属性 diff --git a/docs/devtools/ci-cd.md b/docs/devtools/ci-cd.md index 4381e34..089c851 100644 --- a/docs/devtools/ci-cd.md +++ b/docs/devtools/ci-cd.md @@ -1,6 +1,8 @@ ### CI/CD 集成 -> info **提示** 本章介绍 Nest Devtools 与 Nest 框架的集成。如需了解 Devtools 应用程序,请访问 [Devtools](https://devtools.nestjs.com) 官网。 +:::info 提示 +本章介绍 Nest Devtools 与 Nest 框架的集成。如需了解 Devtools 应用程序,请访问 [Devtools](https://devtools.nestjs.com) 官网。 +::: CI/CD 集成功能适用于**[企业版](/settings)**计划的用户。 @@ -174,7 +176,11 @@ const publishOptions = { }; ``` -> **提示** 理想情况下,`DEVTOOLS_API_KEY` 环境变量应从机密信息中获取。 +:::info 提示 +理想情况下,`DEVTOOLS_API_KEY` 环境变量应从机密信息中获取。 +::: + + 该工作流将在每个针对 `master` 分支的拉取请求时运行,或者当有代码直接提交到 `master` 分支时触发。您可以根据项目需求自由调整此配置。关键在于我们需要为 `GraphPublisher` 类提供必要的环境变量(以便运行)。 diff --git a/docs/devtools/overview.md b/docs/devtools/overview.md index ea8a646..bc93ea9 100644 --- a/docs/devtools/overview.md +++ b/docs/devtools/overview.md @@ -1,6 +1,8 @@ ### 概述 -> info **提示** 本章介绍 Nest Devtools 与 Nest 框架的集成。如需了解 Devtools 应用程序,请访问 [Devtools](https://devtools.nestjs.com) 官网。 +:::info 提示 +本章介绍 Nest Devtools 与 Nest 框架的集成。如需了解 Devtools 应用程序,请访问 [Devtools](https://devtools.nestjs.com) 官网。 +::: 要开始调试本地应用程序,请打开 `main.ts` 文件,并确保在应用程序选项对象中将 `snapshot` 属性设置为 `true`,如下所示: @@ -21,7 +23,9 @@ async function bootstrap() { $ npm i @nestjs/devtools-integration ``` -> warning **注意** 如果您的应用中使用了 `@nestjs/graphql` 包,请确保安装最新版本(`npm i @nestjs/graphql@11`)。 +:::warning 注意 +如果您的应用中使用了 `@nestjs/graphql` 包,请确保安装最新版本(`npm i @nestjs/graphql@11`)。 +::: 有了这个依赖项后,让我们打开 `app.module.ts` 文件并导入刚刚安装的 `DevtoolsModule`: @@ -38,13 +42,17 @@ $ npm i @nestjs/devtools-integration export class AppModule {} ``` -> warning **注意** 此处检查 `NODE_ENV` 环境变量的原因是——切勿在生产环境使用此模块! +:::warning 注意 + 此处检查 `NODE_ENV` 环境变量的原因是——切勿在生产环境使用此模块! +::: 当 `DevtoolsModule` 导入完成且应用启动运行后(`npm run start:dev`),您应当能够访问 [Devtools](https://devtools.nestjs.com) 网址并查看自省生成的图谱。 ![](/assets/devtools/modules-graph.png) -> info **提示** 如上方截图所示,每个模块都连接到 `InternalCoreModule`。`InternalCoreModule` 是一个始终被导入根模块的全局模块。由于它被注册为全局节点,Nest 会自动在所有模块与 `InternalCoreModule` 节点之间创建连接边。现在,若要从图中隐藏全局模块,可以使用侧边栏中的" **隐藏全局模块** "复选框。 +:::info 提示 +如上方截图所示,每个模块都连接到 `InternalCoreModule`。`InternalCoreModule` 是一个始终被导入根模块的全局模块。由于它被注册为全局节点,Nest 会自动在所有模块与 `InternalCoreModule` 节点之间创建连接边。现在,若要从图中隐藏全局模块,可以使用侧边栏中的" **隐藏全局模块** "复选框。 +::: 由此可见,`DevtoolsModule` 会让你的应用暴露一个额外的 HTTP 服务器(运行在 8000 端口),Devtools 应用将通过该端口来内省你的应用程序。 @@ -54,11 +62,15 @@ export class AppModule {} 要聚焦特定节点,点击矩形框后图形界面会弹出包含 **"聚焦"** 按钮的窗口。您也可以使用侧边栏的搜索栏来定位特定节点。 -> info **提示** 如果点击**检查**按钮,应用程序将带您进入 `/debug` 页面并自动选中该特定节点。 +:::info 提示 +如果点击**检查**按钮,应用程序将带您进入 `/debug` 页面并自动选中该特定节点。 +::: ![](/assets/devtools/node-popup.png) -> info **提示** 要将图表导出为图片,请点击图表右上角的**导出为 PNG** 按钮。 +:::info 提示 +要将图表导出为图片,请点击图表右上角的**导出为 PNG** 按钮。 +::: 使用位于侧边栏(左侧)的表单控件,您可以控制边的接近度,例如可视化特定的应用程序子树: @@ -70,7 +82,11 @@ export class AppModule {} #### 排查"无法解析依赖项"错误 -> **注意** 此功能支持 `@nestjs/core` 版本 ≥`v9.3.10`。 +:::info 注意 +此功能支持 `@nestjs/core` 版本 ≥`v9.3.10`。 +::: + + 您可能遇到的最常见错误消息是关于 Nest 无法解析提供者依赖项的问题。使用 Nest Devtools,您可以轻松识别问题并学习如何解决它。 @@ -114,7 +130,9 @@ const app = await NestFactory.create(AppModule, { ![](/assets/devtools/routes.png) -> info **提示** 此页面不仅显示 HTTP 路由,还包括所有其他类型的入口点(例如 WebSockets、gRPC、GraphQL 解析器等)。 +:::info 提示 +此页面不仅显示 HTTP 路由,还包括所有其他类型的入口点(例如 WebSockets、gRPC、GraphQL 解析器等)。 +::: 入口点按其宿主控制器分组显示。您也可以使用搜索栏查找特定入口点。 @@ -134,7 +152,11 @@ const app = await NestFactory.create(AppModule, { ![](/assets/devtools/sandbox-table.png) -> **提示** :要美观地显示对象数组,可使用 `console.table()`(或直接使用 `table()`)函数。 +:::info 提示 +要美观地显示对象数组,可使用 `console.table()`(或直接使用 `table()`)函数。 +::: + + 您可以通过这个视频查看**交互式演练场(Interactive Playground)** 功能的实际应用: @@ -152,7 +174,9 @@ const app = await NestFactory.create(AppModule, { ![](/assets/devtools/audit.png) -> info **提示** 上面的截图并未显示所有可用的审计规则。 +:::info 提示 +上面的截图并未显示所有可用的审计规则。 +::: 当您需要识别应用程序中的潜在问题时,本页面非常有用。 @@ -165,7 +189,9 @@ await app.listen(process.env.PORT ?? 3000); // OR await app.init() fs.writeFileSync('./graph.json', app.get(SerializedGraph).toString()); ``` -> info **提示**`SerializedGraph` 是从 `@nestjs/core` 包中导出的。 +:::info 提示 +`SerializedGraph` 是从 `@nestjs/core` 包中导出的。 +::: 然后你可以拖放/上传这个文件: diff --git a/docs/faq/errors.md b/docs/faq/errors.md index 662b66f..5dff19a 100644 --- a/docs/faq/errors.md +++ b/docs/faq/errors.md @@ -4,7 +4,9 @@ #### "无法解析依赖项"错误 -> info **提示** 查看 [NestJS Devtools](./devtools/overview#调查无法解析依赖项错误) 可以帮助您轻松解决"无法解析依赖项"错误。 +:::info 提示 +查看 [NestJS Devtools](./devtools/overview#调查无法解析依赖项错误) 可以帮助您轻松解决"无法解析依赖项"错误。 +::: 最常见的错误消息是关于 Nest 无法解析提供者的依赖项。错误消息通常如下所示: diff --git a/docs/faq/global-prefix.md b/docs/faq/global-prefix.md index 55cf00d..d68ef1f 100644 --- a/docs/faq/global-prefix.md +++ b/docs/faq/global-prefix.md @@ -21,4 +21,7 @@ app.setGlobalPrefix('v1', { app.setGlobalPrefix('v1', { exclude: ['cats'] }); ``` -> info **提示** `path` 属性支持使用 [path-to-regexp](https://github.com/pillarjs/path-to-regexp#parameters) 包进行通配参数匹配。注意:这里不接受星号通配符 `*`,而必须使用参数形式(`:param`)或命名通配符(`*splat`)。 +:::info 提示 +`path` 属性支持使用 [path-to-regexp](https://github.com/pillarjs/path-to-regexp#parameters) 包进行通配参数匹配。注意:这里不接受星号通配符 `*`,而必须使用参数形式(`:param`)或命名通配符(`*splat`)。 +::: + diff --git a/docs/faq/http-adapter.md b/docs/faq/http-adapter.md index fc0dbcd..f51110e 100644 --- a/docs/faq/http-adapter.md +++ b/docs/faq/http-adapter.md @@ -23,7 +23,11 @@ export class CatsService { } ``` -> **提示** `HttpAdapterHost` 是从 `@nestjs/core` 包导入的。 +:::info 提示 +`HttpAdapterHost` 是从 `@nestjs/core` 包导入的。 +::: + + `HttpAdapterHost` **并非**真正的 `HttpAdapter`。要获取实际的 `HttpAdapter` 实例,只需访问 `httpAdapter` 属性。 @@ -56,4 +60,4 @@ this.httpAdapterHost.listen$.subscribe(() => if (this.httpAdapterHost.listening) { console.log('HTTP server is listening'); } -``` \ No newline at end of file +``` diff --git a/docs/faq/hybrid-application.md b/docs/faq/hybrid-application.md index 65acfb5..13ae4f5 100644 --- a/docs/faq/hybrid-application.md +++ b/docs/faq/hybrid-application.md @@ -12,7 +12,10 @@ await app.startAllMicroservices(); await app.listen(3001); ``` -> info **注意** `app.listen(port)` 方法会在指定地址启动 HTTP 服务器。如果您的应用不处理 HTTP 请求,则应改用 `app.init()` 方法。 +:::info 注意 +`app.listen(port)` 方法会在指定地址启动 HTTP 服务器。如果您的应用不处理 HTTP 请求,则应改用 `app.init()` 方法。 +::: + 要连接多个微服务实例,需为每个微服务调用 `connectMicroservice()` 方法: @@ -52,7 +55,9 @@ getTCPDate(@Payload() data: number[]) { } ``` -> info **提示**`@Payload()`、`@Ctx()`、`Transport` 和 `NatsContext` 都是从 `@nestjs/microservices` 导入的。 +:::info 提示 +`@Payload()`、`@Ctx()`、`Transport` 和 `NatsContext` 都是从 `@nestjs/microservices` 导入的。 +::: #### 共享配置 diff --git a/docs/faq/keep-alive-connections.md b/docs/faq/keep-alive-connections.md index 891a220..8bbe5b7 100644 --- a/docs/faq/keep-alive-connections.md +++ b/docs/faq/keep-alive-connections.md @@ -4,7 +4,11 @@ 若您希望应用无需等待请求结束即可退出,可在创建 NestJS 应用时启用 `forceCloseConnections` 配置项。 -> **注意** :多数用户无需启用此选项。但当出现「应用未按预期退出」的情况时(通常发生在启用 `app.enableShutdownHooks()` 后,尤其是在开发环境中使用 `--watch` 参数运行 NestJS 应用时),则可能需要启用该选项。 +:::info 注意 +多数用户无需启用此选项。但当出现「应用未按预期退出」的情况时(通常发生在启用 `app.enableShutdownHooks()` 后,尤其是在开发环境中使用 `--watch` 参数运行 NestJS 应用时),则可能需要启用该选项。 +::: + + #### 用法 @@ -22,4 +26,4 @@ async function bootstrap() { } bootstrap(); -``` \ No newline at end of file +``` diff --git a/docs/faq/multiple-servers.md b/docs/faq/multiple-servers.md index 7e7505a..c35c554 100644 --- a/docs/faq/multiple-servers.md +++ b/docs/faq/multiple-servers.md @@ -74,6 +74,13 @@ shutdownObserver.addHttpServer(httpServer); shutdownObserver.addHttpServer(httpsServer); ``` -> info **注意** `ExpressAdapter` 是从 `@nestjs/platform-express` 包导入的。`http` 和 `https` 是 Node.js 原生包。 +:::info 注意 +注意 +::: + + +:::warning 警告 +此方案不适用于 [GraphQL 订阅](/graphql/subscriptions) 。 +::: + -> **警告** 此方案不适用于 [GraphQL 订阅](/graphql/subscriptions) 。 diff --git a/docs/faq/raw-body.md b/docs/faq/raw-body.md index 8bc5f92..9b7002f 100644 --- a/docs/faq/raw-body.md +++ b/docs/faq/raw-body.md @@ -2,7 +2,9 @@ 访问原始请求体最常见的用途之一是执行 Webhook 签名验证。通常,为了进行 Webhook 签名验证,需要未序列化的请求体来计算 HMAC 哈希值。 -> warning **注意** 该功能仅在启用了内置全局 body 解析器中间件时可用,即在创建应用时不能传递 `bodyParser: false` 参数。 +:::warning 注意 +该功能仅在启用了内置全局 body 解析器中间件时可用,即在创建应用时不能传递 `bodyParser: false` 参数。 +::: #### 与 Express 配合使用 @@ -45,7 +47,9 @@ class CatsController { app.useBodyParser('text'); ``` -> warning **警告** 请确保向 `NestFactory.create` 调用提供了正确的应用程序类型。对于 Express 应用,正确的类型是 `NestExpressApplication`,否则将找不到 `.useBodyParser` 方法。 +:::warning 警告 + 请确保向 `NestFactory.create` 调用提供了正确的应用程序类型。对于 Express 应用,正确的类型是 `NestExpressApplication`,否则将找不到 `.useBodyParser` 方法。 +::: #### 请求体解析器大小限制 diff --git a/docs/faq/request-lifecycle.md b/docs/faq/request-lifecycle.md index 05980a0..c84572c 100644 --- a/docs/faq/request-lifecycle.md +++ b/docs/faq/request-lifecycle.md @@ -26,7 +26,10 @@ export class CatsController { `Guard1` 会在 `Guard2` 之前执行,而二者都会在 `Guard3` 之前执行。 -> info **注意** 当讨论全局绑定与控制器或本地绑定的区别时,关键在于守卫(或其他组件)绑定的位置。如果使用 `app.useGlobalGuard()` 或通过模块提供该组件,则为全局绑定。否则,装饰器位于控制器类前时绑定到控制器,位于路由声明前时则绑定到路由。 +:::info 注意 +当讨论全局绑定与控制器或本地绑定的区别时,关键在于守卫(或其他组件)绑定的位置。如果使用 `app.useGlobalGuard()` 或通过模块提供该组件,则为全局绑定。否则,装饰器位于控制器类前时绑定到控制器,位于路由声明前时则绑定到路由。 +::: + #### 拦截器 @@ -60,7 +63,9 @@ export class CatsController { 过滤器是唯一不优先解析全局组件的部分。相反,过滤器会从最低层级开始解析,这意味着执行首先从路由绑定的过滤器开始,然后是控制器级别,最后才是全局过滤器。需要注意的是异常无法在过滤器之间传递;如果路由级别的过滤器捕获了异常,控制器或全局级别的过滤器就无法捕获同一个异常。要实现类似效果的唯一方法是使用过滤器之间的继承关系。 -> info **提示** 过滤器仅在请求过程中发生未捕获异常时才会执行。已捕获的异常(例如通过 `try/catch` 捕获的异常)不会触发异常过滤器。一旦遇到未捕获异常,请求将跳过剩余生命周期直接进入过滤器处理阶段。 +:::info 提示 +过滤器仅在请求过程中发生未捕获异常时才会执行。已捕获的异常(例如通过 `try/catch` 捕获的异常)不会触发异常过滤器。一旦遇到未捕获异常,请求将跳过剩余生命周期直接进入过滤器处理阶段。 +::: #### 概述 diff --git a/docs/faq/serverless.md b/docs/faq/serverless.md index f3fc178..9894a94 100644 --- a/docs/faq/serverless.md +++ b/docs/faq/serverless.md @@ -4,7 +4,10 @@ 采用**无服务器架构**时,您只需专注于应用程序代码中的各个函数。诸如 AWS Lambda、Google Cloud Functions 和 Microsoft Azure Functions 等服务会负责所有物理硬件、虚拟机操作系统及 Web 服务器软件的管理。 -> info **注意** 本章节不讨论无服务器函数的优缺点,也不会深入探讨任何云提供商的具体实现细节。 +:::info 注意 +本章节不讨论无服务器函数的优缺点,也不会深入探讨任何云提供商的具体实现细节。 +::: + #### 冷启动 @@ -68,7 +71,10 @@ bootstrap(); | Nest(独立应用) | 0.1117 秒(111.7 毫秒) | | 原始 Node.js 脚本 | 0.0071 秒(7.1 毫秒) | -> info **注意** 设备:MacBook Pro 2014 年中款,2.5 GHz 四核 Intel Core i7 处理器,16 GB 1600 MHz DDR3 内存,固态硬盘。 +:::info 注意 +设备:MacBook Pro 2014 年中款,2.5 GHz 四核 Intel Core i7 处理器,16 GB 1600 MHz DDR3 内存,固态硬盘。 +::: + 现在,让我们重复所有基准测试,但这次使用 `webpack`(如果已安装 [Nest CLI](/cli/overview),可以运行 `nest build --webpack`)将我们的应用程序打包成单个可执行 JavaScript 文件。不过,我们将确保将所有依赖项(`node_modules`)一起打包,而不是使用 Nest CLI 自带的默认 `webpack` 配置,具体如下: @@ -101,7 +107,9 @@ module.exports = (options, webpack) => { }; ``` -> info **提示** 要指示 Nest CLI 使用此配置,请在项目根目录中创建一个新的 `webpack.config.js` 文件。 +:::info 提示 +要指示 Nest CLI 使用此配置,请在项目根目录中创建一个新的 `webpack.config.js` 文件。 +::: 使用此配置后,我们得到了以下结果: @@ -112,9 +120,14 @@ module.exports = (options, webpack) => { | Nest(独立应用) | 0.0319 秒(31.9 毫秒) | | 原始 Node.js 脚本 | 0.0066 秒(6.6 毫秒) | -> info **注意** 机器配置:MacBook Pro 2014 年中款,2.5 GHz 四核 Intel Core i7 处理器,16 GB 1600 MHz DDR3 内存,固态硬盘。 +:::info 注意 +机器配置:MacBook Pro 2014 年中款,2.5 GHz 四核 Intel Core i7 处理器,16 GB 1600 MHz DDR3 内存,固态硬盘。 +::: + -> info **提示** 您可以通过应用额外的代码压缩与优化技术(如使用 `webpack` 插件等)进一步优化。 +:::info 提示 +您可以通过应用额外的代码压缩与优化技术(如使用 `webpack` 插件等)进一步优化。 +::: 如您所见,编译方式(以及是否打包代码)至关重要,对整体启动时间有显著影响。使用 `webpack` 时,独立 Nest 应用(包含一个模块、控制器和服务的初始项目)的平均引导时间可降至约 32 毫秒,基于 Express 的常规 HTTP NestJS 应用则可降至约 81.5 毫秒。 @@ -169,13 +182,15 @@ $ npm i @codegenie/serverless-express aws-lambda $ npm i -D @types/aws-lambda serverless-offline ``` -> info **提示** 为了加快开发周期,我们安装了 `serverless-offline` 插件来模拟 AWS λ 和 API Gateway。 +:::info 提示 +为了加快开发周期,我们安装了 `serverless-offline` 插件来模拟 AWS λ 和 API Gateway。 +::: 安装过程完成后,让我们创建 `serverless.yml` 文件来配置 Serverless 框架: ```yaml service: serverless-example -```typescript + plugins: - serverless-offline @@ -195,7 +210,9 @@ functions: path: '{proxy+}' ``` -> info **提示** 要了解更多关于 Serverless 框架的信息,请访问 [官方文档](https://www.serverless.com/framework/docs/) 。 +:::info 提示 +要了解更多关于 Serverless 框架的信息,请访问 [官方文档](https://www.serverless.com/framework/docs/) 。 +::: 完成这些设置后,我们现在可以转到 `main.ts` 文件,用所需的样板代码更新我们的引导代码: @@ -225,9 +242,13 @@ export const handler: Handler = async ( }; ``` -> info **提示** 如需创建多个无服务器函数并在它们之间共享公共模块,我们推荐使用 [CLI 单仓库模式](/cli/monorepo#monorepo-模式) 。 +:::info 提示 +如需创建多个无服务器函数并在它们之间共享公共模块,我们推荐使用 [CLI 单仓库模式](/cli/monorepo#monorepo-模式) 。 +::: -> warning **警告** 如果使用 `@nestjs/swagger` 包,需要执行几个额外步骤才能使其在无服务器函数环境中正常工作。查看此 [讨论帖](https://github.com/nestjs/swagger/issues/199) 获取更多信息。 +:::warning 警告 +如果使用 `@nestjs/swagger` 包,需要执行几个额外步骤才能使其在无服务器函数环境中正常工作。查看此 [讨论帖](https://github.com/nestjs/swagger/issues/199) 获取更多信息。 +::: 接下来,打开 `tsconfig.json` 文件并确保启用 `esModuleInterop` 选项,以使 `@codegenie/serverless-express` 包能正确加载。 @@ -294,7 +315,7 @@ return { 或者,如果您希望保持函数非常轻量级且不需要任何 HTTP 相关功能(路由、守卫、拦截器、管道等),可以仅使用 `NestFactory.createApplicationContext` (如前所述)而不运行整个 HTTP 服务器(以及底层的 `express`),如下所示: -```typescript title="main" + ```typescript title="main.ts" import { HttpStatus } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { Callback, Context, Handler } from 'aws-lambda'; @@ -316,7 +337,10 @@ export const handler: Handler = async ( }; ``` -> info **注意** 请注意 `NestFactory.createApplicationContext` 不会用增强器(守卫、拦截器等)包装控制器方法。为此,您必须使用 `NestFactory.create` 方法。 +:::info 注意 +请注意 `NestFactory.createApplicationContext` 不会用增强器(守卫、拦截器等)包装控制器方法。为此,您必须使用 `NestFactory.create` 方法。 +::: + 您还可以将 `event` 对象传递给例如 `EventsService` 提供者,该提供者可以处理它并返回相应的值(取决于输入值和业务逻辑)。 diff --git a/docs/fundamentals/async-components.md b/docs/fundamentals/async-components.md index fbf7387..9e8aea6 100644 --- a/docs/fundamentals/async-components.md +++ b/docs/fundamentals/async-components.md @@ -14,7 +14,9 @@ } ``` -> info **提示** 了解更多关于自定义提供者语法的信息,请点击[此处](/fundamentals/custom-providers) 。 +:::info 提示 +了解更多关于自定义提供者语法的信息,请点击[此处](/fundamentals/custom-providers) 。 +::: #### 注入 diff --git a/docs/fundamentals/circular-dependency.md b/docs/fundamentals/circular-dependency.md index 7e3fe0f..9f10142 100644 --- a/docs/fundamentals/circular-dependency.md +++ b/docs/fundamentals/circular-dependency.md @@ -6,13 +6,15 @@ 我们还将介绍如何解决模块间的循环依赖问题。 -> warning **警告** 使用“桶文件”/index.ts 文件对导入进行分组也可能导致循环依赖。在涉及模块/提供者类时,应省略桶文件。例如,在导入与桶文件位于同一目录中的文件时不应使用桶文件,即 `cats/cats.controller` 不应导入 `cats` 来导入 `cats/cats.service` 文件。更多详情请参阅[此 GitHub issue](https://github.com/nestjs/nest/issues/1181#issuecomment-430197191)。 +:::warning 警告 +使用“桶文件”/index.ts 文件对导入进行分组也可能导致循环依赖。在涉及模块/提供者类时,应省略桶文件。例如,在导入与桶文件位于同一目录中的文件时不应使用桶文件,即 `cats/cats.controller` 不应导入 `cats` 来导入 `cats/cats.service` 文件。更多详情请参阅[此 GitHub issue](https://github.com/nestjs/nest/issues/1181#issuecomment-430197191)。 +::: #### 前向引用 **前向引用**允许 Nest 通过 `forwardRef()` 工具函数引用尚未定义的类。例如,如果 `CatsService` 和 `CommonService` 相互依赖,关系的两侧都可以使用 `@Inject()` 和 `forwardRef()` 工具来解决循环依赖。否则,Nest 将不会实例化它们,因为所有必要的元数据都将不可用。示例如下: -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService { constructor( @@ -22,11 +24,13 @@ export class CatsService { } ``` -> info **提示** `forwardRef()` 函数是从 `@nestjs/common` 包中导入的。 +:::info 提示 +`forwardRef()` 函数是从 `@nestjs/common` 包中导入的。 +::: 这涵盖了关系的一侧。现在让我们对 `CommonService` 做同样的事情: -```typescript title="common.service" + ```typescript title="common.service.ts" @Injectable() export class CommonService { constructor( @@ -36,7 +40,9 @@ export class CommonService { } ``` -> warning **警告** 实例化顺序是不确定的。请确保您的代码不依赖于首先调用哪个构造函数。依赖于具有 `Scope.REQUEST` 的提供者的循环依赖可能导致未定义的依赖关系。更多信息请参见[此处](https://github.com/nestjs/nest/issues/5778)。 +:::warning 警告 + 实例化顺序是不确定的。请确保您的代码不依赖于首先调用哪个构造函数。依赖于具有 `Scope.REQUEST` 的提供者的循环依赖可能导致未定义的依赖关系。更多信息请参见[此处](https://github.com/nestjs/nest/issues/5778)。 +::: #### ModuleRef 类的替代方案 @@ -46,7 +52,7 @@ export class CommonService { 为了解决模块之间的循环依赖,请在模块关联的两侧使用相同的 `forwardRef()` 工具函数。例如: -```typescript title="common.module" + ```typescript title="common.module.ts" @Module({ imports: [forwardRef(() => CatsModule)], }) @@ -55,7 +61,7 @@ export class CommonModule {} 这涵盖了关系的一侧。现在让我们对 `CatsModule` 做同样的事情: -```typescript title="cats.module" + ```typescript title="cats.module.ts" @Module({ imports: [forwardRef(() => CommonModule)], }) diff --git a/docs/fundamentals/dependency-injection.md b/docs/fundamentals/dependency-injection.md index d52325c..373ee2a 100644 --- a/docs/fundamentals/dependency-injection.md +++ b/docs/fundamentals/dependency-injection.md @@ -8,7 +8,7 @@ 首先我们定义一个提供者。`@Injectable()` 装饰器将 `CatsService` 类标记为一个提供者。 -```typescript title="cats.service" + ```typescript title="cats.service.ts" import { Injectable } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; @@ -24,7 +24,7 @@ export class CatsService { 然后我们请求 Nest 将这个提供者注入到我们的控制器类中: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" import { Controller, Get } from '@nestjs/common'; import { CatsService } from './cats.service'; import { Cat } from './interfaces/cat.interface'; @@ -42,7 +42,7 @@ export class CatsController { 最后,我们将提供者注册到 Nest 的控制反转(IoC)容器中: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { CatsController } from './cats/cats.controller'; import { CatsService } from './cats/cats.service'; @@ -103,7 +103,9 @@ providers: [ Nest 允许你定义自定义提供者来处理这些情况。它提供了多种定义自定义提供者的方式,下面我们来逐一了解。 -> info **提示** 如果遇到依赖解析问题,可以设置 `NEST_DEBUG` 环境变量,这样在启动时就能获取额外的依赖解析日志。 +:::info 提示 +如果遇到依赖解析问题,可以设置 `NEST_DEBUG` 环境变量,这样在启动时就能获取额外的依赖解析日志。 +::: #### 值提供者:`useValue` @@ -152,7 +154,9 @@ export class AppModule {} 在这个例子中,我们将一个字符串值令牌(`'CONNECTION'`)与从外部文件导入的现有 `connection` 对象关联起来。 -> warning **注意** 除了使用字符串作为令牌值外,还可以使用 JavaScript 的 [symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) 或 TypeScript 的 [enums](https://www.typescriptlang.org/docs/handbook/enums.html)。 +:::warning 注意 + 除了使用字符串作为令牌值外,还可以使用 JavaScript 的 [symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) 或 TypeScript 的 [enums](https://www.typescriptlang.org/docs/handbook/enums.html)。 +::: 我们之前已经了解了如何使用标准的[基于构造函数的注入](../overview/providers#依赖注入)模式来注入提供者。这种模式**要求**依赖项必须使用类名声明。而 `'CONNECTION'` 自定义提供者使用的是字符串令牌。让我们看看如何注入这样的提供者。为此,我们使用 `@Inject()` 装饰器。这个装饰器接受一个参数——令牌。 @@ -163,7 +167,9 @@ export class CatsRepository { } ``` -> info **提示** `@Inject()` 装饰器是从 `@nestjs/common` 包中导入的。 +:::info 提示 +`@Inject()` 装饰器是从 `@nestjs/common` 包中导入的。 +::: 虽然我们在上面的示例中直接使用字符串 `'CONNECTION'` 是为了说明目的,但为了代码整洁的组织,最佳实践是在单独的文件中定义令牌,例如 `constants.ts`。就像对待符号或枚举一样,在它们自己的文件中定义并在需要时导入。 diff --git a/docs/fundamentals/discovery-service.md b/docs/fundamentals/discovery-service.md index 43bba3e..7af7ebb 100644 --- a/docs/fundamentals/discovery-service.md +++ b/docs/fundamentals/discovery-service.md @@ -20,7 +20,7 @@ export class ExampleModule {} 模块设置完成后,`DiscoveryService` 可以被注入到任何需要动态发现的提供者或服务中。 -```typescript title="example.service" + ```typescript title="example.service.ts" @Injectable() export class ExampleService { constructor(private readonly discoveryService: DiscoveryService) {} @@ -87,4 +87,4 @@ console.log( #### 结论 -`DiscoveryService` 是一个多功能且强大的工具,能够在 NestJS 应用程序中实现运行时内省。通过支持动态发现提供者、控制器和元数据,它在构建可扩展框架、插件和自动化驱动功能方面发挥着关键作用。无论是需要扫描和处理提供者、提取元数据进行高级处理,还是创建模块化和可扩展的架构,`DiscoveryService` 都为实现这些目标提供了高效且结构化的方法。 \ No newline at end of file +`DiscoveryService` 是一个多功能且强大的工具,能够在 NestJS 应用程序中实现运行时内省。通过支持动态发现提供者、控制器和元数据,它在构建可扩展框架、插件和自动化驱动功能方面发挥着关键作用。无论是需要扫描和处理提供者、提取元数据进行高级处理,还是创建模块化和可扩展的架构,`DiscoveryService` 都为实现这些目标提供了高效且结构化的方法。 diff --git a/docs/fundamentals/dynamic-modules.md b/docs/fundamentals/dynamic-modules.md index 456e494..c2d4b3c 100644 --- a/docs/fundamentals/dynamic-modules.md +++ b/docs/fundamentals/dynamic-modules.md @@ -126,7 +126,10 @@ export class AppModule {} 动态模块必须返回一个具有完全相同接口的对象,外加一个名为 `module` 的附加属性。`module` 属性用作模块名称,应与模块的类名相同,如下例所示。 -> info **注意** 对于动态模块,模块选项对象的所有属性都是可选的**除了** `module`。 +:::info 注意 +对于动态模块,模块选项对象的所有属性都是可选的**除了** `module`。 +::: + 那么静态的 `register()` 方法呢?我们现在可以明白,它的作用是返回一个具有 `DynamicModule` 接口的对象。当我们调用它时,实际上是在向 `imports` 列表提供一个模块,这与我们在静态情况下通过列出模块类名(如 `imports: [UsersModule]`)的做法类似。换句话说,动态模块 API 只是返回一个模块,但我们不是通过 `@Module` 装饰器固定属性,而是以编程方式指定它们。 @@ -155,7 +158,9 @@ export class ConfigModule { 现在应该很清楚这些部分是如何结合在一起的。调用 `ConfigModule.register(...)` 会返回一个 `DynamicModule` 对象,其属性本质上与我们之前通过 `@Module()` 装饰器提供的元数据相同。 -> info **提示** 从 `@nestjs/common` 导入 `DynamicModule`。 +:::info 提示 +从 `@nestjs/common` 导入 `DynamicModule`。 +::: 我们的动态模块目前还不太有趣,因为我们尚未实现之前提到的**配置**功能。接下来我们就来解决这个问题。 @@ -293,7 +298,7 @@ export interface ConfigModuleOptions { 在此基础上,新建一个专用文件(与现有的 `config.module.ts` 文件放在一起)并命名为 `config.module-definition.ts`。在此文件中,我们将使用 `ConfigurableModuleBuilder` 来构建 `ConfigModule` 的定义。 -```typescript title="config.module-definition" + ```typescript title="config.module-definition.ts" import { ConfigurableModuleBuilder } from '@nestjs/common'; import { ConfigModuleOptions } from './interfaces/config-module-options.interface'; @@ -387,7 +392,7 @@ export class ConfigService { 默认情况下,`ConfigurableModuleClass` 提供 `register` 及其对应方法 `registerAsync`。如需使用不同方法名,可采用 `ConfigurableModuleBuilder#setClassMethodName` 方法,如下所示: -```typescript title="config.module-definition" + ```typescript title="config.module-definition.ts" export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder().setClassMethodName('forRoot').build(); ``` @@ -429,7 +434,7 @@ export class AppModule {} 默认情况下,该类必须提供 `create()` 方法以返回模块配置对象。但如果您的库遵循不同的命名约定,可以通过 `ConfigurableModuleBuilder#setFactoryMethodName` 方法调整此行为,指示 `ConfigurableModuleBuilder` 改用其他方法(例如 `createConfigOptions`): -```typescript title="config.module-definition" + ```typescript title="config.module-definition.ts" export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder().setFactoryMethodName('createConfigOptions').build(); ``` diff --git a/docs/fundamentals/execution-context.md b/docs/fundamentals/execution-context.md index 8474949..e7f8160 100644 --- a/docs/fundamentals/execution-context.md +++ b/docs/fundamentals/execution-context.md @@ -24,7 +24,11 @@ if (host.getType() === 'http') { } ``` -> **提示** `GqlContextType` 需从 `@nestjs/graphql` 包导入。 +:::info 提示 +`GqlContextType` 需从 `@nestjs/graphql` 包导入。 +::: + + 有了应用类型后,我们可以编写更通用的组件,如下所示。 @@ -144,7 +148,7 @@ export const Roles = Reflector.createDecorator(); 现在要使用这个装饰器,我们只需用它来注解处理器: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() @Roles(['admin']) async create(@Body() createCatDto: CreateCatDto) { @@ -156,14 +160,16 @@ async create(@Body() createCatDto: CreateCatDto) { 为了访问路由的角色(自定义元数据),我们将再次使用 `Reflector` 辅助类。`Reflector` 可以通过常规方式注入到类中: -```typescript title="roles.guard" + ```typescript title="roles.guard.ts" @Injectable() export class RolesGuard { constructor(private reflector: Reflector) {} } ``` -> info **提示** `Reflector` 类是从 `@nestjs/core` 包导入的。 +:::info 提示 +`Reflector` 类是从 `@nestjs/core` 包导入的。 +::: 现在,要读取处理程序的元数据,请使用 `get()` 方法: @@ -175,7 +181,7 @@ const roles = this.reflector.get(Roles, context.getHandler()); 或者,我们也可以通过将元数据应用到控制器级别来组织控制器,这将应用于控制器类中的所有路由。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Roles(['admin']) @Controller('cats') export class CatsController {} @@ -183,7 +189,7 @@ export class CatsController {} 在这种情况下,为了提取控制器元数据,我们传递 `context.getClass()` 作为第二个参数(以提供控制器类作为元数据提取的上下文),而不是 `context.getHandler()`: -```typescript title="roles.guard" + ```typescript title="roles.guard.ts" const roles = this.reflector.get(Roles, context.getClass()); ``` @@ -191,7 +197,7 @@ const roles = this.reflector.get(Roles, context.getClass()); 考虑以下场景,您在这两个级别都提供了 `Roles` 元数据。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Roles(['user']) @Controller('cats') export class CatsController { @@ -231,7 +237,7 @@ const roles = this.reflector.getAllAndMerge(Roles, [ 如前所述,除了使用 `Reflector#createDecorator` 外,您也可以使用内置的 `@SetMetadata()` 装饰器来为处理器附加元数据。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() @SetMetadata('roles', ['admin']) async create(@Body() createCatDto: CreateCatDto) { @@ -239,11 +245,14 @@ async create(@Body() createCatDto: CreateCatDto) { } ``` -> info **注意** `@SetMetadata()` 装饰器是从 `@nestjs/common` 包中导入的。 +:::info 注意 +`@SetMetadata()` 装饰器是从 `@nestjs/common` 包中导入的。 +::: + 通过上述构建,我们将 `roles` 元数据(`roles` 是元数据键,`['admin']` 是关联值)附加到了 `create()` 方法上。虽然这种方式有效,但直接在路由中使用 `@SetMetadata()` 并不是最佳实践。相反,您可以创建自己的装饰器,如下所示: -```typescript title="roles.decorator" + ```typescript title="roles.decorator.ts" import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles); @@ -253,7 +262,7 @@ export const Roles = (...roles: string[]) => SetMetadata('roles', roles); 现在我们有了自定义的 `@Roles()` 装饰器,就可以用它来装饰 `create()` 方法了。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() @Roles('admin') async create(@Body() createCatDto: CreateCatDto) { @@ -263,14 +272,16 @@ async create(@Body() createCatDto: CreateCatDto) { 为了访问路由的角色信息(自定义元数据),我们将再次使用 `Reflector` 辅助类: -```typescript title="roles.guard" + ```typescript title="roles.guard.ts" @Injectable() export class RolesGuard { constructor(private reflector: Reflector) {} } ``` -> info **提示** `Reflector` 类是从 `@nestjs/core` 包中导入的。 +:::info 提示 +`Reflector` 类是从 `@nestjs/core` 包中导入的。 +::: 现在,要读取处理程序的元数据,请使用 `get()` 方法。 diff --git a/docs/fundamentals/lazy-loading-modules.md b/docs/fundamentals/lazy-loading-modules.md index 018d3f9..8868874 100644 --- a/docs/fundamentals/lazy-loading-modules.md +++ b/docs/fundamentals/lazy-loading-modules.md @@ -4,22 +4,28 @@ 懒加载可以通过仅加载特定无服务器函数调用所需的模块来减少引导时间。此外,一旦无服务器函数"预热"后,您还可以异步加载其他模块,从而进一步加快后续调用的引导时间(延迟模块注册)。 -> info **提示** 如果您熟悉 **[Angular](https://angular.dev/)** 框架,可能之前见过" [懒加载模块](https://angular.dev/guide/ngmodules/lazy-loading#lazy-loading-basics) "这个术语。请注意这项技术在 Nest 中**功能上有所不同** ,因此请将其视为共享相似命名规范的完全不同的功能。 +:::info 提示 +如果您熟悉 **[Angular](https://angular.dev/)** 框架,可能之前见过" [懒加载模块](https://angular.dev/guide/ngmodules/lazy-loading#lazy-loading-basics) "这个术语。请注意这项技术在 Nest 中**功能上有所不同** ,因此请将其视为共享相似命名规范的完全不同的功能。 +::: -> warning **警告** 请注意[生命周期钩子方法](../fundamentals/lifecycle-events)在懒加载模块和服务中不会被调用。 +:::warning 警告 +请注意[生命周期钩子方法](../fundamentals/lifecycle-events)在懒加载模块和服务中不会被调用。 +::: #### 入门指南 为了实现按需加载模块,Nest 提供了 `LazyModuleLoader` 类,可以通过常规方式注入到类中: -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService { constructor(private lazyModuleLoader: LazyModuleLoader) {} } ``` -> info **提示** `LazyModuleLoader` 类是从 `@nestjs/core` 包中导入的。 +:::info 提示 +`LazyModuleLoader` 类是从 `@nestjs/core` 包中导入的。 +::: 或者,你也可以从应用程序引导文件(`main.ts`)中获取 `LazyModuleLoader` 提供者的引用,如下所示: @@ -35,7 +41,10 @@ const { LazyModule } = await import('./lazy.module'); const moduleRef = await this.lazyModuleLoader.load(() => LazyModule); ``` -> info **提示** "懒加载"模块会在首次调用 `LazyModuleLoader#load` 方法时被**缓存** 。这意味着后续每次尝试加载 `LazyModule` 都会**非常快速** ,并返回缓存实例,而不会重新加载模块。 +:::info 提示 + "懒加载"模块会在首次调用 `LazyModuleLoader#load` 方法时被**缓存** 。这意味着后续每次尝试加载 `LazyModule` 都会**非常快速** ,并返回缓存实例,而不会重新加载模块。 +::: + > > ```bash > Load "LazyModule" attempt: 1 @@ -62,7 +71,9 @@ const moduleRef = await this.lazyModuleLoader.load(() => LazyModule); export class LazyModule {} ``` -> info **提示** 延迟加载的模块不能注册为**全局模块** ,这毫无意义(因为它们是在所有静态注册模块都已实例化后,按需延迟注册的)。同样,已注册的**全局增强器** (守卫/拦截器等) **也无法**正常工作。 +:::info 提示 +延迟加载的模块不能注册为**全局模块** ,这毫无意义(因为它们是在所有静态注册模块都已实例化后,按需延迟注册的)。同样,已注册的**全局增强器** (守卫/拦截器等) **也无法**正常工作。 +::: 通过这种方式,我们可以获取 `LazyService` 提供者的引用,如下所示: @@ -74,7 +85,10 @@ const { LazyService } = await import('./lazy.service'); const lazyService = moduleRef.get(LazyService); ``` -> warning **警告** 如果使用 **Webpack**,请确保更新您的 `tsconfig.json` 文件 - 将 `compilerOptions.module` 设置为 `"esnext"` 并添加值为 `"node"` 的 `compilerOptions.moduleResolution` 属性: +:::warning 警告 + 如果使用 **Webpack**,请确保更新您的 `tsconfig.json` 文件 - 将 `compilerOptions.module` 设置为 `"esnext"` 并添加值为 `"node"` 的 `compilerOptions.moduleResolution` 属性: +::: + > > ```json > { @@ -92,7 +106,9 @@ const lazyService = moduleRef.get(LazyService); 由于 Nest 中的控制器(或 GraphQL 应用中的解析器)代表路由/路径/主题集(或查询/变更),您**无法通过** `LazyModuleLoader` 类实现懒加载。 - > error **警告** 在懒加载模块中注册的控制器、 [解析器](/graphql/resolvers)和[网关](/websockets/gateways)将无法按预期工作。同样,你也不能按需注册中间件函数(通过实现 `MiddlewareConsumer` 接口)。 + :::warning 警告 + 在懒加载模块中注册的控制器、 [解析器](/graphql/resolvers)和[网关](/websockets/gateways)将无法按预期工作。同样,你也不能按需注册中间件函数(通过实现 `MiddlewareConsumer` 接口)。 +::: 例如,假设你正在构建一个底层使用 Fastify 驱动(通过 `@nestjs/platform-fastify` 包)的 REST API(HTTP 应用)。Fastify 不允许在应用准备就绪/成功监听消息后注册路由。这意味着即使我们分析了模块控制器中注册的路由映射,所有懒加载路由也无法访问,因为在运行时无法注册它们。 diff --git a/docs/fundamentals/lifecycle-events.md b/docs/fundamentals/lifecycle-events.md index fdeccd1..1cc5ede 100644 --- a/docs/fundamentals/lifecycle-events.md +++ b/docs/fundamentals/lifecycle-events.md @@ -26,9 +26,13 @@ Nest 应用程序以及其中的每个组件都拥有由 Nest 管理的生命周 \* 对于这些事件,若未显式调用 `app.close()`,则需手动启用才能使其在系统信号(如 `SIGTERM`)下生效。详见下方[应用关闭](fundamentals/lifecycle-events#应用程序关闭)章节。 -> warning **注意** 上述生命周期钩子不会在**请求作用域**类中触发。请求作用域类与应用程序生命周期无关,其生存周期不可预测。它们专为每个请求创建,并在响应发送后自动进行垃圾回收。 +:::warning 注意 +上述生命周期钩子不会在**请求作用域**类中触发。请求作用域类与应用程序生命周期无关,其生存周期不可预测。它们专为每个请求创建,并在响应发送后自动进行垃圾回收。 +::: -> info **说明** `onModuleInit()` 和 `onApplicationBootstrap()` 的执行顺序直接取决于模块导入顺序,会等待前一个钩子完成。 +:::info 说明 + `onModuleInit()` 和 `onApplicationBootstrap()` 的执行顺序直接取决于模块导入顺序,会等待前一个钩子完成。 +::: #### 使用说明 @@ -76,9 +80,13 @@ async function bootstrap() { bootstrap(); ``` -> warning **警告** 由于平台固有局限性,NestJS 在 Windows 系统上对应用关机钩子的支持有限。您可以预期 `SIGINT` 能正常工作,`SIGBREAK` 以及在一定程度上 `SIGHUP` 也能工作 - [了解更多](https://nodejs.org/api/process.html#process_signal_events) 。但 `SIGTERM` 在 Windows 上永远不会生效,因为通过任务管理器终止进程是无条件的,"即应用程序无法检测或阻止此操作"。以下是 libuv 提供的[相关文档](https://docs.libuv.org/en/v1.x/signal.html) ,可了解更多关于 `SIGINT`、`SIGBREAK` 等信号在 Windows 上的处理方式。另请参阅 Node.js 的[进程信号事件](https://nodejs.org/api/process.html#process_signal_events)文档。 +:::warning 警告 + 由于平台固有局限性,NestJS 在 Windows 系统上对应用关机钩子的支持有限。您可以预期 `SIGINT` 能正常工作,`SIGBREAK` 以及在一定程度上 `SIGHUP` 也能工作 - [了解更多](https://nodejs.org/api/process.html#process_signal_events) 。但 `SIGTERM` 在 Windows 上永远不会生效,因为通过任务管理器终止进程是无条件的,"即应用程序无法检测或阻止此操作"。以下是 libuv 提供的[相关文档](https://docs.libuv.org/en/v1.x/signal.html) ,可了解更多关于 `SIGINT`、`SIGBREAK` 等信号在 Windows 上的处理方式。另请参阅 Node.js 的[进程信号事件](https://nodejs.org/api/process.html#process_signal_events)文档。 +::: -> info **提示** `enableShutdownHooks` 会通过启动监听器消耗内存。当您在单个 Node 进程中运行多个 Nest 应用时(例如使用 Jest 运行并行测试),Node 可能会因过多的监听器进程而报错。因此,`enableShutdownHooks` 默认处于禁用状态。在单个 Node 进程中运行多个实例时请注意这一情况。 +:::info 提示 +`enableShutdownHooks` 会通过启动监听器消耗内存。当您在单个 Node 进程中运行多个 Nest 应用时(例如使用 Jest 运行并行测试),Node 可能会因过多的监听器进程而报错。因此,`enableShutdownHooks` 默认处于禁用状态。在单个 Node 进程中运行多个实例时请注意这一情况。 +::: 当应用接收到终止信号时,它将按照上述顺序调用所有已注册的 `onModuleDestroy()`、`beforeApplicationShutdown()` 以及 `onApplicationShutdown()` 方法,并将相应信号作为第一个参数传入。如果注册的函数需要等待异步调用(返回 promise),Nest 将在此 promise 被解析或拒绝前暂停执行后续序列。 @@ -91,4 +99,7 @@ class UsersService implements OnApplicationShutdown { } ``` -> info **提示** 调用 `app.close()` 不会终止 Node 进程,只会触发 `onModuleDestroy()` 和 `onApplicationShutdown()` 钩子函数,因此如果存在定时器、长时间运行的后台任务等情况,进程不会自动终止。 +:::info 提示 + 调用 `app.close()` 不会终止 Node 进程,只会触发 `onModuleDestroy()` 和 `onApplicationShutdown()` 钩子函数,因此如果存在定时器、长时间运行的后台任务等情况,进程不会自动终止。 +::: + diff --git a/docs/fundamentals/module-reference.md b/docs/fundamentals/module-reference.md index a678c3f..d19810d 100644 --- a/docs/fundamentals/module-reference.md +++ b/docs/fundamentals/module-reference.md @@ -2,20 +2,22 @@ Nest 提供了 `ModuleRef` 类来导航内部提供者列表,并使用其注入令牌作为查找键获取任何提供者的引用。`ModuleRef` 类还提供了一种动态实例化静态和范围提供者的方法。`ModuleRef` 可以以常规方式注入到类中: -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService { constructor(private moduleRef: ModuleRef) {} } ``` -> info **提示** `ModuleRef` 类是从 `@nestjs/core` 包中导入的。 +:::info 提示 +`ModuleRef` 类是从 `@nestjs/core` 包中导入的。 +::: #### 获取实例 `ModuleRef` 实例(以下简称**模块引用** )具有一个 `get()` 方法。默认情况下,该方法会返回一个已注册并在*当前模块*中使用其注入令牌/类名实例化的提供者、控制器或可注入对象(如守卫、拦截器等)。如果找不到实例,则会抛出异常。 -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService implements OnModuleInit { private service: Service; @@ -27,7 +29,9 @@ export class CatsService implements OnModuleInit { } ``` -> warning **警告** 无法通过 `get()` 方法检索作用域提供者(瞬时或请求作用域)。请改用下文[所述技术](../fundamentals/module-reference#解析作用域提供者) 。了解如何控制作用域请参阅[此处](/fundamentals/injection-scopes) 。 +:::warning 警告 +无法通过 `get()` 方法检索作用域提供者(瞬时或请求作用域)。请改用下文[所述技术](../fundamentals/module-reference#解析作用域提供者) 。了解如何控制作用域请参阅[此处](/fundamentals/provider-scopes) 。 +::: 要从全局上下文中检索提供者(例如,如果该提供者已注入到其他模块中),请将 `{ strict: false }` 选项作为第二个参数传递给 `get()`。 @@ -39,7 +43,7 @@ this.moduleRef.get(Service, { strict: false }); 要动态解析一个作用域提供者(瞬态或请求作用域),请使用 `resolve()` 方法,并将提供者的注入令牌作为参数传入。 -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService implements OnModuleInit { private transientService: TransientService; @@ -53,7 +57,7 @@ export class CatsService implements OnModuleInit { `resolve()` 方法会从它自己的**依赖注入容器子树**中返回该提供者的唯一实例。每个子树都有一个唯一的**上下文标识符** 。因此,如果多次调用此方法并比较实例引用,你会发现它们并不相同。 -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService implements OnModuleInit { constructor(private moduleRef: ModuleRef) {} @@ -70,7 +74,7 @@ export class CatsService implements OnModuleInit { 要在多个 `resolve()` 调用间生成单一实例,并确保它们共享相同的依赖注入容器子树,你可以向 `resolve()` 方法传入一个上下文标识符。使用 `ContextIdFactory` 类来生成上下文标识符,该类提供了 `create()` 方法,可返回一个合适的唯一标识符。 -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService implements OnModuleInit { constructor(private moduleRef: ModuleRef) {} @@ -86,7 +90,10 @@ export class CatsService implements OnModuleInit { } ``` -> info **注意** `ContextIdFactory` 类是从 `@nestjs/core` 包导入的。 +:::info 注意 +`ContextIdFactory` 类是从 `@nestjs/core` 包导入的。 +::: + #### 注册 `REQUEST` 提供者 @@ -103,7 +110,7 @@ this.moduleRef.registerRequestByContextId(/* YOUR_REQUEST_OBJECT */, contextId); 有时,你可能需要在**请求上下文**中解析一个请求作用域提供者的实例。假设 `CatsService` 是请求作用域的,而你想解析同样标记为请求作用域提供者的 `CatsRepository` 实例。为了共享同一个 DI 容器子树,你必须获取当前上下文标识符,而不是生成新的标识符(例如使用上文所示的 `ContextIdFactory.create()` 函数)。要获取当前上下文标识符,首先使用 `@Inject()` 装饰器注入请求对象。 -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService { constructor( @@ -112,7 +119,9 @@ export class CatsService { } ``` -> info **了解**请求提供者的更多信息,请点击[此处](../fundamentals/injection-scopes#请求提供者) 。 +:::info 了解 + 请求提供者的更多信息,请点击[此处](../fundamentals/provider-scopes#请求提供者) 。 +::: 现在,使用 `ContextIdFactory` 类的 `getByRequest()` 方法基于请求对象创建上下文 ID,并将其传递给 `resolve()` 调用: @@ -125,7 +134,7 @@ const catsRepository = await this.moduleRef.resolve(CatsRepository, contextId); 要动态实例化一个**先前未注册**为**提供者**的类,可使用模块引用的 `create()` 方法。 -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService implements OnModuleInit { private catsFactory: CatsFactory; diff --git a/docs/fundamentals/provider-scopes.md b/docs/fundamentals/provider-scopes.md index efc2b60..454bd95 100644 --- a/docs/fundamentals/provider-scopes.md +++ b/docs/fundamentals/provider-scopes.md @@ -14,7 +14,9 @@ | `REQUEST` | 会为每个传入的**请求**专属创建新的提供者实例。该实例在请求处理完成后会被垃圾回收。 | | `TRANSIENT` | 瞬时提供者不会在多个消费者之间共享。每个注入瞬时提供者的消费者都会获得一个全新的专属实例。 | -> info **提示** 对于大多数使用场景, **推荐**使用单例作用域。在多个消费者和请求之间共享提供者意味着实例可以被缓存,且其初始化仅在应用启动时发生一次。 +:::info 提示 +对于大多数使用场景, **推荐**使用单例作用域。在多个消费者和请求之间共享提供者意味着实例可以被缓存,且其初始化仅在应用启动时发生一次。 +::: #### 用法 @@ -37,11 +39,15 @@ export class CatsService {} } ``` -> info **提示** 从 `@nestjs/common` 导入 `Scope` 枚举 +:::info 提示 +从 `@nestjs/common` 导入 `Scope` 枚举 +::: 单例作用域是默认使用的,无需显式声明。如需明确声明提供者为单例作用域,请将 `scope` 属性设为 `Scope.DEFAULT` 值。 -> warning **注意** WebSocket 网关不应使用请求作用域的提供者,因为它们必须作为单例运行。每个网关都封装了一个真实的 socket 连接且不能被多次实例化。此限制同样适用于其他一些提供者,如 [_Passport 策略_](../security/authentication#请求作用域策略) 或 _Cron 控制器_ 。 +:::warning 注意 +WebSocket 网关不应使用请求作用域的提供者,因为它们必须作为单例运行。每个网关都封装了一个真实的 socket 连接且不能被多次实例化。此限制同样适用于其他一些提供者,如 [_Passport 策略_](../security/authentication#请求作用域策略) 或 _Cron 控制器_ 。 +::: #### 控制器作用域 @@ -138,7 +144,11 @@ export class AppService { 使用请求作用域的提供者会影响应用程序性能。虽然 Nest 会尽可能缓存元数据,但仍需在每个请求中创建类实例。因此这会降低平均响应时间并影响整体基准测试结果。除非必须使用请求作用域,否则强烈建议采用默认的单例作用域。 -> **提示** 尽管听起来有些令人担忧,但合理设计的使用请求作用域提供者的应用程序,其延迟增加通常不会超过约 5%。 +:::info 提示 +尽管听起来有些令人担忧,但合理设计的使用请求作用域提供者的应用程序,其延迟增加通常不会超过约 5%。 +::: + + #### 持久化提供者 @@ -184,9 +194,14 @@ export class AggregateByTenantContextIdStrategy implements ContextIdStrategy { } ``` -> info **注意** 与请求作用域类似,持久化特性会沿依赖链向上传递。这意味着如果 A 依赖于被标记为 `durable` 的 B,那么 A 也会隐式成为持久化的(除非 A 提供者被显式设置为 `durable` 为 `false`)。 +:::info 注意 +与请求作用域类似,持久化特性会沿依赖链向上传递。这意味着如果 A 依赖于被标记为 `durable` 的 B,那么 A 也会隐式成为持久化的(除非 A 提供者被显式设置为 `durable` 为 `false`)。 +::: -> warning **警告** 请注意此策略不适用于处理大量租户的应用程序。 + +:::warning 警告 + 请注意此策略不适用于处理大量租户的应用程序。 +::: `attach` 方法返回的值指示 Nest 应为给定宿主使用何种上下文标识符。在本例中,我们指定当宿主组件(例如请求范围的控制器)被标记为持久时,应使用 `tenantSubTreeId` 而非原始自动生成的 `contextId` 对象(您可以在下方了解如何将提供者标记为持久)。此外,在上例中, **不会注册任何有效载荷** (其中有效载荷 = 表示"根"的 `REQUEST`/`CONTEXT` 提供者 - 子树父级)。 @@ -209,7 +224,10 @@ return { ContextIdFactory.apply(new AggregateByTenantContextIdStrategy()); ``` -> info **注意** `ContextIdFactory` 类是从 `@nestjs/core` 包导入的。 +:::info 注意 +`ContextIdFactory` 类是从 `@nestjs/core` 包导入的。 +::: + 只要注册操作发生在任何请求到达你的应用之前,一切都会按预期工作。 diff --git a/docs/fundamentals/unit-testing.md b/docs/fundamentals/unit-testing.md index c73307b..96dd380 100644 --- a/docs/fundamentals/unit-testing.md +++ b/docs/fundamentals/unit-testing.md @@ -23,7 +23,7 @@ $ npm i --save-dev @nestjs/testing 在以下示例中,我们测试两个类:`CatsController` 和 `CatsService`。如前所述,[Jest](https://github.com/facebook/jest) 是默认提供的测试框架,它既是测试运行器,又提供了断言函数和测试替身工具,可用于模拟、监视等操作。在这个基础测试中,我们手动实例化这些类,并确保控制器和服务满足它们的 API 约定。 -```typescript title="cats.controller.spec" + ```typescript title="cats.controller.spec.ts" import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @@ -47,7 +47,9 @@ describe('CatsController', () => { }); ``` -> info **提示** 将测试文件保存在它们所测试的类附近。测试文件应带有 `.spec` 或 `.test` 后缀。 +:::info 提示 +将测试文件保存在它们所测试的类附近。测试文件应带有 `.spec` 或 `.test` 后缀。 +::: 由于上述示例过于简单,我们并未真正测试任何 Nest 特有的功能。实际上,我们甚至没有使用依赖注入(注意我们是直接将 `CatsService` 实例传递给 `catsController`)。这种手动实例化待测类的测试形式通常被称为**隔离测试** ,因为它独立于框架运行。接下来我们将介绍一些更高级的功能,帮助您测试那些更充分利用 Nest 特性的应用程序。 @@ -55,7 +57,7 @@ describe('CatsController', () => { `@nestjs/testing` 包提供了一系列实用工具,能够实现更健壮的测试流程。让我们使用内置的 `Test` 类重写之前的示例: -```typescript title="cats.controller.spec" + ```typescript title="cats.controller.spec.ts" import { Test } from '@nestjs/testing'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @@ -87,7 +89,11 @@ describe('CatsController', () => { `Test` 类为应用提供了执行上下文,它本质上模拟了完整的 Nest 运行时环境,同时提供了便于管理类实例的钩子,包括模拟和重写功能。该类的 `createTestingModule()` 方法接收一个模块元数据对象作为参数(与传入 `@Module()` 装饰器的对象相同),返回一个 `TestingModule` 实例,该实例又提供了若干方法。对于单元测试而言,关键方法是 `compile()`,它会引导模块及其依赖项(类似于传统 `main.ts` 文件中使用 `NestFactory.create()` 引导应用的方式),并返回一个准备就绪的测试模块。 -> **提示** `compile()` 方法是**异步的** ,因此需要使用 await。模块编译完成后,可通过 `get()` 方法获取其声明的任何**静态**实例(控制器和提供者)。 +:::info 提示 +`compile()` 方法是**异步的** ,因此需要使用 await。模块编译完成后,可通过 `get()` 方法获取其声明的任何**静态实例(控制器和提供者)。 +::: + + `TestingModule` 继承自[模块引用](/fundamentals/module-ref)类,因此具备动态解析作用域提供者(瞬时或请求作用域)的能力。可通过 `resolve()` 方法实现(而 `get()` 方法仅能获取静态实例)。 @@ -100,9 +106,13 @@ const moduleRef = await Test.createTestingModule({ catsService = await moduleRef.resolve(CatsService); ``` -> warning **警告** `resolve()` 方法会从自身的 **DI 容器子树**返回提供者的唯一实例。每个子树都有唯一的上下文标识符。因此,若多次调用此方法并比较实例引用,会发现它们并不相同。 +:::warning 警告 +`resolve()` 方法会从自身的 **DI 容器子树**返回提供者的唯一实例。每个子树都有唯一的上下文标识符。因此,若多次调用此方法并比较实例引用,会发现它们并不相同。 +::: -> info **提示** 了解更多模块引用特性请[点击此处](/fundamentals/module-ref) 。 +:::info 提示 +了解更多模块引用特性请[点击此处](/fundamentals/module-ref) 。 +::: 您可以用[自定义提供者](/fundamentals/custom-providers)覆盖任何生产环境的提供者实现来进行测试。例如,可以模拟数据库服务而非连接真实数据库。我们将在下一节讨论覆盖机制,该功能同样适用于单元测试场景。 @@ -145,15 +155,19 @@ describe('CatsController', () => { 您也可以像通常获取自定义提供者那样从测试容器中检索这些模拟对象,例如 `moduleRef.get(CatsService)`。 -> info **提示** 通用模拟工厂(如 [`@golevelup/ts-jest`](https://github.com/golevelup/nestjs/tree/master/packages/testing) 中的 `createMock`)也可以直接传入使用。 +:::info 提示 +通用模拟工厂(如 [`@golevelup/ts-jest`](https://github.com/golevelup/nestjs/tree/master/packages/testing) 中的 `createMock`)也可以直接传入使用。 +::: -> info **提示** `REQUEST` 和 `INQUIRER` 提供者无法被自动模拟,因为它们已在上下文中预定义。但可以通过自定义提供者语法或使用 `.overrideProvider` 方法进行*覆盖* 。 +:::info 提示 +`REQUEST` 和 `INQUIRER` 提供者无法被自动模拟,因为它们已在上下文中预定义。但可以通过自定义提供者语法或使用 `.overrideProvider` 方法进行*覆盖* 。 +::: #### 端到端测试 与专注于单个模块和类的单元测试不同,端到端(e2e)测试涵盖了类和模块在更高聚合层级上的交互——更接近最终用户与生产系统的交互方式。随着应用规模增长,手动测试每个 API 端点的端到端行为变得困难。自动化端到端测试帮助我们确保系统的整体行为正确并满足项目需求。执行 e2e 测试时,我们使用与**单元测试**相似的配置。此外,Nest 可以轻松使用 [Supertest](https://github.com/visionmedia/supertest) 库来模拟 HTTP 请求。 -```typescript title="cats.e2e-spec" + ```typescript title="cats.e2e-spec.ts" import * as request from 'supertest'; import { Test } from '@nestjs/testing'; import { CatsModule } from '../../src/cats/cats.module'; @@ -191,7 +205,10 @@ describe('Cats', () => { }); ``` -> info **提示** 如果您使用 [Fastify](/techniques/performance) 作为 HTTP 适配器,它需要稍有不同的配置,并具有内置的测试能力: +:::info 提示 + 如果您使用 [Fastify](/techniques/performance) 作为 HTTP 适配器,它需要稍有不同的配置,并具有内置的测试能力: +::: + > > ```ts > let app: NestFastifyApplication; @@ -266,7 +283,9 @@ const moduleRef = await Test.createTestingModule({ | `resolve()` | 获取应用程序上下文中可用的控制器或提供者(包括守卫、过滤器等)的动态创建作用域实例(请求或瞬态)。继承自模块引用类。 | | `select()` | 遍历模块的依赖关系图;可用于从选定模块中检索特定实例(与 `get()` 方法中的严格模式 `strict: true` 一起使用)。 | -> info **提示** 将端到端测试文件保存在 `test` 目录中。测试文件应使用 `.e2e-spec` 后缀。 +:::info 提示 +将端到端测试文件保存在 `test` 目录中。测试文件应使用 `.e2e-spec` 后缀。 +::: #### 覆盖全局注册的增强器 @@ -294,7 +313,10 @@ providers: [ ], ``` -> info **注意** 将 `useClass` 改为 `useExisting` 以引用已注册的提供者,而不是让 Nest 在令牌背后实例化它。 +:::info 注意 +将 `useClass` 改为 `useExisting` 以引用已注册的提供者,而不是让 Nest 在令牌背后实例化它。 +::: + 现在 `JwtAuthGuard` 对 Nest 而言是一个常规提供者,在创建 `TestingModule` 时可被覆盖: @@ -311,7 +333,7 @@ const moduleRef = await Test.createTestingModule({ #### 测试请求作用域实例 -[请求作用域](/fundamentals/injection-scopes)的提供者会为每个传入的**请求**单独创建。实例会在请求处理完成后被垃圾回收。这带来了一个问题,因为我们无法访问专门为测试请求生成的依赖注入子树。 +[请求作用域](/fundamentals/provider-scopes)的提供者会为每个传入的**请求**单独创建。实例会在请求处理完成后被垃圾回收。这带来了一个问题,因为我们无法访问专门为测试请求生成的依赖注入子树。 根据前文所述,我们知道可以使用 `resolve()` 方法来获取动态实例化的类。同时,如[此处](../fundamentals/module-reference#解析作用域提供者)所描述的,我们知道可以传递唯一的上下文标识符来控制 DI 容器子树的生命周期。那么如何在测试环境中利用这一点呢? diff --git a/docs/fundamentals/versioning.md b/docs/fundamentals/versioning.md deleted file mode 100644 index fc6ade6..0000000 --- a/docs/fundamentals/versioning.md +++ /dev/null @@ -1,256 +0,0 @@ -# 版本控制 - -> info **提示** 本章节仅适用于基于 HTTP 的应用程序。 - -版本控制允许你在同一应用程序中运行**不同版本**的控制器或独立路由。应用程序经常发生变化,在需要支持旧版本的同时进行破坏性变更的情况并不罕见。 - -支持 4 种类型的版本控制: - - - - - - - - - - - - - - - - - - -
URI Versioning版本将通过请求的 URI 传递(默认)
Header Versioning自定义请求头将指定版本
Media Type Versioning请求的 Accept 头将指定版本
Custom Versioning请求的任何部分都可用于指定版本,并提供了自定义函数来提取所述版本。
- -#### URI 版本控制类型 - -URI 版本控制使用请求 URI 中传递的版本号,例如 `https://example.com/v1/route` 和 `https://example.com/v2/route`。 - -> warning **注意** 使用 URI 版本控制时,版本号会自动添加到 [全局路径前缀](faq/global-prefix) (如果存在)之后的 URI 中,且位于任何控制器或路由路径之前。 - -要为您的应用程序启用 URI 版本控制,请执行以下操作: - -```typescript -const app = await NestFactory.create(AppModule); -// or "app.enableVersioning()" -app.enableVersioning({ - type: VersioningType.URI, -}); -await app.listen(process.env.PORT ?? 3000); -``` - -> warning **注意** URI 中的版本号默认会自动添加 `v` 前缀,但您可以通过设置 `prefix` 键值来自定义前缀,或设为 `false` 来禁用该功能。 - -> info **说明** 您可以使用从 `@nestjs/common` 包导入的 `VersioningType` 枚举来设置 `type` 属性。 - -#### 头部版本控制类型 - -头部版本控制通过自定义的用户指定请求头来指定版本,该请求头的值将作为请求使用的版本号。 - -HTTP 头部版本控制的示例请求: - -要为您的应用程序启用**头部版本控制** ,请执行以下操作: - -```typescript -const app = await NestFactory.create(AppModule); -app.enableVersioning({ - type: VersioningType.HEADER, - header: 'Custom-Header', -}); -await app.listen(process.env.PORT ?? 3000); -``` - -`header` 属性应为包含请求版本的头部名称。 - -> info **提示** `VersioningType` 枚举可用于 `type` 属性,该枚举从 `@nestjs/common` 包导入。 - -#### 媒体类型版本控制类型 - -媒体类型版本控制使用请求的 `Accept` 头部来指定版本。 - -在 `Accept` 头部中,版本号与媒体类型之间用分号 `;` 分隔。它应包含一个表示请求所用版本的键值对,例如 `Accept: application/json;v=2`。在确定要配置的版本时,该键更多地被视为前缀,配置时将包含键和分隔符。 - -要为应用程序启用**媒体类型版本控制** ,请执行以下操作: - -```typescript -const app = await NestFactory.create(AppModule); -app.enableVersioning({ - type: VersioningType.MEDIA_TYPE, - key: 'v=', -}); -await app.listen(process.env.PORT ?? 3000); -``` - -`key` 属性应作为包含版本信息的键值对的键名和分隔符。例如 `Accept: application/json;v=2` 中,`key` 属性应设置为 `v=`。 - -> **提示** `VersioningType` 枚举可用于 `type` 属性,该枚举从 `@nestjs/common` 包导入。 - -#### 自定义版本控制类型 - -自定义版本控制可使用请求的任何方面来指定版本(或多个版本)。通过 `extractor` 提取函数分析传入请求,该函数返回字符串或字符串数组。 - -如果请求方提供了多个版本,提取器函数可以返回一个字符串数组,按版本从高到低的顺序排序。版本会按照从高到低的顺序依次匹配路由。 - -如果从 `extractor` 返回空字符串或空数组,则不会匹配任何路由并返回 404。 - -例如,如果传入请求指定支持版本 `1`、`2` 和 `3`,则 `extractor` **必须**返回 `[3, 2, 1]`。这确保会优先选择最高可能的路由版本。 - -如果提取的版本是 `[3, 2, 1]`,但仅存在版本 `2` 和 `1` 的路由,则会匹配版本 `2` 的路由 已选中(版本 `3` 会被自动忽略) - -> warning **注意** 由于设计限制,基于 `extractor` 返回的数组选择最高匹配版本 > **在 Express 适配器中无法可靠工作** 。单一版本(字符串或单元素数组)在 Express 中可正常工作。Fastify 则能正确支持最高匹配版本选择和单一版本选择。 - -要为应用启用 **自定义版本控制** ,请创建 `extractor` 函数并按如下方式传入应用: - -```typescript title="main" -// Example extractor that pulls out a list of versions from a custom header and turns it into a sorted array. -// This example uses Fastify, but Express requests can be processed in a similar way. -const extractor = (request: FastifyRequest): string | string[] => - [request.headers['custom-versioning-field'] ?? ''] - .flatMap(v => v.split(',')) - .filter(v => !!v) - .sort() - .reverse() -``` - -const app = await NestFactory.create(AppModule); -app.enableVersioning({ - type: VersioningType.CUSTOM, - extractor, -}); -await app.listen(process.env.PORT ?? 3000); -``` - -#### 使用方法 - -版本控制功能允许您对控制器、单个路由进行版本管理,同时也为某些资源提供了退出版本控制的选项。无论应用使用何种版本控制类型,其使用方式都保持一致。 - -> warning **注意** 如果应用程序启用了版本控制,但控制器或路由未指定版本,对该控制器/路由的任何请求都将返回 `404` 响应状态。同样,如果收到的请求包含没有对应控制器或路由的版本,也将返回 `404` 响应状态。 - -#### 控制器版本 - -可以将版本应用于控制器,为该控制器内的所有路由设置版本。 - -要为控制器添加版本,请执行以下操作: - -```typescript -@Controller({ - version: '1', -}) -export class CatsControllerV1 { - @Get('cats') - findAll(): string { - return 'This action returns all cats for version 1'; - } -} -``` - -#### 路由版本 - -可以为单个路由应用版本。该版本将覆盖所有会影响该路由的其他版本,例如控制器版本。 - -要为单个路由添加版本,请执行以下操作: - -```typescript -import { Controller, Get, Version } from '@nestjs/common'; - -@Controller() -export class CatsController { - @Version('1') - @Get('cats') - findAllV1(): string { - return 'This action returns all cats for version 1'; - } - - @Version('2') - @Get('cats') - findAllV2(): string { - return 'This action returns all cats for version 2'; - } -} -``` - -#### 多版本 - -可以对控制器或路由应用多个版本。要使用多个版本,您需要将版本设置为数组。 - -添加多个版本的操作如下: - -```typescript -@Controller({ - version: ['1', '2'], -}) -export class CatsController { - @Get('cats') - findAll(): string { - return 'This action returns all cats for version 1 or 2'; - } -} -``` - -#### 版本"中性" - -某些控制器或路由可能不关心版本,无论版本如何都具有相同的功能。为了适应这种情况,可以将版本设置为 `VERSION_NEUTRAL` 符号。 - -无论请求中是否包含版本号,传入的请求都将被映射到 `VERSION_NEUTRAL` 控制器或路由。 - -> **注意** 对于 URI 版本控制,`VERSION_NEUTRAL` 资源不会在 URI 中包含版本号。 - -要添加版本中立的控制器或路由,请执行以下操作: - -```typescript title="cats.controller" -import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common'; - -@Controller({ - version: VERSION_NEUTRAL, -}) -export class CatsController { - @Get('cats') - findAll(): string { - return 'This action returns all cats regardless of version'; - } -} -``` - -#### 全局默认版本 - -如果您不想为每个控制器/单独路由提供版本,或者希望为所有未指定版本的控制器/路由设置默认版本,可以按如下方式配置 `defaultVersion`: - -```typescript title="main" -app.enableVersioning({ - // ... - defaultVersion: '1' - // or - defaultVersion: ['1', '2'] - // or - defaultVersion: VERSION_NEUTRAL -}); -``` - -#### 中间件版本控制 - -[中间件](../overview/middlewares)同样可以利用版本元数据来为特定路由版本配置中间件。为此,需将版本号作为 `MiddlewareConsumer.forRoutes()` 方法的参数之一: - -```typescript title="app.module" -import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; -import { LoggerMiddleware } from './common/middleware/logger.middleware'; -import { CatsModule } from './cats/cats.module'; -import { CatsController } from './cats/cats.controller'; - -@Module({ - imports: [CatsModule], -}) -export class AppModule implements NestModule { - configure(consumer: MiddlewareConsumer) { - consumer - .apply(LoggerMiddleware) - .forRoutes({ path: 'cats', method: RequestMethod.GET, version: '2' }); - } -} -``` - -通过上述代码,`LoggerMiddleware` 将仅应用于'2'版本的 `/cats` 端点。 - -> info **注意** 中间件适用于本节描述的任何版本控制类型:`URI`、`Header`、`Media Type` 或 `Custom`。 diff --git a/docs/graphql/cli-plugin.md b/docs/graphql/cli-plugin.md index ec94866..d378a01 100644 --- a/docs/graphql/cli-plugin.md +++ b/docs/graphql/cli-plugin.md @@ -1,10 +1,18 @@ ## CLI 插件 -> **警告** 本章仅适用于代码优先(code first)方法。 +:::warning 警告 +本章仅适用于代码优先(code first)方法。 +::: + + TypeScript 的元数据反射系统存在若干限制,例如无法确定类包含哪些属性,或者识别某个属性是可选的还是必需的。不过,其中部分限制可以在编译时得到解决。Nest 提供了一个插件来增强 TypeScript 编译过程,从而减少所需的样板代码量。 -> **提示** 此插件为**可选项**。如果你愿意,可以手动声明所有装饰器,或者仅在需要的地方声明特定装饰器。 +:::info 提示 +此插件为**可选项**。如果你愿意,可以手动声明所有装饰器,或者仅在需要的地方声明特定装饰器。 +::: + + #### 概述 @@ -19,7 +27,7 @@ GraphQL 插件将自动: 根据目前所学,您需要重复大量代码来让包知道您的类型应如何在 GraphQL 中声明。例如,您可以如下定义一个简单的 `Author` 类: -```typescript title="authors/models/author.model.ts" + ```typescript title="authors/models/author.model.ts" @ObjectType() export class Author { @Field(type => ID) @@ -40,7 +48,7 @@ export class Author { 通过启用 GraphQL 插件,上述类定义可以简化为: -```typescript title="authors/models/author.model.ts" + ```typescript title="authors/models/author.model.ts" @ObjectType() export class Author { @Field(type => ID) @@ -54,7 +62,10 @@ export class Author { 该插件会基于**抽象语法树**动态添加适当的装饰器。因此,您无需再为散落在代码各处的 `@Field` 装饰器而烦恼。 -> info **注意** 插件会自动生成所有缺失的 GraphQL 属性,但如需覆盖它们,只需通过 `@Field()` 显式设置即可。 +:::info 注意 +插件会自动生成所有缺失的 GraphQL 属性,但如需覆盖它们,只需通过 `@Field()` 显式设置即可。 +::: + #### 注释内省 diff --git a/docs/graphql/complexity.md b/docs/graphql/complexity.md index 82a524a..0439ad9 100644 --- a/docs/graphql/complexity.md +++ b/docs/graphql/complexity.md @@ -1,6 +1,8 @@ ### 复杂度 -> warning **警告** 本章仅适用于代码优先方法。 +:::warning 警告 +本章仅适用于代码优先方法。 +::: 查询复杂度功能允许您定义特定字段的复杂程度,并通过设置**最大复杂度**来限制查询。其核心思想是使用简单数字来定义每个字段的复杂度,通常默认给每个字段分配 `1` 的复杂度值。此外,GraphQL 查询的复杂度计算可以通过所谓的复杂度估算器进行自定义。复杂度估算器是一个计算字段复杂度的简单函数,您可以在规则中添加任意数量的估算器,它们会按顺序依次执行。第一个返回数值型复杂度结果的估算器将决定该字段的最终复杂度。 @@ -70,7 +72,11 @@ export class ComplexityPlugin implements ApolloServerPlugin { - `simpleEstimator`:简单估算器为每个字段返回一个固定的复杂度值 - `fieldExtensionsEstimator`:字段扩展估算器用于提取模式中每个字段的复杂度值 -> **提示** 请记得将此类添加到任意模块的 providers 数组中 +:::info 提示 +请记得将此类添加到任意模块的 providers 数组中 +::: + + #### 字段级复杂度 diff --git a/docs/graphql/directives.md b/docs/graphql/directives.md index 730c463..0f5cf3d 100644 --- a/docs/graphql/directives.md +++ b/docs/graphql/directives.md @@ -68,7 +68,9 @@ GraphQLModule.forRoot({ title: string; ``` -> info **提示** `@Directive()` 装饰器是从 `@nestjs/graphql` 包中导出的。 +:::info 提示 +`@Directive()` 装饰器是从 `@nestjs/graphql` 包中导出的。 +::: 指令可以应用于字段、字段解析器、输入和对象类型,以及查询、变更和订阅操作。以下是将指令应用于查询处理器层级的示例: @@ -80,7 +82,9 @@ async getAuthor(@Args({ name: 'id', type: () => Int }) id: number) { } ``` -> warning **警告** 通过 `@Directive()` 装饰器应用的指令不会反映在生成的模式定义文件中。 +:::warning 警告 + 通过 `@Directive()` 装饰器应用的指令不会反映在生成的模式定义文件中。 +::: 最后,请确保在 `GraphQLModule` 中声明指令,如下所示: @@ -99,7 +103,9 @@ GraphQLModule.forRoot({ }), ``` -> info **提示** `GraphQLDirective` 和 `DirectiveLocation` 均从 `graphql` 包中导出。 +:::info 提示 +`GraphQLDirective` 和 `DirectiveLocation` 均从 `graphql` 包中导出。 +::: #### 模式优先 diff --git a/docs/graphql/extensions.md b/docs/graphql/extensions.md index 1e0b25e..c74e5f4 100644 --- a/docs/graphql/extensions.md +++ b/docs/graphql/extensions.md @@ -1,6 +1,8 @@ ### 扩展功能 -> warning **警告** 本章仅适用于代码优先方法。 +:::warning 警告 +本章仅适用于代码优先方法。 +::: 扩展是一项**高级底层特性** ,允许您在类型配置中定义任意数据。通过为特定字段附加自定义元数据,您可以创建更复杂、通用的解决方案。例如,借助扩展功能,您可以定义访问特定字段所需的字段级角色。这些角色可在运行时反映,以确定调用者是否具备检索特定字段的足够权限。 diff --git a/docs/graphql/federation.md b/docs/graphql/federation.md index 5479149..a9af9b7 100644 --- a/docs/graphql/federation.md +++ b/docs/graphql/federation.md @@ -9,7 +9,11 @@ - 图形结构应便于客户端使用。通过联合服务,可以构建出完整的产品导向型图形结构,准确反映客户端实际消费方式。 - 这只是使用标准规范的 **GraphQL** 功能。任何编程语言(不仅是 JavaScript)都能实现联邦查询。 -> **警告** 联邦当前不支持订阅。 +:::warning 警告 +联邦当前不支持订阅。 +::: + + 在接下来的章节中,我们将搭建一个包含网关和两个联邦端点的演示应用:用户服务和帖子服务。 @@ -391,7 +395,11 @@ export class AppModule {} $ npm install --save @apollo/subgraph @nestjs/mercurius ``` -> **提示** 需要 `@apollo/subgraph` 包来构建子图模式(`buildSubgraphSchema`、`printSubgraphSchema` 函数)。 +:::info 提示 +需要 `@apollo/subgraph` 包来构建子图模式(`buildSubgraphSchema`、`printSubgraphSchema` 函数)。 +::: + + #### 模式优先 @@ -747,7 +755,11 @@ export class AppModule {} 引用 [Apollo 文档](https://www.apollographql.com/docs/federation/federation-2/new-in-federation-2)的说法,联邦 2 改进了原始 Apollo 联邦(在本文档中称为联邦 1)的开发者体验,与大多数原始超级图向后兼容。 -> **警告** Mercurius 不完全支持联邦 2。您可以在[此处](https://www.apollographql.com/docs/federation/supported-subgraphs#javascript--typescript)查看支持联邦 2 的库列表。 +:::warning 警告 +Mercurius 不完全支持联邦 2。您可以在[此处](https://www.apollographql.com/docs/federation/supported-subgraphs#javascript--typescript)查看支持联邦 2 的库列表。 +::: + + 在接下来的章节中,我们将把之前的示例升级到联邦 2。 diff --git a/docs/graphql/field-middleware.md b/docs/graphql/field-middleware.md index fdb104a..deb6560 100644 --- a/docs/graphql/field-middleware.md +++ b/docs/graphql/field-middleware.md @@ -1,6 +1,8 @@ ### 字段中间件 -> warning **警告** 本章仅适用于代码优先方法。 +:::warning 警告 +本章仅适用于代码优先方法。 +::: 字段中间件允许你在字段解析**之前或之后**运行任意代码。字段中间件可用于转换字段结果、验证字段参数,甚至检查字段级别的角色(例如,访问目标字段所需的权限,中间件函数将为此执行)。 @@ -23,9 +25,13 @@ const loggerMiddleware: FieldMiddleware = async ( }; ``` -> info **提示** `MiddlewareContext` 是一个包含与 GraphQL 解析器函数通常接收的相同参数的对象 ( `{ source, args, context, info }` ),而 `NextFn` 是一个允许您执行堆栈中下一个中间件(绑定到此字段)或实际字段解析器的函数。 +:::info 提示 +`MiddlewareContext` 是一个包含与 GraphQL 解析器函数通常接收的相同参数的对象 ( `{ source, args, context, info }` ),而 `NextFn` 是一个允许您执行堆栈中下一个中间件(绑定到此字段)或实际字段解析器的函数。 +::: -> warning **注意** 字段中间件函数无法注入依赖项也无法访问 Nest 的 DI 容器,因为它们被设计得非常轻量级且不应执行任何可能耗时的操作(如从数据库检索数据)。如果您需要调用外部服务/从数据源查询数据,应在绑定到根查询/变更处理程序的守卫/拦截器中完成,并将其分配给可从字段中间件内部(特别是从 `MiddlewareContext` 对象中)访问的 `context` 对象。 +:::warning 注意 + 字段中间件函数无法注入依赖项也无法访问 Nest 的 DI 容器,因为它们被设计得非常轻量级且不应执行任何可能耗时的操作(如从数据库检索数据)。如果您需要调用外部服务/从数据源查询数据,应在绑定到根查询/变更处理程序的守卫/拦截器中完成,并将其分配给可从字段中间件内部(特别是从 `MiddlewareContext` 对象中)访问的 `context` 对象。 +::: 请注意,字段中间件必须符合 `FieldMiddleware` 接口规范。在上述示例中,我们先执行 `next()` 函数(该函数会执行实际的字段解析器并返回字段值),然后将该值记录到终端。此外,中间件函数返回的值会完全覆盖之前的值,由于我们不希望进行任何修改,因此直接返回原始值。 @@ -41,9 +47,15 @@ export class Recipe { 现在每当我们请求 `Recipe` 对象类型的 `title` 字段时,原始字段值将被记录到控制台。 -> **提示** 要了解如何通过 [extensions](/graphql/extensions) 功能实现字段级权限系统,请查看此[章节](/graphql/extensions#使用自定义元数据) 。 +:::info 提示 +要了解如何通过 [extensions](/graphql/extensions) 功能实现字段级权限系统,请查看此[章节](/graphql/extensions#使用自定义元数据) 。 +::: -> warning **警告** 字段中间件只能应用于 `ObjectType` 类。更多详情请查看此 [问题](https://github.com/nestjs/graphql/issues/2446) 。 + + +:::warning 警告 + 字段中间件只能应用于 `ObjectType` 类。更多详情请查看此 [问题](https://github.com/nestjs/graphql/issues/2446) 。 +::: 此外,如上所述,我们可以在中间件函数内部控制字段值。出于演示目的,我们将食谱标题(如果存在)转换为大写: @@ -63,7 +75,9 @@ title() { } ``` -> warning **警告** 如果在字段解析器级别启用了增强器( [了解更多](/graphql/other-features#在字段解析器级别执行增强器) ),字段中间件函数将在任何拦截器、守卫等**绑定到方法**之前运行(但在为查询或变更处理程序注册的根级别增强器之后)。 +:::warning 警告 + 如果在字段解析器级别启用了增强器( [了解更多](/graphql/other-features#在字段解析器级别执行增强器) ),字段中间件函数将在任何拦截器、守卫等**绑定到方法**之前运行(但在为查询或变更处理程序注册的根级别增强器之后)。 +::: #### 全局字段中间件 @@ -78,4 +92,7 @@ GraphQLModule.forRoot({ }), ``` -> info **提示** 全局注册的字段中间件函数将在本地注册的中间件(直接绑定到特定字段的那些) **之前**执行。 +:::info 提示 + 全局注册的字段中间件函数将在本地注册的中间件(直接绑定到特定字段的那些) **之前**执行。 +::: + diff --git a/docs/graphql/guards-interceptors.md b/docs/graphql/guards-interceptors.md index e6fc04a..334c7bf 100644 --- a/docs/graphql/guards-interceptors.md +++ b/docs/graphql/guards-interceptors.md @@ -59,7 +59,10 @@ export class HttpExceptionFilter implements GqlExceptionFilter { } ``` -> info **注意** `GqlExceptionFilter` 和 `GqlArgumentsHost` 都是从 `@nestjs/graphql` 包导入的。 +:::info 注意 +`GqlExceptionFilter` 和 `GqlArgumentsHost` 都是从 `@nestjs/graphql` 包导入的。 +::: + 请注意与 REST 不同,这里不使用原生的 `response` 对象来生成响应。 @@ -84,7 +87,11 @@ async upvotePost( ) {} ``` -> **提示** 在上例中,我们假设 `user` 对象已分配给你的 GraphQL 应用程序上下文。 +:::info 提示 +在上例中,我们假设 `user` 对象已分配给你的 GraphQL 应用程序上下文。 +::: + + #### 在字段解析器级别执行增强器 @@ -96,7 +103,11 @@ GraphQLModule.forRoot({ }), ``` -> **警告** 为字段解析器启用增强器可能导致性能问题,特别是当您返回大量记录且字段解析器被执行数千次时。因此,当启用 `fieldResolverEnhancers` 时,建议跳过对字段解析器非严格必需的增强器执行。您可以使用以下辅助函数实现: +:::warning 警告 +为字段解析器启用增强器可能导致性能问题,特别是当您返回大量记录且字段解析器被执行数千次时。因此,当启用 `fieldResolverEnhancers` 时,建议跳过对字段解析器非严格必需的增强器执行。您可以使用以下辅助函数实现: +::: + + ```typescript export function isResolvingGraphQLField(context: ExecutionContext): boolean { diff --git a/docs/graphql/index.md b/docs/graphql/index.md index 056cbad..1180c1b 100644 --- a/docs/graphql/index.md +++ b/docs/graphql/index.md @@ -42,7 +42,11 @@ $ npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql # npm i @nestjs/graphql @nestjs/mercurius graphql mercurius ``` -> **警告** `@nestjs/graphql@>=9` 和 `@nestjs/apollo^10` 包与 Apollo v3 兼容,而 `@nestjs/graphql@^8` 仅支持 Apollo v2。 +:::warning 警告 +`@nestjs/graphql@>=9` 和 `@nestjs/apollo^10` 包与 Apollo v3 兼容,而 `@nestjs/graphql@^8` 仅支持 Apollo v2。 +::: + + ## 快速开始 @@ -69,7 +73,11 @@ Playground 是一个图形化、交互式、浏览器内的 GraphQL IDE,默认 应用程序运行后,打开浏览器并导航到 `http://localhost:3000/graphql`(主机和端口可能因配置而异)。 -> **注意** 默认的 Apollo playground 已被弃用,将在下一个主要版本中删除。建议使用 [GraphiQL](https://github.com/graphql/graphiql),只需在 `GraphQLModule` 配置中设置 `graphiql: true`: +:::info 注意 +默认的 Apollo playground 已被弃用,将在下一个主要版本中删除。建议使用 [GraphiQL](https://github.com/graphql/graphiql),只需在 `GraphQLModule` 配置中设置 `graphiql: true`: +::: + + ```typescript GraphQLModule.forRoot({ diff --git a/docs/graphql/interfaces.md b/docs/graphql/interfaces.md index 0bcb090..c8b286b 100644 --- a/docs/graphql/interfaces.md +++ b/docs/graphql/interfaces.md @@ -19,7 +19,9 @@ export abstract class Character { } ``` -> warning **注意** TypeScript 接口不能用于定义 GraphQL 接口。 +:::warning 注意 +TypeScript 接口不能用于定义 GraphQL 接口。 +::: 这将生成以下 GraphQL 模式定义语言(SDL)部分: @@ -42,7 +44,11 @@ export class Human implements Character { } ``` -> **提示** `@ObjectType()` 装饰器是从 `@nestjs/graphql` 包导出的。 +:::info 提示 +`@ObjectType()` 装饰器是从 `@nestjs/graphql` 包导出的。 +::: + + 该库生成的默认 `resolveType()` 函数会根据解析器方法返回的值提取类型。这意味着你必须返回类实例(不能返回字面量 JavaScript 对象)。 @@ -89,7 +95,9 @@ export class CharacterInterfaceResolver { 现在 `friends` 字段解析器会自动为所有实现 `Character` 接口的对象类型注册。 -> warning **警告** 这需要将 `inheritResolversFromInterfaces` 属性设置为 true 并配置在 `GraphQLModule` 中。 +:::warning 警告 + 这需要将 `inheritResolversFromInterfaces` 属性设置为 true 并配置在 `GraphQLModule` 中。 +::: #### Schema first @@ -126,4 +134,6 @@ export class CharactersResolver { } ``` -> info 所有装饰器均从 `@nestjs/graphql` 包中导出。 +:::info +所有装饰器均从 `@nestjs/graphql` 包中导出。 +::: diff --git a/docs/graphql/mapped-types.md b/docs/graphql/mapped-types.md index 767898b..9c3c491 100644 --- a/docs/graphql/mapped-types.md +++ b/docs/graphql/mapped-types.md @@ -1,6 +1,8 @@ ### 映射类型 -> warning **警告** 本章仅适用于代码优先方法。 +:::warning 警告 +本章仅适用于代码优先方法。 +::: 在构建 CRUD(创建/读取/更新/删除)等功能时,基于基础实体类型构造变体通常很有用。Nest 提供了几个实用函数来执行类型转换,使这一任务更加便捷。 @@ -33,7 +35,9 @@ class CreateUserInput { export class UpdateUserInput extends PartialType(CreateUserInput) {} ``` -> info **提示** `PartialType()` 函数是从 `@nestjs/graphql` 包中导入的。 +:::info 提示 +`PartialType()` 函数是从 `@nestjs/graphql` 包中导入的。 +::: `PartialType()` 函数接受一个可选的第二个参数,该参数是对装饰器工厂的引用。此参数可用于更改应用于结果(子)类的装饰器函数。如果未指定,子类实际上会使用与**父**类(第一个参数引用的类)相同的装饰器。在上面的示例中,我们正在扩展用 `@InputType()` 装饰器注解的 `CreateUserInput`。由于我们希望 `UpdateUserInput` 也被视为使用 `@InputType()` 装饰,因此无需将 `InputType` 作为第二个参数传递。如果父类型和子类型不同(例如父类用 `@ObjectType` 装饰),我们会将 `InputType` 作为第二个参数传递。例如: @@ -69,7 +73,9 @@ export class UpdateEmailInput extends PickType(CreateUserInput, [ ] as const) {} ``` -> info **提示** `PickType()` 函数是从 `@nestjs/graphql` 包中导入的。 +:::info 提示 +`PickType()` 函数是从 `@nestjs/graphql` 包中导入的。 +::: #### 省略 @@ -98,7 +104,9 @@ export class UpdateUserInput extends OmitType(CreateUserInput, [ ] as const) {} ``` -> info **提示** `OmitType()` 函数是从 `@nestjs/graphql` 包中导入的。 +:::info 提示 +`OmitType()` 函数是从 `@nestjs/graphql` 包中导入的。 +::: #### 交叉类型 @@ -134,7 +142,9 @@ export class UpdateUserInput extends IntersectionType( ) {} ``` -> info **提示** `IntersectionType()` 函数是从 `@nestjs/graphql` 包中导入的。 +:::info 提示 +`IntersectionType()` 函数是从 `@nestjs/graphql` 包中导入的。 +::: #### 组合 diff --git a/docs/graphql/mutations.md b/docs/graphql/mutations.md index 2aac0c6..da604df 100644 --- a/docs/graphql/mutations.md +++ b/docs/graphql/mutations.md @@ -15,7 +15,9 @@ async upvotePost(@Args({ name: 'postId', type: () => Int }) postId: number) { } ``` -> info **提示** 所有装饰器(例如 `@Resolver`、`@ResolveField`、`@Args` 等)均从 `@nestjs/graphql` 包中导出。 +:::info 提示 +所有装饰器(例如 `@Resolver`、`@ResolveField`、`@Args` 等)均从 `@nestjs/graphql` 包中导出。 +::: 这将生成以下 GraphQL 模式定义语言(SDL)部分: @@ -39,7 +41,9 @@ export class UpvotePostInput { } ``` -> info **提示** `@InputType()` 装饰器接收一个选项对象作为参数,因此您可以指定输入类型的描述等信息。请注意,由于 TypeScript 元数据反射系统的限制,您必须使用 `@Field` 装饰器手动指定类型,或者使用 [CLI 插件](/graphql/cli-plugin) 。 +:::info 提示 +`@InputType()` 装饰器接收一个选项对象作为参数,因此您可以指定输入类型的描述等信息。请注意,由于 TypeScript 元数据反射系统的限制,您必须使用 `@Field` 装饰器手动指定类型,或者使用 [CLI 插件](/graphql/cli-plugin) 。 +::: 我们可以在解析器类中使用此类型: diff --git a/docs/graphql/plugins.md b/docs/graphql/plugins.md index d0be76d..6b292a3 100644 --- a/docs/graphql/plugins.md +++ b/docs/graphql/plugins.md @@ -45,13 +45,17 @@ GraphQLModule.forRoot({ }), ``` -> info **提示** `ApolloServerOperationRegistry` 插件是从 `@apollo/server-plugin-operation-registry` 包导出的。 +:::info 提示 +`ApolloServerOperationRegistry` 插件是从 `@apollo/server-plugin-operation-registry` 包导出的。 +::: #### 与 Mercurius 搭配使用的插件 部分现有的 mercurius 专属 Fastify 插件必须在 mercurius 插件之后加载(详见插件树[此处](https://mercurius.dev/#/docs/plugins) )。 -> warning **注意** [mercurius-upload](https://github.com/mercurius-js/mercurius-upload) 是个例外,应在主文件中注册。 +:::warning 注意 +[mercurius-upload](https://github.com/mercurius-js/mercurius-upload) 是个例外,应在主文件中注册。 +::: 为此,`MercuriusDriver` 提供了一个可选的 `plugins` 配置项。它表示一个由对象组成的数组,每个对象包含两个属性:`plugin` 及其对应的 `options`。因此,注册 [缓存插件](https://github.com/mercurius-js/cache) 的示例如下: diff --git a/docs/graphql/quick-start.md b/docs/graphql/quick-start.md index 4880211..6835a9f 100644 --- a/docs/graphql/quick-start.md +++ b/docs/graphql/quick-start.md @@ -21,7 +21,11 @@ $ npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql # npm i @nestjs/graphql @nestjs/mercurius graphql mercurius ``` -> **警告** `@nestjs/graphql@>=9` 和 `@nestjs/apollo^10` 包仅兼容 **Apollo v3**(详情请参阅 Apollo Server 3 的[迁移指南](https://www.apollographql.com/docs/apollo-server/migration/)),而 `@nestjs/graphql@^8` 仅支持 **Apollo v2**(例如 `apollo-server-express@2.x.x` 包)。 +:::warning 警告 +`@nestjs/graphql@>=9` 和 `@nestjs/apollo^10` 包仅兼容 **Apollo v3**(详情请参阅 Apollo Server 3 的[迁移指南](https://www.apollographql.com/docs/apollo-server/migration/)),而 `@nestjs/graphql@^8` 仅支持 **Apollo v2(例如 `apollo-server-express@2.x.x` 包)。 +::: + + #### 概述 @@ -33,7 +37,11 @@ Nest 提供了两种构建 GraphQL 应用的方式:**代码优先**和**模式 #### GraphQL 与 TypeScript 入门指南 -> **提示** 在接下来的章节中,我们将集成 `@nestjs/apollo` 包。如需改用 `mercurius` 包,请跳转至[此章节](#mercurius-集成)。 +:::info 提示 +在接下来的章节中,我们将集成 `@nestjs/apollo` 包。如需改用 `mercurius` 包,请跳转至[此章节](#mercurius-集成)。 +::: + + 安装完相关包后,我们可以导入 `GraphQLModule` 并通过 `forRoot()` 静态方法进行配置。 @@ -52,7 +60,11 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; export class AppModule {} ``` -> **提示** 对于 `mercurius` 集成,您应当使用 `MercuriusDriver` 和 `MercuriusDriverConfig`,两者均从 `@nestjs/mercurius` 包导出。 +:::info 提示 +对于 `mercurius` 集成,您应当使用 `MercuriusDriver` 和 `MercuriusDriverConfig`,两者均从 `@nestjs/mercurius` 包导出。 +::: + + `forRoot()` 方法接收一个配置对象作为参数。这些配置会被传递到底层驱动实例(更多可用设置请参阅:[Apollo](https://www.apollographql.com/docs/apollo-server/api/apollo-server) 和 [Mercurius](https://github.com/mercurius-js/mercurius/blob/master/docs/api/options.md#plugin-options))。例如,若要禁用 `playground` 并关闭 `debug` 模式(针对 Apollo),可传递如下配置: @@ -84,9 +96,17 @@ Playground 是一个图形化、交互式的浏览器内 GraphQL IDE,默认情 -> **注意** `@nestjs/mercurius` 集成不包含内置的 GraphQL Playground 功能。作为替代,您可以使用 [GraphiQL](https://github.com/graphql/graphiql)(设置 `graphiql: true` 参数)。 +:::info 注意 +`@nestjs/mercurius` 集成不包含内置的 GraphQL Playground 功能。作为替代,您可以使用 [GraphiQL](https://github.com/graphql/graphiql)(设置 `graphiql: true` 参数)。 +::: + + + +:::warning 警告 +更新(2025 年 4 月 14 日):默认的 Apollo playground 已被弃用,并将在下一个主要版本中移除。作为替代,您可以使用 [GraphiQL](https://github.com/graphql/graphiql),只需在 `GraphQLModule` 配置中设置 `graphiql: true`,如下所示: +::: + -> **警告** 更新(2025 年 4 月 14 日):默认的 Apollo playground 已被弃用,并将在下一个主要版本中移除。作为替代,您可以使用 [GraphiQL](https://github.com/graphql/graphiql),只需在 `GraphQLModule` 配置中设置 `graphiql: true`,如下所示: > > ```typescript > GraphQLModule.forRoot({ @@ -96,6 +116,7 @@ Playground 是一个图形化、交互式的浏览器内 GraphQL IDE,默认情 > ``` > > 如果您的应用程序使用[订阅](./subscriptions)功能,请务必使用 `graphql-ws`,因为 GraphiQL 不支持 `subscriptions-transport-ws`。 + #### 代码优先 在**代码优先**方法中,您可以使用装饰器和 TypeScript 类来生成相应的 GraphQL 模式。 @@ -188,7 +209,11 @@ definitionsFactory.generate({ $ ts-node generate-typings ``` -> **提示** 你可以预先编译该脚本(例如使用 `tsc`),然后通过 `node` 来执行它。 +:::info 提示 +你可以预先编译该脚本(例如使用 `tsc`),然后通过 `node` 来执行它。 +::: + + 要为脚本启用监视模式(在任意 `.graphql` 文件变更时自动生成类型定义),请向 `generate()` 方法传入 `watch` 选项。 @@ -260,7 +285,11 @@ export class AppModule {} const { schema } = app.get(GraphQLSchemaHost); ``` -> **提示** 您必须在应用程序初始化完成后(即在 `app.listen()` 或 `app.init()` 方法触发 `onModuleInit` 钩子之后)调用 `GraphQLSchemaHost#schema` 的 getter 方法。 +:::info 提示 +您必须在应用程序初始化完成后(即在 `app.listen()` 或 `app.init()` 方法触发 `onModuleInit` 钩子之后)调用 `GraphQLSchemaHost#schema` 的 getter 方法。 +::: + + #### 示例 @@ -345,7 +374,11 @@ import { MercuriusDriver, MercuriusDriverConfig } from '@nestjs/mercurius'; export class AppModule {} ``` -> **提示** 应用运行后,在浏览器中访问 `http://localhost:3000/graphiql` ,您将看到 [GraphQL 集成开发环境](https://github.com/graphql/graphiql) 。 +:::info 提示 +应用运行后,在浏览器中访问 `http://localhost:3000/graphiql` ,您将看到 [GraphQL 集成开发环境](https://github.com/graphql/graphiql) 。 +::: + + `forRoot()` 方法接收一个配置对象作为参数,这些配置会被传递给底层驱动实例。更多可用设置请参阅[此处](https://github.com/mercurius-js/mercurius/blob/master/docs/api/options.md#plugin-options)。 @@ -359,7 +392,11 @@ GraphQLModule.forRoot({ }), ``` -> **警告** 如果在单个应用中使用 `@apollo/server` 和 `@as-integrations/fastify` 包配置多个 GraphQL 端点,请确保在 `GraphQLModule` 配置中启用 `disableHealthCheck` 设置。 +:::warning 警告 +如果在单个应用中使用 `@apollo/server` 和 `@as-integrations/fastify` 包配置多个 GraphQL 端点,请确保在 `GraphQLModule` 配置中启用 `disableHealthCheck` 设置。 +::: + + #### 第三方集成 diff --git a/docs/graphql/resolvers-map.md b/docs/graphql/resolvers-map.md index a5183e3..ffe8458 100644 --- a/docs/graphql/resolvers-map.md +++ b/docs/graphql/resolvers-map.md @@ -23,7 +23,7 @@ type Author { 而在代码优先(code first)方法中,我们使用 TypeScript 类和装饰器来定义模式并标注类的字段。上述 SDL 在代码优先中等效于: -```typescript title="authors/models/author.model" + ```typescript title="authors/models/author.model.ts" import { Field, Int, ObjectType } from '@nestjs/graphql'; import { Post } from './post'; @@ -43,11 +43,15 @@ export class Author { } ``` -> info **提示** TypeScript 的元数据反射系统存在若干限制,例如无法确定类由哪些属性组成,或识别某个属性是可选的还是必需的。由于这些限制,我们必须在模式定义类中显式使用 `@Field()` 装饰器来提供每个字段的 GraphQL 类型和可选性元数据,或者使用 [CLI 插件](/graphql/cli-plugin)来为我们生成这些元数据。 +:::info 提示 +TypeScript 的元数据反射系统存在若干限制,例如无法确定类由哪些属性组成,或识别某个属性是可选的还是必需的。由于这些限制,我们必须在模式定义类中显式使用 `@Field()` 装饰器来提供每个字段的 GraphQL 类型和可选性元数据,或者使用 [CLI 插件](/graphql/cli-plugin)来为我们生成这些元数据。 +::: 与任何类一样,`Author` 对象类型由一组字段组成,每个字段都声明了一个类型。字段类型对应 [GraphQL 类型](https://graphql.org/learn/schema/) 。字段的 GraphQL 类型可以是另一个对象类型,也可以是标量类型。GraphQL 标量类型是解析为单个值的原始类型(如 `ID`、`String`、`Boolean` 或 `Int`)。 -> info **提示** 除了 GraphQL 内置的标量类型外,您还可以定义自定义标量类型(阅读[更多](/graphql/scalars) )。 +:::info 提示 +除了 GraphQL 内置的标量类型外,您还可以定义自定义标量类型(阅读[更多](/graphql/scalars) )。 +::: 上述 `Author` 对象类型定义将导致 Nest **生成**我们之前展示的 SDL: @@ -86,7 +90,11 @@ info **提示** 你也可以为整个对象类型添加描述或标记为已弃 posts: Post[]; ``` -> **提示** 使用方括号标记(`[ ]`)可以表示数组的维度。例如,使用 `[[Int]]` 表示一个整数矩阵。 +:::info 提示 +使用方括号标记(`[ ]`)可以表示数组的维度。例如,使用 `[[Int]]` 表示一个整数矩阵。 +::: + + 若要声明数组元素(而非数组本身)可为空,需将 `nullable` 属性设置为 `'items'`,如下所示: @@ -95,11 +103,15 @@ posts: Post[]; posts: Post[]; ``` -> **提示** 若数组及其元素均可为空,则应将 `nullable` 设置为 `'itemsAndList'`。 +:::info 提示 +若数组及其元素均可为空,则应将 `nullable` 设置为 `'itemsAndList'`。 +::: + + 既然已创建 `Author` 对象类型,现在我们来定义 `Post` 对象类型。 -```typescript title="posts/models/post.model" + ```typescript title="posts/models/post.model.ts" import { Field, Int, ObjectType } from '@nestjs/graphql'; @ObjectType() @@ -129,7 +141,7 @@ type Post { 至此,我们已定义了数据图中可存在的对象(类型定义),但客户端尚无法与这些对象交互。为此,我们需要创建一个解析器类。在代码优先方法中,解析器类既定义解析函数**又**生成 **Query 类型** 。通过下面的示例,这一点将变得清晰: -```typescript title="authors/authors.resolver" + ```typescript title="authors/authors.resolver.ts" @Resolver(() => Author) export class AuthorsResolver { constructor( @@ -150,11 +162,15 @@ export class AuthorsResolver { } ``` -> info **提示** 所有装饰器(例如 `@Resolver`、`@ResolveField`、`@Args` 等)均从 `@nestjs/graphql` 包中导出。 +:::info 提示 +所有装饰器(例如 `@Resolver`、`@ResolveField`、`@Args` 等)均从 `@nestjs/graphql` 包中导出。 +::: 您可以定义多个解析器类。Nest 将在运行时将它们组合起来。有关代码组织的更多信息,请参阅下面的[模块](/graphql/resolvers#模块)部分。 -> warning **注意** `AuthorsService` 和 `PostsService` 类中的逻辑可以根据需要简单或复杂。本示例的主要目的是展示如何构建解析器以及它们如何与其他提供者交互。 +:::warning 注意 +`AuthorsService` 和 `PostsService` 类中的逻辑可以根据需要简单或复杂。本示例的主要目的是展示如何构建解析器以及它们如何与其他提供者交互。 +::: 在上面的示例中,我们创建了 `AuthorsResolver`,它定义了一个查询解析器函数和一个字段解析器函数。要创建解析器,我们需要创建一个以解析器函数作为方法的类,并用 `@Resolver()` 装饰器来注解该类。 @@ -166,7 +182,9 @@ export class AuthorsResolver { 我们可以定义多个 `@Query()` 解析器函数(既可以在这个类中,也可以在其他任何解析器类中),它们将被聚合到生成的 SDL 中的单个 **Query 类型**定义里,并包含解析器映射中的相应条目。这允许您将查询定义在靠近它们所使用的模型和服务的地方,并保持它们在模块中的良好组织。 -> info **提示** Nest CLI 提供了一个生成器(原理图),能自动生成**所有样板代码** ,帮助我们避免手动完成这些工作,使开发者体验更加简单。了解更多关于此功能的信息[请点击这里](/recipes/crud-generator) 。 +:::info 提示 +Nest CLI 提供了一个生成器(原理图),能自动生成**所有样板代码** ,帮助我们避免手动完成这些工作,使开发者体验更加简单。了解更多关于此功能的信息[请点击这里](/recipes/crud-generator) 。 +::: #### 查询类型名称 @@ -187,11 +205,15 @@ type Query { } ``` -> **提示** 了解更多关于 GraphQL 查询的信息[请点击此处](https://graphql.org/learn/queries/) 。 +:::info 提示 +了解更多关于 GraphQL 查询的信息[请点击此处](https://graphql.org/learn/queries/) 。 +::: + + 按照惯例,我们更倾向于解耦这些名称;例如,我们倾向于使用类似 `getAuthor()` 的名称作为查询处理方法,但仍使用 `author` 作为查询类型名称。同样适用于我们的字段解析器。我们可以通过将映射名称作为 `@Query()` 和 `@ResolveField()` 装饰器的参数传递来实现这一点,如下所示: -```typescript title="authors/authors.resolver" + ```typescript title="authors/authors.resolver.ts" @Resolver(() => Author) export class AuthorsResolver { constructor( @@ -265,7 +287,11 @@ getAuthor( ) {} ``` -> **提示** 对于 GraphQL 可为空字段 `firstName`,不需要在字段类型中添加非值类型 `null` 或 `undefined`。但请注意,你需要在解析器中为这些可能的非值类型添加类型保护,因为 GraphQL 可为空字段会允许这些类型传递到解析器。 +:::info 提示 +对于 GraphQL 可为空字段 `firstName`,不需要在字段类型中添加非值类型 `null` 或 `undefined`。但请注意,你需要在解析器中为这些可能的非值类型添加类型保护,因为 GraphQL 可为空字段会允许这些类型传递到解析器。 +::: + + #### 专用参数类 @@ -277,7 +303,7 @@ getAuthor( 使用 `@ArgsType()` 创建 `GetAuthorArgs` 类,如下所示: -```typescript title="authors/dto/get-author.args" + ```typescript title="authors/dto/get-author.args.ts" import { MinLength } from 'class-validator'; import { Field, ArgsType } from '@nestjs/graphql'; @@ -292,7 +318,9 @@ class GetAuthorArgs { } ``` -> info **提示** 再次强调,由于 TypeScript 元数据反射系统的限制,必须使用 `@Field` 装饰器手动指定类型和可选性,或者使用 [CLI 插件](/graphql/cli-plugin) 。另外,对于 GraphQL 可为空字段 `firstName`,不需要在字段类型中添加 `null` 或 `undefined` 等非值类型。只需注意,你需要在解析器中为这些可能的非值类型添加类型保护,因为 GraphQL 可为空字段会允许这些类型传递到解析器。 +:::info 提示 +再次强调,由于 TypeScript 元数据反射系统的限制,必须使用 `@Field` 装饰器手动指定类型和可选性,或者使用 [CLI 插件](/graphql/cli-plugin) 。另外,对于 GraphQL 可为空字段 `firstName`,不需要在字段类型中添加 `null` 或 `undefined` 等非值类型。只需注意,你需要在解析器中为这些可能的非值类型添加类型保护,因为 GraphQL 可为空字段会允许这些类型传递到解析器。 +::: 这将生成以下 GraphQL 模式定义语言(SDL)部分: @@ -302,7 +330,9 @@ type Query { } ``` -> info **提示** 请注意,像 `GetAuthorArgs` 这样的参数类与 `ValidationPipe` 配合得很好(阅读[更多](/techniques/validation) )。 +:::info 提示 +请注意,像 `GetAuthorArgs` 这样的参数类与 `ValidationPipe` 配合得很好(阅读[更多](/techniques/validation) )。 +::: #### 类继承 @@ -457,7 +487,10 @@ class PaginatedAuthor extends Paginated(Author) {} 如[前一章](/graphql/quick-start)所述,在模式优先方法中,我们首先手动在 SDL 中定义模式类型(阅读[更多](https://graphql.org/learn/schema/#type-language) )。考虑以下 SDL 类型定义。 -> info **注意** 为方便起见,本章节将所有 SDL 集中在一处(如下所示的单个 `.graphql` 文件)。实际开发中,您可能会发现以模块化方式组织代码更为合适。例如,可以为每个领域实体创建单独的 SDL 文件,包含类型定义、相关服务、解析器代码以及该实体的 Nest 模块定义类,并统一存放在该实体的专属目录中。Nest 会在运行时自动聚合所有独立的模式类型定义。 +:::info 注意 +为方便起见,本章节将所有 SDL 集中在一处(如下所示的单个 `.graphql` 文件)。实际开发中,您可能会发现以模块化方式组织代码更为合适。例如,可以为每个领域实体创建单独的 SDL 文件,包含类型定义、相关服务、解析器代码以及该实体的 Nest 模块定义类,并统一存放在该实体的专属目录中。Nest 会在运行时自动聚合所有独立的模式类型定义。 +::: + ```graphql type Author { @@ -482,11 +515,14 @@ type Query { 上述模式公开了一个查询方法 - `author(id: Int!): Author`。 -> info **注意** 了解更多关于 GraphQL 查询的信息,请[点击此处](https://graphql.org/learn/queries/) 。 +:::info 注意 +了解更多关于 GraphQL 查询的信息,请[点击此处](https://graphql.org/learn/queries/) 。 +::: + 现在我们来创建一个解析作者查询的 `AuthorsResolver` 类: -```typescript title="authors/authors.resolver" + ```typescript title="authors/authors.resolver.ts" @Resolver('Author') export class AuthorsResolver { constructor( @@ -507,9 +543,13 @@ export class AuthorsResolver { } ``` -> info **提示** 所有装饰器(如 `@Resolver`、`@ResolveField`、`@Args` 等)都是从 `@nestjs/graphql` 包中导出的。 +:::info 提示 +所有装饰器(如 `@Resolver`、`@ResolveField`、`@Args` 等)都是从 `@nestjs/graphql` 包中导出的。 +::: -> warning **注意** `AuthorsService` 和 `PostsService` 类中的逻辑可以根据需要简单或复杂。本示例的主要目的是展示如何构建解析器以及它们如何与其他提供者交互。 +:::warning 注意 + `AuthorsService` 和 `PostsService` 类中的逻辑可以根据需要简单或复杂。本示例的主要目的是展示如何构建解析器以及它们如何与其他提供者交互。 +::: `@Resolver()` 装饰器是必需的。它接受一个可选的字符串参数,用于指定类名。当类包含 `@ResolveField()` 装饰器时,这个类名是必需的,用于告知 Nest 被装饰的方法与父类型相关联(在我们当前的例子中是 `Author` 类型)。或者,也可以不在类顶部设置 `@Resolver()`,而是为每个方法单独设置: @@ -524,9 +564,17 @@ async posts(@Parent() author) { 在此情况下(方法层级的 `@Resolver()` 装饰器),若类中包含多个 `@ResolveField()` 装饰器,则必须为所有方法添加 `@Resolver()`。这种做法不被视为最佳实践(因其会产生额外开销)。 -> **提示** 传递给 `@Resolver()` 的任何类名参数**不会**影响查询(`@Query()` 装饰器)或变更(`@Mutation()` 装饰器)。 +:::info 提示 +传递给 `@Resolver()` 的任何**类名参数**不会影响查询(`@Query()` 装饰器)或变更(`@Mutation()` 装饰器)。 +::: + + + +:::warning 警告 +在**代码优先**方法中不支持在方法层级使用 `@Resolver` 装饰器。 +::: + -> **警告** 在**代码优先**方法中不支持在方法层级使用 `@Resolver` 装饰器。 上述示例中,`@Query()` 和 `@ResolveField()` 装饰器会根据方法名关联到 GraphQL 模式类型。例如,考虑前文示例中的以下结构: @@ -547,7 +595,7 @@ type Query { 按照惯例,我们更倾向于解耦这些内容,使用诸如 `getAuthor()` 或 `getPosts()` 这样的名称作为解析器方法。我们可以通过将映射名称作为装饰器的参数来实现这一点,如下所示: -```typescript title="authors/authors.resolver" + ```typescript title="authors/authors.resolver.ts" @Resolver('Author') export class AuthorsResolver { constructor( @@ -568,13 +616,17 @@ export class AuthorsResolver { } ``` -> **提示** Nest CLI 提供了一个生成器(schematic),能自动生成**所有样板代码** ,帮助我们避免手动完成这些工作,使开发体验更加简单。点击[此处](/recipes/crud-generator)了解更多关于此功能的信息。 +:::info 提示 +Nest CLI 提供了一个生成器(schematic),能自动生成**所有样板代码**,帮助我们避免手动完成这些工作,使开发体验更加简单。点击[此处](/recipes/crud-generator)了解更多关于此功能的信息。 +::: + + #### 生成类型 假设我们采用模式优先(schema first)方法并启用了类型生成功能(如[前一章](/graphql/quick-start)所示,设置 `outputAs: 'class'`),运行应用后将在 `GraphQLModule.forRoot()` 方法指定的位置生成如下文件(例如 `src/graphql.ts`): -```typescript title="graphql" + ```typescript title="graphql.ts" export (class Author { id: number; firstName?: string; @@ -604,7 +656,11 @@ export class CreatePostInput { } ``` -> **注意** 要实现输入(及参数)的自动验证,请使用 `ValidationPipe`。有关验证的更多信息请[参阅此处](/techniques/validation) ,关于管道的具体说明请[查看这里](/pipes) 。 +:::info 注意 +要实现输入(及参数)的自动验证,请使用 `ValidationPipe`。有关验证的更多信息请[参阅此处](/techniques/validation) ,关于管道的具体说明请[查看这里](/pipes) 。 +::: + + 然而,若直接将装饰器添加到自动生成的文件中,每次文件重新生成时这些装饰器都会被**覆盖** 。正确的做法是创建一个单独的文件来扩展生成的类。 @@ -659,7 +715,7 @@ export class CreatePostInput extends Post { 例如,我们可以在 `AuthorsModule` 中实现这一功能,该模块还能提供此场景下所需的其他服务。请确保在某个位置(如根模块或被根模块导入的其他模块中)导入 `AuthorsModule`。 -```typescript title="authors/authors.module" + ```typescript title="authors/authors.module.ts" @Module({ imports: [PostsModule], providers: [AuthorsService, AuthorsResolver], @@ -667,4 +723,8 @@ export class CreatePostInput extends Post { export class AuthorsModule {} ``` -> **提示** 按照所谓的**领域模型** (类似于在 REST API 中组织入口点的方式)来组织代码会很有帮助。采用这种方法时,请将模型(`ObjectType` 类)、解析器和服务都集中放在代表领域模型的 Nest 模块中。每个模块的所有组件都应存放在单一文件夹内。当您这样做并使用 [Nest CLI](/cli/overview) 生成每个元素时,Nest 会自动为您将这些部分连接起来(将文件定位到适当文件夹、在 `provider` 和 `imports` 数组中生成条目等)。 +:::info 提示 +按照所谓的**领域模型**(类似于在 REST API 中组织入口点的方式)来组织代码会很有帮助。采用这种方法时,请将模型(`ObjectType` 类)、解析器和服务都集中放在代表领域模型的 Nest 模块中。每个模块的所有组件都应存放在单一文件夹内。当您这样做并使用 [Nest CLI](/cli/overview) 生成每个元素时,Nest 会自动为您将这些部分连接起来(将文件定位到适当文件夹、在 `provider` 和 `imports` 数组中生成条目等)。 +::: + + diff --git a/docs/graphql/scalars.md b/docs/graphql/scalars.md index 84416cf..0f18072 100644 --- a/docs/graphql/scalars.md +++ b/docs/graphql/scalars.md @@ -256,7 +256,11 @@ definitionsFactory.generate({ }); ``` -> **提示** 或者,你也可以使用类型引用,例如:`DateTime: Date`。这种情况下,`GraphQLDefinitionsFactory` 将提取指定类型的名称属性(`Date.name`)来生成 TS 定义。注意:对于非内置类型(自定义类型),需要添加对应的导入语句。 +:::info 提示 +或者,你也可以使用类型引用,例如:`DateTime: Date`。这种情况下,`GraphQLDefinitionsFactory` 将提取指定类型的名称属性(`Date.name`)来生成 TS 定义。注意:对于非内置类型(自定义类型),需要添加对应的导入语句。 +::: + + 现在,给定以下 GraphQL 自定义标量类型: @@ -278,4 +282,7 @@ export type Payload = unknown; 在此,我们使用了 `customScalarTypeMapping` 属性来提供我们希望为自定义标量声明的类型映射。我们还提供了一个 `additionalHeader` 属性,以便添加这些类型定义所需的任何导入项。最后,我们添加了一个默认标量类型 `defaultScalarType`,其值为 `'unknown'`,这样任何未在 `customScalarTypeMapping` 中指定的自定义标量都将被别名化为 `unknown` 而非 `any`(自 TypeScript 3.0 起[官方推荐](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type)使用前者以增强类型安全性)。 -> info **注意** 我们已从 `bignumber.js` 导入了 `_BigNumber`;这是为了避免[循环类型引用](https://github.com/Microsoft/TypeScript/issues/12525#issuecomment-263166239) 。 +:::info 注意 +我们已从 `bignumber.js` 导入了 `_BigNumber`;这是为了避免[循环类型引用](https://github.com/Microsoft/TypeScript/issues/12525#issuecomment-263166239) 。 +::: + diff --git a/docs/graphql/schema-generator.md b/docs/graphql/schema-generator.md index 79a1b17..6a17136 100644 --- a/docs/graphql/schema-generator.md +++ b/docs/graphql/schema-generator.md @@ -1,6 +1,8 @@ ### 生成 SDL -> warning **警告** 本章仅适用于代码优先方法。 +:::warning 警告 +本章仅适用于代码优先方法。 +::: 要手动生成 GraphQL SDL 模式(即无需运行应用程序、连接数据库、配置解析器等操作),请使用 `GraphQLSchemaBuilderModule`。 @@ -15,7 +17,9 @@ async function generateSchema() { } ``` -> info **提示** `GraphQLSchemaBuilderModule` 和 `GraphQLSchemaFactory` 从 `@nestjs/graphql` 包导入。`printSchema` 函数从 `graphql` 包导入。 +:::info 提示 +`GraphQLSchemaBuilderModule` 和 `GraphQLSchemaFactory` 从 `@nestjs/graphql` 包导入。`printSchema` 函数从 `graphql` 包导入。 +::: #### 使用方法 diff --git a/docs/graphql/sharing-models.md b/docs/graphql/sharing-models.md index a3f59ee..266a3cb 100644 --- a/docs/graphql/sharing-models.md +++ b/docs/graphql/sharing-models.md @@ -1,6 +1,8 @@ ### 共享模型 -> warning **警告** 本章仅适用于代码优先方法。 +:::warning 警告 +本章仅适用于代码优先方法。 +::: 在项目后端使用 TypeScript 的最大优势之一,是能够通过共享 TypeScript 包,在基于 TypeScript 的前端应用中复用相同的模型。 @@ -15,9 +17,12 @@ ```typescript resolve: { // see: https://webpack.js.org/configuration/resolve/ alias: { - "@nestjs/graphql": path.resolve(__dirname, "../node_modules/@nestjs/graphql/dist/extra/graphql-model-shim") + "@nestjs/graphql": path.resolve(__dirname, "../node_modules/@nestjs/graphql/dist/extra/graphql-model-shim") } } ``` -> info **注意** [TypeORM](/techniques/database) 包也有类似的垫片,可在此处[查看](https://github.com/typeorm/typeorm/blob/master/extra/typeorm-model-shim.js) 。 +:::info 注意 +[TypeORM](/techniques/sql) 包也有类似的垫片,可在此处[查看](https://github.com/typeorm/typeorm/blob/master/extra/typeorm-model-shim.js) 。 +::: + diff --git a/docs/graphql/subscriptions.md b/docs/graphql/subscriptions.md index 7ff4822..b7ed783 100644 --- a/docs/graphql/subscriptions.md +++ b/docs/graphql/subscriptions.md @@ -15,7 +15,9 @@ GraphQLModule.forRoot({ }), ``` -> warning **注意** `installSubscriptionHandlers` 配置选项已在最新版 Apollo 服务器中移除,并即将在本包中弃用。默认情况下,`installSubscriptionHandlers` 会回退使用 `subscriptions-transport-ws`( [了解更多](https://github.com/apollographql/subscriptions-transport-ws) ),但我们强烈建议改用 `graphql-ws`( [了解更多](https://github.com/enisdenjo/graphql-ws) )库。 +:::warning 注意 +`installSubscriptionHandlers` 配置选项已在最新版 Apollo 服务器中移除,并即将在本包中弃用。默认情况下,`installSubscriptionHandlers` 会回退使用 `subscriptions-transport-ws`( [了解更多](https://github.com/apollographql/subscriptions-transport-ws) ),但我们强烈建议改用 `graphql-ws`( [了解更多](https://github.com/enisdenjo/graphql-ws) )库。 +::: 要切换使用 `graphql-ws` 包,请使用以下配置: @@ -28,7 +30,9 @@ GraphQLModule.forRoot({ }), ``` -> info **说明** 您也可以同时使用两个包(`subscriptions-transport-ws` 和 `graphql-ws`),例如为了保持向后兼容性。 +:::info 说明 + 您也可以同时使用两个包(`subscriptions-transport-ws` 和 `graphql-ws`),例如为了保持向后兼容性。 +::: #### 代码优先 @@ -49,9 +53,15 @@ export class AuthorResolver { } ``` -> **提示** 所有装饰器都从 `@nestjs/graphql` 包导出,而 `PubSub` 类则从 `graphql-subscriptions` 包导出。 +:::info 提示 +所有装饰器都从 `@nestjs/graphql` 包导出,而 `PubSub` 类则从 `graphql-subscriptions` 包导出。 +::: -> warning **注意** `PubSub` 是一个提供简单 `发布` 和 `订阅 API` 的类。了解更多信息请点击 [此处](https://www.apollographql.com/docs/graphql-subscriptions/setup.html) 。请注意 Apollo 文档警告默认实现不适合生产环境(详见 [此处](https://github.com/apollographql/graphql-subscriptions#getting-started-with-your-first-subscription) )。生产环境应用应使用由外部存储支持的 `PubSub` 实现(详见 [此处](https://github.com/apollographql/graphql-subscriptions#pubsub-implementations) )。 + + +:::warning 注意 + `PubSub` 是一个提供简单 `发布` 和 `订阅 API` 的类。了解更多信息请点击 [此处](https://www.apollographql.com/docs/graphql-subscriptions/setup.html) 。请注意 Apollo 文档警告默认实现不适合生产环境(详见 [此处](https://github.com/apollographql/graphql-subscriptions#getting-started-with-your-first-subscription) )。生产环境应用应使用由外部存储支持的 `PubSub` 实现(详见 [此处](https://github.com/apollographql/graphql-subscriptions#pubsub-implementations) )。 +::: 这将生成以下 GraphQL 模式定义语言(SDL)部分: @@ -78,7 +88,7 @@ subscribeToCommentAdded() { 现在,要发布事件,我们使用 `PubSub#publish` 方法。这通常在变更操作中使用,当对象图的部分发生改变时触发客户端更新。例如: -```typescript title="posts/posts.resolver" + ```typescript title="posts/posts.resolver.ts" @Mutation(() => Comment) async addComment( @Args('postId', { type: () => Int }) postId: number, @@ -127,7 +137,9 @@ commentAdded() { } ``` -> warning **注意** 如果使用 `resolve` 选项,应当返回未经包装的有效载荷(例如在我们的示例中,直接返回 `newComment` 对象,而非 `{ commentAdded: newComment }` 对象)。 +:::warning 注意 + 如果使用 `resolve` 选项,应当返回未经包装的有效载荷(例如在我们的示例中,直接返回 `newComment` 对象,而非 `{ commentAdded: newComment }` 对象)。 +::: 如需访问注入的提供程序(例如使用外部服务验证数据),请采用以下构造方式。 @@ -327,7 +339,9 @@ GraphQLModule.forRoot({ 本例中的 `authToken` 仅在连接首次建立时由客户端发送一次。使用此连接进行的所有订阅都将具有相同的 `authToken`,因此也具有相同的用户信息。 -> warning **注意** `subscriptions-transport-ws` 中存在一个漏洞,允许连接跳过 `onConnect` 阶段( [了解更多](https://github.com/apollographql/subscriptions-transport-ws/issues/349) )。不应假设用户开始订阅时已调用 `onConnect`,而应始终检查 `context` 是否已填充。 +:::warning 注意 + `subscriptions-transport-ws` 中存在一个漏洞,允许连接跳过 `onConnect` 阶段( [了解更多](https://github.com/apollographql/subscriptions-transport-ws/issues/349) )。不应假设用户开始订阅时已调用 `onConnect`,而应始终检查 `context` 是否已填充。 +::: 如果你使用的是 `graphql-ws` 包,`onConnect` 回调函数的签名会略有不同: @@ -361,7 +375,11 @@ GraphQLModule.forRoot({ }), ``` -> **提示** 你也可以传递选项对象来设置自定义发射器、验证传入连接等。更多信息请参阅 [此处](https://github.com/mercurius-js/mercurius/blob/master/docs/api/options.md#plugin-options) (参见 `subscription`)。 +:::info 提示 +你也可以传递选项对象来设置自定义发射器、验证传入连接等。更多信息请参阅 [此处](https://github.com/mercurius-js/mercurius/blob/master/docs/api/options.md#plugin-options) (参见 `subscription`)。 +::: + + #### 代码优先 @@ -380,9 +398,14 @@ export class AuthorResolver { } ``` -> info **注意** 上例中使用的所有装饰器都从 `@nestjs/graphql` 包导出,而 `PubSub` 类则从 `mercurius` 包导出。 +:::info 注意 +上例中使用的所有装饰器都从 `@nestjs/graphql` 包导出,而 `PubSub` 类则从 `mercurius` 包导出。 +::: + -> warning **注意** `PubSub` 是一个暴露简单 `publish` 和 `subscribe` API 的类。查看[本节](https://github.com/mercurius-js/mercurius/blob/master/docs/subscriptions.md#subscriptions-with-custom-pubsub)了解如何注册自定义 `PubSub` 类。 +:::warning 注意 + `PubSub` 是一个暴露简单 `publish` 和 `subscribe` API 的类。查看[本节](https://github.com/mercurius-js/mercurius/blob/master/docs/subscriptions.md#subscriptions-with-custom-pubsub)了解如何注册自定义 `PubSub` 类。 +::: 这将生成以下 GraphQL 模式定义语言(SDL)部分: @@ -409,7 +432,7 @@ subscribeToCommentAdded(@Context('pubsub') pubSub: PubSub) { 现在,要发布事件,我们使用 `PubSub#publish` 方法。这通常用于在对象图的某部分发生变更时,通过某个 mutation 触发客户端更新。例如: -```typescript title="posts/posts.resolver" + ```typescript title="posts/posts.resolver.ts" @Mutation(() => Comment) async addComment( @Args('postId', { type: () => Int }) postId: number, diff --git a/docs/graphql/unions-and-enums.md b/docs/graphql/unions-and-enums.md index a8a4ee3..8695845 100644 --- a/docs/graphql/unions-and-enums.md +++ b/docs/graphql/unions-and-enums.md @@ -37,7 +37,11 @@ export const ResultUnion = createUnionType({ }); ``` -> **警告** 必须为 `createUnionType` 函数的 `types` 属性返回的数组添加 const 断言。如果未添加 const 断言,编译时将生成错误的声明文件,在其他项目中使用时会导致错误。 +:::warning 警告 +必须为 `createUnionType` 函数的 `types` 属性返回的数组添加 const 断言。如果未添加 const 断言,编译时将生成错误的声明文件,在其他项目中使用时会导致错误。 +::: + + 现在,我们可以在查询中引用 `ResultUnion`: @@ -134,7 +138,9 @@ export class ResultUnionResolver { } ``` -> info 所有装饰器均从 `@nestjs/graphql` 包中导出。 +:::info 提示 +所有装饰器均从 `@nestjs/graphql` 包中导出。 +::: ### 枚举 @@ -252,7 +258,9 @@ export const allowedColorResolver: Record = { }; ``` -> info 所有装饰器均从 `@nestjs/graphql` 包中导出。 +:::info 提示 +所有装饰器均从 `@nestjs/graphql` 包中导出。 +::: 然后将此解析器对象与 `GraphQLModule#forRoot()` 方法的 `resolvers` 属性一起使用,如下所示: diff --git a/docs/guide/index.md b/docs/guide/index.md index 63baaad..6003226 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -59,7 +59,7 @@ You can use the `:::` syntax to create custom containers and support custom titl **Input:** ```markdown -:::tip +:::info This is a `block` of type `tip` ::: ``` @@ -80,18 +80,18 @@ This is a `block` of type `danger` This is a `block` of type `details` ::: -:::tip Custom Title +:::info Custom Title This is a `block` of `Custom Title` ::: -:::tip{title="Custom Title"} +:::info "Custom Title" This is a `block` of `Custom Title` ::: ``` **Output:** -:::tip +:::info This is a `block` of type `tip` ::: @@ -111,11 +111,11 @@ This is a `block` of type `danger` This is a `block` of type `details` ::: -:::tip Custom Title +:::info Custom Title This is a `block` of `Custom Title` ::: -:::tip{title="Custom Title"} +:::info "Custom Title" This is a `block` of `Custom Title` ::: diff --git a/docs/introduction.md b/docs/introduction.md index 9716a0e..51882e6 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,8 +1,16 @@ # 介绍 -> **关于本文档**:这是 NestJS 官方文档的中文翻译版本。文档源码托管在 [GitHub](https://github.com/nestcn/docs.nestjs.cn),欢迎 Star、Fork 和贡献翻译!如发现翻译问题或需要改进,请提交 Issue 或 Pull Request。 -> -> **旧版文档**:如需访问旧版文档,请点击 [这里](https://25650abe.nestjs.pages.dev/)。 +:::info 关于本文档 +这是 NestJS 官方文档的中文翻译版本。文档源码托管在 [GitHub](https://github.com/nestcn/docs.nestjs.cn),欢迎 Star、Fork 和贡献翻译!如发现翻译问题或需要改进,请提交 Issue 或 Pull Request。 +::: + + + +:::info 旧版文档 +如需访问旧版文档,请点击 [这里](https://25650abe.nestjs.pages.dev/)。 +::: + + Nest(NestJS)是一个用于构建高效、可扩展的 [Node.js](https://nodejs.org/) 服务端应用的框架。它采用渐进式 JavaScript,使用 [TypeScript](http://www.typescriptlang.org/) 构建并全面支持 TypeScript(同时仍允许开发者使用纯 JavaScript 编码),融合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数响应式编程)的元素。 @@ -25,7 +33,7 @@ Nest 提供了一套开箱即用的应用架构,使开发者和团队能够创 - **Node.js**:版本 ≥20(推荐使用最新 LTS 版本) - **包管理器**:npm(Node.js 自带)、yarn 或 pnpm -:::tip 提示 +:::info 提示 推荐使用 [nvm](https://github.com/nvm-sh/nvm)(Linux/macOS)或 [nvm-windows](https://github.com/coreybutler/nvm-windows)(Windows)来管理 Node.js 版本,这样可以方便地在不同项目间切换 Node.js 版本。 ::: diff --git a/docs/microservices/basics.md b/docs/microservices/basics.md index 195c28a..536fbf9 100644 --- a/docs/microservices/basics.md +++ b/docs/microservices/basics.md @@ -20,7 +20,7 @@ $ npm i --save @nestjs/microservices 要实例化微服务,请使用 `NestFactory` 类的 `createMicroservice()` 方法: -```typescript title="main" + ```typescript title="main.ts" import { NestFactory } from '@nestjs/core'; import { Transport, MicroserviceOptions } from '@nestjs/microservices'; import { AppModule } from './app.module'; @@ -37,7 +37,10 @@ async function bootstrap() { bootstrap(); ``` -> info **注意** 微服务默认使用 **TCP** 传输层。 +:::info 注意 +微服务默认使用 **TCP** 传输层。 +::: + `createMicroservice()` 方法的第二个参数是一个 `options` 对象,该对象可能包含两个成员: @@ -93,7 +96,9 @@ bootstrap(); -> info **提示** 上述属性为 TCP 传输器特有。如需了解其他传输器的可用选项,请参阅相关章节。 +:::info 提示 +上述属性为 TCP 传输器特有。如需了解其他传输器的可用选项,请参阅相关章节。 +::: #### 消息与事件模式 @@ -107,7 +112,7 @@ bootstrap(); 要创建基于请求-响应模式的消息处理器,请使用从 `@nestjs/microservices` 包导入的 `@MessagePattern()` 装饰器。该装饰器应仅在[控制器](../overview/controllers)类中使用,因为它们是应用程序的入口点。在提供者中使用它将不会产生任何效果,因为它们会被 Nest 运行时忽略。 -```typescript title="math.controller" + ```typescript title="math.controller.ts" import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; @@ -159,7 +164,11 @@ async handleUserCreated(data: Record) { } ``` -> **提示** 您可以为**同一个**事件模式注册多个事件处理器,它们都将被自动并行触发。 +:::info 提示 +您可以为**同一个**事件模式注册多个事件处理器,它们都将被自动并行触发。 +::: + + `handleUserCreated()` **事件处理器**监听 `'user_created'` 事件。该处理器接收单个参数——来自客户端的`数据` (本例中是通过网络发送的事件载荷)。 @@ -175,9 +184,13 @@ getDate(@Payload() data: number[], @Ctx() context: NatsContext) { } ``` -> info **提示**`@Payload()`、`@Ctx()` 和 `NatsContext` 是从 `@nestjs/microservices` 导入的。 +:::info 提示 +`@Payload()`、`@Ctx()` 和 `NatsContext` 是从 `@nestjs/microservices` 导入的。 +::: -> info **提示** 您还可以向 `@Payload()` 装饰器传入一个属性键,以从传入的负载对象中提取特定属性,例如 `@Payload('id')`。 +:::info 提示 +您还可以向 `@Payload()` 装饰器传入一个属性键,以从传入的负载对象中提取特定属性,例如 `@Payload('id')`。 +::: #### 客户端(生产者类) @@ -229,7 +242,9 @@ constructor( ) {} ``` -> info **提示** `ClientsModule` 和 `ClientProxy` 类是从 `@nestjs/microservices` 包中导入的。 +:::info 提示 +`ClientsModule` 和 `ClientProxy` 类是从 `@nestjs/microservices` 包中导入的。 +::: 有时,您可能需要从其他服务(如 `ConfigService`)获取传输器配置,而不是在客户端应用中硬编码。为此,您可以使用 `ClientProxyFactory` 类注册[自定义提供者](/fundamentals/custom-providers) 。该类提供了静态方法 `create()`,该方法接收传输器选项对象并返回一个定制化的 `ClientProxy` 实例。 @@ -249,7 +264,9 @@ constructor( }) ``` -> info **提示** `ClientProxyFactory` 是从 `@nestjs/microservices` 包导入的。 +:::info 提示 +`ClientProxyFactory` 是从 `@nestjs/microservices` 包导入的。 +::: 另一种选择是使用 `@Client()` 属性装饰器。 @@ -258,7 +275,9 @@ constructor( client: ClientProxy; ``` -> info **提示** `@Client()` 装饰器是从 `@nestjs/microservices` 包导入的。 +:::info 提示 +`@Client()` 装饰器是从 `@nestjs/microservices` 包导入的。 +::: 使用 `@Client()` 装饰器并非首选技术方案,因为这会增加测试难度且难以共享客户端实例。 @@ -302,7 +321,7 @@ async publish() { 对于来自不同编程语言背景的开发者来说,可能会惊讶地发现:在 Nest 中,大多数内容都是在传入请求间共享的。这包括数据库连接池、具有全局状态的单例服务等。需要注意的是,Node.js 并不遵循请求/响应多线程无状态模型(即每个请求由独立线程处理)。因此,在我们的应用中使用单例实例是**安全**的。 -然而,在某些边缘情况下,可能需要基于请求的生命周期来管理处理器。这包括诸如 GraphQL 应用中的按请求缓存、请求追踪或多租户等场景。您可[在此](/fundamentals/injection-scopes)了解更多关于控制作用域的方法。 +然而,在某些边缘情况下,可能需要基于请求的生命周期来管理处理器。这包括诸如 GraphQL 应用中的按请求缓存、请求追踪或多租户等场景。您可[在此](/fundamentals/provider-scopes)了解更多关于控制作用域的方法。 请求作用域的处理器和提供者可以通过结合使用 `@Inject()` 装饰器与 `CONTEXT` 令牌来注入 `RequestContext`: @@ -337,7 +356,11 @@ this.client.status.subscribe((status: TcpStatus) => { }); ``` -> **提示** `TcpStatus` 类型是从 `@nestjs/microservices` 包导入的。 +:::info 提示 +`TcpStatus` 类型是从 `@nestjs/microservices` 包导入的。 +::: + + 同样地,您可以订阅服务器的 `status` 流来接收有关服务器状态的通知。 @@ -366,7 +389,11 @@ server.on('error', (err) => { }); ``` -> **提示** `TcpEvents` 类型是从 `@nestjs/microservices` 包中导入的。 +:::info 提示 +`TcpEvents` 类型是从 `@nestjs/microservices` 包中导入的。 +::: + + #### 底层驱动访问 @@ -398,7 +425,9 @@ this.client .pipe(timeout(5000)); ``` -> info **提示** `timeout` 操作符是从 `rxjs/operators` 包中导入的。 +:::info 提示 +`timeout` 操作符是从 `rxjs/operators` 包中导入的。 +::: 如果微服务在 5 秒内未响应,将抛出错误。 diff --git a/docs/microservices/custom-transport.md b/docs/microservices/custom-transport.md index e875c48..fa3a3d5 100644 --- a/docs/microservices/custom-transport.md +++ b/docs/microservices/custom-transport.md @@ -2,11 +2,16 @@ Nest 提供了多种开箱即用的**传输器** ,同时提供了允许开发者构建新自定义传输策略的 API。传输器让你能够通过可插拔的通信层和非常简单的应用级消息协议(阅读完整[文章](https://dev.to/nestjs/integrate-nestjs-with-external-services-using-microservice-transporters-part-1-p3) )在网络中连接组件。 -> info **注意** 使用 Nest 构建微服务并不一定意味着你必须使用 `@nestjs/microservices` 包。例如,如果你需要与外部服务通信(比如用其他语言编写的微服务),可能并不需要 `@nestjs/microservice` 库提供的所有功能。实际上,如果你不需要通过装饰器(`@EventPattern` 或 `@MessagePattern`)来声明式定义订阅者,运行一个[独立应用](/application-context)并手动维护连接/订阅通道对大多数用例来说已经足够,还能提供更大的灵活性。 +:::info 注意 +使用 Nest 构建微服务并不一定意味着你必须使用 `@nestjs/microservices` 包。例如,如果你需要与外部服务通信(比如用其他语言编写的微服务),可能并不需要 `@nestjs/microservice` 库提供的所有功能。实际上,如果你不需要通过装饰器(`@EventPattern` 或 `@MessagePattern`)来声明式定义订阅者,运行一个[独立应用](/application-context)并手动维护连接/订阅通道对大多数用例来说已经足够,还能提供更大的灵活性。 +::: + 通过自定义传输器,您可以集成任何消息系统/协议(包括 Google Cloud Pub/Sub、Amazon Kinesis 等),或在现有基础上扩展功能(例如为 MQTT 添加 [QoS](https://github.com/mqttjs/MQTT.js/blob/master/README.md#qos))。 -> info **建议** 为了更好地理解 Nest 微服务的工作原理以及如何扩展现有传输器的功能,我们推荐阅读 [《NestJS 微服务实战》](https://dev.to/johnbiundo/series/4724) 和 [《NestJS 高级微服务》](https://dev.to/nestjs/part-1-introduction-and-setup-1a2l) 系列文章。 +:::info 建议 + 为了更好地理解 Nest 微服务的工作原理以及如何扩展现有传输器的功能,我们推荐阅读 [《NestJS 微服务实战》](https://dev.to/johnbiundo/series/4724) 和 [《NestJS 高级微服务》](https://dev.to/nestjs/part-1-introduction-and-setup-1a2l) 系列文章。 +::: #### 创建策略 @@ -51,7 +56,9 @@ class GoogleCloudPubSubServer } ``` -> warning **注意** 请注意,本章节不会实现一个功能完整的 Google Cloud Pub/Sub 服务器,因为这需要深入探讨传输器相关的技术细节。 +:::warning 注意 + 请注意,本章节不会实现一个功能完整的 Google Cloud Pub/Sub 服务器,因为这需要深入探讨传输器相关的技术细节。 +::: 在上述示例中,我们声明了 `GoogleCloudPubSubServer` 类,并提供了由 `CustomTransportStrategy` 接口强制要求的 `listen()` 和 `close()` 方法。此外,我们的类继承自 `@nestjs/microservices` 包导入的 `Server` 类,该类提供了一些实用方法,例如 Nest 运行时用于注册消息处理器的方法。或者,如果您想扩展现有传输策略的功能,可以继承对应的服务器类,例如 `ServerRedis`。按照惯例,我们为类添加了 `"Server"` 后缀,因为它将负责订阅消息/事件(并在必要时响应它们)。 @@ -94,7 +101,9 @@ listen(callback: () => void) { Map { 'echo' => [AsyncFunction] { isEventHandler: false } } ``` -> info **提示** 如果我们使用 `@EventPattern` 装饰器,您会看到相同的输出,但 `isEventHandler` 属性会被设置为 `true`。 +:::info 提示 +如果我们使用 `@EventPattern` 装饰器,您会看到相同的输出,但 `isEventHandler` 属性会被设置为 `true`。 +::: 如您所见,`messageHandlers` 属性是一个包含所有消息(和事件)处理器的 `Map` 集合,其中模式被用作键。现在,您可以使用键(例如 `"echo"`)来获取消息处理器的引用: @@ -133,7 +142,11 @@ async listen(callback: () => void) { 正如我们在第一节中提到的,您不一定需要使用 `@nestjs/microservices` 包来创建微服务,但如果决定这样做且需要集成自定义策略,您还需要提供一个"客户端"类。 -> **提示** 再次说明,要实现一个与所有 `@nestjs/microservices` 功能(例如流式传输)兼容的全功能客户端类,需要深入理解框架使用的通信技术。了解更多信息,请查看这篇[文章](https://dev.to/nestjs/part-4-basic-client-component-16f9) 。 +:::info 提示 +再次说明,要实现一个与所有 `@nestjs/microservices` 功能(例如流式传输)兼容的全功能客户端类,需要深入理解框架使用的通信技术。了解更多信息,请查看这篇[文章](https://dev.to/nestjs/part-4-basic-client-component-16f9) 。 +::: + + 要与外部服务通信/发送和发布消息(或事件),您可以使用特定库的 SDK 包,或者实现一个继承自 `ClientProxy` 的自定义客户端类,如下所示: @@ -154,7 +167,9 @@ class GoogleCloudPubSubClient extends ClientProxy { } ``` -> warning **注意** 请注意,本章节不会实现一个功能完整的 Google Cloud Pub/Sub 客户端,因为这需要深入探讨传输器相关的技术细节。 +:::warning 注意 + 请注意,本章节不会实现一个功能完整的 Google Cloud Pub/Sub 客户端,因为这需要深入探讨传输器相关的技术细节。 +::: 如你所见,`ClientProxy` 类要求我们提供多个方法用于建立和关闭连接、发布消息(`publish`)和事件(`dispatchEvent`)。注意,如果不需要请求-响应式的通信风格支持,可以将 `publish()` 方法留空。同样地,如果不需要支持基于事件的通信,可以跳过 `dispatchEvent()` 方法。 @@ -224,7 +239,9 @@ googlePubSubClient ); ``` -> info **提示** `timeout` 操作符是从 `rxjs/operators` 包中导入的。 +:::info 提示 +`timeout` 操作符是从 `rxjs/operators` 包中导入的。 +::: 应用 `timeout` 操作符后,您的终端输出应如下所示: @@ -252,7 +269,7 @@ event to dispatch: { pattern: 'event', data: 'Hello world!' } 若需在客户端围绕响应序列化添加自定义逻辑,可创建一个继承自 `ClientProxy` 或其子类的自定义类。要修改成功请求,可重写 `serializeResponse` 方法;若要修改经此客户端的所有错误,可重写 `serializeError` 方法。使用此自定义类时,可通过 `customClass` 属性将类本身传入 `ClientsModule.register()` 方法。以下是将每个错误序列化为 `RpcException` 的自定义 `ClientProxy` 示例。 -```typescript title="error-handling.proxy" + ```typescript title="error-handling.proxy.ts" import { ClientTcp, RpcException } from '@nestjs/microservices'; class ErrorHandlingProxy extends ClientTCP { @@ -264,7 +281,7 @@ class ErrorHandlingProxy extends ClientTCP { 然后在 `ClientsModule` 中这样使用: -```typescript title="app.module" + ```typescript title="app.module.ts" @Module({ imports: [ ClientsModule.register([{ @@ -276,4 +293,7 @@ class ErrorHandlingProxy extends ClientTCP { export class AppModule ``` -> info **注意** 这里传入 `customClass` 的是类本身而非类的实例。Nest 会在底层自动创建实例,并将提供给 `options` 属性的所有配置传递给新建的 `ClientProxy`。 +:::info 注意 +这里传入 `customClass` 的是类本身而非类的实例。Nest 会在底层自动创建实例,并将提供给 `options` 属性的所有配置传递给新建的 `ClientProxy`。 +::: + diff --git a/docs/microservices/exception-filters.md b/docs/microservices/exception-filters.md index 15c1c16..f19e8bb 100644 --- a/docs/microservices/exception-filters.md +++ b/docs/microservices/exception-filters.md @@ -6,7 +6,11 @@ HTTP [异常过滤器](/exception-filters)层与对应微服务层的唯一区 throw new RpcException('Invalid credentials.'); ``` -> **提示** `RpcException` 类是从 `@nestjs/microservices` 包导入的。 +:::info 提示 +`RpcException` 类是从 `@nestjs/microservices` 包导入的。 +::: + + 使用上述示例时,Nest 将处理抛出的异常并返回具有以下结构的 `error` 对象: @@ -21,7 +25,7 @@ throw new RpcException('Invalid credentials.'); 微服务异常过滤器的行为与 HTTP 异常过滤器类似,只有一个小区别。`catch()` 方法必须返回一个 `Observable`。 -```typescript title="rpc-exception.filter" + ```typescript title="rpc-exception.filter.ts" import { Catch, RpcExceptionFilter, ArgumentsHost } from '@nestjs/common'; import { Observable, throwError } from 'rxjs'; import { RpcException } from '@nestjs/microservices'; @@ -34,7 +38,9 @@ export class ExceptionFilter implements RpcExceptionFilter { } ``` -> warning **警告** 使用[混合应用](/faq/hybrid-application)时,全局微服务异常过滤器默认未启用。 +:::warning 警告 +使用[混合应用](/faq/hybrid-application)时,全局微服务异常过滤器默认未启用。 +::: 以下示例使用了手动实例化的方法作用域过滤器。与基于 HTTP 的应用类似,您也可以使用控制器作用域过滤器(即在控制器类前添加 `@UseFilters()` 装饰器)。 diff --git a/docs/microservices/grpc.md b/docs/microservices/grpc.md index e13daf8..fdaffd5 100644 --- a/docs/microservices/grpc.md +++ b/docs/microservices/grpc.md @@ -18,7 +18,7 @@ $ npm i --save @grpc/grpc-js @grpc/proto-loader 与其他 Nest 微服务传输层实现类似,您可以通过传递给 `createMicroservice()` 方法的选项对象中的 `transport` 属性来选择 gRPC 传输机制。在以下示例中,我们将设置一个英雄服务。`options` 属性提供了有关该服务的元数据;其属性描述见[下文](microservices/grpc#选项) 。 -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.GRPC, options: { @@ -28,7 +28,9 @@ const app = await NestFactory.createMicroservice(AppModule, }); ``` -> info **提示** `join()` 函数是从 `path` 包导入的;`Transport` 枚举是从 `@nestjs/microservices` 包导入的。 +:::info 提示 +`join()` 函数是从 `path` 包导入的;`Transport` 枚举是从 `@nestjs/microservices` 包导入的。 +::: 在 `nest-cli.json` 文件中,我们添加了 `assets` 属性以允许分发非 TypeScript 文件,以及 `watchAssets` 属性用于开启对所有非 TypeScript 资源的监视。在本例中,我们希望 `.proto` 文件能自动复制到 `dist` 文件夹。 @@ -82,9 +84,11 @@ message Hero { 接下来我们需要实现该服务。为了定义一个满足此要求的处理程序,我们在控制器中使用 `@GrpcMethod()` 装饰器,如下所示。该装饰器提供了将方法声明为 gRPC 服务方法所需的元数据。 -> info **提示** 在前面的微服务章节中介绍的 `@MessagePattern()` 装饰器( [了解更多](microservices/basics#请求-响应) )不适用于基于 gRPC 的微服务。对于基于 gRPC 的微服务,`@GrpcMethod()` 装饰器有效地取代了它的位置。 +:::info 提示 +在前面的微服务章节中介绍的 `@MessagePattern()` 装饰器( [了解更多](microservices/basics#请求-响应) )不适用于基于 gRPC 的微服务。对于基于 gRPC 的微服务,`@GrpcMethod()` 装饰器有效地取代了它的位置。 +::: -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" @Controller() export class HeroesController { @GrpcMethod('HeroesService', 'FindOne') @@ -98,7 +102,9 @@ export class HeroesController { } ``` -> info **提示** `@GrpcMethod()` 装饰器是从 `@nestjs/microservices` 包导入的,而 `Metadata` 和 `ServerUnaryCall` 则来自 `grpc` 包。 +:::info 提示 +`@GrpcMethod()` 装饰器是从 `@nestjs/microservices` 包导入的,而 `Metadata` 和 `ServerUnaryCall` 则来自 `grpc` 包。 +::: 上述装饰器接收两个参数。第一个是服务名称(例如 `'HeroesService'`),对应 `hero.proto` 文件中的 `HeroesService` 服务定义。第二个参数(字符串 `'FindOne'`)对应 `hero.proto` 文件里 `HeroesService` 服务中定义的 `FindOne()` rpc 方法。 @@ -106,7 +112,7 @@ export class HeroesController { `@GrpcMethod()` 装饰器的两个参数都是可选的。如果调用时不传第二个参数(例如 `'FindOne'`),Nest 会根据处理方法名自动将其转换为大驼峰命名(例如将 `findOne` 处理方法关联到 `FindOne` rpc 调用定义)来关联 `.proto` 文件中的 rpc 方法。如下所示。 -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" @Controller() export class HeroesController { @GrpcMethod('HeroesService') @@ -122,7 +128,7 @@ export class HeroesController { 你也可以省略第一个 `@GrpcMethod()` 参数。在这种情况下,Nest 会根据定义处理程序的 **class** 名称,自动将该处理程序与 proto 定义文件中的服务定义关联起来。例如,在以下代码中,类 `HeroesService` 会基于名称 `'HeroesService'` 的匹配,将其处理程序方法与 `hero.proto` 文件中的 `HeroesService` 服务定义相关联。 -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" @Controller() export class HeroesService { @GrpcMethod() @@ -157,7 +163,11 @@ imports: [ ]; ``` -> **提示** `register()` 方法接收对象数组作为参数。通过提供以逗号分隔的注册对象列表,可同时注册多个服务包。 +:::info 提示 +`register()` 方法接收对象数组作为参数。通过提供以逗号分隔的注册对象列表,可同时注册多个服务包。 +::: + + 注册完成后,我们可以通过 `@Inject()` 注入配置好的 `ClientGrpc` 对象。然后使用该对象的 `getService()` 方法获取服务实例,如下所示。 @@ -222,7 +232,7 @@ interface HeroesService { 消息处理器也可以返回一个 `Observable`,在这种情况下,结果值将持续发射直到流完成。 -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" @Get() call(): Observable { return this.heroesService.findOne({ id: 1 }); @@ -240,7 +250,9 @@ call(): Observable { } ``` -> info **提示** `Metadata` 类是从 `grpc` 包中导入的。 +:::info 提示 +`Metadata` 类是从 `grpc` 包中导入的。 +::: 请注意,这将需要我们更新之前几步中定义的 `HeroesService` 接口。 @@ -260,7 +272,7 @@ $ npm i --save @grpc/reflection 然后可以通过 gRPC 服务器选项中的 `onLoadPackageDefinition` 钩子将其集成到 gRPC 服务器,如下所示: -```typescript title="main" + ```typescript title="main.ts" import { ReflectionService } from '@grpc/reflection'; const app = await NestFactory.createMicroservice(AppModule, { @@ -328,7 +340,9 @@ interface HelloResponse { } ``` -> info **提示** proto 接口可以通过 [ts-proto](https://github.com/stephenh/ts-proto) 包自动生成,了解更多 [here](https://github.com/stephenh/ts-proto/blob/main/NESTJS.markdown)。 +:::info 提示 +proto 接口可以通过 [ts-proto](https://github.com/stephenh/ts-proto) 包自动生成,了解更多 [here](https://github.com/stephenh/ts-proto/blob/main/NESTJS.markdown)。 +::: #### 主题策略 @@ -356,9 +370,13 @@ bidiHello(messages: Observable, metadata: Metadata, call: ServerDuplexStrea } ``` -> warning **警告** 为了支持与 `@GrpcStreamMethod()` 装饰器的全双工交互,控制器方法必须返回一个 RxJS 的 `Observable` 对象。 +:::warning 警告 +为了支持与 `@GrpcStreamMethod()` 装饰器的全双工交互,控制器方法必须返回一个 RxJS 的 `Observable` 对象。 +::: -> info **提示** `Metadata` 和 `ServerUnaryCall` 类/接口是从 `grpc` 包中导入的。 +:::info 提示 +`Metadata` 和 `ServerUnaryCall` 类/接口是从 `grpc` 包中导入的。 +::: 根据服务定义(在 `.proto` 文件中),`BidiHello` 方法应该向服务端流式传输请求。为了从客户端向流发送多个异步消息,我们利用了 RxJS 的 `ReplaySubject` 类。 @@ -395,7 +413,10 @@ bidiHello(requestStream: any) { } ``` -> info **注意** 该装饰器不需要提供任何特定的返回参数。预期该流将像处理其他标准流类型一样被处理。 +:::info 注意 +该装饰器不需要提供任何特定的返回参数。预期该流将像处理其他标准流类型一样被处理。 +::: + 在上面的示例中,我们使用了 `write()` 方法将对象写入响应流。作为第二个参数传入 `.on()` 方法的回调函数会在服务每次接收到新数据块时被调用。 @@ -425,7 +446,7 @@ $ npm i --save grpc-health-check 随后可通过 gRPC 服务器选项中的 `onLoadPackageDefinition` 钩子将其集成到 gRPC 服务中,如下所示。注意 `protoPath` 需同时包含健康检查与 hero 包的定义。 -```typescript title="main" + ```typescript title="main.ts" import { HealthImplementation, protoPath as healthCheckProtoPath } from 'grpc-health-check'; const app = await NestFactory.createMicroservice(AppModule, { @@ -446,7 +467,11 @@ const app = await NestFactory.createMicroservice(AppModule, }); ``` -> **提示** [gRPC 健康探针](https://github.com/grpc-ecosystem/grpc-health-probe)是一个实用的 CLI 工具,可用于容器化环境中测试 gRPC 健康检查。 +:::info 提示 +[gRPC 健康探针](https://github.com/grpc-ecosystem/grpc-health-probe)是一个实用的 CLI 工具,可用于容器化环境中测试 gRPC 健康检查。 +::: + + #### gRPC 元数据 @@ -456,7 +481,7 @@ const app = await NestFactory.createMicroservice(AppModule, 要从处理程序返回元数据,请使用 `ServerUnaryCall#sendMetadata()` 方法(处理程序的第三个参数)。 -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" @Controller() export class HeroesService { @GrpcMethod() diff --git a/docs/microservices/guards.md b/docs/microservices/guards.md index 777f0b3..98881d6 100644 --- a/docs/microservices/guards.md +++ b/docs/microservices/guards.md @@ -2,7 +2,10 @@ 微服务守卫与[常规 HTTP 应用守卫](/guards)没有本质区别。唯一的不同在于,你应该使用 `RpcException` 而不是抛出 `HttpException`。 -> info **注意** `RpcException` 类是从 `@nestjs/microservices` 包中导出的。 +:::info 注意 +`RpcException` 类是从 `@nestjs/microservices` 包中导出的。 +::: + #### 绑定守卫 diff --git a/docs/microservices/index.md b/docs/microservices/index.md index ed03f80..90b9e1c 100644 --- a/docs/microservices/index.md +++ b/docs/microservices/index.md @@ -75,7 +75,11 @@ async function bootstrap() { bootstrap(); ``` -> **提示** 微服务默认使用 TCP 传输层。 +:::info 提示 +微服务默认使用 TCP 传输层。 +::: + + ### 消息处理器 diff --git a/docs/microservices/kafka.md b/docs/microservices/kafka.md index aeeac0c..6230ec8 100644 --- a/docs/microservices/kafka.md +++ b/docs/microservices/kafka.md @@ -20,7 +20,7 @@ $ npm i --save kafkajs 与其他 Nest 微服务传输层实现类似,您可以通过传递给 `createMicroservice()` 方法的选项对象中的 `transport` 属性来选择 Kafka 传输机制,同时还可使用可选的 `options` 属性,如下所示: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.KAFKA, options: { @@ -31,7 +31,11 @@ const app = await NestFactory.createMicroservice(AppModule, }); ``` -> **提示** `Transport` 枚举是从 `@nestjs/microservices` 包中导入的。 +:::info 提示 +`Transport` 枚举是从 `@nestjs/microservices` 包中导入的。 +::: + + #### 选项 @@ -113,11 +117,13 @@ Kafka 微服务消息模式利用两个主题分别处理请求和回复通道 #### 消息响应订阅 -> warning **注意** 本节仅适用于使用[请求-响应](/microservices/basics#请求-响应)消息模式的情况(配合 `@MessagePattern` 装饰器和 `ClientKafkaProxy#send` 方法)。对于[基于事件](/microservices/basics#基于事件)的通信方式(使用 `@EventPattern` 装饰器和 `ClientKafkaProxy#emit` 方法),则无需订阅响应主题。 +:::warning 注意 +本节仅适用于使用[请求-响应](/microservices/basics#请求-响应)消息模式的情况(配合 `@MessagePattern` 装饰器和 `ClientKafkaProxy#send` 方法)。对于[基于事件](/microservices/basics#基于事件)的通信方式(使用 `@EventPattern` 装饰器和 `ClientKafkaProxy#emit` 方法),则无需订阅响应主题。 +::: `ClientKafkaProxy` 类提供了 `subscribeToResponseOf()` 方法。该方法以请求主题名称作为参数,并将派生的回复主题名称添加到回复主题集合中。在实现消息模式时必须调用此方法。 -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" onModuleInit() { this.client.subscribeToResponseOf('hero.kill.dragon'); } @@ -125,7 +131,7 @@ onModuleInit() { 如果 `ClientKafkaProxy` 实例是异步创建的,则必须在调用 `connect()` 方法之前调用 `subscribeToResponseOf()` 方法。 -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" async onModuleInit() { this.client.subscribeToResponseOf('hero.kill.dragon'); await this.client.connect(); @@ -140,7 +146,7 @@ Nest 接收传入的 Kafka 消息时,会将其作为一个包含 `key`、`valu Nest 在发布事件或发送消息时,会通过序列化过程发送传出的 Kafka 消息。该过程会对传入 `ClientKafkaProxy` 的 `emit()` 和 `send()` 方法的参数,或从 `@MessagePattern` 方法返回的值进行序列化。此序列化过程会通过 `JSON.stringify()` 或原型方法 `toString()` 将非字符串或缓冲区的对象"字符串化"。 -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" @Controller() export class HeroesController { @MessagePattern('hero.kill.dragon') @@ -155,11 +161,14 @@ export class HeroesController { } ``` -> info **注意** `@Payload()` 需从 `@nestjs/microservices` 包中导入。 +:::info 注意 +`@Payload()` 需从 `@nestjs/microservices` 包中导入。 +::: + 传出消息也可以通过传递包含 `key` 和 `value` 属性的对象进行键控。消息键控对于满足[共同分区要求](https://docs.confluent.io/current/ksql/docs/developer-guide/partition-data.html#co-partitioning-requirements)非常重要。 -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" @Controller() export class HeroesController { @MessagePattern('hero.kill.dragon') @@ -186,7 +195,7 @@ export class HeroesController { 此外,以此格式传递的消息还可以包含设置在 `headers` 哈希属性中的自定义标头。标头哈希属性值必须是 `string` 类型或 `Buffer` 类型。 -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" @Controller() export class HeroesController { @MessagePattern('hero.kill.dragon') @@ -228,7 +237,9 @@ killDragon(@Payload() message: KillDragonMessage, @Ctx() context: KafkaContext) } ``` -> info **提示**`@Payload()`、`@Ctx()` 和 `KafkaContext` 都是从 `@nestjs/microservices` 包导入的。 +:::info 提示 +`@Payload()`、`@Ctx()` 和 `KafkaContext` 都是从 `@nestjs/microservices` 包导入的。 +::: 要访问原始的 Kafka `IncomingMessage` 对象,请使用 `KafkaContext` 对象的 `getMessage()` 方法,如下所示: @@ -279,7 +290,7 @@ async killDragon(@Payload() message: KillDragonMessage, @Ctx() context: KafkaCon Kafka 微服务组件会在 `client.clientId` 和 `consumer.groupId` 选项后附加各自角色描述,以防止 Nest 微服务客户端与服务器组件之间发生冲突。默认情况下,`ClientKafkaProxy` 组件会附加 `-client`,而 `ServerKafka` 组件会附加 `-server` 到这两个选项中。请注意下方提供的值是如何按此方式转换的(如注释所示)。 -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.KAFKA, options: { @@ -296,7 +307,7 @@ const app = await NestFactory.createMicroservice(AppModule, 对于客户端: -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" @Client({ transport: Transport.KAFKA, options: { @@ -312,23 +323,29 @@ const app = await NestFactory.createMicroservice(AppModule, client: ClientKafkaProxy; ``` -> info **提示** Kafka 客户端和消费者的命名规则可以通过在自定义提供程序中扩展 `ClientKafkaProxy` 和 `KafkaServer` 并重写构造函数来自定义。 +:::info 提示 +Kafka 客户端和消费者的命名规则可以通过在自定义提供程序中扩展 `ClientKafkaProxy` 和 `KafkaServer` 并重写构造函数来自定义。 +::: 由于 Kafka 微服务消息模式使用两个主题分别处理请求和回复通道,回复模式应从请求主题派生。默认情况下,回复主题的名称由请求主题名称与附加的 `.reply` 组合而成。 -```typescript title="heroes.controller" + ```typescript title="heroes.controller.ts" onModuleInit() { this.client.subscribeToResponseOf('hero.get'); // hero.get.reply } ``` -> info **提示** Kafka 回复主题的命名规则可以通过在自定义提供程序中扩展 `ClientKafkaProxy` 并重写 `getResponsePatternName` 方法来自定义。 +:::info 提示 +Kafka 回复主题的命名规则可以通过在自定义提供程序中扩展 `ClientKafkaProxy` 并重写 `getResponsePatternName` 方法来自定义。 +::: #### 可重试异常 与其他传输器类似,所有未处理的异常都会被自动包装成 `RpcException` 并转换为"用户友好"格式。但在某些边缘情况下,您可能希望绕过此机制,让异常由 `kafkajs` 驱动程序直接处理。在处理消息时抛出异常会指示 `kafkajs` **重试**该消息(重新投递),这意味着即使消息(或事件)处理程序已被触发,偏移量也不会提交到 Kafka。 -> warning **注意** 对于事件处理程序(基于事件的通信),默认情况下所有未处理异常都被视为**可重试异常** 。 +:::warning 注意 + 对于事件处理程序(基于事件的通信),默认情况下所有未处理异常都被视为**可重试异常** 。 +::: 为此,您可以使用名为 `KafkaRetriableException` 的专用类,如下所示: @@ -336,7 +353,10 @@ onModuleInit() { throw new KafkaRetriableException('...'); ``` -> info:**KafkaRetriableException** 类是从 `@nestjs/microservices` 包中导出的。 +:::info 提示 +**KafkaRetriableException** 类是从 `@nestjs/microservices` 包中导出的。 +::: + ### 自定义异常处理 @@ -460,7 +480,7 @@ async handleUserCreated(@Payload() data: IncomingMessage, @Ctx() context: KafkaC 要禁用消息自动提交,请在 `run` 配置中设置 `autoCommit: false`,如下所示: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.KAFKA, options: { @@ -484,7 +504,11 @@ this.client.status.subscribe((status: KafkaStatus) => { }); ``` -> **提示** `KafkaStatus` 类型是从 `@nestjs/microservices` 包导入的。 +:::info 提示 +`KafkaStatus` 类型是从 `@nestjs/microservices` 包导入的。 +::: + + 同样地,您可以订阅服务器的 `status` 流来接收有关服务器状态的通知。 diff --git a/docs/microservices/mqtt.md b/docs/microservices/mqtt.md index 4598e5c..8be69b4 100644 --- a/docs/microservices/mqtt.md +++ b/docs/microservices/mqtt.md @@ -14,7 +14,7 @@ $ npm i --save mqtt 使用 MQTT 传输器时,请将以下配置对象传入 `createMicroservice()` 方法: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.MQTT, options: { @@ -23,7 +23,10 @@ const app = await NestFactory.createMicroservice(AppModule, }); ``` -> info **注意** `Transport` 枚举是从 `@nestjs/microservices` 包中导入的。 +:::info 注意 +`Transport` 枚举是从 `@nestjs/microservices` 包中导入的。 +::: + #### 选项 @@ -65,7 +68,11 @@ getNotifications(@Payload() data: number[], @Ctx() context: MqttContext) { } ``` -> **提示** `@Payload()`、`@Ctx()` 和 `MqttContext` 均从 `@nestjs/microservices` 包导入。 +:::info 提示 +`@Payload()`、`@Ctx()` 和 `MqttContext` 均从 `@nestjs/microservices` 包导入。 +::: + + 要访问原始的 MQTT [数据包](https://github.com/mqttjs/mqtt-packet) ,请使用 `MqttContext` 对象的 `getPacket()` 方法,如下所示: @@ -91,7 +98,7 @@ getTemperature(@Ctx() context: MqttContext) { 使用 `@MessagePattern` 或 `@EventPattern` 装饰器创建的任何订阅都将以 QoS 0 级别进行订阅。如需更高 QoS 级别,可在建立连接时通过 `subscribeOptions` 块全局设置,如下所示: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.MQTT, options: { @@ -118,7 +125,9 @@ const record = new MqttRecordBuilder(':cat:') client.send('replace-emoji', record).subscribe(...); ``` -> info **提示** `MqttRecordBuilder` 类是从 `@nestjs/microservices` 包导出的。 +:::info 提示 +`MqttRecordBuilder` 类是从 `@nestjs/microservices` 包导出的。 +::: 您也可以通过访问 `MqttContext` 在服务端读取这些选项。 @@ -164,7 +173,9 @@ this.client.status.subscribe((status: MqttStatus) => { }); ``` -> info **提示** `MqttStatus` 类型是从 `@nestjs/microservices` 包导入的。 +:::info 提示 +`MqttStatus` 类型是从 `@nestjs/microservices` 包导入的。 +::: 同样地,您可以订阅服务器的 `status` 流来接收有关服务器状态的通知。 @@ -193,7 +204,9 @@ server.on('error', (err) => { }); ``` -> info **提示** `MqttEvents` 类型是从 `@nestjs/microservices` 包导入的。 +:::info 提示 +`MqttEvents` 类型是从 `@nestjs/microservices` 包导入的。 +::: #### 底层驱动访问 diff --git a/docs/microservices/nats.md b/docs/microservices/nats.md index a11e457..ed3bd3b 100644 --- a/docs/microservices/nats.md +++ b/docs/microservices/nats.md @@ -14,7 +14,7 @@ $ npm i --save nats 使用 NATS 传输器时,请将以下配置对象传入 `createMicroservice()` 方法: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.NATS, options: { @@ -23,7 +23,9 @@ const app = await NestFactory.createMicroservice(AppModule, }); ``` -> info **提示** `Transport` 枚举是从 `@nestjs/microservices` 包中导入的。 +:::info 提示 +`Transport` 枚举是从 `@nestjs/microservices` 包中导入的。 +::: #### 选项 @@ -72,7 +74,7 @@ const app = await NestFactory.createMicroservice(AppModule, NATS 提供了一个名为[分布式队列](https://docs.nats.io/nats-concepts/queue)的内置负载均衡功能。要创建队列订阅,请按如下方式使用 `queue` 属性: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.NATS, options: { @@ -93,7 +95,9 @@ getNotifications(@Payload() data: number[], @Ctx() context: NatsContext) { } ``` -> info:**提示**`@Payload()`、`@Ctx()` 和 `NatsContext` 均从 `@nestjs/microservices` 包导入。 +:::info 提示 +`@Payload()`、`@Ctx()` 和 `NatsContext` 均从 `@nestjs/microservices` 包导入。 +::: #### 通配符 @@ -122,7 +126,9 @@ const record = new NatsRecordBuilder(':cat:').setHeaders(headers).build(); this.client.send('replace-emoji', record).subscribe(...); ``` -> info **提示**`NatsRecordBuilder` 类是从 `@nestjs/microservices` 包中导出的。 +:::info 提示 +`NatsRecordBuilder` 类是从 `@nestjs/microservices` 包中导出的。 +::: 你也可以在服务器端通过访问 `NatsContext` 来读取这些头部信息,如下所示: @@ -168,7 +174,9 @@ this.client.status.subscribe((status: NatsStatus) => { }); ``` -> info **提示** `NatsStatus` 类型是从 `@nestjs/microservices` 包中导入的。 +:::info 提示 +`NatsStatus` 类型是从 `@nestjs/microservices` 包中导入的。 +::: 同样地,您可以订阅服务器的 `status` 流来接收有关服务器状态的通知。 @@ -197,7 +205,9 @@ server.on('error', (err) => { }); ``` -> info **提示** `NatsEvents` 类型是从 `@nestjs/microservices` 包中导入的。 +:::info 提示 +`NatsEvents` 类型是从 `@nestjs/microservices` 包中导入的。 +::: #### 底层驱动访问 diff --git a/docs/microservices/pipes.md b/docs/microservices/pipes.md index fa0933d..c50e1b2 100644 --- a/docs/microservices/pipes.md +++ b/docs/microservices/pipes.md @@ -2,7 +2,10 @@ 常规管道与微服务管道之间没有本质区别。唯一的区别在于,你应该使用 `RpcException` 而不是抛出 `HttpException`。 -> info **注意** `RpcException` 类是从 `@nestjs/microservices` 包中导出的。 +:::info 注意 +`RpcException` 类是从 `@nestjs/microservices` 包中导出的。 +::: + #### 绑定管道 diff --git a/docs/microservices/rabbitmq.md b/docs/microservices/rabbitmq.md index 92e90bb..8853a9c 100644 --- a/docs/microservices/rabbitmq.md +++ b/docs/microservices/rabbitmq.md @@ -14,7 +14,7 @@ $ npm i --save amqplib amqp-connection-manager 要使用 RabbitMQ 传输器,请将以下配置对象传入 `createMicroservice()` 方法: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.RMQ, options: { @@ -27,7 +27,11 @@ const app = await NestFactory.createMicroservice(AppModule, }); ``` -> **提示** `Transport` 枚举是从 `@nestjs/microservices` 包中导入的。 +:::info 提示 +`Transport` 枚举是从 `@nestjs/microservices` 包中导入的。 +::: + + #### 选项 @@ -93,7 +97,9 @@ getNotifications(@Payload() data: number[], @Ctx() context: RmqContext) { } ``` -> info **提示**`@Payload()`、`@Ctx()` 和 `RmqContext` 均从 `@nestjs/microservices` 包导入 +:::info 提示 +`@Payload()`、`@Ctx()` 和 `RmqContext` 均从 `@nestjs/microservices` 包导入 +::: 要访问原始的 RabbitMQ 消息(包含 `properties`、`fields` 和 `content`),请使用 `RmqContext` 对象的 `getMessage()` 方法,如下所示: @@ -160,7 +166,9 @@ const record = new RmqRecordBuilder(message) this.client.send('replace-emoji', record).subscribe(...); ``` -> info:**提示**`RmqRecordBuilder` 类是从 `@nestjs/microservices` 包中导出的。 +:::info 提示 +`RmqRecordBuilder` 类是从 `@nestjs/microservices` 包中导出的。 +::: 你也可以在服务端通过访问 `RmqContext` 来读取这些值,如下所示: @@ -182,7 +190,10 @@ this.client.status.subscribe((status: RmqStatus) => { }); ``` -> info **注意** `RmqStatus` 类型是从 `@nestjs/microservices` 包中导入的。 +:::info 注意 +`RmqStatus` 类型是从 `@nestjs/microservices` 包中导入的。 +::: + 同样地,您可以订阅服务器的 `status` 流来接收有关服务器状态的通知。 @@ -211,7 +222,10 @@ server.on('error', (err) => { }); ``` -> info **注意** `RmqEvents` 类型是从 `@nestjs/microservices` 包中导入的。 +:::info 注意 +`RmqEvents` 类型是从 `@nestjs/microservices` 包中导入的。 +::: + #### 底层驱动访问 diff --git a/docs/microservices/redis.md b/docs/microservices/redis.md index 4890d49..d8becb6 100644 --- a/docs/microservices/redis.md +++ b/docs/microservices/redis.md @@ -16,7 +16,7 @@ $ npm i --save ioredis 要使用 Redis 传输器,请将以下配置对象传入 `createMicroservice()` 方法: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.createMicroservice(AppModule, { transport: Transport.REDIS, options: { @@ -26,7 +26,11 @@ const app = await NestFactory.createMicroservice(AppModule, }); ``` -> **提示** `Transport` 枚举是从 `@nestjs/microservices` 包中导入的。 +:::info 提示 +`Transport` 枚举是从 `@nestjs/microservices` 包中导入的。 +::: + + #### 选项 @@ -79,7 +83,9 @@ getNotifications(@Payload() data: number[], @Ctx() context: RedisContext) { } ``` -> info **提示**`@Payload()`、`@Ctx()` 和 `RedisContext` 均从 `@nestjs/microservices` 包导入。 +:::info 提示 +`@Payload()`、`@Ctx()` 和 `RedisContext` 均从 `@nestjs/microservices` 包导入。 +::: #### 通配符 @@ -113,7 +119,9 @@ this.client.status.subscribe((status: RedisStatus) => { }); ``` -> info **提示** `RedisStatus` 类型是从 `@nestjs/microservices` 包导入的。 +:::info 提示 +`RedisStatus` 类型是从 `@nestjs/microservices` 包导入的。 +::: 同样地,您可以订阅服务器的 `status` 流来接收有关服务器状态的通知。 @@ -142,7 +150,10 @@ server.on('error', (err) => { }); ``` -> info **注意** `RedisEvents` 类型是从 `@nestjs/microservices` 包中导入的。 +:::info 注意 +`RedisEvents` 类型是从 `@nestjs/microservices` 包中导入的。 +::: + #### 底层驱动访问 diff --git a/docs/migration-guide.md b/docs/migration-guide.md index 9ae2fe7..fb409bd 100644 --- a/docs/migration-guide.md +++ b/docs/migration-guide.md @@ -44,7 +44,11 @@ findAll() { } ``` -> **警告** 注意 `*splat` 是一个命名通配符,匹配任何不包含根路径的路径。如果您需要也匹配根路径(`/users`),可以使用 `/users/{*splat}`,将通配符包装在大括号中(可选组)。注意 `splat` 只是通配符参数的名称,没有特殊含义。您可以随意命名,例如 `*wildcard`。 +:::warning 警告 +注意 `*splat` 是一个命名通配符,匹配任何不包含根路径的路径。如果您需要也匹配根路径(`/users`),可以使用 `/users/{*splat}`,将通配符包装在大括号中(可选组)。注意 `splat` 只是通配符参数的名称,没有特殊含义。您可以随意命名,例如 `*wildcard`。 +::: + + 同样,如果您有在所有路由上运行的中间件,可能需要更新路径以使用命名通配符: @@ -64,7 +68,11 @@ forRoutes('{*splat}'); // <-- 这在 Express v5 中将工作 ### 查询参数解析 -> **注意** 此更改仅适用于 Express v5。 +:::info 注意 +此更改仅适用于 Express v5。 +::: + + 在 Express v5 中,默认情况下不再使用 `qs` 库解析查询参数。相反,使用 `simple` 解析器,它不支持嵌套对象或数组。 @@ -95,7 +103,11 @@ bootstrap(); `@nestjs/platform-fastify` v11 现在最终支持 Fastify v5。对大多数用户来说这个更新应该是无缝的;但是,Fastify v5 引入了一些重大更改,尽管这些不太可能影响大多数 NestJS 用户。更详细的信息,请参考 [Fastify v5 迁移指南](https://fastify.dev/docs/v5.1.x/Guides/Migration-Guide-V5/)。 -> **提示** Fastify v5 中路径匹配没有更改(除了中间件,请参见下面的部分),因此您可以继续像以前一样使用通配符语法。行为保持不变,使用通配符(如 `*`)定义的路由仍将按预期工作。 +:::info 提示 +Fastify v5 中路径匹配没有更改(除了中间件,请参见下面的部分),因此您可以继续像以前一样使用通配符语法。行为保持不变,使用通配符(如 `*`)定义的路由仍将按预期工作。 +::: + + ### Fastify CORS @@ -103,7 +115,6 @@ bootstrap(); ```typescript const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']; // 或逗号分隔的字符串 'GET,POST,PUT,PATH,DELETE' -``` const app = await NestFactory.create( AppModule, @@ -183,7 +194,11 @@ C -> B -> A A -> B -> C ``` -> **提示** 全局模块被视为所有其他模块的依赖项。这意味着全局模块首先初始化,最后销毁。 +:::info 提示 +全局模块被视为所有其他模块的依赖项。这意味着全局模块首先初始化,最后销毁。 +::: + + ## 中间件注册顺序 @@ -232,7 +247,11 @@ CacheModule.registerAsync({ 其中 `KeyvRedis` 从 `@keyv/redis` 包导入。查看[缓存文档](./techniques/caching.md)了解更多。 -> **警告** 在此更新中,Keyv 库处理的缓存数据现在结构化为包含 `value` 和 `expires` 字段的对象,例如:`{"value": "yourData", "expires": 1678901234567}`。虽然 Keyv 在通过其 API 访问数据时会自动检索 `value` 字段,但如果您直接与缓存数据交互(例如,在 cache-manager API 之外)或需要支持使用先前版本的 `@nestjs/cache-manager` 编写的数据,了解这种更改很重要。 +:::warning 警告 +在此更新中,Keyv 库处理的缓存数据现在结构化为包含 `value` 和 `expires` 字段的对象,例如:`{"value": "yourData", "expires": 1678901234567}`。虽然 Keyv 在通过其 API 访问数据时会自动检索 `value` 字段,但如果您直接与缓存数据交互(例如,在 cache-manager API 之外)或需要支持使用先前版本的 `@nestjs/cache-manager` 编写的数据,了解这种更改很重要。 +::: + + ## Config 模块 @@ -334,7 +353,11 @@ export class DogHealthIndicator { - `HealthIndicatorService` 替换了遗留的 `HealthIndicator` 和 `HealthCheckError` 类,为健康检查提供了更清洁的 API。 - `check` 方法允许轻松的状态跟踪(`up` 或 `down`),同时支持在健康检查响应中包含额外的元数据。 -> **信息** 请注意,`HealthIndicator` 和 `HealthCheckError` 类已被标记为已弃用,并计划在下一个主要版本中删除。 +:::info 信息 +请注意,`HealthIndicator` 和 `HealthCheckError` 类已被标记为已弃用,并计划在下一个主要版本中删除。 +::: + + ## Node.js v16 和 v18 不再支持 diff --git a/docs/openapi/cli-plugin.md b/docs/openapi/cli-plugin.md index 28f7448..dab8335 100644 --- a/docs/openapi/cli-plugin.md +++ b/docs/openapi/cli-plugin.md @@ -2,7 +2,11 @@ [TypeScript](https://www.typescriptlang.org/docs/handbook/decorators.html) 的元数据反射系统存在若干限制,例如无法确定类包含哪些属性,或识别某个属性是可选的还是必需的。不过,其中部分限制可以在编译时得到解决。Nest 提供了一个插件来增强 TypeScript 的编译过程,从而减少所需的样板代码量。 -> **提示** 此插件为**可选功能**。如果愿意,您可以手动声明所有装饰器,或仅在需要的地方声明特定装饰器。 +:::info 提示 +此插件为**可选功能**。如果愿意,您可以手动声明所有装饰器,或仅在需要的地方声明特定装饰器。 +::: + + #### 概述 @@ -52,15 +56,27 @@ export class CreateUserDto { } ``` -> **注意** Swagger 插件会从 TypeScript 类型和 class-validator 装饰器推导出 `@ApiProperty()` 注解。这有助于为生成的 Swagger UI 文档清晰地描述您的 API。然而,运行时的验证仍将由 class-validator 装饰器处理。因此仍需继续使用如 `IsEmail()`、`IsNumber()` 等验证器。 +:::info 注意 +Swagger 插件会从 TypeScript 类型和 class-validator 装饰器推导出 `@ApiProperty()` 注解。这有助于为生成的 Swagger UI 文档清晰地描述您的 API。然而,运行时的验证仍将由 class-validator 装饰器处理。因此仍需继续使用如 `IsEmail()`、`IsNumber()` 等验证器。 +::: + + 因此,如果您打算依赖自动注解生成文档,同时仍希望进行运行时验证,那么 class-validator 装饰器仍然是必需的。 -> **提示** 在 DTO 中使用[映射类型工具](../openapi/mapped-types)(如 `PartialType`)时,请从 `@nestjs/swagger` 而非 `@nestjs/mapped-types` 导入它们,以便插件能够识别模式。 +:::info 提示 +在 DTO 中使用[映射类型工具](../openapi/mapped-types)(如 `PartialType`)时,请从 `@nestjs/swagger` 而非 `@nestjs/mapped-types` 导入它们,以便插件能够识别模式。 +::: + + 该插件基于**抽象语法树**动态添加适当的装饰器。因此您无需为分散在代码中的 `@ApiProperty` 装饰器而烦恼。 -> **提示** 插件会自动生成所有缺失的 swagger 属性,但如需覆盖这些属性,只需通过 `@ApiProperty()` 显式设置即可。 +:::info 提示 +插件会自动生成所有缺失的 swagger 属性,但如需覆盖这些属性,只需通过 `@ApiProperty()` 显式设置即可。 +::: + + #### 注释自省功能 @@ -108,7 +124,11 @@ export class SomeController { @ApiOperation({ summary: "Create some resource" }) ``` -> **提示** 对于模型而言,相同逻辑适用,但需改用 `ApiProperty` 装饰器。 +:::info 提示 +对于模型而言,相同逻辑适用,但需改用 `ApiProperty` 装饰器。 +::: + + 对于控制器,您不仅可以提供摘要,还能添加描述(备注)、标签(例如 `@deprecated`)以及响应示例,如下所示: diff --git a/docs/openapi/index.md b/docs/openapi/index.md index 3be267a..5301f87 100644 --- a/docs/openapi/index.md +++ b/docs/openapi/index.md @@ -46,7 +46,11 @@ async function bootstrap() { bootstrap(); ``` -> **提示** 工厂方法 `SwaggerModule.createDocument()` 专门用于在您请求时生成 Swagger 文档。这种方法有助于节省一些初始化时间,结果文档是符合 [OpenAPI 文档](https://swagger.io/specification/#openapi-document)规范的可序列化对象。 +:::info 提示 +工厂方法 `SwaggerModule.createDocument()` 专门用于在您请求时生成 Swagger 文档。这种方法有助于节省一些初始化时间,结果文档是符合 [OpenAPI 文档](https://swagger.io/specification/#openapi-document)规范的可序列化对象。 +::: + + `DocumentBuilder` 帮助构建符合 OpenAPI 规范的基础文档。它提供了几种方法,允许设置标题、描述、版本等属性。为了创建完整的文档(包含所有定义的 HTTP 路由),我们使用 `SwaggerModule` 类的 `createDocument()` 方法。此方法接受两个参数:应用程序实例和 Swagger 选项对象。 @@ -67,7 +71,11 @@ $ npm run start 如您所见,`SwaggerModule` 自动反映了您的所有端点。 -> **提示** 要生成和下载 Swagger JSON 文件,请导航到 `http://localhost:3000/api-json`(假设您的 Swagger 文档在 `http://localhost:3000/api` 下可用)。也可以使用来自 `@nestjs/swagger` 的 setup 方法将其暴露在您选择的路由上: +:::info 提示 +要生成和下载 Swagger JSON 文件,请导航到 `http://localhost:3000/api-json`(假设您的 Swagger 文档在 `http://localhost:3000/api` 下可用)。也可以使用来自 `@nestjs/swagger` 的 setup 方法将其暴露在您选择的路由上: +::: + + ```typescript SwaggerModule.setup('swagger', app, documentFactory, { @@ -77,7 +85,11 @@ SwaggerModule.setup('swagger', app, documentFactory, { 这将在 `http://localhost:3000/swagger/json` 处暴露它。 -> **警告** 当使用 `fastify` 和 `helmet` 时,可能会出现 [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 问题,要解决此冲突,请按如下所示配置 CSP: +:::warning 警告 +当使用 `fastify` 和 `helmet` 时,可能会出现 [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 问题,要解决此冲突,请按如下所示配置 CSP: +::: + + ```typescript app.register(helmet, { diff --git a/docs/openapi/introduction.md b/docs/openapi/introduction.md index cd24f10..b80a2d6 100644 --- a/docs/openapi/introduction.md +++ b/docs/openapi/introduction.md @@ -14,7 +14,7 @@ $ npm install --save @nestjs/swagger 安装过程完成后,打开 `main.ts` 文件并使用 `SwaggerModule` 类初始化 Swagger: -```typescript title="main.ts" + ```typescript title="main.ts" import { NestFactory } from '@nestjs/core'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; @@ -36,7 +36,11 @@ async function bootstrap() { bootstrap(); ``` -> **提示** 工厂方法 `SwaggerModule.createDocument()` 专门用于在请求时生成 Swagger 文档。这种方法有助于节省初始化时间,生成的文档是一个符合 [OpenAPI 文档](https://swagger.io/specification/#openapi-document)规范的可序列化对象。除了通过 HTTP 提供文档外,您还可以将其保存为 JSON 或 YAML 文件以多种方式使用。 +:::info 提示 +工厂方法 `SwaggerModule.createDocument()` 专门用于在请求时生成 Swagger 文档。这种方法有助于节省初始化时间,生成的文档是一个符合 [OpenAPI 文档](https://swagger.io/specification/#openapi-document)规范的可序列化对象。除了通过 HTTP 提供文档外,您还可以将其保存为 JSON 或 YAML 文件以多种方式使用。 +::: + + `DocumentBuilder` 用于构建符合 OpenAPI 规范的基础文档结构。它提供了多种方法用于设置标题、描述、版本等属性。要创建完整文档(包含所有已定义的 HTTP 路由),我们使用 `SwaggerModule` 类的 `createDocument()` 方法。该方法接收两个参数:应用实例和 Swagger 配置对象。此外,我们还可以提供第三个参数,其类型应为 `SwaggerDocumentOptions`。更多细节请参阅[文档选项章节](#文档选项)。 @@ -59,7 +63,11 @@ $ npm run start 如你所见,`SwaggerModule` 会自动反映所有端点。 -> **提示** 要生成并下载 Swagger JSON 文件,请访问 `http://localhost:3000/api-json`(假设你的 Swagger 文档位于 `http://localhost:3000/api`)。你也可以仅通过 `@nestjs/swagger` 中的 setup 方法将其暴露在你选择的路由上,如下所示: +:::info 提示 +要生成并下载 Swagger JSON 文件,请访问 `http://localhost:3000/api-json`(假设你的 Swagger 文档位于 `http://localhost:3000/api`)。你也可以仅通过 `@nestjs/swagger` 中的 setup 方法将其暴露在你选择的路由上,如下所示: +::: + + > > ```typescript > SwaggerModule.setup('swagger', app, documentFactory, { @@ -69,7 +77,11 @@ $ npm run start > > 这将在 `http://localhost:3000/swagger/json` 上暴露它 -> **警告** 当使用 `fastify` 和 `helmet` 时,可能会出现 [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 问题,要解决此冲突,请按如下方式配置 CSP: +:::warning 警告 +当使用 `fastify` 和 `helmet` 时,可能会出现 [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 问题,要解决此冲突,请按如下方式配置 CSP: +::: + + > > ```typescript > app.register(helmet, { @@ -285,7 +297,11 @@ export interface SwaggerCustomOptions { } ``` -> **提示** `ui` 和 `raw` 是独立选项。禁用 Swagger UI (`ui: false`) 不会禁用 API 定义 (JSON/YAML)。反之,禁用 API 定义 (`raw: []`) 也不会影响 Swagger UI 的使用。 +:::info 提示 +`ui` 和 `raw` 是独立选项。禁用 Swagger UI (`ui: false`) 不会禁用 API 定义 (JSON/YAML)。反之,禁用 API 定义 (`raw: []`) 也不会影响 Swagger UI 的使用。 +::: + + > > 例如,以下配置将禁用 Swagger UI 但仍允许访问 API 定义: > diff --git a/docs/openapi/mapped-types.md b/docs/openapi/mapped-types.md index 6a15ff0..515a4d6 100644 --- a/docs/openapi/mapped-types.md +++ b/docs/openapi/mapped-types.md @@ -31,7 +31,9 @@ export class CreateCatDto { export class UpdateCatDto extends PartialType(CreateCatDto) {} ``` -> info **提示** `PartialType()` 函数是从 `@nestjs/swagger` 包中导入的。 +:::info 提示 +`PartialType()` 函数是从 `@nestjs/swagger` 包中导入的。 +::: #### Pick @@ -58,7 +60,9 @@ export class CreateCatDto { export class UpdateCatAgeDto extends PickType(CreateCatDto, ['age'] as const) {} ``` -> info **提示** `PickType()` 函数是从 `@nestjs/swagger` 包中导入的。 +:::info 提示 +`PickType()` 函数是从 `@nestjs/swagger` 包中导入的。 +::: #### Omit @@ -85,7 +89,9 @@ export class CreateCatDto { export class UpdateCatDto extends OmitType(CreateCatDto, ['name'] as const) {} ``` -> info **提示** `OmitType()` 函数是从 `@nestjs/swagger` 包中导入的。 +:::info 提示 +`OmitType()` 函数是从 `@nestjs/swagger` 包中导入的。 +::: #### 交叉类型 @@ -117,7 +123,9 @@ export class UpdateCatDto extends IntersectionType( ) {} ``` -> info **提示** `IntersectionType()` 函数是从 `@nestjs/swagger` 包中导入的。 +:::info 提示 +`IntersectionType()` 函数是从 `@nestjs/swagger` 包中导入的。 +::: #### 组合 diff --git a/docs/openapi/operations.md b/docs/openapi/operations.md index 6b3e968..a5ba4e7 100644 --- a/docs/openapi/operations.md +++ b/docs/openapi/operations.md @@ -290,7 +290,9 @@ export const ApiPaginatedResponse = >( }; ``` -> info **提示** `Type` 接口和 `applyDecorators` 函数都是从 `@nestjs/common` 包中导入的。 +:::info 提示 +`Type` 接口和 `applyDecorators` 函数都是从 `@nestjs/common` 包中导入的。 +::: 为确保 `SwaggerModule` 会为我们的模型生成定义,必须像之前在控制器中对 `PaginatedDto` 所做的那样,将其作为额外模型添加。 diff --git a/docs/openapi/types-and-parameters.md b/docs/openapi/types-and-parameters.md index 88f77bf..2607d8c 100644 --- a/docs/openapi/types-and-parameters.md +++ b/docs/openapi/types-and-parameters.md @@ -9,7 +9,9 @@ async create(@Body() createCatDto: CreateCatDto) { } ``` -> info **提示** 要显式设置请求体定义,请使用 `@ApiBody()` 装饰器(从 `@nestjs/swagger` 包导入)。 +:::info 提示 +要显式设置请求体定义,请使用 `@ApiBody()` 装饰器(从 `@nestjs/swagger` 包导入)。 +::: 基于 `CreateCatDto`,Swagger UI 将创建以下模型定义: @@ -32,7 +34,11 @@ export class CreateCatDto { } ``` -> **提示** 与其手动标注每个属性,建议使用 Swagger 插件(参见[插件](/openapi/cli-plugin)章节)来自动完成此操作。 +:::info 提示 +与其手动标注每个属性,建议使用 Swagger 插件(参见[插件](/openapi/cli-plugin)章节)来自动完成此操作。 +::: + + 让我们打开浏览器验证生成的 `CreateCatDto` 模型: @@ -49,7 +55,9 @@ export class CreateCatDto { age: number; ``` -> info:无需显式输入 `{{"@ApiProperty({ required: false })"}}` ,您可以使用 `@ApiPropertyOptional()` 快捷装饰器。 +:::info 提示 +无需显式输入 `{{"@ApiProperty({ required: false })"}}` ,您可以使用 `@ApiPropertyOptional()` 快捷装饰器。 +::: 如需显式设置属性类型,请使用 `type` 键: @@ -69,7 +77,9 @@ age: number; names: string[]; ``` -> info **提示** 考虑使用 Swagger 插件(参见 [插件](/openapi/cli-plugin) 部分),它将自动检测数组。 +:::info 提示 +考虑使用 Swagger 插件(参见 [插件](/openapi/cli-plugin) 部分),它将自动检测数组。 +::: 要么将类型作为数组的第一个元素包含(如上所示),要么将 `isArray` 属性设置为 `true`。 @@ -82,7 +92,9 @@ names: string[]; node: Node; ``` -> info **提示** 考虑使用 Swagger 插件(参见[插件](/openapi/cli-plugin)部分),该插件将自动检测循环依赖。 +:::info 提示 +考虑使用 Swagger 插件(参见[插件](/openapi/cli-plugin)部分),该插件将自动检测循环依赖。 +::: #### 泛型与接口 @@ -169,7 +181,9 @@ export enum CatInformationEnum { } ``` -> info **提示** 上述代码片段是使用名为 [NSwag](https://github.com/RicoSuter/NSwag) 的工具生成的。 +:::info 提示 +上述代码片段是使用名为 [NSwag](https://github.com/RicoSuter/NSwag) 的工具生成的。 +::: 可以看到现在有两个完全相同的`枚举` 。为了解决这个问题,你可以在装饰器中同时传入 `enumName` 和 `enum` 属性。 @@ -198,7 +212,10 @@ CatBreed: - Siamese ``` -> info **注意** 任何接受 `enum` 作为属性的**装饰器**也都支持 `enumName` 参数。 +:::info 注意 +任何接受 `enum` 作为属性的**装饰器**也都支持 `enumName` 参数。 +::: + #### 属性值示例 @@ -288,7 +305,10 @@ async create(@Body() coords: number[][]) {} export class CreateCatDto {} ``` -> info **注意** 对于特定模型类,您只需使用一次 `@ApiExtraModels()`。 +:::info 注意 +对于特定模型类,您只需使用一次 `@ApiExtraModels()`。 +::: + 或者,您也可以向 `SwaggerModule.createDocument()` 方法传递一个包含 `extraModels` 属性的选项对象,如下所示: @@ -338,7 +358,9 @@ type Pet = Cat | Dog; pets: Pet[]; ``` -> info **提示** `getSchemaPath()` 函数是从 `@nestjs/swagger` 导入的。 +:::info 提示 +`getSchemaPath()` 函数是从 `@nestjs/swagger` 导入的。 +::: `Cat` 和 `Dog` 都必须使用 `@ApiExtraModels()` 装饰器(在类级别)定义为额外模型。 diff --git a/docs/overview/controllers.md b/docs/overview/controllers.md index 9aeab14..cddfeff 100644 --- a/docs/overview/controllers.md +++ b/docs/overview/controllers.md @@ -8,7 +8,9 @@ 要创建基本控制器,我们使用类和**装饰器**。装饰器将类与必要的元数据关联起来,使 Nest 能够创建将请求连接到相应控制器的路由映射。 -> info **提示** 要快速创建带有内置[验证](../techniques/validation)功能的 CRUD 控制器,可以使用 CLI 的 [CRUD 生成器](../recipes/crud-generator#crud-生成器):`nest g resource [name]`。 +:::info 提示 +要快速创建带有内置[验证](../techniques/validation)功能的 CRUD 控制器,可以使用 CLI 的 [CRUD 生成器](../recipes/crud-generator#crud-生成器):`nest g resource [name]`。 +::: #### 路由 @@ -26,7 +28,9 @@ export class CatsController { } ``` -> info **提示** 要使用 CLI 创建控制器,只需执行 `$ nest g controller [name]` 命令。 +:::info 提示 +要使用 CLI 创建控制器,只需执行 `$ nest g controller [name]` 命令。 +::: `@Get()` HTTP 请求方法装饰器放置在 `findAll()` 方法前,告知 Nest 为 HTTP 请求创建特定端点的处理器。该端点由 HTTP 请求方法(本例中为 GET)和路由路径共同定义。那么什么是路由路径?处理器的路由路径由控制器声明的(可选)前缀与方法装饰器中指定的路径组合而成。由于我们为所有路由设置了 `cats` 前缀且未在方法装饰器中添加具体路径,Nest 会将 `GET /cats` 请求映射到该处理器。 @@ -50,7 +54,9 @@ export class CatsController { -> warning **警告** 当 Nest 检测到处理程序使用了 `@Res()` 或 `@Next()` 时,表明您选择了特定库实现方式。如果同时使用两种方式,标准方式将针对该路由**自动禁用**且不再按预期工作。若要同时使用两种方式(例如通过注入响应对象仅设置 cookies/headers 但仍将剩余工作交给框架处理),必须在 `@Res({ passthrough: true })` 装饰器中将 `passthrough` 选项设为 `true`。 +:::warning 警告 +当 Nest 检测到处理程序使用了 `@Res()` 或 `@Next()` 时,表明您选择了特定库实现方式。如果同时使用两种方式,标准方式将针对该路由**自动禁用**且不再按预期工作。若要同时使用两种方式(例如通过注入响应对象仅设置 cookies/headers 但仍将剩余工作交给框架处理),必须在 `@Res({ passthrough: true })` 装饰器中将 `passthrough` 选项设为 `true`。 +::: #### 请求对象 @@ -68,7 +74,9 @@ export class CatsController { } ``` -> info **提示** 要充分利用 `express` 的类型定义(如上面 `request: Request` 参数示例所示),请确保安装 `@types/express` 包。 +:::info 提示 +要充分利用 `express` 的类型定义(如上面 `request: Request` 参数示例所示),请确保安装 `@types/express` 包。 +::: 请求对象代表 HTTP 请求,包含查询字符串、参数、HTTP 标头和正文等属性(更多信息请参阅[此处](https://expressjs.com/en/api.html#req) )。在大多数情况下,您不需要手动访问这些属性。相反,可以直接使用开箱即用的专用装饰器,如 `@Body()` 或 `@Query()`。以下是提供的装饰器及其对应平台特定对象的列表。 @@ -117,13 +125,15 @@ export class CatsController { \* 为兼容底层 HTTP 平台(如 Express 和 Fastify)的类型定义,Nest 提供了 `@Res()` 和 `@Response()` 装饰器。`@Res()` 是 `@Response()` 的别名。两者都直接暴露底层原生平台的 `response` 对象接口。使用时还需导入相应底层库的类型定义(如 `@types/express`)以获得完整支持。注意:在方法处理程序中注入 `@Res()` 或 `@Response()` 时,该处理程序将进入**库特定模式** ,此时需手动管理响应。必须通过调用 `response` 对象方法(如 `res.json(...)` 或 `res.send(...)`)返回响应,否则 HTTP 服务器会挂起。 -> info **提示** 要了解如何创建自定义装饰器,请参阅[本章节](/custom-decorators) 。 +:::info 提示 +要了解如何创建自定义装饰器,请参阅[本章节](/custom-decorators) 。 +::: #### 资源 此前,我们定义了一个用于获取猫咪资源的端点(**GET** 路由)。通常我们还需要提供创建新记录的端点。为此,让我们创建 **POST** 处理器: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" import { Controller, Get, Post } from '@nestjs/common'; @Controller('cats') @@ -171,7 +181,9 @@ create() { } ``` -> info **提示** 从 `@nestjs/common` 包中导入 `HttpCode`。 +:::info 提示 +从 `@nestjs/common` 包中导入 `HttpCode`。 +::: 通常,您的状态码并非静态,而是取决于多种因素。在这种情况下,您可以使用库特定的**响应**对象(通过 `@Res()` 注入),或者在出错时抛出异常。 @@ -187,7 +199,9 @@ create() { } ``` -> info **提示** 从 `@nestjs/common` 包中导入 `Header`。 +:::info 提示 +从 `@nestjs/common` 包中导入 `Header`。 +::: #### 重定向 @@ -200,7 +214,9 @@ create() { @Redirect('https://nestjs.com', 301) ``` -> info **提示** 有时您可能需要动态决定 HTTP 状态码或重定向 URL。可通过返回遵循 `HttpRedirectResponse` 接口(来自 `@nestjs/common`)的对象来实现。 +:::info 提示 +有时您可能需要动态决定 HTTP 状态码或重定向 URL。可通过返回遵循 `HttpRedirectResponse` 接口(来自 `@nestjs/common`)的对象来实现。 +::: 返回值将覆盖传递给 `@Redirect()` 装饰器的任何参数。例如: @@ -218,7 +234,10 @@ getDocs(@Query('version') version) { 当需要接收**动态数据**作为请求的一部分时(例如通过 `GET /cats/1` 获取 ID 为 `1` 的猫),静态路径路由将无法工作。要定义带参数的路由,您可以在路由路径中添加路由参数**标记**来捕获 URL 中的动态值。下面 `@Get()` 装饰器示例中的路由参数标记展示了这种方法。然后可以使用 `@Param()` 装饰器来访问这些路由参数,该装饰器应添加到方法签名中。 -> info **注意** 带参数的路由应在所有静态路径之后声明。这样可以防止参数化路径拦截本该由静态路径处理的流量。 +:::info 注意 +带参数的路由应在所有静态路径之后声明。这样可以防止参数化路径拦截本该由静态路径处理的流量。 +::: + ```typescript @Get(':id') @@ -230,7 +249,9 @@ findOne(@Param() params: any): string { `@Param()` 装饰器用于修饰方法参数(如上例中的 `params`),使得**路由**参数可以在方法内部通过该装饰参数的属性进行访问。如代码所示,你可以通过 `params.id` 来访问 `id` 参数。或者,你也可以向装饰器传递特定的参数标记,直接在方法体中按名称引用路由参数。 -> info **提示** 从 `@nestjs/common` 包中导入 `Param`。 +:::info 提示 +从 `@nestjs/common` 包中导入 `Param`。 +::: ```typescript @Get(':id') @@ -253,7 +274,9 @@ export class AdminController { } ``` -> warning **警告** 由于 **Fastify** 不支持嵌套路由器,如果您正在使用子域名路由,建议改用默认的 Express 适配器。 +:::warning 警告 + 由于 **Fastify** 不支持嵌套路由器,如果您正在使用子域名路由,建议改用默认的 Express 适配器。 +::: 与路由 `path` 类似,`host` 选项可以使用令牌来捕获主机名中该位置的动态值。下面 `@Controller()` 装饰器示例中的主机参数令牌演示了这种用法。通过这种方式声明的主机参数可以使用 `@HostParam()` 装饰器访问,该装饰器应添加到方法签名中。 @@ -271,13 +294,13 @@ export class AccountController { 对于来自其他编程语言的开发者来说,可能会惊讶地发现,在 Nest 中几乎所有内容都在传入请求之间共享。这包括数据库连接池、具有全局状态的单例服务等资源。需要理解的是,Node.js 不使用请求/响应的多线程无状态模型(即每个请求由单独的线程处理)。因此,在我们的应用程序中使用单例实例是完全 **安全** 的。 -话虽如此,在某些特定边缘场景中,控制器可能需要基于请求的生命周期。例如 GraphQL 应用中的请求级缓存、请求追踪或多租户实现。您可在此处详细了解如何控制注入作用域 [here](/fundamentals/injection-scopes)。 +话虽如此,在某些特定边缘场景中,控制器可能需要基于请求的生命周期。例如 GraphQL 应用中的请求级缓存、请求追踪或多租户实现。您可在此处详细了解如何控制注入作用域 [here](/fundamentals/provider-scopes)。 #### 异步特性 我们热爱现代 JavaScript,尤其推崇其**异步**数据处理机制。因此 Nest 全面支持 `async` 函数,每个 `async` 函数都必须返回 `Promise`,这使得您可以返回一个延迟值由 Nest 自动解析。示例如下: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Get() async findAll(): Promise { return []; @@ -286,7 +309,7 @@ async findAll(): Promise { 这段代码完全有效。但 Nest 更进一步,允许路由处理器返回 RxJS 的[可观察流](https://rxjs-dev.firebaseapp.com/guide/observable) ,Nest 会在内部处理订阅并在流完成时解析最终发出的值。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Get() findAll(): Observable { return of([]); @@ -303,7 +326,7 @@ findAll(): Observable { 我们来创建 `CreateCatDto` 类: -```typescript title="create-cat.dto" + ```typescript title="create-cat.dto.ts" export class CreateCatDto { name: string; age: number; @@ -313,14 +336,16 @@ export class CreateCatDto { 它仅包含三个基本属性。之后我们就可以在 `CatsController` 中使用新创建的 DTO: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() async create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; } ``` -> info **提示** 我们的 `ValidationPipe` 可以过滤掉不应被方法处理器接收的属性。在这种情况下,我们可以将可接受的属性加入白名单,任何未包含在白名单中的属性都会自动从结果对象中剔除。在 `CreateCatDto` 示例中,我们的白名单包含 `name`、`age` 和 `breed` 属性。了解更多 [请点击这里](../techniques/validation#剥离属性) 。 +:::info 提示 +我们的 `ValidationPipe` 可以过滤掉不应被方法处理器接收的属性。在这种情况下,我们可以将可接受的属性加入白名单,任何未包含在白名单中的属性都会自动从结果对象中剔除。在 `CreateCatDto` 示例中,我们的白名单包含 `name`、`age` 和 `breed` 属性。了解更多 [请点击这里](../techniques/validation#剥离属性) 。 +::: #### 查询参数 @@ -328,7 +353,7 @@ async create(@Body() createCatDto: CreateCatDto) { 假设有个路由需要基于 `age` 和 `breed` 等查询参数筛选猫咪列表。首先在 `CatsController` 中定义查询参数: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Get() async findAll(@Query('age') age: number, @Query('breed') breed: string) { return `This action returns all cats filtered by age: ${age} and breed: ${breed}`; @@ -368,7 +393,9 @@ const app = await NestFactory.create( ); ``` -> info **提示**`qs` 是一个支持嵌套和数组的查询字符串解析器。您可以通过 `npm install qs` 命令安装它。 +:::info 提示 +`qs` 是一个支持嵌套和数组的查询字符串解析器。您可以通过 `npm install qs` 命令安装它。 +::: #### 错误处理 @@ -378,7 +405,7 @@ const app = await NestFactory.create( 以下示例展示了如何使用多个可用装饰器来创建基础控制器。该控制器提供了一些方法来访问和操作内部数据。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common'; import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto'; @@ -411,7 +438,9 @@ export class CatsController { } ``` -> info **Nest CLI** 提供了一个生成器(schematic),可自动创建**所有样板代码** ,省去手动操作并提升开发体验。详细了解此功能请点击[此处](/recipes/crud-generator) 。 +:::info Nest + CLI** 提供了一个生成器(schematic),可自动创建**所有样板代码** ,省去手动操作并提升开发体验。详细了解此功能请点击[此处](/recipes/crud-generator) 。 +::: #### 快速开始 @@ -419,7 +448,7 @@ export class CatsController { 控制器必须始终属于某个模块,这就是为什么我们要在 `@Module()` 装饰器的 `controllers` 数组中包含它们。由于目前除了根模块 `AppModule` 外尚未定义其他模块,我们将用它来注册 `CatsController`: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { CatsController } from './cats/cats.controller'; diff --git a/docs/overview/custom-decorators.md b/docs/overview/custom-decorators.md index 2072879..a10e509 100644 --- a/docs/overview/custom-decorators.md +++ b/docs/overview/custom-decorators.md @@ -2,7 +2,9 @@ Nest 的核心构建基于一种称为**装饰器**的语言特性。装饰器在许多常用编程语言中是个广为人知的概念,但在 JavaScript 领域仍相对较新。为了更好地理解装饰器的工作原理,我们建议阅读[这篇文章](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841) 。这里给出一个简单定义: -> ES2016 装饰器是一个返回函数的表达式,可以接收目标对象、名称和属性描述符作为参数。使用时需要在装饰目标上方添加 `@` 字符前缀。装饰器可以定义在类、方法或属性上。 +::: +ES2016 装饰器是一个返回函数的表达式,可以接收目标对象、名称和属性描述符作为参数。使用时需要在装饰目标上方添加 `@` 字符前缀。装饰器可以定义在类、方法或属性上。 +::: #### 参数装饰器 @@ -53,17 +55,17 @@ Nest 提供了一组实用的**参数装饰器** ,可与 HTTP 路由处理程 -Additionally, you can create your own **custom decorators**. Why is this useful? +此外,你可以创建自己的**自定义装饰器**。为什么这很有用? -In the node.js world, it's common practice to attach properties to the **request** object. Then you manually extract them in each route handler, using code like the following: +在 node.js 领域,通常的做法是将属性附加到 **request** 对象上。然后在每个路由处理程序中手动提取它们,使用如下代码 ```typescript const user = req.user; ``` -In order to make your code more readable and transparent, you can create a `@User()` decorator and reuse it across all of your controllers. +为了让您的代码更具可读性和透明性,您可以创建一个 `@User()` 装饰器,并在所有控制器中重复使用它。 -```typescript title="user.decorator" + ```typescript title="user.decorator.ts" import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const User = createParamDecorator( @@ -99,7 +101,7 @@ async findOne(@User() user: UserEntity) { 让我们定义一个装饰器,它接收属性名作为键,若存在则返回关联值(若不存在或 `user` 对象尚未创建,则返回 undefined)。 -```typescript title="user.decorator" + ```typescript title="user.decorator.ts" import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const User = createParamDecorator( @@ -123,7 +125,9 @@ async findOne(@User('firstName') firstName: string) { 您可以使用相同的装饰器搭配不同的键来访问不同的属性。如果 `user` 对象具有深层或复杂的结构,这种方式能使请求处理程序的实现更简单且更具可读性。 -> info **提示** 对于 TypeScript 用户,请注意 `createParamDecorator()` 是一个泛型。这意味着您可以显式地强制类型安全,例如 `createParamDecorator((data, ctx) => ...)` 。或者,在工厂函数中指定参数类型,例如 `createParamDecorator((data: string, ctx) => ...)` 。如果两者都省略,则 `data` 的类型将为 `any`。 +:::info 提示 +对于 TypeScript 用户,请注意 `createParamDecorator()` 是一个泛型。这意味着您可以显式地强制类型安全,例如 `createParamDecorator((data, ctx) => ...)` 。或者,在工厂函数中指定参数类型,例如 `createParamDecorator((data: string, ctx) => ...)` 。如果两者都省略,则 `data` 的类型将为 `any`。 +::: #### 使用管道 @@ -139,13 +143,16 @@ async findOne( } ``` -> info **注意** 需要将 `validateCustomDecorators` 选项设置为 true。默认情况下 `ValidationPipe` 不会验证带有自定义装饰器注解的参数。 +:::info 注意 +需要将 `validateCustomDecorators` 选项设置为 true。默认情况下 `ValidationPipe` 不会验证带有自定义装饰器注解的参数。 +::: + #### 装饰器组合 Nest 提供了一个辅助方法来组合多个装饰器。例如,假设您希望将与身份验证相关的所有装饰器合并为一个装饰器。可以通过以下构造实现: -```typescript title="auth.decorator" + ```typescript title="auth.decorator.ts" import { applyDecorators } from '@nestjs/common'; export function Auth(...roles: Role[]) { @@ -158,7 +165,7 @@ export function Auth(...roles: Role[]) { } ``` -You can then use this custom `@Auth()` decorator as follows: +然后您可以按如下方式使用这个自定义的 `@Auth()` 装饰器: ```typescript @Get('users') @@ -166,6 +173,9 @@ You can then use this custom `@Auth()` decorator as follows: findAllUsers() {} ``` -This has the effect of applying all four decorators with a single declaration. +这样就可以通过声明一个装饰器从而包含四个装饰器的效果。 + +:::warning 警告 +从 `@nestjs/swagger` 包引入的 `@ApiHideProperty()` 装饰器不可组合,并且无法与 `applyDecorators` 函数一起使用。 +::: -> warning **警告** The `@ApiHideProperty()` decorator from the `@nestjs/swagger` package is not composable and won't work properly with the `applyDecorators` function. diff --git a/docs/overview/exception-filters.md b/docs/overview/exception-filters.md index ad8e047..9ea0f4b 100644 --- a/docs/overview/exception-filters.md +++ b/docs/overview/exception-filters.md @@ -13,7 +13,10 @@ Nest 内置了一个**异常处理层** ,负责处理应用程序中所有未 } ``` -> info **注意** 全局异常过滤器部分支持 `http-errors` 库。基本上,任何包含 `statusCode` 和 `message` 属性的异常都会被正确解析并作为响应返回(而不是对无法识别的异常默认返回 `InternalServerErrorException`)。 +:::info 注意 +全局异常过滤器部分支持 `http-errors` 库。基本上,任何包含 `statusCode` 和 `message` 属性的异常都会被正确解析并作为响应返回(而不是对无法识别的异常默认返回 `InternalServerErrorException`)。 +::: + #### 抛出标准异常 @@ -21,14 +24,16 @@ Nest 提供了一个内置的 `HttpException` 类,该类从 `@nestjs/common` 例如,在 `CatsController` 中,我们有一个 `findAll()` 方法(一个 `GET` 路由处理程序)。假设这个路由处理程序由于某种原因抛出了异常。为了演示这一点,我们将其硬编码如下: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Get() async findAll() { throw new HttpException('Forbidden', HttpStatus.FORBIDDEN); } ``` -> info **提示** 我们在这里使用了 `HttpStatus`。这是一个从 `@nestjs/common` 包导入的辅助枚举。 +:::info 提示 +我们在这里使用了 `HttpStatus`。这是一个从 `@nestjs/common` 包导入的辅助枚举。 +::: 当客户端调用此端点时,响应如下所示: @@ -57,7 +62,7 @@ async findAll() { 以下是覆盖整个响应体并提供错误原因的示例: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Get() async findAll() { try { @@ -94,7 +99,7 @@ async findAll() { 多数情况下,您无需编写自定义异常,直接使用内置的 Nest HTTP 异常即可(详见下一节)。如需创建定制化异常,最佳实践是建立**异常层级结构** ,让自定义异常继承基础 `HttpException` 类。通过这种方式,Nest 能识别您的异常并自动处理错误响应。下面我们实现一个自定义异常: -```typescript title="forbidden.exception" + ```typescript title="forbidden.exception.ts" export class ForbiddenException extends HttpException { constructor() { super('Forbidden', HttpStatus.FORBIDDEN); @@ -104,7 +109,7 @@ export class ForbiddenException extends HttpException { 由于 `ForbiddenException` 继承自基础 `HttpException`,它能与内置异常处理器无缝协作,因此我们可在 `findAll()` 方法中直接使用。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Get() async findAll() { throw new ForbiddenException(); @@ -161,7 +166,7 @@ throw new BadRequestException('Something bad happened', { 让我们创建一个异常过滤器,负责捕获 `HttpException` 类的实例异常,并为它们实现自定义响应逻辑。为此,我们需要访问底层平台的 `Request` 和 `Response` 对象。我们将访问 `Request` 对象以提取原始 `url` 并将其包含在日志信息中。我们将使用 `Response` 对象通过 `response.json()` 方法直接控制发送的响应。 -```typescript title="http-exception.filter" + ```typescript title="http-exception.filter.ts" import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Request, Response } from 'express'; @@ -184,9 +189,13 @@ export class HttpExceptionFilter implements ExceptionFilter { } ``` -> info **提示** 所有异常过滤器都应实现泛型接口 `ExceptionFilter`。这要求您提供带有指定签名的 `catch(exception: T, host: ArgumentsHost)` 方法。`T` 表示异常的类型。 +:::info 提示 +所有异常过滤器都应实现泛型接口 `ExceptionFilter`。这要求您提供带有指定签名的 `catch(exception: T, host: ArgumentsHost)` 方法。`T` 表示异常的类型。 +::: -> warning **警告** 如果您使用 `@nestjs/platform-fastify`,可以用 `response.send()` 替代 `response.json()`。别忘了从 `fastify` 导入正确的类型。 +:::warning 警告 +如果您使用 `@nestjs/platform-fastify`,可以用 `response.send()` 替代 `response.json()`。别忘了从 `fastify` 导入正确的类型。 +::: `@Catch(HttpException)` 装饰器将所需的元数据绑定到异常过滤器,告诉 Nest 这个特定过滤器只查找 `HttpException` 类型的异常。`@Catch()` 装饰器可接受单个参数或以逗号分隔的列表,让您能一次性为多种异常类型设置过滤器。 @@ -200,7 +209,7 @@ export class HttpExceptionFilter implements ExceptionFilter { 让我们将新的 `HttpExceptionFilter` 绑定到 `CatsController` 的 `create()` 方法上。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() @UseFilters(new HttpExceptionFilter()) async create(@Body() createCatDto: CreateCatDto) { @@ -208,11 +217,14 @@ async create(@Body() createCatDto: CreateCatDto) { } ``` -> info **注意** `@UseFilters()` 装饰器是从 `@nestjs/common` 包中导入的。 +:::info 注意 +`@UseFilters()` 装饰器是从 `@nestjs/common` 包中导入的。 +::: + 我们在此使用了 `@UseFilters()` 装饰器。与 `@Catch()` 装饰器类似,它可以接收单个过滤器实例或以逗号分隔的过滤器实例列表。这里我们直接创建了 `HttpExceptionFilter` 的实例。或者,你也可以传入类(而非实例),将实例化的责任交给框架,并启用**依赖注入** 。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() @UseFilters(HttpExceptionFilter) async create(@Body() createCatDto: CreateCatDto) { @@ -220,11 +232,13 @@ async create(@Body() createCatDto: CreateCatDto) { } ``` -> info **提示** 尽可能通过类而非实例来应用过滤器。这会降低**内存消耗** ,因为 Nest 可以在整个模块中轻松复用相同类的实例。 +:::info 提示 +尽可能通过类而非实例来应用过滤器。这会降低**内存消耗** ,因为 Nest 可以在整个模块中轻松复用相同类的实例。 +::: 在上面的示例中,`HttpExceptionFilter` 仅应用于单个 `create()` 路由处理器,使其成为方法作用域的。异常过滤器可以具有不同的作用域级别:控制器/解析器/网关的方法作用域、控制器作用域或全局作用域。例如,要将过滤器设置为控制器作用域,可以这样做: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Controller() @UseFilters(new HttpExceptionFilter()) export class CatsController {} @@ -234,7 +248,7 @@ export class CatsController {} 要创建全局作用域的过滤器,需执行以下操作: -```typescript title="main" + ```typescript title="main.ts" async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new HttpExceptionFilter()); @@ -243,11 +257,13 @@ async function bootstrap() { bootstrap(); ``` -> warning **警告** `useGlobalFilters()` 方法不会为网关或混合应用设置过滤器。 +:::warning 警告 + `useGlobalFilters()` 方法不会为网关或混合应用设置过滤器。 +::: 全局作用域的过滤器用于整个应用程序,作用于每个控制器和每个路由处理器。在依赖注入方面,从任何模块外部注册的全局过滤器(如上述示例中使用 `useGlobalFilters()`)无法注入依赖项,因为这是在模块上下文之外完成的。为解决此问题,您可以使用以下构造**直接从任何模块**注册全局作用域的过滤器: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; @@ -262,7 +278,10 @@ import { APP_FILTER } from '@nestjs/core'; export class AppModule {} ``` -> info **注意** 使用此方法执行过滤器依赖注入时,需注意无论在哪一个模块中使用该构造,过滤器实际上是全局性的。应该在哪里进行操作呢?应选择过滤器(如上例中的 `HttpExceptionFilter`)被定义的模块。同时,`useClass` 并非处理自定义提供者注册的唯一方式。了解更多[请点击此处](/fundamentals/custom-providers) 。 +:::info 注意 +使用此方法执行过滤器依赖注入时,需注意无论在哪一个模块中使用该构造,过滤器实际上是全局性的。应该在哪里进行操作呢?应选择过滤器(如上例中的 `HttpExceptionFilter`)被定义的模块。同时,`useClass` 并非处理自定义提供者注册的唯一方式。了解更多[请点击此处](/fundamentals/custom-providers) 。 +::: + 您可以根据需要使用此技术添加任意数量的过滤器,只需将每个过滤器添加到 providers 数组中即可。 @@ -309,7 +328,9 @@ export class CatchEverythingFilter implements ExceptionFilter { } ``` -> warning **注意** 当将捕获所有异常的过滤器与绑定到特定类型的过滤器结合使用时,应首先声明"捕获所有"过滤器,以便特定过滤器能正确处理绑定类型。 +:::warning 注意 + 当将捕获所有异常的过滤器与绑定到特定类型的过滤器结合使用时,应首先声明"捕获所有"过滤器,以便特定过滤器能正确处理绑定类型。 +::: #### 继承 @@ -317,7 +338,7 @@ export class CatchEverythingFilter implements ExceptionFilter { 要将异常处理委托给基础过滤器,需要扩展 `BaseExceptionFilter` 并调用继承的 `catch()` 方法。 -```typescript title="all-exceptions.filter" + ```typescript title="all-exceptions.filter.ts" import { Catch, ArgumentsHost } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; @@ -329,7 +350,11 @@ export class AllExceptionsFilter extends BaseExceptionFilter { } ``` -> **警告** 扩展自 `BaseExceptionFilter` 的方法作用域和控制器作用域过滤器不应使用 `new` 实例化,而应由框架自动实例化。 +:::warning 警告 +扩展自 `BaseExceptionFilter` 的方法作用域和控制器作用域过滤器不应使用 `new` 实例化,而应由框架自动实例化。 +::: + + 全局过滤器**可以**扩展基础过滤器,可通过以下两种方式之一实现。 diff --git a/docs/overview/first-steps.md b/docs/overview/first-steps.md index ffd82df..fe050f7 100644 --- a/docs/overview/first-steps.md +++ b/docs/overview/first-steps.md @@ -58,7 +58,9 @@ $ npm i -g @nestjs/cli $ nest new project-name ``` -> 提示 要使用 TypeScript 的[更严格](https://www.typescriptlang.org/tsconfig#strict)功能集创建新项目,请在 `nest new` 命令中传递 `--strict` 标志。 +:::info 提示 +要使用 TypeScript 的[更严格](https://www.typescriptlang.org/tsconfig#strict)功能集创建新项目,请在 `nest new` 命令中传递 `--strict` 标志。 +::: `project-name` 目录将被创建,node 模块和一些其他样板文件将被安装,`src/` 目录将被创建并填充几个核心文件。 @@ -100,7 +102,9 @@ bootstrap(); 请注意,使用 Nest CLI 搭建的项目会创建一个初始项目结构,鼓励开发者遵循将每个模块保存在其自己的专用目录中的约定。 -> 提示 默认情况下,如果在创建应用程序时发生任何错误,你的应用程序将以代码 `1` 退出。如果你希望它抛出错误,请禁用 `abortOnError` 选项(例如,`NestFactory.create(AppModule, { abortOnError: false })`)。 +:::info 提示 + 默认情况下,如果在创建应用程序时发生任何错误,你的应用程序将以代码 `1` 退出。如果你希望它抛出错误,请禁用 `abortOnError` 选项(例如,`NestFactory.create(AppModule, { abortOnError: false })`)。 +::: ## 平台 diff --git a/docs/overview/guards.md b/docs/overview/guards.md index 69e4268..9698faa 100644 --- a/docs/overview/guards.md +++ b/docs/overview/guards.md @@ -8,13 +8,15 @@ 但中间件本质上是"哑"的,它不知道调用 `next()` 函数后会执行哪个处理程序。而**守卫**则能访问 `ExecutionContext` 实例,因此确切知道接下来要执行什么。与异常过滤器、管道和拦截器类似,守卫的设计让你能在请求/响应周期的精确时点介入处理逻辑,并以声明式方式实现。这有助于保持代码的 DRY 原则和声明式风格。 -> info **提示** 守卫在所有中间件**之后**执行,但在任何拦截器或管道**之前**执行。 +:::info 提示 +守卫在所有中间件**之后**执行,但在任何拦截器或管道**之前**执行。 +::: #### 授权守卫 如前所述, **授权**是守卫的绝佳应用场景,因为特定路由应当仅在调用者(通常是已认证的特定用户)拥有足够权限时才可用。我们将要构建的 `AuthGuard` 假设用户已通过认证(因此请求头中附带了令牌)。它将提取并验证令牌,利用提取的信息来判断是否允许该请求继续执行。 -```typescript title="auth.guard" + ```typescript title="auth.guard.ts" import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @@ -29,7 +31,9 @@ export class AuthGuard implements CanActivate { } ``` -> info **提示** 若需查看如何在应用中实现认证机制的实际案例,请访问[本章节](/security/authentication) 。同样地,如需更复杂的授权示例,请参阅[此页面](/security/authorization) 。 +:::info 提示 +若需查看如何在应用中实现认证机制的实际案例,请访问[本章节](/security/authentication) 。同样地,如需更复杂的授权示例,请参阅[此页面](/security/authorization) 。 +::: `validateRequest()` 函数内部的逻辑可根据需求简单或复杂处理。本示例的核心在于展示守卫如何融入请求/响应周期。 @@ -48,7 +52,7 @@ export class AuthGuard implements CanActivate { 我们来构建一个功能更完善的守卫,只允许特定角色的用户访问。我们将从一个基本的守卫模板开始,并在接下来的章节中逐步完善它。现在,它允许所有请求通过: -```typescript title="roles.guard" + ```typescript title="roles.guard.ts" import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @@ -72,7 +76,10 @@ export class RolesGuard implements CanActivate { export class CatsController {} ``` -> info **注意** `@UseGuards()` 装饰器是从 `@nestjs/common` 包导入的。 +:::info 注意 +`@UseGuards()` 装饰器是从 `@nestjs/common` 包导入的。 +::: + 上面,我们传递了 `RolesGuard` 类(而非实例),将实例化的责任交给框架处理,并启用了依赖注入。与管道和异常过滤器类似,我们也可以直接传递一个即时实例: @@ -91,11 +98,13 @@ const app = await NestFactory.create(AppModule); app.useGlobalGuards(new RolesGuard()); ``` -> warning **注意** 对于混合应用,`useGlobalGuards()` 方法默认不会为网关和微服务设置守卫(有关如何更改此行为的信息,请参阅[混合应用](/faq/hybrid-application) )。对于"标准"(非混合)微服务应用,`useGlobalGuards()` 会全局挂载守卫。 +:::warning 注意 +对于混合应用,`useGlobalGuards()` 方法默认不会为网关和微服务设置守卫(有关如何更改此行为的信息,请参阅[混合应用](/faq/hybrid-application) )。对于"标准"(非混合)微服务应用,`useGlobalGuards()` 会全局挂载守卫。 +::: 全局守卫用于整个应用程序,作用于每个控制器和每个路由处理器。在依赖注入方面,从任何模块外部注册的全局守卫(如上例中使用 `useGlobalGuards()`)无法注入依赖项,因为这发生在任何模块的上下文之外。为解决此问题,您可以直接从任何模块使用以下结构设置守卫: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; @@ -110,7 +119,10 @@ import { APP_GUARD } from '@nestjs/core'; export class AppModule {} ``` -> info **注意** 当使用此方法为守卫执行依赖注入时,请注意无论该结构应用于哪个模块,该守卫实际上是全局的。应在何处进行此操作?选择定义守卫的模块(如上例中的 `RolesGuard`)。此外,`useClass` 并非处理自定义提供程序注册的唯一方式。了解更多[此处](/fundamentals/custom-providers) 。 +:::info 注意 +当使用此方法为守卫执行依赖注入时,请注意无论该结构应用于哪个模块,该守卫实际上是全局的。应在何处进行此操作?选择定义守卫的模块(如上例中的 `RolesGuard`)。此外,`useClass` 并非处理自定义提供程序注册的唯一方式。了解更多[此处](/fundamentals/custom-providers) 。 +::: + #### 为每个处理器设置角色 @@ -130,7 +142,7 @@ export const Roles = Reflector.createDecorator(); 现在要使用这个装饰器,我们只需用它来注解处理器: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() @Roles(['admin']) async create(@Body() createCatDto: CreateCatDto) { @@ -146,7 +158,7 @@ async create(@Body() createCatDto: CreateCatDto) { 现在让我们回到 `RolesGuard` 并将其整合起来。目前它只是简单地返回 `true`,允许所有请求通过。我们希望根据**当前用户分配的角色**与当前处理路由所需实际角色的比较结果来条件化返回值。为了访问路由的角色(自定义元数据),我们将再次使用 `Reflector` 辅助类,如下所示: -```typescript title="roles.guard" + ```typescript title="roles.guard.ts" import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Roles } from './roles.decorator'; @@ -167,9 +179,13 @@ export class RolesGuard implements CanActivate { } ``` -> info **提示** 在 Node.js 环境中,通常会将授权用户附加到 `request` 对象上。因此,在上述示例代码中,我们假设 `request.user` 包含用户实例及其允许的角色。在您的应用中,您可能会在自定义的**认证守卫** (或中间件)中建立这种关联。有关此主题的更多信息,请参阅[本章节](/security/authentication) 。 +:::info 提示 +在 Node.js 环境中,通常会将授权用户附加到 `request` 对象上。因此,在上述示例代码中,我们假设 `request.user` 包含用户实例及其允许的角色。在您的应用中,您可能会在自定义的**认证守卫** (或中间件)中建立这种关联。有关此主题的更多信息,请参阅[本章节](/security/authentication) 。 +::: -> warning **警告** `matchRoles()` 函数内部的逻辑可以根据需要简单或复杂。本示例的主要目的是展示守卫如何融入请求/响应周期。 +:::warning 警告 + `matchRoles()` 函数内部的逻辑可以根据需要简单或复杂。本示例的主要目的是展示守卫如何融入请求/响应周期。 +::: 有关在上下文敏感方式中使用 `Reflector` 的更多细节,请参阅 **执行上下文** 章节中的 [反射与元数据](../fundamentals/execution-context#反射与元数据) 部分。 @@ -191,4 +207,8 @@ throw new UnauthorizedException(); 守卫抛出的任何异常都将由[异常处理层](/exception-filters) (全局异常过滤器及应用于当前上下文的任何异常过滤器)处理。 -> **提示** 如果您正在寻找如何实现授权的实际示例,请查看[本章节](/security/authorization) 。 +:::info 提示 +如果您正在寻找如何实现授权的实际示例,请查看[本章节](/security/authorization) 。 +::: + + diff --git a/docs/overview/interceptors.md b/docs/overview/interceptors.md index b823c32..096b832 100644 --- a/docs/overview/interceptors.md +++ b/docs/overview/interceptors.md @@ -32,7 +32,7 @@ 我们将探讨的第一个用例是使用拦截器记录用户交互(例如存储用户调用、异步派发事件或计算时间戳)。下面展示一个简单的 `LoggingInterceptor` 实现: -```typescript title="logging.interceptor" + ```typescript title="logging.interceptor.ts" import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @@ -52,9 +52,13 @@ export class LoggingInterceptor implements NestInterceptor { } ``` -> info **提示** `NestInterceptor` 是一个泛型接口,其中 `T` 表示 `Observable` 的类型(支持响应流),而 `R` 是 `Observable` 所包装值的类型。 +:::info 提示 +`NestInterceptor` 是一个泛型接口,其中 `T` 表示 `Observable` 的类型(支持响应流),而 `R` 是 `Observable` 所包装值的类型。 +::: -> warning **注意** 拦截器与控制器、提供者、守卫等一样,可以通过它们的 `constructor` 来**注入依赖** 。 +:::warning 注意 +拦截器与控制器、提供者、守卫等一样,可以通过它们的 `constructor` 来**注入依赖** 。 +::: 由于 `handle()` 返回一个 RxJS `Observable`,我们可以使用多种操作符来操作流。在上面的示例中,我们使用了 `tap()` 操作符,它会在可观察流正常或异常终止时调用我们的匿名日志记录函数,但不会干扰响应周期。 @@ -62,12 +66,14 @@ export class LoggingInterceptor implements NestInterceptor { 要设置拦截器,我们需要使用从 `@nestjs/common` 包导入的 `@UseInterceptors()` 装饰器。与[管道](/pipes)和[守卫](/guards)类似,拦截器可以作用于控制器范围、方法范围或全局范围。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @UseInterceptors(LoggingInterceptor) export class CatsController {} ``` -> info **提示** `@UseInterceptors()` 装饰器是从 `@nestjs/common` 包导入的。 +:::info 提示 +`@UseInterceptors()` 装饰器是从 `@nestjs/common` 包导入的。 +::: 通过上述设置,`CatsController` 中定义的每个路由处理器都将使用 `LoggingInterceptor`。当有人调用 `GET /cats` 端点时,您将在标准输出中看到以下内容: @@ -78,7 +84,7 @@ After... 1ms 请注意,我们传入的是 `LoggingInterceptor` 类(而不是实例),将实例化的责任交给框架并启用依赖注入。与管道、守卫和异常过滤器一样,我们也可以直接传入一个实例: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @UseInterceptors(new LoggingInterceptor()) export class CatsController {} ``` @@ -94,7 +100,7 @@ app.useGlobalInterceptors(new LoggingInterceptor()); 全局拦截器会作用于整个应用程序中的每个控制器和每个路由处理程序。在依赖注入方面,从任何模块外部注册的全局拦截器(如上述示例中使用 `useGlobalInterceptors()`)无法注入依赖项,因为这是在模块上下文之外完成的。为解决此问题,您可以使用以下构造**直接从任何模块中**设置拦截器: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; @@ -109,17 +115,21 @@ import { APP_INTERCEPTOR } from '@nestjs/core'; export class AppModule {} ``` -> info **提示** 使用此方法为拦截器执行依赖注入时,请注意无论该构造应用于哪个模块,拦截器实际上是全局的。应在何处进行此操作?选择定义拦截器的模块(如上例中的 `LoggingInterceptor`)。此外,`useClass` 并非处理自定义提供程序注册的唯一方式。了解更多[此处](/fundamentals/custom-providers) 。 +:::info 提示 +使用此方法为拦截器执行依赖注入时,请注意无论该构造应用于哪个模块,拦截器实际上是全局的。应在何处进行此操作?选择定义拦截器的模块(如上例中的 `LoggingInterceptor`)。此外,`useClass` 并非处理自定义提供程序注册的唯一方式。了解更多[此处](/fundamentals/custom-providers) 。 +::: #### 响应映射 我们已经知道 `handle()` 返回一个 `Observable`。该流包含从路由处理程序**返回**的值,因此我们可以轻松使用 RxJS 的 `map()` 操作符来改变它。 -> warning **警告** 响应映射功能不适用于库特定的响应策略(直接使用 `@Res()` 对象是被禁止的)。 +:::warning 警告 + 响应映射功能不适用于库特定的响应策略(直接使用 `@Res()` 对象是被禁止的)。 +::: 我们来创建 `TransformInterceptor`,它将通过简单修改每个响应来演示这个过程。它将使用 RxJS 的 `map()` 操作符,将响应对象赋值给新创建对象的 `data` 属性,然后将新对象返回给客户端。 -```typescript title="transform.interceptor" + ```typescript title="transform.interceptor.ts" import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -136,7 +146,9 @@ export class TransformInterceptor implements NestInterceptor> } ``` -> info **提示** Nest 拦截器同时支持同步和异步的 `intercept()` 方法。如果需要,你可以简单地将方法切换为 `async`。 +:::info 提示 +Nest 拦截器同时支持同步和异步的 `intercept()` 方法。如果需要,你可以简单地将方法切换为 `async`。 +::: 通过上述实现,当有人调用 `GET /cats` 端点时,响应将如下所示(假设路由处理程序返回一个空数组 `[]`): @@ -167,7 +179,7 @@ export class ExcludeNullInterceptor implements NestInterceptor { 另一个有趣的用例是利用 RxJS 的 `catchError()` 操作符来覆盖抛出的异常: -```typescript title="errors.interceptor" + ```typescript title="errors.interceptor.ts" import { Injectable, NestInterceptor, @@ -194,7 +206,7 @@ export class ErrorsInterceptor implements NestInterceptor { 有时我们可能希望完全阻止调用处理程序并返回不同的值,这有几个原因。一个明显的例子是实现缓存以提高响应时间。让我们看一个简单的**缓存拦截器** ,它从缓存中返回响应。在实际应用中,我们还需要考虑 TTL、缓存失效、缓存大小等其他因素,但这超出了本次讨论的范围。这里我们将提供一个展示核心概念的基础示例。 -```typescript title="cache.interceptor" + ```typescript title="cache.interceptor.ts" import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable, of } from 'rxjs'; @@ -216,7 +228,7 @@ export class CacheInterceptor implements NestInterceptor { 使用 RxJS 操作符操作流的能力为我们提供了许多可能性。让我们考虑另一个常见用例。假设你想处理路由请求的**超时**问题。当你的端点在一段时间后没有返回任何内容时,你希望以错误响应终止。以下结构实现了这一功能: -```typescript title="timeout.interceptor" + ```typescript title="timeout.interceptor.ts" import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common'; import { Observable, throwError, TimeoutError } from 'rxjs'; import { catchError, timeout } from 'rxjs/operators'; diff --git a/docs/overview/middlewares.md b/docs/overview/middlewares.md index 92d2b2f..491a0b1 100644 --- a/docs/overview/middlewares.md +++ b/docs/overview/middlewares.md @@ -19,9 +19,11 @@ 您可以在函数中或在带有 `@Injectable()` 装饰器的类中实现自定义 Nest 中间件。该类应该实现 `NestMiddleware` 接口,而函数没有任何特殊要求。让我们首先使用类方法实现一个简单的中间件功能。 -> warning **警告** `Express` 和 `fastify` 处理中间件的方式不同,并提供不同的方法签名,更多信息请阅读[此处](/techniques/performance#中间件)。 +:::warning 警告 +`Express` 和 `fastify` 处理中间件的方式不同,并提供不同的方法签名,更多信息请阅读[此处](/techniques/performance#中间件)。 +::: -```typescript title="logger.middleware" + ```typescript title="logger.middleware.ts" import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @@ -42,7 +44,7 @@ Nest 中间件完全支持依赖注入。与提供者和控制器一样,它们 在 `@Module()` 装饰器中没有中间件的位置。相反,我们使用模块类的 `configure()` 方法来设置它们。包含中间件的模块必须实现 `NestModule` 接口。让我们在 `AppModule` 级别设置 `LoggerMiddleware`。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; @@ -61,7 +63,7 @@ export class AppModule implements NestModule { 在上述示例中,我们为之前定义在 `CatsController` 中的 `/cats` 路由处理器配置了 `LoggerMiddleware`。在配置中间件时,我们还可以通过向 `forRoutes()` 方法传递包含路由 `path` 和请求 `method` 的对象来进一步限制中间件仅适用于特定请求方法。在下面的示例中,请注意我们导入了 `RequestMethod` 枚举来引用所需的请求方法类型。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; @@ -78,9 +80,13 @@ export class AppModule implements NestModule { } ``` -> info **提示** 可以通过使用 `async/await` 使 `configure()` 方法变为异步(例如,您可以在 `configure()` 方法体内 `await` 异步操作的完成)。 +:::info 提示 +可以通过使用 `async/await` 使 `configure()` 方法变为异步(例如,您可以在 `configure()` 方法体内 `await` 异步操作的完成)。 +::: -> warning **警告** 使用 `express` 适配器时,NestJS 应用默认会注册 `body-parser` 包中的 `json` 和 `urlencoded` 中间件。这意味着如果你想通过 `MiddlewareConsumer` 自定义该中间件,就需要在使用 `NestFactory.create()` 创建应用时将 `bodyParser` 标志设为 `false` 来禁用全局中间件。 +:::warning 警告 + 使用 `express` 适配器时,NestJS 应用默认会注册 `body-parser` 包中的 `json` 和 `urlencoded` 中间件。这意味着如果你想通过 `MiddlewareConsumer` 自定义该中间件,就需要在使用 `NestFactory.create()` 创建应用时将 `bodyParser` 标志设为 `false` 来禁用全局中间件。 +::: #### 路由通配符 @@ -93,7 +99,10 @@ forRoutes({ }); ``` -> info **注意** `splat` 仅仅是通配参数的名称,并无特殊含义。你可以随意命名它,例如 `*wildcard`。 +:::info 注意 +`splat` 仅仅是通配参数的名称,并无特殊含义。你可以随意命名它,例如 `*wildcard`。 +::: + 路由路径 `'abcd/*'` 将匹配 `abcd/1`、`abcd/123`、`abcd/abc` 等路径。基于字符串的路径会原样解析连字符(`-`)和点号(`.`)。但单独的 `abcd/` 不会匹配该路由,此时需要用花括号包裹通配符以使其可选: @@ -108,7 +117,7 @@ forRoutes({ `MiddlewareConsumer` 是一个辅助类,提供多个内置方法来管理中间件。这些方法都支持**链式调用**的[流畅风格](https://en.wikipedia.org/wiki/Fluent_interface) 。`forRoutes()` 方法可接收单个字符串、多个字符串、`RouteInfo` 对象、控制器类甚至多个控制器类。多数情况下只需传入逗号分隔的**控制器**列表。以下是单个控制器的示例: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; @@ -126,7 +135,11 @@ export class AppModule implements NestModule { } ``` -> **提示** `apply()` 方法既可接收单个中间件,也可通过多个参数指定[多个中间件](/overview/middlewares#多个中间件) 。 +:::info 提示 +`apply()` 方法既可接收单个中间件,也可通过多个参数指定[多个中间件](/overview/middlewares#多个中间件) 。 +::: + + #### 排除路由 @@ -145,7 +158,11 @@ consumer .forRoutes(CatsController); ``` -> **提示** :`exclude()` 方法支持使用 [path-to-regexp](https://github.com/pillarjs/path-to-regexp#parameters) 包进行通配符参数匹配。 +:::info 提示 +`exclude()` 方法支持使用 [path-to-regexp](https://github.com/pillarjs/path-to-regexp#parameters) 包进行通配符参数匹配。 +::: + + 在上述示例中,`LoggerMiddleware` 将被绑定到 `CatsController` 内定义的所有路由**除了**传递给 `exclude()` 方法的三个路由。 @@ -155,7 +172,7 @@ consumer 我们一直使用的 `LoggerMiddleware` 类非常简单。它没有成员变量、没有额外方法、也没有依赖项。为什么我们不能直接用一个简单函数来定义它,而非要用类呢?实际上是可以的。这种类型的中间件被称为**函数式中间件** 。让我们将基于类的日志中间件转换为函数式中间件来说明两者的区别: -```typescript title="logger.middleware" + ```typescript title="logger.middleware.ts" import { Request, Response, NextFunction } from 'express'; export function logger(req: Request, res: Response, next: NextFunction) { @@ -166,13 +183,15 @@ export function logger(req: Request, res: Response, next: NextFunction) { 并在 `AppModule` 中使用它: -```typescript title="app.module" + ```typescript title="app.module.ts" consumer .apply(logger) .forRoutes(CatsController); ``` -> info **提示** 当您的中间件不需要任何依赖项时,请考虑使用更简单的 **函数式中间件** 替代方案。 +:::info 提示 +当您的中间件不需要任何依赖项时,请考虑使用更简单的 **函数式中间件** 替代方案。 +::: #### 多个中间件 @@ -186,10 +205,13 @@ consumer.apply(cors(), helmet(), logger).forRoutes(CatsController); 如果我们需要一次性将中间件绑定到所有已注册的路由,可以使用 `INestApplication` 实例提供的 `use()` 方法: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.create(AppModule); app.use(logger); await app.listen(process.env.PORT ?? 3000); ``` -> info **注意** 在全局中间件中无法访问 DI 容器。使用 `app.use()` 时,可以改用[函数式中间件](middleware#函数式中间件) 。或者,也可以使用类中间件并通过 `AppModule`(或其他模块)中的 `.forRoutes('*')` 来消费它。 +:::info 注意 +在全局中间件中无法访问 DI 容器。使用 `app.use()` 时,可以改用[函数式中间件](middleware#函数式中间件) 。或者,也可以使用类中间件并通过 `AppModule`(或其他模块)中的 `.forRoutes('*')` 来消费它。 +::: + diff --git a/docs/overview/modules.md b/docs/overview/modules.md index 1d9ff8f..3886d64 100644 --- a/docs/overview/modules.md +++ b/docs/overview/modules.md @@ -8,11 +8,11 @@ `@Module()` 装饰器接收一个带有描述模块属性的对象: -| | | -| ----------- | --------------------------------------------------------------------------------------------------------------- | -| providers | 将由 Nest 注入器实例化,且至少可在本模块内共享的提供者 | -| controllers | 本模块中定义的需要实例化的控制器集合 | -| imports | 导入模块的列表,这些模块导出了本模块所需的提供者 | +| 属性 | | +|-------------|-------------------------------------------------------------------| +| providers | 将由 Nest 注入器实例化,且至少可在本模块内共享的提供者 | +| controllers | 本模块中定义的需要实例化的控制器集合 | +| imports | 导入模块的列表,这些模块导出了本模块所需的提供者 | | exports | 本模块提供的 providers 子集,这些提供者应可供导入本模块的其他模块使用。可以使用提供者本身或其令牌(provide 值) | 默认情况下,模块**封装**了提供者,这意味着您只能注入属于当前模块或从其他导入模块显式导出的提供者。模块导出的提供者本质上充当了该模块的公共接口或 API。 @@ -23,7 +23,7 @@ 接下来,我们将创建 `CatsModule` 来演示如何将控制器和服务进行分组。 -```typescript title="cats/cats.module" + ```typescript title="cats/cats.module.ts" import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @@ -35,11 +35,13 @@ import { CatsService } from './cats.service'; export class CatsModule {} ``` -> info **提示** 要使用 CLI 创建模块,只需执行 `$ nest g module cats` 命令。 +:::info 提示 +要使用 CLI 创建模块,只需执行 `$ nest g module cats` 命令。 +::: 如上所述,我们在 `cats.module.ts` 文件中定义了 `CatsModule`,并将与此模块相关的所有内容移至 `cats` 目录。最后需要做的是将此模块导入根模块(即 `AppModule`,定义在 `app.module.ts` 文件中)。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { CatsModule } from './cats/cats.module'; @@ -81,7 +83,7 @@ export class AppModule {} 每个模块自动成为**共享模块** 。一旦创建,它就可以被任何模块重复使用。假设我们想在多个其他模块之间共享 `CatsService` 的实例。为此,我们首先需要通过将该提供者添加到模块的 `exports` 数组来**导出** `CatsService`,如下所示: -```typescript title="cats.module" + ```typescript title="cats.module.ts" import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @@ -118,7 +120,7 @@ export class CoreModule {} 模块类本身也可以 **注入** 提供者(例如用于配置目的): -```typescript title="cats.module" + ```typescript title="cats.module.ts" import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @@ -156,7 +158,9 @@ export class CatsModule {} `@Global()` 装饰器使模块具有全局作用域。全局模块通常应由根模块或核心模块**仅注册一次**。在上例中,`CatsService` 提供者将无处不在,希望注入该服务的模块无需在其 imports 数组中导入 `CatsModule`。 -> info **提示** 从设计实践角度不推荐将所有内容全局化。虽然全局模块能减少样板代码,但通常更好的做法是使用 `imports` 数组来可控且清晰地暴露模块 API 给其他模块。这种方式能提供更好的结构和可维护性,确保只共享模块的必要部分,同时避免应用无关部分之间产生不必要的耦合。 +:::info 提示 +从设计实践角度不推荐将所有内容全局化。虽然全局模块能减少样板代码,但通常更好的做法是使用 `imports` 数组来可控且清晰地暴露模块 API 给其他模块。这种方式能提供更好的结构和可维护性,确保只共享模块的必要部分,同时避免应用无关部分之间产生不必要的耦合。 +::: #### 动态模块 @@ -183,7 +187,9 @@ export class DatabaseModule { } ``` -> info **提示** `forRoot()` 方法可以同步或异步(例如通过 `Promise`)返回动态模块。 +:::info 提示 +`forRoot()` 方法可以同步或异步(例如通过 `Promise`)返回动态模块。 +::: 该模块默认定义了 `Connection` 提供者(在 `@Module()` 装饰器元数据中),此外根据传入 `forRoot()` 方法的 `entities` 和 `options` 对象,还会暴露一系列提供者,例如存储库。请注意动态模块返回的属性会**扩展** (而非覆盖)`@Module()` 装饰器中定义的基础模块元数据。这样既保留了静态声明的 `Connection` 提供者**又**能导出动态生成的存储库提供者。 @@ -198,7 +204,9 @@ export class DatabaseModule { } ``` -> warning **警告** 如前所述,将所有内容全局化**并非良好的设计决策**。 +:::warning 警告 +如前所述,将所有内容全局化**并非良好的设计决策**。 +::: 可按以下方式导入并配置 `DatabaseModule`: @@ -229,4 +237,7 @@ export class AppModule {} [动态模块](/fundamentals/dynamic-modules)章节对此主题有更详细讲解,并包含一个[实际示例](https://github.com/nestjs/nest/tree/master/sample/25-dynamic-modules) 。 -> info **提示** 通过[本章节](/fundamentals/dynamic-modules#可配置模块构建器)学习如何使用 `ConfigurableModuleBuilder` 构建高度可定制的动态模块。 +:::info 提示 + 通过[本章节](/fundamentals/dynamic-modules#可配置模块构建器)学习如何使用 `ConfigurableModuleBuilder` 构建高度可定制的动态模块。 +::: + diff --git a/docs/overview/pipes.md b/docs/overview/pipes.md index 80a2383..cbc8a80 100644 --- a/docs/overview/pipes.md +++ b/docs/overview/pipes.md @@ -13,7 +13,9 @@ Nest 内置了多种开箱即用的管道。您也可以构建自定义管道。本章将介绍内置管道及其与路由处理器的绑定方式,随后通过几个自定义管道示例展示如何从零开始构建管道。 -> info **提示** 管道在异常区域内运行。这意味着当管道抛出异常时,该异常将由异常层处理(全局异常过滤器以及应用于当前上下文的任何[异常过滤器](/exception-filters) )。鉴于上述情况,应当明确的是:当管道中抛出异常时,后续不会执行任何控制器方法。这为你在系统边界验证来自外部源输入应用程序的数据提供了一种最佳实践技术。 +:::info 提示 +管道在异常区域内运行。这意味着当管道抛出异常时,该异常将由异常层处理(全局异常过滤器以及应用于当前上下文的任何[异常过滤器](/exception-filters) )。鉴于上述情况,应当明确的是:当管道中抛出异常时,后续不会执行任何控制器方法。这为你在系统边界验证来自外部源输入应用程序的数据提供了一种最佳实践技术。 +::: #### 内置管道 @@ -97,11 +99,17 @@ async findOne(@Param('uuid', new ParseUUIDPipe()) uuid: string) { } ``` -> info **注意** 使用 `ParseUUIDPipe()` 时会解析版本 3、4 或 5 的 UUID,若只需特定版本的 UUID,可在管道选项中传入版本号。 +:::info 注意 +使用 `ParseUUIDPipe()` 时会解析版本 3、4 或 5 的 UUID,若只需特定版本的 UUID,可在管道选项中传入版本号。 +::: + 上文我们已了解如何绑定各类内置的 `Parse*` 解析管道。绑定验证管道略有不同,我们将在下一节详细讨论。 -> info **注意** 另请参阅[验证技术](/techniques/validation)章节获取验证管道的详细示例。 +:::info 注意 +另请参阅[验证技术](/techniques/validation)章节获取验证管道的详细示例。 +::: + #### 自定义管道 @@ -109,7 +117,7 @@ async findOne(@Param('uuid', new ParseUUIDPipe()) uuid: string) { 我们从简单的 `ValidationPipe` 开始。最初,我们让它简单地接收一个输入值并立即返回相同的值,表现得像一个恒等函数。 -```typescript title="validation.pipe" + ```typescript title="validation.pipe.ts" import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() @@ -120,7 +128,9 @@ export class ValidationPipe implements PipeTransform { } ``` -> info **提示**`PipeTransform` 是一个必须由所有管道实现的泛型接口。该泛型接口使用 `T` 表示输入 `value` 的类型,`R` 表示 `transform()` 方法的返回类型。 +:::info 提示 +`PipeTransform` 是一个必须由所有管道实现的泛型接口。该泛型接口使用 `T` 表示输入 `value` 的类型,`R` 表示 `transform()` 方法的返回类型。 +::: 每个管道都必须实现 `transform()` 方法来满足 `PipeTransform` 接口契约。该方法有两个参数: @@ -145,7 +155,11 @@ export interface ArgumentMetadata { | `metatype` | 提供参数的元类型,例如 `String`。注意:该值为 `undefined`,如果您在路由处理方法签名中省略类型声明,或使用原生 JavaScript。 | | `data` | 传递给装饰器的字符串,例如 `@Body('string')`。如果装饰器括号留空,则为 `undefined`。 | -> **警告** TypeScript 接口在转译过程中会被移除。因此,如果方法参数的类型声明为接口而非类,`metatype` 的值将会是 `Object`。 +:::warning 警告 +TypeScript 接口在转译过程中会被移除。因此,如果方法参数的类型声明为接口而非类,`metatype` 的值将会是 `Object`。 +::: + + #### 基于模式的验证 @@ -160,7 +174,7 @@ async create(@Body() createCatDto: CreateCatDto) { 让我们重点关注 `createCatDto` 这个 body 参数。它的类型是 `CreateCatDto`: -```typescript title="create-cat.dto" + ```typescript title="create-cat.dto.ts" export class CreateCatDto { name: string; age: number; @@ -242,7 +256,7 @@ export type CreateCatDto = z.infer; 我们通过使用如下所示的 `@UsePipes()` 装饰器来实现: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() @UsePipes(new ZodValidationPipe(createCatSchema)) async create(@Body() createCatDto: CreateCatDto) { @@ -250,13 +264,19 @@ async create(@Body() createCatDto: CreateCatDto) { } ``` -> info **提示** `@UsePipes()` 装饰器需要从 `@nestjs/common` 包中导入。 +:::info 提示 +`@UsePipes()` 装饰器需要从 `@nestjs/common` 包中导入。 +::: -> warning **注意** `zod` 库要求在你的 `tsconfig.json` 文件中启用 `strictNullChecks` 配置。 +:::warning 注意 + `zod` 库要求在你的 `tsconfig.json` 文件中启用 `strictNullChecks` 配置。 +::: #### 类验证器 -> warning **警告** 本节中的技术需要使用 TypeScript,如果你的应用使用原生 JavaScript 编写则无法使用。 +:::warning 警告 + 本节中的技术需要使用 TypeScript,如果你的应用使用原生 JavaScript 编写则无法使用。 +::: 让我们来看另一种验证技术的实现方案。 @@ -268,7 +288,7 @@ $ npm i --save class-validator class-transformer 安装完成后,我们就可以给 `CreateCatDto` 类添加一些装饰器了。这里我们可以看到这项技术的一个显著优势:`CreateCatDto` 类仍然是 Post 请求体对象的唯一真实来源(而不需要创建单独的验证类)。 -```typescript title="create-cat.dto" + ```typescript title="create-cat.dto.ts" import { IsString, IsInt } from 'class-validator'; export class CreateCatDto { @@ -283,11 +303,13 @@ export class CreateCatDto { } ``` -> info **提示** 了解更多关于 class-validator 装饰器的信息,请点击[此处](https://github.com/typestack/class-validator#用法) 。 +:::info 提示 +了解更多关于 class-validator 装饰器的信息,请点击[此处](https://github.com/typestack/class-validator#用法) 。 +::: 现在我们可以创建一个使用这些注解的 `ValidationPipe` 类。 -```typescript title="validation.pipe" + ```typescript title="validation.pipe.ts" import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; import { validate } from 'class-validator'; import { plainToInstance } from 'class-transformer'; @@ -313,9 +335,13 @@ export class ValidationPipe implements PipeTransform { } ``` -> info **提示** 需要提醒的是,您不必自己构建通用验证管道,因为 Nest 已经内置提供了 `ValidationPipe`。内置的 `ValidationPipe` 比本章构建的示例提供了更多选项,本章示例保持基础性是为了说明自定义管道的机制。您可以[在此处](/techniques/validation)找到完整细节及大量示例。 +:::info 提示 +需要提醒的是,您不必自己构建通用验证管道,因为 Nest 已经内置提供了 `ValidationPipe`。内置的 `ValidationPipe` 比本章构建的示例提供了更多选项,本章示例保持基础性是为了说明自定义管道的机制。您可以[在此处](/techniques/validation)找到完整细节及大量示例。 +::: -> warning **注意** 我们上面使用了 [class-transformer](https://github.com/typestack/class-transformer) 库,它与 **class-validator** 库由同一作者开发,因此它们能完美协同工作。 +:::warning 注意 + 我们上面使用了 [class-transformer](https://github.com/typestack/class-transformer) 库,它与 **class-validator** 库由同一作者开发,因此它们能完美协同工作。 +::: 让我们来看这段代码。首先注意 `transform()` 方法被标记为 `async`,这是因为 Nest 同时支持同步和**异步**管道。我们将这个方法设为 `async` 是因为某些 class-validator 验证[可能是异步的](https://github.com/typestack/class-validator#custom-validation-classes) (使用了 Promise)。 @@ -329,7 +355,7 @@ export class ValidationPipe implements PipeTransform { 最后一步是绑定 `ValidationPipe`。管道可以作用于参数范围、方法范围、控制器范围或全局范围。之前在使用基于 Zod 的验证管道时,我们看到了在方法级别绑定管道的示例。在下面的示例中,我们将把管道实例绑定到路由处理器的 `@Body()` 装饰器上,这样就会调用我们的管道来验证 post body。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() async create( @Body(new ValidationPipe()) createCatDto: CreateCatDto, @@ -344,7 +370,7 @@ async create( 由于 `ValidationPipe` 被设计为尽可能通用,我们可以通过将其设置为**全局作用域**管道来充分发挥其效用,这样它就会应用到整个应用程序的每个路由处理器上。 -```typescript title="main" + ```typescript title="main.ts" async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); @@ -359,7 +385,7 @@ warning **注意** 对于[混合应用](faq/hybrid-application) ,`useGlobalPip 请注意,在依赖注入方面,从任何模块外部注册的全局管道(如上例中使用 `useGlobalPipes()`)无法注入依赖项,因为绑定是在任何模块上下文之外完成的。为解决此问题,您可以使用以下构造**直接从任何模块**设置全局管道: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { APP_PIPE } from '@nestjs/core'; @@ -374,7 +400,9 @@ import { APP_PIPE } from '@nestjs/core'; export class AppModule {} ``` -> info **提示** 当使用这种方法为管道执行依赖注入时,请注意无论该构造应用于哪个模块,该管道实际上都是全局的。应该在何处进行此操作?选择定义管道(上例中的 `ValidationPipe`)的模块。此外,`useClass` 并非处理自定义提供程序注册的唯一方式。了解更多[此处](/fundamentals/custom-providers) 。 +:::info 提示 +当使用这种方法为管道执行依赖注入时,请注意无论该构造应用于哪个模块,该管道实际上都是全局的。应该在何处进行此操作?选择定义管道(上例中的 `ValidationPipe`)的模块。此外,`useClass` 并非处理自定义提供程序注册的唯一方式。了解更多[此处](/fundamentals/custom-providers) 。 +::: #### 内置的 ValidationPipe @@ -388,7 +416,7 @@ export class AppModule {} 这里有一个简单的 `ParseIntPipe`,负责将字符串解析为整数值。(如前所述,Nest 框架内置了一个更复杂的 `ParseIntPipe`;我们在此展示这个自定义转换管道的简单示例)。 -```typescript title="parse-int.pipe" + ```typescript title="parse-int.pipe.ts" import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; @Injectable() diff --git a/docs/overview/providers.md b/docs/overview/providers.md index d917637..e5051ec 100644 --- a/docs/overview/providers.md +++ b/docs/overview/providers.md @@ -1,6 +1,6 @@ # 提供者 -提供者(Provider)是 Nest 的核心概念之一。许多基础的 Nest 类(如服务、存储库、工厂和辅助工具)都可以被视为提供者。提供者的核心特性在于它能够作为依赖项被注入到其他类中。默认情况下,提供者的生命周期与应用程序的生命周期一致:启动时:所有依赖项会被解析,每个提供者实例化一次(单例模式)。关闭时:这些实例会被销毁。但 Nest 也支持将提供者设置为请求作用域(request-scoped),此时其生命周期与单个 HTTP 请求绑定,而非整个应用程序。更多细节可参考[注入作用域](/fundamentals/injection-scopes)。 +提供者(Provider)是 Nest 的核心概念之一。许多基础的 Nest 类(如服务、存储库、工厂和辅助工具)都可以被视为提供者。提供者的核心特性在于它能够作为依赖项被注入到其他类中。默认情况下,提供者的生命周期与应用程序的生命周期一致:启动时:所有依赖项会被解析,每个提供者实例化一次(单例模式)。关闭时:这些实例会被销毁。但 Nest 也支持将提供者设置为请求作用域(request-scoped),此时其生命周期与单个 HTTP 请求绑定,而非整个应用程序。更多细节可参考[注入作用域](/fundamentals/provider-scopes)。 这使得对象之间能够形成各种关联关系。这些对象的"连接"工作主要由 Nest 运行时系统负责处理。 @@ -8,13 +8,16 @@ 在前一章中,我们创建了一个简单的 `CatsController`。控制器应当处理 HTTP 请求,并将更复杂的任务委托给**提供者** 。提供者是在 NestJS 模块中被声明为 `providers` 的普通 JavaScript 类。更多细节请参阅"模块"章节。 -> info **注意** 由于 Nest 允许您以面向对象的方式设计和组织依赖关系,我们强烈建议遵循 [SOLID 原则](https://en.wikipedia.org/wiki/SOLID) 。 +:::info 注意 +由于 Nest 允许您以面向对象的方式设计和组织依赖关系,我们强烈建议遵循 [SOLID 原则](https://en.wikipedia.org/wiki/SOLID) 。 +::: + #### 服务 让我们从创建一个简单的 `CatsService` 开始。该服务将处理数据存储和检索,并将被 `CatsController` 使用。由于其在管理应用逻辑中的角色,它非常适合被定义为一个提供者。 -```typescript title="cats.service" + ```typescript title="cats.service.ts" import { Injectable } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; @@ -32,13 +35,15 @@ export class CatsService { } ``` -> info **提示** 要使用 CLI 创建服务,只需执行 `$ nest g service cats` 命令。 +:::info 提示 +要使用 CLI 创建服务,只需执行 `$ nest g service cats` 命令。 +::: 我们的 `CatsService` 是一个具有一个属性和两个方法的基础类。这里的关键添加是 `@Injectable()` 装饰器。该装饰器将元数据附加到类上,表明 `CatsService` 是一个可以由 Nest[IoC](https://en.wikipedia.org/wiki/Inversion_of_control) 容器管理的类。 此外,这个示例使用了 `Cat` 接口,其定义大致如下: -```typescript title="interfaces/cat.interface" + ```typescript title="interfaces/cat.interface.ts" export interface Cat { name: string; age: number; @@ -48,7 +53,7 @@ export interface Cat { 现在我们有了获取猫数据的服务类,让我们在 `CatsController` 中使用它: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" import { Controller, Get, Post, Body } from '@nestjs/common'; import { CreateCatDto } from './dto/create-cat.dto'; import { CatsService } from './cats.service'; @@ -84,7 +89,7 @@ constructor(private catsService: CatsService) {} #### 作用域 -提供程序通常具有与应用程序生命周期一致的生存期("作用域")。当应用程序启动时,每个依赖项都必须被解析,这意味着每个提供程序都会被实例化。同样,当应用程序关闭时,所有提供程序都会被销毁。但也可以将提供程序设置为**请求作用域** ,这意味着其生存期与特定请求而非应用程序生命周期相关联。您可以在[注入作用域](/fundamentals/injection-scopes)章节中了解更多相关技术。 +提供程序通常具有与应用程序生命周期一致的生存期("作用域")。当应用程序启动时,每个依赖项都必须被解析,这意味着每个提供程序都会被实例化。同样,当应用程序关闭时,所有提供程序都会被销毁。但也可以将提供程序设置为**请求作用域** ,这意味着其生存期与特定请求而非应用程序生命周期相关联。您可以在[注入作用域](/fundamentals/provider-scopes)章节中了解更多相关技术。 #### 自定义提供程序 @@ -121,13 +126,15 @@ export class HttpService { } ``` -> warning **警告** 如果你的类没有继承其他类,通常最好使用**基于构造函数**的注入方式。构造函数能明确指定所需的依赖项,相比使用 `@Inject` 注解的类属性,这种方式提供了更好的可见性并使代码更易于理解。 +:::warning 警告 +如果你的类没有继承其他类,通常最好使用**基于构造函数**的注入方式。构造函数能明确指定所需的依赖项,相比使用 `@Inject` 注解的类属性,这种方式提供了更好的可见性并使代码更易于理解。 +::: #### 提供者注册 既然我们已经定义了一个提供者(`CatsService`)和一个消费者(`CatsController`),现在需要将该服务注册到 Nest 中以便处理依赖注入。这需要通过编辑模块文件(`app.module.ts`)并将服务添加到 `@Module()` 装饰器的 `providers` 数组来实现。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { CatsController } from './cats/cats.controller'; import { CatsService } from './cats/cats.service'; diff --git a/docs/recipes/async-local-storage.md b/docs/recipes/async-local-storage.md index bdc8238..48d163c 100644 --- a/docs/recipes/async-local-storage.md +++ b/docs/recipes/async-local-storage.md @@ -12,11 +12,15 @@ NestJS 本身并未为 `AsyncLocalStorage` 提供任何内置抽象,因此让我们通过最简单的 HTTP 案例来了解如何自行实现,以便更好地理解整个概念: -> **提示** 如需使用现成的[专用包](#nestjs-cls),请继续阅读下文。 +:::info 提示 +如需使用现成的[专用包](#nestjs-cls),请继续阅读下文。 +::: + + 1. 首先,在某个共享源文件中创建一个新的 `AsyncLocalStorage` 实例。由于我们使用 NestJS,让我们也将其转换为带有自定义提供者的模块。 -```typescript title="als.module.ts" + ```typescript title="als.module.ts" @Module({ providers: [ { @@ -29,11 +33,15 @@ NestJS 本身并未为 `AsyncLocalStorage` 提供任何内置抽象,因此让 export class AlsModule {} ``` -> **提示** `AsyncLocalStorage` 是从 `async_hooks` 导入的。 +:::info 提示 +`AsyncLocalStorage` 是从 `async_hooks` 导入的。 +::: + + 2. 我们只关注 HTTP,所以让我们使用中间件将 `next` 函数用 `AsyncLocalStorage#run` 包装起来。由于中间件是请求最先到达的地方,这将使得 `store` 在所有增强器和系统其余部分中都可用。 -```typescript title="app.module.ts" + ```typescript title="app.module.ts" @Module({ imports: [AlsModule], providers: [CatsService], @@ -85,7 +93,11 @@ export class CatsService { 4. 就这样,我们现在有了无需注入整个 `REQUEST` 对象就能共享请求相关状态的方法。 -> **警告** 请注意,虽然该技术在许多用例中很有用,但它本质上会使代码流程变得晦涩(创建隐式上下文),因此请负责任地使用它,尤其要避免创建上下文式的" [上帝对象](https://en.wikipedia.org/wiki/God_object) "。 +:::warning 警告 +请注意,虽然该技术在许多用例中很有用,但它本质上会使代码流程变得晦涩(创建隐式上下文),因此请负责任地使用它,尤其要避免创建上下文式的" [上帝对象](https://en.wikipedia.org/wiki/God_object) "。 +::: + + ### NestJS CLS @@ -93,7 +105,9 @@ export class CatsService { 然后可以通过可注入的 `ClsService` 访问存储,或者通过使用[代理提供者](https://www.npmjs.com/package/nestjs-cls#proxy-providers)将其完全从业务逻辑中抽象出来。 -> info **nestjs-cls**`nestjs-cls` 是第三方包,不由 NestJS 核心团队维护。如发现该库的任何问题,请在[相应仓库](https://github.com/Papooch/nestjs-cls/issues)中报告。 +:::info nestjs-cls +`nestjs-cls` 是第三方包,不由 NestJS 核心团队维护。如发现该库的任何问题,请在[相应仓库](https://github.com/Papooch/nestjs-cls/issues)中报告。 +::: #### 安装 @@ -159,7 +173,9 @@ export interface MyClsStore extends ClsStore { } ``` -> info **提示** 也可以让包自动生成一个请求 ID,稍后通过 `cls.getId()` 访问它,或者使用 `cls.get(CLS_REQ)` 获取整个请求对象。 +:::info 提示 +也可以让包自动生成一个请求 ID,稍后通过 `cls.getId()` 访问它,或者使用 `cls.get(CLS_REQ)` 获取整个请求对象。 +::: #### 测试 diff --git a/docs/recipes/cqrs.md b/docs/recipes/cqrs.md index 1ea4c0d..ac3854d 100644 --- a/docs/recipes/cqrs.md +++ b/docs/recipes/cqrs.md @@ -51,7 +51,7 @@ export class AppModule {} 命令用于改变应用程序状态。它们应基于任务而非以数据为中心。当命令被分派时,将由对应的**命令处理器**进行处理。该处理器负责更新应用程序状态。 -```typescript title="heroes-game.service" + ```typescript title="heroes-game.service.ts" @Injectable() export class HeroesGameService { constructor(private commandBus: CommandBus) {} @@ -66,7 +66,7 @@ export class HeroesGameService { 在上述代码片段中,我们实例化了 `KillDragonCommand` 类并将其传递给 `CommandBus` 的 `execute()` 方法。以下是演示的命令类: -```typescript title="kill-dragon.command" + ```typescript title="kill-dragon.command.ts" export class KillDragonCommand extends Command<{ actionId: string // This type represents the command execution result }> { @@ -79,13 +79,15 @@ export class KillDragonCommand extends Command<{ 如你所见,`KillDragonCommand` 类继承自 `Command` 类。`Command` 是从 `@nestjs/cqrs` 包导出的简单工具类,可用于定义命令的返回类型。本例中返回类型是一个包含 `actionId` 属性的对象。现在每当 `KillDragonCommand` 命令被派发时,`CommandBus#execute()` 方法的返回类型将被推断为 `Promise<{ actionId: string }>` 。这在需要从命令处理器返回数据时非常有用。 -> info **提示** 继承 `Command` 类是可选的,仅当需要定义命令返回类型时才必须使用。 +:::info 提示 +继承 `Command` 类是可选的,仅当需要定义命令返回类型时才必须使用。 +::: `CommandBus` 表示一个命令**流** ,负责将命令分派给相应的处理程序。`execute()` 方法返回一个 Promise,该 Promise 会解析为处理程序返回的值。 让我们为 `KillDragonCommand` 命令创建一个处理程序。 -```typescript title="kill-dragon.handler" + ```typescript title="kill-dragon.handler.ts" @CommandHandler(KillDragonCommand) export class KillDragonHandler implements ICommandHandler { constructor(private repository: HeroesRepository) {} @@ -131,7 +133,7 @@ export class GetHeroQuery extends Query { 要获取英雄数据,我们需要创建一个查询处理器: -```typescript title="get-hero.handler" + ```typescript title="get-hero.handler.ts" @QueryHandler(GetHeroQuery) export class GetHeroHandler implements IQueryHandler { constructor(private repository: HeroesRepository) {} @@ -162,7 +164,7 @@ const hero = await this.queryBus.execute(new GetHeroQuery(heroId)); // "hero" wi 出于演示目的,让我们创建一个事件类: -```typescript title="hero-killed-dragon.event" + ```typescript title="hero-killed-dragon.event.ts" export class HeroKilledDragonEvent { constructor( public readonly heroId: string, @@ -173,7 +175,7 @@ export class HeroKilledDragonEvent { 虽然可以直接使用 `EventBus.publish()` 方法派发事件,但我们也可以从模型中进行派发。让我们更新 `Hero` 模型,使其在调用 `killEnemy()` 方法时派发 `HeroKilledDragonEvent` 事件。 -```typescript title="hero.model" + ```typescript title="hero.model.ts" export class Hero extends AggregateRoot { constructor(private id: string) { super(); @@ -188,7 +190,7 @@ export class Hero extends AggregateRoot { `apply()` 方法用于派发事件,它接受一个事件对象作为参数。但由于我们的模型并不知道 `EventBus` 的存在,我们需要将其与模型关联。这可以通过使用 `EventPublisher` 类来实现。 -```typescript title="kill-dragon.handler" + ```typescript title="kill-dragon.handler.ts" @CommandHandler(KillDragonCommand) export class KillDragonHandler implements ICommandHandler { constructor( @@ -235,11 +237,13 @@ const hero = new HeroModel('id'); // <-- HeroModel is a class this.eventBus.publish(new HeroKilledDragonEvent()); ``` -> info **提示** `EventBus` 是一个可注入的类。 +:::info 提示 +`EventBus` 是一个可注入的类。 +::: 每个事件可以包含多个**事件处理器** 。 -```typescript title="hero-killed-dragon.handler" + ```typescript title="hero-killed-dragon.handler.ts" @EventsHandler(HeroKilledDragonEvent) export class HeroKilledDragonHandler implements IEventHandler { constructor(private repository: HeroesRepository) {} @@ -250,7 +254,11 @@ export class HeroKilledDragonHandler implements IEventHandler **提示** 请注意,当你开始使用事件处理器时,你将脱离传统的 HTTP 网络上下文。 +:::info 提示 +请注意,当你开始使用事件处理器时,你将脱离传统的 HTTP 网络上下文。 +::: + + > > - `命令处理器`中的错误仍可被内置的[异常过滤器](/exception-filters)捕获。 > - `事件处理器`中的错误无法被异常过滤器捕获:你必须手动处理它们。可以通过简单的 `try/catch`,使用 [Sagas](../recipes/cqrs#sagas) 触发补偿事件,或选择其他任何解决方案。 @@ -271,7 +279,7 @@ Saga 是一个极其强大的功能。单个 saga 可以监听 1..\* 个事件 让我们创建一个 saga,它监听 `HeroKilledDragonEvent` 并分发 `DropAncientItemCommand` 命令。 -```typescript title="heroes-game.saga" + ```typescript title="heroes-game.saga.ts" @Injectable() export class HeroesGameSagas { @Saga() @@ -284,7 +292,9 @@ export class HeroesGameSagas { } ``` -> info **提示** `ofType` 操作符和 `@Saga()` 装饰器是从 `@nestjs/cqrs` 包中导出的。 +:::info 提示 +`ofType` 操作符和 `@Saga()` 装饰器是从 `@nestjs/cqrs` 包中导出的。 +::: `@Saga()` 装饰器将方法标记为一个 saga。`events``` 参数是一个包含所有事件的 Observable 流。`ofType` 操作符通过指定的事件类型过滤该流。`map` 操作符将事件映射为一个新的命令实例。 @@ -377,7 +387,7 @@ onModuleDestroy() { 对于来自不同编程语言背景的开发者来说,可能会惊讶地发现:在 Nest 中,大多数内容都是在传入请求间共享的。这包括数据库连接池、具有全局状态的单例服务等。需要注意的是,Node.js 并不遵循请求/响应多线程无状态模型(即每个请求由独立线程处理)。因此,在我们的应用中使用单例实例是**安全**的。 -然而,在某些边缘情况下,可能需要基于请求的生命周期来管理处理器。这包括诸如 GraphQL 应用中的按请求缓存、请求追踪或多租户等场景。您可[在此](/fundamentals/injection-scopes)了解更多关于控制作用域的方法。 +然而,在某些边缘情况下,可能需要基于请求的生命周期来管理处理器。这包括诸如 GraphQL 应用中的按请求缓存、请求追踪或多租户等场景。您可[在此](/fundamentals/provider-scopes)了解更多关于控制作用域的方法。 将请求作用域的提供者与 CQRS 一起使用可能会很复杂,因为 `CommandBus`、`QueryBus` 和 `EventBus` 都是单例。幸运的是,`@nestjs/cqrs` 包通过为每个处理的命令、查询或事件自动创建请求作用域处理程序的新实例,简化了这一过程。 diff --git a/docs/recipes/crud-generator.md b/docs/recipes/crud-generator.md index 6ee36ec..bf0b109 100644 --- a/docs/recipes/crud-generator.md +++ b/docs/recipes/crud-generator.md @@ -16,7 +16,10 @@ 为了加速这一重复性流程,[Nest CLI](/cli/overview) 提供了一个生成器(schematic),它能自动生成所有样板代码,帮助我们省去这些繁琐操作,大幅简化开发体验。 -> info **注意** 该 schematic 支持生成 **HTTP** 控制器、 **微服务** 控制器、**GraphQL** 解析器(包括代码优先和架构优先两种模式)以及 **WebSocket** 网关。 +:::info 注意 +该 schematic 支持生成 **HTTP** 控制器、 **微服务** 控制器、**GraphQL** 解析器(包括代码优先和架构优先两种模式)以及 **WebSocket** 网关。 +::: + #### 生成新资源 @@ -64,7 +67,9 @@ export class UsersController { 此外,它会自动为所有 CRUD 端点创建占位符(REST API 的路由、GraphQL 的查询和变更、微服务和 WebSocket 网关的消息订阅)——所有这些都无需手动操作。 -> warning **注意** 生成的服务类**不**与任何特定的 **ORM(或数据源)** 绑定。这使得生成器具有足够通用性,可满足任何项目的需求。默认情况下,所有方法都将包含占位符,允许您根据项目特定的数据源进行填充。 +:::warning 注意 +生成的服务类**不**与任何特定的 **ORM(或数据源)** 绑定。这使得生成器具有足够通用性,可满足任何项目的需求。默认情况下,所有方法都将包含占位符,允许您根据项目特定的数据源进行填充。 +::: 同样地,如果您想为 GraphQL 应用生成解析器,只需选择 `GraphQL (code first)`(或 `GraphQL (schema first)`)作为传输层。 @@ -86,7 +91,9 @@ $ nest g resource users > UPDATE src/app.module.ts (312 bytes) ``` -> info **提示** 若要避免生成测试文件,可传入 `--no-spec` 标志,如下所示: `nest g resource users --no-spec` +:::info 提示 +若要避免生成测试文件,可传入 `--no-spec` 标志,如下所示: `nest g resource users --no-spec` +::: 我们可以看到,不仅所有样板化的变更和查询都已生成,而且所有内容都已完美整合。我们正在使用 `UsersService` 服务、`User` 实体以及我们的 DTO 对象。 diff --git a/docs/recipes/hot-reload.md b/docs/recipes/hot-reload.md index d427ce2..6282dcf 100644 --- a/docs/recipes/hot-reload.md +++ b/docs/recipes/hot-reload.md @@ -2,7 +2,11 @@ 对应用程序启动过程影响最大的是 **TypeScript 编译** 。幸运的是,借助 [webpack](https://github.com/webpack/webpack) 的 HMR(热模块替换)功能,我们无需在每次变更时重新编译整个项目。这大幅减少了实例化应用程序所需的时间,使迭代开发变得更加轻松。 -> **警告** 请注意 `webpack` 不会自动将资源文件(例如 `graphql` 文件)复制到 `dist` 目录。同样地,`webpack` 也不支持通配符静态路径(例如 `TypeOrmModule` 中的 `entities` 属性)。 +:::warning 警告 +请注意 `webpack` 不会自动将资源文件(例如 `graphql` 文件)复制到 `dist` 目录。同样地,`webpack` 也不支持通配符静态路径(例如 `TypeOrmModule` 中的 `entities` 属性)。 +::: + + ### 使用 CLI @@ -16,7 +20,9 @@ $ npm i --save-dev webpack-node-externals run-script-webpack-plugin webpack ``` -> info **提示** 若使用 **Yarn Berry**(非经典版 Yarn),请安装 `webpack-pnp-externals` 而非 `webpack-node-externals`。 +:::info 提示 +若使用 **Yarn Berry**(非经典版 Yarn),请安装 `webpack-pnp-externals` 而非 `webpack-node-externals`。 +::: #### 配置 @@ -50,7 +56,11 @@ module.exports = function (options, webpack) { }; ``` -> **提示** 使用 **Yarn Berry**(非经典 Yarn)时,不要在 `externals` 配置属性中使用 `nodeExternals`,而应改用 `webpack-pnp-externals` 包中的 `WebpackPnpExternals`: `WebpackPnpExternals({ exclude: ['webpack/hot/poll?100'] })` 。 +:::info 提示 +使用 **Yarn Berry**(非经典 Yarn)时,不要在 `externals` 配置属性中使用 `nodeExternals`,而应改用 `webpack-pnp-externals` 包中的 `WebpackPnpExternals`: `WebpackPnpExternals({ exclude: ['webpack/hot/poll?100'] })` 。 +::: + + 该函数第一个参数接收包含默认 webpack 配置的原始对象,第二个参数接收 Nest CLI 使用的底层 `webpack` 包引用。它返回一个修改后的 webpack 配置,其中包含 `HotModuleReplacementPlugin`、`WatchIgnorePlugin` 和 `RunScriptWebpackPlugin` 插件。 @@ -97,7 +107,9 @@ $ npm run start:dev $ npm i --save-dev webpack webpack-cli webpack-node-externals ts-loader run-script-webpack-plugin ``` -> info **提示** 如果使用 **Yarn Berry**(非经典版 Yarn),请安装 `webpack-pnp-externals` 包而非 `webpack-node-externals`。 +:::info 提示 +如果使用 **Yarn Berry**(非经典版 Yarn),请安装 `webpack-pnp-externals` 包而非 `webpack-node-externals`。 +::: #### 配置 @@ -141,7 +153,11 @@ module.exports = { }; ``` -> **提示** 使用 **Yarn Berry**(非经典版 Yarn)时,不要在 `externals` 配置属性中使用 `nodeExternals`,而应改用 `webpack-pnp-externals` 包中的 `WebpackPnpExternals`: `WebpackPnpExternals({ exclude: ['webpack/hot/poll?100'] })` 。 +:::info 提示 +使用 **Yarn Berry**(非经典版 Yarn)时,不要在 `externals` 配置属性中使用 `nodeExternals`,而应改用 `webpack-pnp-externals` 包中的 `WebpackPnpExternals`: `WebpackPnpExternals({ exclude: ['webpack/hot/poll?100'] })` 。 +::: + + 此配置向 webpack 说明了关于应用程序的几个关键信息:入口文件的位置、存放**编译后**文件的目录,以及用于编译源文件的加载器类型。通常即使您不完全理解所有选项,也可以直接使用此文件。 diff --git a/docs/recipes/mikroorm.md b/docs/recipes/mikroorm.md index 5700cb3..94d655c 100644 --- a/docs/recipes/mikroorm.md +++ b/docs/recipes/mikroorm.md @@ -2,7 +2,10 @@ 本指南旨在帮助用户在 Nest 中快速上手 MikroORM。MikroORM 是基于数据映射器、工作单元和身份映射模式的 Node.js TypeScript ORM,是 TypeORM 的优秀替代方案,从 TypeORM 迁移也相当容易。完整文档可查阅[此处](https://mikro-orm.io/docs) 。 -> info **注意** `@mikro-orm/nestjs` 是第三方包,不由 NestJS 核心团队维护。发现任何问题请提交至[对应代码库](https://github.com/mikro-orm/nestjs) 。 +:::info 注意 +`@mikro-orm/nestjs` 是第三方包,不由 NestJS 核心团队维护。发现任何问题请提交至[对应代码库](https://github.com/mikro-orm/nestjs) 。 +::: + #### 安装 @@ -77,13 +80,19 @@ export class MyService { } ``` -> **注意** :请注意 `EntityManager` 是从 `@mikro-orm/driver` 包导入的,其中 driver 可以是 `mysql`、`sqlite`、`postgres` 或您正在使用的其他驱动。如果您安装了 `@mikro-orm/knex` 作为依赖项,也可以从那里导入 `EntityManager`。 +:::info 注意 +请注意 `EntityManager` 是从 `@mikro-orm/driver` 包导入的,其中 driver 可以是 `mysql`、`sqlite`、`postgres` 或您正在使用的其他驱动。如果您安装了 `@mikro-orm/knex` 作为依赖项,也可以从那里导入 `EntityManager`。 +::: + + #### 存储库 MikroORM 支持仓储设计模式。我们可以为每个实体创建仓储库。完整的仓储库文档请参阅[此处](https://mikro-orm.io/docs/repositories) 。要定义当前作用域中应注册哪些仓储库,可以使用 `forFeature()` 方法。例如: -> info **提示** 您**不应**通过 `forFeature()` 注册基础实体,因为这些实体没有对应的仓储库。另一方面,基础实体需要包含在 `forRoot()` 的列表中(或包含在 ORM 配置中)。 +:::info 提示 +您**不应**通过 `forFeature()` 注册基础实体,因为这些实体没有对应的仓储库。另一方面,基础实体需要包含在 `forRoot()` 的列表中(或包含在 ORM 配置中)。 +::: ```typescript // photo.module.ts @@ -164,13 +173,21 @@ export class AppModule {} 指定该选项后,通过 `forFeature()` 注册的每个实体 该方法将自动添加到配置的实体数组中 对象。 -> info **注意** 未通过 `forFeature()` 方法注册,而仅通过关系从实体引用的实体不会通过 `autoLoadEntities` 设置被包含。 +:::info 注意 +未通过 `forFeature()` 方法注册,而仅通过关系从实体引用的实体不会通过 `autoLoadEntities` 设置被包含。 +::: + + +:::info 注意 +使用 `autoLoadEntities` 对 MikroORM CLI 也没有影响 - 我们仍然需要包含完整实体列表的 CLI 配置。另一方面,我们可以在 CLI 中使用通配符,因为 CLI 不会经过 webpack 处理。 +::: -> info **注意** 使用 `autoLoadEntities` 对 MikroORM CLI 也没有影响 - 我们仍然需要包含完整实体列表的 CLI 配置。另一方面,我们可以在 CLI 中使用通配符,因为 CLI 不会经过 webpack 处理。 #### 序列化 -> warning **注意** MikroORM 将每个实体关系都包装在 `Reference` 或 `Collection` 对象中,以提供更好的类型安全性。这将导致 [Nest 内置的序列化器](/techniques/serialization) 无法识别任何被包装的关系。换句话说,如果你从 HTTP 或 WebSocket 处理器返回 MikroORM 实体,它们的所有关系都将不会被序列化。 +:::warning 注意 +MikroORM 将每个实体关系都包装在 `Reference` 或 `Collection` 对象中,以提供更好的类型安全性。这将导致 [Nest 内置的序列化器](/techniques/serialization) 无法识别任何被包装的关系。换句话说,如果你从 HTTP 或 WebSocket 处理器返回 MikroORM 实体,它们的所有关系都将不会被序列化。 +::: 幸运的是,MikroORM 提供了一个[序列化 API](https://mikro-orm.io/docs/serializing),可以用来替代 `ClassSerializerInterceptor`。 @@ -211,7 +228,9 @@ export class MyService { } ``` -> warning **注意** 顾名思义,该装饰器总是会创建新的上下文,这与替代方案 `@EnsureRequestContext` 不同——后者仅在尚未处于其他上下文时才会创建。 +:::warning 注意 + 顾名思义,该装饰器总是会创建新的上下文,这与替代方案 `@EnsureRequestContext` 不同——后者仅在尚未处于其他上下文时才会创建。 +::: #### 测试 diff --git a/docs/recipes/mongodb.md b/docs/recipes/mongodb.md index a440ed8..8c39274 100644 --- a/docs/recipes/mongodb.md +++ b/docs/recipes/mongodb.md @@ -1,6 +1,10 @@ ### MongoDB (Mongoose) -> **警告** 在本文中,您将学习如何基于 **Mongoose** 包从头开始使用自定义组件创建 `DatabaseModule`。因此,该解决方案包含许多额外工作,您可以直接使用现成的专用 `@nestjs/mongoose` 包来避免这些操作。了解更多信息,请参阅[此处](/techniques/mongodb) 。 +:::warning 警告 +在本文中,您将学习如何基于 **Mongoose** 包从头开始使用自定义组件创建 `DatabaseModule`。因此,该解决方案包含许多额外工作,您可以直接使用现成的专用 `@nestjs/mongoose` 包来避免这些操作。了解更多信息,请参阅[此处](/techniques/mongodb) 。 +::: + + [Mongoose](https://mongoosejs.com) 是最受欢迎的 [MongoDB](https://www.mongodb.org/) 对象建模工具。 @@ -14,7 +18,7 @@ $ npm install --save mongoose 我们首先需要使用 `connect()` 函数建立与数据库的连接。`connect()` 函数返回一个 `Promise`,因此我们必须创建一个[异步提供者](/fundamentals/async-components) 。 -```typescript title="database.providers" + ```typescript title="database.providers.ts" import * as mongoose from 'mongoose'; export const databaseProviders = [ @@ -26,11 +30,15 @@ export const databaseProviders = [ ]; ``` -> **提示** 遵循最佳实践,我们在单独的文件中声明了自定义提供者,该文件具有 `*.providers.ts` 后缀。 +:::info 提示 +遵循最佳实践,我们在单独的文件中声明了自定义提供者,该文件具有 `*.providers.ts` 后缀。 +::: + + 接下来,我们需要导出这些提供者,使它们对应用程序的其余部分**可访问** 。 -```typescript title="database.module" + ```typescript title="database.module.ts" import { Module } from '@nestjs/common'; import { databaseProviders } from './database.providers'; @@ -47,7 +55,7 @@ export class DatabaseModule {} 在 Mongoose 中,所有内容都源自 [Schema](https://mongoosejs.com/docs/guide.html)。让我们定义 `CatSchema`: -```typescript title="schemas/cat.schema" + ```typescript title="schemas/cat.schema.ts" import * as mongoose from 'mongoose'; export const CatSchema = new mongoose.Schema({ @@ -61,7 +69,7 @@ export const CatSchema = new mongoose.Schema({ 现在是时候创建一个 **Model** 提供者了: -```typescript title="cats.providers" + ```typescript title="cats.providers.ts" import { Connection } from 'mongoose'; import { CatSchema } from './schemas/cat.schema'; @@ -74,11 +82,13 @@ export const catsProviders = [ ]; ``` -> warning **警告** 在实际应用中应避免使用**魔法字符串** 。`CAT_MODEL` 和 `DATABASE_CONNECTION` 都应保存在独立的 `constants.ts` 文件中。 +:::warning 警告 +在实际应用中应避免使用**魔法字符串** 。`CAT_MODEL` 和 `DATABASE_CONNECTION` 都应保存在独立的 `constants.ts` 文件中。 +::: 现在我们可以通过 `@Inject()` 装饰器将 `CAT_MODEL` 注入到 `CatsService` 中: -```typescript title="cats.service" + ```typescript title="cats.service.ts" import { Model } from 'mongoose'; import { Injectable, Inject } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; @@ -118,7 +128,7 @@ export interface Cat extends Document { 以下是最终的 `CatsModule`: -```typescript title="cats.module" + ```typescript title="cats.module.ts" import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @@ -136,7 +146,11 @@ import { DatabaseModule } from '../database/database.module'; export class CatsModule {} ``` -> **提示** 不要忘记将 `CatsModule` 导入根模块 `AppModule`。 +:::info 提示 +不要忘记将 `CatsModule` 导入根模块 `AppModule`。 +::: + + #### 示例 diff --git a/docs/recipes/necord.md b/docs/recipes/necord.md index 18d2c9e..df00698 100644 --- a/docs/recipes/necord.md +++ b/docs/recipes/necord.md @@ -2,7 +2,11 @@ Necord 是一个强大的模块,可简化 [Discord](https://discord.com) 机器人的创建过程,实现与 NestJS 应用的无缝集成。 -> **注意** Necord 是第三方包,并非由 NestJS 核心团队官方维护。如遇任何问题,请提交至 [官方仓库](https://github.com/necordjs/necord) 。 +:::info 注意 +Necord 是第三方包,并非由 NestJS 核心团队官方维护。如遇任何问题,请提交至 [官方仓库](https://github.com/necordjs/necord) 。 +::: + + #### 安装 @@ -16,7 +20,7 @@ $ npm install necord discord.js 要在项目中使用 Necord,请导入 `NecordModule` 并使用必要的选项进行配置。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { NecordModule } from 'necord'; import { IntentsBitField } from 'discord.js'; @@ -35,11 +39,13 @@ import { AppService } from './app.service'; export class AppModule {} ``` -> info **提示** 您可以在此处 [here](https://discord.com/developers/docs/topics/gateway#gateway-intents) 找到可用意图的完整列表。 +:::info 提示 +您可以在此处 [here](https://discord.com/developers/docs/topics/gateway#gateway-intents) 找到可用意图的完整列表。 +::: 通过此设置,您可以将 `AppService` 注入到提供者中,轻松注册命令、事件等功能。 -```typescript title="app.service" + ```typescript title="app.service.ts" import { Injectable, Logger } from '@nestjs/common'; import { Context, On, Once, ContextOf } from 'necord'; import { Client } from 'discord.js'; @@ -66,11 +72,15 @@ export class AppService { #### 文本命令 -> **警告** 文本命令依赖于消息内容,而该功能即将对已验证机器人和拥有超过 100 个服务器的应用程序弃用。这意味着如果你的机器人无法访问消息内容,文本命令将无法工作。了解更多关于此变更的信息[请点击此处](https://support-dev.discord.com/hc/en-us/articles/4404772028055-Message-Content-Access-Deprecation-for-Verified-Bots) 。 +:::warning 警告 +文本命令依赖于消息内容,而该功能即将对已验证机器人和拥有超过 100 个服务器的应用程序弃用。这意味着如果你的机器人无法访问消息内容,文本命令将无法工作。了解更多关于此变更的信息[请点击此处](https://support-dev.discord.com/hc/en-us/articles/4404772028055-Message-Content-Access-Deprecation-for-Verified-Bots) 。 +::: + + 下面展示如何使用 `@TextCommand` 装饰器为消息创建一个简单的命令处理器。 -```typescript title="app.commands" + ```typescript title="app.commands.ts" import { Injectable } from '@nestjs/common'; import { Context, TextCommand, TextCommandContext, Arguments } from 'necord'; @@ -101,7 +111,7 @@ export class AppCommands { 要使用 Necord 定义斜杠命令,可以使用 `SlashCommand` 装饰器。 -```typescript title="app.commands" + ```typescript title="app.commands.ts" import { Injectable } from '@nestjs/common'; import { Context, SlashCommand, SlashCommandContext } from 'necord'; @@ -117,13 +127,17 @@ export class AppCommands { } ``` -> **提示** 当你的机器人客户端登录时,它会自动注册所有已定义的命令。请注意全局命令最多会被缓存一小时。为避免全局缓存问题,请使用 Necord 模块中的 `development` 参数,该参数会将命令可见性限制在单个服务器中。 +:::info 提示 +当你的机器人客户端登录时,它会自动注册所有已定义的命令。请注意全局命令最多会被缓存一小时。为避免全局缓存问题,请使用 Necord 模块中的 `development` 参数,该参数会将命令可见性限制在单个服务器中。 +::: + + ##### 选项 你可以使用选项装饰器为斜杠命令定义参数。为此我们创建一个 `TextDto` 类: -```typescript title="text.dto" + ```typescript title="text.dto.ts" import { StringOption } from 'necord'; export class TextDto { @@ -138,7 +152,7 @@ export class TextDto { 然后你就可以在 `AppCommands` 类中使用这个 DTO: -```typescript title="app.commands" + ```typescript title="app.commands.ts" import { Injectable } from '@nestjs/common'; import { Context, SlashCommand, Options, SlashCommandContext } from 'necord'; import { TextDto } from './length.dto'; @@ -166,7 +180,7 @@ export class AppCommands { 要为斜杠命令实现自动补全功能,您需要创建一个拦截器。该拦截器将处理用户在自动补全字段中输入时的请求。 -```typescript title="cats-autocomplete.interceptor" + ```typescript title="cats-autocomplete.interceptor.ts" import { Injectable } from '@nestjs/common'; import { AutocompleteInteraction } from 'discord.js'; import { AutocompleteInterceptor } from 'necord'; @@ -192,7 +206,7 @@ class CatsAutocompleteInterceptor extends AutocompleteInterceptor { 您还需要用 `autocomplete: true` 标记您的选项类: -```typescript title="cat.dto" + ```typescript title="cat.dto.ts" import { StringOption } from 'necord'; export class CatDto { @@ -208,7 +222,7 @@ export class CatDto { 最后,将拦截器应用到您的斜杠命令: -```typescript title="cats.commands" + ```typescript title="cats.commands.ts" import { Injectable, UseInterceptors } from '@nestjs/common'; import { Context, SlashCommand, Options, SlashCommandContext } from 'necord'; import { CatDto } from '/cat.dto'; @@ -236,7 +250,7 @@ export class CatsCommands { 用户命令出现在右击(或点击)用户时显示的上下文菜单中。这些命令提供直接针对用户的快速操作。 -```typescript title="app.commands" + ```typescript title="app.commands.ts" import { Injectable } from '@nestjs/common'; import { Context, UserCommand, UserCommandContext, TargetUser } from 'necord'; import { User } from 'discord.js'; @@ -263,7 +277,7 @@ export class AppCommands { 右键点击消息时,上下文菜单中会显示消息命令,可快速执行与该消息相关的操作。 -```typescript title="app.commands" + ```typescript title="app.commands.ts" import { Injectable } from '@nestjs/common'; import { Context, MessageCommand, MessageCommandContext, TargetMessage } from 'necord'; import { Message } from 'discord.js'; @@ -284,7 +298,7 @@ export class AppCommands { [按钮](https://discord.com/developers/docs/interactions/message-components#buttons)是可在消息中包含的交互元素。点击时,它们会向您的应用程序发送一个[交互](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object) 。 -```typescript title="app.components" + ```typescript title="app.components.ts" import { Injectable } from '@nestjs/common'; import { Context, Button, ButtonContext } from 'necord'; @@ -301,7 +315,7 @@ export class AppComponents { [选择菜单](https://discord.com/developers/docs/interactions/message-components#select-menus)是消息中出现的另一种交互组件,它为用户提供类似下拉框的界面来选择选项。 -```typescript title="app.components" + ```typescript title="app.components.ts" import { Injectable } from '@nestjs/common'; import { Context, StringSelect, StringSelectContext, SelectedStrings } from 'necord'; @@ -323,7 +337,7 @@ export class AppComponents { 模态框是弹出式表单,允许用户提交格式化的输入内容。以下是使用 Necord 创建和处理模态框的方法: -```typescript title="app.modals" + ```typescript title="app.modals.ts" import { Injectable } from '@nestjs/common'; import { Context, Modal, ModalContext } from 'necord'; diff --git a/docs/recipes/nest-commander.md b/docs/recipes/nest-commander.md index 4eb9660..85a5188 100644 --- a/docs/recipes/nest-commander.md +++ b/docs/recipes/nest-commander.md @@ -2,7 +2,10 @@ 除了[独立应用](/standalone-applications)文档外,还有 [nest-commander](https://jmcdo29.github.io/nest-commander) 包可用于编写命令行应用程序,其结构类似于典型的 Nest 应用。 -> info **注意** `nest-commander` 是第三方包,并非由 NestJS 核心团队全面管理。如发现该库的任何问题,请在[对应代码库](https://github.com/jmcdo29/nest-commander/issues/new/choose)中报告 +:::info 注意 +`nest-commander` 是第三方包,并非由 NestJS 核心团队全面管理。如发现该库的任何问题,请在[对应代码库](https://github.com/jmcdo29/nest-commander/issues/new/choose)中报告 +::: + #### 安装 diff --git a/docs/recipes/passport.md b/docs/recipes/passport.md index fa40b7a..29bdff5 100644 --- a/docs/recipes/passport.md +++ b/docs/recipes/passport.md @@ -23,7 +23,9 @@ $ npm install --save @nestjs/passport passport passport-local $ npm install --save-dev @types/passport-local ``` -> warning **注意** 无论选择**哪种** Passport 策略,您始终需要安装 `@nestjs/passport` 和 `passport` 包。此外,还需要安装实现特定认证策略的策略专用包(例如 `passport-jwt` 或 `passport-local`)。您也可以安装 Passport 策略的类型定义,如上文中的 `@types/passport-local`,这将在编写 TypeScript 代码时提供辅助。 +:::warning 注意 +无论选择**哪种** Passport 策略,您始终需要安装 `@nestjs/passport` 和 `passport` 包。此外,还需要安装实现特定认证策略的策略专用包(例如 `passport-jwt` 或 `passport-local`)。您也可以安装 Passport 策略的类型定义,如上文中的 `@types/passport-local`,这将在编写 TypeScript 代码时提供辅助。 +::: #### 实现 Passport 策略 @@ -52,7 +54,7 @@ $ nest g service users 替换这些生成文件的默认内容,如下所示。在我们的示例应用中,`UsersService` 仅维护一个硬编码的内存用户列表,并通过 find 方法按用户名检索用户。在实际应用中,这里应该使用您选择的库(如 TypeORM、Sequelize、Mongoose 等)构建用户模型和持久层。 -```typescript title="users/users.service" + ```typescript title="users/users.service.ts" import { Injectable } from '@nestjs/common'; // This should be a real class/interface representing a user entity @@ -81,7 +83,7 @@ export class UsersService { 在 `UsersModule` 中,唯一需要做的改动是将 `UsersService` 添加到 `@Module` 装饰器的 exports 数组中,使其在该模块外部可见(稍后我们将在 `AuthService` 中使用它)。 -```typescript title="users/users.module" + ```typescript title="users/users.module.ts" import { Module } from '@nestjs/common'; import { UsersService } from './users.service'; @@ -94,7 +96,7 @@ export class UsersModule {} 我们的 `AuthService` 负责检索用户并验证密码。为此我们创建了一个 `validateUser()` 方法。在下面的代码中,我们使用便捷的 ES6 扩展运算符在返回用户对象前移除 password 属性。稍后我们将从 Passport 本地策略调用这个 `validateUser()` 方法。 -```typescript title="auth/auth.service" + ```typescript title="auth/auth.service.ts" import { Injectable } from '@nestjs/common'; import { UsersService } from '../users/users.service'; @@ -113,11 +115,13 @@ export class AuthService { } ``` -> warning **警告** 在实际应用中,当然不应以明文存储密码。正确的做法是使用像 [bcrypt](https://github.com/kelektiv/node.bcrypt.js#readme) 这样的库,配合加盐单向哈希算法。采用这种方式时,你只需存储哈希后的密码,然后将存储的密码与**输入**密码的哈希版本进行比对,从而避免以明文形式存储或暴露用户密码。为了让示例应用保持简单,我们违反了这个绝对原则而使用了明文。 **切勿在实际应用中这样做!** +:::warning 警告 + 在实际应用中,当然不应以明文存储密码。正确的做法是使用像 [bcrypt](https://github.com/kelektiv/node.bcrypt.js#readme) 这样的库,配合加盐单向哈希算法。采用这种方式时,你只需存储哈希后的密码,然后将存储的密码与**输入**密码的哈希版本进行比对,从而避免以明文形式存储或暴露用户密码。为了让示例应用保持简单,我们违反了这个绝对原则而使用了明文。 **切勿在实际应用中这样做!** +::: 现在,我们更新 `AuthModule` 以导入 `UsersModule`。 -```typescript title="auth/auth.module" + ```typescript title="auth/auth.module.ts" import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { UsersModule } from '../users/users.module'; @@ -133,7 +137,7 @@ export class AuthModule {} 现在我们可以实现 Passport 的**本地认证策略** 。在 `auth` 文件夹中创建名为 `local.strategy.ts` 的文件,并添加以下代码: -```typescript title="auth/local.strategy" + ```typescript title="auth/local.strategy.ts" import { Strategy } from 'passport-local'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common'; @@ -157,7 +161,11 @@ export class LocalStrategy extends PassportStrategy(Strategy) { 我们已按照前述方法为所有 Passport 策略实现了配置。在使用 passport-local 的案例中,由于没有配置选项,我们的构造函数仅调用 `super()` 而不传入选项对象。 -> **提示** 我们可以在调用 `super()` 时传入选项对象来自定义 passport 策略的行为。本例中,passport-local 策略默认要求请求体包含名为 `username` 和 `password` 的属性。通过传入选项对象可指定不同的属性名,例如: `super({ usernameField: 'email' })` 。更多信息请参阅 [Passport 文档](http://www.passportjs.org/docs/configure/) 。 +:::info 提示 +我们可以在调用 `super()` 时传入选项对象来自定义 passport 策略的行为。本例中,passport-local 策略默认要求请求体包含名为 `username` 和 `password` 的属性。通过传入选项对象可指定不同的属性名,例如: `super({ usernameField: 'email' })` 。更多信息请参阅 [Passport 文档](http://www.passportjs.org/docs/configure/) 。 +::: + + 我们还实现了 `validate()` 方法。对于每个策略,Passport 会使用特定策略的参数集合来调用验证函数(在 `@nestjs/passport` 中通过 `validate()` 方法实现)。对于 local-strategy,Passport 期望 `validate()` 方法具有以下签名: `validate(username: string, password:string): any` 。 @@ -167,7 +175,7 @@ export class LocalStrategy extends PassportStrategy(Strategy) { 我们需要配置 `AuthModule` 来使用刚定义的 Passport 功能。将 `auth.module.ts` 更新如下: -```typescript title="auth/auth.module" + ```typescript title="auth/auth.module.ts" import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { UsersModule } from '../users/users.module'; @@ -203,7 +211,7 @@ export class AuthModule {} 打开 `app.controller.ts` 文件,将其内容替换为以下代码: -```typescript title="app.controller" + ```typescript title="app.controller.ts" import { Controller, Request, Post, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @@ -231,7 +239,7 @@ $ # result -> {"userId":1,"username":"john"} 虽然这种方式可行,但直接将策略名称传入 `AuthGuard()` 会在代码中引入魔术字符串。我们建议改为创建自定义类,如下所示: -```typescript title="auth/local-auth.guard" + ```typescript title="auth/local-auth.guard.ts" import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @@ -284,7 +292,7 @@ $ npm install --save-dev @types/passport-jwt 考虑到这一点,我们现在可以最终生成一个真实的 JWT,并在此路由中返回它。为了保持服务的模块化整洁,我们将在 `authService` 中处理 JWT 生成。打开 `auth` 文件夹中的 `auth.service.ts` 文件,添加 `login()` 方法,并按所示导入 `JwtService`: -```typescript title="auth/auth.service" + ```typescript title="auth/auth.service.ts" import { Injectable } from '@nestjs/common'; import { UsersService } from '../users/users.service'; import { JwtService } from '@nestjs/jwt'; @@ -320,7 +328,7 @@ export class AuthService { 首先,在 `auth` 文件夹中创建 `constants.ts`,并添加以下代码: -```typescript title="auth/constants" + ```typescript title="auth/constants.ts" export const jwtConstants = { secret: 'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.', }; @@ -328,11 +336,13 @@ export const jwtConstants = { 我们将使用它在 JWT 签名和验证步骤之间共享密钥。 -> warning **警告\*\***切勿公开此密钥** 。我们在此展示仅是为了说明代码功能,但在生产环境中**必须通过密钥保险库、环境变量或配置服务等适当措施保护此密钥\*\* 。 +:::warning 警告\ +*\***切勿公开此密钥** 。我们在此展示仅是为了说明代码功能,但在生产环境中**必须通过密钥保险库、环境变量或配置服务等适当措施保护此密钥\*\* 。 +::: 现在,打开 `auth` 文件夹中的 `auth.module.ts` 文件,并按如下内容更新: -```typescript title="auth/auth.module" + ```typescript title="auth/auth.module.ts" import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { LocalStrategy } from './local.strategy'; @@ -360,7 +370,7 @@ export class AuthModule {} 现在我们可以更新 `/auth/login` 路由以返回 JWT 令牌。 -```typescript title="app.controller" + ```typescript title="app.controller.ts" import { Controller, Request, Post, UseGuards } from '@nestjs/common'; import { LocalAuthGuard } from './auth/local-auth.guard'; import { AuthService } from './auth/auth.service'; @@ -390,7 +400,7 @@ $ # Note: above JWT truncated 现在我们可以解决最后的需求:通过要求请求中包含有效的 JWT 来保护端点。Passport 在这方面也能帮助我们。它提供了 [passport-jwt](https://github.com/mikenicholson/passport-jwt) 策略来用 JSON Web Tokens 保护 RESTful 端点。首先在 `auth` 文件夹中创建名为 `jwt.strategy.ts` 的文件,并添加以下代码: -```typescript title="auth/jwt.strategy" + ```typescript title="auth/jwt.strategy.ts" import { ExtractJwt, Strategy } from 'passport-jwt'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable } from '@nestjs/common'; @@ -428,7 +438,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { 将新的 `JwtStrategy` 作为提供者添加到 `AuthModule` 中: -```typescript title="auth/auth.module" + ```typescript title="auth/auth.module.ts" import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { LocalStrategy } from './local.strategy'; @@ -457,7 +467,7 @@ export class AuthModule {} 最后,我们定义继承内置 `AuthGuard` 的 `JwtAuthGuard` 类: -```typescript title="auth/jwt-auth.guard" + ```typescript title="auth/jwt-auth.guard.ts" import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @@ -471,7 +481,7 @@ export class JwtAuthGuard extends AuthGuard('jwt') {} 打开 `app.controller.ts` 文件并按如下所示进行更新: -```typescript title="app.controller" + ```typescript title="app.controller.ts" import { Controller, Get, Request, Post, UseGuards } from '@nestjs/common'; import { JwtAuthGuard } from './auth/jwt-auth.guard'; import { LocalAuthGuard } from './auth/local-auth.guard'; @@ -615,7 +625,7 @@ export class JwtAuthGuard extends AuthGuard('jwt') { #### 请求作用域策略 -Passport API 基于向库的全局实例注册策略。因此策略并非设计为具有请求相关选项或按请求动态实例化(了解更多关于[请求作用域](/fundamentals/injection-scopes)提供者的信息)。当您将策略配置为请求作用域时,Nest 永远不会实例化它,因为它不与任何特定路由绑定。实际上无法确定每个请求应执行哪些"请求作用域"策略。 +Passport API 基于向库的全局实例注册策略。因此策略并非设计为具有请求相关选项或按请求动态实例化(了解更多关于[请求作用域](/fundamentals/provider-scopes)提供者的信息)。当您将策略配置为请求作用域时,Nest 永远不会实例化它,因为它不与任何特定路由绑定。实际上无法确定每个请求应执行哪些"请求作用域"策略。 不过,存在在策略内动态解析请求作用域提供者的方法。为此,我们利用了[模块引用](/fundamentals/module-ref)功能。 @@ -629,7 +639,9 @@ constructor(private moduleRef: ModuleRef) { } ``` -> info **提示** `ModuleRef` 类是从 `@nestjs/core` 包中导入的。 +:::info 提示 +`ModuleRef` 类是从 `@nestjs/core` 包中导入的。 +::: 请确保将 `passReqToCallback` 配置属性设置为 `true`,如上所示。 diff --git a/docs/recipes/prisma.md b/docs/recipes/prisma.md index 505e95c..12e46e2 100644 --- a/docs/recipes/prisma.md +++ b/docs/recipes/prisma.md @@ -4,7 +4,11 @@ 虽然 Prisma 可以用于原生 JavaScript 项目,但它深度整合 TypeScript 并提供超越 TypeScript 生态中其他 ORM 的类型安全保障。您可以通过[此链接](https://www.prisma.io/docs/concepts/more/comparisons/prisma-and-typeorm#type-safety)查看 Prisma 与 TypeORM 在类型安全方面的详细对比。 -> **注意** 如需快速了解 Prisma 的工作原理,可按照[快速入门](https://www.prisma.io/docs/getting-started/quickstart)指南操作,或阅读[官方文档](https://www.prisma.io/docs/)中的[介绍章节](https://www.prisma.io/docs/understand-prisma/introduction) 。[`prisma-examples`](https://github.com/prisma/prisma-examples/) 代码库中还提供了 [REST](https://github.com/prisma/prisma-examples/tree/b53fad046a6d55f0090ddce9fd17ec3f9b95cab3/orm/nest) 和 [GraphQL](https://github.com/prisma/prisma-examples/tree/b53fad046a6d55f0090ddce9fd17ec3f9b95cab3/orm/nest-graphql) 的即用型示例。 +:::info 注意 +如需快速了解 Prisma 的工作原理,可按照[快速入门](https://www.prisma.io/docs/getting-started/quickstart)指南操作,或阅读[官方文档](https://www.prisma.io/docs/)中的[介绍章节](https://www.prisma.io/docs/understand-prisma/introduction) 。[`prisma-examples`](https://github.com/prisma/prisma-examples/) 代码库中还提供了 [REST](https://github.com/prisma/prisma-examples/tree/b53fad046a6d55f0090ddce9fd17ec3f9b95cab3/orm/nest) 和 [GraphQL](https://github.com/prisma/prisma-examples/tree/b53fad046a6d55f0090ddce9fd17ec3f9b95cab3/orm/nest-graphql) 的即用型示例。 +::: + + #### 快速开始 @@ -12,7 +16,11 @@ 本指南将使用 [SQLite](https://sqlite.org/) 数据库以避免搭建数据库服务器的开销。请注意,即使您使用 PostgreSQL 或 MySQL,仍可遵循本指南——在适当位置会提供针对这些数据库的额外说明。 -> **注意** 如果您已有现有项目并考虑迁移至 Prisma,可参考[将 Prisma 添加到现有项目](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project-typescript-postgres)的指南。若需从 TypeORM 迁移,请阅读[从 TypeORM 迁移到 Prisma](https://www.prisma.io/docs/guides/migrate-to-prisma/migrate-from-typeorm) 指南。 +:::info 注意 +如果您已有现有项目并考虑迁移至 Prisma,可参考[将 Prisma 添加到现有项目](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project-typescript-postgres)的指南。若需从 TypeORM 迁移,请阅读[从 TypeORM 迁移到 Prisma](https://www.prisma.io/docs/guides/migrate-to-prisma/migrate-from-typeorm) 指南。 +::: + + #### 创建您的 NestJS 项目 @@ -256,7 +264,10 @@ $ npm install @prisma/client 请注意,安装过程中 Prisma 会自动为您调用 `prisma generate` 命令。今后,每次修改 Prisma 模型后,您都需要运行此命令以更新生成的 Prisma Client。 -> info **注意** :`prisma generate` 命令会读取您的 Prisma 架构,并更新位于 `node_modules/@prisma/client` 中的生成 Prisma 客户端库。 +:::info 注意 +`prisma generate` 命令会读取您的 Prisma 架构,并更新位于 `node_modules/@prisma/client` 中的生成 Prisma 客户端库。 +::: + #### 在 NestJS 服务中使用 Prisma 客户端 @@ -278,7 +289,11 @@ export class PrismaService extends PrismaClient implements OnModuleInit { } ``` -> **注意** `onModuleInit` 是可选的——如果省略它,Prisma 将在首次调用数据库时延迟连接。 +:::info 注意 +`onModuleInit` 是可选的——如果省略它,Prisma 将在首次调用数据库时延迟连接。 +::: + + 接下来,你可以编写服务来为 Prisma 模式中的 `User` 和 `Post` 模型进行数据库调用。 diff --git a/docs/recipes/repl.md b/docs/recipes/repl.md index 15e4808..2af1467 100644 --- a/docs/recipes/repl.md +++ b/docs/recipes/repl.md @@ -6,7 +6,7 @@ REPL 是一种简单的交互式环境,能够接收用户输入的单条命令 要在 REPL 模式下运行 NestJS 应用,请新建 `repl.ts` 文件(与现有的 `main.ts` 文件同级),并在其中添加以下代码: -```typescript title="repl" + ```typescript title="repl.ts" import { repl } from '@nestjs/core'; import { AppModule } from './src/app.module'; @@ -22,7 +22,9 @@ bootstrap(); $ npm run start -- --entryFile repl ``` -> info **提示** `repl` 返回一个 [Node.js REPL 服务器](https://nodejs.org/api/repl.html)对象。 +:::info 提示 +`repl` 返回一个 [Node.js REPL 服务器](https://nodejs.org/api/repl.html)对象。 +::: 当它启动并运行后,你将在控制台中看到以下消息: @@ -87,7 +89,11 @@ Retrieves an instance of either injectable or controller, otherwise, throws exce Interface: $(token: InjectionToken) => any ``` -> **提示** 这些函数接口是用 [TypeScript 函数类型表达式语法](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-type-expressions)编写的。 +:::info 提示 +这些函数接口是用 [TypeScript 函数类型表达式语法](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-type-expressions)编写的。 +::: + + | 功能 | 描述 | 签名 | | -------- | ------------------------------------------------------------ | --------------------------------------------------------------- | diff --git a/docs/recipes/router-module.md b/docs/recipes/router-module.md index a3ed76a..349ec70 100644 --- a/docs/recipes/router-module.md +++ b/docs/recipes/router-module.md @@ -1,6 +1,9 @@ ### 路由模块 -> info **注意** 本章节仅适用于基于 HTTP 的应用程序。 +:::info 注意 +本章节仅适用于基于 HTTP 的应用程序。 +::: + 在 HTTP 应用(如 REST API)中,处理程序的路由路径由控制器(在 `@Controller` 装饰器内)声明的(可选)前缀与方法装饰器(例如 `@Get('users')`)中指定的任何路径拼接而成。您可以在[本节](/overview/controllers#路由)了解更多相关信息。此外,您可以为应用中注册的所有路由定义[全局前缀](/faq/global-prefix) ,或启用[版本控制](/techniques/versioning) 。 @@ -21,7 +24,9 @@ export class AppModule {} ``` -> info **提示** `RouterModule` 类是从 `@nestjs/core` 包中导出的。 +:::info 提示 +`RouterModule` 类是从 `@nestjs/core` 包中导出的。 +::: 此外,您可以定义层级结构。这意味着每个模块都可以拥有 `children` 子模块。子模块将继承其父模块的前缀。在以下示例中,我们将把 `AdminModule` 注册为 `DashboardModule` 和 `MetricsModule` 的父模块。 @@ -51,6 +56,8 @@ export class AppModule {} }); ``` -> info **提示** 此功能应谨慎使用,过度使用可能导致代码难以长期维护。 +:::info 提示 +此功能应谨慎使用,过度使用可能导致代码难以长期维护。 +::: 在上例中,任何在 `DashboardModule` 内注册的控制器都将拥有额外的 `/admin/dashboard` 前缀(因为模块会递归地从父级到子级自顶向下拼接路径)。同样地,在 `MetricsModule` 内定义的每个控制器都将拥有额外的模块级前缀 `/admin/metrics`。 diff --git a/docs/recipes/sentry.md b/docs/recipes/sentry.md index 534046a..7be526a 100644 --- a/docs/recipes/sentry.md +++ b/docs/recipes/sentry.md @@ -10,13 +10,16 @@ $ npm install --save @sentry/nestjs @sentry/profiling-node ``` -> info **注意** `@sentry/profiling-node` 是可选项,但建议用于性能分析。 +:::info 注意 +`@sentry/profiling-node` 是可选项,但建议用于性能分析。 +::: + #### 基础设置 要开始使用 Sentry,您需要创建一个名为 `instrument.ts` 的文件,该文件应在应用程序中其他模块之前导入: -```typescript title="instrument" + ```typescript title="instrument.ts" const Sentry = require("@sentry/nestjs"); const { nodeProfilingIntegration } = require("@sentry/profiling-node"); @@ -40,7 +43,7 @@ Sentry.init({ 更新您的 `main.ts` 文件,确保在其他导入之前引入 `instrument.ts`: -```typescript title="main" + ```typescript title="main.ts" // Import this first! import "./instrument"; @@ -58,7 +61,7 @@ bootstrap(); 随后,将 `SentryModule` 作为根模块添加到您的主模块中: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from "@nestjs/common"; import { SentryModule } from "@sentry/nestjs/setup"; import { AppController } from "./app.controller"; @@ -96,9 +99,11 @@ export class YourCatchAllExceptionFilter implements ExceptionFilter { 如果您没有全局捕获所有异常的过滤器,请将 `SentryGlobalFilter` 添加到主模块的 providers 中。该过滤器会将其他错误过滤器未捕获的任何未处理错误报告给 Sentry。 -> warning **警告** 需要在注册其他异常过滤器之前注册 `SentryGlobalFilter`。 +:::warning 警告 +需要在注册其他异常过滤器之前注册 `SentryGlobalFilter`。 +::: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from "@nestjs/common"; import { APP_FILTER } from "@nestjs/core"; import { SentryGlobalFilter } from "@sentry/nestjs/setup"; diff --git a/docs/recipes/serve-static.md b/docs/recipes/serve-static.md index 8720750..c64843c 100644 --- a/docs/recipes/serve-static.md +++ b/docs/recipes/serve-static.md @@ -39,7 +39,9 @@ export class AppModule {} [ServeStaticModule](https://github.com/nestjs/serve-static) 可通过多种选项进行配置以自定义其行为。您可以设置渲染静态应用的路径、指定排除路径、启用或禁用 Cache-Control 响应头设置等。完整选项列表请参阅[此处](https://github.com/nestjs/serve-static/blob/master/lib/interfaces/serve-static-options.interface.ts) 。 -> warning **注意** 静态应用的默认 `renderPath` 为 `*`(所有路径),该模块将返回"index.html"文件作为响应。这使您能为 SPA 创建客户端路由。控制器中指定的路径将回退至服务器。您可以通过设置 `serveRoot`、`renderPath` 并结合其他选项来更改此行为。此外,Fastify 适配器中已实现 `serveStaticOptions.fallthrough` 选项以模拟 Express 的穿透行为,需将其设为 `true` 才能在路由不存在时发送 `index.html` 而非 404 错误。 +:::warning 注意 +静态应用的默认 `renderPath` 为 `*`(所有路径),该模块将返回"index.html"文件作为响应。这使您能为 SPA 创建客户端路由。控制器中指定的路径将回退至服务器。您可以通过设置 `serveRoot`、`renderPath` 并结合其他选项来更改此行为。此外,Fastify 适配器中已实现 `serveStaticOptions.fallthrough` 选项以模拟 Express 的穿透行为,需将其设为 `true` 才能在路由不存在时发送 `index.html` 而非 404 错误。 +::: #### 示例 diff --git a/docs/recipes/sql-sequelize.md b/docs/recipes/sql-sequelize.md index fa9c182..1e8796e 100644 --- a/docs/recipes/sql-sequelize.md +++ b/docs/recipes/sql-sequelize.md @@ -2,7 +2,11 @@ ##### 本章仅适用于 TypeScript -> **警告** 本文中,您将学习如何基于 **Sequelize** 包使用自定义组件从零开始创建 `DatabaseModule`。因此,该技术包含许多额外工作,您可以通过使用开箱即用的专用 `@nestjs/sequelize` 包来避免。了解更多信息,请参阅[此处](/techniques/sql#sequelize-集成) 。 +:::warning 警告 +本文中,您将学习如何基于 **Sequelize** 包使用自定义组件从零开始创建 `DatabaseModule`。因此,该技术包含许多额外工作,您可以通过使用开箱即用的专用 `@nestjs/sequelize` 包来避免。了解更多信息,请参阅[此处](/techniques/sql#sequelize-集成) 。 +::: + + [Sequelize](https://github.com/sequelize/sequelize) 是一个用原生 JavaScript 编写的流行对象关系映射器(ORM),但有一个 [sequelize-typescript](https://github.com/RobinBuschmann/sequelize-typescript) TypeScript 包装器,为基础 sequelize 提供了一系列装饰器和其他附加功能。 @@ -17,7 +21,7 @@ $ npm install --save-dev @types/sequelize 第一步是创建一个带有选项对象的 **Sequelize** 实例,并将其传入构造函数。此外,我们需要添加所有模型(另一种方法是使用 `modelPaths` 属性)并 `sync()` 我们的数据库表。 -```typescript title="database.providers" + ```typescript title="database.providers.ts" import { Sequelize } from 'sequelize-typescript'; import { Cat } from '../cats/cat.entity'; @@ -41,7 +45,11 @@ export const databaseProviders = [ ]; ``` -> **提示** 遵循最佳实践,我们在单独的文件中声明了自定义提供者,该文件具有 `*.providers.ts` 后缀。 +:::info 提示 +遵循最佳实践,我们在单独的文件中声明了自定义提供者,该文件具有 `*.providers.ts` 后缀。 +::: + + 然后,我们需要导出这些提供者,使它们对应用程序的其余部分**可访问** 。 @@ -62,7 +70,7 @@ export class DatabaseModule {} 在 [Sequelize](https://github.com/sequelize/sequelize) 中,**Model** 定义了数据库中的一张表。该类的实例代表数据库中的一行记录。首先,我们至少需要一个实体: -```typescript title="cat.entity" + ```typescript title="cat.entity.ts" import { Table, Column, Model } from 'sequelize-typescript'; @Table @@ -80,7 +88,7 @@ export class Cat extends Model { `Cat` 实体属于 `cats` 目录,该目录代表 `CatsModule` 模块。现在该创建一个 **Repository** 提供者了: -```typescript title="cats.providers" + ```typescript title="cats.providers.ts" import { Cat } from './cat.entity'; export const catsProviders = [ @@ -91,13 +99,15 @@ export const catsProviders = [ ]; ``` -> warning **注意** 在实际应用中应避免使用 **魔法字符串** 。建议将 `CATS_REPOSITORY` 和 `SEQUELIZE` 都存放在独立的 `constants.ts` 文件中。 +:::warning 注意 +在实际应用中应避免使用 **魔法字符串** 。建议将 `CATS_REPOSITORY` 和 `SEQUELIZE` 都存放在独立的 `constants.ts` 文件中。 +::: 在 Sequelize 中,我们使用静态方法来操作数据,因此这里创建了一个**别名** 。 现在我们可以通过 `@Inject()` 装饰器将 `CATS_REPOSITORY` 注入到 `CatsService` 中: -```typescript title="cats.service" + ```typescript title="cats.service.ts" import { Injectable, Inject } from '@nestjs/common'; import { CreateCatDto } from './dto/create-cat.dto'; import { Cat } from './cat.entity'; @@ -119,7 +129,7 @@ export class CatsService { 以下是最终的 `CatsModule`: -```typescript title="cats.module" + ```typescript title="cats.module.ts" import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @@ -137,4 +147,8 @@ import { DatabaseModule } from '../database/database.module'; export class CatsModule {} ``` -> **提示** 不要忘记将 `CatsModule` 导入根模块 `AppModule`。 +:::info 提示 +不要忘记将 `CatsModule` 导入根模块 `AppModule`。 +::: + + diff --git a/docs/recipes/sql-typeorm.md b/docs/recipes/sql-typeorm.md index dc13985..ea04b58 100644 --- a/docs/recipes/sql-typeorm.md +++ b/docs/recipes/sql-typeorm.md @@ -2,7 +2,11 @@ ##### 本章仅适用于 TypeScript -> **警告** 在本文中,您将学习如何基于 **TypeORM** 包,使用自定义提供者机制从零开始创建 `DatabaseModule`。因此,该解决方案包含许多额外工作,您可以直接使用现成的专用 `@nestjs/typeorm` 包来避免这些工作。了解更多信息,请参阅[此处](/techniques/sql) 。 +:::warning 警告 +在本文中,您将学习如何基于 **TypeORM** 包,使用自定义提供者机制从零开始创建 `DatabaseModule`。因此,该解决方案包含许多额外工作,您可以直接使用现成的专用 `@nestjs/typeorm` 包来避免这些工作。了解更多信息,请参阅[此处](/techniques/sql) 。 +::: + + [TypeORM](https://github.com/typeorm/typeorm) 无疑是 Node.js 领域最成熟的对象关系映射器(ORM)。由于它是用 TypeScript 编写的,因此与 Nest 框架配合得非常好。 @@ -16,7 +20,7 @@ $ npm install --save typeorm mysql2 第一步需要使用从 `typeorm` 包导入的 `new DataSource().initialize()` 类建立与数据库的连接。`initialize()` 函数返回一个 `Promise`,因此我们需要创建一个[异步提供者](/fundamentals/async-components) 。 -```typescript title="database.providers" + ```typescript title="database.providers.ts" import { DataSource } from 'typeorm'; export const databaseProviders = [ @@ -42,13 +46,17 @@ export const databaseProviders = [ ]; ``` -> warning **注意** 生产环境中不应使用 `synchronize: true` 设置——否则可能导致生产数据丢失。 +:::warning 注意 +生产环境中不应使用 `synchronize: true` 设置——否则可能导致生产数据丢失。 +::: -> info **建议** 遵循最佳实践,我们在单独的文件中声明了自定义提供者,该文件具有 `*.providers.ts` 后缀。 +:::info 建议 +遵循最佳实践,我们在单独的文件中声明了自定义提供者,该文件具有 `*.providers.ts` 后缀。 +::: 接着,我们需要导出这些提供者,使它们对应用程序的**其他部分可访问** 。 -```typescript title="database.module" + ```typescript title="database.module.ts" import { Module } from '@nestjs/common'; import { databaseProviders } from './database.providers'; @@ -67,7 +75,7 @@ export class DatabaseModule {} 但首先,我们需要至少一个实体。我们将复用官方文档中的 `Photo` 实体。 -```typescript title="photo.entity" + ```typescript title="photo.entity.ts" import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() @@ -94,7 +102,7 @@ export class Photo { `Photo` 实体属于 `photo` 目录,该目录代表 `PhotoModule`。现在让我们创建一个 **Repository** 提供者: -```typescript title="photo.providers" + ```typescript title="photo.providers.ts" import { DataSource } from 'typeorm'; import { Photo } from './photo.entity'; @@ -107,11 +115,13 @@ export const photoProviders = [ ]; ``` -> warning **注意** 在实际应用中应避免使用**魔术字符串** 。`PHOTO_REPOSITORY` 和 `DATA_SOURCE` 都应保存在单独的 `constants.ts` 文件中。 +:::warning 注意 + 在实际应用中应避免使用**魔术字符串** 。`PHOTO_REPOSITORY` 和 `DATA_SOURCE` 都应保存在单独的 `constants.ts` 文件中。 +::: 现在我们可以使用 `@Inject()` 装饰器将 `Repository` 注入到 `PhotoService` 中: -```typescript title="photo.service" + ```typescript title="photo.service.ts" import { Injectable, Inject } from '@nestjs/common'; import { Repository } from 'typeorm'; import { Photo } from './photo.entity'; @@ -133,7 +143,7 @@ export class PhotoService { 以下是最终的 `PhotoModule`: -```typescript title="photo.module" + ```typescript title="photo.module.ts" import { Module } from '@nestjs/common'; import { DatabaseModule } from '../database/database.module'; import { photoProviders } from './photo.providers'; @@ -149,4 +159,8 @@ import { PhotoService } from './photo.service'; export class PhotoModule {} ``` -> **提示** 别忘了将 `PhotoModule` 导入根模块 `AppModule` 中。 +:::info 提示 +别忘了将 `PhotoModule` 导入根模块 `AppModule` 中。 +::: + + diff --git a/docs/recipes/suites.md b/docs/recipes/suites.md index 3c08373..627b5b3 100644 --- a/docs/recipes/suites.md +++ b/docs/recipes/suites.md @@ -2,7 +2,10 @@ 套件是一个具有明确设计理念且灵活多变的测试元框架,旨在提升后端系统的软件测试体验。通过将多种测试工具整合到统一框架中,套件简化了可靠测试的创建过程,助力开发高质量软件。 -> info **注意**`套件`是第三方包,不由 NestJS 核心团队维护。请将库的任何问题反馈至[相应代码库](https://github.com/suites-dev/suites) 。 +:::info 注意 +`套件`是第三方包,不由 NestJS 核心团队维护。请将库的任何问题反馈至[相应代码库](https://github.com/suites-dev/suites) 。 +::: + #### 介绍 @@ -16,13 +19,15 @@ $ npm i -D @suites/unit @suites/di.nestjs @suites/doubles.jest ``` -> info **提示**`Suites` 同时支持 Vitest 和 Sinon 作为测试替身,分别对应 `@suites/doubles.vitest` 和 `@suites/doubles.sinon` 包。 +:::info 提示 +`Suites` 同时支持 Vitest 和 Sinon 作为测试替身,分别对应 `@suites/doubles.vitest` 和 `@suites/doubles.sinon` 包。 +::: #### 示例及模块配置 考虑一个为 `CatsService` 设置的模块,它包含 `CatsApiService`、`CatsDAL`、`HttpClient` 和 `Logger`。这将作为本示例的基础: -```typescript title="cats.module" + ```typescript title="cats.module.ts" import { HttpModule } from '@nestjs/axios'; import { PrismaModule } from '../prisma.module'; @@ -38,7 +43,7 @@ export class CatsModule {} 让我们首先单独测试 `CatsHttpService`。该服务负责从 API 获取猫的数据并记录操作。 -```typescript title="cats-http.service" + ```typescript title="cats-http.service.ts" @Injectable() export class CatsHttpService { constructor(private httpClient: HttpClient, private logger: Logger) {} @@ -53,7 +58,7 @@ export class CatsHttpService { 我们希望隔离 `CatsHttpService` 并模拟其依赖项 `HttpClient` 和 `Logger`。Suites 允许我们通过使用 `TestBed` 中的 `.solitary()` 方法轻松实现这一点。 -```typescript title="cats-http.service.spec" + ```typescript title="cats-http.service.spec.ts" import { TestBed, Mocked } from '@suites/unit'; describe('Cats Http Service Unit Test', () => { @@ -97,7 +102,7 @@ describe('Cats Http Service Unit Test', () => { 在此情况下,我们将不使用 Suites,而是使用 Nest 的 `TestingModule` 来测试 `HttpModule` 的实际配置。我们将利用 `nock` 来模拟 HTTP 请求,而无需在此场景中模拟 `HttpClient`。 -```typescript title="cats-api.service" + ```typescript title="cats-api.service.ts" import { HttpClient } from '@nestjs/axios'; @Injectable() @@ -113,7 +118,7 @@ export class CatsApiService { 我们需要使用真实的、未经模拟的 `HttpClient` 来测试 `CatsApiService`,以确保 `Axios`(http)的依赖注入和配置正确。这涉及导入 `CatsModule` 并使用 `nock` 进行 HTTP 请求模拟。 -```typescript title="cats-api.service.integration.test" + ```typescript title="cats-api.service.integration.test.ts" import { Test } from '@nestjs/testing'; import * as nock from 'nock'; @@ -149,7 +154,7 @@ describe('Cats Api Service Integration Test', () => { 接下来,让我们测试依赖于 `CatsApiService` 和 `CatsDAL` 的 `CatsService`。我们将模拟 `CatsApiService` 并暴露 `CatsDAL`。 -```typescript title="cats.dal" + ```typescript title="cats.dal.ts" import { PrismaClient } from '@prisma/client'; @Injectable() @@ -164,7 +169,7 @@ export class CatsDAL { 接下来是 `CatsService`,它依赖于 `CatsApiService` 和 `CatsDAL`: -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService { constructor( @@ -181,7 +186,7 @@ export class CatsService { 现在,让我们使用 Suites 的可社交测试来测试 `CatsService`: -```typescript title="cats.service.spec" + ```typescript title="cats.service.spec.ts" import { TestBed, Mocked } from '@suites/unit'; import { PrismaClient } from '@prisma/client'; diff --git a/docs/recipes/swc.md b/docs/recipes/swc.md index 4d4c11e..9002ec4 100644 --- a/docs/recipes/swc.md +++ b/docs/recipes/swc.md @@ -2,7 +2,10 @@ [SWC](https://swc.rs/)(Speedy Web Compiler)是一个基于 Rust 的可扩展平台,可用于编译和打包。将 SWC 与 Nest CLI 结合使用是显著加速开发流程的绝佳且简单的方式。 -> info **注意** SWC 的编译速度比默认 TypeScript 编译器快约 **20 倍** 。 +:::info 注意 +SWC 的编译速度比默认 TypeScript 编译器快约 **20 倍** 。 +::: + #### 安装 @@ -18,11 +21,14 @@ $ npm i --save-dev @swc/cli @swc/core ```bash $ nest start -b swc -``` # OR nest start --builder swc ``` -> **提示** 如果你的代码库是 monorepo,请查阅 [本节内容](../recipes/swc#monorepo) 。 +:::info 提示 +如果你的代码库是 monorepo,请查阅 [本节内容](../recipes/swc#monorepo) 。 +::: + + 除了使用 `-b` 标志外,你也可以直接在 `nest-cli.json` 文件中将 `compilerOptions.builder` 属性设置为 `"swc"`,如下所示: @@ -53,7 +59,6 @@ $ nest start -b swc ```bash $ nest start -b swc -w -``` # OR nest start --builder swc --watch ``` @@ -152,7 +157,9 @@ generator.generate({ }); ``` -> info **提示** 本示例中我们使用了 `@nestjs/swagger` 插件,但您可以选择使用任何插件。 +:::info 提示 +本示例中我们使用了 `@nestjs/swagger` 插件,但您可以选择使用任何插件。 +::: `generate()` 方法接受以下选项: @@ -169,7 +176,6 @@ generator.generate({ ```bash $ npx ts-node src/generate-metadata.ts -``` # OR npx ts-node apps/{YOUR_APP}/src/generate-metadata.ts ``` @@ -185,7 +191,9 @@ export class User { } ``` -> info:**Relation** `类型`是从 `typeorm` 包中导出的。 +:::info 提示 +**Relation** `类型`是从 `typeorm` 包中导出的。 +::: 这样做可以避免属性类型被保存在转译代码的属性元数据中,从而防止循环依赖问题。 @@ -378,4 +386,7 @@ export default defineConfig({ 通过此配置,您现在可以在 NestJS 项目中享受使用 Vitest 带来的优势,包括更快的测试执行速度和更现代化的测试体验。 -> info **提示** 您可以在该 [代码库](https://github.com/TrilonIO/nest-vitest) 中查看实际示例 +:::info 提示 +您可以在该 [代码库](https://github.com/TrilonIO/nest-vitest) 中查看实际示例 +::: + diff --git a/docs/recipes/terminus.md b/docs/recipes/terminus.md index c115834..3607d9a 100644 --- a/docs/recipes/terminus.md +++ b/docs/recipes/terminus.md @@ -29,9 +29,11 @@ $ npm install --save @nestjs/terminus 要开始我们的第一个健康检查,让我们创建 `HealthModule` 模块,并在其 imports 数组中导入 `TerminusModule`。 -> info **提示** 要使用 [Nest CLI](cli/overview) 创建该模块,只需执行 `$ nest g module health` 命令。 +:::info 提示 +要使用 [Nest CLI](cli/overview) 创建该模块,只需执行 `$ nest g module health` 命令。 +::: -```typescript title="health.module" + ```typescript title="health.module.ts" import { Module } from '@nestjs/common'; import { TerminusModule } from '@nestjs/terminus'; @@ -47,7 +49,9 @@ export class HealthModule {} $ nest g controller health ``` -> info **信息** 强烈建议在应用程序中启用关闭钩子。如果启用,Terminus 集成会利用此生命周期事件。了解更多关于关闭钩子的信息 [请点击这里](fundamentals/lifecycle-events#应用程序关闭) 。 +:::info 信息 +强烈建议在应用程序中启用关闭钩子。如果启用,Terminus 集成会利用此生命周期事件。了解更多关于关闭钩子的信息 [请点击这里](fundamentals/lifecycle-events#应用程序关闭) 。 +::: #### HTTP 健康检查 @@ -61,7 +65,7 @@ $ npm i --save @nestjs/axios axios 现在我们可以设置 `HealthController` 了: -```typescript title="health.controller" + ```typescript title="health.controller.ts" import { Controller, Get } from '@nestjs/common'; import { HealthCheckService, HttpHealthIndicator, HealthCheck } from '@nestjs/terminus'; @@ -82,7 +86,7 @@ export class HealthController { } ``` -```typescript title="health.module" + ```typescript title="health.module.ts" import { Module } from '@nestjs/common'; import { TerminusModule } from '@nestjs/terminus'; import { HttpModule } from '@nestjs/axios'; @@ -129,7 +133,7 @@ export class HealthModule {} 若返回的响应代码不是 `204`,则以下示例将被视为不健康。第三个参数要求提供一个(同步或异步)函数,该函数返回布尔值以判断响应是否健康(`true`)或不健康(`false`)。 -```typescript title="health.controller" + ```typescript title="health.controller.ts" // Within the `HealthController`-class @Get() @@ -150,9 +154,13 @@ check() { Terminus 提供了将数据库检查添加到健康检查的能力。要开始使用此健康指标,您应查阅[数据库章节](/techniques/sql)并确保应用程序中的数据库连接已建立。 -> **提示** 在底层,`TypeOrmHealthIndicator` 仅执行一条常用于验证数据库是否存活的 `SELECT 1` SQL 命令。若使用 Oracle 数据库,则会执行 `SELECT 1 FROM DUAL`。 +:::info 提示 +在底层,`TypeOrmHealthIndicator` 仅执行一条常用于验证数据库是否存活的 `SELECT 1` SQL 命令。若使用 Oracle 数据库,则会执行 `SELECT 1 FROM DUAL`。 +::: -```typescript title="health.controller" + + + ```typescript title="health.controller.ts" @Controller('health') export class HealthController { constructor( @@ -191,7 +199,7 @@ export class HealthController { 如果您的应用使用[多个数据库](techniques/database#多个数据库) ,需要将每个连接注入到 `HealthController` 中。然后就可以直接将连接引用传递给 `TypeOrmHealthIndicator`。 -```typescript title="health.controller" + ```typescript title="health.controller.ts" @Controller('health') export class HealthController { constructor( @@ -218,7 +226,7 @@ export class HealthController { 通过 `DiskHealthIndicator` 我们可以检查存储空间的使用情况。要开始使用,请确保注入 `DiskHealthIndicator` 将以下代码添加到你的 `HealthController` 中。以下示例检查路径 `/`(在 Windows 上可以使用 `C:\\`)的存储使用情况。如果使用量超过总存储空间的 50%,健康检查将返回不健康状态。 -```typescript title="health.controller" + ```typescript title="health.controller.ts" @Controller('health') export class HealthController { constructor( @@ -238,7 +246,7 @@ export class HealthController { 通过 `DiskHealthIndicator.checkStorage` 函数,你还可以检查固定大小的存储空间。以下示例中,如果路径 `/my-app/` 超过 250GB,健康状态将变为不健康。 -```typescript title="health.controller" + ```typescript title="health.controller.ts" // Within the `HealthController`-class @Get() @@ -254,12 +262,15 @@ check() { 为确保你的进程不超过特定内存限制,可以使用 `MemoryHealthIndicator`。以下示例可用于检查进程的堆内存使用情况。 -> info **提示** 堆是内存中动态分配内存(即通过 malloc 分配的内存)所在的区域。从堆中分配的内存将保持分配状态,直到发生以下情况之一: +:::info 提示 + 堆是内存中动态分配内存(即通过 malloc 分配的内存)所在的区域。从堆中分配的内存将保持分配状态,直到发生以下情况之一: +::: + > > - 内存被*释放* > - 程序终止 -```typescript title="health.controller" + ```typescript title="health.controller.ts" @Controller('health') export class HealthController { constructor( @@ -279,9 +290,11 @@ export class HealthController { 您还可以使用 `MemoryHealthIndicator.checkRSS` 来验证进程的内存 RSS。如果您的进程确实分配了超过 150MB 的内存,此示例将返回一个异常响应码。 -> info **提示** RSS(常驻内存集)用于显示分配给该进程且驻留在 RAM 中的内存量。它不包括被交换出去的内存,但包含来自共享库的内存(只要这些库的页面实际存在于内存中),同时包含所有栈和堆内存。 +:::info 提示 +RSS(常驻内存集)用于显示分配给该进程且驻留在 RAM 中的内存量。它不包括被交换出去的内存,但包含来自共享库的内存(只要这些库的页面实际存在于内存中),同时包含所有栈和堆内存。 +::: -```typescript title="health.controller" + ```typescript title="health.controller.ts" // Within the `HealthController`-class @Get() @@ -299,7 +312,7 @@ check() { 让我们从创建一个代表自定义指标的服务开始。为了基本了解指标的结构,我们将创建一个示例 `DogHealthIndicator`。当每个 `Dog` 对象的类型为 `'goodboy'` 时,该服务应处于 `'up'` 状态。如果条件不满足,则应抛出错误。 -```typescript title="dog.health" + ```typescript title="dog.health.ts" import { Injectable } from '@nestjs/common'; import { HealthIndicatorService } from '@nestjs/terminus'; @@ -335,7 +348,7 @@ export class DogHealthIndicator { 接下来我们需要将健康指标注册为提供者。 -```typescript title="health.module" + ```typescript title="health.module.ts" import { Module } from '@nestjs/common'; import { TerminusModule } from '@nestjs/terminus'; import { DogHealthIndicator } from './dog.health'; @@ -348,11 +361,13 @@ import { DogHealthIndicator } from './dog.health'; export class HealthModule { } ``` -> info **提示** 在实际应用中,`DogHealthIndicator` 应该在一个单独的模块中提供,例如 `DogModule`,然后由 `HealthModule` 导入。 +:::info 提示 +在实际应用中,`DogHealthIndicator` 应该在一个单独的模块中提供,例如 `DogModule`,然后由 `HealthModule` 导入。 +::: 最后一步是将现已可用的健康指标添加到所需的健康检查端点。为此,我们回到 `HealthController` 并将其添加到我们的 `check` 函数中。 -```typescript title="health.controller" + ```typescript title="health.controller.ts" import { HealthCheckService, HealthCheck } from '@nestjs/terminus'; import { Injectable, Dependencies, Get } from '@nestjs/common'; import { DogHealthIndicator } from './dog.health'; @@ -380,9 +395,13 @@ Terminus 仅记录错误消息,例如当健康检查失败时。通过 `Termin 在本节中,我们将指导您如何创建自定义日志记录器 `TerminusLogger`。该日志记录器扩展了内置日志功能,因此您可以选择性地覆盖日志记录器的特定部分 -> **提示** 如需了解更多关于 NestJS 中自定义日志记录器的信息, [请点击此处阅读更多内容](/techniques/logger#注入自定义日志记录器) 。 +:::info 提示 +如需了解更多关于 NestJS 中自定义日志记录器的信息, [请点击此处阅读更多内容](/techniques/logger#注入自定义日志记录器) 。 +::: + + -```typescript title="terminus-logger.service" + ```typescript title="terminus-logger.service.ts" import { Injectable, Scope, ConsoleLogger } from '@nestjs/common'; @Injectable({ scope: Scope.TRANSIENT }) @@ -402,7 +421,7 @@ export class TerminusLogger extends ConsoleLogger { 创建自定义日志记录器后,您只需将其传入 `TerminusModule.forRoot()` 即可,如下所示。 -```typescript title="health.module" + ```typescript title="health.module.ts" @Module({ imports: [ TerminusModule.forRoot({ @@ -415,7 +434,7 @@ export class HealthModule {} 若要完全抑制来自 Terminus 的所有日志消息(包括错误消息),请按如下方式配置 Terminus。 -```typescript title="health.module" + ```typescript title="health.module.ts" @Module({ imports: [ TerminusModule.forRoot({ @@ -435,7 +454,7 @@ Terminus 允许您配置健康检查错误在日志中的显示方式。 您可以通过如下代码片段所示的 `errorLogStyle` 配置选项来更改日志样式 -```typescript title="health.module" + ```typescript title="health.module.ts" @Module({ imports: [ TerminusModule.forRoot({ @@ -450,7 +469,7 @@ export class HealthModule {} 如果您的应用程序需要延迟关闭过程,Terminus 可以为您处理。这一设置在配合 Kubernetes 等编排器使用时尤为有益。通过将延迟时间设置为略长于就绪检查间隔,您可以在关闭容器时实现零停机。 -```typescript title="health.module" + ```typescript title="health.module.ts" @Module({ imports: [ TerminusModule.forRoot({ diff --git a/docs/security/authentication.md b/docs/security/authentication.md index a418c4e..0d89678 100644 --- a/docs/security/authentication.md +++ b/docs/security/authentication.md @@ -25,7 +25,7 @@ $ nest g service users 将这些生成文件的默认内容替换为如下所示。对于我们的示例应用程序,`UsersService` 只是维护一个硬编码的内存中用户列表,以及一个根据用户名检索用户的 find 方法。在真实的应用程序中,这是您构建用户模型和持久化层的地方,使用您选择的库(例如 TypeORM、Sequelize、Mongoose 等)。 -```typescript title="users/users.service" + ```typescript title="users/users.service.ts" import { Injectable } from '@nestjs/common'; // 这应该是一个表示用户实体的真实类/接口 @@ -54,7 +54,7 @@ export class UsersService { 现在,更新 `UsersModule` 以导出 `UsersService`,以便在模块外部可用(我们很快就会在 `AuthService` 中使用它): -```typescript title="users/users.module" + ```typescript title="users/users.module.ts" import { Module } from '@nestjs/common'; import { UsersService } from './users.service'; @@ -67,7 +67,7 @@ export class UsersModule {} 我们的 `AuthService` 的工作是检索用户并验证密码。我们为此创建一个 `signIn()` 方法。在下面的代码中,我们使用方便的 ES6 展开运算符从 user 对象中剥离密码属性,然后返回它。这是一种常见做法,当从用户对象返回时,您希望避免包含敏感字段,如密码。 -```typescript title="auth/auth.service" + ```typescript title="auth/auth.service.ts" import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from '../users/users.service'; @@ -88,11 +88,13 @@ export class AuthService { } ``` -> **警告** 当然,在真实的应用程序中,您不会以明文形式存储密码。您会使用加了盐的单向哈希算法,如 bcrypt。通过这种方法,您只会存储哈希密码,然后将存储的哈希与传入密码的哈希版本进行比较,因此您永远不会以明文形式存储或暴露用户密码。为了保持我们的示例应用程序的简单性,我们违反了这个绝对要求并使用明文。**不要在真实应用程序中这样做!** +:::warning 警告 +当然,在真实的应用程序中,您不会以明文形式存储密码。您会使用加了盐的单向哈希算法,如 bcrypt。通过这种方法,您只会存储哈希密码,然后将存储的哈希与传入密码的哈希版本进行比较,因此您永远不会以明文形式存储或暴露用户密码。为了保持我们的示例应用程序的简单性,我们违反了这个绝对要求并使用明文。**不要在真实应用程序中这样做!** 现在,我们需要更新 `AuthModule` 以导入 `UsersModule`: +::: -现在,我们需要更新 `AuthModule` 以导入 `UsersModule`: -```typescript title="auth/auth.module" + + ```typescript title="auth/auth.module.ts" import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { UsersModule } from '../users/users.module'; @@ -117,11 +119,15 @@ export class AuthModule {} $ npm install --save @nestjs/jwt ``` -> **提示** `@nestjs/jwt` 包(见[这里](https://github.com/nestjs/jwt))是一个实用程序包,有助于 JWT 操作。 +:::info 提示 +`@nestjs/jwt` 包(见[这里](https://github.com/nestjs/jwt))是一个实用程序包,有助于 JWT 操作。 +::: + + 为了保持我们的服务清洁和模块化,我们将在 `authService` 中处理 JWT 生成。打开 `auth/auth.service.ts` 文件,注入 `JwtService`,并更新 `signIn` 方法以生成 JWT 令牌,如下所示: -```typescript title="auth/auth.service" + ```typescript title="auth/auth.service.ts" import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UsersService } from '../users/users.service'; import { JwtService } from '@nestjs/jwt'; @@ -152,7 +158,7 @@ export class AuthService { 首先,在 `auth` 文件夹中创建 `constants.ts`,并添加以下代码: -```typescript title="auth/constants" + ```typescript title="auth/constants.ts" export const jwtConstants = { secret: 'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.', }; @@ -160,11 +166,15 @@ export const jwtConstants = { 我们将使用它在 JWT 签名和验证步骤之间共享我们的密钥。 -> **警告** **不要在生产代码中公开暴露此密钥**。我们在这里这样做是为了清楚地说明代码在做什么,但在生产系统中,您必须使用适当的措施来保护此密钥,如机密库、环境变量或配置服务。 +:::warning 警告 +**不要在生产代码中公开暴露此密钥**。我们在这里这样做是为了清楚地说明代码在做什么,但在生产系统中,您必须使用适当的措施来保护此密钥,如机密库、环境变量或配置服务。 +::: + + 现在,打开 `auth` 文件夹中的 `auth.module.ts` 并更新它,如下所示: -```typescript title="auth/auth.module" + ```typescript title="auth/auth.module.ts" import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { UsersModule } from '../users/users.module'; @@ -186,7 +196,11 @@ import { jwtConstants } from './constants'; export class AuthModule {} ``` -> **提示** 我们使用 `global: true` 注册 `JwtModule` 以便简化。这意味着我们不需要在任何其他地方导入 `JwtModule`。 +:::info 提示 +我们使用 `global: true` 注册 `JwtModule` 以便简化。这意味着我们不需要在任何其他地方导入 `JwtModule`。 +::: + + 我们使用 `register()` 配置 `JwtModule`,传入一个配置对象。查看[这里](https://github.com/nestjs/jwt/blob/master/README.md)了解更多关于 Nest `JwtModule` 的信息,[这里](https://github.com/auth0/node-jsonwebtoken#usage)了解更多关于可用配置选项的信息。 @@ -194,7 +208,7 @@ export class AuthModule {} 现在我们可以实现一个简单的 `/auth/login` 路由,该路由会 POST 用户的凭据以获取 JWT。打开 `auth/auth.controller.ts` 文件并添加以下代码: -```typescript title="auth/auth.controller" + ```typescript title="auth/auth.controller.ts" import { Body, Controller, @@ -216,11 +230,15 @@ export class AuthController { } ``` -> **提示** 理想情况下,不要使用 `Record` 类型。相反,应该创建一个 DTO 类来定义 body 的形状。查看[验证](../techniques/validation)章节了解更多信息。 +:::info 提示 +理想情况下,不要使用 `Record` 类型。相反,应该创建一个 DTO 类来定义 body 的形状。查看[验证](../techniques/validation)章节了解更多信息。 +::: + + 不要忘记将 `AuthController` 添加到 `AuthModule`: -```typescript title="auth/auth.module" + ```typescript title="auth/auth.module.ts" import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { UsersModule } from '../users/users.module'; @@ -257,7 +275,7 @@ $ # Note: above JWT truncated 我们现在可以处理最后的要求:通过要求请求中存在有效的 JWT 来保护端点。我们将通过创建一个 `AuthGuard` 来实现,该守卫可用于保护我们的路由。 -```typescript title="auth/auth.guard" + ```typescript title="auth/auth.guard.ts" import { CanActivate, ExecutionContext, @@ -303,7 +321,7 @@ export class AuthGuard implements CanActivate { 我们现在可以实现一个受保护的路由和一个用于测试我们守卫的注册装饰器。打开 `auth/auth.controller.ts` 文件并更新它,如下所示: -```typescript title="auth/auth.controller" + ```typescript title="auth/auth.controller.ts" import { Body, Controller, @@ -376,7 +394,7 @@ providers: [ 现在我们必须提供一种机制来声明路由为公共的。为此,我们可以使用 `SetMetadata` 装饰器工厂函数创建自定义装饰器。 -```typescript title="auth/decorators/public.decorator" + ```typescript title="auth/decorators/public.decorator.ts" import { SetMetadata } from '@nestjs/common'; export const IS_PUBLIC_KEY = 'isPublic'; diff --git a/docs/security/authorization.md b/docs/security/authorization.md index b663541..6514bcc 100644 --- a/docs/security/authorization.md +++ b/docs/security/authorization.md @@ -12,18 +12,22 @@ 首先,让我们创建一个 `Role` 枚举来表示系统中的角色: -```typescript title="role.enum" + ```typescript title="role.enum.ts" export enum Role { User = 'user', Admin = 'admin', } ``` -> **提示** 在更复杂的系统中,您可能会将角色存储在数据库中,或从外部认证提供商中获取它们。 +:::info 提示 +在更复杂的系统中,您可能会将角色存储在数据库中,或从外部认证提供商中获取它们。 +::: + + 有了这个,我们可以创建一个 `@Roles()` 装饰器。此装饰器允许指定访问特定资源所需的角色。 -```typescript title="roles.decorator" + ```typescript title="roles.decorator.ts" import { SetMetadata } from '@nestjs/common'; import { Role } from '../enums/role.enum'; @@ -33,7 +37,7 @@ export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); 现在我们有了一个自定义的 `@Roles()` 装饰器,我们可以使用它来装饰任何路由处理程序。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() @Roles(Role.Admin) create(@Body() createCatDto: CreateCatDto) { @@ -43,7 +47,7 @@ create(@Body() createCatDto: CreateCatDto) { 最后,我们创建一个 `RolesGuard` 类,它将比较分配给当前用户的角色与当前正在处理的路由实际需要的角色。为了访问路由的角色(自定义元数据),我们将使用 `Reflector` 辅助类,该类由框架开箱即用提供,并从 `@nestjs/core` 包中暴露。 -```typescript title="roles.guard" + ```typescript title="roles.guard.ts" import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @@ -65,15 +69,23 @@ export class RolesGuard implements CanActivate { } ``` -> **提示** 请参阅[执行上下文](../fundamentals/execution-context)章节的 `Reflector` 部分,了解更多关于在上下文敏感的方式下使用 `Reflector` 的细节。 +:::info 提示 +请参阅[执行上下文](../fundamentals/execution-context)章节的 `Reflector` 部分,了解更多关于在上下文敏感的方式下使用 `Reflector` 的细节。 +::: + + + +:::info 注意 +这个例子被命名为"**基本**",因为我们在这里检查的 RBAC 实现相当简单。在更复杂的 RBAC 实现中,您需要考虑权限、操作、资源、关系等,其中权限不仅仅由简单的角色定义,而且可能具有多维特征。要了解更多关于这种方法的信息,请查看 [Casbin](https://github.com/casbin/casbin) 库和 [Node-Casbin](https://github.com/casbin/node-casbin) 包。 +::: + -> **注意** 这个例子被命名为"**基本**",因为我们在这里检查的 RBAC 实现相当简单。在更复杂的 RBAC 实现中,您需要考虑权限、操作、资源、关系等,其中权限不仅仅由简单的角色定义,而且可能具有多维特征。要了解更多关于这种方法的信息,请查看 [Casbin](https://github.com/casbin/casbin) 库和 [Node-Casbin](https://github.com/casbin/node-casbin) 包。 在守卫内部,我们从 `request.user` 属性中提取用户实例(我们假设它之前在认证过程中被设置)。在真实的应用程序中,用户实例可能包含更多信息 - 有关用户对象的更多详细信息和格式,请参阅您的认证实现。 要使用这个守卫,我们可以在控制器级别绑定它: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Controller('cats') @UseGuards(RolesGuard) export class CatsController {} @@ -81,7 +93,7 @@ export class CatsController {} 或者全局绑定: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.create(AppModule); app.useGlobalGuards(new RolesGuard(reflector)); ``` @@ -92,7 +104,7 @@ app.useGlobalGuards(new RolesGuard(reflector)); 要在 Nest 中实现基于声明的授权,您可以按照与上面显示的基于角色的授权相同的步骤,但有一个重要区别:不是检查特定角色,而是应该比较**权限**。每个用户都将被分配一组权限。同样,每个资源/端点将定义访问它们所需的权限(例如,通过专用的 `@RequirePermissions()` 装饰器)。 -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() @RequirePermissions(Permission.CREATE_CAT) create(@Body() createCatDto: CreateCatDto) { @@ -100,7 +112,11 @@ create(@Body() createCatDto: CreateCatDto) { } ``` -> **提示** 在上面的例子中,`Permission`(类似于我们之前显示的 `Role`)是一个 TypeScript 枚举,包含系统中的所有权限。 +:::info 提示 +在上面的例子中,`Permission`(类似于我们之前显示的 `Role`)是一个 TypeScript 枚举,包含系统中的所有权限。 +::: + + #### 集成 CASL @@ -112,7 +128,11 @@ create(@Body() createCatDto: CreateCatDto) { $ npm i @casl/ability ``` -> **提示** 在此例中,我们选择了 CASL,但您可以根据您的偏好和项目需求使用任何其他库,如 `accesscontrol` 或 `acl`。 +:::info 提示 +在此例中,我们选择了 CASL,但您可以根据您的偏好和项目需求使用任何其他库,如 `accesscontrol` 或 `acl`。 +::: + + 一旦安装完成,为了说明 CASL 的机制,我们将定义两个实体类:`User` 和 `Article`。 @@ -140,7 +160,7 @@ class Article { 有了这个思想,我们可以开始创建一个 `Ability` 类,表示用户在系统中可以做什么: -```typescript title="casl-ability.factory" + ```typescript title="casl-ability.factory.ts" import { Ability, AbilityBuilder, AbilityClass, ExtractSubjectType, InferSubjects } from '@casl/ability'; import { Injectable } from '@nestjs/common'; import { Article } from './article'; @@ -183,11 +203,15 @@ export enum Action { } ``` -> **提示** `all` 是 CASL 中的一个特殊关键字,表示"任何主体"。 +:::info 提示 +`all` 是 CASL 中的一个特殊关键字,表示"任何主体"。 +::: + + 现在,创建一个 `PoliciesGuard`,它将针对 CASL 检查权限: -```typescript title="policies.guard" + ```typescript title="policies.guard.ts" import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { AppAbility, CaslAbilityFactory } from './casl-ability.factory'; @@ -229,7 +253,7 @@ export class PoliciesGuard implements CanActivate { 让我们解释一下这个例子。`policyHandlers` 是一个分配给方法通过 `@CheckPolicies()` 装饰器的处理程序数组。处理程序可以是函数或实现 `PolicyHandler` 接口的类的实例: -```typescript title="policy-handler.interface" + ```typescript title="policy-handler.interface.ts" import { AppAbility } from './casl-ability.factory'; interface IPolicyHandler { @@ -243,7 +267,7 @@ export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback; 最后,创建 `@CheckPolicies()` 装饰器: -```typescript title="check-policies.decorator" + ```typescript title="check-policies.decorator.ts" import { SetMetadata } from '@nestjs/common'; import { PolicyHandler } from './policy-handler.interface'; @@ -254,7 +278,7 @@ export const CheckPolicies = (...handlers: PolicyHandler[]) => 现在,我们可以将这个装饰器与一些策略处理程序结合使用: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Get() @UseGuards(PoliciesGuard) @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, Article)) @@ -265,7 +289,7 @@ findAll() { 或者,我们可以使用处理程序类: -```typescript title="read-article-policy.handler" + ```typescript title="read-article-policy.handler.ts" import { AppAbility } from './casl-ability.factory'; import { IPolicyHandler } from './policy-handler.interface'; import { Action } from './casl-ability.factory'; @@ -279,7 +303,7 @@ export class ReadArticlePolicyHandler implements IPolicyHandler { 然后按如下方式使用它: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Get() @UseGuards(PoliciesGuard) @CheckPolicies(new ReadArticlePolicyHandler()) @@ -288,7 +312,11 @@ findAll() { } ``` -> **警告** 由于我们必须使用 `new` 关键字就地实例化策略处理程序,因此 `ReadArticlePolicyHandler` 类无法使用依赖注入。这可以通过 `ModuleRef#get` 方法解决(在[这里](../fundamentals/module-ref)阅读更多)。基本上,不是通过 `@CheckPolicies()` 装饰器传递函数和实例,您需要注册所有处理程序作为提供者,并只通过引用传递它们。 +:::warning 警告 +由于我们必须使用 `new` 关键字就地实例化策略处理程序,因此 `ReadArticlePolicyHandler` 类无法使用依赖注入。这可以通过 `ModuleRef#get` 方法解决(在[这里](../fundamentals/module-ref)阅读更多)。基本上,不是通过 `@CheckPolicies()` 装饰器传递函数和实例,您需要注册所有处理程序作为提供者,并只通过引用传递它们。 +::: + + #### 示例 diff --git a/docs/security/csrf.md b/docs/security/csrf.md index 143cabd..c0b2b5b 100644 --- a/docs/security/csrf.md +++ b/docs/security/csrf.md @@ -10,7 +10,11 @@ $ npm i csrf-csrf ``` -> **警告** 如 [csrf-csrf 文档](https://github.com/Psifi-Solutions/csrf-csrf?tab=readme-ov-file#getting-started)中所述,此中间件需要预先初始化会话中间件或 `cookie-parser`。请参阅文档以获取更多详细信息。 +:::warning 警告 +如 [csrf-csrf 文档](https://github.com/Psifi-Solutions/csrf-csrf?tab=readme-ov-file#getting-started)中所述,此中间件需要预先初始化会话中间件或 `cookie-parser`。请参阅文档以获取更多详细信息。 +::: + + 安装完成后,将 `csrf-csrf` 中间件注册为全局中间件。 @@ -44,4 +48,8 @@ import fastifyCsrf from '@fastify/csrf-protection'; await app.register(fastifyCsrf); ``` -> **警告** 如 `@fastify/csrf-protection` 文档[这里](https://github.com/fastify/csrf-protection#usage)所解释的,此插件需要首先初始化存储插件。请参阅该文档以获取进一步的说明。 +:::warning 警告 +如 `@fastify/csrf-protection` 文档[这里](https://github.com/fastify/csrf-protection#usage)所解释的,此插件需要首先初始化存储插件。请参阅该文档以获取进一步的说明。 +::: + + diff --git a/docs/security/helmet.md b/docs/security/helmet.md index 1e22f5d..f1dcd9e 100644 --- a/docs/security/helmet.md +++ b/docs/security/helmet.md @@ -2,7 +2,11 @@ [Helmet](https://github.com/helmetjs/helmet) 可以通过适当设置 HTTP 头部来帮助保护您的应用程序免受一些已知的 Web 漏洞的攻击。一般来说,Helmet 只是一个较小中间件函数的集合,这些函数设置与安全相关的 HTTP 头部(阅读[更多](https://github.com/helmetjs/helmet#how-it-works))。 -> **提示** 请注意,将 `helmet` 应用为全局中间件或注册它必须在其他调用 `app.use()` 或可能调用 `app.use()` 的设置函数之前进行。这是由于底层平台(即 Express 或 Fastify)的工作方式,其中定义中间件/路由的顺序很重要。如果您在定义路由后使用像 `helmet` 或 `cors` 这样的中间件,那么该中间件将不会应用于该路由,它只会应用于在中间件之后定义的路由。 +:::info 提示 +请注意,将 `helmet` 应用为全局中间件或注册它必须在其他调用 `app.use()` 或可能调用 `app.use()` 的设置函数之前进行。这是由于底层平台(即 Express 或 Fastify)的工作方式,其中定义中间件/路由的顺序很重要。如果您在定义路由后使用像 `helmet` 或 `cors` 这样的中间件,那么该中间件将不会应用于该路由,它只会应用于在中间件之后定义的路由。 +::: + + #### 与 Express 一起使用(默认) @@ -20,7 +24,11 @@ import helmet from 'helmet'; app.use(helmet()); ``` -> **警告** 当使用 `helmet`、`@apollo/server`(4.x)和 [Apollo Sandbox](../graphql/quick-start#apollo-sandbox) 时,Apollo Sandbox 上可能会出现 [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 问题。要解决此问题,请按如下所示配置 CSP: +:::warning 警告 +当使用 `helmet`、`@apollo/server`(4.x)和 [Apollo Sandbox](../graphql/quick-start#apollo-sandbox) 时,Apollo Sandbox 上可能会出现 [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 问题。要解决此问题,请按如下所示配置 CSP: +::: + + > > ```typescript > app.use(helmet({ @@ -52,7 +60,11 @@ import helmet from '@fastify/helmet' await app.register(helmet) ``` -> **警告** 当使用 `apollo-server-fastify` 和 `@fastify/helmet` 时,GraphQL playground 上可能会出现 [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 问题,要解决此冲突,请按如下所示配置 CSP: +:::warning 警告 +当使用 `apollo-server-fastify` 和 `@fastify/helmet` 时,GraphQL playground 上可能会出现 [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 问题,要解决此冲突,请按如下所示配置 CSP: +::: + + > > ```typescript > await app.register(fastifyHelmet, { diff --git a/docs/security/rate-limiting.md b/docs/security/rate-limiting.md index b3acb11..3dab60b 100644 --- a/docs/security/rate-limiting.md +++ b/docs/security/rate-limiting.md @@ -8,7 +8,7 @@ $ npm i --save @nestjs/throttler 安装完成后,可以像配置其他 Nest 包一样,使用 `forRoot` 或 `forRootAsync` 方法配置 `ThrottlerModule`。 -```typescript title="app.module.ts" + ```typescript title="app.module.ts" @Module({ imports: [ ThrottlerModule.forRoot({ @@ -39,7 +39,7 @@ export class AppModule {} 有时您可能需要设置多个限流定义,比如每秒不超过 3 次调用,10 秒内不超过 20 次调用,以及一分钟内不超过 100 次调用。为此,您可以在数组中设置带有命名选项的定义,这些选项稍后可以在 `@SkipThrottle()` 和 `@Throttle()` 装饰器中引用以再次更改选项。 -```typescript title="app.module.ts" + ```typescript title="app.module.ts" @Module({ imports: [ ThrottlerModule.forRoot([ @@ -109,7 +109,7 @@ findAll() { 以下示例演示如何为 Express 适配器启用 `trust proxy`: -```typescript title="main.ts" + ```typescript title="main.ts" import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { NestExpressApplication } from '@nestjs/platform-express'; @@ -125,7 +125,7 @@ bootstrap(); 启用 `trust proxy` 允许您从 `X-Forwarded-For` 头部检索原始 IP 地址。您还可以通过覆盖 `getTracker()` 方法来自定义应用程序的行为,从此头部提取 IP 地址而不是依赖 `req.ip`。以下示例演示如何为 Express 和 Fastify 实现这一点: -```typescript title="throttler-behind-proxy.guard.ts" + ```typescript title="throttler-behind-proxy.guard.ts" import { ThrottlerGuard } from '@nestjs/throttler'; import { Injectable } from '@nestjs/common'; @@ -137,7 +137,11 @@ export class ThrottlerBehindProxyGuard extends ThrottlerGuard { } ``` -> **提示** 您可以在 [这里](https://expressjs.com/en/api.html#req.ips) 找到 express 的 `req` 请求对象 API,在 [这里](https://www.fastify.io/docs/latest/Reference/Request/) 找到 fastify 的 API。 +:::info 提示 +您可以在 [这里](https://expressjs.com/en/api.html#req.ips) 找到 express 的 `req` 请求对象 API,在 [这里](https://www.fastify.io/docs/latest/Reference/Request/) 找到 fastify 的 API。 +::: + + ## WebSockets @@ -191,14 +195,22 @@ export class WsThrottlerGuard extends ThrottlerGuard { } ``` -> **提示** 如果您使用 ws,需要将 `_socket` 替换为 `conn`。 +:::info 提示 +如果您使用 ws,需要将 `_socket` 替换为 `conn`。 +::: + + 使用 WebSockets 时需要记住几点: - 守卫不能使用 `APP_GUARD` 或 `app.useGlobalGuards()` 注册 - 当达到限制时,Nest 将发出 `exception` 事件,因此确保有监听器准备好处理此事件 -> **提示** 如果您使用 `@nestjs/platform-ws` 包,可以使用 `client._socket.remoteAddress` 代替。 +:::info 提示 +如果您使用 `@nestjs/platform-ws` 包,可以使用 `client._socket.remoteAddress` 代替。 +::: + + ## GraphQL @@ -325,7 +337,11 @@ export class AppModule {} 对于分布式服务器,您可以使用社区存储提供程序 [Redis](https://github.com/jmcdo29/nest-lab/tree/main/packages/throttler-storage-redis) 以获得单一事实来源。 -> **注意** `ThrottlerStorage` 可以从 `@nestjs/throttler` 导入。 +:::info 注意 +`ThrottlerStorage` 可以从 `@nestjs/throttler` 导入。 +::: + + ## 时间助手 @@ -341,6 +357,10 @@ export class AppModule {} 任何 `@Throttle()` 装饰器现在也应该接受一个具有字符串键的对象,关联到限流器上下文的名称(如果没有名称,则为 `'default'`),以及具有 `limit` 和 `ttl` 键的对象值。 -> **重要** `ttl` 现在以**毫秒**为单位。如果您想要保持 ttl 以秒为单位以提高可读性,请使用此包中的 `seconds` 助手。它只是将 ttl 乘以 1000 使其以毫秒为单位。 +:::info 重要 +`ttl` 现在以 **毫秒** 为单位。如果您想要保持 ttl 以秒为单位以提高可读性,请使用此包中的 `seconds` 助手。它只是将 ttl 乘以 1000 使其以毫秒为单位。 +::: + + 更多信息请参见 [变更日志](https://github.com/nestjs/throttler/blob/master/CHANGELOG.md#500)。 diff --git a/docs/standalone-applications.md b/docs/standalone-applications.md index 088adba..056a43f 100644 --- a/docs/standalone-applications.md +++ b/docs/standalone-applications.md @@ -49,7 +49,9 @@ const tasksService = app.select(TasksModule).get(TasksService, { strict: true }) -> info **提示** 在非严格模式下,默认选择根模块。要选择任何其他模块,您需要手动导航模块图,逐步进行。 +:::info 提示 +在非严格模式下,默认选择根模块。要选择任何其他模块,您需要手动导航模块图,逐步进行。 +::: 请记住,独立应用程序没有任何网络监听器,因此任何与 HTTP 相关的 Nest 功能(例如,中间件、拦截器、管道、守卫等)在此上下文中都不可用。 diff --git a/docs/techniques/caching.md b/docs/techniques/caching.md index c008bf3..a4f8be0 100644 --- a/docs/techniques/caching.md +++ b/docs/techniques/caching.md @@ -38,7 +38,9 @@ export class AppModule {} constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {} ``` -> info **提示** `Cache` 类是从 `cache-manager` 导入的,而 `CACHE_MANAGER` 令牌则来自 `@nestjs/cache-manager` 包。 +:::info 提示 +`Cache` 类是从 `cache-manager` 导入的,而 `CACHE_MANAGER` 令牌则来自 `@nestjs/cache-manager` 包。 +::: `Cache` 实例(来自 `cache-manager` 包)上的 `get` 方法用于从缓存中检索项目。如果缓存中不存在该项目,将返回 `null`。 @@ -52,7 +54,9 @@ const value = await this.cacheManager.get('key'); await this.cacheManager.set('key', 'value'); ``` -> warning **注意** 内存缓存存储仅能保存[结构化克隆算法](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#javascript_types)支持类型的值。 +:::warning 注意 +内存缓存存储仅能保存[结构化克隆算法](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#javascript_types)支持类型的值。 +::: 您可以为该特定键手动指定 TTL(过期时间,以毫秒为单位),如下所示: @@ -82,7 +86,9 @@ await this.cacheManager.clear(); #### 自动缓存响应 -> warning **警告** 在 [GraphQL](/graphql/quick-start) 应用中,拦截器会针对每个字段解析器单独执行。因此,`CacheModule`(使用拦截器来缓存响应)将无法正常工作。 +:::warning 警告 + 在 [GraphQL](/graphql/quick-start) 应用中,拦截器会针对每个字段解析器单独执行。因此,`CacheModule`(使用拦截器来缓存响应)将无法正常工作。 +::: 要启用自动缓存响应,只需在你想要缓存数据的地方绑定 `CacheInterceptor`。 @@ -97,7 +103,9 @@ export class AppController { } ``` -> warning **警告** 只有 `GET` 端点会被缓存。此外,注入原生响应对象(`@Res()`)的 HTTP 服务器路由无法使用缓存拦截器。详情请参阅 [响应映射](../overview/interceptors#响应映射) 。 +:::warning 警告 + 只有 `GET` 端点会被缓存。此外,注入原生响应对象(`@Res()`)的 HTTP 服务器路由无法使用缓存拦截器。详情请参阅 [响应映射](../overview/interceptors#响应映射) 。 +::: 为了减少所需的样板代码,你可以将 `CacheInterceptor` 全局绑定到所有端点: @@ -158,7 +166,9 @@ export class AppController { } ``` -> info **提示** `@CacheKey()` 和 `@CacheTTL()` 装饰器是从 `@nestjs/cache-manager` 包导入的。 +:::info 提示 +`@CacheKey()` 和 `@CacheTTL()` 装饰器是从 `@nestjs/cache-manager` 包导入的。 +::: `@CacheKey()` 装饰器可以单独使用,也可以与 `@CacheTTL()` 装饰器配合使用,反之亦然。开发者可以选择仅覆盖 `@CacheKey()` 或仅覆盖 `@CacheTTL()`。未被装饰器覆盖的配置将使用全局注册的默认值(参见[自定义缓存](./caching) )。 @@ -188,7 +198,9 @@ handleEvent(client: Client, data: string[]): Observable { } ``` -> info **提示** `@CacheTTL()` 装饰器可以单独使用,也可以与对应的 `@CacheKey()` 装饰器配合使用。 +:::info 提示 +`@CacheTTL()` 装饰器可以单独使用,也可以与对应的 `@CacheKey()` 装饰器配合使用。 +::: #### 调整追踪方式 @@ -305,7 +317,9 @@ CacheModule.registerAsync({ 其工作原理与 `useClass` 相同,但存在一个关键区别——`CacheModule` 会查找已导入的模块以复用任何已创建的 `ConfigService`,而非实例化自身的副本。 -> info **提示**:`CacheModule#register`、`CacheModule#registerAsync` 和 `CacheOptionsFactory` 具有可选的泛型(类型参数),用于收窄存储特定的配置选项,从而确保类型安全。 +:::info 提示 + `CacheModule#register`、`CacheModule#registerAsync` 和 `CacheOptionsFactory` 具有可选的泛型(类型参数),用于收窄存储特定的配置选项,从而确保类型安全。 +::: 您还可以向 `registerAsync()` 方法传递所谓的 `extraProviders`。这些提供程序将与模块提供程序合并。 diff --git a/docs/techniques/configuration.md b/docs/techniques/configuration.md index 8359891..40fe96e 100644 --- a/docs/techniques/configuration.md +++ b/docs/techniques/configuration.md @@ -16,15 +16,19 @@ $ npm i --save @nestjs/config ``` -> info **提示** `@nestjs/config` 包内部使用了 [dotenv](https://github.com/motdotla/dotenv)。 +:::info 提示 +`@nestjs/config` 包内部使用了 [dotenv](https://github.com/motdotla/dotenv)。 +::: -> warning **注意**`@nestjs/config` 需要 TypeScript 4.1 或更高版本。 +:::warning 注意 +`@nestjs/config` 需要 TypeScript 4.1 或更高版本。 +::: #### 快速开始 安装过程完成后,我们可以导入 `ConfigModule`。通常我们会将其导入根模块 `AppModule`,并使用静态方法 `.forRoot()` 来控制其行为。在此步骤中,环境变量的键/值对会被解析和处理。稍后我们将看到在其他功能模块中访问 `ConfigModule` 的 `ConfigService` 类的几种方法。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -95,7 +99,7 @@ ConfigModule.forRoot({ 自定义配置文件导出一个返回配置对象的工厂函数。该配置对象可以是任意嵌套的普通 JavaScript 对象。`process.env` 对象将包含完全解析后的环境变量键值对(其中 `.env` 文件及外部定义的变量会按照[上文](techniques/configuration#入门)所述方式解析合并)。由于您控制着返回的配置对象,因此可以添加任何必要的逻辑来将值转换为适当类型、设置默认值等。例如: -```typescript title="config/configuration" + ```typescript title="config/configuration.ts" export default () => ({ port: parseInt(process.env.PORT, 10) || 3000, database: { @@ -120,7 +124,10 @@ import configuration from './config/configuration'; export class AppModule {} ``` -> info **注意** 分配给 `load` 属性的值是一个数组,允许您加载多个配置文件(例如 `load: [databaseConfig, authConfig]` ) +:::info 注意 +分配给 `load` 属性的值是一个数组,允许您加载多个配置文件(例如 `load: [databaseConfig, authConfig]` ) +::: + 通过自定义配置文件,我们还可以管理 YAML 文件等自定义文件。以下是使用 YAML 格式的配置示例: @@ -148,7 +155,7 @@ $ npm i -D @types/js-yaml 安装该包后,使用 `yaml#load` 函数加载我们刚才创建的 YAML 文件。 -```typescript title="config/configuration" + ```typescript title="config/configuration.ts" import { readFileSync } from 'fs'; import * as yaml from 'js-yaml'; import { join } from 'path'; @@ -162,13 +169,15 @@ export default () => { }; ``` -> warning **注意** Nest CLI 在构建过程中不会自动将"assets"(非 TS 文件)移动到 `dist` 文件夹。为确保 YAML 文件被复制,您需要在 `nest-cli.json` 文件的 `compilerOptions#资源` 对象中进行指定。例如,如果 `config` 文件夹与 `src` 文件夹位于同一层级,则添加值为 `"assets": [{"include": "../config/*.yaml", "outDir": "./dist/config"}]` 的 `compilerOptions#资源`。了解更多[此处](/cli/monorepo#资源) 。 +:::warning 注意 + Nest CLI 在构建过程中不会自动将"assets"(非 TS 文件)移动到 `dist` 文件夹。为确保 YAML 文件被复制,您需要在 `nest-cli.json` 文件的 `compilerOptions#资源` 对象中进行指定。例如,如果 `config` 文件夹与 `src` 文件夹位于同一层级,则添加值为 `"assets": [{"include": "../config/*.yaml", "outDir": "./dist/config"}]` 的 `compilerOptions#资源`。了解更多[此处](/cli/monorepo#资源) 。 +::: 快速提示 - 即使您在 NestJS 的 `ConfigModule` 中使用 `validationSchema` 选项,配置文件也不会自动验证。如果您需要验证或想应用任何转换,必须在工厂函数中处理这些操作,因为在那里您可以完全控制配置对象。这使您能够根据需要实现任何自定义验证逻辑。 例如,若需确保端口号处于特定范围内,可在工厂函数中添加验证步骤: -```typescript title="config/configuration" + ```typescript title="config/configuration.ts" export default () => { const config = yaml.load( readFileSync(join(__dirname, YAML_CONFIG_FILENAME), 'utf8'), @@ -190,7 +199,7 @@ export default () => { 要从 `ConfigService` 获取配置值,首先需要注入 `ConfigService`。与任何提供程序一样,需将其所属模块——`ConfigModule`——导入到将使用它的模块中(除非在传递给 `ConfigModule.forRoot()` 方法的选项对象中将 `isGlobal` 属性设为 `true`)。如下所示将其导入功能模块。 -```typescript title="feature.module" + ```typescript title="feature.module.ts" @Module({ imports: [ConfigModule], // ... @@ -203,7 +212,9 @@ export default () => { constructor(private configService: ConfigService) {} ``` -> info **提示** `ConfigService` 是从 `@nestjs/config` 包导入的。 +:::info 提示 +`ConfigService` 是从 `@nestjs/config` 包导入的。 +::: 并在我们的类中使用它: @@ -278,13 +289,15 @@ constructor(private configService: ConfigService<{ PORT: number }, true>) { } ``` -> info **提示** 为确保 `ConfigService#get` 方法仅从自定义配置文件中获取值而忽略 `process.env` 变量,请在 `ConfigModule` 的 `forRoot()` 方法的选项对象中将 `skipProcessEnv` 选项设为 `true`。 +:::info 提示 +为确保 `ConfigService#get` 方法仅从自定义配置文件中获取值而忽略 `process.env` 变量,请在 `ConfigModule` 的 `forRoot()` 方法的选项对象中将 `skipProcessEnv` 选项设为 `true`。 +::: #### 配置命名空间 `ConfigModule` 允许您定义并加载多个自定义配置文件,如上方[自定义配置文件](techniques/configuration#自定义配置文件)所示。您可以通过嵌套配置对象来管理复杂的配置对象层次结构,如该章节所示。或者,您也可以使用 `registerAs()` 函数返回一个"命名空间"配置对象,如下所示: -```typescript title="config/database.config" + ```typescript title="config/database.config.ts" export default registerAs('database', () => ({ host: process.env.DATABASE_HOST, port: process.env.DATABASE_PORT || 5432 @@ -293,7 +306,9 @@ export default registerAs('database', () => ({ 与自定义配置文件相同,在 `registerAs()` 工厂函数内部,`process.env` 对象将包含完全解析的环境变量键值对(其中 `.env` 文件和外部定义的变量会按照[上文](techniques/configuration#入门)所述进行解析和合并)。 -> info **提示** `registerAs` 函数是从 `@nestjs/config` 包中导出的。 +:::info 提示 +`registerAs` 函数是从 `@nestjs/config` 包中导出的。 +::: 通过 `forRoot()` 方法选项对象中的 `load` 属性加载命名空间配置,其加载方式与加载自定义配置文件相同: @@ -325,7 +340,9 @@ constructor( ) {} ``` -> info **提示** `ConfigType` 是从 `@nestjs/config` 包导出的。 +:::info 提示 +`ConfigType` 是从 `@nestjs/config` 包导出的。 +::: #### 模块中的命名空间配置 @@ -379,7 +396,9 @@ import databaseConfig from './config/database.config'; export class DatabaseModule {} ``` -> info **警告** 在某些情况下,您可能需要通过 `onModuleInit()` 钩子而非构造函数来访问通过部分注册加载的属性。这是因为 `forFeature()` 方法会在模块初始化期间执行,而模块初始化的顺序是不确定的。如果您在构造函数中访问由其他模块以此方式加载的值,配置所依赖的模块可能尚未初始化。`onModuleInit()` 方法仅在其依赖的所有模块都初始化完成后才会运行,因此这种技术是安全的。 +:::warning 警告 + 在某些情况下,您可能需要通过 `onModuleInit()` 钩子而非构造函数来访问通过部分注册加载的属性。这是因为 `forFeature()` 方法会在模块初始化期间执行,而模块初始化的顺序是不确定的。如果您在构造函数中访问由其他模块以此方式加载的值,配置所依赖的模块可能尚未初始化。`onModuleInit()` 方法仅在其依赖的所有模块都初始化完成后才会运行,因此这种技术是安全的。 +::: #### 模式验证 @@ -396,7 +415,7 @@ $ npm install --save joi 现在我们可以定义一个 Joi 验证模式,并通过 `forRoot()` 方法选项对象中的 `validationSchema` 属性传递它,如下所示: -```typescript title="app.module" + ```typescript title="app.module.ts" import * as Joi from 'joi'; @Module({ @@ -418,7 +437,7 @@ export class AppModule {} 默认情况下,允许未知的环境变量(即键未在模式中定义的环境变量)且不会触发验证异常。默认情况下,所有验证错误都会被报告。您可以通过在 `forRoot()` 配置对象的 `validationOptions` 键中传递选项对象来修改这些行为。该选项对象可以包含任何由 [Joi 验证选项](https://joi.dev/api/?v=17.3.0#anyvalidatevalue-options)提供的标准验证选项属性。例如,要反转上述两个设置,可传递如下选项: -```typescript title="app.module" + ```typescript title="app.module.ts" import * as Joi from 'joi'; @Module({ @@ -447,7 +466,9 @@ export class AppModule {} 请注意,一旦决定传递 `validationOptions` 对象,任何未显式传递的设置都将默认使用 `Joi` 的标准默认值(而非 `@nestjs/config` 的默认值)。例如,如果在自定义的 `validationOptions` 对象中未指定 `allowUnknowns`,则该值将采用 `Joi` 的默认值 `false`。因此,最稳妥的做法是在自定义对象中**同时**指定这两个设置。 -> info **提示** 若要禁用预定义环境变量的验证,请在 `forRoot()` 方法的选项对象中将 `validatePredefined` 属性设为 `false`。预定义环境变量是指在模块导入前已设置的进程变量(`process.env` 变量)。例如,若使用 `PORT=3000 node main.js` 启动应用,则 `PORT` 即为预定义环境变量。 +:::info 提示 +若要禁用预定义环境变量的验证,请在 `forRoot()` 方法的选项对象中将 `validatePredefined` 属性设为 `false`。预定义环境变量是指在模块导入前已设置的进程变量(`process.env` 变量)。例如,若使用 `PORT=3000 node main.js` 启动应用,则 `PORT` 即为预定义环境变量。 +::: #### 自定义验证函数 @@ -458,7 +479,7 @@ export class AppModule {} - 一个带有验证约束的类, - 以及一个利用 `plainToInstance` 和 `validateSync` 函数的验证函数。 -```typescript title="env.validation" + ```typescript title="env.validation.ts" import { plainToInstance } from 'class-transformer'; import { IsEnum, IsNumber, Max, Min, validateSync } from 'class-validator'; @@ -496,7 +517,7 @@ export function validate(config: Record) { 配置完成后,将 `validate` 函数作为 `ConfigModule` 的配置选项使用,如下所示: -```typescript title="app.module" + ```typescript title="app.module.ts" import { validate } from './env.validation'; @Module({ @@ -526,7 +547,7 @@ export class ApiConfigService { 现在我们可以如下使用 getter 函数: -```typescript title="app.service" + ```typescript title="app.service.ts" @Injectable() export class AppService { constructor(apiConfigService: ApiConfigService) { @@ -592,11 +613,13 @@ SUPPORT_EMAIL=support@${APP_URL} 通过这种构造方式,变量 `SUPPORT_EMAIL` 将被解析为 `'support@mywebsite.com'`。注意其中使用了 `${...}` 语法来触发在 `SUPPORT_EMAIL` 定义内部解析 `APP_URL` 变量值。 -> info **提示** 该功能在内部使用了 [dotenv-expand](https://github.com/motdotla/dotenv-expand) 依赖包。 +:::info 提示 +该功能在内部使用了 [dotenv-expand](https://github.com/motdotla/dotenv-expand) 依赖包。 +::: 要启用环境变量扩展功能,需在传递给 `ConfigModule` 的 `forRoot()` 方法的配置对象中设置 `expandVariables` 属性,如下所示: -```typescript title="app.module" + ```typescript title="app.module.ts" @Module({ imports: [ ConfigModule.forRoot({ diff --git a/docs/techniques/cookies.md b/docs/techniques/cookies.md index 4c90768..424f6e0 100644 --- a/docs/techniques/cookies.md +++ b/docs/techniques/cookies.md @@ -38,7 +38,9 @@ findAll(@Req() request: Request) { } ``` -> info **提示** `@Req()` 装饰器需从 `@nestjs/common` 导入,而 `Request` 需从 `express` 包导入。 +:::info 提示 +`@Req()` 装饰器需从 `@nestjs/common` 导入,而 `Request` 需从 `express` 包导入。 +::: 要为输出响应附加 cookie,请使用 `Response#cookie()` 方法: @@ -49,9 +51,13 @@ findAll(@Res({ passthrough: true }) response: Response) { } ``` -> warning **警告** 如果希望将响应处理逻辑交由框架处理,请记得将 `passthrough` 选项设为 `true`,如上所示。更多信息请参阅 [此处](/overview/controllers#库特定方法) 。 +:::warning 警告 +如果希望将响应处理逻辑交由框架处理,请记得将 `passthrough` 选项设为 `true`,如上所示。更多信息请参阅 [此处](/overview/controllers#库特定方法) 。 +::: -> info **提示** `@Res()` 装饰器从 `@nestjs/common` 导入,而 `Response` 则来自 `express` 包。 +:::info 提示 +`@Res()` 装饰器从 `@nestjs/common` 导入,而 `Response` 则来自 `express` 包。 +::: #### 与 Fastify 一起使用 @@ -85,7 +91,10 @@ findAll(@Req() request: FastifyRequest) { } ``` -> info **注意** `@Req()` 装饰器是从 `@nestjs/common` 导入的,而 `FastifyRequest` 则来自 `fastify` 包。 +:::info 注意 +`@Req()` 装饰器是从 `@nestjs/common` 导入的,而 `FastifyRequest` 则来自 `fastify` 包。 +::: + 要为传出响应附加 cookie,请使用 `FastifyReply#setCookie()` 方法: @@ -98,9 +107,13 @@ findAll(@Res({ passthrough: true }) response: FastifyReply) { 要了解更多关于 `FastifyReply#setCookie()` 方法的信息,请查看此[页面](https://github.com/fastify/fastify-cookie#sending) 。 -> warning **警告** 如果希望将响应处理逻辑交由框架处理,请记得将 `passthrough` 选项设为 `true`,如上所示。更多信息请参阅 [此处](/overview/controllers#库特定方法) 。 +:::warning 警告 + 如果希望将响应处理逻辑交由框架处理,请记得将 `passthrough` 选项设为 `true`,如上所示。更多信息请参阅 [此处](/overview/controllers#库特定方法) 。 +::: -> info **提示** `@Res()` 装饰器从 `@nestjs/common` 导入,而 `FastifyReply` 则来自 `fastify` 包。 +:::info 提示 +`@Res()` 装饰器从 `@nestjs/common` 导入,而 `FastifyReply` 则来自 `fastify` 包。 +::: #### 创建自定义装饰器(跨平台) diff --git a/docs/techniques/events.md b/docs/techniques/events.md index e359c33..451b4e2 100644 --- a/docs/techniques/events.md +++ b/docs/techniques/events.md @@ -57,7 +57,9 @@ EventEmitterModule.forRoot({ constructor(private eventEmitter: EventEmitter2) {} ``` -> info **提示** 从 `@nestjs/event-emitter` 包中导入 `EventEmitter2`。 +:::info 提示 +从 `@nestjs/event-emitter` 包中导入 `EventEmitter2`。 +::: 然后在类中按如下方式使用: @@ -82,7 +84,9 @@ handleOrderCreatedEvent(payload: OrderCreatedEvent) { } ``` -> warning **警告** 事件订阅者不能是请求作用域的。 +:::warning 警告 +事件订阅者不能是请求作用域的。 +::: 第一个参数可以是简单事件发射器的 `string` 或 `symbol`,在通配符发射器情况下则是 `string | symbol | Array` 。 @@ -108,7 +112,9 @@ export type OnEventOptions = OnOptions & { }; ``` -> info **提示** 了解更多关于 `OnOptions` 选项对象的信息,请参阅 [`eventemitter2`](https://github.com/EventEmitter2/EventEmitter2#emitteronevent-listener-options-objectboolean)。 +:::info 提示 +了解更多关于 `OnOptions` 选项对象的信息,请参阅 [`eventemitter2`](https://github.com/EventEmitter2/EventEmitter2#emitteronevent-listener-options-objectboolean)。 +::: ```typescript @OnEvent('order.created', { async: true }) @@ -137,7 +143,9 @@ handleEverything(payload: any) { } ``` -> info **提示** `EventEmitter2` 类提供了多个实用方法来处理事件,例如 `waitFor` 和 `onAny`。您可以点击[此处](https://github.com/EventEmitter2/EventEmitter2)了解更多信息。 +:::info 提示 +`EventEmitter2` 类提供了多个实用方法来处理事件,例如 `waitFor` 和 `onAny`。您可以点击[此处](https://github.com/EventEmitter2/EventEmitter2)了解更多信息。 +::: #### 防止事件丢失 @@ -153,7 +161,10 @@ this.eventEmitter.emit( ); ``` -> info **注意** 这仅适用于在 `onApplicationBootstrap` 生命周期钩子完成之前发出的事件。 +:::info 注意 +这仅适用于在 `onApplicationBootstrap` 生命周期钩子完成之前发出的事件。 +::: + #### 示例 diff --git a/docs/techniques/file-upload.md b/docs/techniques/file-upload.md index 7c1cc47..e8699c9 100644 --- a/docs/techniques/file-upload.md +++ b/docs/techniques/file-upload.md @@ -2,7 +2,9 @@ 为处理文件上传,Nest 提供了一个基于 Express 的 [multer](https://github.com/expressjs/multer) 中间件包的内置模块。Multer 处理以 `multipart/form-data` 格式发布的数据,该格式主要用于通过 HTTP `POST` 请求上传文件。该模块完全可配置,您可以根据应用程序需求调整其行为。 -> warning **警告** Multer 无法处理不支持的多部分格式(`multipart/form-data`)数据。另请注意,该包与 `FastifyAdapter` 不兼容。 +:::warning 警告 +Multer 无法处理不支持的多部分格式(`multipart/form-data`)数据。另请注意,该包与 `FastifyAdapter` 不兼容。 +::: 为了获得更好的类型安全性,让我们安装 Multer 类型定义包: @@ -24,14 +26,18 @@ uploadFile(@UploadedFile() file: Express.Multer.File) { } ``` -> info **提示** `FileInterceptor()` 装饰器从 `@nestjs/platform-express` 包导出。`@UploadedFile()` 装饰器从 `@nestjs/common` 导出。 +:::info 提示 +`FileInterceptor()` 装饰器从 `@nestjs/platform-express` 包导出。`@UploadedFile()` 装饰器从 `@nestjs/common` 导出。 +::: `FileInterceptor()` 装饰器接收两个参数: - `fieldName`:字符串类型,提供 HTML 表单中包含文件的字段名称 - `options`:可选参数,类型为 `MulterOptions` 的对象。该对象与 multer 构造函数使用的对象相同(更多详情[参见此处](https://github.com/expressjs/multer#multeropts) )。 -> warning **警告**`FileInterceptor()` 可能与 Google Firebase 等第三方云服务提供商不兼容。 +:::warning 警告 +`FileInterceptor()` 可能与 Google Firebase 等第三方云服务提供商不兼容。 +::: #### 文件验证 @@ -118,7 +124,9 @@ export abstract class FileValidator> { } ``` -> info **提示** `FileValidator` 接口通过其 `isValid` 函数支持异步验证。为了利用类型安全,如果您使用 express(默认)作为驱动,还可以将 `file` 参数类型指定为 `Express.Multer.File`。 +:::info 提示 +`FileValidator` 接口通过其 `isValid` 函数支持异步验证。为了利用类型安全,如果您使用 express(默认)作为驱动,还可以将 `file` 参数类型指定为 `Express.Multer.File`。 +::: `FileValidator` 是一个常规类,可以访问文件对象并根据客户端提供的选项对其进行验证。Nest 提供了两个内置的 `FileValidator` 实现供您在项目中使用: @@ -139,7 +147,9 @@ export abstract class FileValidator> { file: Express.Multer.File, ``` -> info **提示** 如果验证器数量大幅增加或其选项使文件变得杂乱,可以将此数组定义在单独的文件中,并作为命名常量(如 `fileValidators`)导入此处。 +:::info 提示 +如果验证器数量大幅增加或其选项使文件变得杂乱,可以将此数组定义在单独的文件中,并作为命名常量(如 `fileValidators`)导入此处。 +::: 最后,您可以使用特殊的 `ParseFilePipeBuilder` 类来组合和构建验证器。如下所示使用它,可以避免手动实例化每个验证器,直接传递它们的选项即可: @@ -159,7 +169,9 @@ file: Express.Multer.File, file: Express.Multer.File, ``` -> info **提示** 默认情况下文件是必传的,但您可以通过在 `build` 函数选项中(与 `errorHttpStatusCode` 同级)添加 `fileIsRequired: false` 参数来使其变为可选。 +:::info 提示 +默认情况下文件是必传的,但您可以通过在 `build` 函数选项中(与 `errorHttpStatusCode` 同级)添加 `fileIsRequired: false` 参数来使其变为可选。 +::: #### 文件数组 @@ -179,7 +191,9 @@ uploadFile(@UploadedFiles() files: Array) { } ``` -> info **提示** `FilesInterceptor()` 装饰器从 `@nestjs/platform-express` 包导出。`@UploadedFiles()` 装饰器从 `@nestjs/common` 导出 +:::info 提示 +`FilesInterceptor()` 装饰器从 `@nestjs/platform-express` 包导出。`@UploadedFiles()` 装饰器从 `@nestjs/common` 导出 +::: #### 多个文件 @@ -201,7 +215,9 @@ uploadFile(@UploadedFiles() files: { avatar?: Express.Multer.File[], background? } ``` -> info **提示** `FileFieldsInterceptor()` 装饰器从 `@nestjs/platform-express` 包导出。`@UploadedFiles()` 装饰器从 `@nestjs/common` 导出。 +:::info 提示 +`FileFieldsInterceptor()` 装饰器从 `@nestjs/platform-express` 包导出。`@UploadedFiles()` 装饰器从 `@nestjs/common` 导出。 +::: #### 任意文件 @@ -217,7 +233,9 @@ uploadFile(@UploadedFiles() files: Array) { } ``` -> info **提示** `AnyFilesInterceptor()` 装饰器从 `@nestjs/platform-express` 包导出。`@UploadedFiles()` 装饰器从 `@nestjs/common` 导出。 +:::info 提示 +`AnyFilesInterceptor()` 装饰器从 `@nestjs/platform-express` 包导出。`@UploadedFiles()` 装饰器从 `@nestjs/common` 导出。 +::: #### 默认选项 @@ -229,7 +247,9 @@ MulterModule.register({ }); ``` -> info **提示** `MulterModule` 模块从 `@nestjs/platform-express` 包导出。 +:::info 提示 +`MulterModule` 模块从 `@nestjs/platform-express` 包导出。 +::: #### Azure 存储及其他云提供商 diff --git a/docs/techniques/http-module.md b/docs/techniques/http-module.md index 19de4e6..6066c9a 100644 --- a/docs/techniques/http-module.md +++ b/docs/techniques/http-module.md @@ -2,7 +2,9 @@ [Axios](https://github.com/axios/axios) 是一个功能丰富的 HTTP 客户端包,被广泛使用。Nest 封装了 Axios 并通过内置的 `HttpModule` 暴露它。`HttpModule` 导出了 `HttpService` 类,该类提供了基于 Axios 的方法来执行 HTTP 请求。该库还将生成的 HTTP 响应转换为 `Observables`。 -> info **提示** 你也可以直接使用任何通用的 Node.js HTTP 客户端库,包括 [got](https://github.com/sindresorhus/got) 或 [undici](https://github.com/nodejs/undici)。 +:::info 提示 +你也可以直接使用任何通用的 Node.js HTTP 客户端库,包括 [got](https://github.com/sindresorhus/got) 或 [undici](https://github.com/nodejs/undici)。 +::: #### 安装 @@ -26,7 +28,9 @@ export class CatsModule {} 接下来,通过常规的构造函数注入方式注入 `HttpService`。 -> info **提示**`HttpModule` 和 `HttpService` 是从 `@nestjs/axios` 包中导入的。 +:::info 提示 +`HttpModule` 和 `HttpService` 是从 `@nestjs/axios` 包中导入的。 +::: ```typescript @Injectable() @@ -39,7 +43,9 @@ export class CatsService { } ``` -> info **提示**`AxiosResponse` 是从 `axios` 包(`$ npm i axios`)导出的接口。 +:::info 提示 +`AxiosResponse` 是从 `axios` 包(`$ npm i axios`)导出的接口。 +::: 所有 `HttpService` 方法都会返回一个封装在 `Observable` 对象中的 `AxiosResponse`。 @@ -173,4 +179,7 @@ export class CatsService { } ``` -> info **提示** 请访问 RxJS 关于 [`firstValueFrom`](https://rxjs.dev/api/index/function/firstValueFrom) 和 [`lastValueFrom`](https://rxjs.dev/api/index/function/lastValueFrom) 的文档,以了解它们之间的区别。 +:::info 提示 + 请访问 RxJS 关于 [`firstValueFrom`](https://rxjs.dev/api/index/function/firstValueFrom) 和 [`lastValueFrom`](https://rxjs.dev/api/index/function/lastValueFrom) 的文档,以了解它们之间的区别。 +::: + diff --git a/docs/techniques/logger.md b/docs/techniques/logger.md index 8f13f09..b6394e0 100644 --- a/docs/techniques/logger.md +++ b/docs/techniques/logger.md @@ -93,7 +93,11 @@ const app = await NestFactory.create(AppModule, { 此外,如果您使用 [NestJS Mau](https://mau.nestjs.com),JSON 日志记录可以简化查看日志的过程,使其以结构化的格式良好组织,这对于调试和性能监控特别有用。 -> **注意** 当 `json` 设置为 `true` 时,`ConsoleLogger` 会自动通过将 `colors` 属性设为 `false` 来禁用文本着色。这确保输出保持为有效的 JSON 格式,不包含格式化痕迹。不过,出于开发目的,您可以通过显式将 `colors` 设为 `true` 来覆盖此行为。这会添加带颜色的 JSON 日志,使本地调试时的日志条目更易读。 +:::info 注意 +当 `json` 设置为 `true` 时,`ConsoleLogger` 会自动通过将 `colors` 属性设为 `false` 来禁用文本着色。这确保输出保持为有效的 JSON 格式,不包含格式化痕迹。不过,出于开发目的,您可以通过显式将 `colors` 设为 `true` 来覆盖此行为。这会添加带颜色的 JSON 日志,使本地调试时的日志条目更易读。 +::: + + 启用 JSON 日志记录后,日志输出将如下所示(单行形式): @@ -277,7 +281,10 @@ app.useLogger(app.get(MyLogger)); await app.listen(process.env.PORT ?? 3000); ``` -> info **注意** 在上面的示例中,我们将 `bufferLogs` 设置为 `true` 以确保所有日志都会被缓冲,直到附加了自定义日志记录器(本例中的 `MyLogger`)且应用程序初始化过程完成或失败。如果初始化过程失败,Nest 将回退到原始的 `ConsoleLogger` 来打印所有报告的错误消息。此外,您可以将 `autoFlushLogs` 设置为 `false`(默认为 `true`)以手动刷新日志(使用 `Logger.flush()` 方法)。 +:::info 注意 +在上面的示例中,我们将 `bufferLogs` 设置为 `true` 以确保所有日志都会被缓冲,直到附加了自定义日志记录器(本例中的 `MyLogger`)且应用程序初始化过程完成或失败。如果初始化过程失败,Nest 将回退到原始的 `ConsoleLogger` 来打印所有报告的错误消息。此外,您可以将 `autoFlushLogs` 设置为 `false`(默认为 `true`)以手动刷新日志(使用 `Logger.flush()` 方法)。 +::: + 这里我们在 `NestApplication` 实例上使用 `get()` 方法来获取 `MyLogger` 对象的单例实例。这种技术本质上是一种为 Nest "注入"日志记录器实例以供使用的方式。`app.get()` 调用会获取 `MyLogger` 的单例实例,并依赖于该实例首先在另一个模块中被注入,如上所述。 @@ -285,7 +292,7 @@ await app.listen(process.env.PORT ?? 3000); #### 注入自定义日志记录器 -首先,使用如下代码扩展内置日志记录器。我们提供 `scope` 选项作为 `ConsoleLogger` 类的配置元数据,指定一个[瞬时](/fundamentals/injection-scopes)作用域,以确保在每个功能模块中都有唯一的 `MyLogger` 实例。在本示例中,我们没有扩展单个 `ConsoleLogger` 方法(如 `log()`、`warn()` 等),但您可以选择这样做。 +首先,使用如下代码扩展内置日志记录器。我们提供 `scope` 选项作为 `ConsoleLogger` 类的配置元数据,指定一个[瞬时](/fundamentals/provider-scopes)作用域,以确保在每个功能模块中都有唯一的 `MyLogger` 实例。在本示例中,我们没有扩展单个 `ConsoleLogger` 方法(如 `log()`、`warn()` 等),但您可以选择这样做。 ```typescript import { Injectable, Scope, ConsoleLogger } from '@nestjs/common'; @@ -347,7 +354,9 @@ app.useLogger(new MyLogger()); await app.listen(process.env.PORT ?? 3000); ``` -> info **提示** 除了将 `bufferLogs` 设为 `true` 之外,您还可以通过 `logger: false` 指令临时禁用日志记录器。请注意,如果您向 `NestFactory.create` 传递 `logger: false` 参数,在调用 `useLogger` 之前将不会记录任何日志,因此可能会错过一些重要的初始化错误。如果您不介意部分初始消息会使用默认日志记录器进行记录,可以直接省略 `logger: false` 选项。 +:::info 提示 +除了将 `bufferLogs` 设为 `true` 之外,您还可以通过 `logger: false` 指令临时禁用日志记录器。请注意,如果您向 `NestFactory.create` 传递 `logger: false` 参数,在调用 `useLogger` 之前将不会记录任何日志,因此可能会错过一些重要的初始化错误。如果您不介意部分初始消息会使用默认日志记录器进行记录,可以直接省略 `logger: false` 选项。 +::: #### 使用外部日志记录器 diff --git a/docs/techniques/mongo.md b/docs/techniques/mongo.md index ca1ccfc..efd72be 100644 --- a/docs/techniques/mongo.md +++ b/docs/techniques/mongo.md @@ -10,7 +10,7 @@ $ npm i @nestjs/mongoose mongoose 安装完成后,我们可以将 `MongooseModule` 导入根模块 `AppModule`。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; @@ -30,7 +30,7 @@ export class AppModule {} 我们来定义 `CatSchema`: -```typescript title="schemas/cat.schema" + ```typescript title="schemas/cat.schema.ts" import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument } from 'mongoose'; @@ -51,7 +51,9 @@ export class Cat { export const CatSchema = SchemaFactory.createForClass(Cat); ``` -> info **提示** 请注意,你也可以使用 `DefinitionsFactory` 类(来自 `nestjs/mongoose`)生成原始模式定义。这允许你手动修改基于所提供元数据生成的模式定义。对于某些难以完全用装饰器表示的边缘情况,这非常有用。 +:::info 提示 +请注意,你也可以使用 `DefinitionsFactory` 类(来自 `nestjs/mongoose`)生成原始模式定义。这允许你手动修改基于所提供元数据生成的模式定义。对于某些难以完全用装饰器表示的边缘情况,这非常有用。 +::: `@Schema()` 装饰器将一个类标记为模式定义。它将我们的 `Cat` 类映射到同名的 MongoDB 集合,但末尾会添加一个"s"——因此最终的 Mongo 集合名称将是 `cats`。该装饰器接受一个可选参数,即模式选项对象。可以将其视为通常作为 `mongoose.Schema` 类构造函数的第二个参数传递的对象(例如 `new mongoose.Schema(_, options)` )。要了解有关可用模式选项的更多信息,请参阅[本章](https://mongoosejs.com/docs/guide.html#选项) 。 @@ -106,7 +108,9 @@ async findAllPopulated() { } ``` -> info **提示** 若无外联文档可供填充,类型可能是 `Owner | null`,具体取决于您的 [Mongoose 配置](https://mongoosejs.com/docs/populate.html#doc-not-found) 。或者,它可能会抛出错误,此时类型将为 `Owner`。 +:::info 提示 +若无外联文档可供填充,类型可能是 `Owner | null`,具体取决于您的 [Mongoose 配置](https://mongoosejs.com/docs/populate.html#doc-not-found) 。或者,它可能会抛出错误,此时类型将为 `Owner`。 +::: 最后,也可以将**原始**模式定义传递给装饰器。这在某些场景下非常有用,例如当某个属性表示一个未定义为类的嵌套对象时。为此,请使用 `@nestjs/mongoose` 包中的 `raw()` 函数,如下所示: @@ -132,7 +136,7 @@ export const CatSchema = new mongoose.Schema({ 让我们看看 `CatsModule`: -```typescript title="cats.module" + ```typescript title="cats.module.ts" import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { CatsController } from './cats.controller'; @@ -151,7 +155,7 @@ export class CatsModule {} 注册完模式后,就可以使用 `@InjectModel()` 装饰器将 `Cat` 模型注入到 `CatsService` 中: -```typescript title="cats.service" + ```typescript title="cats.service.ts" import { Model } from 'mongoose'; import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; @@ -216,7 +220,7 @@ export class CatsService { 某些项目需要连接多个数据库。使用本模块同样可以实现这一需求。要使用多个连接,首先需要创建这些连接。在这种情况下, **必须**为连接命名。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; @@ -233,7 +237,9 @@ import { MongooseModule } from '@nestjs/mongoose'; export class AppModule {} ``` -> warning **注意** 请勿创建多个未命名或同名的连接,否则它们会被覆盖。 +:::warning 注意 +请勿创建多个未命名或同名的连接,否则它们会被覆盖。 +::: 在此配置下,您需要告知 `MongooseModule.forFeature()` 函数应使用哪个连接。 @@ -273,7 +279,7 @@ export class CatsService { 如果仅需从命名数据库注入模型,可将连接名称作为 `@InjectModel()` 装饰器的第二个参数使用。 -```typescript title="cats.service" + ```typescript title="cats.service.ts" @Injectable() export class CatsService { constructor(@InjectModel(Cat.name, 'cats') private catModel: Model) {} @@ -354,7 +360,7 @@ export class AppModule {} 要一次性为所有模式注册插件,请调用 `Connection` 对象的 `.plugin()` 方法。您应在创建模型前访问连接,为此可使用 `connectionFactory`: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; @@ -377,7 +383,7 @@ export class AppModule {} 假设您需要在单个集合中追踪不同类型的事件。每个事件都将包含时间戳。 -```typescript title="event.schema" + ```typescript title="event.schema.ts" @Schema({ discriminatorKey: 'kind' }) export class Event { @Prop({ @@ -394,13 +400,15 @@ export class Event { export const EventSchema = SchemaFactory.createForClass(Event); ``` -> info **提示** Mongoose 通过"鉴别键"来区分不同的鉴别器模型,默认情况下该键为 `__t`。Mongoose 会在您的模式中添加一个名为 `__t` 的字符串路径,用于跟踪该文档属于哪个鉴别器的实例。您也可以使用 `discriminatorKey` 选项来定义鉴别路径。 +:::info 提示 +Mongoose 通过"鉴别键"来区分不同的鉴别器模型,默认情况下该键为 `__t`。Mongoose 会在您的模式中添加一个名为 `__t` 的字符串路径,用于跟踪该文档属于哪个鉴别器的实例。您也可以使用 `discriminatorKey` 选项来定义鉴别路径。 +::: `SignedUpEvent` 和 `ClickedLinkEvent` 实例将与通用事件存储在同一集合中。 现在,我们来定义 `ClickedLinkEvent` 类,如下所示: -```typescript title="click-link-event.schema" + ```typescript title="click-link-event.schema.ts" @Schema() export class ClickedLinkEvent { kind: string; @@ -415,7 +423,7 @@ export const ClickedLinkEventSchema = SchemaFactory.createForClass(ClickedLinkEv 以及 `SignUpEvent` 类: -```typescript title="sign-up-event.schema" + ```typescript title="sign-up-event.schema.ts" @Schema() export class SignUpEvent { kind: string; @@ -430,7 +438,7 @@ export const SignUpEventSchema = SchemaFactory.createForClass(SignUpEvent); 配置完成后,使用 `discriminators` 选项为指定模式注册鉴别器。该选项同时适用于 `MongooseModule.forFeature` 和 `MongooseModule.forFeatureAsync` : -```typescript title="event.module" + ```typescript title="event.module.ts" import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; @@ -574,7 +582,7 @@ MongooseModule.forRootAsync({ 要在父文档中嵌套子文档,您可以按如下方式定义模式: -```typescript title="name.schema" + ```typescript title="name.schema.ts" @Schema() export class Name { @Prop() @@ -589,7 +597,7 @@ export const NameSchema = SchemaFactory.createForClass(Name); 然后在父模式中引用子文档: -```typescript title="person.schema" + ```typescript title="person.schema.ts" @Schema() export class Person { @Prop(NameSchema) @@ -607,7 +615,7 @@ export type PersonDocument = HydratedDocument; 如果需要包含多个子文档,可以使用子文档数组。重要的是要相应地覆盖属性的类型: -```typescript title="name.schema" + ```typescript title="name.schema.ts" @Schema() export class Person { @Prop([NameSchema]) @@ -644,7 +652,9 @@ class Person { } ``` -> info **提示** `@Virtual()` 装饰器是从 `@nestjs/mongoose` 包中导入的。 +:::info 提示 +`@Virtual()` 装饰器是从 `@nestjs/mongoose` 包中导入的。 +::: 在此示例中,`fullName` 虚拟属性由 `firstName` 和 `lastName` 派生而来。虽然访问时表现得像普通属性,但它永远不会被保存到 MongoDB 文档中。 diff --git a/docs/techniques/mvc.md b/docs/techniques/mvc.md index 034d50a..85c6591 100644 --- a/docs/techniques/mvc.md +++ b/docs/techniques/mvc.md @@ -17,7 +17,7 @@ $ npm install --save hbs 我们使用了 `hbs`([Handlebars](https://github.com/pillarjs/hbs#readme))模板引擎,当然你也可以根据需求选择其他引擎。安装完成后,需要通过以下代码配置 express 实例: -```typescript title="main" + ```typescript title="main.ts" import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { join } from 'path'; @@ -58,7 +58,7 @@ bootstrap(); 接下来,打开 `app.controller` 文件,将 `root()` 方法替换为以下代码: -```typescript title="app.controller" + ```typescript title="app.controller.ts" import { Get, Controller, Render } from '@nestjs/common'; @Controller() @@ -79,9 +79,11 @@ export class AppController { 如果应用逻辑需要动态决定渲染哪个模板,则应使用 `@Res()` 装饰器,并在路由处理器中提供视图名称,而非在 `@Render()` 装饰器中指定: -> info **提示** 当 Nest 检测到 `@Res()` 装饰器时,会注入特定库的 `response` 对象。我们可以利用该对象动态渲染模板。了解更多关于 `response` 对象 API 的信息请[点击此处](https://expressjs.com/en/api.html) 。 +:::info 提示 +当 Nest 检测到 `@Res()` 装饰器时,会注入特定库的 `response` 对象。我们可以利用该对象动态渲染模板。了解更多关于 `response` 对象 API 的信息请[点击此处](https://expressjs.com/en/api.html) 。 +::: -```typescript title="app.controller" + ```typescript title="app.controller.ts" import { Get, Controller, Res } from '@nestjs/common'; import { Response } from 'express'; import { AppService } from './app.service'; @@ -114,7 +116,7 @@ $ npm i --save @fastify/static @fastify/view handlebars 接下来的步骤与 Express 几乎相同,仅存在一些平台特有的细微差异。安装过程完成后,打开 `main.ts` 文件并更新其内容: -```typescript title="main" + ```typescript title="main.ts" import { NestFactory } from '@nestjs/core'; import { NestFastifyApplication, FastifyAdapter } from '@nestjs/platform-fastify'; import { AppModule } from './app.module'; @@ -144,7 +146,7 @@ Fastify API 存在一些差异,但这些方法调用的最终结果相同。 配置方式如下: -```typescript title="app.controller" + ```typescript title="app.controller.ts" import { Get, Controller, Render } from '@nestjs/common'; @Controller() @@ -159,7 +161,7 @@ export class AppController { 或者,您也可以使用 `@Res()` 装饰器直接注入响应对象并指定要渲染的视图,如下所示: -```typescript title="app.controller" + ```typescript title="app.controller.ts" import { Res } from '@nestjs/common'; import { FastifyReply } from 'fastify'; diff --git a/docs/techniques/performance.md b/docs/techniques/performance.md index 5bd2d9e..5bab3f5 100644 --- a/docs/techniques/performance.md +++ b/docs/techniques/performance.md @@ -2,7 +2,10 @@ 默认情况下,Nest 使用 [Express](https://expressjs.com/) 框架。如前所述,Nest 也兼容其他库,例如 [Fastify](https://github.com/fastify/fastify)。Nest 通过实现框架适配器来达成这种框架无关性,该适配器的主要功能是将中间件和处理器代理到相应库的特定实现。 -> info **注意** 要实现框架适配器,目标库必须提供与 Express 类似的请求/响应管道处理机制。 +:::info 注意 +要实现框架适配器,目标库必须提供与 Express 类似的请求/响应管道处理机制。 +::: + [Fastify](https://github.com/fastify/fastify) 是 Nest 的绝佳替代框架,因为它以类似 Express 的方式解决设计问题。但 fastify 比 Express **快得多** ,基准测试结果几乎快两倍。一个合理的问题是:为什么 Nest 默认使用 Express 作为 HTTP 提供者?原因是 Express 使用广泛、知名度高,并拥有大量兼容中间件,这些都可以被 Nest 用户直接使用。 @@ -20,7 +23,7 @@ $ npm i --save @nestjs/platform-fastify 安装 Fastify 平台后,我们就可以使用 `FastifyAdapter` 了。 -```typescript title="main" + ```typescript title="main.ts" import { NestFactory } from '@nestjs/core'; import { FastifyAdapter, @@ -77,7 +80,7 @@ new FastifyAdapter({ logger: true }); 中间件函数获取的是原始的 `req` 和 `res` 对象,而非 Fastify 的封装对象。这是底层使用的 `middie` 包以及 `fastify` 的工作机制 - 更多信息请参阅此[页面](https://www.fastify.io/docs/latest/Reference/Middleware/) -```typescript title="logger.middleware" + ```typescript title="logger.middleware.ts" import { Injectable, NestMiddleware } from '@nestjs/common'; import { FastifyRequest, FastifyReply } from 'fastify'; @@ -113,7 +116,9 @@ newFeature() { } ``` -> info **提示**`@RouteConfig()` 和 `@RouteConstraints` 是从 `@nestjs/platform-fastify` 导入的。 +:::info 提示 +`@RouteConfig()` 和 `@RouteConstraints` 是从 `@nestjs/platform-fastify` 导入的。 +::: #### 示例 diff --git a/docs/techniques/queues.md b/docs/techniques/queues.md index 1ec31f8..caca605 100644 --- a/docs/techniques/queues.md +++ b/docs/techniques/queues.md @@ -22,7 +22,7 @@ $ npm install --save @nestjs/bullmq bullmq 安装完成后,我们可以将 `BullModule` 导入根 `AppModule` 中。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { BullModule } from '@nestjs/bullmq'; @@ -57,7 +57,9 @@ BullModule.registerQueue({ }); ``` -> info **提示** 通过向 `registerQueue()` 方法传递多个以逗号分隔的配置对象,可以创建多个队列。 +:::info 提示 +通过向 `registerQueue()` 方法传递多个以逗号分隔的配置对象,可以创建多个队列。 +::: `registerQueue()` 方法用于实例化和/或注册队列。队列在连接到具有相同凭证的同一底层 Redis 数据库的模块和进程之间共享。每个队列通过其 name 属性保持唯一性。队列名称既用作注入令牌(用于将队列注入控制器/提供者),也用作装饰器的参数,以将消费者类和监听器与队列关联起来。 @@ -126,7 +128,9 @@ export class AudioService { } ``` -> info **提示** `@InjectQueue()` 装饰器通过队列名称来标识队列,该名称在 `registerQueue()` 方法调用中提供(例如 `'audio'`)。 +:::info 提示 +`@InjectQueue()` 装饰器通过队列名称来标识队列,该名称在 `registerQueue()` 方法调用中提供(例如 `'audio'`)。 +::: 现在,通过调用队列的 `add()` 方法来添加任务,传入一个用户自定义的任务对象。任务以可序列化的 JavaScript 对象形式表示(因为它们会存储在 Redis 数据库中)。传入的任务对象结构可以自由定义,用于体现任务对象的语义。同时需要为任务指定名称,这样就能创建专门的[消费者](techniques/queues#消费者)来仅处理特定名称的任务。 @@ -202,7 +206,9 @@ import { Processor } from '@nestjs/bullmq'; export class AudioConsumer {} ``` -> info **提示** 消费者必须注册为 `providers`,这样 `@nestjs/bullmq` 包才能识别它们。 +:::info 提示 +消费者必须注册为 `providers`,这样 `@nestjs/bullmq` 包才能识别它们。 +::: 装饰器的字符串参数(例如 `'audio'`)表示要与类方法关联的队列名称。 @@ -230,7 +236,9 @@ export class AudioConsumer extends WorkerHost { 在旧版本 Bull 中,您可以通过将特定 `name` 传递给 `@Process()` 装饰器来指定某个任务处理方法**仅**处理特定类型的任务(具有特定 `name` 的任务),如下所示。 -> warning **注意** 这在 BullMQ 中无效,请继续阅读。 +:::warning 注意 +这在 BullMQ 中无效,请继续阅读。 +::: ```typescript @Process('transcode') @@ -286,7 +294,9 @@ constructor(@Inject(JOB_REF) jobRef: Job) { } ``` -> info **提示** `JOB_REF` 令牌是从 `@nestjs/bullmq` 包导入的。 +:::info 提示 +`JOB_REF` 令牌是从 `@nestjs/bullmq` 包导入的。 +::: #### 事件监听器 @@ -333,7 +343,11 @@ export class AudioEventsListener extends QueueEventsHost { } ``` -> **提示** 队列事件监听器必须注册为 `providers`,这样 `@nestjs/bullmq` 包才能识别它们。 +:::info 提示 +队列事件监听器必须注册为 `providers`,这样 `@nestjs/bullmq` 包才能识别它们。 +::: + + 完整事件列表及其参数可作为 QueueEventsListener 的属性[在此查看](https://api.docs.bullmq.io/interfaces/v4.QueueEventsListener.html) 。 @@ -362,7 +376,7 @@ await audioQueue.resume(); - 能显著提高多核 CPU 的利用率。 - 减少与 Redis 的连接数。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { BullModule } from '@nestjs/bullmq'; import { join } from 'path'; @@ -378,7 +392,9 @@ import { join } from 'path'; export class AppModule {} ``` -> warning **警告** 请注意,由于您的函数在分叉进程中执行,依赖注入(及 IoC 容器)将不可用。这意味着您的处理器函数需要包含(或创建)其所依赖的所有外部实例。 +:::warning 警告 + 请注意,由于您的函数在分叉进程中执行,依赖注入(及 IoC 容器)将不可用。这意味着您的处理器函数需要包含(或创建)其所依赖的所有外部实例。 +::: #### 异步配置 @@ -495,7 +511,11 @@ export class AudioService implements OnModuleInit { #### Bull 安装 -> **注意** 如果决定使用 BullMQ,请跳过本节及后续章节。 +:::info 注意 +如果决定使用 BullMQ,请跳过本节及后续章节。 +::: + + 要开始使用 Bull,我们首先需要安装所需的依赖项。 @@ -505,7 +525,7 @@ $ npm install --save @nestjs/bull bull 安装过程完成后,我们可以将 `BullModule` 导入根模块 `AppModule` 中。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { BullModule } from '@nestjs/bull'; @@ -540,7 +560,9 @@ BullModule.registerQueue({ }); ``` -> info **提示** 通过向 `registerQueue()` 方法传入多个逗号分隔的配置对象,可以创建多个队列。 +:::info 提示 +通过向 `registerQueue()` 方法传入多个逗号分隔的配置对象,可以创建多个队列。 +::: `registerQueue()` 方法用于实例化或注册队列。队列会被共享给连接到同一 Redis 底层数据库且使用相同凭证的模块和进程。每个队列通过其名称属性保持唯一性。队列名称既作为注入令牌(用于将队列注入控制器/提供者),也作为装饰器的参数来将消费者类与监听器同队列关联起来。 @@ -599,7 +621,11 @@ export class AudioService { } ``` -> **提示** `@InjectQueue()` 装饰器通过队列名称进行标识,该名称需与 `registerQueue()` 方法调用时提供的名称一致(例如 `'audio'`)。 +:::info 提示 +`@InjectQueue()` 装饰器通过队列名称进行标识,该名称需与 `registerQueue()` 方法调用时提供的名称一致(例如 `'audio'`)。 +::: + + 通过调用队列的 `add()` 方法并传入用户自定义的任务对象即可添加任务。任务以可序列化的 JavaScript 对象形式表示(因为它们将存储在 Redis 数据库中)。传入的任务对象结构可自定义,用于体现任务语义。 @@ -683,7 +709,10 @@ import { Processor } from '@nestjs/bull'; export class AudioConsumer {} ``` -> info **注意** 消费者必须注册为 `providers`,这样 `@nestjs/bull` 包才能识别它们。 +:::info 注意 +消费者必须注册为 `providers`,这样 `@nestjs/bull` 包才能识别它们。 +::: + 其中装饰器的字符串参数(例如 `'audio'`)是与类方法关联的队列名称。 @@ -719,7 +748,9 @@ export class AudioConsumer { async transcode(job: Job) { ... } ``` -> warning **注意** 当为同一队列定义多个消费者时, `@Process({ concurrency: 1 })` 中的 `concurrency` 选项将不会生效。最低 `concurrency` 值将与定义的消费者数量匹配。即使 `@Process()` 处理器使用不同的`名称`来处理命名任务,此规则同样适用。 +:::warning 注意 + 当为同一队列定义多个消费者时, `@Process({ concurrency: 1 })` 中的 `concurrency` 选项将不会生效。最低 `concurrency` 值将与定义的消费者数量匹配。即使 `@Process()` 处理器使用不同的`名称`来处理命名任务,此规则同样适用。 +::: #### 请求作用域的消费者 @@ -740,7 +771,9 @@ constructor(@Inject(JOB_REF) jobRef: Job) { } ``` -> info **提示** `JOB_REF` 令牌是从 `@nestjs/bull` 包导入的。 +:::info 提示 +`JOB_REF` 令牌是从 `@nestjs/bull` 包导入的。 +::: #### 事件监听器 @@ -795,7 +828,11 @@ async onGlobalCompleted(jobId: number, result: any) { } ``` -> **提示** 要访问 `Queue` 对象(以进行 `getJob()` 调用),当然需要先注入它。此外,Queue 必须在你执行注入的模块中完成注册。 +:::info 提示 +要访问 `Queue` 对象(以进行 `getJob()` 调用),当然需要先注入它。此外,Queue 必须在你执行注入的模块中完成注册。 +::: + + 除了特定的事件监听器装饰器外,你还可以使用通用的 `@OnQueueEvent()` 装饰器,配合 `BullQueueEvents` 或 `BullQueueGlobalEvents` 枚举使用。了解更多关于事件的信息[请点击这里](https://github.com/OptimalBits/bull/blob/master/REFERENCE.md#事件) 。 diff --git a/docs/techniques/serialization.md b/docs/techniques/serialization.md index 5cdc491..26b337c 100644 --- a/docs/techniques/serialization.md +++ b/docs/techniques/serialization.md @@ -6,7 +6,9 @@ Nest 提供了内置功能来帮助确保这些操作能够以简单直接的方式完成。`ClassSerializerInterceptor` 拦截器利用强大的 [class-transformer](https://github.com/typestack/class-transformer) 包,提供了一种声明式且可扩展的对象转换方式。其基本操作是获取方法处理程序返回的值,并应用 [class-transformer](https://github.com/typestack/class-transformer) 中的 `instanceToPlain()` 函数。在此过程中,它可以应用实体/DTO 类上由 `class-transformer` 装饰器表达的规则,如下所述。 -> info **提示** 序列化不适用于 [StreamableFile](./streaming-files) 响应。 +:::info 提示 +序列化不适用于 [StreamableFile](./streaming-files) 响应。 +::: #### 排除属性 @@ -44,9 +46,15 @@ findOne(): UserEntity { } ``` -> **警告** 注意必须返回类的实例。如果返回普通的 JavaScript 对象(例如 `{ user: new UserEntity() }` ),该对象将无法被正确序列化。 +:::warning 警告 +注意必须返回类的实例。如果返回普通的 JavaScript 对象(例如 `{ user: new UserEntity() }` ),该对象将无法被正确序列化。 +::: -> info **提示** `ClassSerializerInterceptor` 是从 `@nestjs/common` 导入的。 + + +:::info 提示 +`ClassSerializerInterceptor` 是从 `@nestjs/common` 导入的。 +::: 当请求此端点时,客户端会收到以下响应: @@ -94,7 +102,9 @@ findOne(): UserEntity { } ``` -> info **提示** `@SerializeOptions()` 装饰器是从 `@nestjs/common` 导入的。 +:::info 提示 +`@SerializeOptions()` 装饰器是从 `@nestjs/common` 导入的。 +::: 通过 `@SerializeOptions()` 传递的选项会作为底层 `instanceToPlain()` 函数的第二个参数传递。在这个示例中,我们会自动排除所有以 `_` 前缀开头的属性。 @@ -127,7 +137,9 @@ findOne(@Query() { id }: { id: number }): UserEntity { } ``` -> info **提示** 通过为控制器指定预期的返回类型,你可以利用 TypeScript 的类型检查功能来确保返回的普通对象符合 DTO 或实体的结构。`plainToInstance` 函数不提供这种级别的类型提示,如果普通对象与预期的 DTO 或实体结构不匹配,可能会导致潜在错误。 +:::info 提示 +通过为控制器指定预期的返回类型,你可以利用 TypeScript 的类型检查功能来确保返回的普通对象符合 DTO 或实体的结构。`plainToInstance` 函数不提供这种级别的类型提示,如果普通对象与预期的 DTO 或实体结构不匹配,可能会导致潜在错误。 +::: #### 示例 diff --git a/docs/techniques/server-sent-events.md b/docs/techniques/server-sent-events.md index 8b1a25a..ac7502c 100644 --- a/docs/techniques/server-sent-events.md +++ b/docs/techniques/server-sent-events.md @@ -13,9 +13,14 @@ sse(): Observable { } ``` -> info **注意** `@Sse()` 装饰器和 `MessageEvent` 接口从 `@nestjs/common` 导入,而 `Observable`、`interval` 和 `map` 则从 `rxjs` 包导入。 +:::info 注意 +`@Sse()` 装饰器和 `MessageEvent` 接口从 `@nestjs/common` 导入,而 `Observable`、`interval` 和 `map` 则从 `rxjs` 包导入。 +::: -> warning **警告** Server-Sent Events 路由必须返回一个 `Observable` 流。 + +:::warning 警告 +Server-Sent Events 路由必须返回一个 `Observable` 流。 +::: 在上面的示例中,我们定义了一个名为 `sse` 的路由,它将允许我们传播实时更新。这些事件可以使用 [EventSource API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) 进行监听。 diff --git a/docs/techniques/sessions.md b/docs/techniques/sessions.md index de0c7dc..a99110b 100644 --- a/docs/techniques/sessions.md +++ b/docs/techniques/sessions.md @@ -25,7 +25,9 @@ app.use( ); ``` -> warning **注意** 默认的服务器端会话存储特意未设计用于生产环境。在大多数情况下会出现内存泄漏,无法扩展到单个进程之外,仅适用于调试和开发。更多信息请参阅[官方仓库](https://github.com/expressjs/session) 。 +:::warning 注意 +默认的服务器端会话存储特意未设计用于生产环境。在大多数情况下会出现内存泄漏,无法扩展到单个进程之外,仅适用于调试和开发。更多信息请参阅[官方仓库](https://github.com/expressjs/session) 。 +::: `secret` 用于签署会话 ID cookie。可以是单个密钥的字符串,也可以是多个密钥的数组。如果提供了密钥数组,则只有第一个元素会用于签署会话 ID cookie,而在验证请求中的签名时将考虑所有元素。密钥本身不应容易被人工解析,最好是一组随机字符。 @@ -35,7 +37,10 @@ app.use( 你可以向 `session` 中间件传递其他多个选项,更多信息请参阅 [API 文档](https://github.com/expressjs/session#选项) 。 -> info **注意** 请注意 `secure: true` 是一个推荐选项。但这要求网站启用 HTTPS,即安全 cookie 需要 HTTPS 协议。如果设置了 secure 选项却通过 HTTP 访问站点,cookie 将不会被设置。如果你的 node.js 部署在代理后方且使用 `secure: true`,则需要在 express 中设置 `"trust proxy"`。 +:::info 注意 +请注意 `secure: true` 是一个推荐选项。但这要求网站启用 HTTPS,即安全 cookie 需要 HTTPS 协议。如果设置了 secure 选项却通过 HTTP 访问站点,cookie 将不会被设置。如果你的 node.js 部署在代理后方且使用 `secure: true`,则需要在 express 中设置 `"trust proxy"`。 +::: + 完成上述配置后,你现在可以像下面这样在路由处理程序中设置和读取会话值: @@ -46,7 +51,9 @@ findAll(@Req() request: Request) { } ``` -> info **提示** `@Req()` 装饰器是从 `@nestjs/common` 导入的,而 `Request` 则来自 `express` 包。 +:::info 提示 +`@Req()` 装饰器是从 `@nestjs/common` 导入的,而 `Request` 则来自 `express` 包。 +::: 或者,您也可以使用 `@Session()` 装饰器从请求中提取会话对象,如下所示: @@ -57,7 +64,9 @@ findAll(@Session() session: Record) { } ``` -> info **提示** `@Session()` 装饰器是从 `@nestjs/common` 包导入的。 +:::info 提示 +`@Session()` 装饰器是从 `@nestjs/common` 包导入的。 +::: #### 与 Fastify 一起使用 @@ -83,7 +92,9 @@ await app.register(secureSession, { }); ``` -> info **您也可以预生成密钥** ( [查看说明](https://github.com/fastify/fastify-secure-session) )或使用[密钥轮换](https://github.com/fastify/fastify-secure-session#using-keys-with-key-rotation) 。 +:::info 您也可以预生成密钥 + ( [查看说明](https://github.com/fastify/fastify-secure-session) )或使用[密钥轮换](https://github.com/fastify/fastify-secure-session#using-keys-with-key-rotation) 。 +::: 更多可用选项请参阅[官方仓库](https://github.com/fastify/fastify-secure-session) 。 @@ -107,4 +118,7 @@ findAll(@Session() session: secureSession.Session) { } ``` -> info **提示** `@Session()` 装饰器是从 `@nestjs/common` 导入的,而 `secureSession.Session` 则来自 `@fastify/secure-session` 包(导入语句: `import * as secureSession from '@fastify/secure-session'` )。 +:::info 提示 + `@Session()` 装饰器是从 `@nestjs/common` 导入的,而 `secureSession.Session` 则来自 `@fastify/secure-session` 包(导入语句: `import * as secureSession from '@fastify/secure-session'` )。 +::: + diff --git a/docs/techniques/sql.md b/docs/techniques/sql.md index 6254b36..4b1e74b 100644 --- a/docs/techniques/sql.md +++ b/docs/techniques/sql.md @@ -18,7 +18,7 @@ $ npm install --save @nestjs/typeorm typeorm mysql2 安装完成后,我们可以将 `TypeOrmModule` 导入到根模块 `AppModule` 中。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; @@ -39,7 +39,9 @@ import { TypeOrmModule } from '@nestjs/typeorm'; export class AppModule {} ``` -> warning **注意** 切勿在生产环境中使用 `synchronize: true` 设置,否则可能导致生产数据丢失。 +:::warning 注意 +切勿在生产环境中使用 `synchronize: true` 设置,否则可能导致生产数据丢失。 +::: `forRoot()` 方法支持 [TypeORM](https://typeorm.io/data-source-options#common-data-source-options) 包中 `DataSource` 构造函数公开的所有配置属性。此外,还支持以下描述的若干额外配置属性。 @@ -60,11 +62,13 @@ export class AppModule {} -> info **提示** 了解更多数据源选项请点击[此处](https://typeorm.io/data-source-options) 。 +:::info 提示 +了解更多数据源选项请点击[此处](https://typeorm.io/data-source-options) 。 +::: 完成后,TypeORM `DataSource` 和 `EntityManager` 对象将可在整个项目中注入使用(无需导入任何模块),例如: -```typescript title="app.module" + ```typescript title="app.module.ts" import { DataSource } from 'typeorm'; @Module({ @@ -81,7 +85,7 @@ export class AppModule { 继续这个示例,我们至少需要一个实体。让我们定义 `User` 实体。 -```typescript title="user.entity" + ```typescript title="user.entity.ts" import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() @@ -100,13 +104,15 @@ export class User { } ``` -> info **提示** 了解更多关于实体的信息,请参阅 [TypeORM 文档](https://typeorm.io/#/entities) 。 +:::info 提示 +了解更多关于实体的信息,请参阅 [TypeORM 文档](https://typeorm.io/#/entities) 。 +::: `User` 实体文件位于 `users` 目录中。该目录包含与 `UsersModule` 相关的所有文件。您可以自行决定模型文件的存放位置,但我们建议将其创建在对应的**领域**附近,即相应的模块目录中。 要开始使用 `User` 实体,我们需要通过将其插入模块 `forRoot()` 方法选项中的 `entities` 数组来让 TypeORM 识别它(除非您使用静态 glob 路径): -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './users/user.entity'; @@ -130,7 +136,7 @@ export class AppModule {} 接下来,我们来看 `UsersModule`: -```typescript title="users.module" + ```typescript title="users.module.ts" import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UsersService } from './users.service'; @@ -147,7 +153,7 @@ export class UsersModule {} 该模块使用 `forFeature()` 方法来定义当前作用域中注册的存储库。完成此操作后,我们就可以使用 `@InjectRepository()` 装饰器将 `UsersRepository` 注入到 `UsersService` 中: -```typescript title="users.service" + ```typescript title="users.service.ts" import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -174,11 +180,13 @@ export class UsersService { } ``` -> warning **注意** 不要忘记将 `UsersModule` 导入到根 `AppModule` 中。 +:::warning 注意 + 不要忘记将 `UsersModule` 导入到根 `AppModule` 中。 +::: 如果你想在导入 `TypeOrmModule.forFeature` 的模块之外使用该存储库,需要重新导出由其生成的提供者。可以通过导出整个模块来实现,如下所示: -```typescript title="users.module" + ```typescript title="users.module.ts" import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; @@ -192,7 +200,7 @@ export class UsersModule {} 现在如果我们在 `UserHttpModule` 中导入 `UsersModule`,就可以在后者的提供者中使用 `@InjectRepository(User)`。 -```typescript title="users-http.module" + ```typescript title="users-http.module.ts" import { Module } from '@nestjs/common'; import { UsersModule } from './users.module'; import { UsersService } from './users.service'; @@ -231,7 +239,7 @@ export class UserHttpModule {} 要在实体中定义关系,请使用相应的**装饰器** 。例如,要定义每个 `User` 可以拥有多张照片,请使用 `@OneToMany()` 装饰器。 -```typescript title="user.entity" + ```typescript title="user.entity.ts" import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm'; import { Photo } from '../photos/photo.entity'; @@ -254,13 +262,15 @@ export class User { } ``` -> info **提示** 要了解更多关于 TypeORM 中的关系,请访问 [TypeORM 文档](https://typeorm.io/#/relations) 。 +:::info 提示 +要了解更多关于 TypeORM 中的关系,请访问 [TypeORM 文档](https://typeorm.io/#/relations) 。 +::: #### 自动加载实体 手动将实体添加到数据源选项的 `entities` 数组中可能非常繁琐。此外,从根模块引用实体破坏了应用程序领域边界,并导致实现细节泄漏到应用程序的其他部分。为解决这个问题,我们提供了替代方案。要自动加载实体,请将配置对象(传入 `forRoot()` 方法)的 `autoLoadEntities` 属性设置为 `true`,如下所示: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; @@ -277,7 +287,11 @@ export class AppModule {} 指定该选项后,通过 `forFeature()` 方法注册的每个实体都将自动添加到配置对象的 `entities` 数组中。 -> **警告** 请注意,未通过 `forFeature()` 方法注册,而仅通过关系从实体引用的实体,不会因 `autoLoadEntities` 设置而被包含。 +:::warning 警告 +请注意,未通过 `forFeature()` 方法注册,而仅通过关系从实体引用的实体,不会因 `autoLoadEntities` 设置而被包含。 +::: + + #### 分离实体定义 @@ -316,7 +330,9 @@ export const UserSchema = new EntitySchema({ }); ``` -> warning 错误 **警告** 如果提供了 `target` 选项,则 `name` 选项值必须与目标类的名称相同。如果不提供 `target`,则可以使用任意名称。 +:::warning 警告 +如果提供了 `target` 选项,则 `name` 选项值必须与目标类的名称相同。如果不提供 `target`,则可以使用任意名称。 +::: Nest 允许您在需要 `Entity` 的任何地方使用 `EntitySchema` 实例,例如: @@ -350,7 +366,11 @@ export class UsersService { } ``` -> **提示** `DataSource` 类是从 `typeorm` 包中导入的。 +:::info 提示 +`DataSource` 类是从 `typeorm` 包中导入的。 +::: + + 现在,我们可以使用这个对象来创建事务。 @@ -375,7 +395,10 @@ async createMany(users: User[]) { } ``` -> info **注意** 请注意 `dataSource` 仅用于创建 `QueryRunner`。但要测试这个类需要模拟整个 `DataSource` 对象(它暴露了多个方法)。因此,我们建议使用辅助工厂类(例如 `QueryRunnerFactory`)并定义一个接口,其中只包含维护事务所需的有限方法集。这种技术使得模拟这些方法变得相当简单。 +:::info 注意 +请注意 `dataSource` 仅用于创建 `QueryRunner`。但要测试这个类需要模拟整个 `DataSource` 对象(它暴露了多个方法)。因此,我们建议使用辅助工厂类(例如 `QueryRunnerFactory`)并定义一个接口,其中只包含维护事务所需的有限方法集。这种技术使得模拟这些方法变得相当简单。 +::: + 或者,你也可以使用回调风格的方法,通过 `DataSource` 对象的 `transaction` 方法来实现( [了解更多](https://typeorm.io/#/transactions/creating-and-using-transactions) )。 @@ -417,7 +440,9 @@ export class UserSubscriber implements EntitySubscriberInterface { } ``` -> error **警告** 事件订阅器不能是[请求作用域](/fundamentals/injection-scopes)的。 +:::warning 警告 + 事件订阅器不能是[请求作用域](/fundamentals/provider-scopes)的。 +::: 现在,将 `UserSubscriber` 类添加到 `providers` 数组中: @@ -437,7 +462,9 @@ import { UserSubscriber } from './user.subscriber'; export class UsersModule {} ``` -> info **提示** 了解更多关于实体订阅器的内容[请点击这里](https://typeorm.io/#/listeners-and-subscribers/what-is-a-subscriber) 。 +:::info 提示 +了解更多关于实体订阅器的内容[请点击这里](https://typeorm.io/#/listeners-and-subscribers/what-is-a-subscriber) 。 +::: #### 迁移 @@ -479,9 +506,14 @@ const defaultOptions = { export class AppModule {} ``` -> warning **注意** 如果您没有为数据源设置 `name`,其名称将被设为 `default`。请注意,不应存在多个未命名或同名的连接,否则它们会被覆盖。 +:::warning 注意 + 如果您没有为数据源设置 `name`,其名称将被设为 `default`。请注意,不应存在多个未命名或同名的连接,否则它们会被覆盖。 +::: + +:::warning 注意 + 如果您使用 `TypeOrmModule.forRootAsync`,则必须**同时**在 `useFactory` 之外设置数据源名称。例如: +::: -> warning **注意** 如果您使用 `TypeOrmModule.forRootAsync`,则必须**同时**在 `useFactory` 之外设置数据源名称。例如: > > ```typescript > TypeOrmModule.forRootAsync({ @@ -637,7 +669,11 @@ TypeOrmModule.forRootAsync({ 这种构造方式与 `useClass` 的工作原理相同,但有一个关键区别——`TypeOrmModule` 会查找已导入的模块来重用现有的 `ConfigService`,而不是实例化一个新的。 -> **提示** 请确保 `name` 属性定义在与 `useFactory`、`useClass` 或 `useValue` 属性相同的层级上。这样 Nest 才能正确地将数据源注册到相应的注入令牌下。 +:::info 提示 +请确保 `name` 属性定义在与 `useFactory`、`useClass` 或 `useValue` 属性相同的层级上。这样 Nest 才能正确地将数据源注册到相应的注入令牌下。 +::: + + #### 自定义数据源工厂 @@ -670,7 +706,11 @@ TypeOrmModule.forRootAsync({ }); ``` -> **提示** `DataSource` 类是从 `typeorm` 包导入的。 +:::info 提示 +`DataSource` 类是从 `typeorm` 包导入的。 +::: + + #### 示例 @@ -689,7 +729,7 @@ $ npm install --save-dev @types/sequelize 安装过程完成后,我们可以将 `SequelizeModule` 导入根模块 `AppModule` 中。 -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { SequelizeModule } from '@nestjs/sequelize'; @@ -738,7 +778,7 @@ export class AppModule {} 完成后,`Sequelize` 对象将可被注入到整个项目中(无需导入任何模块),例如: -```typescript title="app.service" + ```typescript title="app.service.ts" import { Injectable } from '@nestjs/common'; import { Sequelize } from 'sequelize-typescript'; @@ -752,7 +792,7 @@ export class AppService { Sequelize 实现了 Active Record 模式。通过该模式,您可以直接使用模型类与数据库交互。要继续这个示例,我们至少需要一个模型。让我们定义 `User` 模型。 -```typescript title="user.model" + ```typescript title="user.model.ts" import { Column, Model, Table } from 'sequelize-typescript'; @Table @@ -768,13 +808,15 @@ export class User extends Model { } ``` -> info **提示** 了解更多可用的装饰器请[点击此处](https://github.com/RobinBuschmann/sequelize-typescript#column) 。 +:::info 提示 +了解更多可用的装饰器请[点击此处](https://github.com/RobinBuschmann/sequelize-typescript#column) 。 +::: `User` 模型文件位于 `users` 目录中。该目录包含与 `UsersModule` 相关的所有文件。您可以自行决定模型文件的存放位置,但我们建议将其创建在对应的**领域**附近,即相应的模块目录中。 要开始使用 `User` 模型,我们需要通过将其插入模块 `forRoot()` 方法选项中的 `models` 数组来让 Sequelize 识别它: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { SequelizeModule } from '@nestjs/sequelize'; import { User } from './users/user.model'; @@ -797,7 +839,7 @@ export class AppModule {} 接下来,我们来看 `UsersModule`: -```typescript title="users.module" + ```typescript title="users.module.ts" import { Module } from '@nestjs/common'; import { SequelizeModule } from '@nestjs/sequelize'; import { User } from './user.model'; @@ -814,7 +856,7 @@ export class UsersModule {} 该模块使用 `forFeature()` 方法来定义当前作用域中注册的模型。完成此操作后,我们就可以使用 `@InjectModel()` 装饰器将 `UserModel` 注入到 `UsersService` 中: -```typescript title="users.service" + ```typescript title="users.service.ts" import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/sequelize'; import { User } from './user.model'; @@ -845,11 +887,13 @@ export class UsersService { } ``` -> warning **注意** 不要忘记将 `UsersModule` 导入根模块 `AppModule` 中。 +:::warning 注意 + 不要忘记将 `UsersModule` 导入根模块 `AppModule` 中。 +::: 如果你想在导入 `SequelizeModule.forFeature` 的模块之外使用该模型,需要重新导出由其生成的 providers。可以通过导出整个模块来实现,如下所示: -```typescript title="users.module" + ```typescript title="users.module.ts" import { Module } from '@nestjs/common'; import { SequelizeModule } from '@nestjs/sequelize'; import { User } from './user.entity'; @@ -863,7 +907,7 @@ export class UsersModule {} 现在如果我们在 `UserHttpModule` 中导入 `UsersModule`,就可以在后者的 providers 中使用 `@InjectModel(User)` 了。 -```typescript title="users-http.module" + ```typescript title="users-http.module.ts" import { Module } from '@nestjs/common'; import { UsersModule } from './users.module'; import { UsersService } from './users.service'; @@ -902,7 +946,7 @@ export class UserHttpModule {} 要在模型中定义关系,请使用相应的**装饰器** 。例如,要定义每个 `User` 可以拥有多张照片,可使用 `@HasMany()` 装饰器。 -```typescript title="user.model" + ```typescript title="user.model.ts" import { Column, Model, Table, HasMany } from 'sequelize-typescript'; import { Photo } from '../photos/photo.model'; @@ -922,13 +966,15 @@ export class User extends Model { } ``` -> info **提示** 要了解更多关于 Sequelize 中的关联关系,请阅读[本章](https://github.com/RobinBuschmann/sequelize-typescript#model-association)内容。 +:::info 提示 +要了解更多关于 Sequelize 中的关联关系,请阅读[本章](https://github.com/RobinBuschmann/sequelize-typescript#model-association)内容。 +::: #### 自动加载模型 手动将模型添加到连接选项的 `models` 数组中可能很繁琐。此外,从根模块引用模型会破坏应用程序领域边界,导致实现细节泄漏到应用程序的其他部分。为解决此问题,可通过将配置对象(传入 `forRoot()` 方法)的 `autoLoadModels` 和 `synchronize` 属性都设为 `true` 来自动加载模型,如下所示: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { SequelizeModule } from '@nestjs/sequelize'; @@ -946,7 +992,9 @@ export class AppModule {} 指定该选项后,通过 `forFeature()` 方法注册的每个模型都将自动添加到配置对象的 `models` 数组中。 -> warning **警告** 请注意,未通过 `forFeature()` 方法注册、仅通过关联从模型引用的模型将不会被包含。 +:::warning 警告 + 请注意,未通过 `forFeature()` 方法注册、仅通过关联从模型引用的模型将不会被包含。 +::: #### Sequelize 事务 @@ -963,7 +1011,11 @@ export class UsersService { } ``` -> **提示** `Sequelize` 类是从 `sequelize-typescript` 包中导入的。 +:::info 提示 +`Sequelize` 类是从 `sequelize-typescript` 包中导入的。 +::: + + 现在,我们可以使用这个对象来创建事务。 @@ -989,7 +1041,10 @@ async createMany() { } ``` -> info **注意** `Sequelize` 实例仅用于启动事务。但要测试该类需要模拟整个 `Sequelize` 对象(它暴露了多个方法)。因此,我们建议使用辅助工厂类(例如 `TransactionRunner`)并定义一个接口,该接口仅包含维护事务所需的有限方法集。这种技术使得模拟这些方法变得相当简单。 +:::info 注意 +`Sequelize` 实例仅用于启动事务。但要测试该类需要模拟整个 `Sequelize` 对象(它暴露了多个方法)。因此,我们建议使用辅助工厂类(例如 `TransactionRunner`)并定义一个接口,该接口仅包含维护事务所需的有限方法集。这种技术使得模拟这些方法变得相当简单。 +::: + #### 迁移 @@ -1031,7 +1086,9 @@ const defaultOptions = { export class AppModule {} ``` -> warning **注意** 如果您没有为连接设置 `name`,其名称将被设为 `default`。请注意不应存在多个未命名或同名的连接,否则它们会被覆盖。 +:::warning 注意 + 如果您没有为连接设置 `name`,其名称将被设为 `default`。请注意不应存在多个未命名或同名的连接,否则它们会被覆盖。 +::: 此时,您已注册了带有各自连接的 `User` 和 `Album` 模型。在此设置下,需要告知 `SequelizeModule.forFeature()` 方法和 `@InjectModel()` 装饰器应使用哪个连接。若未传递任何连接名称,则默认使用 `default` 连接。 diff --git a/docs/techniques/streaming-files.md b/docs/techniques/streaming-files.md index b3e8aa6..43581a7 100644 --- a/docs/techniques/streaming-files.md +++ b/docs/techniques/streaming-files.md @@ -1,6 +1,9 @@ ### 流式文件 -> info **注意** 本章展示如何从你的 **HTTP 应用**中流式传输文件。以下示例不适用于 GraphQL 或微服务应用。 +:::info 注意 +本章展示如何从你的 **HTTP 应用**中流式传输文件。以下示例不适用于 GraphQL 或微服务应用。 +::: + 有时你可能需要从 REST API 向客户端返回文件。在 Nest 中通常你会这样做: @@ -21,7 +24,9 @@ export class FileController { `StreamableFile` 是一个封装待返回流的类。要创建新的 `StreamableFile`,可以向 `StreamableFile` 构造函数传入 `Buffer` 或 `Stream`。 -> info **提示** `StreamableFile` 类可从 `@nestjs/common` 导入。 +:::info 提示 +`StreamableFile` 类可从 `@nestjs/common` 导入。 +::: #### 跨平台支持 diff --git a/docs/techniques/task-scheduling.md b/docs/techniques/task-scheduling.md index 2ac477e..38e0f22 100644 --- a/docs/techniques/task-scheduling.md +++ b/docs/techniques/task-scheduling.md @@ -12,7 +12,7 @@ $ npm install --save @nestjs/schedule 要激活任务调度功能,请将 `ScheduleModule` 导入根模块 `AppModule`,并运行如下所示的 `forRoot()` 静态方法: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; @@ -120,7 +120,11 @@ export class TasksService { 或者,你也可以向 `@Cron()` 装饰器传入一个 JavaScript 的 `Date` 对象。这样做会使任务在指定日期精确执行一次。 -> **提示** 使用 JavaScript 日期运算来安排相对于当前日期的任务。例如, `@Cron(new Date(Date.now() + 10 * 1000))` 可以让任务在应用启动 10 秒后运行。 +:::info 提示 +使用 JavaScript 日期运算来安排相对于当前日期的任务。例如, `@Cron(new Date(Date.now() + 10 * 1000))` 可以让任务在应用启动 10 秒后运行。 +::: + + 此外,你还可以将额外选项作为第二个参数传给 `@Cron()` 装饰器。 @@ -159,7 +163,11 @@ handleInterval() { } ``` -> **提示** 此机制底层使用 JavaScript 的 `setInterval()` 函数。您也可以使用 cron 任务来安排重复作业。 +:::info 提示 +此机制底层使用 JavaScript 的 `setInterval()` 函数。您也可以使用 cron 任务来安排重复作业。 +::: + + 若要通过[动态 API](/techniques/task-scheduling#动态调度模块-api) 在声明类外部控制声明式间隔,请使用以下构造将间隔与名称关联: @@ -183,7 +191,11 @@ handleTimeout() { } ``` -> **提示** 该机制底层使用了 JavaScript 的 `setTimeout()` 函数。 +:::info 提示 +该机制底层使用了 JavaScript 的 `setTimeout()` 函数。 +::: + + 如果发生异常,它将被记录到控制台,因为每个用 `@Timeout()` 注解的方法都会自动被包裹在 `try-catch` 代码块中。 @@ -208,7 +220,11 @@ handleTimeout() {} constructor(private schedulerRegistry: SchedulerRegistry) {} ``` -> **提示** 从 `@nestjs/schedule` 包中导入 `SchedulerRegistry`。 +:::info 提示 +从 `@nestjs/schedule` 包中导入 `SchedulerRegistry`。 +::: + + 然后在类中按如下方式使用。假设已通过以下声明创建了一个 cron job: @@ -237,7 +253,9 @@ console.log(job.lastDate()); - `nextDate()` - 返回作业下一次计划执行日期的 `DateTime` 表示形式。 - `nextDates(count: number)` - 提供一组(大小为 `count`)`DateTime` 表示形式,包含接下来会触发作业执行的日期集合。`count` 默认为 0,此时返回空数组。 -> info **提示** 在 `DateTime` 对象上使用 `toJSDate()` 可将其渲染为与此 DateTime 等效的 JavaScript Date 对象。 +:::info 提示 +在 `DateTime` 对象上使用 `toJSDate()` 可将其渲染为与此 DateTime 等效的 JavaScript Date 对象。 +::: **创建**一个新的定时任务,可通过动态调用 `SchedulerRegistry#addCronJob` 方法实现,如下所示: @@ -258,7 +276,11 @@ addCronJob(name: string, seconds: string) { 在这段代码中,我们使用 `CronJob` 对象(来自 `cron` 包)来创建定时任务。`CronJob` 构造函数接收两个参数:第一个是 cron 表达式(与 `@Cron()` [装饰器](techniques/task-scheduling#声明式-cron-任务)的格式相同),第二个是定时触发器触发时执行的回调函数。`SchedulerRegistry#addCronJob` 方法同样接收两个参数:定时任务的名称和 `CronJob` 对象本身。 -> **警告** 请记得在使用前先注入 `SchedulerRegistry`。同时需要从 `cron` 包中导入 `CronJob`。 +:::warning 警告 +请记得在使用前先注入 `SchedulerRegistry`。同时需要从 `cron` 包中导入 `CronJob`。 +::: + + **删除**指定名称的定时任务可通过 `SchedulerRegistry#deleteCronJob` 方法实现,如下所示: diff --git a/docs/techniques/validation.md b/docs/techniques/validation.md index 817b77a..d89209e 100644 --- a/docs/techniques/validation.md +++ b/docs/techniques/validation.md @@ -22,7 +22,9 @@ $ npm i --save class-validator class-transformer ``` -> info **提示** `ValidationPipe` 是从 `@nestjs/common` 包中导出的。 +:::info 提示 +`ValidationPipe` 是从 `@nestjs/common` 包中导出的。 +::: 由于该管道使用了 [`class-validator`](https://github.com/typestack/class-validator) 和 [`class-transformer`](https://github.com/typestack/class-transformer) 库,因此有许多可用选项。您可以通过传递给管道的配置对象来配置这些设置。以下是内置选项: @@ -56,7 +58,10 @@ export interface ValidationPipeOptions extends ValidatorOptions { | validationError.value | boolean | 指示是否应将验证值暴露在 ValidationError 中。 | | stopAtFirstError | boolean | 当设置为 true 时,给定属性的验证将在遇到第一个错误后停止。默认为 false。 | -> info **注意** 更多关于 `class-validator` 包的信息请参阅其[代码库](https://github.com/typestack/class-validator) 。 +:::info 注意 +更多关于 `class-validator` 包的信息请参阅其[代码库](https://github.com/typestack/class-validator) 。 +::: + #### 自动验证 @@ -80,9 +85,13 @@ create(@Body() createUserDto: CreateUserDto) { } ``` -> info **提示** 由于 TypeScript 不会存储关于**泛型或接口**的元数据,当你在 DTO 中使用它们时,`ValidationPipe` 可能无法正确验证传入数据。因此,请考虑在 DTO 中使用具体类。 +:::info 提示 +由于 TypeScript 不会存储关于**泛型或接口**的元数据,当你在 DTO 中使用它们时,`ValidationPipe` 可能无法正确验证传入数据。因此,请考虑在 DTO 中使用具体类。 +::: -> info **提示** 导入 DTO 时,不能使用仅类型导入,因为这在运行时会被擦除,即记得使用 `import { CreateUserDto }` 而不是 `import type { CreateUserDto }` 。 +:::info 提示 +导入 DTO 时,不能使用仅类型导入,因为这在运行时会被擦除,即记得使用 `import { CreateUserDto }` 而不是 `import type { CreateUserDto }` 。 +::: 现在我们可以在 `CreateUserDto` 中添加一些验证规则。我们使用 `class-validator` 包提供的装饰器来实现这一点,具体描述见[此处](https://github.com/typestack/class-validator#validation-decorators) 。通过这种方式,任何使用 `CreateUserDto` 的路由都会自动执行这些验证规则。 @@ -162,7 +171,7 @@ app.useGlobalPipes( 通过网络传输的有效载荷是纯 JavaScript 对象。`ValidationPipe` 可以自动将这些有效载荷转换为根据其 DTO 类定义类型的对象。要启用自动转换功能,需将 `transform` 设置为 `true`。这可以在方法级别进行配置: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Post() @UsePipes(new ValidationPipe({ transform: true })) async create(@Body() createCatDto: CreateCatDto) { @@ -210,13 +219,19 @@ findOne( } ``` -> info **提示** `ParseIntPipe` 和 `ParseBoolPipe` 是从 `@nestjs/common` 包导出的。 +:::info 提示 +`ParseIntPipe` 和 `ParseBoolPipe` 是从 `@nestjs/common` 包导出的。 +::: #### 映射类型 在构建 **CRUD**(创建/读取/更新/删除)等功能时,基于基础实体类型创建变体通常很有用。Nest 提供了几个实用函数来执行类型转换,使这项任务更加便捷。 -> **警告** 如果您的应用使用了 `@nestjs/swagger` 包,请参阅[本章节](/openapi/mapped-types)了解有关 Mapped Types 的更多信息。同样地,如果使用 `@nestjs/graphql` 包,请查看[本章节](/graphql/mapped-types) 。这两个包都重度依赖类型系统,因此需要采用不同的导入方式。如果您错误地使用了 `@nestjs/mapped-types`(而非根据应用类型选择正确的 `@nestjs/swagger` 或 `@nestjs/graphql`),可能会遇到各种未记录的副作用。 +:::warning 警告 +如果您的应用使用了 `@nestjs/swagger` 包,请参阅[本章节](/openapi/mapped-types)了解有关 Mapped Types 的更多信息。同样地,如果使用 `@nestjs/graphql` 包,请查看[本章节](/graphql/mapped-types) 。这两个包都重度依赖类型系统,因此需要采用不同的导入方式。如果您错误地使用了 `@nestjs/mapped-types`(而非根据应用类型选择正确的 `@nestjs/swagger` 或 `@nestjs/graphql`),可能会遇到各种未记录的副作用。 +::: + + 构建输入验证类型(也称为 DTO)时,通常需要在同一类型上创建 **create**(创建)和 **update**(更新)变体。例如,**create** 变体可能需要所有字段,而 **update** 变体则可能将所有字段设为可选。 @@ -238,7 +253,10 @@ export class CreateCatDto { export class UpdateCatDto extends PartialType(CreateCatDto) {} ``` -> info **注意** `PartialType()` 函数是从 `@nestjs/mapped-types` 包导入的。 +:::info 注意 +`PartialType()` 函数是从 `@nestjs/mapped-types` 包导入的。 +::: + `PickType()` 函数通过从输入类型中选择一组属性来构造新类型(类)。例如,假设我们从以下类型开始: @@ -256,7 +274,10 @@ export class CreateCatDto { export class UpdateCatAgeDto extends PickType(CreateCatDto, ['age'] as const) {} ``` -> info **注意** `PickType()` 函数是从 `@nestjs/mapped-types` 包导入的。 +:::info 注意 +`PickType()` 函数是从 `@nestjs/mapped-types` 包导入的。 +::: + `OmitType()` 函数通过从输入类型中选取所有属性,然后移除特定键集合来构造一个类型。例如,假设我们从以下类型开始: @@ -274,7 +295,10 @@ export class CreateCatDto { export class UpdateCatDto extends OmitType(CreateCatDto, ['name'] as const) {} ``` -> info **注意** `OmitType()` 函数是从 `@nestjs/mapped-types` 包导入的。 +:::info 注意 +`OmitType()` 函数是从 `@nestjs/mapped-types` 包导入的。 +::: + `IntersectionType()` 函数将两种类型合并为一个新类型(类)。例如,假设我们有以下两种类型: @@ -298,7 +322,9 @@ export class UpdateCatDto extends IntersectionType( ) {} ``` -> info **提示** `IntersectionType()` 函数是从 `@nestjs/mapped-types` 包中导入的。 +:::info 提示 +`IntersectionType()` 函数是从 `@nestjs/mapped-types` 包中导入的。 +::: 类型映射工具函数是可组合的。例如,以下代码将生成一个类型(类),该类型包含除 `name` 之外 `CreateCatDto` 类型的所有属性,并且这些属性将被设置为可选: diff --git a/docs/techniques/versioning.md b/docs/techniques/versioning.md index 09ba8c6..238afec 100644 --- a/docs/techniques/versioning.md +++ b/docs/techniques/versioning.md @@ -1,6 +1,8 @@ # 版本控制 -> info **提示** 本章节仅适用于基于 HTTP 的应用程序。 +:::info 提示 +本章节仅适用于基于 HTTP 的应用程序。 +::: 版本控制允许你在同一应用程序中运行**不同版本**的控制器或独立路由。应用程序经常发生变化,在需要支持旧版本的同时进行破坏性变更的情况并不罕见。 @@ -29,11 +31,13 @@ URI 版本控制使用请求 URI 中传递的版本号,例如 `https://example.com/v1/route` 和 `https://example.com/v2/route`。 -> warning **注意** 使用 URI 版本控制时,版本号会自动添加到 URI 中,位于全局路径前缀(如果存在)之后,任何控制器或路由路径之前。 +:::warning 注意 +使用 URI 版本控制时,版本号会自动添加到 URI 中,位于全局路径前缀(如果存在)之后,任何控制器或路由路径之前。 +::: 要为您的应用程序启用 URI 版本控制,请执行以下操作: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.create(AppModule); // or "app.enableVersioning()" app.enableVersioning({ @@ -42,9 +46,13 @@ app.enableVersioning({ await app.listen(process.env.PORT ?? 3000); ``` -> warning **注意** URI 中的版本号默认会自动添加前缀 `v`,但您可以通过设置 `prefix` 键来自定义前缀值,或设为 `false` 来禁用该功能。 +:::warning 注意 + URI 中的版本号默认会自动添加前缀 `v`,但您可以通过设置 `prefix` 键来自定义前缀值,或设为 `false` 来禁用该功能。 +::: -> info **提示** `VersioningType` 枚举可用于 `type` 属性,它从 `@nestjs/common` 包中导入。 +:::info 提示 +`VersioningType` 枚举可用于 `type` 属性,它从 `@nestjs/common` 包中导入。 +::: #### 头部版本控制类型 @@ -54,7 +62,7 @@ await app.listen(process.env.PORT ?? 3000); 要为您的应用程序启用**头部版本控制** ,请执行以下操作: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.create(AppModule); app.enableVersioning({ type: VersioningType.HEADER, @@ -65,7 +73,9 @@ await app.listen(process.env.PORT ?? 3000); `header` 属性应为包含请求版本的头部名称。 -> info **提示** `VersioningType` 枚举可用于 `type` 属性,该枚举从 `@nestjs/common` 包导入。 +:::info 提示 +`VersioningType` 枚举可用于 `type` 属性,该枚举从 `@nestjs/common` 包导入。 +::: #### 媒体类型版本控制类型 @@ -75,7 +85,7 @@ await app.listen(process.env.PORT ?? 3000); 要为应用程序启用**媒体类型版本控制** ,请执行以下操作: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.create(AppModule); app.enableVersioning({ type: VersioningType.MEDIA_TYPE, @@ -86,7 +96,9 @@ await app.listen(process.env.PORT ?? 3000); `key` 属性应作为包含版本信息的键值对的键名和分隔符。例如 `Accept: application/json;v=2` 中,`key` 属性应设置为 `v=`。 -> info **提示** `VersioningType` 枚举可用于 `type` 属性,该枚举从 `@nestjs/common` 包导入。 +:::info 提示 +`VersioningType` 枚举可用于 `type` 属性,该枚举从 `@nestjs/common` 包导入。 +::: #### 自定义版本控制类型 @@ -100,11 +112,13 @@ await app.listen(process.env.PORT ?? 3000); 如果提取的版本是 `[3, 2, 1]`,但仅存在版本 `2` 和 `1` 的路由,则会选中匹配版本 `2` 的路由(版本 `3` 会被自动忽略)。 -> warning **注意** 由于设计限制,基于 `extractor` 返回的数组选择最高匹配版本**在 Express 适配器中无法可靠工作**。单一版本(字符串或单元素数组)在 Express 中可正常工作。Fastify 则能正确支持最高匹配版本选择和单一版本选择。 +:::warning 注意 + 由于设计限制,基于 `extractor` 返回的数组选择最高匹配版本**在 Express 适配器中无法可靠工作**。单一版本(字符串或单元素数组)在 Express 中可正常工作。Fastify 则能正确支持最高匹配版本选择和单一版本选择。 +::: 要为应用启用 **自定义版本控制** ,请创建 `extractor` 函数并按如下方式传入应用: -```typescript title="main" + ```typescript title="main.ts" // 从自定义头部提取版本列表并转换为排序数组的示例提取器。 // 此示例使用 Fastify,但 Express 请求也可以类似处理。 const extractor = (request: FastifyRequest): string | string[] => @@ -113,7 +127,6 @@ const extractor = (request: FastifyRequest): string | string[] => .filter(v => !!v) .sort() .reverse() -``` const app = await NestFactory.create(AppModule); app.enableVersioning({ @@ -127,7 +140,9 @@ await app.listen(process.env.PORT ?? 3000); 版本控制功能允许您对控制器、单个路由进行版本管理,同时也为某些资源提供了退出版本控制的选项。无论应用使用何种版本控制类型,其使用方式都保持一致。 -> warning **注意** 如果应用程序启用了版本控制,但控制器或路由未指定版本,对该控制器/路由的任何请求都将返回 `404` 响应状态。同样,如果收到的请求包含没有对应控制器或路由的版本,也将返回 `404` 响应状态。 +:::warning 注意 + 如果应用程序启用了版本控制,但控制器或路由未指定版本,对该控制器/路由的任何请求都将返回 `404` 响应状态。同样,如果收到的请求包含没有对应控制器或路由的版本,也将返回 `404` 响应状态。 +::: #### 控制器版本 @@ -135,7 +150,7 @@ await app.listen(process.env.PORT ?? 3000); 要为控制器添加版本,请执行以下操作: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Controller({ version: '1', }) @@ -153,7 +168,7 @@ export class CatsControllerV1 { 要为单个路由添加版本,请执行以下操作: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" import { Controller, Get, Version } from '@nestjs/common'; @Controller() @@ -178,7 +193,7 @@ export class CatsController { 添加多个版本的操作如下: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" @Controller({ version: ['1', '2'], }) @@ -196,11 +211,15 @@ export class CatsController { 无论请求中是否包含版本号,传入的请求都将被映射到 `VERSION_NEUTRAL` 控制器或路由。 -> **注意** 对于 URI 版本控制,`VERSION_NEUTRAL` 资源不会在 URI 中包含版本号。 +:::info 注意 +对于 URI 版本控制,`VERSION_NEUTRAL` 资源不会在 URI 中包含版本号。 +::: + + 要添加版本中立的控制器或路由,请执行以下操作: -```typescript title="cats.controller" + ```typescript title="cats.controller.ts" import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common'; @Controller({ @@ -218,7 +237,7 @@ export class CatsController { 如果您不想为每个控制器/单独路由提供版本,或者希望为所有未指定版本的控制器/路由设置默认版本,可以按如下方式配置 `defaultVersion`: -```typescript title="main" + ```typescript title="main.ts" app.enableVersioning({ // ... defaultVersion: '1' @@ -233,7 +252,7 @@ app.enableVersioning({ [中间件](../overview/middlewares)同样可以利用版本元数据来为特定路由版本配置中间件。为此,需将版本号作为 `MiddlewareConsumer.forRoutes()` 方法的参数之一: -```typescript title="app.module" + ```typescript title="app.module.ts" import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; @@ -253,4 +272,7 @@ export class AppModule implements NestModule { 通过上述代码,`LoggerMiddleware` 将仅应用于'2'版本的 `/cats` 端点。 -> info **提示** 中间件适用于本节描述的任何版本控制类型:`URI`、`Header`、`Media Type` 或 `Custom`。 +:::info 提示 + 中间件适用于本节描述的任何版本控制类型:`URI`、`Header`、`Media Type` 或 `Custom`。 +::: + diff --git a/docs/websockets/adapter.md b/docs/websockets/adapter.md index e5fd652..8394344 100644 --- a/docs/websockets/adapter.md +++ b/docs/websockets/adapter.md @@ -14,7 +14,9 @@ WebSockets 模块是平台无关的,因此你可以通过使用 `WebSocketAdap [socket.io](https://github.com/socketio/socket.io) 包被封装在 `IoAdapter` 类中。如果您想增强适配器的基础功能该怎么办?例如,您的技术要求需要具备跨多个负载均衡的 Web 服务实例广播事件的能力。为此,您可以扩展 `IoAdapter` 并重写一个负责实例化新 socket.io 服务器的方法。但首先,我们需要安装所需的包。 -> warning **警告** 要在多个负载均衡实例中使用 socket.io,您要么必须在客户端 socket.io 配置中通过设置 `transports: ['websocket']` 来禁用轮询,要么必须在负载均衡器中启用基于 cookie 的路由。仅 Redis 是不够的。更多信息请参阅[此处](https://socket.io/docs/v4/using-multiple-nodes/#enabling-sticky-session) 。 +:::warning 警告 +要在多个负载均衡实例中使用 socket.io,您要么必须在客户端 socket.io 配置中通过设置 `transports: ['websocket']` 来禁用轮询,要么必须在负载均衡器中启用基于 cookie 的路由。仅 Redis 是不够的。更多信息请参阅[此处](https://socket.io/docs/v4/using-multiple-nodes/#enabling-sticky-session) 。 +::: ```bash $ npm i --save redis socket.io @socket.io/redis-adapter @@ -62,7 +64,9 @@ app.useWebSocketAdapter(redisIoAdapter); 另一个可用的适配器是 `WsAdapter`,它充当框架与集成的极速且经过全面测试的 [ws](https://github.com/websockets/ws) 库之间的代理。该适配器完全兼容原生浏览器 WebSocket,且比 socket.io 包快得多。遗憾的是,它开箱即用的功能要少得多。不过在有些情况下,您可能并不需要这些功能。 -> info: **注意** `ws` 库不支持命名空间(由 `socket.io` 推广的通信通道)。但为了模拟这一特性,您可以在不同路径上挂载多个 `ws` 服务器(示例: `@WebSocketGateway({ path: '/users' })` )。 +:::info 注意 +`ws` 库不支持命名空间(由 `socket.io` 推广的通信通道)。但为了模拟这一特性,您可以在不同路径上挂载多个 `ws` 服务器(示例: `@WebSocketGateway({ path: '/users' })` )。 +::: 要使用 `ws`,我们首先需要安装这个必需的包: @@ -77,7 +81,11 @@ const app = await NestFactory.create(AppModule); app.useWebSocketAdapter(new WsAdapter(app)); ``` -> **提示** `WsAdapter` 是从 `@nestjs/platform-ws` 导入的。 +:::info 提示 +`WsAdapter` 是从 `@nestjs/platform-ws` 导入的。 +::: + + `wsAdapter` 设计用于处理 `{ event: string, data: any }` 格式的消息。如果需要接收和处理其他格式的消息,需配置消息解析器将其转换为所需格式。 @@ -129,7 +137,7 @@ export class NotificationsGateway {} 出于演示目的,我们将手动集成 [ws](https://github.com/websockets/ws) 库。如前所述,该库的适配器已经创建并通过 `@nestjs/platform-ws` 包的 `WsAdapter` 类公开。以下是简化后的实现可能呈现的样子: -```typescript title="ws-adapter" + ```typescript title="ws-adapter.ts" import * as WebSocket from 'ws'; import { WebSocketAdapter, INestApplicationContext } from '@nestjs/common'; import { MessageMappingProperties } from '@nestjs/websockets'; @@ -181,11 +189,14 @@ export class WsAdapter implements WebSocketAdapter { } ``` -> info **注意** 当你需要使用 [ws](https://github.com/websockets/ws) 库时,请使用内置的 `WsAdapter` 而不是自己创建。 +:::info 注意 +当你需要使用 [ws](https://github.com/websockets/ws) 库时,请使用内置的 `WsAdapter` 而不是自己创建。 +::: + 接着,我们可以通过 `useWebSocketAdapter()` 方法设置自定义适配器: -```typescript title="main" + ```typescript title="main.ts" const app = await NestFactory.create(AppModule); app.useWebSocketAdapter(new WsAdapter(app)); ``` diff --git a/docs/websockets/exception-filters.md b/docs/websockets/exception-filters.md index 4645604..16410c4 100644 --- a/docs/websockets/exception-filters.md +++ b/docs/websockets/exception-filters.md @@ -6,7 +6,9 @@ HTTP [异常过滤器](/exception-filters) 层和相应的 web sockets 层之间 throw new WsException('Invalid credentials.'); ``` -> info **提示** `WsException` 类从 `@nestjs/websockets` 包导入。 +:::info 提示 +`WsException` 类从 `@nestjs/websockets` 包导入。 +::: 使用上面的示例,Nest 将处理抛出的异常并发出具有以下结构的 `exception` 消息: @@ -36,7 +38,7 @@ onEvent(client, data: any): WsResponse { 为了将异常处理委托给基础过滤器,您需要扩展 `BaseWsExceptionFilter` 并调用继承的 `catch()` 方法。 -```typescript title="all-exceptions.filter.ts" + ```typescript title="all-exceptions.filter.ts" import { Catch, ArgumentsHost } from '@nestjs/common'; import { BaseWsExceptionFilter } from '@nestjs/websockets'; diff --git a/docs/websockets/gateways.md b/docs/websockets/gateways.md index 5f849ce..647ad16 100644 --- a/docs/websockets/gateways.md +++ b/docs/websockets/gateways.md @@ -6,7 +6,9 @@ ![](/assets/Gateways_1.png) -> info **提示** 网关可以被视为[提供者](/providers) ;这意味着它们可以通过类构造函数注入依赖项。同时,网关也可以被其他类(提供者和控制器)注入。 +:::info 提示 +网关可以被视为[提供者](/providers) ;这意味着它们可以通过类构造函数注入依赖项。同时,网关也可以被其他类(提供者和控制器)注入。 +::: #### 安装 @@ -24,7 +26,9 @@ $ npm i --save @nestjs/websockets @nestjs/platform-socket.io @WebSocketGateway(80, { namespace: 'events' }) ``` -> warning **注意** 网关在被现有模块的 providers 数组引用之前不会被实例化。 +:::warning 注意 +网关在被现有模块的 providers 数组引用之前不会被实例化。 +::: 您可以通过 `@WebSocketGateway()` 装饰器的第二个参数,向 socket 构造函数传递任何受支持的[选项](https://socket.io/docs/v4/server-options/) ,如下所示: @@ -41,7 +45,9 @@ handleEvent(@MessageBody() data: string): string { } ``` -> info **提示** `@SubscribeMessage()` 和 `@MessageBody()` 装饰器是从 `@nestjs/websockets` 包导入的。 +:::info 提示 +`@SubscribeMessage()` 和 `@MessageBody()` 装饰器是从 `@nestjs/websockets` 包导入的。 +::: 网关创建完成后,我们可以在模块中注册它。 @@ -88,7 +94,9 @@ handleEvent( } ``` -> info **提示** `@ConnectedSocket()` 装饰器是从 `@nestjs/websockets` 包导入的。 +:::info 提示 +`@ConnectedSocket()` 装饰器是从 `@nestjs/websockets` 包导入的。 +::: 但是,在这种情况下,您将无法利用拦截器。如果您不想向用户回应,可以简单地跳过 `return` 语句(或明确返回"虚假"值,例如 `undefined`)。 @@ -116,9 +124,13 @@ handleEvent(@MessageBody() data: unknown): WsResponse { } ``` -> info **提示** `WsResponse` 接口是从 `@nestjs/websockets` 包中导入的。 +:::info 提示 +`WsResponse` 接口是从 `@nestjs/websockets` 包中导入的。 +::: -> warning **注意** 如果您的 `data` 字段依赖于 `ClassSerializerInterceptor`,则应返回实现 `WsResponse` 的类实例,因为该拦截器会忽略普通的 JavaScript 对象响应。 +:::warning 注意 + 如果您的 `data` 字段依赖于 `ClassSerializerInterceptor`,则应返回实现 `WsResponse` 的类实例,因为该拦截器会忽略普通的 JavaScript 对象响应。 +::: 客户端需要添加另一个事件监听器才能接收传入的响应。 @@ -175,7 +187,9 @@ onEvent(@MessageBody() data: unknown): Observable> { -> info **提示** 每个生命周期接口都从 `@nestjs/websockets` 包中导出。 +:::info 提示 +每个生命周期接口都从 `@nestjs/websockets` 包中导出。 +::: #### 服务器与命名空间 @@ -193,7 +207,9 @@ server: Server; namespace: Namespace; ``` -> warning **注意** `@WebSocketServer()` 装饰器需要从 `@nestjs/websockets` 包中导入。 +:::warning 注意 + `@WebSocketServer()` 装饰器需要从 `@nestjs/websockets` 包中导入。 +::: 一旦服务器实例准备就绪,Nest 会自动将其分配给该属性。 @@ -203,7 +219,7 @@ namespace: Namespace; import { Module } from '@nestjs/common'; import { EventsGateway } from './events.gateway'; -```typescript title="events.module" + ```typescript title="events.module.ts" @Module({ providers: [EventsGateway] }) @@ -212,7 +228,7 @@ export class EventsModule {} 你也可以向装饰器传入属性键,以便从传入消息体中提取特定属性: -```typescript title="events.gateway" + ```typescript title="events.gateway.ts" @SubscribeMessage('events') handleEvent(@MessageBody('id') id: number): number { // id === messageBody.id @@ -222,7 +238,7 @@ handleEvent(@MessageBody('id') id: number): number { 如果您不想使用装饰器,以下代码在功能上是等效的: -```typescript title="events.gateway" + ```typescript title="events.gateway.ts" @SubscribeMessage('events') handleEvent(client: Socket, data: string): string { return data; @@ -233,7 +249,7 @@ handleEvent(client: Socket, data: string): string { 当接收到 `events` 消息时,处理程序会发送一个包含网络传输数据的确认响应。此外,还可以使用库特定的方式发送消息,例如利用 `client.emit()` 方法。要访问已连接的 socket 实例,请使用 `@ConnectedSocket()` 装饰器。 -```typescript title="events.gateway" + ```typescript title="events.gateway.ts" @SubscribeMessage('events') handleEvent( @MessageBody() data: string, @@ -243,7 +259,9 @@ handleEvent( } ``` -> info **提示**`@ConnectedSocket()` 装饰器是从 `@nestjs/websockets` 包中导入的。 +:::info 提示 +`@ConnectedSocket()` 装饰器是从 `@nestjs/websockets` 包中导入的。 +::: 然而,在这种情况下,您将无法利用拦截器。若不想对用户作出响应,可直接跳过 `return` 语句(或显式返回"falsy"值,例如 `undefined`)。 @@ -263,7 +281,7 @@ socket.emit('events', { name: 'Nest' }, (data) => console.log(data)); 确认通知仅会发送一次。此外,原生 WebSockets 实现并不支持此功能。为解决这一限制,您可以返回一个包含两个属性的对象:`event` 表示触发事件的名称,`data` 则是需要转发给客户端的数据。 -```typescript title="events.gateway" + ```typescript title="events.gateway.ts" @SubscribeMessage('events') handleEvent(@MessageBody() data: unknown): WsResponse { const event = 'events'; @@ -271,9 +289,13 @@ handleEvent(@MessageBody() data: unknown): WsResponse { } ``` -> info **提示** `WsResponse` 接口是从 `@nestjs/websockets` 包中导入的。 +:::info 提示 +`WsResponse` 接口是从 `@nestjs/websockets` 包中导入的。 +::: -> warning **注意** 如果您的 `data` 字段依赖于 `ClassSerializerInterceptor`,则应返回实现 `WsResponse` 的类实例,因为该拦截器会忽略普通的 JavaScript 对象响应。 +:::warning 注意 + 如果您的 `data` 字段依赖于 `ClassSerializerInterceptor`,则应返回实现 `WsResponse` 的类实例,因为该拦截器会忽略普通的 JavaScript 对象响应。 +::: 客户端需要添加另一个事件监听器才能接收传入的响应。 @@ -285,7 +307,7 @@ socket.on('events', (data) => console.log(data)); 消息处理器能够以**同步**或**异步**方式响应。因此,支持 `async` 方法。消息处理器还能返回一个 `Observable`,在这种情况下,结果值将持续发射直到流完成。 -```typescript title="events.gateway" + ```typescript title="events.gateway.ts" @SubscribeMessage('events') onEvent(@MessageBody() data: unknown): Observable> { const event = 'events'; @@ -309,7 +331,11 @@ onEvent(@MessageBody() data: unknown): Observable> { | `OnGatewayConnection` | 强制实现 `handleConnection()` 方法。接收特定库的客户端套接字实例作为参数 | | `OnGatewayDisconnect` | 强制实现 `handleDisconnect()` 方法。接收特定库的客户端套接字实例作为参数 | -> **提示** 每个生命周期接口都从 `@nestjs/websockets` 包中导出。 +:::info 提示 +每个生命周期接口都从 `@nestjs/websockets` 包中导出。 +::: + + 以下是实现这些生命周期钩子的完整示例: @@ -363,7 +389,9 @@ server: Server; namespace: Namespace; ``` -> warning **注意** `@WebSocketServer()` 装饰器需要从 `@nestjs/websockets` 包中导入。 +:::warning 注意 + `@WebSocketServer()` 装饰器需要从 `@nestjs/websockets` 包中导入。 +::: 一旦服务器实例准备就绪,Nest 会自动将其分配给该属性。 diff --git a/docs/websockets/guards.md b/docs/websockets/guards.md index 126746f..6c4893c 100644 --- a/docs/websockets/guards.md +++ b/docs/websockets/guards.md @@ -2,7 +2,9 @@ WebSocket 守卫与[常规 HTTP 应用守卫](/guards)之间没有根本区别。唯一的差异是应该使用 `WsException` 而不是抛出 `HttpException`。 -> info **提示** `WsException` 类从 `@nestjs/websockets` 包中导出。 +:::info 提示 +`WsException` 类从 `@nestjs/websockets` 包中导出。 +::: #### 绑定守卫 diff --git a/docs/websockets/pipes.md b/docs/websockets/pipes.md index 59a32c7..eaaf96c 100644 --- a/docs/websockets/pipes.md +++ b/docs/websockets/pipes.md @@ -2,13 +2,15 @@ [常规管道](/pipes)与 WebSocket 管道之间没有根本区别。唯一的区别是,您应该使用 `WsException` 而不是抛出 `HttpException`。此外,所有管道将仅应用于 `data` 参数(因为验证或转换 `client` 实例没有意义)。 -> info **提示** `WsException` 类从 `@nestjs/websockets` 包导出。 +:::info 提示 +`WsException` 类从 `@nestjs/websockets` 包导出。 +::: #### 绑定管道 以下示例使用手动实例化的方法作用域管道。就像基于 HTTP 的应用程序一样,您也可以使用网关作用域管道(即,在网关类前加上 `@UsePipes()` 装饰器)。 -```typescript title="app.gateway.ts" + ```typescript title="app.gateway.ts" @UsePipes(new ValidationPipe({ exceptionFactory: (errors) => new WsException(errors) })) @SubscribeMessage('events') handleEvent(client: Client, data: unknown): WsResponse { diff --git a/rspress.config.ts b/rspress.config.ts index 201feb9..63850cd 100644 --- a/rspress.config.ts +++ b/rspress.config.ts @@ -97,17 +97,17 @@ export default defineConfig({ collapsed: true, items: [ { text: '依赖注入', link: '/fundamentals/dependency-injection' }, + { text: '异步提供者', link: '/fundamentals/async-components' }, { text: '动态模块', link: '/fundamentals/dynamic-modules' }, - { text: '异步组件', link: '/fundamentals/async-components' }, + { text: '注入作用域', link: '/fundamentals/provider-scopes' }, { text: '循环依赖', link: '/fundamentals/circular-dependency' }, { text: '模块引用', link: '/fundamentals/module-reference' }, { text: '懒加载模块', link: '/fundamentals/lazy-loading-modules' }, { text: '执行上下文', link: '/fundamentals/execution-context' }, { text: '生命周期事件', link: '/fundamentals/lifecycle-events' }, + { text: '发现服务', link: '/fundamentals/discovery-service' }, { text: '平台无关', link: '/fundamentals/platform-agnosticism' }, { text: '单元测试', link: '/fundamentals/unit-testing' }, - { text: 'Provider 作用域', link: '/fundamentals/provider-scopes' }, - { text: '发现服务', link: '/fundamentals/discovery-service' }, ], }, { @@ -121,20 +121,34 @@ export default defineConfig({ { text: '验证', link: '/techniques/validation' }, { text: '缓存', link: '/techniques/caching' }, { text: '序列化', link: '/techniques/serialization' }, + { text: '版本控制', link: '/techniques/versioning' }, { text: '任务调度', link: '/techniques/task-scheduling' }, { text: '队列', link: '/techniques/queues' }, { text: '日志', link: '/techniques/logger' }, { text: 'Cookies', link: '/techniques/cookies' }, + { text: '事件', link: '/techniques/events' }, { text: '压缩', link: '/techniques/compression' }, { text: '文件上传', link: '/techniques/file-upload' }, { text: '文件流', link: '/techniques/streaming-files' }, { text: 'HTTP 模块', link: '/techniques/http-module' }, + { text: 'Session', link: '/techniques/sessions' }, { text: 'MVC', link: '/techniques/mvc' }, - { text: '性能', link: '/techniques/performance' }, + { text: '性能(Fastify)', link: '/techniques/performance' }, { text: 'SSE', link: '/techniques/server-sent-events' }, - { text: '事件', link: '/techniques/events' }, - { text: 'Session', link: '/techniques/sessions' }, - { text: '版本控制', link: '/techniques/versioning' }, + ], + }, + { + text: '安全', + collapsible: true, + collapsed: true, + items: [ + { text: '认证', link: '/security/authentication' }, + { text: '授权', link: '/security/authorization' }, + { text: '加密与哈希', link: '/security/encryption-hashing' }, + { text: 'Helmet', link: '/security/helmet' }, + { text: 'CORS', link: '/security/cors' }, + { text: 'CSRF', link: '/security/csrf' }, + { text: '速率限制', link: '/security/rate-limiting' }, ], }, { @@ -144,50 +158,54 @@ export default defineConfig({ items: [ { text: '快速开始', link: '/graphql/quick-start' }, { text: '解析器', link: '/graphql/resolvers-map' }, - { text: '类型映射', link: '/graphql/mapped-types' }, - { text: '插件', link: '/graphql/plugins' }, + { text: '变更', link: '/graphql/mutations' }, + { text: '订阅', link: '/graphql/subscriptions' }, + { text: '标量', link: '/graphql/scalars' }, { text: '指令', link: '/graphql/directives' }, - { text: '中间件', link: '/graphql/field-middleware' }, - { text: '联合与枚举', link: '/graphql/unions-and-enums' }, { text: '接口', link: '/graphql/interfaces' }, - { text: '标量', link: '/graphql/scalars' }, - { text: '订阅', link: '/graphql/subscriptions' }, + { text: '联合与枚举', link: '/graphql/unions-and-enums' }, + { text: '字段中间件', link: '/graphql/field-middleware' }, + { text: '类型映射', link: '/graphql/mapped-types' }, + { text: '插件', link: '/graphql/plugins' }, { text: '复杂度', link: '/graphql/complexity' }, - { text: '联邦', link: '/graphql/federation' }, { text: '扩展', link: '/graphql/extensions' }, { text: 'CLI 插件', link: '/graphql/cli-plugin' }, + { text: '生成SDL', link: '/graphql/schema-generator' }, + { text: '共享模型', link: '/graphql/sharing-models' }, + { text: '其他功能', link: '/graphql/guards-interceptors' }, + { text: '联邦', link: '/graphql/federation' }, ], }, { - text: '微服务', + text: 'WebSocket', collapsible: true, collapsed: true, items: [ - { text: '基础', link: '/microservices/basics' }, - { text: '自定义传输', link: '/microservices/custom-transport' }, - { text: '异常过滤器', link: '/microservices/exception-filters' }, - { text: 'gRPC', link: '/microservices/grpc' }, - { text: '守卫', link: '/microservices/guards' }, - { text: '拦截器', link: '/microservices/interceptors' }, - { text: 'Kafka', link: '/microservices/kafka' }, - { text: 'MQTT', link: '/microservices/mqtt' }, - { text: 'NATS', link: '/microservices/nats' }, - { text: '管道', link: '/microservices/pipes' }, - { text: 'RabbitMQ', link: '/microservices/rabbitmq' }, - { text: 'Redis', link: '/microservices/redis' }, + { text: '网关', link: '/websockets/gateways' }, + { text: '异常过滤器', link: '/websockets/exception-filters' }, + { text: '管道', link: '/websockets/pipes' }, + { text: '守卫', link: '/websockets/guards' }, + { text: '拦截器', link: '/websockets/interceptors' }, + { text: '适配器', link: '/websockets/adapter' }, ], }, { - text: 'WebSocket', + text: '微服务', collapsible: true, collapsed: true, items: [ - { text: '网关', link: '/websockets/gateways' }, - { text: '守卫', link: '/websockets/guards' }, - { text: '管道', link: '/websockets/pipes' }, - { text: '拦截器', link: '/websockets/interceptors' }, - { text: '异常过滤器', link: '/websockets/exception-filters' }, - { text: '适配器', link: '/websockets/adapter' }, + { text: '基础', link: '/microservices/basics' }, + { text: 'Redis', link: '/microservices/redis' }, + { text: 'MQTT', link: '/microservices/mqtt' }, + { text: 'NATS', link: '/microservices/nats' }, + { text: 'RabbitMQ', link: '/microservices/rabbitmq' }, + { text: 'Kafka', link: '/microservices/kafka' }, + { text: 'gRPC', link: '/microservices/grpc' }, + { text: '自定义传输', link: '/microservices/custom-transport' }, + { text: '异常过滤器', link: '/microservices/exception-filters' }, + { text: '管道', link: '/microservices/pipes' }, + { text: '守卫', link: '/microservices/guards' }, + { text: '拦截器', link: '/microservices/interceptors' }, ], }, { @@ -198,6 +216,18 @@ export default defineConfig({ text: '独立应用程序', link: '/standalone-applications' }, + { + text: 'CLI', + collapsible: true, + collapsed: true, + items: [ + { text: '概述', link: '/cli/overview' }, + { text: '工作区', link: '/cli/workspaces' }, + { text: '库', link: '/cli/libraries' }, + { text: '用法', link: '/cli/usages' }, + { text: '脚本', link: '/cli/scripts' }, + ], + }, { text: 'OpenAPI', collapsible: true, @@ -214,42 +244,31 @@ export default defineConfig({ ], }, { - text: 'CLI', + text: '实用示例', collapsible: true, collapsed: true, items: [ - { text: '概述', link: '/cli/overview' }, - { text: '库', link: '/cli/libraries' }, - { text: '脚本', link: '/cli/scripts' }, - { text: '用法', link: '/cli/usages' }, - { text: '工作区', link: '/cli/workspaces' }, - ], - }, - { - text: '实用食谱', - collapsible: true, - collapsed: true, - items: [ - { text: 'AsyncLocalStorage', link: '/recipes/async-local-storage' }, - { text: 'CQRS', link: '/recipes/cqrs' }, - { text: 'CRUD 生成器', link: '/recipes/crud-generator' }, - { text: '文档', link: '/recipes/documentation' }, + { text: 'REPL', link: '/recipes/repl' }, + { text: 'CRUD生成器', link: '/recipes/crud-generator' }, + { text: 'SWC', link: '/recipes/swc' }, + { text: 'Passport(认证)', link: '/recipes/passport' }, { text: '热重载', link: '/recipes/hot-reload' }, { text: 'MikroORM', link: '/recipes/mikroorm' }, - { text: 'MongoDB', link: '/recipes/mongodb' }, - { text: 'Necord', link: '/recipes/necord' }, - { text: 'Commander', link: '/recipes/nest-commander' }, - { text: 'Passport', link: '/recipes/passport' }, - { text: 'Prisma', link: '/recipes/prisma' }, - { text: 'REPL', link: '/recipes/repl' }, + { text: 'TypeORM', link: '/recipes/sql-typeorm' }, + { text: 'Mongoose', link: '/recipes/mongodb' }, + { text: 'Sequelize', link: '/recipes/sql-sequelize' }, { text: '路由模块', link: '/recipes/router-module' }, + { text: 'Swagger', link: '/openapi/introduction' }, + { text: '健康检查', link: '/recipes/terminus' }, + { text: 'CQRS', link: '/recipes/cqrs' }, + { text: 'Compodoc', link: '/recipes/documentation' }, + { text: 'Prisma', link: '/recipes/prisma' }, { text: 'Sentry', link: '/recipes/sentry' }, { text: '静态资源', link: '/recipes/serve-static' }, - { text: 'Sequelize', link: '/recipes/sql-sequelize' }, - { text: 'TypeORM', link: '/recipes/sql-typeorm' }, - { text: 'Suites (Automock)', link: '/recipes/suites' }, - { text: 'SWC', link: '/recipes/swc' }, - { text: '健康检查', link: '/recipes/terminus' }, + { text: 'Commander', link: '/recipes/nest-commander' }, + { text: '异步本地存储', link: '/recipes/async-local-storage' }, + { text: 'Necord', link: '/recipes/necord' }, + { text: '套件(原Automock)', link: '/recipes/suites' }, ], }, { @@ -257,29 +276,16 @@ export default defineConfig({ collapsible: true, collapsed: true, items: [ - { text: '错误', link: '/faq/errors' }, - { text: '全局前缀', link: '/faq/global-prefix' }, + { text: 'Serverless', link: '/faq/serverless' }, { text: 'HTTP 适配器', link: '/faq/http-adapter' }, - { text: '混合应用', link: '/faq/hybrid-application' }, { text: '长连接', link: '/faq/keep-alive-connections' }, - { text: '多服务器', link: '/faq/multiple-servers' }, + { text: '全局前缀', link: '/faq/global-prefix' }, { text: '原始请求体', link: '/faq/raw-body' }, + { text: '混合应用', link: '/faq/hybrid-application' }, + + { text: 'HTTPS & 多服务器', link: '/faq/multiple-servers' }, { text: '请求生命周期', link: '/faq/request-lifecycle' }, - { text: 'Serverless', link: '/faq/serverless' }, - ], - }, - { - text: '安全', - collapsible: true, - collapsed: true, - items: [ - { text: '认证', link: '/security/authentication' }, - { text: '授权', link: '/security/authorization' }, - { text: 'CORS', link: '/security/cors' }, - { text: 'CSRF', link: '/security/csrf' }, - { text: '加密与哈希', link: '/security/encryption-hashing' }, - { text: 'Helmet', link: '/security/helmet' }, - { text: '速率限制', link: '/security/rate-limiting' }, + { text: '错误', link: '/faq/errors' }, ], }, { @@ -295,6 +301,10 @@ export default defineConfig({ text: '迁移指南', link: '/migration-guide' }, + { + text: 'API参考(官方)', + link: 'https://api-references-nestjs.netlify.app/api' + }, { text: '生态与案例', collapsible: true, @@ -326,4 +336,5 @@ export default defineConfig({ mediumZoom: { selector: '.rspress-doc img', }, + globalStyles: path.join(__dirname, 'styles/styles.css') }); diff --git a/styles/styles.css b/styles/styles.css new file mode 100644 index 0000000..67cd70e --- /dev/null +++ b/styles/styles.css @@ -0,0 +1,115 @@ +/* office docs style vars */ + +.light-mode,html:not([mode=dark]):not([mode=light]),html[mode=light] { + --primary: #ea2845; + --primary-accent: #ea2868; + --primary-1dp: #d71e38; + --primary-2dp: #da2640; + --primary-3dp: #db2840; + --primary-4dp: #e40020; + --primary-5dp: #ff0023; + --primary-gradient: linear-gradient(90deg, var(--primary) 0%, var(--primary-accent) 100%); + --color: #404040; + --color-1dp: #151515; + --background: #fdfdfd; + --background-1dp: #f7f7f7; + --background-2dp: #f0f2f3; + --background-3dp: #e8e8e8; + --background-4dp: #efefef; + --background-5dp: #cccccc; + --header-background: #151515; + --menu-color: #151515; + --menu-background: #f5f5f5; + --inline-code-color: #2876d2; + --code-background: #1d1d1d; + --warning: #ffb36f; + --warning-color: #ed8529; + --warning-background: #fff5ec; + --info: #0894e2; + --info-color: #0894e2; + --info-background: rgba(8, 148, 226, .038); + --error: #ed2945; + --error-background: #f9eff1; + --company-filter: grayscale(100%); + --company-filter-hover: grayscale(0%); + --company-logo-filter: grayscale(1); + --company-logo-opacity: .5; + --images-filter: unset; + --images-box-shadow: 0 0 50px 0 rgba(0, 0, 0, .08) +} + +.dark-mode,html[mode=dark] { + --primary: #f23551; + --primary-accent: #e23770; + --primary-1dp: #f45f75; + --primary-2dp: #f4526a; + --primary-3dp: #f1455f; + --primary-4dp: #f23c57; + --primary-5dp: #f23551; + --primary-gradient: linear-gradient(90deg, var(--primary) 0%, var(--primary-accent) 100%); + --color: #dfdfe3; + --color-1dp: #d0d0d4; + --background: #1f1f22; + --background-1dp: #232327; + --background-2dp: #252528; + --background-3dp: #29292d; + --background-4dp: #3d3d41; + --background-5dp: #39393e; + --header-background: #1b1b1d; + --menu-color: #dfdfe3; + --menu-background: #242427; + --inline-code-color: #8ec2ff; + --code-background: #18181a; + --warning: #ffb36f; + --warning-color: #ed8529; + --warning-background: #504337; + --info: #0894e2; + --info-color: #0894e2; + --info-background: rgba(8, 148, 226, .038); + --error: #ff677c; + --error-background: #3a2f30; + --company-filter: contrast(.5); + --company-filter-hover: opacity(1); + --company-logo-filter: contrast(.5) grayscale(100%); + --company-logo-opacity: unset; + --images-filter: invert(1) contrast(.85); + --images-box-shadow: 0 0 0px 0 rgba(0, 0, 0, .08) +} + +/* file tree style */ +.file-tree { + background: var(--background-1dp); + border: 4px solid color-mix(in srgb,var(--background-3dp),rgba(0,0,0,0) 50%); + margin: 40px 0; + padding: 16px 32px +} + +.file-tree .item { + display: block; + line-height: 32px; + font-size: 15px; + color: var(--color-1dp) +} + +.file-tree .children { + padding-left: 30px; + position: relative; + overflow: hidden +} + +.file-tree .children .item { + position: relative +} + +.file-tree .children .item:before { + content: ""; + left: -18px; + bottom: 16px; + width: 16px; + height: 9999px; + position: absolute; + border-width: 0 0 1px 1px; + border-style: solid; + border-color: #dbdbdb; + border-radius: 0 0 0 3px +}