首页 最新 热门 推荐

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

【Nest.js 通关秘籍 - 基础篇】带你轻松掌握后端开发

  • 25-04-18 19:27
  • 2856
  • 8708
juejin.cn

前言

juejin.cn/book/722698…

bosombaby.blog.csdn.net/article/det…

  1. Express 进一步封装的企业级框架,对标 Java
  2. 国外远程工作、外包接单、独立开发自己产品
  3. 全栈开发、竞争力提升、学习优秀的架构设计
  4. 后续强类型后端语言统一使用 IDEA 进行开发

基础概念

  1. entity:对应数据库表的实体
  2. handler:控制器里处理路由的方法
  3. controller:控制器,用于处理路由,解析请求参数
  4. service:实现业务逻辑的地方,比如操作数据库等
  5. dto:data transfer object,数据传输对象,可校验数据格式,请求 => dto => 参数 => controller
  6. module:模块,包含 controller、service 等,比如用户模块、书籍模块。其中:
    • controllers 只能被注入,使用 @Controller 声明
    • providers 可以注入也能被注入,使用 @Injectable 声明
    • exports 暴露给外部模块使用
    • 同时也可以使用 imports 注入其他模块逻辑代码
    • 被大量模块依赖时使用 @Global 装饰器,这样不导入也可用
  7. ioc:Inverse of Control,反转控制或者叫依赖注入,只要声明依赖,运行时 Nest 会自动注入依赖的实例
  8. aop:Aspect Oriented Programming 面向切面编程,在多个请求响应流程中可以复用的逻辑,比如日志记录等,具体包含:
    • middleware
    • interceptor
    • guard
    • exception filter
    • pipe

Nest CLI

  • nest new 快速创建项目
    • 这里 eslint 9.0 版本的配置有问题,需要降低为 [email protected]
  • nest generate 快速生成各种代码
    • module
    • controller
    • middleware
    • guard
    • interceptor
    • pipe
    • decorator
    • service
    • resource
    • --no-spec 无测试文件
  • nest build 使用 tsc 或者 webpack 构建代码
  • nest start 启动开发服务,支持 watch 和调试
  • nest info 打印 node、npm、nest 包的依赖版本

HTTP 数据传输

我们用 axios 发送请求,使用 Nest 承接后端服务,实现了 5 种 http/https 的数据传输方式:

  1. 其中前两种是 url 中的:
    • url param:url 中的参数,Nest 中使用 @Param 来取
    • query:url 中 ? 后的字符串,Nest 中使用 @Query 来取
  2. 后三种是 body 中的:
    • form urlencoded:类似 query 字符串,只不过是放在 body 中。Nest 中使用 @Body 来取,axios 中需要指定 content type 为 application/x-www-form-urlencoded,并且对数据用 qs 或者 query-string 库做 url encode`
    • json:json 格式的数据。Nest 中使用 @Body 来取,axios 中不需要单独指定 content type,axios 内部会处理,项目中较为常用
    • form data:通过 ----- 作为 boundary 分隔的数据。主要用于传输文件,Nest 中要使用 FilesInterceptor 来处理其中的 binary 字段,用 @UseInterceptors 来启用,其余字段用 @Body 来取。axios 中需要指定 content type 为 multipart/form-data,并且用 FormData 对象来封装传输的内容。

IOC(依赖注入)

  1. 不需要声明对象的实例,只需要在特定位置注入类,那么 Nest 会按照一定的规则自动进行依赖注入实例:
    1. 构造器注入:写在构造函数的依赖
    2. 属性注入:@Inject 写在属性上的依赖
    3. 两种属性一致,只是注入的时机不同

为什么会出现 IOC ?

javascript
代码解读
复制代码
const config = new Config({ username: 'xxx', password: 'xxx'}); const dataSource = new DataSource(config); const repository = new Repository(dataSource); const service = new Service(repository); const controller = new Controller(service);

  • 上述执行逻辑要按照一定的顺序才能执行最终的代码,人为梳理较为麻烦。
  • IOC 会按照模块的维度 module 实现一个存放对象的容器,扫描带有 @Controller、@Injectable 装饰器的类,这些类根据依赖关系自动注入它所依赖的对象,然后将实例放到容器中。
  • 原本是自己手动创建,但后续改为被动等待 Nest 梳理,这就叫 反转控制。

循环依赖

  1. Bbb 依赖 Aaa,但是 Aaa 又同时依赖 Bbb,此时 Bbb 还未处理完成,导致 Aaa 未完成,此时 Bbb 就是 undefined。imports 和 Provider 的原理一致。
  2. 可以利用 forwardRef 进行包裹,会先创建两个模块,然后再把引用转发给对方。

AOP(切面编程)

  • 跨多个 controller 的逻辑,Nest 封装了通用逻辑,可以在执行过程中复用代码逻辑。
  • 本质:请求的时候进行拦截,先执行公共的代码逻辑,然后放行,可以保持业务逻辑的纯粹性。

Middleware(中间件)

javascript
代码解读
复制代码
app.use(function (req: Request, res: Response, next: NextFunction) { console.log('before request...', req.url); next(); console.log('after request...'); });
  1. 全局中间件、路由中间件
  2. 访问前 => next => 访问后
  3. 全局中间件先触发,路由中间件执行,最后全局中间件结束

Guard(守卫)

javascript
代码解读
复制代码
1. @UseGuards(LoginGuard); 2. app.useGlobalGuards(new LoginGuard());
  1. 穿透全局、路由前置中间件处理规则触发,也就是 next 之前判断,是否放行
  2. 可以单路由声明、全局声明,其中 guard 也可以注入别的模块的执行代码逻辑进行判断

Interceptor(拦截器)

  1. Interceptor 可以拿到调用的 controller 和 handler,与中间件不同。
  2. 也具有单个路由启用、全局路由启用的功能。

Guard VS Interceptor

  • 功能定位:
    • Guard 主要用于权限控制,决定是否允许请求继续执行。
    • Interceptor 主要用于在请求处理前后添加额外逻辑,例如日志记录或响应处理。
  • 执行顺序:
    • Guard 在 Interceptor 之前执行,如果 Guard 返回 false,则后续逻辑(包括 Interceptor 和控制器方法)都不会执行。
  • 应用场景:
    • Guard 适合用于认证和授权。
    • Interceptor 适合用于日志记录、响应格式化等。

总结来说,Guard 和 Interceptor 在 NestJS 中分别用于控制访问权限和处理请求/响应逻辑,它们在请求处理流程中扮演不同的角色,可以根据具体需求选择使用。

Pipe(管道)

javascript
代码解读
复制代码
@Injectable() export class ValidatePipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { if (Number.isNaN(parseInt(value))) { throw new BadRequestException(`参数 ${metadata.data} 不是数字`); } console.log('metadata:', metadata); return typeof value === 'number' ? value * 10 : parseInt(value) * 10; } }
  1. 对参数进行校验和转换
  2. 类型如下:
    • ValidationPipe
    • ParseIntPipe
    • ParseBoolPipe
    • ParseArrayPipe
    • ParseUUIDPipe
    • DefaultValuePipe
    • ParseEnumPipe
    • ParseFloatPipe
    • ParseFilePipe

ExceptionFilter(异常处理)

javascript
代码解读
复制代码
@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, }); } }
  1. 在 Nest 应用抛出异常时,捕获并进行对应的响应。

Nest 内置了很多 HTTP 相关的异常,都是 HttpException 的子类,也可以自己扩展:

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

执行流程

  1. Middleware、Guard、Pipe、Interceptor、ExceptionFilter 都是AOP 思想的实现,只不过是不同执行位置的切面,都可以灵活运用在某个路由或全部路由

  1. 执行成功的回调顺序
  2. 全局中间件 => 子中间件 => 路由守卫(不成功就中断执行)
  3. 中间件执行结束
  4. 请求拦截器 => 处理请求参数 => 判断合理性(不合理中断)=> 相应拦截器
  5. 异常错误处理
    1. throw new BadRequestException 官方配置
    2. ExceptionFilter 自定义错误数据结构
  6. 中间件 next、guard 返回 true/false、interceptor 抛出异常、pipe 参数校验错误、异常处理等都可以一定程度上对程序进行中断操作,具体还要看实际项目。

Provider 类型

javascript
代码解读
复制代码
@Module({ imports: [OtherModule], controllers: [AppController], providers: [ // 1. 通过类注入 AppService, // 2. 自定义token注入 { provide: 'app_service', useClass: AppService, }, // 3. 注入指定值 { provide: 'person1', useValue: { name: 'zhangsan', age: 18 }, }, // 4. 注入动态值 { provide: 'person2', useClass: AppService, useFactory: () => { return { name: 'lisi', age: 20 }; }, }, // 5. 注入其他模块的服务 // 支持异步服务,但会阻塞应用程序的启动 { provide: 'person3', useFactory( person: { name: string; age: number }, appService: AppService, ) { return { name: person.name, age: person.age, msg: appService.getHello(), }; }, inject: ['person1', AppService], }, // 6. 指定别名 { provide: 'person4', useExisting: 'person1', }, ], })

生命周期(支持异步)

程序启动流程

  1. 模块维度下递归解析依赖:
    • 按照顺序进行依赖注入。
  2. 模块初始化:
    • 调用模块内的 controller、provider 的 onModuleInit 方法。
    • 调用模块的 onModuleInit 方法。
  3. 应用启动:
    • 初始化完成,调用模块内的 controller、provider 的 onApplicationBootstrap 方法。
    • 调用模块的 onApplicationBootstrap 方法。
  4. 监听网络端口:
    • 开始处理请求。

程序关闭流程

  1. 模块销毁:
    • 调用每个模块的 controller、provider 的 onModuleDestroy 方法。
    • 调用模块的 onModuleDestroy 方法。

  1. 应用关闭前准备:

    • 调用每个模块的 controller、provider 的 beforeApplicationShutdown 方法。
    • 调用模块的 beforeApplicationShutdown 方法,moduleRef 获取对应的 provider 服务,传递系统信号,可执行必要的清理工作。
  2. 停止监听网络端口:

    • 停止处理请求。
  3. 应用关闭:

    • 调用每个模块的 controller、provider 的 onApplicationShutdown 方法。
    • 调用模块的 onApplicationShutdown 方法。
  4. 停止进程:

    • 完成所有关闭操作,结束程序运行。

装饰器

概念

TypeScript 装饰器:高级编程技巧

使用

javascript
代码解读
复制代码
// 1. 参数装饰器 export const MyQuery = createParamDecorator( (key: string, ctx: ExecutionContext) => { const request: Request = ctx.switchToHttp().getRequest(); return request.headers[key]; }, ); // 2. 装饰器汇总 import { applyDecorators, Get, UseGuards, SetMetadata } from '@nestjs/common'; import { LoginGuard } from './login.guard'; export function Bbb(path, role) { return applyDecorators( Get(path), SetMetadata('bbb', role), UseGuards(LoginGuard), ); }
  • 定义:一种特殊的类型声明,一种方法可以注入 类、属性、方法、参数,以此来扩展程序功能
  • createParamDecorator 创建参数装饰器,通过 ExecutionContext 可以获取 reqeust、response,可以实现很多内置装饰器的功能,比如 @Query、@Headers 等装饰器,applyDecorators 可以把多个装饰器汇总到一起执行

汇总

  • @Module: 声明 Nest 模块
  • @Controller:声明模块里的 controller
  • @Injectable:声明模块里可以注入的 provider
  • @Inject:通过 token 手动指定注入的 provider,token 可以是 class 或者 string
  • @Optional:声明注入的 provider 是可选的,可以为空
  • @Global:声明全局模块
  • @Catch:声明 exception filter 处理的 exception 类型
  • @UseFilters:路由级别使用 exception filter
  • @UsePipes:路由级别使用 pipe
  • @UseInterceptors:路由级别使用 interceptor
  • @SetMetadata:在 class 或者 handler 上添加 metadata
  • @Get、@Post、@Put、@Delete、@Patch、@Options、@Head:声明 get、post、put、delete、patch、options、head 的请求方式
  • @Param:取出 url 中的参数,比如 /aaa/:id 中的 id
  • @Query: 取出 query 部分的参数,比如 /aaa?name=xx 中的 name
  • @Body:取出请求 body,通过 dto class 来接收
  • @Headers:取出某个或全部请求头
  • @Session:取出 session 对象,需要启用 express-session 中间件
  • @HostParm: 取出 host 里的参数
  • @Req、@Request:注入 request 对象
  • @Res、@Response:注入 response 对象,一旦注入了这个 Nest 就不会把返回值作为响应了,除非指定 passthrough 为true
  • @Next:注入调用下一个 handler 的 next 方法
  • @HttpCode: 修改响应的状态码
  • @Header:修改响应头
  • @Redirect:指定重定向的 url
  • @Render:指定渲染用的模版引擎

Reflctor 和 Metadata

javascript
代码解读
复制代码
const obj = { name: 'Alice', age: 25 }; const handler = { get(target, property) { if (property in target) { return Reflect.get(target, property); } return 'Property does not exist'; }, set(target, property, value) { if (property === 'age' && value < 0) { throw new Error('Age cannot be negative'); } return Reflect.set(target, property, value); }, deleteProperty(target, property) { if (property === 'name') { return false; // 不允许删除 'name' 属性 } return Reflect.deleteProperty(target, property); } }; const proxy = new Proxy(obj, handler); console.log(proxy.name); // 输出:Alice console.log(proxy.gender); // 输出:Property does not exist proxy.age = -10; // 抛出错误:Age cannot be negative Reflect.deleteProperty(proxy, 'name'); // 返回 false,不允许删除 'name' 属性
  1. Reflector 针对对象属性的获取、删除、修改,可以操作原型、函数、构造函数、扩展等行为,Proxy 实现 handler 对属性 get、set、delete 的方法拦截,具体处理还是由 Reflector 完成。

  2. metadata 在类、方法、属性等上定义(添加)元数据。这个方法允许你在运行时为对象或其属性附加额外的信息,而不改变其数据结构。Nest 运行的时候根据元数据来实现依赖的扫描和对象的创建。

  3. 详细讲解 Reflector VS Proxy 之间的关系(文章待出......)

深入浅出Typescript装饰器与Reflect元数据本文正在参加「金石计划」 装饰器是什么? 在js中,其实装饰器是 - 掘金

ExecutionContext

    1. 上述两个类可以校验不同场景下的服务,如:http、wx、rpc 等。
  1. ExecutionContext 多了获取类和方法是因为需要进行 metadata 的权限认知。

动态模块

场景:引入外部的 module 时,需要动态传递一些参数 options

  • register:用一次注册一次
  • forRoot:注册一次,用多次,在 AppModule 引入
  • forFeature:在 forRoot 的基础上,可以用 forFeature 传入局部配置,一般在具体模块下 imports

RxJS 库

RxJS:组织异步逻辑的库,observable 数据源产生数据后,经过系列的 operator 简化异步逻辑的编写。

  • tap:不修改响应数据,执行一些额外逻辑,比如记录日志、更新缓存等。
  • map:对响应数据做修改,一般都是改成 {code, data, message} 的格式。
  • catchError:在 exception filter 之前处理抛出的异常,可以记录或者抛出别的异常。
  • timeout:处理响应超时的情况,抛出一个 TimeoutError,配合 catchError 可以返回超时的响应。
vue
代码解读
复制代码
import { Contains, IsDate, IsEmail, IsFQDN, IsInt, Length, Max, Min } from 'class-validator'; export class Ppp { @Length(10, 20) title: string; @Contains('hello') text: string; @IsInt() @Min(0) @Max(10) rating: number; @IsEmail() email: string; @IsFQDN() site: string; }

DTO 数据校验

  • class-transformer 将普通对象转为 class 实例。
  • class-validator 包提供基于装饰器声明的规则进行对象校验。
  • 声明参数类型 DTO 类 => pipe 获取类 => 把 POST 请求的参数对象通过 class-transformer 转为实例 => class-validator 进行校验。

核心概念总结

  1. IOC、AOP、全局模块、动态模块、自定义 provider、生命周期等概念。
  2. middleware 适合通用的处理流程,interceptor 适合处理与具体业务相关的逻辑,例如:从 ExecutionContext 获取目标 class 和 handler、通过 reflector 获取 metadata 信息、组织响应处理流程。
注:本文转载自juejin.cn的知心宝贝的文章"https://juejin.cn/post/7493433637829820426"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

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

分类栏目

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

热门文章

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