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 相关文章推荐
两种WEB下的模态对话框 (asp.net或js的分别实现)
Dec 02 Javascript
JavaScript 图片预览效果 推荐
Dec 22 Javascript
jquery ajax中使用jsonp的限制解决方法
Nov 22 Javascript
使用Browserify配合jQuery进行编程的超级指南
Jul 28 Javascript
jquery.cookie.js用法实例详解
Dec 25 Javascript
由简入繁实现Jquery树状结构的方法(推荐)
Jun 10 Javascript
很酷的星级评分系统原生JS实现
Aug 25 Javascript
JavaScript数组操作详解
Feb 04 Javascript
JavaScript条件判断_动力节点Java学院整理
Jun 26 Javascript
详解webpack分包及异步加载套路
Jun 29 Javascript
Underscore之Array_动力节点Java学院整理
Jul 10 Javascript
基于webpack4搭建的react项目框架的方法
Jun 30 Javascript
Angular CLI发布路径的配置项浅析
Mar 29 #Javascript
vue中data改变后让视图同步更新的方法
vue3如何优雅的实现移动端登录注册模块
开发一个封装iframe的vue组件
如何让vue长列表快速加载
Vue3 Composition API的使用简介
JavaScript 语句之常用 for 循环详解
Mar 29 #Javascript
You might like
Linux下将excel数据导入到mssql数据库中的方法
2010/02/08 PHP
php中使用DOM类读取XML文件的实现代码
2011/12/14 PHP
PHP输出日历表代码实例
2015/03/27 PHP
PHP中你应该知道的require()文件包含的正确用法
2015/06/12 PHP
php脚本运行时的超时机制详解
2016/02/17 PHP
PHP正则表达式笔记与实例详解
2019/05/09 PHP
弹出广告特效(一个IP只弹出一次)的代码
2007/07/27 Javascript
IE不出现Flash激活框的小发现的js实现方法
2007/09/07 Javascript
Array.prototype.slice.apply的使用方法
2010/03/17 Javascript
JQuery的Validation插件中Remote验证的中文问题
2010/07/26 Javascript
jQuery选择器简明总结(含用法实例,一目了然)
2014/04/25 Javascript
js中document.write的那点事
2014/12/12 Javascript
jQuery中:button选择器用法实例
2015/01/04 Javascript
JS定义类的六种方式详解
2016/05/12 Javascript
JavaScript实现的数字与字符串转换功能示例
2017/08/23 Javascript
JS 实现百度搜索功能
2018/02/01 Javascript
JavaScript高级函数应用之分时函数实例分析
2018/08/03 Javascript
解决layer弹层遮罩挡住窗体的问题
2018/08/17 Javascript
在element-ui的el-tree组件中用render函数生成el-button的实例代码
2018/11/05 Javascript
微信小程序中的canvas 文字断行和省略号显示功能的处理方法
2018/11/14 Javascript
微信小程序bindtap事件与冒泡阻止详解
2019/08/08 Javascript
Pycharm在创建py文件时,自动添加文件头注释的实例
2018/05/07 Python
Python实现一个数组除以一个数的例子
2019/07/20 Python
python实现银行管理系统
2019/10/25 Python
Python csv文件的读写操作实例详解
2019/11/19 Python
CSS3利用text-shadow属性实现多种效果的文字样式展现方法
2016/08/25 HTML / CSS
美国马匹用品和马钉购物网站:State Line Tack
2018/08/05 全球购物
优乐美广告词
2014/03/14 职场文书
参观接待方案
2014/03/17 职场文书
党员教师一句话承诺
2014/05/30 职场文书
员工家属慰问信
2015/03/24 职场文书
大学团日活动总结书
2015/05/11 职场文书
2016中秋节广告语
2016/01/28 职场文书
Netty结合Protobuf进行编解码的方法
2021/06/26 Java/Android
golang生成并解析JSON
2022/04/14 Golang
Python Matplotlib绘制动画的代码详解
2022/05/30 Python