当前位置: 首页 > 后端技术 > Node.js

Nestjs最佳实践教程:5个自动验证、序列化和异常处理

时间:2023-04-03 19:05:32 Node.js

视频地址:https://www.bilibili.com/video...如有疑问,请扫描视频中的QQ群二维码进行交流。另外,我正在找工作,希望有一个远程工作匹配(不能去其他地方)。有有需要的老大可以看看我的个人介绍:pincman.com/aboutLearningtarget全局自动数据验证管道全局数据序列化拦截器全局异常处理过滤器文件结构本节主要关注CoreModulesrc/core├──constants。ts├──core.module.ts├──装饰器│├──dto-validation.decorator.ts│└──index.ts├──helpers.ts├──index.ts├──providers│├──app.filter.ts│├──app.interceptor.ts│├──app.pipe.ts│└──index.ts└──types.tsapplicationcoding本节使用一个新的Typescript知识点-Customdecorators还有matedata,具体可以参考我写的一篇相关文章。装饰器添加一个装饰器,用于为Dto//src/core/decorators/dto-validation.decorator.tsexportconstDtoValidation=(options?:ValidatorOptions&{transformOptions?:ClassTransformOptions;}&{type?:Paramtype},)=>SetMetadata(DTO_VALIDATION_OPTIONS,选项??{});Validationpipeline自定义一个全局的validationpipeline(继承自NestjsValidationPipewithpipeline)代码:src/core/providers/app.pipe.ts大概的验证流程如下获取待验证的dto类获取Dto自定义的matadata数据(定义通过上面的装饰器)合并默认验证选项(在CoreModule注册管道时传入Defined)和matadata根据DTO类上设置的类型设置当前DTO请求类型('bo迪'|'查询'|'参数'|'custom')如果已验证的DTO设置的请求类型与已验证的数据请求类型不同,则跳过此管道并合并当前转换选项和自定义选项(已验证数据使用class-transfomer序列化`)如果dto类中有一个transform静态方法,返回进一步调用transform后的结果。重置验证选项和转换选项是默认的序列化拦截器。默认的序列化拦截器是无法处理分页数据的,所以自定义全局序列化拦截器类重写serialize方法对分页数据进行拦截和序列化//src/core/providers/app.interceptor.tsserialize(response:PlainLiteralObject|Array,选项:ClassTransformOptions,):PlainLiteralObject|PlainLiteralObject[]{constisArray=Array.isArray(响应);如果(!isObject(响应)&&!isArray)返回响应;//如果响应数据是一个数组,则遍历并序列化每个项目}//如果是分页数据,则序列化items中的每一项if('meta'inresponse&&'items'inresponse&&Array.isArray(responsese.items)){return{...response,items:(response.itemsasPlainLiteralObject[]).map((item)=>this.transformToPlain(item,options),),};}//如果response是对象,则直接序列化returnthis.transformToPlain(response,options);}异常处理过滤器Typeorm在找不到模型数据的时候会抛出一个EntityNotFound异常,而这个异常不会被捕获和处理,这样会直接抛出500错误。一般当找不到数据的时候,我们需要抛出的是404异常,所以我们需要定义一个全局的异常处理过滤器来捕获和处理。全局异常处理过滤器继承自Nestjs自带的BaseExceptionFilter。在自定义类中定义一个对象属性,重写catch方法根据该属性中的不同异常进行判断处理//src/core/providers/app.filter.tsprotectedresExceptions:Array<{class:Type;状态?:数字}|Type>=[{class:EntityNotFoundError,status:HttpStatus.NOT_FOUND}];catch(exception:T,host:ArgumentsHost){...}在CoreModule中分别注册globals验证管道,序列化拦截器和异常处理过滤器注册全局管道验证时传入默认参数//src/core/core.module.tsproviders:[{provide:APP_PIPE,useFactory:()=>newAppPipe({transform:true,forbidUnknownValues:true,validationError:{target:false},}),},{provide:APP_FILTER,useClass:AppFilter,},{provide:APP_INTERCEPTOR,useClass:AppInterceptor,},],})逻辑代码需要为validator修改Dto和Controller。它需要修改拦截器的实体和控制器。它需要修改过滤器的服务。自动序列化以PostEntity为例,例如在展示文章列表数据时为了减少数据量,不需要展示正文内容,但单独访问一篇文章时需要。这时候可以添加一个序列化组post-detail,并且为了保证每个model的字段在读取数据的时候只显示我们需要的,所以在class之前添加一个@Exclude装饰器。对于对象类型,需要通过@Type装饰器的字段被转义。示例//src/modules/content/entities/post.entity.ts...@Expose()@Type(()=>Date)@CreateDateColumn({comment:'Creationtime',})createdAt!:Date;@Expose()@Type(()=>CategoryEntity)@ManyToMany((type)=>CategoryEntity,(category)=>category.posts,{cascade:true,})@JoinTable()类别!:类别实体[];@Expose({groups:['post-detail']})@Column({comment:'articlecontent',type:'longtext'})body!:string;然后可以在controller中添加@SerializeOptions装饰器,用于controller中特殊配置的序列化,比如序列化组example//src/modules/content/controllers/post.controller.ts...@Get(':post')@SerializeOptions({groups:['post-detail']})asyncshow(@Param('post',newParseUUIDentityPipe(PostEntity))post:string,){returnthis.postService.detail(post);自动验证代码简洁性,将同一模型的所有DTO类放在一个文件中,因此以下2个dto文件src/modules/content/dtos/category.dto.tssrc/modules/content/dtos/post.dto。ts为dto文件,在需要传入自定义验证参数的类中添加@DtoValidation装饰器,如@DtoValidation({groups:['create']})注意paramType默认为body,所以对于query,需要添加type:'query'example//src/modules/content/dtos/category.dto.ts@Injectable()@DtoValidation({type:'query'})exportclassQueryCategoryDtoimplementsPaginateDto{...}现在可以删除在controllers中所有的newValidatePipe(...)代码都没有了,因为全局验证管道会自己自动处理异常现在将服务中的findOne和其他查询全部改成findOneOrFail等,去掉NotFoundError抛出的异常,当typeorm抛出默认的EntityNotFound异常时,会响应404Example//src/modules/content/services/post.service.tsasyncfindOne(id:string){constquery=awaitthis.getItemQuery();constitem=awaitquery.where('post.id=:id',{id}).getOne();if(!item)thrownewEntityNotFoundError(PostEntity,`Post${id}不存在!`);归还物品;}