前言

bosombaby.blog.csdn.net/article/det...
Express
进一步封装的企业级框架,对标Java
- 国外远程工作、外包接单、独立开发自己产品
- 全栈开发、竞争力提升、学习优秀的架构设计
- 后续强类型后端语言统一使用
IDEA
进行开发
基础概念

- entity:对应数据库表的实体
- handler:控制器里处理路由的方法
- controller:控制器,用于处理路由,解析请求参数
- service:实现业务逻辑的地方,比如操作数据库等
- dto :
data transfer object
,数据传输对象,可校验数据格式,请求 => dto => 参数 => controller - module :模块,包含
controller
、service
等,比如用户模块、书籍模块。其中:- controllers 只能被注入,使用
@Controller
声明 - providers 可以注入也能被注入,使用
@Injectable
声明 - exports 暴露给外部模块使用
- 同时也可以使用
imports
注入其他模块逻辑代码 - 被大量模块依赖时使用
@Global
装饰器,这样不导入也可用
- controllers 只能被注入,使用
- ioc :
Inverse of Control
,反转控制或者叫依赖注入,只要声明依赖,运行时 Nest 会自动注入依赖的实例 - 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
的数据传输方式:
- 其中前两种是
url
中的:- url param :
url
中的参数,Nest
中使用@Param
来取 - query :
url
中?
后的字符串,Nest
中使用@Query
来取
- url param :
- 后三种是
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
对象来封装传输的内容。
- form urlencoded :类似
IOC(依赖注入)

- 不需要声明对象的实例,只需要在特定位置注入类,那么
Nest
会按照一定的规则自动进行依赖注入实例:- 构造器注入:写在构造函数的依赖
- 属性注入:
@Inject
写在属性上的依赖 - 两种属性一致,只是注入的时机不同
为什么会出现 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
梳理,这就叫 反转控制。
循环依赖


Bbb
依赖Aaa
,但是Aaa
又同时依赖Bbb
,此时Bbb
还未处理完成,导致Aaa
未完成,此时Bbb
就是undefined
。imports
和Provider
的原理一致。- 可以利用
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...');
});
- 全局中间件、路由中间件
- 访问前 =>
next
=> 访问后 - 全局中间件先触发,路由中间件执行,最后全局中间件结束
Guard(守卫)

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

Interceptor
可以拿到调用的controller
和handler
,与中间件不同。- 也具有单个路由启用、全局路由启用的功能。
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;
}
}
- 对参数进行校验和转换
- 类型如下:
- 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,
});
}
}
- 在
Nest
应用抛出异常时,捕获并进行对应的响应。
Nest
内置了很多 HTTP
相关的异常,都是 HttpException
的子类,也可以自己扩展:
- BadRequestException
- UnauthorizedException
- NotFoundException
- ForbiddenException
- NotAcceptableException
- RequestTimeoutException
- ConflictException
- GoneException
- PayloadTooLargeException
- UnsupportedMediaTypeException
- UnprocessableException
- InternalServerErrorException
- NotImplementedException
- BadGatewayException
- ServiceUnavailableException
- GatewayTimeoutException
执行流程

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


- 执行成功的回调顺序
- 全局中间件 => 子中间件 => 路由守卫(不成功就中断执行)
- 中间件执行结束
- 请求拦截器 => 处理请求参数 => 判断合理性(不合理中断)=> 相应拦截器
- 异常错误处理
throw new BadRequestException
官方配置ExceptionFilter
自定义错误数据结构
- 中间件
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',
},
],
})
生命周期(支持异步)

程序启动流程
- 模块维度下递归解析依赖 :
- 按照顺序进行依赖注入。
- 模块初始化 :
- 调用模块内的
controller
、provider
的onModuleInit
方法。 - 调用模块的
onModuleInit
方法。
- 调用模块内的
- 应用启动 :
- 初始化完成,调用模块内的
controller
、provider
的onApplicationBootstrap
方法。 - 调用模块的
onApplicationBootstrap
方法。
- 初始化完成,调用模块内的
- 监听网络端口 :
- 开始处理请求。
程序关闭流程
- 模块销毁 :
- 调用每个模块的
controller
、provider
的onModuleDestroy
方法。 - 调用模块的
onModuleDestroy
方法。
- 调用每个模块的


-
应用关闭前准备:
- 调用每个模块的
controller
、provider
的beforeApplicationShutdown
方法。 - 调用模块的
beforeApplicationShutdown
方法,moduleRef
获取对应的provider
服务,传递系统信号,可执行必要的清理工作。
- 调用每个模块的
-
停止监听网络端口:
- 停止处理请求。
-
应用关闭:
- 调用每个模块的
controller
、provider
的onApplicationShutdown
方法。 - 调用模块的
onApplicationShutdown
方法。
- 调用每个模块的
-
停止进程:
- 完成所有关闭操作,结束程序运行。
装饰器
概念
使用
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' 属性
-
Reflector
针对对象属性的获取、删除、修改,可以操作原型、函数、构造函数、扩展等行为,Proxy
实现handler
对属性get
、set
、delete
的方法拦截,具体处理还是由Reflector
完成。 -
metadata
在类、方法、属性等上定义(添加)元数据。这个方法允许你在运行时为对象或其属性附加额外的信息,而不改变其数据结构。Nest 运行的时候根据元数据来实现依赖的扫描和对象的创建。 -
详细讲解
Reflector
VSProxy
之间的关系(文章待出......)
深入浅出Typescript装饰器与Reflect元数据本文正在参加「金石计划」 装饰器是什么? 在js中,其实装饰器是 - 掘金
ExecutionContext

-
- 上述两个类可以校验不同场景下的服务,如:http、wx、rpc 等。
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
进行校验。
核心概念总结
IOC
、AOP
、全局模块、动态模块、自定义provider
、生命周期等概念。middleware
适合通用的处理流程,interceptor
适合处理与具体业务相关的逻辑,例如:从ExecutionContext
获取目标class
和handler
、通过reflector
获取metadata
信息、组织响应处理流程。