填坑 内容如题
DTO可以用 interface 来写,也可以用 class 来写,不过TS最终会被编译成JS来执行,最终写的 interface 会在编译时消除。
interface
class
按官方文档的意思, pipe 等特性中可能需要获取DTO的元数据,所以用 class ,何况 class 还可以用装饰器…
pipe
123456789101112
// create-user.dto.tsexport class CreateUserDto { readonly name: string readonly age: number readonly mobile: string}// user.controller.ts@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 中进行校验,又很丑,还会造成冗余。
controller
pipe 是进入控制器前的最后一道处理,可以用来做数据转换、数据验证等数据方面的处理。
自定义的管道需要实现 PipeTransform 接口
PipeTransform
123
export interface PipeTransform<T = any, R = any> { transform(value: T, metadata: ArgumentMetadata): R;}
value 是当前在处理的数据, metadata 则是其元数据
value
metadata
12345
export interface ArgumentMetadata { readonly type: Paramtype; readonly metatype?: Type<any> | undefined; readonly data?: string | undefined;}
type 表示数据是来自body,query,param还是自定义参数
type
metatype 表示数据的元类型,比如 CreateUserDto ,如果没有声明类型则是 undefined
metatype
CreateUserDto
undefined
data 是传递给装饰器的字符串,比如 @Body('abc') , data 的值就是 abc ,没有传值则是 undefined
data
@Body('abc')
abc
class-validator 是官方推荐的校验器,它是基于装饰器的,所以和Nest、TypeScript相性较合
class-validator
npm i --save class-validator class-transformer
class-transformer 是同个作者做的配套用的库,能把数据转换成DTO的实例,比如上述参数会被转换成这样
class-transformer
CreateUserDto { name: 'abc', age: '1', mobile: 123 }
然后改造DTO
12345678910111213
// create-user.dto.tsimport { 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}
改造完成后,就可以编写自定义的校验管道了
123456789101112131415161718192021222324252627282930313233
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) }}
这里我只抛出了校验失败的第一条信息,可以根据需要修改
最后将管道注册为全局管道
12345678910
import { APP_PIPE } from '@nestjs/core'import { ValidationPipe } from './providers/pipe/validation.pipe'// app.module.ts (省略了其他模块)@Module({ providers: [ { provide: APP_PIPE, useClass: ValidationPipe } ]})export class AppModule {}
现在所有请求都会走一遍该管道进行参数校验了
// request{ "name": "abc", "age": 18, "mobile": 123}// response{ "msg": "手机号长度必须为11位", "code": 4002, "path": "/users/add"}
这里数据库用的是mongoDB
还挺方便的,官方提供了 @nestjs/mongoose 模块帮助数据库连接,不过假如用的是TypeORM,官方文档也有对应的连接方法。
@nestjs/mongoose
npm install --save @nestjs/mongoose mongoose
12345678910111213141516
// app.module.ts (省略了其他模块)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 {}
如果不用配置文件就更方便了..
1234567
// app.module.ts (省略了其他模块)import { MongooseModule } from '@nestjs/mongoose'@Module({ imports: [MongooseModule.forRoot('mongodb://localhost/nest')]})export class AppModule {}
首先是定义Schema
1234567891011
// user.schema.tsimport * as mongoose from 'mongoose'export const UserSchema = new mongoose.Schema( { name: String, age: Number, mobile: String }, { collection: 'user' })
然后在用到的module处进行注册
// users.module.tsimport { 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
UserModel
123456789
import { Document } from 'mongoose'export interface User { name: string age: number mobile: string}export interface UserModel extends User, Document {}
最后就能在service中使用了
12345678910111213141516171819202122
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 }}