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 这个字段,否则普通查询的方法不会包括这个字段。
Nest.js参数校验和自定义返回数据格式详解
- Author -
Jaxson Wang声明:登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。
Reply on: @reply_date@
@reply_contents@