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

在 MVC 的基础上,Nest 还提供了 AOP(Aspect Oriented Programming)的能力,即面向切面编程。
面向切面编程的含义可以依据实际场景理解:
当请求过来时,会经过 Controller、Service、View。
如果有一些通用的逻辑,比如日志记录、权限控制、异常处理等,这些都应该放在哪一层?很明显,放在 Controller 层比较好,因为它用于控制。
但是 Controller 层我们一般会写很多跟业务相关的逻辑,如果再加入通用的逻辑,这一层的代码就会变得臃肿。
所以最好的处理方式是在 Controller 层之前或者之后加入一层处理通用逻辑的阶段。
这种横向的扩展点就叫做切面,加入一些切面逻辑的编程就叫做 AOP。
AOP 的好处是可以把一些通用逻辑分离到切面中,保持业务逻辑的纯粹性,这样切面逻辑可以复用,还可以动态的增删。
Nest 实现 AOP 的方式很多,一共有五种,包括 Middleware、Guard、Pipe、Interceptor、ExceptionFilter。
中间件 Middleware
中间件分全局中间件和路由中间件:
全局中间件是这么写的:
ts1async function bootstrap() { 2 const app = await NestFactory.create(AppModule) 3 await app.use(logger) 4 await app.listen(3000) 5} 6bootstrap()
路由中间件则是针对某个路由来说的:
ts1export class AppModule implements NestModule { 2 configure(consumer: MiddlewareConsumer) { 3 consumer.apply(LoggerMiddleware).forRoutes('cats') 4 } 5}
路由守卫 Guard
Guard 是路由守卫的意思,可以用于在调用某个 Controller 之前判断权限,返回 true 或者 false 来决定是否放行:
-
创建 guard
bash1nest generate guard 2 3? What name would you like to use for the guard? Roles 4CREATE src/roles-guard/roles-guard.guard.spec.ts (185 bytes) 5CREATE src/roles-guard/roles-guard.guard.ts (305 bytes)此时生成 guard 代码:
ts1@Injectable() 2export class RolesGuard implements CanActivate { 3 canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { 4 return true 5 } 6} -
通过@UseGuards 装饰器注入到 Controller 中
ts1@Controller() 2export class AppController { 3 @Get() 4 @UseGuards(RolesGuard) 5 getHello(): string { 6 return 'Hello World!' 7 } 8}
Controller 本身不需要做啥修改,却透明的加上了权限判断的逻辑,这就是 AOP 架构的好处。
就像 Middleware 支持全局级别和路由级别一样,Guard 也可以全局启用:
ts1async function bootstrap() { 2 const app = await NestFactory.create(AppModule) 3 app.useGlobalGuards(new RolesGuard()) 4 await app.listen(3000) 5} 6bootstrap()
拦截器 Interceptor
Guard 可以抽离路由的访问控制逻辑,但是不能对请求、响应做修改,这种逻辑可以使用 Interceptor。
Interceptor 是拦截器的意思,可以在目标 Controller 方法前后加入一些逻辑:
bash1nest generate interceptor Route
生成代码
ts1@Injectable() 2export class RouteInterceptor implements NestInterceptor { 3 intercept(context: ExecutionContext, next: CallHandler): Observable<any> { 4 console.log('...before') 5 6 return next.handle().pipe(tap(() => console.log('...after'))) 7 } 8}
Interceptor 要实现 NestInterceptor 接口,实现 intercept 方法,调用 next.handle() 就会调用目标 Controller,可以在之前和之后加入一些处理逻辑。
Controller 之前之后的处理逻辑可能是异步的。Nest 里通过 rxjs 来组织它们,所以可以使用 rxjs 的各种 operator。
在单个 Controller 中使用:
ts1@Controller() 2export class AppController { 3 @Get() 4 @UseInterceptors(new RouteInterceptor()) 5 getHello(): string { 6 return 'Hello World!' 7 } 8}
全局使用:
ts1async function bootstrap() { 2 const app = await NestFactory.create(AppModule) 3 app.useGlobalInterceptors(new RouteInterceptor()) 4 await app.listen(3000) 5} 6bootstrap()
管道 Pipe
除了路由的权限控制、目标 Controller 之前之后的处理这些都是通用逻辑外,对参数的处理也是一个通用的逻辑,所以 Nest 也抽出了对应的切面,也就是 Pipe:
ts1nest generate pipe App
生成代码:
ts1@Injectable() 2export class AppPipe implements PipeTransform { 3 transform(value: any, metadata: ArgumentMetadata) { 4 return value 5 } 6}
Pipe 要实现 PipeTransform 接口,实现 transform 方法,里面可以对传入的参数值 value 做参数验证,比如格式、类型是否正确,不正确就抛出异常。也可以做转换,返回转换后的值.
内置的有 9 个 Pipe,从名字就能看出它们的意思:
- ValidationPipe
- ParseIntPipe
- ParseBoolPipe
- ParseArrayPipe
- ParseUUIDPipe
- DefaultValuePipe
- ParseEnumPipe
- ParseFloatPipe
- ParseFilePipe
同样,Pipe 可以只对某个参数生效:
tsx1@Controller() 2export class AppController { 3 @Get(':id') 4 getHello(@Param('id', AppPipe) id: string): string { 5 console.log('——————🚀🚀🚀🚀🚀 —— id:', id) 6 return 'Hello World!' 7 } 8}
只对某个路由生效:
ts1 @Post() 2 @UsePipes(AppPipe) 3 async create(@Body() createUser: CreateUserDto) { 4 // ... 5 }
global 生效:
ts1async function bootstrap() { 2 const app = await NestFactory.create(AppModule) 3 app.useGlobalPipes(new AppPipe()) 4 await app.listen(3000) 5} 6bootstrap()
异常处理 ExceptionFilter
ExceptionFilter 可以对抛出的异常做处理,返回对应的响应:
bash1nest generate filter HttpException
生成代码
ts1@Catch(HttpException) 2export class HttpExceptionFilter implements ExceptionFilter { 3 catch(exception: HttpException, host: ArgumentsHost) { 4 const ctx = host.switchToHttp() 5 const response = ctx.getResponse() 6 const request = ctx.getRequest() 7 const status = exception.getStatus() 8 response.status(status).json({ 9 statusCode: status, 10 path: request.url, 11 message: exception.message, 12 }) 13 } 14}
首先要实现 ExceptionFilter 接口,实现 catch 方法,就可以拦截异常了,但是要拦截什么异常还需要用 @Catch 装饰器来声明。
Nest 会 catch 所有未捕获异常,如果是 Exception Filter 声明的异常,那就会调用 filter 来处理。
拦截了异常之后,可以返回对应的响应,给用户更友好的提示。
Nest 内置了很多 http 相关的异常,都是 HttpException 的子类:
- BadRequestException
- UnauthorizedException
- NotFoundException
- ForbiddenException
- NotAcceptableException
- RequestTimeoutException
- ConflictException
- GoneException
- PayloadTooLargeException
- UnsupportedMediaTypeException
- UnprocessableException
- InternalServerErrorException
- NotImplementedException
- BadGatewayException
- ServiceUnavailableException
- GatewayTimeoutException
也可以自己扩展,只需要继承自 HttpException 即可
ts1export class IpRestriction extends HttpException { 2 constructor() { 3 super('Forbidden', 403) 4 } 5}
Nest 通过这样的方式实现了异常到响应的对应关系,代码里只要抛出不同的异常,就会返回对应的响应,很方便。
同样,ExceptionFilter 也可以选择全局生效或者某个路由生效:
某个路由生效:
ts1@Controller() 2export class AppController { 3 @Get() 4 @UseFilters(new HttpExceptionFilter()) 5 getHello(): string { 6 return 'Hello World!' 7 } 8}
全局路由生效:
ts1async function bootstrap() { 2 const app = await NestFactory.create(AppModule) 3 app.useGlobalFilters(new HttpExceptionFilter()) 4 await app.listen(3000) 5} 6bootstrap()
顺序
这里有一张别人总结好的图,根据需要在不同的切面里加入不同的逻辑即可。
总结
Nest 在传统 MVC 架构基础上,加入了 AOP 编程的思想,我们可以利用这些切面完成额外的某些逻辑,让 Controller 只专注于实现业务逻辑。
Nest 通过以下扩展点(顺序排放)实现 AOP:
- Middleware 中间件
- Guard 路由守卫,可以控制是否让 request 进入 handler 逻辑
- Interceptor 拦截器,可以做参数处理,修改 request 和 Response
- Pipe 管道:参数处理(转换、校验等)
- ExceptionFilter :错误处理