From 88b2e6321f8a3e9308e1bd2ac5a5c144fe339564 Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Mon, 17 Feb 2025 00:55:50 +0800 Subject: [PATCH] fix: lint --- packages/i18n/src/i18nService.ts | 1 - packages/validation-joi/index.d.ts | 2 +- packages/validation-joi/package.json | 2 +- packages/validation-joi/src/index.ts | 29 +- packages/validation-zod/src/index.ts | 41 +- packages/validation/src/configuration.ts | 12 +- packages/validation/src/error.ts | 13 +- packages/validation/src/pipe.ts | 15 +- packages/validation/src/service.ts | 49 +- site/docs/extensions/validation.md | 575 +++++++++++++++++++++++ 10 files changed, 688 insertions(+), 51 deletions(-) create mode 100644 site/docs/extensions/validation.md diff --git a/packages/i18n/src/i18nService.ts b/packages/i18n/src/i18nService.ts index dc1316e2ca7..ff9b3c3c95e 100644 --- a/packages/i18n/src/i18nService.ts +++ b/packages/i18n/src/i18nService.ts @@ -41,7 +41,6 @@ export class MidwayI18nServiceSingleton { }); } // add origin locale json - } /** diff --git a/packages/validation-joi/index.d.ts b/packages/validation-joi/index.d.ts index b5b83ea8c54..167de796aa2 100644 --- a/packages/validation-joi/index.d.ts +++ b/packages/validation-joi/index.d.ts @@ -3,6 +3,6 @@ import * as Joi from 'joi'; declare module '@midwayjs/core/dist/interface' { interface MidwayConfig { - joi?: Joi.ValidationOptions + joi?: Joi.ValidationOptions; } } diff --git a/packages/validation-joi/package.json b/packages/validation-joi/package.json index 3ff172dbb41..6b170b5148e 100644 --- a/packages/validation-joi/package.json +++ b/packages/validation-joi/package.json @@ -23,11 +23,11 @@ }, "license": "MIT", "dependencies": { + "@midwayjs/i18n": "workspace:^", "@midwayjs/validation": "workspace:^" }, "devDependencies": { "@midwayjs/core": "workspace:^", - "@midwayjs/express": "workspace:^", "@midwayjs/koa": "workspace:^", "@midwayjs/mock": "workspace:^", "joi": "17.13.3" diff --git a/packages/validation-joi/src/index.ts b/packages/validation-joi/src/index.ts index ad325a16566..40f435c725c 100644 --- a/packages/validation-joi/src/index.ts +++ b/packages/validation-joi/src/index.ts @@ -4,7 +4,7 @@ import { getRuleMeta, ValidateResult, ValidationExtendOptions, - IValidationService + IValidationService, } from '@midwayjs/validation'; import { MidwayI18nServiceSingleton } from '@midwayjs/i18n'; @@ -23,15 +23,20 @@ export default (container: IMidwayContainer) => { joi: require('../locales/zh_CN.json'), }, }, - } + }, }); - return new class implements IValidationService { + return new (class implements IValidationService { defaultValidatorOptions: Joi.ValidationOptions; async init() { - const i18nServiceSingleton = await container.getAsync(MidwayI18nServiceSingleton); + const i18nServiceSingleton = await container.getAsync( + MidwayI18nServiceSingleton + ); for (const locale of i18nServiceSingleton.getLocaleList('joi')) { - localeMapping.set(locale, i18nServiceSingleton.getOriginLocaleJSON(locale, 'joi')); + localeMapping.set( + locale, + i18nServiceSingleton.getOriginLocaleJSON(locale, 'joi') + ); } this.defaultValidatorOptions = configService.getConfiguration('joi'); @@ -42,15 +47,19 @@ export default (container: IMidwayContainer) => { options: ValidationExtendOptions, validatorOptions: any = {} ): ValidateResult { - const locale = localeMapping.has(options.locale) ? options.locale : (localeMapping.has(options.fallbackLocale) ? options.fallbackLocale : 'en-us'); + const locale = localeMapping.has(options.locale) + ? options.locale + : localeMapping.has(options.fallbackLocale) + ? options.fallbackLocale + : 'en-us'; const newValidatorOptions = { errors: { - language: locale + language: locale, }, messages: localeMapping.get(locale), ...this.defaultValidatorOptions, ...validatorOptions, - } + }; const result = schema.validate(value, newValidatorOptions); if (result.error) { @@ -87,5 +96,5 @@ export default (container: IMidwayContainer) => { public getStringSchema(): Joi.StringSchema { return Joi.string().required(); } - }; -} + })(); +}; diff --git a/packages/validation-zod/src/index.ts b/packages/validation-zod/src/index.ts index 3923991da47..97263f14746 100644 --- a/packages/validation-zod/src/index.ts +++ b/packages/validation-zod/src/index.ts @@ -1,11 +1,16 @@ import { IMidwayContainer, MidwayConfigService } from '@midwayjs/core'; -import { getRuleMeta, IValidationService, ValidateResult, ValidationExtendOptions } from '@midwayjs/validation'; +import { + getRuleMeta, + IValidationService, + ValidateResult, + ValidationExtendOptions, +} from '@midwayjs/validation'; import { z, ParseParams } from 'zod'; import { fromError } from 'zod-validation-error'; import * as i18next from 'i18next'; import { makeZodI18nMap } from 'zod-i18n-map'; -import * as en from "zod-i18n-map/locales/en/zod.json"; -import * as cn from "zod-i18n-map/locales/zh-CN/zod.json"; +import * as en from 'zod-i18n-map/locales/en/zod.json'; +import * as cn from 'zod-i18n-map/locales/zh-CN/zod.json'; import { MidwayI18nServiceSingleton } from '@midwayjs/i18n'; // 将 i18n 的标准 locale 转换为 zod-i18n-map 提供的 locale @@ -41,7 +46,7 @@ const lngMapping = { 'tr-tr': 'tr', 'uk-ua': 'uk-UA', 'zh-cn': 'zh-CN', - 'zh-tw': 'zh-TW' + 'zh-tw': 'zh-TW', }; const localeMapping = new Map(); @@ -58,15 +63,18 @@ export default async (container: IMidwayContainer) => { zod: cn, }, }, - } + }, }); - return new class implements IValidationService { + return new (class implements IValidationService { defaultZodOptions: z.ParseParams; async init(container: IMidwayContainer) { - const i18nServiceSingleton = await container.getAsync(MidwayI18nServiceSingleton); + const i18nServiceSingleton = await container.getAsync( + MidwayI18nServiceSingleton + ); const configService = await container.getAsync(MidwayConfigService); - this.defaultZodOptions = configService.getConfiguration('zod'); + this.defaultZodOptions = + configService.getConfiguration('zod'); for (const locale of i18nServiceSingleton.getLocaleList('zod')) { const instance = i18next.createInstance(); @@ -91,14 +99,21 @@ export default async (container: IMidwayContainer) => { validatorOptions: Partial = {} ) { const res = {} as ValidateResult; - const locale = localeMapping.has(options.locale) ? options.locale : (localeMapping.has(options.fallbackLocale) ? options.fallbackLocale : 'en-us'); + const locale = localeMapping.has(options.locale) + ? options.locale + : localeMapping.has(options.fallbackLocale) + ? options.fallbackLocale + : 'en-us'; const newValidatorOptions = { errorMap: localeMapping.get(locale), ...this.defaultZodOptions, ...validatorOptions, - } + }; - const { success, data, error } = schema.safeParse(value, newValidatorOptions); + const { success, data, error } = schema.safeParse( + value, + newValidatorOptions + ); if (success) { res.status = true; res.value = data; @@ -130,5 +145,5 @@ export default async (container: IMidwayContainer) => { getStringSchema(): z.ZodType { return z.string(); } - }; -} + })(); +}; diff --git a/packages/validation/src/configuration.ts b/packages/validation/src/configuration.ts index 46818655758..2cec029d907 100644 --- a/packages/validation/src/configuration.ts +++ b/packages/validation/src/configuration.ts @@ -59,10 +59,18 @@ export class ValidationConfiguration { } ); - const validators = this.configService.getConfiguration Promise>>>('validation.validators'); + const validators = this.configService.getConfiguration< + Record< + string, + (container: IMidwayContainer) => Promise> + > + >('validation.validators'); if (validators) { for (const [name, validatorHandler] of Object.entries(validators)) { - registry.register(name, await validatorHandler(this.applicationContext)); + registry.register( + name, + await validatorHandler(this.applicationContext) + ); } } } diff --git a/packages/validation/src/error.ts b/packages/validation/src/error.ts index 5943231f150..bd675fe3185 100644 --- a/packages/validation/src/error.ts +++ b/packages/validation/src/error.ts @@ -15,8 +15,13 @@ export class MidwayValidationError extends MidwayHttpError { export class MidwayValidatorNotFoundError extends MidwayHttpError { constructor(name: string, status: number, cause?: Error) { - super(`validator ${name} not found`, status, ValidateErrorCode.VALIDATOR_NOT_FOUND, { - cause, - }); + super( + `validator ${name} not found`, + status, + ValidateErrorCode.VALIDATOR_NOT_FOUND, + { + cause, + } + ); } -} \ No newline at end of file +} diff --git a/packages/validation/src/pipe.ts b/packages/validation/src/pipe.ts index 4e0d21cbe72..86c51e228cb 100644 --- a/packages/validation/src/pipe.ts +++ b/packages/validation/src/pipe.ts @@ -23,7 +23,8 @@ export abstract class AbstractValidationPipe implements PipeTransform { options: TransformOptions, schema: any ): any { - const validateOptions: ValidationDecoratorOptions = this.parseValidationOptions(options); + const validateOptions: ValidationDecoratorOptions = + this.parseValidationOptions(options); return ( this.validationService.validateWithSchema( schema, @@ -39,8 +40,16 @@ export abstract class AbstractValidationPipe implements PipeTransform { if (options.metaType.isBaseType || !options.metaType.originDesign) { return value; } - const validateOptions: ValidationDecoratorOptions = this.parseValidationOptions(options); - return this.validationService.validate(options.metaType.originDesign as any, value, validateOptions, validateOptions?.validatorOptions)?.value ?? value; + const validateOptions: ValidationDecoratorOptions = + this.parseValidationOptions(options); + return ( + this.validationService.validate( + options.metaType.originDesign as any, + value, + validateOptions, + validateOptions?.validatorOptions + )?.value ?? value + ); } protected parseValidationOptions( diff --git a/packages/validation/src/service.ts b/packages/validation/src/service.ts index 0e9f2a517b8..aeac76da00b 100644 --- a/packages/validation/src/service.ts +++ b/packages/validation/src/service.ts @@ -4,15 +4,13 @@ import { Init, MetadataManager, Singleton, - MidwayConfigService, MidwayCommonError, + MidwayConfigService, + MidwayCommonError, IMidwayContainer, - ApplicationContext + ApplicationContext, } from '@midwayjs/core'; import { RULES_KEY } from './constants'; -import { - formatLocale, - I18nOptions, -} from '@midwayjs/i18n'; +import { formatLocale, I18nOptions } from '@midwayjs/i18n'; import { ValidateResult, ValidationOptions } from './interface'; import { MidwayValidationError, MidwayValidatorNotFoundError } from './error'; import { registry } from './registry'; @@ -63,10 +61,18 @@ export class ValidationService { ): ValidateResult | undefined { const validator = this.getValidator(validationOptions?.defaultValidator); if (!validator) { - throw new MidwayValidatorNotFoundError(validationOptions?.defaultValidator, 500); + throw new MidwayValidatorNotFoundError( + validationOptions?.defaultValidator, + 500 + ); } const anySchema = validator.getSchema(ClzType); - return this.validateWithSchema(anySchema, value, validationOptions, validatorOptions); + return this.validateWithSchema( + anySchema, + value, + validationOptions, + validatorOptions + ); } public validateWithSchema( @@ -82,16 +88,27 @@ export class ValidationService { const validator = this.getValidator(validationOptions?.defaultValidator); if (!validator) { - throw new MidwayValidatorNotFoundError(validationOptions?.defaultValidator, 500); + throw new MidwayValidatorNotFoundError( + validationOptions?.defaultValidator, + 500 + ); } - const res = validator.validateWithSchema(schema, value, { - locale: formatLocale(validationOptions?.locale), - fallbackLocale: this.defaultFallbackLocale, - }, validatorOptions); - - const throwValidateError = validationOptions?.throwValidateError ?? this.validateConfig.throwValidateError; - const errorStatus = validationOptions?.errorStatus ?? this.validateConfig.errorStatus; + const res = validator.validateWithSchema( + schema, + value, + { + locale: formatLocale(validationOptions?.locale), + fallbackLocale: this.defaultFallbackLocale, + }, + validatorOptions + ); + + const throwValidateError = + validationOptions?.throwValidateError ?? + this.validateConfig.throwValidateError; + const errorStatus = + validationOptions?.errorStatus ?? this.validateConfig.errorStatus; if (res.status === false && throwValidateError) { throw new MidwayValidationError( diff --git a/site/docs/extensions/validation.md b/site/docs/extensions/validation.md new file mode 100644 index 00000000000..3fc8a8e59c6 --- /dev/null +++ b/site/docs/extensions/validation.md @@ -0,0 +1,575 @@ +# 参数校验 + +本模块自 v4.0.0 起替换 @midwayjs/validate 组件。新版本提供了更灵活的验证器扩展机制,支持多种验证器(如 Joi、Zod 等)的无缝切换,并提供了更好的类型支持和性能优化。 + +相关信息: + +| 描述 | | +| ----------------- | ---- | +| 可用于标准项目 | ✅ | +| 可用于 Serverless | ✅ | +| 可用于一体化 | ✅ | +| 包含独立主框架 | ❌ | +| 包含独立日志 | ❌ | + +## 背景 + +最常用参数校验的地方是控制器(Controller),同时你也可以在任意的 Class 中使用这个能力。 + +我们以控制器(Controller)中使用为例,还是那个 user。 + +```typescript +➜ my_midway_app tree +. +├── src +│ ├── controller +│ │ └── user.ts +│ ├── interface.ts +│ └── service +│ └── user.ts +├── test +├── package.json +└── tsconfig.json +``` + +普通情况下,我们从 `body` 上拿到所有 Post 结果,并进行一些校验。 + +```typescript +// 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` 装饰器,用来 **快速定义校验的规则**,帮助用户 **减少这些重复的代码**。 + +## 安装依赖 + +首先,你需要安装 validation 组件和你选择的验证器: + +```bash +## 安装 validation 组件 +$ npm i @midwayjs/validation@4 --save + +## 选择一个或多个验证器 +$ npm i @midwayjs/validation-joi@4 --save # 使用 Joi 验证器 +$ npm i @midwayjs/validation-zod@4 --save # 使用 Zod 验证器 +``` + +或者在 `package.json` 中增加如下依赖后,重新安装。 + +```json +{ + "dependencies": { + "@midwayjs/validation": "^4.0.0", + "@midwayjs/validation-joi": "^4.0.0", // 使用 Joi 验证器 + "@midwayjs/validation-zod": "^4.0.0", // 使用 Zod 验证器 + // ... + }, + "devDependencies": { + // ... + } +} +``` + +## 开启组件 + +在 `configuration.ts` 中增加组件: + +```typescript +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() { + // ... + } +} +``` + +在配置文件中设置验证器: + +```typescript +// src/config/config.default.ts +import * as joi from '@midwayjs/validation-joi'; // 导入 Joi 验证器 +import * as zod from '@midwayjs/validation-zod'; // 导入 Zod 验证器 + +export default { + // ... + validation: { + // 配置验证器 + validators: { + joi, // 使用 Joi 验证器 + zod, // 使用 Zod 验证器 + }, + // 设置默认验证器 + defaultValidator: 'joi' // 或 'zod' + } +} +``` + +## 使用 Joi 验证器 + +如果你选择使用 Joi 验证器,需要先安装 `joi` 包: + +```bash +$ npm i joi --save +``` + +然后就可以使用 Joi 的验证规则: + +```typescript +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; +} +``` + +## 使用 Zod 验证器 + +如果你选择使用 Zod 验证器,需要先安装 `zod` 包: + +```bash +$ npm i zod --save +``` + +然后就可以使用 Zod 的验证规则: + +```typescript +import { Rule } from '@midwayjs/validation'; +import { z } from 'zod'; + +export class UserDTO { + @Rule(z.number().min(1)) + id: number; + + @Rule(z.string().min(1)) + firstName: string; + + @Rule(z.string().max(10)) + lastName: string; + + @Rule(z.number().max(60)) + age: number; +} +``` + +## 校验参数 + +定义完类型之后,就可以直接在业务代码中使用了。 + +```typescript +// 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 变为数字。 + +```typescript +async updateUser(@Body() user: UserDTO ) { + // typeof user.id === 'number' +} +``` + +如果需要对方法级别单独配置信息,可以使用 `@Validate` 装饰器,比如单独配置错误状态。 + +```typescript +// 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 + } +} +``` + +## 通用场景校验 + +如果参数不是 DTO,可以使用 `@Valid` 装饰器进行校验,`@Valid` 装饰器可以直接传递一个验证规则。 + +使用 Joi: +```typescript +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: +```typescript +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` 装饰器来进行校验。 + +```typescript +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`,则可以使用组件提供的管道进行校验。 + +```typescript +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` +- `ParseFloatPipe` +- `ParseBoolPipe` +- `DefaultValuePipe` + +## 自定义校验管道 + +如果默认的管道不满足需求,可以通过继承,快速实现一个自定义校验管道。 + +```typescript +import { Pipe } from '@midwayjs/core'; +import { ParsePipe } from '@midwayjs/validation'; +import * as Joi from 'joi'; // 或者使用 zod + +@Pipe() +export class ParseCustomDataPipe extends ParsePipe { + getSchema() { + return Joi.number().integer().required(); // 使用 Joi + // 或者使用 Zod + // return z.number().int(); + } +} +``` + +## 多语言 + +在 Validation 中,同时依赖了 [i18n](./i18n) 组件来实现校验消息的国际化。 + +### Joi 多语言支持 + +Joi 验证器默认提供了 `en_US` 和 `zh_CN` 两种校验的翻译文本。 + +### Zod 多语言支持 + +Zod 验证器使用了 `zod-i18n-map` 提供的翻译,支持更多的语言,包括: + +- 简体中文 (zh-CN) +- 繁体中文 (zh-TW) +- 英语 (en) +- 日语 (ja) +- 韩语 (ko) +- 俄语 (ru) +等多种语言。 + +### 通过装饰器指定语言 + +```typescript +@Controller('/user') +export class UserController { + @Post('/') + @Validate({ + locale: 'en_US', // 或其他支持的语言代码 + }) + async getUser(@Body() bodyData: UserDTO) { + // ... + } +} +``` + +### 通过参数指定语言 + +除了装饰器指定,我们也可以使用标准的 i18n 通过参数指定语言的方式。 + +``` +Get /user/get_user?locale=zh_CN +``` + +## 默认配置 + +我们可以对 validation 组件做一些配置。 + +| 配置项 | 类型 | 描述 | +| ----------------- | ----------------------------- | ------------------------------------------------------------ | +| errorStatus | number | 当校验出错时,返回的 Http 状态码,在 http 场景生效,默认 422 | +| locale | string | 校验出错文本的默认语言,默认为 `en_US`,会根据 i18n 组件的规则切换 | +| validators | Record | 配置要使用的验证器 | +| defaultValidator | string | 设置默认使用的验证器 | + +## 独立的校验服务 + +组件底层提供了单例的 `ValidationService` 校验服务类,如有必要,可以在中间件或者独立的服务中使用。 + +```typescript +import { ValidationService } from '@midwayjs/validation'; + +export class UserService { + @Inject() + validateService: ValidationService; + + async invoke() { + // ... + const result = this.validateService.validate(UserDTO, { + name: 'harry', + nickName: 'harry', + }); + + // 失败返回 result.error + // 成功返回 result.value + } +} +``` + +## 自定义验证器 + +除了使用内置的 Joi 和 Zod 验证器,你还可以实现自己的验证器。验证器需要实现 `IValidationService` 接口: + +```typescript +import { IMidwayContainer } from '@midwayjs/core'; +import { IValidationService, ValidateResult, ValidationExtendOptions } from '@midwayjs/validation'; + +class CustomValidator implements IValidationService { + // 初始化验证器 + async init(container: IMidwayContainer): Promise { + // 在这里进行初始化操作 + } + + // 使用 schema 进行验证 + validateWithSchema( + schema: any, + value: any, + options: ValidationExtendOptions, + validatorOptions: any + ): ValidateResult { + const res = {} as ValidateResult; + try { + // 实现你的验证逻辑 + res.status = true; + res.value = value; // 可以在这里对值进行转换 + } catch (error) { + res.status = false; + res.error = error; + res.message = error.message; + } + return res; + } + + // 获取 schema + getSchema(ClzType: any): any { + // 实现获取 schema 的逻辑 + } + + // 获取基础类型的 schema + getIntSchema(): any { + // 返回整数类型的 schema + } + + getBoolSchema(): any { + // 返回布尔类型的 schema + } + + getFloatSchema(): any { + // 返回浮点数类型的 schema + } + + getStringSchema(): any { + // 返回字符串类型的 schema + } +} + +// 导出验证器工厂函数 +export default async (container: IMidwayContainer) => { + return new CustomValidator(); +}; +``` + +然后在配置中使用你的自定义验证器: + +```typescript +// src/config/config.default.ts +import customValidator from './custom.validator'; + +export default { + validation: { + validators: { + custom: customValidator, // 注册自定义验证器 + }, + defaultValidator: 'custom' // 设置为默认验证器 + } +}; +``` + +## 常见问题 + +### 1、允许未定义的字段 + +对于 Joi 验证器,可以通过以下配置允许未定义的字段: + +```typescript +// src/config/config.default.ts +export default { + // ... + joi: { + allowUnknown: true, + } +}; +``` + +对于 Zod 验证器: + +```typescript +// src/config/config.default.ts +export default { + // ... + zod: { + passthrough: true, + } +}; +``` + +### 2、处理校验错误 + +上面提到,Midway 会在校验失败时抛出 `MidwayValidationError` 错误,我们可以在 [异常处理器](../error_filter) 中处理。 + +```typescript +// src/filter/validate.filter +import { Catch } from '@midwayjs/core'; +import { MidwayValidationError } from '@midwayjs/validation'; +import { Context } from '@midwayjs/koa'; + +@Catch(MidwayValidationError) +export class ValidateErrorFilter { + async catch(err: MidwayValidationError, ctx: Context) { + return { + status: 422, + message: '校验参数错误,' + err.message, + }; + } +} +``` + +### 3、验证器切换 + +如果你在同一个项目中配置了多个验证器,可以通过 `@Rule` 装饰器的参数来选择使用哪个验证器: + +```typescript +import { Rule } from '@midwayjs/validation'; +import * as Joi from 'joi'; +import { z } from 'zod'; + +export class UserDTO { + @Rule(Joi.number().required()) // 使用 Joi 验证器 + id: number; + + @Rule(z.string().min(1)) // 使用 Zod 验证器 + name: string; +} +``` + +验证器的选择会自动根据传入的规则类型来确定。 \ No newline at end of file