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 相关文章推荐
用js+xml自动生成表格的东西
Dec 21 Javascript
jQuery获得内容和属性方法及示例
Dec 02 Javascript
删除条目时弹出的确认对话框
Jun 05 Javascript
jQuery实现类似标签风格的导航菜单效果代码
Aug 25 Javascript
JavaScript定义函数_动力节点Java学院整理
Jun 27 Javascript
React组件之间的通信的实例代码
Jun 27 Javascript
使用原生js封装的ajax实例(兼容jsonp)
Oct 12 Javascript
Vue.extend实现挂载到实例上的方法
May 01 Javascript
Vue Router的手写实现方法实现
Mar 02 Javascript
原生JS实现汇率转换功能代码实例
May 13 Javascript
ant-design-vue按需加载的坑的解决
May 14 Javascript
vue 函数调用加括号与不加括号的区别
Oct 29 Javascript
Angular CLI发布路径的配置项浅析
Mar 29 #Javascript
vue中data改变后让视图同步更新的方法
vue3如何优雅的实现移动端登录注册模块
开发一个封装iframe的vue组件
如何让vue长列表快速加载
Vue3 Composition API的使用简介
JavaScript 语句之常用 for 循环详解
Mar 29 #Javascript
You might like
在WINDOWS中设置计划任务执行PHP文件的方法
2011/12/19 PHP
php实例分享之二维数组排序
2014/05/15 PHP
PHP+Apache环境中如何隐藏Apache版本
2017/11/24 PHP
PHP排序算法之快速排序(Quick Sort)及其优化算法详解
2018/04/21 PHP
jQuery 三击事件实现代码
2013/09/11 Javascript
JavaScript中的迭代器和生成器详解
2014/10/29 Javascript
angularjs的一些优化小技巧
2014/12/06 Javascript
jQuery插件jFade实现鼠标经过的图片高亮其它变暗
2015/03/14 Javascript
jQuery超精致图片轮播幻灯片特效代码分享
2015/09/10 Javascript
jQuery+JSON实现AJAX二级联动实例分析
2015/12/18 Javascript
javascript新闻跑马灯实例代码
2020/07/29 Javascript
基于jquery实现表格无刷新分页
2016/01/07 Javascript
详解JavaScript中return的用法
2017/05/08 Javascript
jQuery 实现鼠标画框并对框内数据选中的实例代码
2017/08/29 jQuery
基于webpack-hot-middleware热加载相关错误的解决方法
2018/02/22 Javascript
vue 微信授权登录解决方案
2018/04/10 Javascript
AJAX在JQuery中的应用详解
2019/01/30 jQuery
小程序实现搜索界面 小程序实现推荐搜索列表效果
2019/05/18 Javascript
详解Vue+elementUI build打包部署后字体图标丢失问题
2020/07/13 Javascript
vue.js封装switch开关组件的操作
2020/10/26 Javascript
[01:08:43]DOTA2-DPC中国联赛定级赛 Phoenix vs DLG BO3第一场 1月9日
2021/03/11 DOTA
Python入门学习之字符串与比较运算符
2015/10/12 Python
python 爬取微信文章
2016/01/30 Python
Django使用httpresponse返回用户头像实例代码
2018/01/26 Python
Python切片操作实例分析
2018/03/16 Python
详解从Django Rest Framework响应中删除空字段
2019/01/11 Python
TensorFlow自定义损失函数来预测商品销售量
2020/02/05 Python
Python字符串格式化f-string多种功能实现
2020/05/07 Python
python网络爬虫实现发送短信验证码的方法
2021/02/25 Python
使用html5新特性轻松监听任何App自带返回键的示例
2018/03/13 HTML / CSS
运动会入场词60字
2014/02/15 职场文书
寒假家长评语大全
2014/04/16 职场文书
自愿离婚协议书范文2014
2014/10/12 职场文书
如何用PHP实现多线程编程
2021/05/26 PHP
sql server删除前1000行数据的方法实例
2021/08/30 SQL Server
阿里面试Nacos配置中心交互模型是push还是pull原理解析
2022/07/23 Java/Android