Nestjs 最佳实践教程:5 Nodejs 自动验证,序列化与异常处理
Nestjs 最佳实践教程:5 Nodejs 自动验证,序列化与异常处理
- 教程地址: https://v.pincman.com/courses/64.html
- 视频地址: https://www.bilibili.com/video/BV1j34y1H7MA?spm_id_from=333.999.0.0
- qq: 1849600177
- qq 群: 455820533
注意: 此处文档只起配合作用,为了您的身心愉悦,请看 B 站视频教程,不要直接略过视频直接看这个文档,这样你将什么都看不到😄
另,本人在找工作中,希望能有远程工作匹配(无法去外地),有需要的老板可以看一下我的个人介绍: https://pincman.com/about
学习目标
- 全局自动数据验证管道
- 全局数据序列化拦截器
- 全局异常处理过滤器
文件结构
本节内容主要聚焦于CoreModule
src/core
├── constants.ts
├── core.module.ts
├── decorators
│ ├── dto-validation.decorator.ts
│ └── index.ts
├── helpers.ts
├── index.ts
├── providers
│ ├── app.filter.ts
│ ├── app.interceptor.ts
│ ├── app.pipe.ts
│ └── index.ts
└── types.ts
应用编码
本节中用到一个新的Typescript
知识点-自定义装饰器和matedata
,详细使用请查看我写的一篇相关文章
装饰器
添加一个用于为Dto
构造metadata
数据的装饰器
// src/core/decorators/dto-validation.decorator.ts
export const DtoValidation = (
options?: ValidatorOptions & {
transformOptions?: ClassTransformOptions;
} & { type?: Paramtype },
) => SetMetadata(DTO_VALIDATION_OPTIONS, options ?? {});
验证管道
自定义一个全局的验证管道(继承自Nestjs
自带的ValidationPipe
管道)
代码: src/core/providers/app.pipe.ts
大致验证流程如下
- 获取要验证的 dto 类
- 获取
Dto
自定义的matadata
数据(通过上面的装饰器定义) - 合并默认验证选项(通过在
CoreModule
注册管道时定义)与matadata
- 根据 DTO 类上设置的 type 来设置当前的 DTO 请求类型('body' | 'query' | 'param' | 'custom')
- 如果被验证的 DTO 设置的请求类型与被验证的数据的请求类型不是同一种类型则跳过此管道
- 合并当前 transform 选项和自定义选项(验证后的数据使用 class-transfomer`序列化)
- 如果 dto 类的中存在 transform 静态方法,则返回调用进一步 transform 之后的结果
- 重置验证选项和 transform 选项为默认
序列化拦截器
默认的序列化拦截器是无法对分页数据进行处理的,所以自定义的全局序列化拦截器类重写serialize
方法,以便对分页数据进行拦截并序列化
// src/core/providers/app.interceptor.ts
serialize(
response: PlainLiteralObject | Array<PlainLiteralObject>,
options: ClassTransformOptions,
): PlainLiteralObject | PlainLiteralObject[] {
const isArray = Array.isArray(response);
if (!isObject(response) && !isArray) return response;
// 如果是响应数据是数组,则遍历对每一项进行序列化
if (isArray) {
return (response as PlainLiteralObject[]).map((item) =>
this.transformToPlain(item, options),
);
}
// 如果是分页数据,则对 items 中的每一项进行序列化
if (
'meta' in response &&
'items' in response &&
Array.isArray(response.items)
) {
return {
...response,
items: (response.items as PlainLiteralObject[]).map((item) =>
this.transformToPlain(item, options),
),
};
}
// 如果响应是个对象则直接序列化
return this.transformToPlain(response, options);
}
异常处理过滤器
Typeorm 在找不到模型数据时会抛出EntityNotFound
的异常,而此异常不会被捕获进行处理,以至于直接抛出500
错误,一般在数据找不到时我们需要抛出的是404
异常,所以需要定义一个全局异常处理的过滤器来进行捕获并处理.
全局的异常处理过滤器继承自 Nestjs 自带的BaseExceptionFilter
,在自定义的类中定义一个对象属性,并复写catch
方法以根据此属性中不同的异常进行判断处理
// src/core/providers/app.filter.ts
protected resExceptions: Array<
{ class: Type<Error>; status?: number } | Type<Error>
> = [{ class: EntityNotFoundError, status: HttpStatus.NOT_FOUND }];
catch(exception: T, host: ArgumentsHost) {...}
注册全局
在CoreModule
中分别为全局的验证管道,序列化拦截器和异常处理过滤器进行注册
在注册全局管道验证时传入默认参数
// src/core/core.module.ts
providers: [
{
provide: APP_PIPE,
useFactory: () =>
new AppPipe({
transform: true,
forbidUnknownValues: true,
validationError: { target: false },
}),
},
{
provide: APP_FILTER,
useClass: AppFilter,
},
{
provide: APP_INTERCEPTOR,
useClass: AppIntercepter,
},
],
})
逻辑代码
- 对于验证器需要修改
Dto
和Controller
- 对于拦截器需要修改
Entity
和Controller
- 对于过滤器需要修改
Service
自动序列化
以PostEntity
为例,比如在显示文章列表数据的时候为了减少数据量不需要显示body
内容,而单独访问一篇文章的时候则需要,这时候可以添加添加一个序列化组post-detail
,而为了确定每个模型的字段在读取数据时只显示我们需要的,所以在类前添加一个@Exclude
装饰器
对于对象类型需要通过
@Type
装饰器的字段转义
示例
// src/modules/content/entities/post.entity.ts
...
@Expose()
@Type(() => Date)
@CreateDateColumn({
comment: '创建时间',
})
createdAt!: Date;
@Expose()
@Type(() => CategoryEntity)
@ManyToMany((type) => CategoryEntity, (category) => category.posts, {
cascade: true,
})
@JoinTable()
categories!: CategoryEntity[];
@Expose({ groups: ['post-detail'] })
@Column({ comment: '文章内容', type: 'longtext' })
body!: string;
然后可以在在控制器中针对有特殊配置的序列化添加@SerializeOptions
装饰器,如序列化组
示例
// src/modules/content/controllers/post.controller.ts
...
@Get(':post')
@SerializeOptions({ groups: ['post-detail'] })
async show(
@Param('post', new ParseUUIDEntityPipe(PostEntity))
post: string,
) {
return this.postService.detail(post);
}
自动验证
为了代码简洁,把所有针对同一模型的DTO
类全部放入一个文件,于是有了以下 2 个dto
文件
src/modules/content/dtos/category.dto.ts
src/modules/content/dtos/post.dto.ts
为dto
文件中需要传入自定义验证参数的类添加@DtoValidation
装饰器,比如@DtoValidation({ groups: ['create'] })
注意的是默认的paramType
为body
,所以对于query
,需要额外加上type: 'query'
示例
// src/modules/content/dtos/category.dto.ts
@Injectable()
@DtoValidation({ type: 'query' })
export class QueryCategoryDto implements PaginateDto {
...
}
现在可以在控制器中删除所有的new ValidatePipe(...)
代码了,因为全局验证管道会自行处理
自动处理异常
现在把服务中的findOne
等查询全部改成findOneOrFail
等,把抛出的NotFoundError
这些异常去除就可以在 typeorm 抛出默认的EntityNotFound
异常时就会响应404
示例
// src/modules/content/services/post.service.ts
async findOne(id: string) {
const query = await this.getItemQuery();
const item = await query.where('post.id = :id', { id }).getOne();
if (!item)
throw new EntityNotFoundError(PostEntity, `Post ${id} not exists!`);
return item;
}
。。。
在NestJS中,自动验证、序列化和异常处理是构建健壮应用程序的关键部分。以下是一些最佳实践及其代码示例:
自动验证
使用类验证器(class-validator)库,可以在DTO(数据传输对象)中定义验证规则。
import { IsString, IsInt, Min } from 'class-validator';
export class CreateUserDto {
@IsString()
readonly name: string;
@IsInt()
@Min(18)
readonly age: number;
}
序列化
使用类转换器(class-transformer)库,可以自动将对象转换为响应格式,避免暴露敏感数据。
import { Exclude } from 'class-transformer';
export class User {
id: number;
@Exclude()
password: string;
name: string;
}
异常处理
创建全局异常过滤器,统一处理API错误响应。
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
response.status(status).json({
statusCode: status,
message: exception.message || 'Internal server error',
});
}
}
将全局异常过滤器应用到应用程序中:
app.useGlobalFilters(new AllExceptionsFilter());
这些实践有助于确保NestJS应用程序的健壮性、安全性和可维护性。