参数校验必须在Body首次读取前完成,使用c.Body()而非c.Request().Body;表单用c.FormValue(),JSON校验需在中间件手动反序列化并验证,动态参数须正确匹配c.Params()与c.QueryParam()。

参数校验必须在 Body 被首次读取前完成,否则 c.Body() 返回空或损坏数据;关键不是“用什么库”,而是“在哪个时机、以什么方式拿到原始输入”。
中间件里校验 JSON 参数必须用 c.Body(),别碰 c.Request().Body
很多人想手动读 c.Request().Body 做校验,结果后续 c.JSON() 或 c.BodyParser() 全部失败——因为 fasthttp 的底层 Body 是单次可读流,没有 rewind 机制。
-
c.Body()是 Fiber 封装好的安全入口,它内部已处理好复用逻辑,多次调用返回相同内容 -
c.Request().Body是原始*fasthttp.ByteBuffer,直接io.ReadAll()后就清空了,后续任何解析都拿不到数据 - 校验中间件必须注册在
app.Use()链中,且位置早于所有依赖BodyParser()的 handler - 若校验失败,必须
return c.Status(400).JSON(...),不能只c.Next(),否则业务逻辑会 panic
表单字段校验直接用 c.FormValue(),别走 c.Body() 解析
Content-Type 是 application/x-www-form-urlencoded 或 multipart/form-data 时,c.FormValue() 才是唯一正确入口。强行用 json.Unmarshal(c.Body(), ...) 会把 URL 编码的 a=1&b=2 当成 JSON 解析,必然报错。
-
c.FormValue("email")自动处理空值、重复键、编码转义,字段不存在时返回"",不会 panic - 同名多值(如复选框)要用
c.FormMap()获取全部,c.FormValue()只返回第一个 - 文件字段必须先调
c.FormFile("avatar"),再用c.SaveFile();跳过这步直接读c.Body()会导致 boundary 解析失败 - 如果前端发的是
text/plain或错误的Content-Type,c.FormValue()一定为空——这不是 bug,是协议不匹配
用 go-playground/validator 做结构体校验,但得绕开 BodyParser 的坑
直接 c.BodyParser(&req) 再校验,等于把校验时机拖到了 handler 里,中间件无法拦截非法请求。正确做法是:中间件里先 c.Body() 拿原始字节,反序列化到结构体,再交给 validator 校验。
- 定义结构体时加
validatetag:Name string `json:"name" validate:"required,min=2"` - 中间件中手动解码:
if err := json.Unmarshal(c.Body(), &req); err != nil { return c.Status(400).SendString("invalid json") } - 再校验:
if err := validate.Struct(req); err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) } - 校验通过后,把结构体塞进
c.Locals("parsed_body", req),后续 handler 直接取,避免重复解析
动态路由参数和 Query 参数别混用,c.Params() 和 c.QueryParam() 完全隔离
URL 是 /user/123?format=json,c.Params("id") 拿到 "123",c.QueryParam("format") 拿到 "json",两者内存地址不同,互不影响。常见错误是写成 c.Params("format") 或 c.QueryParam("id"),永远返回空。
- 路由必须显式声明:
app.Get("/post/:slug", handler)→c.Params("slug")才有效 -
app.Get("/user/*", handler)→ 用c.Params("*")拿整个尾部路径,不是c.Params("path") - 高并发下,
c.Params()返回的是内部字节切片引用,立刻转成string(c.Params("id"))再传给其他函数,否则 goroutine 里可能看到脏数据 - StrictRouting 开启时,
/user/123/和/user/123是两个路由,别指望c.Params("id")在后者里工作
最常被忽略的是:校验逻辑是否真正在请求生命周期最早期执行。哪怕只多一次 c.Body() 调用,都可能让后续解析失效——这不是 validator 的问题,是 Body 流被消费的底层事实。


















