# 验证数据 > 在处理数据之前,确保您的数据是有效且安全的。 当您在服务器端接收到数据时,必须对其进行验证。所谓验证,是指接收的数据结构必须与预期结构相匹配。这很重要,因为您无法信任来自未知来源的数据,比如用户或外部 API。 不要使用类型泛型来作为验证。为类似 `readBody` 这样的工具提供接口并不是验证。您必须在使用数据之前进行验证。 ## 验证辅助工具 H3 提供了一些辅助工具来帮助您处理数据验证。您将能够验证: - 使用 `getValidatedQuery` 验证查询参数 - 使用 `getValidatedRouterParams` 验证路由参数 - 使用 `readValidatedBody` 验证请求体 H3 不提供任何验证库,但它支持来自**Standard-Schema** 兼容库的 schema,比如:[Zod](https://zod.dev)、[Valibot](https://valibot.dev)、[ArkType](https://arktype.io/) 等……(所有兼容库请查看[它们的官方仓库](https://github.com/standard-schema/standard-schema))。如果您想使用一个不兼容 Standard-Schema 的验证库,您仍然可以使用,但必须使用该库自身提供的解析函数(请参阅下面的[安全解析](#%E5%AE%89%E5%85%A8%E8%A7%A3%E6%9E%90)部分)。 H3 是运行时无关的。这意味着您可以在[任何运行时](/adapters)中使用它。但某些验证库并不兼容所有运行时。 下面让我们看看如何使用 [Zod](https://zod.dev) 和 [Valibot](https://valibot.dev) 进行数据验证。 ### 验证路由参数 您可以使用 `getValidatedRouterParams` 来验证路由参数并获取结果,替代 `getRouterParams`: ```js import { getValidatedRouterParams } from "h3"; import * as z from "zod"; import * as v from "valibot"; // 使用 Zod 示例 const contentSchema = z.object({ topic: z.string().min(1), uuid: z.string().uuid(), }); // 使用 Valibot 示例 const contentSchema = v.object({ topic: v.pipe(v.string(), v.nonEmpty()), uuid: v.pipe(v.string(), v.uuid()), }); router.use( // 您必须使用一个路由以使用参数 "/content/:topic/:uuid", async (event) => { const params = await getValidatedRouterParams(event, contentSchema); return `您正在查找 topic 为 "${params.topic}" 且 uuid 为 "${params.uuid}" 的内容。`; }, ); ``` 如果您向该事件处理程序发送一个有效请求,例如 `/content/posts/123e4567-e89b-12d3-a456-426614174000`,您将得到如下响应: ```txt 您正在查找 topic 为 "posts" 且 uuid 为 "123e4567-e89b-12d3-a456-426614174000" 的内容。 ``` 如果您发送了无效请求且验证失败,H3 将抛出 `400 Validation Error` 错误。在错误数据中,您会找到验证错误信息,可以在客户端使用它来向用户展示友好的错误提示。 ### 验证查询参数 您可以使用 `getValidatedQuery` 来验证查询参数并获取结果,替代 `getQuery`: ```js import { getValidatedQuery } from "h3"; import * as z from "zod"; import * as v from "valibot"; // 使用 Zod 示例 const stringToNumber = z .string() .regex(/^\d+$/, "必须是数字字符串") .transform(Number); const paginationSchema = z.object({ page: stringToNumber.optional().default(1), size: stringToNumber.optional().default(10), }); // 使用 Valibot 示例 const stringToNumber = v.pipe( v.string(), v.regex(/^\d+$/, "必须是数字字符串"), v.transform(Number), ); const paginationSchema = v.object({ page: v.optional(stringToNumber, 1), size: v.optional(stringToNumber, 10), }); app.use(async (event) => { const query = await getValidatedQuery(event, paginationSchema); return `您当前在第 ${query.page} 页,每页显示 ${query.size} 条内容。`; }); ``` 如您所见,相较于 `getValidatedRouterParams` 示例,我们可以利用验证库对传入数据进行转换,例如这里把数字的字符串形式转换为真正的数字,这在内容分页等场景中非常有用。 如果您向该事件处理程序发送一个有效请求,比如 `/?page=2&size=20`,您将得到如下响应: ```txt 您当前在第 2 页,每页显示 20 条内容。 ``` 如果您发送了无效请求且验证失败,H3 将抛出 `400 Validation Error` 错误。在错误数据中,您会找到验证错误信息,可以在客户端使用它来向用户展示友好的错误提示。 ### 验证请求体 您可以使用 `readValidatedBody` 来验证请求体并获取结果,替代 `readBody`: ```js import { readValidatedBody } from "h3"; import { z } from "zod"; import * as v from "valibot"; // 使用 Zod 示例 const userSchema = z.object({ name: z.string().min(3).max(20), age: z.number({ coerce: true }).positive().int(), }); // 使用 Valibot 示例 const userSchema = v.object({ name: v.pipe(v.string(), v.minLength(3), v.maxLength(20)), age: v.pipe(v.number(), v.integer(), v.minValue(0)), }); app.use(async (event) => { const body = await readValidatedBody(event, userSchema); return `你好 ${body.name}!你今年 ${body.age} 岁。`; }); ``` 如果您发送一个带有如下 JSON 请求体的有效 POST 请求: ```json { "name": "John", "age": 42 } ``` 您将得到如下响应: ```txt 你好 John!你今年 42 岁。 ``` 如果您发送了无效请求且验证失败,H3 将抛出 `400 Validation Error` 错误。在错误数据中,您会找到验证错误信息,可以在客户端使用它来向用户展示友好的错误提示。 ## 安全解析 默认情况下,如果直接将 schema 作为第二个参数传给任何验证辅助工具(`getValidatedRouterParams`、`getValidatedQuery` 和 `readValidatedBody`),当验证失败时会抛出 `400 Validation Error` 错误。但在某些情况下,您可能想自己处理验证错误。这时,您应该根据所使用的验证库,传入其提供的实际安全验证函数作为第二个参数。 回到第一个示例中 `getValidatedRouterParams`,对于 Zod 会是这样: ```ts import { getValidatedRouterParams } from "h3"; import { z } from "zod/v4"; const contentSchema = z.object({ topic: z.string().min(1), uuid: z.string().uuid(), }); router.use("/content/:topic/:uuid", async (event) => { const params = await getValidatedRouterParams(event, contentSchema.safeParse); if (!params.success) { // 处理验证错误 return `验证失败:\n${z.prettifyError(params.error)}`; } return `您正在查找 topic 为 "${params.data.topic}" 且 uuid 为 "${params.data.uuid}" 的内容。`; }); ``` 而对于 Valibot 则是这样: ```ts import { getValidatedRouterParams } from "h3"; import * as v from "valibot"; const contentSchema = v.object({ topic: v.pipe(v.string(), v.nonEmpty()), uuid: v.pipe(v.string(), v.uuid()), }); router.use("/content/:topic/:uuid", async (event) => { const params = await getValidatedRouterParams( event, v.safeParser(contentSchema), ); if (!params.success) { // 处理验证错误 return `验证失败:\n${v.summarize(params.issues)}`; } return `您正在查找 topic 为 "${params.output.topic}" 且 uuid 为 "${params.output.uuid}" 的内容。`; }); ```