乐趣区

关于typescript:Nestjs最佳实践教程5自动验证序列化与异常处理

  • 视频地址: https://www.bilibili.com/vide…

有问题请扫描视频中 qq 群二维码交换

另,自己在找工作中,心愿能有近程工作匹配(无奈去当地),有须要的老板能够看一下我的集体介绍: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

大抵验证流程如下

  1. 获取要验证的 dto 类
  2. 获取 Dto 自定义的 matadata 数据(通过下面的装璜器定义)
  3. 合并默认验证选项 (通过在CoreModule 注册管道时定义)与matadata
  4. 依据 DTO 类上设置的 type 来设置以后的 DTO 申请类型(‘body’ | ‘query’ | ‘param’ | ‘custom’)
  5. 如果被验证的 DTO 设置的申请类型与被验证的数据的申请类型不是同一种类型则跳过此管道
  6. 合并以后 transform 选项和自定义选项(验证后的数据应用 class-transfomer` 序列化)
  7. 如果 dto 类的中存在 transform 静态方法, 则返回调用进一步 transform 之后的后果
  8. 重置验证选项和 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,
        },
    ],
})

逻辑代码

  • 对于验证器须要批改 DtoController
  • 对于拦截器须要批改 EntityController
  • 对于过滤器须要批改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'] })

留神的是默认的 paramTypebody, 所以对于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;
    }
退出移动版