填坑 内容如题
参数校验
DTO
DTO可以用interface
来写,也可以用class
来写,不过TS最终会被编译成JS来执行,最终写的interface
会在编译时消除。
按官方文档的意思,pipe
等特性中可能需要获取DTO的元数据,所以用class
,何况class
还可以用装饰器…
举个例子
1 2 3 4 5 6 7 8 9 10 11 12
| export class CreateUserDto { readonly name: string readonly age: number readonly mobile: string }
@Post('/add') async addUser(@Body() createUserDto: CreateUserDto): Promise<void> { await this.userService.createUser(createUserDto) }
|
DTO是有了,但是nest从客户端拿到的是啥就还是啥
1
| { name: 'abc', age: '1', mobile: 123 }
|
像这种数据类型错误的请求,照样会到达controller
这儿,假如在controller
中进行校验,又很丑,还会造成冗余。
pipe
pipe
是进入控制器前的最后一道处理,可以用来做数据转换、数据验证等数据方面的处理。
自定义的管道需要实现PipeTransform
接口
1 2 3
| export interface PipeTransform<T = any, R = any> { transform(value: T, metadata: ArgumentMetadata): R; }
|
value
是当前在处理的数据,metadata
则是其元数据
1 2 3 4 5
| export interface ArgumentMetadata { readonly type: Paramtype; readonly metatype?: Type<any> | undefined; readonly data?: string | undefined; }
|
type
表示数据是来自body,query,param还是自定义参数
metatype
表示数据的元类型,比如CreateUserDto
,如果没有声明类型则是undefined
data
是传递给装饰器的字符串,比如@Body('abc')
,data
的值就是abc
,没有传值则是undefined
class-validator
class-validator
是官方推荐的校验器,它是基于装饰器的,所以和Nest、TypeScript相性较合
1
| npm i --save class-validator class-transformer
|
class-transformer
是同个作者做的配套用的库,能把数据转换成DTO的实例,比如上述参数会被转换成这样
1
| CreateUserDto { name: 'abc', age: '1', mobile: 123 }
|
然后改造DTO
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { Length, Min, Max } from 'class-validator'
export class CreateUserDto { readonly name: string
@Min(6, { message: '年龄不能小于6' }) @Max(70, { message: '年龄不能大于70' }) readonly age: number
@Length(11, 11, { message: '手机号长度必须为11位' }) readonly mobile: string }
|
改造完成后,就可以编写自定义的校验管道了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import { Injectable, PipeTransform, ArgumentMetadata } from '@nestjs/common' import { validate } from 'class-validator' import { plainToClass } from 'class-transformer' import { ApiException } from '../../exceptions/api.exception' import { ApiCode } from '../../enums/api-code.enum'
@Injectable() export class ValidationPipe implements PipeTransform { async transform<T>(value: T, metadata: ArgumentMetadata): Promise<T> { const { metatype } = metadata if (!metatype || !this.toValidate(metatype)) { return value }
const object = plainToClass(metatype, value) const errors = await validate(object) if (errors.length > 0) { const firstError = errors[0] const { constraints } = firstError const keys = Object.keys(constraints)
throw new ApiException(constraints[keys[0]], ApiCode.PARAMS_ERROR, 200) }
return value }
private toValidate(metatype): boolean { const types = [String, Boolean, Number, Array, Object]
return !types.find(type => metatype === type) } }
|
这里我只抛出了校验失败的第一条信息,可以根据需要修改
最后将管道注册为全局管道
1 2 3 4 5 6 7 8 9 10
| import { APP_PIPE } from '@nestjs/core' import { ValidationPipe } from './providers/pipe/validation.pipe'
@Module({ providers: [ { provide: APP_PIPE, useClass: ValidationPipe } ] }) export class AppModule {}
|
现在所有请求都会走一遍该管道进行参数校验了
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "name": "abc", "age": 18, "mobile": 123 }
{ "msg": "手机号长度必须为11位", "code": 4002, "path": "/users/add" }
|
数据库
这里数据库用的是mongoDB
数据库连接
还挺方便的,官方提供了@nestjs/mongoose
模块帮助数据库连接,不过假如用的是TypeORM,官方文档也有对应的连接方法。
1
| npm install --save @nestjs/mongoose mongoose
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { MongooseModule } from '@nestjs/mongoose'
@Module({ imports: [ MongooseModule.forRootAsync({ useFactory: async (configService: ConfigService) => ({ uri: configService.get('DATABASE'), useNewUrlParser: true, useUnifiedTopology: true }), inject: [ConfigService] }) ] }) export class AppModule {}
|
如果不用配置文件就更方便了..
1 2 3 4 5 6 7
| import { MongooseModule } from '@nestjs/mongoose'
@Module({ imports: [MongooseModule.forRoot('mongodb://localhost/nest')] }) export class AppModule {}
|
使用
首先是定义Schema
1 2 3 4 5 6 7 8 9 10 11
| import * as mongoose from 'mongoose'
export const UserSchema = new mongoose.Schema( { name: String, age: Number, mobile: String }, { collection: 'user' } )
|
然后在用到的module处进行注册
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { Module } from '@nestjs/common' import { MongooseModule } from '@nestjs/mongoose' import { UsersController } from './users.controller' import { UsersService } from './users.service' import { UserSchema } from './schemas/user.schema'
@Module({ imports: [MongooseModule.forFeature([{ name: 'User', schema: UserSchema }])], controllers: [UsersController], providers: [UsersService] }) export class UsersModule {}
|
注册完之后,就能在service层注入对应的model了,在这之前先定义UserModel
1 2 3 4 5 6 7 8 9
| import { Document } from 'mongoose'
export interface User { name: string age: number mobile: string }
export interface UserModel extends User, Document {}
|
最后就能在service中使用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { Injectable } from '@nestjs/common' import { UserModel } from './users.interface' import { InjectModel } from '@nestjs/mongoose' import { Model } from 'mongoose' import { CreateUserDto } from './dto/create-user.dto' import { ApiException } from '../../exceptions/api.exception' import { ApiCode } from '../../enums/api-code.enum'
@Injectable() export class UsersService { constructor( @InjectModel('User') private readonly userModel: Model<UserModel> ) {}
async createUser(createUserDto: CreateUserDto): Promise<boolean> { await this.userModel.create(createUserDto).catch(() => { throw new ApiException('创建失败', ApiCode.DATABASE_ERROR, 200) })
return true } }
|