Nest.js参数校验和自定义返回数据格式详解


Posted in Javascript onMarch 29, 2021

0x0 参数校验

参数校验大部分业务是使用 Nest.js 中的管道 方法实现,具体可以查阅文档 。不过编写过程中遇到一些问题,虽然文档讲得比较晦涩。

在做个查询接口,里面包含一些参数,做成 dto 结构数据:

import { ApiProperty } from '@nestjs/swagger'
 
export class QueryUserDto {
 @ApiProperty({
 required: false,
 description: '页码'
 })
 readonly currentPage: number
 
 @ApiProperty({
 required: false,
 description: '条数'
 })
 readonly pageSize: number
 
 @ApiProperty({
 required: false,
 description: '用户账号'
 })
 readonly username?: string
 
 @ApiProperty({
 required: false,
 description: '用户状态'
 })
 readonly activeStatus: number
 
 @ApiProperty({
 required: false,
 description: '排序的方式: ASC, DESC'
 })
 readonly order: 'DESC' | 'ASC'
}
 TYPESCRIPT

在 @Query 请求传入对应的参数,发现得到的数据类型都是 String ,然后查阅相关文档才明白还需要 class-transformer 的 Type 进行转换:

import { ApiProperty } from '@nestjs/swagger'
import { Type } from 'class-transformer'
 
export class QueryUserDto {
 @ApiProperty({
 required: false,
 description: '页码'
 })
 @Type(() => Number)
 readonly currentPage: number = 1
 
 @ApiProperty({
 required: false,
 description: '条数'
 })
 @Type(() => Number)
 readonly pageSize: number = 10
 
 @ApiProperty({
 required: false,
 description: '用户账号'
 })
 readonly username?: string
 
 @ApiProperty({
 required: false,
 description: '用户状态'
 })
 @Type(() => Number)
 readonly activeStatus: number = 3
 
 @ApiProperty({
 required: false,
 description: '排序的方式: ASC, DESC'
 })
 readonly order: 'DESC' | 'ASC' = 'DESC'
}

然后在 ValidationPipe 管道方法里开启 transform 选项:

app.useGlobalPipes(
 new ValidationPipe({
 transform: true
 })
)

或者在 app.modules.ts 注入:

import { ValidationPipe } from '@nestjs/common'
import { APP_PIPE } from '@nestjs/core'
 
@Module({
 imports: [
 // ...
 ],
 controllers: [AppController],
 providers: [
 {
  provide: APP_PIPE,
  useValue: new ValidationPipe({
  transform: true
  })
 }
 ]
})

俩者使用方法区别于程序的是否混合应用类型。

我这边为了省事直接写在全局方法里,最终到 service 拿到的数据就是经过管道业务处理过的数据,不需要在 service 层进行大量的数据类型判断。

0x1 自定义返回数据格式

在 controller 返回的数据都是从数据库表结构而来:

{
 "id": "d8d5a56c-ee9f-4e41-be48-5414a7a5712c",
 "username": "Akeem.Cremin",
 "password": "$2b$10$kRcsmN6ewFC2GOs0TEg6TuvDbNzf1VGCbQf2fI1UeyPAiZCq9rMKm",
 "email": "Garrett87@hotmail.com",
 "nickname": "Wallace Nicolas",
 "role": "user",
 "isActive": true,
 "createdTime": "2021-03-24T15:24:26.806Z",
 "updatedTime": "2021-03-24T15:24:26.806Z"
}

如果需要定义最终返回接口的数据格式例如:

{
 "statusCode": 200,
 "message": "获取成功",
 "data": {
  "id": "d8d5a56c-ee9f-4e41-be48-5414a7a5712c",
  "username": "Akeem.Cremin",
  "password": "$2b$10$kRcsmN6ewFC2GOs0TEg6TuvDbNzf1VGCbQf2fI1UeyPAiZCq9rMKm",
  "email": "Garrett87@hotmail.com",
  "nickname": "Wallace Nicolas",
  "role": "user",
  "isActive": true,
  "createdTime": "2021-03-24T15:24:26.806Z",
  "updatedTime": "2021-03-24T15:24:26.806Z"
 }
}

这里就需要做个自定义成功请求拦截器:

nest g in shared/interceptor/transform
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { Request } from 'express'
 
interface Response<T> {
 data: T
}
 
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
 intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
 const request = context.switchToHttp().getRequest<Request>()
 Logger.log(request.url, '正常接口请求')
 
 return next.handle().pipe(
  map(data => {
  return {
   data: data,
   statusCode: 200,
   message: '请求成功'
  }
  })
 )
 }
}

然后在 app.module.ts 引入即可使用:

import { ValidationPipe } from '@nestjs/common'
import { APP_INTERCEPTOR } from '@nestjs/core'
 
import { TransformInterceptor } from '@/shared/interceptor/transform.interceptor'
 
@Module({
 imports: [
 // ...
 ],
 controllers: [AppController],
 providers: [
 {
  provide: APP_INTERCEPTOR,
  useClass: TransformInterceptor
 }
 ]
})

不过 APP_INTERCEPTOR 排序要注意,TransformInterceptor 最好放在第一个,否则会失效。

错误过滤器:

nest g f shared/filters/httpException
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, Logger } from '@nestjs/common'
import { Response, Request } from 'express'
 
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
 catch(exception: HttpException, host: ArgumentsHost) {
 const context = host.switchToHttp()
 const response = context.getResponse<Response>()
 const request = context.getRequest<Request>()
 const status = exception.getStatus()
 const message = exception.message
 
 Logger.log(`${request.url} - ${message}`, '非正常接口请求')
 
 response.status(status).json({
  statusCode: status,
  message: message,
  path: request.url,
  timestamp: new Date().toISOString()
 })
 }
}

然后在 app.module.ts 引入即可使用:

import { ValidationPipe } from '@nestjs/common'
import { APP_FILTER } from '@nestjs/core'
 
import { HttpExceptionFilter } from '@/shared/filters/http-exception.filter'
 
@Module({
 imports: [
 // ...
 ],
 controllers: [AppController],
 providers: [
 {
  provide: APP_FILTER,
  useClass: HttpExceptionFilter
 }
 ]
})

0x2 隐藏实体类中的某个字段

本来想使用 @Exclude 属性来隐藏数据库中一些敏感的字段,但发现无法满足特殊的需求,如果是返回单条实例可以实现隐藏,但是我有个 findAll 就无法实现了,上面在 Serialization | NestJS - A progressive Node.js framework 文档里说的非常详细,不过这里还有个办法。首先在实力类敏感数据字段上添加属性:

import { BaseEntity, Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
 
@Entity('user')
export class UserEntity extends BaseEntity {
 @PrimaryGeneratedColumn('uuid', {
  comment: '用户编号'
 })
 id: string
 
 @Column({
  type: 'varchar',
  length: 50,
  unique: true,
  comment: '登录用户'
 })
 username: string
 
 @Column({
  type: 'varchar',
  length: 200,
  select: false,
  comment: '密码'
 })
 password: string

select: false 可以在返回查询结果隐藏这个字段,但所有涉及到这个字段查询必须添加这个字段,比如我在 user.service.ts 登录查询中:

const user = await getRepository(UserEntity)
   .createQueryBuilder('user')
   .where('user.username = :username', { username })
   .addSelect('user.password')
   .getOne()

.addSelect('user.password') 添加这个属性查询将会包括 password 这个字段,否则普通查询的方法不会包括这个字段。

Javascript 相关文章推荐
创建一个复制UBB软件信息的链接或按钮的js代码
Jan 06 Javascript
JavaScript文本框脚本编写的注意事项
Jan 25 Javascript
jQuery日历插件datepicker用法详解
Mar 03 Javascript
js操作数据库实现注册和登陆的简单实例
May 26 Javascript
Select2.js下拉框使用小结
Oct 24 Javascript
详解利用exif.js解决ios手机上传竖拍照片旋转90度问题
Nov 04 Javascript
基于javascript的Form表单验证
Dec 29 Javascript
React Native 环境搭建的教程
Aug 19 Javascript
JavaScript中递归实现的方法及其区别
Sep 12 Javascript
微信小程序实现图片放大预览功能
Oct 22 Javascript
vue3修改link标签默认icon无效问题详解
Oct 09 Javascript
基于js判断浏览器是否支持webGL
Apr 18 Javascript
Angular CLI发布路径的配置项浅析
Mar 29 #Javascript
vue中data改变后让视图同步更新的方法
vue3如何优雅的实现移动端登录注册模块
开发一个封装iframe的vue组件
如何让vue长列表快速加载
Vue3 Composition API的使用简介
JavaScript 语句之常用 for 循环详解
Mar 29 #Javascript
You might like
PHP采集腾讯微博的实现代码
2012/01/19 PHP
Erlang的运算符(比较运算符,数值运算符,移位运算符,逻辑运算符)
2012/07/23 PHP
解析php中session的实现原理以及大网站应用应注意的问题
2013/06/17 PHP
浅析PHP原理之变量(Variables inside PHP)
2013/08/09 PHP
php的curl封装类用法实例
2014/11/07 PHP
PHP版本升级到7.x后wordpress的一些修改及wordpress技巧
2015/12/25 PHP
php array_keys 返回数组的键名
2016/10/25 PHP
PHP自动补全表单的两种方法
2017/03/06 PHP
Laravel解决nesting level错误和隐藏index.php的问题
2019/10/12 PHP
Array对象方法参考
2006/10/03 Javascript
javascript 命名规则 变量命名规则
2010/02/25 Javascript
js实现动态添加、删除行、onkeyup表格求和示例
2013/08/18 Javascript
根据表格中的某一列进行排序的javascript代码
2013/11/29 Javascript
JavaScript实现点击文本自动定位到下拉框选中操作
2016/06/15 Javascript
MUI实现上拉加载和下拉刷新效果
2017/06/30 Javascript
ES6使用export和import实现模块化的方法
2018/09/10 Javascript
详解JavaScript中操作符和表达式
2018/09/12 Javascript
elementUI 设置input的只读或禁用的方法
2018/10/30 Javascript
vue中如何实现后台管理系统的权限控制的方法步骤
2019/09/05 Javascript
解决layer.open弹出框不能获取input框的值为空的问题
2019/09/10 Javascript
vue中axios防止多次触发终止多次请求的示例代码(防抖)
2020/02/16 Javascript
前端深入理解Typescript泛型概念
2020/03/09 Javascript
Nuxt页面级缓存的实现
2020/03/09 Javascript
谈谈JavaScript中的函数
2020/09/08 Javascript
vue实现选中效果
2020/10/07 Javascript
Python使用Django实现博客系统完整版
2020/09/29 Python
django加载本地html的方法
2018/05/27 Python
python爬取网页内容转换为PDF文件
2020/07/28 Python
django数据模型(Model)的字段类型解析
2019/12/25 Python
python实现五子棋游戏(pygame版)
2020/01/19 Python
澳大利亚最早和最古老的巨型游戏专家:Yardgames
2020/02/20 全球购物
班组长工作职责
2013/12/25 职场文书
2014离婚协议书范文(3篇)
2014/11/29 职场文书
2019如何书写演讲稿?
2019/07/01 职场文书
2019脱贫攻坚工作总结报告范本!
2019/08/06 职场文书
MySQL事务的隔离级别详情
2022/07/15 MySQL