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 相关文章推荐
Jquery Select操作方法集合脚本之家特别版
May 17 Javascript
为JavaScript添加重载函数的辅助方法
Jul 04 Javascript
javascript getElementsByTagName
Jan 31 Javascript
A标签触发onclick事件而不跳转的多种解决方法
Jun 27 Javascript
点击弹出层效果&amp;弹出窗口后网页背景变暗效果的实现代码
Feb 10 Javascript
javascript里绝对用的上的字符分割函数总结
Jul 31 Javascript
js鼠标点击图片实现随机变换图片的方法
Feb 16 Javascript
jQuery ajaxForm()的应用
Oct 14 Javascript
微信小程序 特效菜单抽屉效果实例代码
Jan 11 Javascript
结合Vue控制字符和字节的显示个数的示例
May 17 Javascript
基于 vue-skeleton-webpack-plugin 的骨架屏实战
Aug 05 Javascript
浅谈Vue开发人员的7个最好的VSCode扩展
Jan 20 Vue.js
Angular CLI发布路径的配置项浅析
Mar 29 #Javascript
vue中data改变后让视图同步更新的方法
vue3如何优雅的实现移动端登录注册模块
开发一个封装iframe的vue组件
如何让vue长列表快速加载
Vue3 Composition API的使用简介
JavaScript 语句之常用 for 循环详解
Mar 29 #Javascript
You might like
PHP 数组实例说明
2008/08/18 PHP
PHP页面中文乱码分析
2013/10/29 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(十三)
2014/06/26 PHP
教你php如何实现验证码
2016/01/20 PHP
PHP中的self关键字详解
2019/06/23 PHP
基于jquery的滚动新闻列表
2010/06/19 Javascript
js保留小数点后几位的写法
2014/01/03 Javascript
node.js中的fs.writeFile方法使用说明
2014/12/14 Javascript
jquery实现向下滑出的二级导航下滑菜单效果
2015/08/25 Javascript
javascript bom是什么及bom和dom的区别
2015/11/26 Javascript
Angular 4中如何显示内容的CSS样式示例代码
2017/11/06 Javascript
angular第三方包开发整理(小结)
2018/04/19 Javascript
解决layer 关闭当前弹窗 关闭遮罩层 input值获取不到的问题
2019/09/25 Javascript
[00:34]DOTA2上海特级锦标赛 Spirit战队宣传片
2016/03/04 DOTA
Python常用的日期时间处理方法示例
2015/02/08 Python
浅谈Python中列表生成式和生成器的区别
2015/08/03 Python
python3使用smtplib实现发送邮件功能
2018/05/22 Python
解决Python3中的中文字符编码的问题
2018/07/18 Python
人工神经网络算法知识点总结
2019/06/11 Python
Pytorch使用MNIST数据集实现基础GAN和DCGAN详解
2020/01/10 Python
Python文件读写w+和r+区别解析
2020/03/26 Python
Pymysql实现往表中插入数据过程解析
2020/06/02 Python
Python实现一个简单的递归下降分析器
2020/08/01 Python
Python lambda表达式原理及用法解析
2020/08/18 Python
python字典按照value排序方法
2020/12/28 Python
CSS3 按钮边框动画的实现
2020/11/12 HTML / CSS
canvas三角函数模拟水波效果的示例代码
2018/07/03 HTML / CSS
Boden美国官网:英伦原创时装品牌
2017/07/03 全球购物
美国最大和最受信任的二手轮胎商店:Bestusedtires.com
2020/06/02 全球购物
教师群众路线教育实践活动学习笔记
2014/11/05 职场文书
2014年领导班子工作总结
2014/12/11 职场文书
2015年综治宣传月活动总结
2015/03/25 职场文书
预备党员考察表党小组意见
2015/06/01 职场文书
springboot + mongodb 通过经纬度坐标匹配平面区域的方法
2021/11/01 MongoDB
解决Redis启动警告问题
2022/02/24 Redis
CSS中实现动画效果-附案例
2022/02/28 HTML / CSS