参数校验
我们经常要在方法调用时执行一些类型检查,参数转换的操作,Midway 提供了一种简单的能力来快速检查参数的类型。
本模块自 v4.0.0
起替换 @midwayjs/validate
组件。
新版本提供了更灵活的验证器扩展机制,支持多种验证器(如 Joi、Zod 等)的无缝切换,并提供了更好的类型支持和性能优化。
相关信息:
描述 | |
---|---|
可用于标准项目 | ✅ |
可用于 Serverless | ✅ |
可用于一体化 | ✅ |
包含独立主框架 | ❌ |
包含独立日志 | ❌ |
背景
最常用参数校验的地方是控制器(Controller),同时你也可以在任意的 Class 中使用这个能力。
我们以控制器(Controller)中 使用为例。
➜ my_midway_app tree
.
├── src
│ ├── controller
│ │ └── user.ts
│ ├── interface.ts
│ └── service
│ └── user.ts
├── test
├── package.json
└── tsconfig.json
普通情况下,我们从 body
上拿到所有 Post 结果,并进行一些校验。
// src/interface.ts
export interface User {
id: number;
firstName: string;
lastName: string;
age: number;
}
// src/controller/home.ts
import { Controller, Get, Provide } from '@midwayjs/core';
@Controller('/api/user')
export class HomeController {
@Post('/')
async updateUser(@Body() user: User) {
if (!user.id || typeof user.id !== 'number') {
throw new Error('id error');
}
if (user.age <= 30) {
throw new Error('age not match');
}
// xxx
}
}
如果每个方法都需要这么校验,会非常的繁琐。
针对这种情况,Midway 提供了 Validation 组件。配合 @Validate
和 @Rule
装饰器,用来 快速定义校验的规则,帮 助用户 减少这些重复的代码。
下面的通用能力将以 joi 来举例。
安装依赖
你需要安装 validation 组件以及对应验证器。
## 安装 validation 组件
$ npm i @midwayjs/validation@4 --save
## 选择一个或多个验证器
$ npm i @midwayjs/validation-joi@4 --save
## 基础库
$ npm i joi --save
或者在 package.json
中增加如下依赖后,重新安装。
{
"dependencies": {
"@midwayjs/validation": "^4.0.0",
"@midwayjs/validation-joi": "^4.0.0",
"joi": "^17.13.3",
// ...
},
"devDependencies": {
// ...
}
}
开启组件
在 configuration.ts
中增加组件:
import { Configuration, App } from '@midwayjs/core';
import * as koa from '@midwayjs/koa';
import * as validation from '@midwayjs/validation';
import { join } from 'path';
@Configuration({
imports: [
koa,
validation,
// ... 其他组件
],
importConfigs: [join(__dirname, './config')],
})
export class MainConfiguration {
@App()
app: koa.Application;
async onReady() {
// ...
}
}
在配置文件中设置验证器:
// src/config/config.default.ts
import * as joi from '@midwayjs/validation-joi';
export default {
// ...
validation: {
// 配置验证器
validators: {
joi,
},
// 设置默认验证器
defaultValidator: 'joi'
}
}
校验规则
通过 @Rule
装饰器,可以传递校验规则。
import { Rule } from '@midwayjs/validation';
import * as Joi from 'joi';
export class UserDTO {
@Rule(Joi.number().required())
id: number;
@Rule(Joi.string().required())
firstName: string;
@Rule(Joi.string().max(10))
lastName: string;
@Rule(Joi.number().max(60))
age: number;
}
校验参数
定义完类型之后,就可以直接在业务代码中使用了,框架将自动帮你校验和转换 DTO。
// src/controller/home.ts
import { Controller, Get, Provide, Body } from '@midwayjs/core';
import { UserDTO } from './dto/user';
@Controller('/api/user')
export class HomeController {
@Post('/')
async updateUser(@Body() user: UserDTO) {
// user.id
}
}
所有的校验代码都通通不见了,业务变的更纯粹了,当然,记得要把原来的 user interface 换成 Class。
一旦校验失败,浏览器或者控制台就会报出类似的错误。
ValidationError: "id" is required
同时,由于定义了 id
的类型,在拿到字符串的情况下,会自动将 id 变为数字。
async updateUser(@Body() user: UserDTO ) {
// typeof user.id === 'number'
}
如果需要对方法级别单独配置信息,可以使用 @Validate
装饰器,比如单独配置错误状态。
// src/controller/home.ts
import { Controller, Get, Provide } from '@midwayjs/core';
import { Validate } from '@midwayjs/validation';
import { UserDTO } from './dto/user';
@Controller('/api/user')
export class HomeController {
@Post('/')
@Validate({
errorStatus: 422,
})
async updateUser(@Body() user: UserDTO) {
// user.id
}
}
@Validate
装饰器可以传递多个配置项,比如 errorStatus
,locale
等。
配置项 | 类型 | 描述 |
---|---|---|
errorStatus | number | 当校 验出错时,返回的 Http 状态码,在 http 场景生效,默认 422 |
locale | string | 校验出错文本的默认语言,默认为 en_US ,会根据 i18n 组件的规则切换 |
throwValidateError | boolean | 是否抛出校验错误,默认 true ,如果设置为 false ,则返回校验结果 |
defaultValidator | string | 设置默认使用的验证器 |
校验结果
校验结果是一个对象,包含了校验的状态,校验的错误,校验的值等信息。Midway 对不同的验证器的返回值做了封装,统一了返回值的格式。
整体结构如下:
interface ValidateResult {
/**
* 校验是否成功
*/
status: boolean;
/**
* 校验错误,如果有多个错误,会返回第一个错误
*/
error?: any;
/**
* 校验的所有错误
*/
errors?: any[];
/**
* 校验错误信息,如果有多个错误,会返回第一个错误的信息
*/
message?: string;
/**
* 校验的所有错误信息
*/
messages?: string[];
/**
* 校验额外信息
*/
extra?: any;
}
不同的验证器返回都已经处理成相同的结构。
通用场景校验
如果参数不是 DTO,可以使用 @Valid
装饰器进行校验,@Valid
装饰器可以直接传递一个验证规则。
使用 Joi:
import { Controller, Get, Query } from '@midwayjs/core';
import { Valid } from '@midwayjs/validation';
import * as Joi from 'joi';
@Controller('/api/user')
export class HomeController {
@Get('/')
async getUser(@Valid(Joi.number().required()) @Query('id') id: number) {
// ...
}
}
使用 Zod:
import { Controller, Get, Query } from '@midwayjs/core';
import { Valid } from '@midwayjs/validation';
import { z } from 'zod';
@Controller('/api/user')
export class HomeController {
@Get('/')
async getUser(@Valid(z.number().min(1)) @Query('id') id: number) {
// ...
}
}
在非 Web 场景下,没有 @Body
等 Web 类装饰器的情况下,也可以使用 @Valid
装饰器来进行校验。
import { Valid } from '@midwayjs/validation';
import { Provide } from '@midwayjs/core';
import { UserDTO } from './dto/user';
@Provide()
export class UserService {
async updateUser(@Valid() user: UserDTO) {
// ...
}
}
校验管道
如果你的参数是基础类型,比如 number
, string
, boolean
,则可以使用组件提供的管道进行校验。
默认的 Web 参数装饰器都可以在第二个参数传入管道。
比如:
import { ParseIntPipe } from '@midwayjs/validation';
import { Controller, Post, Body } from '@midwayjs/core';
@Controller('/api/user')
export class HomeController {
@Post('/update_age')
async updateAge(@Body('age', [ParseIntPipe]) age: number) {
// ...
}
}
ParseIntPipe
管道可以将字符串,数字数据转换为数字,这样从请求参数获取到的 age
字段则会通过管道的校验并转换为数字格式。
可以使用 的内置管道有:
ParseIntPipe
ParseFloatPipe
ParseBoolPipe
DefaultValuePipe
ParseIntPipe
用于将参数转为整形数字。
import { ParseIntPipe } from '@midwayjs/validation';
// ...
async update(@Body('age', [ParseIntPipe]) age: number) {
return age;
}
update({ age: '12'} ); => 12
update({ age: '12.2'} ); => Error
update({ age: 'abc'} ); => Error
ParseFloatPipe
用于将参数转为浮点型数字数字。
import { ParseFloatPipe } from '@midwayjs/validation';
// ...
async update(@Body('size', [ParseFloatPipe]) size: number) {
return size;
}
update({ size: '12.2'} ); => 12.2
update({ size: '12'} ); => 12
ParseBoolPipe
用于将参数转为布尔值。
import { ParseBoolPipe } from '@midwayjs/validation';
// ...
async update(@Body('isMale', [ParseBoolPipe]) isMale: boolean) {
return isMale;
}
update({ isMale: 'true'} ); => true
update({ isMale: '0'} ); => Error
DefaultValuePipe
用于设定默认值。
import { DefaultValuePipe } from '@midwayjs/validation';
// ...
async update(@Body('nickName', [new DefaultValuePipe('anonymous')]) nickName: string) {
return nickName;
}
update({ nickName: undefined} ); => 'anonymous'
自定义校验管道
如果默认的管道不满足需求,可以通过继承,快速实现一个自定义校验管道,组件已经提供了一个 ParsePipe
类用于快速编写。
import { Pipe } from '@midwayjs/core';
import { ParsePipe, RuleType } from '@midwayjs/validation';
@Pipe()
export class ParseCustomDataPipe extends ParsePipe {
getSchema() {
// ...
}
}
getSchema
方法用于返回一个校验规则。
比如 ParseIntPipe
的代码如下,管道执行时会自动获取这个 schema 进行校验,并在校验成功后将值返回。
我们依旧拿 joi
来举例。
import { Pipe } from '@midwayjs/core';
import { ParsePipe, RuleType } from '@midwayjs/validation';
import * as Joi from 'joi';
@Pipe()
export class ParseIntPipe extends ParsePipe {
getSchema() {
return Joi.number().integer().required();
}
}