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 相关文章推荐
javascript indexOf函数使用说明
Jul 03 Javascript
javascript 学习之旅 (1)
Feb 05 Javascript
js 页面关闭前的出现提示的实现代码
May 25 Javascript
js中settimeout方法加参数
Feb 28 Javascript
Javascript实现禁止输入中文或英文的例子
Dec 09 Javascript
js实现横向伸展开的二级导航菜单代码
Aug 28 Javascript
Javascript设计模式之装饰者模式详解篇
Jan 17 Javascript
JavaScript实现替换字符串中最后一个字符的方法
Mar 07 Javascript
360提示[高危]使用存在漏洞的JQuery版本的解决方法
Oct 27 jQuery
vue项目实现记住密码到cookie功能示例(附源码)
Jan 31 Javascript
javascript匿名函数中的'return function()'作用
Oct 15 Javascript
详解JavaScript 高阶函数
Sep 14 Javascript
Angular CLI发布路径的配置项浅析
Mar 29 #Javascript
vue中data改变后让视图同步更新的方法
vue3如何优雅的实现移动端登录注册模块
开发一个封装iframe的vue组件
如何让vue长列表快速加载
Vue3 Composition API的使用简介
JavaScript 语句之常用 for 循环详解
Mar 29 #Javascript
You might like
PHP脚本的10个技巧(3)
2006/10/09 PHP
Apache2中实现多网站域名绑定的实现方法
2011/06/01 PHP
php is_executable判断给定文件名是否可执行实例
2016/09/26 PHP
js输出列表实现代码
2010/09/12 Javascript
DIV+CSS+JS不间断横向滚动实现代码
2013/03/19 Javascript
jquery 实现二级/三级/多级联动菜单的思路及代码
2013/04/08 Javascript
js 获取input点选按钮的值的方法
2014/04/14 Javascript
三种AngularJS中获取数据源的方式
2016/02/02 Javascript
Javascript 事件冒泡机制详细介绍
2016/10/10 Javascript
学习 NodeJS 第八天:Socket 通讯实例
2016/12/21 NodeJs
jQuery实现的简单无刷新评论功能示例
2017/11/08 jQuery
Vue.js在数组中插入重复数据的实现代码
2017/11/17 Javascript
JQuery通过后台获取数据遍历到前台的方法
2018/08/13 jQuery
js实现div色块碰撞
2020/01/16 Javascript
vue实现短信验证码输入框
2020/04/17 Javascript
原生JS实现京东查看商品点击放大
2020/12/21 Javascript
python求列表交集的方法汇总
2014/11/10 Python
深入解析Python中的变量和赋值运算符
2015/10/12 Python
Python利用itchat库向好友或者公众号发消息的实例
2019/02/21 Python
Python使用scrapy爬取阳光热线问政平台过程解析
2019/08/14 Python
wxPython:python首选的GUI库实例分享
2019/10/05 Python
python numpy之np.random的随机数函数使用介绍
2019/10/06 Python
flask框架配置mysql数据库操作详解
2019/11/29 Python
Python 安装 virturalenv 虚拟环境的教程详解
2020/02/21 Python
浅谈selenium如何应对网页内容需要鼠标滚动加载的问题
2020/03/14 Python
canvas三角函数模拟水波效果的示例代码
2018/07/03 HTML / CSS
Java平台和其他软件平台有什么不同
2015/06/05 面试题
集团公司人力资源部岗位职责
2014/01/03 职场文书
超市采购员岗位职责
2014/02/01 职场文书
临床医学生职业规划书范文
2014/10/25 职场文书
蓬莱阁导游词
2015/02/04 职场文书
通讯稿范文
2015/07/22 职场文书
安全主题班会教案
2015/08/12 职场文书
Python竟然能剪辑视频
2021/05/25 Python
Python办公自动化之教你如何用Python将任意文件转为PDF格式
2021/06/28 Python
【2·13】一图读懂中国无线电发展
2022/02/18 无线电