Skip to content

拦截器

AOP在Nest中的应用#

1、中间件#

中间件是 Express 里的概念,Nest 的底层默认是 Express,它在请求流程中的位置大致如下:

image-20250116155649092

中间件可以在路由处理程序之前或者之后插入需要执行的任务,Nest做了进一步细分,主要分为全局中间件局部中间件

我们可以通过命令**nest g mi [中间件名称]**直接来创建中间件,比如nest g mi person就会自动帮我们在person文件夹下创建person.middleware.ts文件

person.middleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response, NextFunction } from 'express'
@Injectable()
export class PersonMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('before 中间件 ---' + req.url)
next()
console.log('after 中间件 ---' + res.statusCode)
}
}

当然,默认生成的参数reqres类型都是any,next就是一个默认函数() => void我们可以加上express对应的相关类型。

我们可以在person模块中调用中间件

person.module.ts

import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
import { PersonService } from './person.service'
import { PersonController } from './person.controller'
import { PersonMiddleware } from './person.middleware'
@Module({
controllers: [PersonController],
providers: [PersonService],
})
export class PersonModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(PersonMiddleware).forRoutes(PersonController)
}
}

当然,我们也可以像下面一样,指定特定的路由:

import {
MiddlewareConsumer,
Module,
NestModule,
RequestMethod,
} from '@nestjs/common'
import { PersonService } from './person.service'
import { PersonController } from './person.controller'
import { PersonMiddleware } from './person.middleware'
@Module({
controllers: [PersonController],
providers: [PersonService],
})
export class PersonModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(PersonMiddleware).forRoutes({
path: '/person',
method: RequestMethod.GET,
})
}
}

当我们访问/person时,后端打印如下效果:

before 中间件 ---/person
person controller
after 中间件 ---200

在Nest中,类中间件不仅可以处理HTTP请求和响应,更重要的是,它能够实现依赖注入。这意味着我们可以在中间件中注入特定的依赖项,并且调用这些依赖项内部的方法。比如:

import { Inject, Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response, NextFunction } from 'express'
import { UserService } from 'src/user/user.service'
import { PersonService } from './person.service'
@Injectable()
export class PersonMiddleware implements NestMiddleware {
@Inject(PersonService)
private personService: PersonService
use(req: Request, res: Response, next: NextFunction) {
console.log('before 中间件 ---' + req.url)
console.log('调用注入的服务 ---' + this.personService.findAll())
next()
console.log('after 中间件 ---' + res.statusCode)
}
}

当然,如果不需要依赖注入的话,也能使用轻量的函数中间件,就和定义普通函数差别不大

export function PersonMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
console.log('before 函数中间件 ---' + req.url)
next()
console.log('after 函数中间件 ---' + res.statusCode)
}

除了在局部引用,也能直接在全局引用,作为全局中间件使用,比如,我创建一个logger中间件,nest g mi logger

import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response, NextFunction } from 'express'
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('before 全局中间件 ---' + req.url)
next()
console.log('after 全局中间件 ---' + res.statusCode)
}
}

在main.ts中全局引入

import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { LoggerMiddleware } from './logger/logger.middleware'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.use(new LoggerMiddleware().use)
await app.listen(8088)
}
bootstrap()

2、守卫#

守卫的职责一般很明确,通常用于权限、角色等授权操作,守卫所在的位置与中间件类似,可以对请求进行拦截和过滤,其实,Guard 就可以理解为路由守卫的意思,可以用于在调用某个 Controller 之前判断权限,返回 true 或者 false 来决定是否放行

image-20250116160157181

守卫在调用路由程序之前返回true或者false来判断是否通行,同样分为全局守卫局部守卫

同样,我们可以使用命令**nest g gu [守卫名称]**来创建守卫模块,比如:nest g gu person就会自动帮我们在person文件夹下创建person.guard.ts文件

要作为一个守卫,必须实现CanActive接口中的canActivate()方法

person.guard.ts

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
import { Observable } from 'rxjs'
@Injectable()
export class PersonGuard implements CanActivate {
canActivate(
context: ExecutionContext
): boolean | Promise<boolean> | Observable<boolean> {
console.log('person guard')
return true
}
}

如果我们进行局部绑定,可以直接在Controller类上添加装饰器@UseGuards

@Controller('person')
@UseGuards(PersonGuard)
export class PersonController {......}

这样,当我们访问/person时,打印如下效果:

before 中间件 ---/person
person guard
after 中间件 ---200
person controller

但是如果在canActivate方法中返回的是false,就会是如下效果

{"message":"Forbidden resource","error":"Forbidden","statusCode":403}

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

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

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(new LoggerMiddleware().use);
app.useGlobalGuards(new PersonGuard());
await app.listen(8088);
}
bootstrap();

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

但是,注意,这种方式是通过自己new的 Guard 实例,不在 IoC 容器里。这会造成什么问题呢?由于没有在IoC容器中所以,当然无非获取从容器中注入的内容,比如像下面的代码:

@Injectable()
export class PersonGuard implements CanActivate {
@Inject(PersonService)
private personService: PersonService
canActivate(
context: ExecutionContext
): boolean | Promise<boolean> | Observable<boolean> {
console.log('person guard ---' + this.personService.findAll())
return true
}
}

PersonGuard中引入了PersonService,但是这需要从IoC容器中进行注入,而我们的PersonGuard是自己new出来的,并没有被IoC容器所托管,因此,在这里是用不了PersonService对象的,会在后台直接报错,直接前台提示500错误。

ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'findAll')
{"statusCode":500,"message":"Internal server error"}

所以,Nest还给我们提供了另外一种全局注册方式,在 AppModule 里声明,当然记得把之前new的Guard实例注释掉

import { APP_GUARD } from '@nestjs/core';
import { PersonGuard } from './person/person.guard';
@Module({
imports: [UserModule, PersonModule],
controllers: [AppController, PersonController],
providers: [
AppService,
PersonService,
{
provide: APP_GUARD,
useClass: PersonGuard,
},
],
})
export class AppModule {}

3、拦截器#

拦截器不同于中间件和守卫,它在路由请求之前和之后都可以进行逻辑处理,能够充分操作requestresponse对象。拦截器通常用于记录请求日志、转换或者格式化相应数据等等

image-20250121104548954

我们现在可以创建一个简单的拦截器,比如使用nest g itc timeout命令,直接帮我们创建一个timeout文件夹,并且创建timeout.interceptor.ts的拦截器文件。

拦截器通过@Injectable()来声明,并且需要实现NestInterceptor接口的intercept方法,接收两个参数:ExecutionContext上下文对象和CallHandler处理程序

ExecutionContext上下文对象能够访问当前请求的详细信息,包括路由、HTTP方法、请求体以及响应体数据,以下几个场景会用到它:

1、记录请求和响应日志,用于追踪、监控和调试

2、进行身份验证和权限检查

3、根据请求头或路由信息来设置缓存策略

4、修改或者转换响应数据,比如对响应进行包装、格式化、加密等操作

CallHandler实现了handle方法,必须在拦截器中调用handle方法,才能执行路由处理方法

import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common'
import { Observable } from 'rxjs'
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('进入拦截器')
return next.handle()
}
}

类似于守卫,拦截器可以设置为作用于某个控制器,或者某个控制器的某个方法,当然,也能作用于整个应用中

比如作用于某个控制器上:

@Controller('person')
@UseInterceptors(TimeoutInterceptor)
export class PersonController {......}

比如作用于某个方法上:

@Get()
@UseInterceptors(TimeoutInterceptor)
findAll() {
console.log('person controller');
return this.personService.findAll();
}

也能作用于全局,和守卫的使用方式基本一致

async function bootstrap() {
const app = await NestFactory.create(AppModule)
// app.use(new LoggerMiddleware().use);
// app.useGlobalGuards(new PersonGuard());
app.useGlobalInterceptors(new TimeoutInterceptor())
await app.listen(8088)
}
bootstrap()

当然,这里使用new方式的缺陷,也和守卫是一个道理,我们同样可以使用另外的方式

@Module({
imports: [UserModule, PersonModule],
controllers: [AppController, PersonController],
providers: [
AppService,
PersonService,
// {
// provide: APP_GUARD,
// useClass: PersonGuard,
// },
{
provide: APP_INTERCEPTOR,
useClass: TimeoutInterceptor,
},
],
})
export class AppModule {}

拦截器的请求和响应流程遵循先进后出的顺序。请求首先通过全局拦截器,然后是控制器级别的拦截器,最后是路由级别的拦截器进行处理。而响应流程则相反,从路由级别的拦截器开始,经过控制器级别的拦截器,最终到达全局拦截器。

这样的设计允许在处理请求的任何阶段,包括由管道、控制器或服务抛出的错误

intercept方法返回Observable对象,而Observable对象位于RxJS库中

**RxJS**是一个用于处理异步数据流的JavaScript·库。你可以把它理解为一个管道,它可以帮你更方便的处理各种事件和数据流,当然我们没必要增加心智负担,可以更直白的认为它就是和前端的Lodash工具库差不多的一个库。

Controller 之前之后的处理逻辑可能是异步的,所以可以使用 RxJS 的各种 operator

它是由数据源产生数据,经过一系列 operator 的处理,最后传给接收者。

这个数据源叫做 observable

RxJS 的核心思想是通过创建和订阅 Observable,使得你能够以同步的方式处理原本异步的事件流。这些异步操作通过 Observable 的内建机制被包装,并且可以利用 RxJS 提供的操作符进行组合、转换和调度。

Observable 的工作原理

  1. 创建 Observable: 你可以使用 new Observable() 来创建一个 Observable 实例,或者使用 RxJS 提供的各类创建操作符(如 of(), from(), interval())来创建。
  2. 订阅 Observable: 你需要调用 subscribe() 方法来订阅一个 Observable,订阅的过程会启动 Observable 发出数据流,subscribe() 会指定如何处理这些数据(如通过 next 处理值,error 处理错误,complete 处理流结束)。
  3. 发出数据流: 当你订阅了 Observable 后,它会根据内部逻辑(如事件、定时器、HTTP 请求等)按顺序发出数据。当数据发出时,next() 被调用。
  4. 终止流: 你可以通过 complete() 来表示数据流的结束,或者通过 error() 来表示发生了错误。流结束后就不再发出新的数据了。
import { Observable } from 'rxjs'
// 创建一个简单的 Observable
const observable = new Observable((subscriber) => {
subscriber.next('Hello')
subscriber.next('World')
subscriber.complete() // 表示流结束
})
// 订阅 Observable
observable.subscribe({
next(value) {
console.log(value)
}, // 处理数据
complete() {
console.log('Complete!')
}, // 处理完成
})

当然也可以使用RxJS提供的操作符来创建Observable

const observable = of(1, 2, 3, 4, 5)
observable.subscribe({
next(value) {
console.log(value)
}, // 处理每个值
complete() {
console.log('Complete!')
}, // 流结束
})

你可以把 of(1, 2, 3, 4, 5) 想象成一个依次“发射”这些值的事件流。每当这些值被发射时,它们会被 subscribe() 里面的 next 处理。

同样,也能把异步事件源转换为Observable

import { from } from 'rxjs'
const promise = new Promise((resolve) =>
setTimeout(() => resolve('Hello World'), 1000)
)
const observable = from(promise) // 将 Promise 转化为 Observable
observable.subscribe({
next(value) {
console.log(value)
},
complete() {
console.log('Complete!')
},
})

如果有多个promise还能使用RxJs提供的各种操作符来处理

import { from } from 'rxjs'
import { concatMap } from 'rxjs/operators'
const promise1 = new Promise((resolve) =>
setTimeout(() => resolve('Hello'), 500)
)
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('World'), 1000)
)
const promise3 = new Promise((resolve) => setTimeout(() => resolve('!'), 1500))
const promises = [promise1, promise2, promise3]
from(promises) // 创建一个 Observable,依次发出每个 Promise
.pipe(
concatMap((promise) => from(promise)) // 串行执行 Promise
)
.subscribe({
next(value) {
console.log(value)
},
complete() {
console.log('Complete!')
},
})

如果你希望多个 Promise 并发执行(同时发出值),你可以使用 forkJoin

forkJoin([promise1, promise2, promise3]) // 并发执行所有 Promise
.subscribe({
next(values) {
console.log(values)
}, // values 是每个 Promise 的结果的数组
complete() {
console.log('Complete!')
},
})

除此之外,还提供了其他非常多的**操作符**,我们可以来试试效果

import { of, filter, map, tap } from 'rxjs'
of(1, 2, 3, 4, 5)
.pipe(map((x) => x * x))
.pipe(filter((x) => x % 2 !== 0))
.subscribe((v) => console.log(`打印: ${v}`))

创建另外一个interceptor来试试,比如nest g itc auth

import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common'
import { catchError, filter, map, Observable, tap, toArray } from 'rxjs'
@Injectable()
export class AuthInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// 请求处理之前打印日志
console.log('before interceptor')
return next.handle().pipe(
// 将用户名转换为大写
map((data) => data.toUpperCase()),
// 过滤出数据中包含A的数据
filter((data) => data.includes('a')),
// 打印处理后的数据
tap((data) => console.log('after interceptor', data)),
// 转换为数组
toArray(),
// 使用catchError处理错误
catchError((err) => {
console.log('error interceptor', err)
throw new Error(err)
})
)
}
}

controller中传递数据

@Get('name')
@UseInterceptors(AuthInterceptor)
findName() {
console.log('进入name---');
return from(['hello', 'worldA', 'abc']);
}

4、管道#

Pipe 就是管道的意思,主要的作用就是解析和验证请求数据。

image-20250122162002384

在后端开发中,数据库表的字段类型在创建时就已经被明确定义了,任何不符合预期类型的数据保存操作都会导致错误。为了确保传入的数据满足预期的格式和规范,Nest会在客户端发起请求的时候,将请求数据传递给管道进行预处理。这些预处理操作包括数据验证、转换或者过滤等等,以确保数据的准确性。处理之后的数据会被传递给路由处理程序。

通常情况下,我们会将管道绑定在方法参数上,这样管道就会与特定的路由方法关联起来了,这种方式称为参数级别管道。当然,管道也能绑定在全局作用域,使其适用于每个控制器和路由方法。

用 nest cli 命令创建一个pipe,nest g pipe validate --no-spec --flat

Pipe 需要要实现 PipeTransform 接口,并实现 transform 方法

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 : parseInt(value)
}
}

在controller中

@Delete(':id')
remove(@Param('id', ValidatePipe) id: string) {
console.log('person delete ---' + id)
return this.personService.remove(+id);
}

我们可以使用apifox或者postman测试一下,如果传递错误的参数,也就是id不是数字或者不能转换为数字,直接报出下面的错误:

{
"message": "参数id错误",
"error": "Bad Request",
"statusCode": 400
}

当然,像这种比较简单的管道验证,nestjs已经帮我们内置了9种开箱即用的管道验证器

这些自定义管道,都是在@nestjs/common包中,比如上面我们自定义的Pipe完全就可以使用ParseIntPipe替换

@Delete(':id')
remove(@Param('id', ParseIntPipe) id: string) {
console.log('person delete ---' + id);
return this.personService.remove(+id);
}

同样,如果传递错误的参数,比如127.0.0.1:8088/person/abc,就会提示如下的错误。

{
"message": "Validation failed (numeric string is expected)",
"error": "Bad Request",
"statusCode": 400
}

当然,这种自带的管道验证,错误响应式默认定义的,我们当然也能自定义,验证器允许传递下面两个参数

1、errorHttpStatusCode:验证器失败时抛出的HTTP状态码,默认为400(错误请求)

2、exceptionFactory:工厂函数,用于接收错误信息并返回相应的错误对象

比如:

@Delete(':id')
remove(
@Param(
'id',
new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }),
)
id: string,
) {
console.log('person delete ---' + id);
return this.personService.remove(+id);
}
}

这样,错误的时候,就会报出下面的信息:

{
"message": "Validation failed (numeric string is expected)",
"error": "Not Acceptable",
"statusCode": 406
}

其中HttpStatus同样出自@nestjs/common包,是一个枚举

export declare enum HttpStatus {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
EARLYHINTS = 103,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
AMBIGUOUS = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
I_AM_A_TEAPOT = 418,
MISDIRECTED = 421,
UNPROCESSABLE_ENTITY = 422,
FAILED_DEPENDENCY = 424,
PRECONDITION_REQUIRED = 428,
TOO_MANY_REQUESTS = 429,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
}

再比如我们可以定义工厂函数

@Delete(':id')
remove(
@Param(
'id',
new ParseIntPipe({
exceptionFactory: (error: string) => {
throw new HttpException('参数id类型错误', HttpStatus.BAD_REQUEST);
},
}),
)
id: string,
) {
console.log('person delete ---' + id);
return this.personService.remove(+id);
}

如果传递错误参数,提示下面的错误:

{
"statusCode": 400,
"message": "参数id类型错误"
}

当然上面只是对某个参数生效,Pipe也能对整个 Controller 都生效

@Controller('person')
@UsePipes(ParseIntPipe)
export class PersonController {......}

或者全局都生效

async function bootstrap() {
const app = await NestFactory.create(AppModule)
// app.use(new LoggerMiddleware().use);
// app.useGlobalGuards(new PersonGuard());
// app.useGlobalInterceptors(new TimeoutInterceptor());
app.useGlobalPipes(new ParseIntPipe())
await app.listen(8088)
}
bootstrap()

另外的方式:

@Module({
imports: [UserModule, PersonModule],
controllers: [AppController, PersonController],
providers: [
AppService,
PersonService,
// {
// provide: APP_GUARD,
// useClass: PersonGuard,
// },
// {
// provide: APP_INTERCEPTOR,
// useClass: TimeoutInterceptor,
// },
{
provide: APP_PIPE,
useClass: ParseIntPipe,
},
],
})
export class AppModule {}

5、过滤器#

image-20250123141912674

nest中最常见的是HTTP异常过滤器,通常用于在后端服务发生异常时向客户端报告异常的类型,目前内置的HTTP异常包括下面几种,它们全部也都包含在@nestjs/common包中:

其实我们在 自定义pipe 里抛的错误,能够返回 400 的响应,就是 Exception Filter 处理的。

当然,我们也能自定义Filter,比如使用下面的命令

nest g filter myException --no-spec --flat

稍微修改一下生成的filter代码

import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
} from '@nestjs/common'
import { Response, Request } from 'express'
@Catch(HttpException)
export class MyExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
// 指定传输协议
const ctx = host.switchToHttp()
// 获取响应对象(注意这里的Response是express的Response)
const response = ctx.getResponse<Response>()
// 获取请求对象(注意这里的Request是express的Request)
const request = ctx.getRequest<Request>()
// 获取状态码
const status = exception.getStatus()
// 获取异常信息
const message = exception.message
let resMessage: string | Record<string, any> = exception.getResponse()
console.log('---MyExceptionFilter---')
if (typeof resMessage === 'object') {
resMessage = resMessage.message
}
// 统一组装返回的数据格式
response.status(status).json({
statusCode: status,
message: resMessage || message,
path: request.url,
success: false,
})
}
}

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

ArgumentsHost能够获取不同平台的传输协议上下文,用于访问requestresponse对象

@Catch()装饰器用于声明要拦截的异常类型,上面的例子使用的是HTTPException

异常过滤器同样可以应用于方法,整个控制器和全局。

比如,将自定义的异常运用到我们之前的删除方法上,如果传递的数据类型不正确

@Delete(':id')
@UseFilters(MyExceptionFilter)
remove(@Param('id', ParseIntPipe) id: string) {
console.log('person delete ---' + id);
return this.personService.remove(+id);
}

现在就报出下面的错误信息:

{
"statusCode": 400,
"message": "Validation failed (numeric string is expected)",
"path": "/person/abc",
"success": false
}

作用于整个controller上

@Controller('person')
// @UseGuards(PersonGuard)
// @UseInterceptors(TimeoutInterceptor)
// @UsePipes(ParseIntPipe)
@UseFilters(MyExceptionFilter)
export class PersonController {......}

作用于全局

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new MyExceptionFilter());
await app.listen(8088);
}
bootstrap();

注入的方式:

@Module({
imports: [UserModule, PersonModule],
controllers: [AppController, PersonController],
providers: [
AppService,
PersonService,
......
{
provide: APP_FILTER,
useClass: MyExceptionFilter,
},
],
})
export class AppModule {}

Nest与Express的关系#

前面我们用的Nestrequestresponse 对象都特地强调了是引入express

实际上,对于http请求响应的处理,Nest可以轻松的切换 express、fastify库,在源代码内部使用了适配器模式

我用简单的例子,给大家解释一下适配器模式,大家就知道了。

首先大家要知道,后端都是强类型的模式,比如我们用动物举例:

class Duck {
quack(): void {
console.log('鸭子:嘎嘎嘎')
}
}
class Chicken {
cluck(): void {
console.log('鸡:咯咯咯')
}
}

鸭子和鸡,是不同的类型,两者像现在这样是无法产生关联的。

但是就算国内与其他地区的插头一样,虽然规格不一样,但是只要我们有一个转接头,就能完美的适配不同地区的接口

因此,我可以创建一个接口:

interface Quackable {
quack(): void
}

当我需要统一的动物叫声的时候,我可以像下面这些写一个方法:

function makeItQuack(animal: Quackable) {
animal.quack()
}

makeItQuack方法的参数,是Quackable接口。

现在,无论是鸭还是鸡,我通过统一的适配器进行转换:

class AnimalAdapter implements Quackable {
private animal: any
private soundMethod: string
constructor(animal: any, soundMethod: string) {
this.animal = animal
this.soundMethod = soundMethod
}
quack(): void {
// 动态调用对应动物的叫声方法
if (this.soundMethod in this.animal) {
this.animal[this.soundMethod]()
} else {
console.log('动物没有这个叫声方法!')
}
}
}

这样,具体在使用的时候,我们就可以非常方便的进行替换

function makeItQuack(animal: Quackable) {
animal.quack()
}
// 使用鸭子(通过适配器)
const duck = new Duck()
const duckAdapter = new AnimalAdapter(duck, 'quack')
makeItQuack(duckAdapter) // 输出: 鸭子:嘎嘎嘎
// 使用鸡(通过适配器)
const chicken = new Chicken()
const chickenAdapter = new AnimalAdapter(chicken, 'cluck')
makeItQuack(chickenAdapter) // 输出: 鸡:咯咯咯

所以,Nest对于请求响应的底层处理,就是通过这种适配器模式,可以非常方便的进行替换,到底是使用express,还是fastify,进本的情况如下图:

image-20250206174152626

当然,默认Nest使用的express,如果你要使用fastify,导入fastify相应的包即可