首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Nest世界中的AOP

  • 25-04-20 18:41
  • 2152
  • 9579
juejin.cn

对于前端开发来说,Nest中有很多全新的概念,这里我们来学习一下nest中AOP思想,并且学习Nest中提供的AOP方式。

AOP

Nest 实现 AOP 的方式更多,一共有五种,包括 Middleware、Guard、Pipe、Interceptor、ExceptionFilter。他们其实都是中间件的概念,只是分成了具体的内容,不同中间件做不同事情。

这张图很形象的介绍了nest整个运行周期所经过的核心逻辑流程。 image.png

中间件 Middleware

默认情况下由于nest底层是express提供服务,所以nest中间件就是express中的中间件。在nest中中间件有两种。一般用于处理请求和响应的,在nest提供的AOP服务中,请求最先到达的就是中间件处理层。

  • 全局中间件,他作用于nest任何一个controller方法。
js
代码解读
复制代码
// main.ts app.use((req, res, next) => { console.log('global middleware before', req.url); next(); console.log('global middleware after'); });
  • 路由和控制器中间件。 中间件的定义就是继承自NestMiddleware接口,并实现use方法。我们一般在app.module.ts中进行使用。
js
代码解读
复制代码
export class AppModule implements NestModule { // 使用中间件 configure(consumer: MiddlewareConsumer) { /** * apply可以指定多个中间件函数或者类 * exclude 可以设置规则匹配排出的路由,一般用于设置控制器中间件。 */ // 1.可以设置路由中间件(匹配规则,限定方法) // consumer.apply(LoggerMiddleware).forRoutes({ // path: 'geturl*', // method: RequestMethod.GET, // }); // 1.可以设置特定路由 // consumer.apply(LoggerMiddleware).forRoutes('api-test/geturlparams/1'); // 2.设置控制器中间件(作用域当前控制器所有路由) consumer.apply(LoggerMiddleware, fnMiddleWare).forRoutes(ApiTestController); } }

image.png

守卫 Guard

守卫是一个用 @Injectable() 装饰器的类,它实现了 CanActivate 接口。通过@UseGuards()使用。在所有中间件之后、任何拦截器或管道之前执行。他相较于middleware而言可以拿到程序执行的上下文。

  • 全局守卫,nest提供了两种注册全局守卫的方式。我们来看看区别
    • 直接在main.ts中进行注册。这种方式需要我们自己手动实例化,并不能交给nest的ioc容器。这样就导致我们不能在LoginGuard中进行注入其他依赖。
    js
    代码解读
    复制代码
    app.useGlobalGuards(new LoginGuard());
    • 在app.module.ts中的provides进行注入。这种方式就是是让LoginGuard类交给nest去实例化和创建,并且可以注入其他依赖。
    js
    代码解读
    复制代码
    providers: [ { provide: APP_GUARD, useClass: LoginGuard, }, ],
  • 控制器和路由守卫 这种守卫,直接使用UseGuards装饰器在对应的controller和方法使用即可。
js
代码解读
复制代码
@UseGuards(LoginGuard) getUrlParam(@Param('id') id: string) { console.log('获取get请求url param参数'); return `获取get请求url param参数: ${id}`; }

image.png

拦截器 Interceptor

拦截器是用 @Injectable() 装饰器的类并实现 NestInterceptor 接口。他和guard一样都可以拿到程序执行的上下文。并且和Guard的作用级别一样,有全局,控制器,路由级别。他区别于其他AOP,提供了前置和后置拦截器,可以在路由处理程序前后做一些逻辑处理。例如设置当前项目的接口标准返回值格式。

js
代码解读
复制代码
// 拦截器 import { Injectable, NestInterceptor, ExecutionContext, CallHandler, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LogInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { // context和guard传入的对象一样,可以获取当前执行上下文,next内部提供了一个handle方法,用于调用控制器方法 console.log('interceptor before...'); return next.handle().pipe(tap(() => console.log(`interceptor after...`))); } }
  • 全局拦截器,也有两种方式,区分的作用和Guard一样。
js
代码解读
复制代码
// 方式一 main.ts app.useGlobalInterceptors(new LogInterceptor()); // 方式二 app.module.ts providers: [ { provide: APP_INTERCEPTOR, useClass: LogInterceptor, }, ]
  • 控制器和路由 这种拦截器,直接使用UseInterceptors装饰器在对应的controller和方法使用即可。
js
代码解读
复制代码
@UseInterceptors(LogInterceptor) getUrlParam(@Param('id') id: string) { console.log('获取get请求url param参数'); return `获取get请求url param参数: ${id}`; }

image.png

了解了拦截器的用法后,我们就来看看如何实现企业级项目的接口的返回值格式。

js
代码解读
复制代码
// 接口一般都会返回和谐信息 { data, // 数据 status: 0, // 接口状态值 extra: {}, // 拓展信息 message: 'success', // 异常信息 success:true // 接口业务返回状态 }

实现,就是拿到路由处理程序返回值做数据映射。

js
代码解读
复制代码
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; interface Response { data: T; } @Injectable() export class TransformInterceptor implements NestInterceptorResponse> { intercept( context: ExecutionContext, next: CallHandler, ): Observable<Response> { // 拿到路由处理程序返回值做数据映射 return next.handle().pipe( map((data) => ({ data, status: 0, extra: {}, message: 'success', success: true, })), ); } }

image.png

管道 pipe

管道是用 @Injectable() 装饰器的类,它实现了 PipeTransform 接口。

管道有两个典型的用例:

  • 转型:将输入数据转换为所需的形式(例如,从字符串到整数)。
  • 验证:评估输入数据,如果有效,只需将其原样传递;否则抛出异常。

管道和其他AOP模型一样,都可以作用于路由,路由控制器和全局,并且管道还可以作用于路由处理器参数。

js
代码解读
复制代码
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException, } from '@nestjs/common'; @Injectable() export class TestPipe implements PipeTransform { // 接受原始数据和方法参数的元素据 transform(value: any, metadata: ArgumentMetadata) { if (typeof value !== 'number') { throw new BadRequestException('url param不是数字'); } return value * 10; } }
  • 全局管道,也有两种方式,区分的作用和Guard一样。
js
代码解读
复制代码
// main.ts app.useGlobalPipes(new TestPipe()); // app.module.ts providers: [ { provide: APP_PIPE, useClass: TestPipe, }, ],

image.png

  • 控制器,路由,路由处理函数参数管道

直接使用UsePipes装饰器在对应的controller和方法和参数解析器(Param, Query, Body等等)中使用即可。

image.png

Nest内置了九个开箱即用的管道,除了ParseFilePipe外转化错误会抛出BadRequestException异常,ParseFilePipe中大小错误将抛出PayloadTooLargeException异常,类型错误将抛出UnsupportedMediaTypeException异常。

ValidationPipe

基于类验证器(class - validator)库,对传入的请求数据(通常是 DTO - Data Transfer Object 中的数据)进行全面验证。可以验证数据的格式、类型、范围等各种规则。

ParseIntPipe

专门用于将输入的值转换为整数类型。在处理需要整数类型数据的场景下非常有用,如处理路由参数、查询参数中的数字标识。

ParseFloatPipe

与 ParseIntPipe 类似,主要用于将输入的值转换为浮点数类型。适用于涉及金额、比例、坐标等需要浮点数表示的数据场景。

ParseBoolPipe

用于将输入的值转换为布尔类型。可以处理来自请求中的各种形式表示的布尔值,如字符串形式的 'true'、'false' 或者数字形式的 1、0 等。

ParseArrayPipe

能够将输入的数据解析为数组形式。可用于处理以特定格式传递的数组数据,如逗号分隔的字符串转换为数组,或者解析复杂格式的数组数据。

ParseUUIDPipe

专门用于解析通用唯一识别码(UUID)类型的数据。在处理与唯一标识相关的场景,如数据库主键为 UUID 类型或者处理外部系统传递的 UUID 标识时非常有用。

ParseEnumPipe

用于将输入的值转换为对应的枚举类型。在 NestJS 中,当业务逻辑使用枚举类型来表示特定的状态或类型时,此管道可确保传入的数据与定义的枚举类型相匹配。

DefaultValuePipe

当传入的参数可能为空或者未定义(null, undefined)时,为该参数设置一个默认值。这在处理可选参数或者数据可能缺失的情况下非常有用。

ParseFilePipe

用于处理文件上传相关的操作。它可以对上传文件的大小、类型等进行限制和验证,确保上传的文件符合应用的要求。

异常过滤器 ExceptionFilter

Nest 带有一个内置的异常层,负责处理应用中所有未处理的异常。当你的应用代码未处理异常时,该层会捕获该异常,然后自动发送适当的用户友好响应。

Nest 提供了一组继承自基 HttpException 的标准异常。

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • HttpVersionNotSupportedException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • ImATeapotException
  • MethodNotAllowedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException
  • PreconditionFailedException

内置的异常处理层已经可以满足处理异常了,但是你可能希望根据某些动态因素添加日志记录或使用不同的 JSON 模式。异常过滤器正是为此目的而设计的。它们让你可以控制准确的控制流和发送回客户端的响应内容。

异常过滤器都需要实现ExceptionFilter接口。使用@Catch()修饰类,并且指定要处理的异常类型,如果未传递异常类型,那么该过滤器将捕获所有类型的异常。 这对于一个健壮的程序来说非常重要,返回统一且明确的信息,可以很快定位错误。

下面我们来看看如何来实现企业级程序的异常处理器。

我们需要处理程序中抛出的所有异常,所以我们就需要实现一个捕获全部异常的过滤器。

js
代码解读
复制代码
// base.exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus, ServiceUnavailableException, LoggerService, Inject, } from '@nestjs/common'; import { Request, Response } from 'express'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { constructor( // 该模块需要等到logger初始化完成之后在进行初始化。 @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, // 可以注入日志系统 ) {} catch(exception: Error, host: ArgumentsHost) { const ctx = host.switchToHttp(); // 获取请求上下文 const response = ctx.getResponse<Response>(); // 获取响应对象 const request = ctx.getRequest<Request>(); // 获取请求对象 this.logger.error(exception); // 输出错误日志 // 非 HTTP 标准异常的处理。 response.status(HttpStatus.SERVICE_UNAVAILABLE).send({ statusCode: HttpStatus.SERVICE_UNAVAILABLE, timestamp: new Date().toISOString(), path: request.url, message: new ServiceUnavailableException().getResponse(), }); } }

如果程序抛出HttpException相关异常,我们就需要设置HttpException统一的异常过滤器,返回更加信息的错误内容。

js
代码解读
复制代码
// http.exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, } from '@nestjs/common'; import { BusinessException } from './business.exception'; import { Request, Response } from 'express'; @Catch(HttpException) // 指定HttpException,只捕获所有http相关的异常 export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception.getStatus(); // 获取当前错误的状态码。由于所有http异常都继承自HttpException,所以这里可以拿到 // 处理业务异常(主动抛出的异常) if (exception instanceof BusinessException) { const error = exception.getResponse(); // BusinessException 异常内部提供error, message属性 response.status(HttpStatus.OK).send({ data: null, status: error['code'], extra: {}, message: error['message'], success: false, }); return; } response.status(status).send({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: exception.getResponse(), }); } }

还有业务相关主动抛出异常我们也可以设置一种过滤器来进行处理。

js
代码解读
复制代码
// business.exception.ts import { HttpException, HttpStatus } from '@nestjs/common'; import { BUSINESS_ERROR_CODE } from './business.error.codes'; type BusinessError = { code: number; message: string; }; export class BusinessException extends HttpException { constructor(err: BusinessError | string) { if (typeof err === 'string') { err = { code: BUSINESS_ERROR_CODE.COMMON, message: err, }; } super(err, HttpStatus.OK); } static throwForbidden() { throw new BusinessException({ code: BUSINESS_ERROR_CODE.ACCESS_FORBIDDEN, message: '抱歉哦,您暂无权限!', }); } } // business.error.codes.ts export const BUSINESS_ERROR_CODE = { // 公共错误码 COMMON: 10001, // 特殊错误码 TOKEN_INVALID: 10002, // 禁止访问 ACCESS_FORBIDDEN: 10003, // 权限已禁用 PERMISSION_DISABLED: 10003, // 用户已冻结 USER_DISABLED: 10004, };

这样我们项目中的异常处理就很健壮了,下面我们来测试一下相关功能。

image.png

image.png

image.png

调试

浏览器中调试(npm run start:debug)

对于前端开发者来说,可能更习惯于在浏览器中进行调试。nest也方便的提供了调试运行命令。npm run start:debug。然后会启动一个ws服务,我们就可以在内部进行断点调试了。

调试1.gif

vscode中调试(npm run start:dev)

可是一般开发后台服务并不需要关心页面内容,所以可能在编译器中调试会更合适,所以我们可以直接使用vscode提供的调试服务。

创建一个launch.json配置文件。然后通过debug运行项目。

js
代码解读
复制代码
{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "debug nest", "runtimeExecutable": "npm", // 运行命令 "args": [ // 命令参数 "run", "start:dev", ], "skipFiles": [ "/**" ], "console": "integratedTerminal", // 用 vscode 的内置终端来打印日志 } ] }

或者一个更简单的方式,ctrl + shift + p搜索Toggle Auto Attach。

image.png 然后启动运行命令调试程序即可。

提供器

提供器有两种方式注入到其他提供器和控制器中。

  • 基于构造函数
js
代码解读
复制代码
constructor(private readonly apiTestService: ApiTestService) {}
  • 基于属性
js
代码解读
复制代码
@Inject() private readonly ApiTestService: ApiTestService;

提供器可以由多种方式构成,并且提供器的token可以是类和字符串构成,如果是字符串构成,我们在@Inject时需要制定该字符串

  • 基于@Injectable标识的类,使用useClass引用。或者直接写类。
js
代码解读
复制代码
providers: [ ProviderTestService, // ProviderTestService 等价于 { provide: ProviderTestService, useClass: ProviderTestService, }, ],
  • 基于任意值,使用useValue引用。
js
代码解读
复制代码
{ provide: 'CUSTOM_VALUE', useValue: 'CUSTOM_VALUE', },
  • 基于工厂函数,使用useFactory引用。这个可以注入其他提供器供其使用。并且支持异步。
js
代码解读
复制代码
{ provide: 'CUSTOM_FN', async useFactory() { return new Promise((resolve) => { setTimeout(() => { resolve('00000s'); }); }); }, },
  • 如果想要修改提供器的token,我们还可以使用useExisting设置别名。为已存在的provide设置别名。
js
代码解读
复制代码
providers: [ ProviderTestService, // ProviderTestService 等价于 { provide: ProviderTestService, useClass: ProviderTestService, }, { provide: 'CUSTOM_VALUE', useValue: 'CUSTOM_VALUE', }, { provide: 'CUSTOM_FN', async useFactory() { return new Promise((resolve) => { setTimeout(() => { resolve('00000s'); }); }); }, }, // 修改ProviderTestService提供器的名称,但是之前的名称还是可以使用的 { provide: 'aliasProvide', useExisting: ProviderTestService, }, ],

往期年度总结

  • 四年沿海城市,刚毕业,一年3家公司
  • 七月仿佛又回到了那一年(2023年中总结)
  • 一位初入职场前端仔的年度终结 <回顾2022,展望2023>
  • 大学两年半的前端学习

往期文章

  • 如何理解js的DOM事件系统
  • 半年没看vue官网,3.5刚刚发布,趁机整理下
  • 啊,你还在找一款强大的表格组件吗?
  • 前端大量数据层级展示及搜索定位预览
  • 如何从0开始认识m3u8(提取,解析及下载)
  • 展示大量数据节点(tree),引发的一次性能排查
  • ts装饰器的那点东西
  • 这是你所知道的ts类型断言和类型守卫吗?
  • TypeScript官网内容解读
  • 经常使用ts的你,知道这些内容?
  • 你有了解过原生css的scope?
  • 现在比较常用的移动端调试你知道哪些?
  • 众多跨标签页通信方式,你知道哪些?(二)
  • 众多跨标签页通信方式,你知道哪些?
  • 反调试吗?如何监听devtools的打开与关闭
  • 因为原生,选择一家公司(前端如何防笔试作弊)
  • 结合开发,带你熟悉package.json与tsconfig.json配置
  • 如何优雅的在项目中使用echarts
  • 如何优雅的做项目国际化
  • 近三个月的排错,原来的憧憬消失喽
  • 带你从0开始了解vue3核心(运行时)
  • 带你从0开始了解vue3核心(computed, watch)
  • 带你从0开始了解vue3核心(响应式)
  • 3w+字的后台管理通用功能解决方案送给你
  • 入职之前,狂补技术,4w字的前端技术解决方案送给你(vue3 + vite )

专栏文章

  • 重学vue及其生态
  • 前端面试
  • 前端工程化
  • amis 低代码实战
  • 协议与安全
  • 重学react
  • 重学nodejs
  • 工作总结

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,    支持一下博主~

公众号:全栈追逐者,不定期的更新内容,关注不错过哦!

注:本文转载自juejin.cn的Spirited_Away的文章"https://juejin.cn/post/7420325706289332260"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top