验证
在 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
文件中注册以下服务提供程序。{providers: [// ...其他提供程序() => import('@adonisjs/core/providers/vinejs_provider')]}
使用验证器
VineJS 使用了验证器的概念。你为应用程序可以执行的每个操作创建一个验证器。例如:为 创建新帖子 定义一个验证器,为 更新帖子 定义另一个验证器,也许还有一个用于 删除帖子 的验证器。
我们将以博客为例,定义用于创建/更新帖子的验证器。让我们首先注册几条路由和 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 命令创建一个验证器。
另请参阅:创建验证器命令
node ace make:validator post
验证器创建在 app/validators
目录中。验证器文件默认为空,你可以从中导出多个验证器。每个验证器都是一个 const
变量,保存 vine.compile
方法的结果。
在以下示例中,我们定义了 createPostValidator
和 updatePostValidator
。两个验证器的模式略有不同。在创建时,我们允许用户提供自定义的帖子 slug,而不允许更新它。
不必太担心验证器模式(schema)中的重复。我们建议你选择易于理解的模式,而不是不惜一切代价避免重复。wet codebase analogy 可能有助于你接受重复。
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 响应的。
此外,会话中间件 重写了 renderValidationErrorAsHTML
方法,并使用闪存消息将验证错误与表单共享。
- 对于带有
Accept=application/json
的 HTTP 请求,将收到由 SimpleErrorReporter 创建的错误消息数组。 - 对于带有
Accept=application/vnd.api+json
的 HTTP 请求,将收到按照 JSON API 规范格式化的错误消息数组。 - 使用 会话包 渲染的服务器表单将通过 会话闪存消息 接收错误。
- 所有其他请求将以纯文本形式接收错误。
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)
}
}
验证 cookies、headers 和路由参数
使用 request.validateUsing
方法时,可以按如下方式验证 cookies、headers 和路由参数。
const validator = vine.compile(
vine.object({
// 请求体中的字段
username: vine.string(),
password: vine.string(),
// 验证 cookies
cookies: vine.object({
}),
// 验证 headers
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
函数来定义我们在模式中期望使用的元数据的静态类型。
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) => {
// validate metadata
})
配置 VineJS
你可以在 start
目录中创建一个 preload 文件 来配置 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.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.file
模式类型由 AdonisJS 核心包添加。
接下来是什么?
- 了解更多关于在 VineJS 中使用 自定义消息 的信息。
- 了解更多关于在 VineJS 中使用 错误报告器 的信息。
- 阅读 VineJS 的 schema API 文档。
- 使用 i18n 翻译 来定义验证错误消息。