• Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • Nest 通关秘籍

    • 1.开篇词
    • 2.给你 5 个学习 Nest 的理由,你会心动么
    • 3.Nest 基础概念扫盲
    • 4.快速掌握 Nest CLI
    • 5.五种HTTP数据传输方式
    • 6.IoC 解决了什么痛点问题?
    • 7.如何调试 Nest 项目
    • 8.使用多种 Provider,灵活注入对象
    • 9.全局模块和生命周期
    • 10.AOP 架构有什么好处?
    • 11.一网打尽 Nest 全部装饰器
    • 12.Nest 如何自定义装饰器
    • 13.Metadata 和 Reflector
    • 14.ExecutionContext:切换不同上下文
    • 15.Module 和 Provider 的循环依赖怎么处理?
    • 16.如何创建动态模块
    • 17.Nest 和 Express 的关系,如何切到 fastify
    • 18.Nest 的 Middleware
    • 19.RxJS 和 Interceptor
    • 20.内置 Pipe 和自定义 Pipe
    • 21.如何使用 ValidationPipe 验证 post 请求参数
    • 22.如何自定义 Exception Filter
    • 23.图解串一串 Nest 核心概念
    • 24.接口如何实现多版本共存
    • 25.Express 如何使用 multer 实现文件上传
    • 26.Nest 如何使用 multer 实现文件上传
    • 27.图书管理系统:需求分析和原型图
    • 28.图书管理系统:用户模块后端开发
    • 29.图书管理系统:图书模块后端开发
    • 30.图书管理系统:用户模块前端开发
    • 31.图书管理系统:图书模块前端开发--图书搜索
    • 32.图书管理系统:图书模块前端开发--图书增删改
    • 33.图书管理系统:项目总结
    • 34.大文件分片上传
    • 35.最完美的 OSS 上传方案
    • 36.Nest 里如何打印日志?
    • 37.为什么 Node 里要用 Winston 打印日志?
    • 38.Nest 集成日志框架 Winston
    • 39.通过 Desktop 学 Docker 也太简单了
    • 40.你的第一个 Dockerfile
    • 41.Nest 项目如何编写 Dockerfile
    • 42.提升 Dockerfile 水平的 5 个技巧
    • 43.Docker 是怎么实现的?
    • 44.为什么 Node 应用要用 PM2 来跑?
    • 45.快速入门 MySQL
    • 46.SQL 查询语句的所有语法和函数
    • 47.一对一、join 查询、级联方式
    • 48.一对多、多对多关系的表设计
    • 49.子查询和 EXISTS
    • 50.SQL 综合练习
    • 51.MySQL 的事务和隔离级别
    • 52.MySQL 的视图、存储过程和函数
    • 53.使用 Node 操作 MySQL 的两种方式
    • 54.快速掌握 TypeORM
    • 55.TypeORM 一对一的映射和关联 CRUD
    • 56.TypeORM 一对多的映射和关联 CRUD
    • 57.TypeORM 多对多的映射和关联 CRUD
    • 58.在 Nest 里集成 TypeORM
    • 59.TypeORM 如何保存任意层级的关系?
    • 60.为什么生产环境要用 TypeORM 的 migration 迁移功能?
    • 61.Nest 项目里如何使用 TypeORM 迁移
    • 62.如何动态读取不同环境的配置?
    • 63.快速入门 Redis
    • 64.在 Nest 里操作 Redis
    • 65.为什么不用 cache-manager 操作 Redis?
    • 66.两种登录状态保存方式:JWT、Session
    • 67.Nest 里实现 Session 和 JWT
    • 68.MySQL + TypeORM + JWT 实现登录注册
    • 69.基于 ACL 实现权限控制
    • 70.基于 RBAC 实现权限控制
    • 71.基于 access_token 和 refresh_token 实现登录状态无感刷新
    • 72.单 token 无限续期,实现登录状态无感刷新
    • 73.使用 passport 做身份认证
    • 74.passport 实现 GitHub 三方账号登录
    • 75.passport 实现 Google 三方账号登录
    • 76.为什么要使用 Docker Compose ?
    • 77.Docker 容器通信的最简单方式:桥接网络
    • 78.Docker 支持重启策略,是否还需要 PM2
    • 79.快速掌握 Nginx 的 2 大核心用法
    • 80.基于 Nginx 实现灰度系统
    • 81.基于 Redis 实现分布式 session
    • 82.Redis + 高德地图,实现附近的充电宝
    • 83.用 Swagger 自动生成 api 文档
    • 84.如何灵活创建 DTO
    • 85.class-validator 的内置装饰器,如何自定义装饰器
    • 86.序列化 Entity,你不需要 VO 对象
    • 87.手写序列化 Entity 的拦截器
    • 88.使用 compodoc 生成文档
    • 89.Node 如何发邮件?
    • 90.实现基于邮箱验证码的登录
    • 91.定时任务 + Redis 实现阅读量计数
    • 92.Nest 的 3 种定时任务
    • 93.Nest 里如何实现事件通信?
    • 94.HttpModule + pinyin 实现天气预报查询服务
    • 95.如何记录请求日志
    • 96.短链服务?自己写一个
    • 97.Nest 实现 Server Sent Event 数据推送
    • 98.用 minio 自己搭一个 OSS 服务
    • 99.前端如何直传文件到 Minio
    • 100.基于 sharp 实现 gif 压缩工具
    • 101.大文件如何实现流式下载?
    • 102.Puppeteer 实现爬虫,爬取 BOSS 直聘全部前端岗位
    • 103.实现扫二维码登录
    • 104.Nest 的 REPL 模式
    • 105.实现 Excel 导入导出
    • 106.如何用代码动态生成 PPT
    • 107.如何拿到服务器 CPU、内存、磁盘状态
    • 108.Nest 如何实现国际化?
    • 109.会议室预订系统:需求分析和原型图
    • 110.会议室预订系统:技术方案和数据库设计
    • 111.会议室预订系统:用户管理模块-用户注册
    • 112.会议室预订系统:用户管理模块-配置抽离、登录认证鉴权
    • 113.会议室预订系统:用户管理模块-interceptor、修改信息接口
    • 114.会议室预订系统:用户管理模块-用户列表和分页查询
    • 115.会议室预订系统:用户管理模块-swagger 接口文档
    • 116.会议室预订系统:用户管理模块-用户端登录注册页面
    • 117.会议室预订系统:用户管理模块-用户端信息修改页面
    • 118.会议室预订系统:用户管理模块-头像上传
    • 119.会议室预订系统:用户管理模块-管理端用户列表页面
    • 120.会议室预订系统:用户管理模块-管理端信息修改页面
    • 121.会议室预订系统:会议室管理模块-后端开发
    • 122.会议室预订系统:会议室管理模块-管理端前端开发
    • 123.会议室预订系统:会议室管理模块-用户端前端开发
    • 124.会议室预订系统:预定管理模块-后端开发
    • 125.会议室预订系统:预定管理模块-管理端前端开发
    • 126.会议室预订系统:预定管理模块-用户端前端开发
    • 127.会议室预订系统:统计管理模块-后端开发
    • 128.会议室预订系统:统计管理模块-前端开发
    • 129.会议室预订系统:后端项目部署到阿里云
    • 130.会议室预订系统:前端项目部署到阿里云
    • 131.会议室预定系统:用 migration 初始化表和数据
    • 132.会议室预定系统:文件上传 OSS
    • 133.会议室预定系统:Google 账号登录后端开发
    • 134.会议室预定系统:Google 账号登录前端开发
    • 135.会议室预定系统:后端代码优化
    • 136.会议室预定系统:集成日志框架 winston
    • 137.会议室预定系统:前端代码优化
    • 138.会议室预定系统:全部功能测试
    • 139.会议室预定系统:项目总结
    • 140.Nest 如何创建微服务?
    • 141.Nest 的 Monorepo 和 Library
    • 142.用 Etcd 实现微服务配置中心和注册中心
    • 143.Nest 集成 Etcd 做注册中心、配置中心
    • 144.用 Nacos 实现微服务配置中心和注册中心
    • 145.基于 gRPC 实现跨语言的微服务通信
    • 146.快速入门 ORM 框架 Prisma
    • 147.Prisma 的全部命令
    • 148.Prisma 的全部 schema 语法
    • 149.Primsa Client 单表 CRUD 的全部 api
    • 150.Prisma Client 多表 CRUD 的全部 api
    • 151.在 Nest 里集成 Prisma
    • 152.为什么前端监控系统要用 RabbitMQ?
    • 153.基于 Redis 实现关注关系
    • 154.基于 Redis 实现各种排行榜(周榜、月榜、年榜)
    • 155.考试系统:需求分析
    • 156.考试系统:技术方案和数据库设计
    • 157.考试系统:微服务、Lib 拆分
    • 158.考试系统;用户注册
    • 159.考试系统:用户登录、修改密码
    • 160.考试系统:考试微服务
    • 161.考试系统:登录、注册页面
    • 162.考试系统:修改密码、试卷列表页面
    • 163.考试系统:新增试卷、回收站
    • 164.考试系统:试卷编辑器
    • 165.考试系统:试卷回显、预览、保存
    • 166.考试系统:答卷微服务
    • 167.考试系统:答题页面
    • 168.考试系统:自动判卷
    • 169.考试系统:分析微服务、排行榜页面
    • 170.考试系统:整体测试
    • 171.考试系统:项目总结
    • 172.用 Node.js 手写 WebSocket 协议
    • 173.Nest 开发 WebSocket 服务
    • 174.基于 Socket.io 的 room 实现群聊
    • 175.聊天室:需求分析和原型图
    • 176.聊天室:技术选型和数据库设计
    • 177.聊天室:用户注册
    • 178.聊天室:用户登录
    • 179.聊天室:修改密码、修改信息
    • 180.聊天室:好友列表、发送好友申请
    • 181.聊天室:创建聊天室、加入群聊
    • 182.聊天室:登录、注册页面开发
    • 183.聊天室:修改密码、信息页面开发
    • 184.聊天室:头像上传
    • 185.聊天室:好友∕群聊列表页面
    • 186.聊天室:添加好友弹窗、通知页面
    • 187.聊天室:聊天功能后端开发
    • 188.聊天室:聊天功能前端开发
    • 189.聊天室:一对一聊天
    • 190.聊天室:创建群聊、进入群聊
    • 191.聊天室:发送表情、图片、文件
    • 192.聊天室:收藏
    • 193.聊天室:全部功能测试
    • 194.聊天室:项目总结
    • 195.MongoDB 快速入门
    • 196.使用 mongoose 操作 MongoDB 数据库
    • 197.GraphQL 快速入门
    • 198.Nest 开发 GraphQL 服务:实现 CRUD
    • 199.GraphQL + Primsa + React 实现 TodoList
    • 200.如何调试 Nest 源码?

后端框架基本都是 MVC 的架构。

MVC 是 Model View Controller 的简写。MVC 架构下,请求会先发送给 Controller,由它调度 Model 层的 Service 来完成业务逻辑,然后返回对应的 View。

在这个流程中,Nest 还提供了 AOP (Aspect Oriented Programming)的能力,也就是面向切面编程的能力。

AOP 是什么意思呢?什么是面向切面编程呢?

一个请求过来,可能会经过 Controller(控制器)、Service(服务)、Repository(数据库访问) 的逻辑:

如果想在这个调用链路里加入一些通用逻辑该怎么加呢?比如日志记录、权限控制、异常处理等。

容易想到的是直接改造 Controller 层代码,加入这段逻辑。

这样可以,但是不优雅,因为这些通用的逻辑侵入到了业务逻辑里面。能不能透明的给这些业务逻辑加上日志、权限等处理呢?

那是不是可以在调用 Controller 之前和之后加入一个执行通用逻辑的阶段呢?

比如这样:

是不是就和切了一刀一样?

这样的横向扩展点就叫做切面,这种透明的加入一些切面逻辑的编程方式就叫做 AOP (面向切面编程)。

AOP 的好处是可以把一些通用逻辑分离到切面中,保持业务逻辑的纯粹性,这样切面逻辑可以复用,还可以动态的增删。

其实 Express 的中间件的洋葱模型也是一种 AOP 的实现,因为你可以透明的在外面包一层,加入一些逻辑,内层感知不到。

而 Nest 实现 AOP 的方式更多,一共有五种,包括 Middleware、Guard、Pipe、Interceptor、ExceptionFilter。

新建个 nest 项目,我们挨个试一下:

nest new aop-test

中间件 Middleware

中间件是 Express 里的概念,Nest 的底层是 Express,所以自然也可以使用中间件,但是做了进一步的细分,分为了全局中间件和路由中间件。

全局中间件就是这样:

在 main.ts 里通过 app.use 使用:

app.use(function (req: Request, res: Response, next: NextFunction) {
    console.log("before", req.url);
    next();
    console.log("after");
});

在 AppController 里也加个打印:

把服务跑起来:

npm run start:dev

浏览器访问下:

可以看到,在调用 handler 前后,执行了中间件的逻辑。

我们再添加几个路由:

@Get('aaa')
aaa(): string {
    console.log('aaa...');
    return 'aaa';
}

@Get('bbb')
bbb(): string {
    console.log('bbb...');
    return 'bbb';
}

然后浏览器访问下:

可以看到,中间件逻辑都执行了:

也就是说,可以在多个 handler 之间复用中间件的逻辑:

这种可以给在 handler 前后动态增加一些可复用的逻辑,就是 AOP 的切面编程的思想。

除了全局中间件,Nest 还支持路由中间件。

用 nest cli 创建一个路由中间件:

nest g middleware log --no-spec --flat

--no-spec 是不生成测试文件,--flat 是平铺,不生成目录。

生成的代码是这样的:

在前后打印下日志:

import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response } from "express";

@Injectable()
export class LogMiddleware implements NestMiddleware {
    use(req: Request, res: Response, next: () => void) {
        console.log("before2", req.url);

        next();

        console.log("after2");
    }
}

然后在 AppModule 里启用:

import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { LogMiddleware } from "./log.middleware";

@Module({
    imports: [],
    controllers: [AppController],
    providers: [AppService],
})
export class AppModule implements NestModule {
    configure(consumer: MiddlewareConsumer) {
        consumer.apply(LogMiddleware).forRoutes("aaa*");
    }
}

在 configure 方法里配置 LogMiddleware 在哪些路由生效。

然后测试下:

可以看到,只有 aaa 的路由,中间件生效了。

这就是全局中间件和路由中间件的区别。

Guard

Guard 是路由守卫的意思,可以用于在调用某个 Controller 之前判断权限,返回 true 或者 false 来决定是否放行:

我们创建个 Guard:

nest g guard login --no-spec --flat

生成的 Guard 代码是这样的:

Guard 要实现 CanActivate 接口,实现 canActivate 方法,可以从 context 拿到请求的信息,然后做一些权限验证等处理之后返回 true 或者 false。

我们加个打印语句,然后返回 false:

import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { Observable } from "rxjs";

@Injectable()
export class LoginGuard implements CanActivate {
    canActivate(
        context: ExecutionContext
    ): boolean | Promise<boolean> | Observable<boolean> {
        console.log("login check");
        return false;
    }
}

之后在 AppController 里启用:

然后再访问下:

aaa 没有权限,返回了 403。

Controller 本身不需要做啥修改,却透明的加上了权限判断的逻辑,这就是 AOP 架构的好处。

而且,就像 Middleware 支持全局级别和路由级别一样,Guard 也可以全局启用:

这样每个路由都会应用这个 Guard:

还有一种全局启用的方式,是在 AppModule 里这样声明:

{
  provide: APP_GUARD,
  useClass: LoginGuard
}

把 main.ts 里的 useGlobalGuards 注释掉:

再试下:

可以看到,Guard 依然是生效的。

那为什么都是声明全局 Guard,需要有两种方式呢?

因为之前这种方式是手动 new 的 Guard 实例,不在 IoC 容器里:

而用 provider 的方式声明的 Guard 是在 IoC 容器里的,可以注入别的 provider:

我们注入下 AppService 试试:

@Inject(AppService)
private appService: AppService;

浏览器访问下:

可以看到,注入的 AppService 生效了。

所以,当需要注入别的 provider 的时候,就要用第二种全局 Guard 的声明方式。

Interceptor

Interceptor 是拦截器的意思,可以在目标 Controller 方法前后加入一些逻辑:

创建个 interceptor:

nest g interceptor time --no-spec --flat

生成的 interceptor 是这样的:

Interceptor 要实现 NestInterceptor 接口,实现 intercept 方法,调用 next.handle() 就会调用目标 Controller,可以在之前和之后加入一些处理逻辑。

Controller 之前之后的处理逻辑可能是异步的。Nest 里通过 rxjs 来组织它们,所以可以使用 rxjs 的各种 operator。

import {
    CallHandler,
    ExecutionContext,
    Injectable,
    NestInterceptor,
} from "@nestjs/common";
import { Observable, tap } from "rxjs";

@Injectable()
export class TimeInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        const startTime = Date.now();

        return next.handle().pipe(
            tap(() => {
                console.log("time: ", Date.now() - startTime);
            })
        );
    }
}

把之前那个 LoginGuard 注掉:

然后启用这个 interceptor:

跑一下:

可以看到,interceptor 生效了。

有的同学可能会觉得 Interceptor 和 Middleware 差不多,其实是有区别的,主要在于参数的不同。

interceptor 可以拿到调用的 controller 和 handler:

后面我们会在 controller 和 handler 上加一些 metadata,这种就只有 interceptor或者 guard 里可以取出来,middleware 不行。

Interceptor 支持每个路由单独启用,只作用于某个 handler:

也可以在 controller 级别启动,作用于下面的全部 handler:

也同样支持全局启用,作用于全部 controller:

两种全局启用方式的区别和 guard 的一样,就不测试了。

除了路由的权限控制、目标 Controller 之前之后的处理这些都是通用逻辑外,对参数的处理也是一个通用的逻辑,所以 Nest 也抽出了对应的切面,也就是 Pipe:

Pipe

Pipe 是管道的意思,用来对参数做一些检验和转换:

用 nest cli 创建个 pipe:

nest g pipe validate --no-spec --flat

生成的代码是这样的:

Pipe 要实现 PipeTransform 接口,实现 transform 方法,里面可以对传入的参数值 value 做参数验证,比如格式、类型是否正确,不正确就抛出异常。也可以做转换,返回转换后的值。

我们实现下:

import {
    ArgumentMetadata,
    BadRequestException,
    Injectable,
    PipeTransform,
} from "@nestjs/common";

@Injectable()
export class ValidatePipe implements PipeTransform {
    transform(value: any, metadata: ArgumentMetadata) {
        if (Number.isNaN(parseInt(value))) {
            throw new BadRequestException(`参数${metadata.data}错误`);
        }

        return typeof value === "number" ? value * 10 : parseInt(value) * 10;
    }
}

这里的 value 就是传入的参数,如果不能转成数字,就返回参数错误,否则乘 10 再传入 handler:

在 AppController 添加一个 handler,然后应用这个 pipe:

@Get('ccc')
ccc(@Query('num', ValidatePipe) num: number) {
    return num + 1;
}

访问下:

可以看到,参数错误的时候返回了 400 响应,参数正确的时候也乘 10 传入了 handler。

这就是 Pipe 的作用。

Nest 内置了一些 Pipe,从名字就能看出它们的意思:

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • DefaultValuePipe
  • ParseEnumPipe
  • ParseFloatPipe
  • ParseFilePipe

同样,Pipe 可以只对某个参数生效,或者整个 Controller 都生效:

或者全局生效:

不管是 Pipe、Guard、Interceptor 还是最终调用的 Controller,过程中都可以抛出一些异常,如何对某种异常做出某种响应呢?

这种异常到响应的映射也是一种通用逻辑,Nest 提供了 ExceptionFilter 来支持:

ExceptionFilter

ExceptionFilter 可以对抛出的异常做处理,返回对应的响应:

其实我们刚刚在 pipe 里抛的这个错误,能够返回 400 的响应,就是 Exception Filter 做的:

创建一个 filter:

nest g filter test --no-spec --flat

生成的代码是这样的:

改一下:

import {
    ArgumentsHost,
    BadRequestException,
    Catch,
    ExceptionFilter,
} from "@nestjs/common";
import { Response } from "express";

@Catch(BadRequestException)
export class TestFilter implements ExceptionFilter {
    catch(exception: BadRequestException, host: ArgumentsHost) {
        const response: Response = host.switchToHttp().getResponse();

        response.status(400).json({
            statusCode: 400,
            message: "test: " + exception.message,
        });
    }
}

实现 ExceptionFilter 接口,实现 catch 方法,就可以拦截异常了。

拦截什么异常用 @Catch 装饰器来声明,然后在 catch 方法返回对应的响应,给用户更友好的提示。

用一下:

再次访问,异常返回的响应就变了:

Nest 内置了很多 http 相关的异常,都是 HttpException 的子类:

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableException
  • InternalServerErrorException
  • NotImplementedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException

当然,也可以自己扩展:

Nest 通过这样的方式实现了异常到响应的对应关系,代码里只要抛出不同的异常,就会返回对应的响应,很方便。

同样,ExceptionFilter 也可以选择全局生效或者某个路由生效:

某个 handler:

某个 controller:

全局:

我们了解了 Nest 提供的 AOP 的机制,但它们的顺序关系是怎样的呢?

几种 AOP 机制的顺序

Middleware、Guard、Pipe、Interceptor、ExceptionFilter 都可以透明的添加某种处理逻辑到某个路由或者全部路由,这就是 AOP 的好处。

但是它们之间的顺序关系是什么呢?

调用关系这个得看源码了。

对应的源码是这样的:

很明显,进入这个路由的时候,会先调用 Guard,判断是否有权限等,如果没有权限,这里就抛异常了:

抛出的 ForbiddenException 会被 ExceptionFilter 处理,返回 403 状态码。

如果有权限,就会调用到拦截器,拦截器组织了一个链条,一个个的调用,最后会调用的 controller 的方法:

调用 controller 方法之前,会使用 pipe 对参数做处理:

会对每个参数做转换:

ExceptionFilter 的调用时机很容易想到,就是在响应之前对异常做一次处理。

而 Middleware 是 express 中的概念,Nest 只是继承了下,那个是在最外层被调用。

这就是这几种 AOP 机制的调用顺序。把这些理清楚,就知道什么逻辑放在什么切面里了。

案例代码在小册仓库。

总结

Nest 基于 express 这种 http 平台做了一层封装,应用了 MVC、IOC、AOP 等架构思想。

MVC 就是 Model、View Controller 的划分,请求先经过 Controller,然后调用 Model 层的 Service、Repository 完成业务逻辑,最后返回对应的 View。

IOC 是指 Nest 会自动扫描带有 @Controller、@Injectable 装饰器的类,创建它们的对象,并根据依赖关系自动注入它依赖的对象,免去了手动创建和组装对象的麻烦。

AOP 则是把通用逻辑抽离出来,通过切面的方式添加到某个地方,可以复用和动态增删切面逻辑。

Nest 的 Middleware、Guard、Interceptor、Pipe、ExceptionFilter 都是 AOP 思想的实现,只不过是不同位置的切面,它们都可以灵活的作用在某个路由或者全部路由,这就是 AOP 的优势。

我们通过源码来看了它们的调用顺序,Middleware 是 Express 的概念,在最外层,到了某个路由之后,会先调用 Guard,Guard 用于判断路由有没有权限访问,然后会调用 Interceptor,对 Contoller 前后扩展一些逻辑,在到达目标 Controller 之前,还会调用 Pipe 来对参数做检验和转换。所有的 HttpException 的异常都会被 ExceptionFilter 处理,返回不同的响应。

Nest 就是通过这种 AOP 的架构方式,实现了松耦合、易于维护和扩展的架构。

AOP 架构的好处,你感受到了么?

上次更新: 6/21/25, 9:42 AM
贡献者: YNight
Prev
9.全局模块和生命周期
Next
11.一网打尽 Nest 全部装饰器