校验
AdonisJS 中的的数据校验通常在控制器层执行。这确保你在应用处理请求后立即校验用户输入,并能在响应中发送错误,这些错误可以显示在表单字段旁边。
校验完成后,你可以使用这些可信的数据来执行其余的操作,比如数据库查询、调度队列任务、发送邮件等。
选择校验库
AdonisJS 核心团队创建了一个与框架无关的的数据校验库,名为 VineJS。以下是使用 VineJS 的一些原因。
-
它是 Node.js 生态中速度最快的校验库之一。
-
在运行时校验的同时提供静态类型安全。
-
它已预配置在
web和api启动套件中。 -
AdonisJS 官方包通过自定义规则扩展了 VineJS。例如,Lucid 为 VineJS 贡献了
unique和exists规则。
不过,AdonisJS 在技术上并不强制你使用 VineJS。你可以使用任何适合你或你团队的校验库。只需卸载 @vinejs/vine 包并安装你想使用的包即可。
配置 VineJS
使用以下命令安装并配置 VineJS。
另请参阅:VineJS 文档
node ace add vinejs
-
使用检测到的包管理器安装
@vinejs/vine包。 -
在
adonisrc.ts文件中注册以下 service provider。{providers: [// ...其他 providers() => import('@adonisjs/core/providers/vinejs_provider')]}
使用校验器
VineJS 使用了校验器(validator)的概念。你为应用能够执行的每个动作创建一个校验器。例如:为创建新帖定义一个校验器,为更新帖子定义另一个,可能还会为删除帖子定义一个校验器。
我们将以一个博客为例,定义创建/更新帖子的校验器。让我们先注册几个路由和 PostsController。
import router from '@adonisjs/core/services/router'
const PostsController = () => import('#controllers/posts_controller')
router.post('posts', [PostsController, 'store'])
router.put('posts/:id', [PostsController, 'update'])
node ace make:controller post store update
import { HttpContext } from '@adonisjs/core/http'
export default class PostsController {
async store({}: HttpContext) {}
async update({}: HttpContext) {}
}
创建校验器
一旦你创建了 PostsController 并定义了路由,就可以使用以下 ace 命令来创建校验器。
另请参阅:Make validator 命令
node ace make:validator post
校验器创建在 app/validators 目录中。校验器文件默认是空的,你可以用它导出多个校验器。每个校验器是一个 const 变量,持有 vine.compile 方法的结果。
在下面的示例中,我们定义了 createPostValidator 和 updatePostValidator。两个校验器的 schema 略有不同。在创建时,我们允许用户为帖子提供自定义 slug,而在更新时我们禁止修改它。
不必过于担心校验器 schema 中的重复。我们建议你选择易于理解的 schema,而不是不惜一切代价避免重复。Wet 代码库类比 或许能帮你接纳重复。
import vine from '@vinejs/vine'
/**
* Validates the post's creation action
*/
export const createPostValidator = vine.compile(
vine.object({
title: vine.string().trim().minLength(6),
slug: vine.string().trim(),
description: vine.string().trim().escape()
})
)
/**
* Validates the post's update action
*/
export const updatePostValidator = vine.compile(
vine.object({
title: vine.string().trim().minLength(6),
description: vine.string().trim().escape()
})
)
在控制器中使用校验器
让我们回到 PostsController,使用校验器来校验请求体。你可以使用 request.all() 方法访问请求体。
import { HttpContext } from '@adonisjs/core/http'
import {
createPostValidator,
updatePostValidator
} from '#validators/post_validator'
export default class PostsController {
async store({ request }: HttpContext) {
const data = request.all()
const payload = await createPostValidator.validate(data)
return payload
}
async update({ request }: HttpContext) {
const data = request.all()
const payload = await updatePostValidator.validate(data)
return payload
}
}
就是这样!校验用户输入在你的控制器里只需两行代码。校验后的输出具有从 schema 推断出的静态类型信息。
此外,你不必将 validate 方法调用包裹在 try/catch 中。因为在出错的情况下,AdonisJS 会自动将错误转换为 HTTP 响应。
错误处理
HttpExceptionHandler 会自动将校验错误转换为 HTTP 响应。异常处理器使用内容协商,并根据 Accept 头的值返回响应。
你可能想看看 ExceptionHandler 代码库,了解校验异常是如何转换为 HTTP 响应的。
此外,session 中间件覆盖了 renderValidationErrorAsHTML 方法,并使用 flash 消息将校验错误分享给表单。
-
带有
Accept=application/json头的 HTTP 请求会收到使用 SimpleErrorReporter 创建的错误消息数组。 -
带有
Accept=application/vnd.api+json头的 HTTP 请求会收到按照 JSON API 规范格式化的错误消息数组。 -
使用 session 包 渲染的服务器端表单会通过 session flash 消息 收到错误。
-
所有其他请求会以纯文本形式收到错误。
request.validateUsing 方法
在控制器内执行校验的推荐方式是使用 request.validateUsing 方法。使用 request.validateUsing 方法时,你不必显式定义校验数据;请求体、查询字符串值以及文件会被合并在一起,作为数据传递给校验器。
import { HttpContext } from '@adonisjs/core/http'
import {
createPostValidator,
updatePostValidator
} from '#validators/posts_validator'
export default class PostsController {
async store({ request }: HttpContext) {
const data = request.all()
const payload = await createPostValidator.validate(data)
const payload = await request.validateUsing(createPostValidator)
}
async update({ request }: HttpContext) {
const data = request.all()
const payload = await updatePostValidator.validate(data)
const payload = await request.validateUsing(updatePostValidator)
}
}
校验 cookie、请求头和路由参数
使用 request.validateUsing 方法时,你可以像下面这样校验 cookie、请求头和路由参数。
const validator = vine.compile(
vine.object({
// 请求体中的字段
username: vine.string(),
password: vine.string(),
// 校验 cookie
cookies: vine.object({
}),
// 校验请求头
headers: vine.object({
}),
// 校验路由参数
params: vine.object({
}),
})
)
await request.validateUsing(validator)
向校验器传递元数据
由于校验器定义在请求生命周期之外,它们无法直接访问请求数据。这通常是有益的,因为它让校验器可以在 HTTP 请求生命周期之外复用。
不过,如果校验器需要访问某些运行时数据,你必须在 validate 方法调用期间将其作为元数据传入。
让我们以 unique 校验规则为例。我们希望确保用户邮箱在数据库中是唯一的,但跳过当前登录用户对应的那一行。
export const updateUserValidator = vine
.compile(
vine.object({
email: vine.string().unique(async (db, value, field) => {
const user = await db
.from('users')
.whereNot('id', field.meta.userId)
.where('email', value)
.first()
return !user
})
})
)
在上面的示例中,我们通过 meta.userId 属性访问当前登录的用户。让我们看看如何在 HTTP 请求期间传入 userId。
async update({ request, auth }: HttpContext) {
await request.validateUsing(
updateUserValidator,
{
meta: {
userId: auth.user!.id
}
}
)
}
让元数据类型安全
在前面的示例中,我们必须记得在校验时传入 meta.userId。如果我们能让 TypeScript 提醒我们这件事就太好了。
在下面的示例中,我们使用 vine.withMetaData 函数来定义我们期望在 schema 中使用的元数据的静态类型。
export const updateUserValidator = vine
.withMetaData<{ userId: number }>()
.compile(
vine.object({
email: vine.string().unique(async (db, value, field) => {
const user = await db
.from('users')
.whereNot('id', field.meta.userId)
.where('email', value)
.first()
return !user
})
})
)
请注意,VineJS 在运行时不校验元数据。不过,如果你想要这样做,可以向 withMetaData 方法传入一个回调来手动执行校验。
vine.withMetaData<{ userId: number }>((meta) => {
// 校验元数据
})
配置 VineJS
你可以在 start 目录内创建一个预加载文件 来配置 VineJS,设置自定义错误消息,或使用自定义错误报告器。
node ace make:preload validator
在下面的示例中,我们定义自定义错误消息。
import vine, { SimpleMessagesProvider } from '@vinejs/vine'
vine.messagesProvider = new SimpleMessagesProvider({
// 适用于所有字段
'required': 'The {{ field }} field is required',
'string': 'The value of {{ field }} field must be a string',
'email': 'The value is not a valid email address',
// username 字段的错误消息
'username.required': 'Please choose a username for your account',
})
在下面的示例中,我们注册一个自定义错误报告器。
import vine, { SimpleMessagesProvider } from '@vinejs/vine'
import { JSONAPIErrorReporter } from '../app/validation_reporters.js'
vine.errorReporter = () => new JSONAPIErrorReporter()
AdonisJS 贡献的规则
以下是 AdonisJS 贡献的 VineJS 规则列表。
vine.fileschema 类型由 AdonisJS 核心包添加。
下一步
- 了解更多关于在 VineJS 中使用自定义消息。
- 了解更多关于在 VineJS 中使用错误报告器。
- 阅读 VineJS 的 schema API 文档。
- 使用 i18n 翻译 来定义校验错误消息。