codecamp

H3 数据校验

服务端收到的每一份数据——来自用户、来自外部 API——都不可信。校验(validation)就是确认「数据的形状符合预期」这一关,H3 为此提供了一组 *Validated* 工具。

类型泛型 ≠ 校验

⚠️ 第一原则:类型泛型不是校验。给 readBody<T& 传一个接口,只是给编辑器看的提示,运行时并不会真的检查数据。你必须显式校验后再使用。

三个校验工具

H3 不自带校验库,但支持 Standard-Schema 规范兼容的 schema(Zod、Valibot、ArkType 等)。对应三种入参各有替换品:

入参 不校验 校验版
路由参数 getRouterParams getValidatedRouterParams
查询串 getQuery getValidatedQuery
请求体 readBody readValidatedBody

校验失败会直接抛 400 Validation Error,错误数据里带有具体的校验问题,可以原样回给客户端做友好提示。

校验路由参数

import { getValidatedRouterParams } from "h3";
import { z } from "zod";

// 用 Zod 描述期望的形状
const contentSchema = z.object({
  topic: z.string().min(1),       // 非空字符串
  uuid: z.string().uuid(),         // 合法 UUID
});

app.all("/content/:topic/:uuid", async (event) => {
  // 校验失败会抛 400,成功则返回带类型的 params
  const params = await getValidatedRouterParams(event, contentSchema);
  return `正在查看 topic="${params.topic}" uuid="${params.uuid}"`;
});

注意:用参数就必须走路由(:topic、:uuid),getRouterParams 那一节讲过原理。

校验查询串,并顺手转换类型

查询串天然是字符串,校验库还能顺便做类型转换——比如把 "2" 变成数字 2,对分页特别有用:

import { getValidatedQuery } from "h3";
import { z } from "zod";

// 先把数字字符串转成真正的 number
const stringToNumber = z
  .string()
  .regex(/^\d+$/, "必须是数字字符串")
  .transform(Number);

const paginationSchema = z.object({
  page: stringToNumber.optional().default(1),  // 默认第 1 页
  size: stringToNumber.optional().default(10), // 默认每页 10 条
});

app.use(async (event) => {
  const query = await getValidatedQuery(event, paginationSchema);
  return `第 ${query.page} 页,每页 ${query.size} 条`;
});

相比参数校验,这里多演示了一层 transform:入参是字符串,出参已是数字。

校验请求体

import { readValidatedBody } from "h3";
import { z } from "zod";

const userSchema = z.object({
  name: z.string().min(3).max(20),     // 3~20 字符
  age: z.number({ coerce: true }).positive().int(), // 正整数(coerce 尝试把字符串转数字)
});

app.use(async (event) => {
  const body = await readValidatedBody(event, userSchema);
  return `你好 ${body.name},你 ${body.age} 岁`;
});

合法的 JSON 体 { "name": "John", "age": 42 } 会得到 你好 John!你 42 岁;不合法则抛 400。

自己处理错误:safeParse 分支

默认直接抛 400 足够简单,但有时你想自己决定错误响应长什么样。把校验库的 safe 解析函数作为第二个参数传入即可:

import { getValidatedRouterParams } from "h3";
import { z } from "zod/v4";

const contentSchema = z.object({
  topic: z.string().min(1),
  uuid: z.string().uuid(),
});

app.all("/content/:topic/:uuid", async (event) => {
  // 传入 .safeParse 而不是 schema 本身,H3 就不会抛错,而是把结果交给你
  const result = await getValidatedRouterParams(event, contentSchema.safeParse);
  if (!result.success) {
    // 自己决定返回什么——注意不要把内部 schema 细节直接暴露给客户端
    return `校验失败:\n${z.prettifyError(result.error)}`;
  }
  return `正在查看 topic="${result.data.topic}" uuid="${result.data.uuid}"`;
});

Valibot 对应的是 v.safeParser(contentSchema)。

💡 别把内部结构泄露给客户端:上面的 prettifyError 只是演示可读性。生产环境应把校验错误收敛成「字段名 + 中文提示」之类的稳定文案,避免把你的 schema 构造、字段规则原样回传,那等于给攻击者画了一张数据模型地图。

原型污染风险:readBody 不再自动反序列化

⚠️ 官方特别强调:新版本 readBody 不再对体做 destr 风格的自动反序列化与对象展开,因此你拿到的就是一个普通对象——但这也意味着没有任何一层替你挡掉原型污染。{ "__proto__": { ... } } 这类体一旦被 Object.assign / 展开运算符合并进你的对象,就可能污染全局原型。务必:用上面这些 Validated 工具(它们基于 schema 白名单取值,天然只取声明的字段),而不要拿原始 readBody 结果直接合并进业务对象。

H3 流式响应
H3 v1 到 v2 迁移:核心变更
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }