验证
本指南介绍在 AdonisJS 中使用 VineJS 验证器在控制器层进行验证。你将学习如何:
- 在控制器中创建并使用 VineJS 验证器
- 通过自动内容协商处理验证错误
- 全局自定义错误消息或使用 i18n
- 验证查询字符串、参数、请求头和 Cookie
- 向验证器传递元数据以实现特定上下文的验证
- 将 Lucid ORM 数据库验证规则与 VineJS 结合使用
- 在作业和命令等非 HTTP 请求场景中使用验证器
概述
AdonisJS 中的验证发生在控制器层,让你能够在提供的数据无效时及早验证并中止请求。这种方法让你能够围绕表单或预期的请求数据来建模验证,而不是将验证与模型层耦合。
一旦数据通过验证,你就可以完全信任它,并将其传递给应用的其他层(无论是服务、数据模型还是业务逻辑),而无需额外的检查。这在你的应用架构中创建了一个清晰的信任边界。
VineJS —— 验证库
AdonisJS 预装了 VineJS ,一个速度极快的验证库。虽然你可以使用不同的验证库并卸载 VineJS,但 VineJS 提供了专为 AdonisJS 设计的额外验证规则,例如检查数据库中的唯一性,或验证 multipart 文件上传。
使用 Lucid ORM 时,你可以在 VineJS 模式中使用数据库验证规则,例如 unique 和 exists。这些规则会在验证期间查询你的数据库,在检查唯一邮箱或确保外键指向现有行时很有用。请参阅
Lucid 验证规则文档
了解所有可用的数据库规则和选项。
创建你的第一个验证器
AdonisJS 中的验证器存储在 app/validators 目录中,每个资源一个文件,包含该资源所有操作的验证器。让我们为博客文章创建一个验证器。
-
生成验证器文件
运行以下命令创建一个新的验证器。
node ace make:validator post这会在
app/validators/post.ts创建一个带有 VineJS 导入的空验证器文件。app/validators/post.tsimport vine from '@vinejs/vine' -
定义你的验证模式
添加一个用于创建文章的验证器。我们将验证
title、body和publishedAt字段。app/validators/post.tsimport vine from '@vinejs/vine' export const createPostValidator = vine.create({ title: vine.string(), body: vine.string(), publishedAt: vine.date() }) -
在你的控制器中使用验证器
将验证器导入到你的控制器中,并使用
request.validateUsing()方法验证请求体。app/controllers/posts_controller.tsimport { createPostValidator } from '#validators/post' import type { HttpContext } from '@adonisjs/core/http' export default class PostsController { async store({ request }: HttpContext) { const payload = await request.validateUsing(createPostValidator) // 现在你可以信任并使用 payload // 创建文章、保存到数据库等 } }request.validateUsing()方法会自动验证请求体。你无需显式传递请求体数据(request对象已经可以访问它)。如果验证失败,会抛出异常并自动处理。验证后的 payload 会被返回,可以在你的应用中安全使用。
理解错误处理
当验证失败时,request.validateUsing() 方法会抛出异常。你无需手动处理这个异常。AdonisJS 的
全局异常处理器
会使用内容协商,根据请求类型自动将其转换为适当的响应。
内容协商如何工作
AdonisJS 会检测客户端期望的响应类型,并相应地格式化验证错误。
| 应用类型 | 行为 | 错误格式 |
|---|---|---|
| Hypermedia(服务端渲染) | 重定向回表单 | 会话中的闪现消息 |
| Inertia | 重定向回表单 | 通过 Inertia 状态共享 |
| API(JSON) | 返回 422 状态 | 带有 errors 数组的 JSON |
对于超媒体应用(传统的服务端渲染应用)
- 用户被重定向回表单
- 错误消息通过 AdonisJS 的会话闪现存储闪现到会话中
- 你可以使用
@field.error组件在模板中显示这些错误
对于 Inertia 应用
- 用户被重定向回表单
- 错误消息通过 Inertia 的共享状态共享
- 错误在你的前端组件中自动可用
对于 API 请求(期望 JSON 的客户端)
- 返回状态码为 422 的 JSON 响应
- 响应包含一个带有所有验证错误消息的
errors数组 - 每个错误都包含字段名、失败的规则和错误消息
{
"errors": [
{
"field": "title",
"rule": "required",
"message": "The title field is required"
},
{
"field": "publishedAt",
"rule": "date",
"message": "The publishedAt field must be a valid date"
}
]
}
这种自动处理意味着你只需编写一次验证逻辑,它就能在所有应用类型中正确工作,无需额外代码。
你无需将 validateUsing() 包裹在 try/catch 块中。全局异常处理器已经将验证异常转换为适当的响应。只有在你需要与默认行为不同的自定义错误处理逻辑时,才使用 try/catch。
自定义错误消息
默认情况下,VineJS 提供通用的错误消息。你可以通过两种方式全局自定义这些消息。你可以使用自定义的 VineJS 错误消息提供器 ,也可以使用 i18n 包来提供本地化消息。
使用自定义消息提供器
创建一个 start/validator.ts 文件来配置全局自定义消息。首先,生成这个预加载文件。
node ace make:preload validator
然后使用 SimpleMessagesProvider 定义你的自定义消息。
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.required': 'Please choose a username for your account',
})
{{ field }} 占位符会自动替换为实际的字段名。字段特定的消息(如 username.required)优先于全局消息。
使用 i18n 进行本地化消息
对于需要多语言的应用,使用 @adonisjs/i18n 包在翻译文件中定义验证消息。这让你能够根据用户的区域设置提供不同语言的验证错误。
首先,安装并配置 i18n 包(完整的设置说明请参阅 i18n 指南 )。然后在特定语言的 JSON 文件中定义你的消息。
{
"shared": {
"fields": {
"first_name": "First name",
"email": "Email address"
},
"messages": {
"required": "Enter {field}",
"username.required": "Choose a username for your account",
"email": "The email must be valid"
}
}
}
fields 对象为你的表单字段定义人类可读的名称,而 messages 对象定义错误消息。这种分离让你可以跨不同的消息复用字段名。
验证不同的数据源
虽然请求体是最常见的要验证的数据源,但你经常需要验证 HTTP 请求的其他部分,例如查询字符串、路由参数、请求头或 Cookie。
验证查询字符串、参数、请求头和 Cookie
为你想要验证的每个数据源在模式中定义嵌套对象。
import vine from '@vinejs/vine'
export const showUserValidator = vine.create({
// 验证请求体和查询字符串值
username: vine.string(),
password: vine.string(),
filters: vine.object({
page: vine.number().optional(),
limit: vine.number().optional()
}),
// 验证路由参数
params: vine.object({
id: vine.number()
}),
// 验证 Cookie
cookies: vine.object({
sessionId: vine.string()
}),
// 验证请求头
headers: vine.object({
'x-api-key': vine.string()
})
})
验证器会根据这些属性名(params、cookies、headers)自动从正确的位置提取数据。
当你调用 request.validateUsing() 时,所有这些来源会同时被验证。
import { showUserValidator } from '#validators/user'
import type { HttpContext } from '@adonisjs/core/http'
export default class UsersController {
async show({ request }: HttpContext) {
const payload = await request.validateUsing(showUserValidator)
// 访问已验证的数据
console.log(payload.params.id)
console.log(payload.filters.page)
console.log(payload.cookies.sessionId)
}
}
这种方法让你可以在一个地方验证所有传入的请求数据,为你的控制器逻辑创建完整的信任边界。
向验证器传递元数据
有时验证器需要访问不属于被验证数据的、与请求特定的信息。一个常见的例子是验证邮箱唯一性,同时允许当前用户保留其现有的邮箱。
在验证器中定义元数据
使用 withMetaData() 方法定义你的验证器期望接收的元数据。
import vine from '@vinejs/vine'
export const updateUserValidator = vine
.withMetaData<{ userId: number }>()
.create({
email: vine.string().email().unique({
table: 'users',
filter: (db, value, field) => {
db.whereNot('id', field.meta.userId)
}
})
})
withMetaData<T>() 方法接受一个 TypeScript 类型,定义元数据的形状。在验证规则内部,你可以通过 field.meta 访问这个元数据。在这个例子中,filter 回调在检查唯一性时排除了当前用户的行。
在验证期间传递元数据
在调用 validateUsing() 时提供元数据。
import { updateUserValidator } from '#validators/user'
import type { HttpContext } from '@adonisjs/core/http'
export default class UsersController {
async update({ request, auth }: HttpContext) {
const payload = await request.validateUsing(updateUserValidator, {
meta: {
userId: auth.user!.id
}
})
}
}
验证器使用提供的 userId 将用户自己的记录从唯一性检查中排除。每当你验证逻辑需要来自当前请求上下文的信息(例如已认证的用户、租户 ID 或其他请求特定的值)时,这种模式都很有用。
在 HTTP 请求之外使用验证器
验证器并不局限于 HTTP 请求。你可以在任何需要验证数据的地方使用它们,例如在后台作业、控制台命令或服务类中。
直接验证数据
直接在你的已编译验证器上调用 validate() 方法。
import { createPostValidator } from '#validators/post'
export default class ImportPostsJob {
async handle(data: unknown[]) {
for (const item of data) {
try {
const validPost = await createPostValidator.validate(item)
// 处理有效的文章数据
await Post.create(validPost)
} catch (error) {
// 处理此项的验证错误
console.error('Invalid post data:', error.messages)
}
}
}
}
validate() 方法在成功时返回已验证的 payload,或在验证失败时抛出异常。与 request.validateUsing() 不同,在非 HTTP 上下文中你通常需要自己处理这些异常,因为没有自动的错误响应。
这种方法确保了整个应用中验证逻辑的一致性,无论是处理 HTTP 请求、处理后台作业,还是在任何其他上下文中验证数据。
下一步
既然你已经了解了 AdonisJS 中的验证,你可以:
- 探索 VineJS 文档 以发现所有可用的模式类型和验证规则
- 查看 Lucid 验证规则 来验证唯一和已存在的数据库值
- 了解 闪现消息 以在模板中显示验证错误
- 阅读 异常处理指南 以理解 AdonisJS 如何处理验证错误
- 查看 i18n 指南 以将验证消息本地化为多种语言