nestjs搭建通用业务框架(3):核心概念

这是《nestjs搭建通用业务框架》系列的第3篇,主要是介绍nestjs中的核心概念,理解这些概念与使用方法是今后学习上的垫脚石。PS: 对于本篇的学习,你需要有一定的TS的基础,理解起来才会比较顺畅。

本章的内容不是随意的复制、粘贴官方的译本,这样没有任何学习的价值。所有的内容经过个人的整理,也算是知识的输出,大家且学且手下留情。

其中比较重要的内容:装饰器、装饰器 - 依赖注入,在依赖注入部分因为概念比较难理解,所以后面打算单独再写一篇关于依赖注入(设计模式)的博文!欢迎订阅RSS!

总览概念

先不废话,给大家总结了一张基础概念的思维导图:

核心概念

大家可以把上面的图,当成是平时查阅概念的一个索引,我们把相关的内容进行了连接与分层,方便大家学习。

装饰器

这是nestjs(Angular)学习中非常!非常!非常重要的一部分!可以说是nestjs借用ES高级特性的一个灵魂~,我们将会从以下几个概念出发来进行学习。

在下面的学习中,不乏会有一些代码的内容,看不懂没有关系,先读每一部分的前置的介绍,然后TS代码的内容,主要是了解基础的应用与该装饰器的作用即可。

控制器

控制器负责处理传入的 请求 和向客户端返回 响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { Controller, Get } from '@nestjs/common';

@Controller('cats') // Controller - 控制器的装饰器, cats - 路由,或者说是请求的一个前缀,类似于 /api/user /api/test ... 中的/api
export class CatsController {
@Get('ab*cd') // Get - 请求的类型的装饰器
findAll(@Req() request: Request): string { // 具体对应上面请求的响应函数
return 'This action returns all cats';
}

@Post()
@HttpCode(204) // http状态码
@Header('Cache-Control', 'none') // 响应头
create() {
return 'This action adds a new cat';
}

@Get('docs')
@Redirect('https://docs.nestjs.com', 302) // 重定向
getDocs(@Query('version') version) {
if (version && version === '5') {
return { url: 'https://docs.nestjs.com/v5/' };
}
}

@Get(':id') // 参数 - 路径传参使用Param,url传参使用Query,post传参使用Body
findOne(@Param() params): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
}

使用如下命令:

1
nest g controller [file-name]

来创建一个标准的控制器文件。

  • 路由 - @Controller

  • Request - 如下表:

    了解Express的同学应该知道,请求对象req与响应对象res

    装饰器 对应的Express中的对象或者用法
    @Request() req
    @Response() @Res()* res
    @Next() next
    @Session() req.session
    @Param(key?: string) req.params / req.params[key]
    @Body(key?: string) req.body / req.body[key]
    @Query(key?: string) req.query / req.query[key]
    @Headers(name?: string) req.headers / req.headers[name]
    @Ip() req.ip
  • 资源(请求方法)- @Put()@Delete()@Patch()@Options()@Head()@All()。这些表示各自的 HTTP请求方法。

  • 路由通配符 - 设置在上面的资源中的正则表达式,字符 ?+* 以及 () 是它们的正则表达式对应项的子集。连字符 (-) 和点 (.) 按字符串路径解析。如:@Get('ab*cd')会匹配成abcdab_cdabecd等。

  • 状态码 - @HttpCode()

  • 响应头 - @Header

提供者

提供者是nest借用Angular的设计模式引入的一个概念。

Providers 是 Nest 的一个基本概念,许多基本的 Nest 类可能被视为 provider - service, repository, factory, helper 等等,他们都可以通过 constructor 注入依赖关系。

providers就是在代码组织上抽象的一层存放公共代码的部分,在程序内部交由框架来处理依赖关系,通过providers@Injectable()注解可以自由的使用这些公共的代码中的逻辑。

服务

这张图很好的解释了@Injectable()的作用:

解释:

一个ServiceA被加上@Injectable()的注解之后,就变成了一个可以被其他Component在构造器部分引用的参数,在nest的提供者providers:[]中需要添加对应的ServiceA,如:providers: [ ServiceA ],以便框架自动来处理依赖关系,并进行必要的实例化。

用法:

1
nest generate service [service-name]

依赖注入(重要)

主要有三类具体的应用:

  • 管道 - 实现数据的转换与验证
  • 守卫 - 实现流程控制,鉴权管理
  • 拦截器 - 绑定函数执行前后的处理函数,如:请求&响应拦截器、错误统一处理、缓存等

这三类全是@Injectable()修饰的,官方在架构上提供的现成的工具类,管道应实现 PipeTransform 接口,守卫应该实现 CanActivate 接口,拦截器应该实现 NestInterceptor 接口。

要理解依赖注入,要先看个例子:我们希望在通知组件(NotificationComponent)中通过消息服务(MessageService)发送一条消息。

如果不使用依赖注入的话,我们的代码大概长这样:

1
2
3
4
5
6
7
8
9
class NotificationComponent {
msg: MessageService;
constructor() {
this.msg = new MessageService();
}
sendMsg(msgType: string, info: string) {
this.msg.send(msgType, info);
}
}

使用依赖注入时:

1
2
3
4
5
6
class NotificationComponent {
constructor(msg: MessageService) {} // Angular 中注入依赖的方式
sendMsg(msgType: string, info: string) {
this.msg.send(msgType, info);
}
}

经过对比,可以看到使用依赖注入有两个很显然的优点:

  • 代码的行数变少了
  • NotificationComponent 与 MessageService 间的耦合性降低了

大家先可以去了解一下IoC(Inversion of Control)控制反转,简单来说它和依赖注入(Dependency Injection)间的区别就是:

  • 依赖注入是一种编程技巧
  • 控制反转是一种设计思想

依赖注入就是不通过 new 这种方式来在类(NotificationComponent)的内部创建所依赖类(MessageService)的对象,而是在外部创建好需要依赖的类对象之后通过构造函数等方式注入进来就可以了。

控制反转中,”控制“是指对程序流程的控制,”反转“则是将控制权从程序员的手里反转到了外层框架。

  1. 管道(Pipe)

    管道有两个作用:

    • 转换:管道将输入数据转换为所需的数据输出
    • 验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常;

    按照作用分的类型:内置管道、结构验证、类验证、转换管道

  2. 守卫(Guard)

    守卫有一个单独的责任,它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。 这通常称为授权。

  3. 拦截器(Interceptor)

    拦截器具有一系列有用的功能,这些功能受面向切面编程(AOP)技术的启发。它们可以:

    • 在函数执行之前/之后绑定额外的逻辑
    • 转换从函数返回的结果
    • 转换从函数抛出的异常
    • 扩展基本函数行为
    • 根据所选条件完全重写函数 (例如, 缓存目的)

模块

模块化的概念无处不在,在nest中,模块是应用程序的基础的组成部分,如下图:

具体特点:

  • 模块是具有 @Module() 装饰器的类。

  • 每个 Nest 应用程序至少有一个模块,即根模块。根模块可能是应用程序中唯一的模块,特别是当应用程序很小时,但是对于大型程序来说这是没有意义的。

  • 在大多数情况下,您将拥有多个模块,每个模块都有一组紧密相关的属性,如下表:

@module() 装饰器接受一个描述模块属性的对象:

属性名 解释
providers 由 Nest 注入器实例化的提供者,并且可以至少在整个模块中共享
controllers 必须创建的一组控制器
imports 导入模块的列表,这些模块导出了此模块中所需提供者
exports 由本模块提供并应在其他模块中可用的提供者的子集。

中间件

中间件是在路由处理程序 之前 调用的函数,实现 NestMiddleware 接口。

Nest 中间件实际上等价于 express 中间件。 下面是Express官方文档中所述的中间件功能:

中间件函数可以执行以下任务:

  • 执行任何代码。
  • 对请求和响应对象进行更改。
  • 结束请求-响应周期。
  • 调用堆栈中的下一个中间件函数。
  • 如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数。否则, 请求将被挂起。

异常过滤器

内置的异常层负责处理整个应用程序中的所有抛出的异常。当捕获到未处理的异常时,最终用户将收到友好的响应。

开箱即用,此操作由内置的全局异常过滤器执行,该过滤器处理类型 HttpException(及其子类)的异常。每个发生的异常都由全局异常过滤器处理, 当这个异常无法被识别时 (既不是 HttpException 也不是继承的类 HttpException ) , 用户将收到以下 JSON 响应:

1
2
3
4
{
"statusCode": 500,
"message": "Internal server error"
}

小结

通过本篇的介绍,大家应该有如下的感受:

  • nest大量使用了注解来简化逻辑
  • nest的程序设计(架构)很丰富,解耦&易用。解耦不用说,从上面的分层就能看出来;易用,主要体现在语义化关键词、强大的CLI命令。

核心的概念:模块 -> 控制器 -> 服务、管道、守卫、拦截器 -> 中间件 -> 异常过滤器。

参考资料

推荐一些非官方的,用于去理解“依赖注入”的资料: