fiber请求参数分三类且来源互不干扰:c.params()读路径占位符(需路由显式声明如:id)、c.queryparam()读url查询串、c.formvalue()/c.bodyparser()读请求体;混用必为空。

直接说结论:Fiber 中的请求参数分三类,来源互不干扰——c.Params() 读路径占位符,c.QueryParam()(或 c.Query())读 URL 查询串,c.FormValue() / c.BodyParser() 读请求体;混用或错位取值,结果必为空。
路径参数必须显式声明才能用 c.Params()
动态路由参数不是“自动识别的”,而是靠你在路由字符串里写 :name 或 * 显式声明。没声明,c.Params("xxx") 永远返回空字符串。
-
app.Get("/user/:id", handler)→ 可用c.Params("id")取到"123" -
app.Get("/files/*", handler)→ 用c.Params("*")取整个尾部,如/files/a/b/c返回"a/b/c" -
app.Get("/user/{id}", handler)或app.Get("/user/:id/", handler)→ Fiber 不识别,404 - StrictRouting 默认开启,
/user/123和/user/123/是两条路由;只注册前者,后者不会命中
查询参数用 c.QueryParam(),别和 c.Params() 搞混
c.QueryParam("key") 和 c.Params("key") 完全无关——前者解析 ?key=value,后者解析 /:key。它们底层内存地址不同,不能互相替代。
- URL 是
/post/abc?format=json&lang=zh:c.Params("slug")→"abc"(前提是路由为/post/:slug)c.QueryParam("format")→"json"c.QueryParam("slug")→ 空(query string 里没slug=)c.Params("format")→ 空(路径里没声明:format) - 推荐统一用
c.QueryParam(),它比c.Query()更健壮(对缺失 key 返回空字符串而非 panic)
请求体参数要分清场景:表单、JSON、文件上传
Fiber 对不同 Content-Type 的请求体提供不同方法,强行混用会丢数据或 panic。
- 普通表单(
application/x-www-form-urlencoded):c.FormValue("field"),支持多值字段(c.FormValues("tags")) - JSON 请求体:
c.BodyParser(&data),自动解码,但要求结构体字段有正确 tag(如json:"name") - 文件上传:
c.FormFile("file")+c.SaveFile(),不要用c.Body()手动读,会破坏 multipart 解析状态 - 手动读原始 body:
c.Body()只能调一次;若需多次使用,先拷贝再用bytes.NewReader()重置
高并发下别保留 c.Params() 或 c.Body() 的引用
fiber.Ctx 是复用对象,c.Params("id") 返回的是内部字节切片的引用,不是新分配的字符串。goroutine、map、结构体字段里存它,下一个请求进来时值就变了。
- 错误写法:
id := c.Params("id"); go process(id)→id可能在 goroutine 执行前就被覆盖 - 正确写法:
id := string(c.Params("id"))或id := c.Params("id").String(),立刻转成独立字符串再传 - 同理,
c.Body()返回的[]byte也是引用,需要append([]byte{}, body...)拷贝后再用
最常被忽略的一点是:路径参数和查询参数的语义完全不同,但开发者常在日志、权限校验、缓存 key 构造中把它们当同一类东西处理——结果就是缓存击穿、权限绕过或日志查不到上下文。别省那几行代码,显式区分来源,才是稳的。











