Gin 的 router.POST 不能直接接收 JSON body,因为默认不自动绑定,必须显式调用 c.ShouldBindJSON(&req) 才能解析;字段需加 json:"name" tag,否则大小写不匹配丢数据。

为什么 Gin 的 router.POST 不能直接接收 JSON body?
因为 Gin 默认不自动绑定 JSON,c.ShouldBindJSON() 才是触发解析的开关。很多新手写完 router.POST("/user", handler) 就以为请求体能自动变成 struct,结果 c.PostForm("name") 返回空、c.Request.Body 读出来是空字节——其实是没调用绑定逻辑。
实操建议:
- 必须在 handler 里显式调用
c.ShouldBindJSON(&req),且req是已定义的 struct 类型 - struct 字段要加
json:tag,比如Name string `json:"name"`,否则字段名大小写不匹配会丢数据 - 如果只读原始 JSON,可用
io.ReadAll(c.Request.Body),但注意 Body 只能读一次,后续再读就是空 - 别在中间件里提前读 Body,除非你手动
c.Request.Body = io.NopCloser(bytes.NewBuffer(...))恢复
Gin 中 c.JSON(200, data) 返回空对象或 panic 怎么办?
常见原因是 data 是 nil 指针、未导出字段(首字母小写)、或 struct 里嵌了 map/slice 但没初始化。Gin 的 json.Marshal 遇到这些会静默失败或返回 {},甚至 panic:「reflect: call of reflect.Value.Interface on zero Value」。
实操建议:
- 返回前先用
json.Marshal(data)手动测试,看是否报错 - 避免直接传指针,比如
c.JSON(200, &user)改成c.JSON(200, *user)(确保非 nil) - map 和 slice 字段声明时初始化,如
Tags []string `json:"tags"`而不是Tags []string(后者序列化为 null) - 用
omitemptytag 控制零值省略,比如Age int `json:"age,omitempty"`
如何让 Gin 接口支持 CORS 而不被浏览器拦截?
浏览器报「No 'Access-Control-Allow-Origin' header」,不是后端没响应,而是响应头缺失。Gin 本身不带 CORS 中间件,得自己加或引入第三方包,但最简方案是手写一行 c.Header()。
实操建议:
- 开发环境可直接加全局中间件:
router.Use(func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*"); c.Next() }) - 生产环境别用
*,改用具体域名,比如"https://myapp.com" - 如果前端发了
Content-Type: application/json或带 credentials,还得加Access-Control-Allow-Headers和Access-Control-Allow-Credentials: true - 预检请求(OPTIONS)必须返回 204,且不能跳过
c.Next(),否则后续 handler 不执行
为什么 gin.Default() 日志里看不到 POST 请求体内容?
因为 gin.Default() 内置的 Logger 中间件只打 URL、状态码、耗时,不打印 body。想调试参数,不能靠它;强行用 c.Request.Body 读取又会导致后续绑定失败。
实操建议:
- 调试阶段用
c.ShouldBindJSON(&req)后直接fmt.Printf("%+v\n", req) - 需要完整日志,用
gin.LoggerWithConfig()自定义,但注意 body 读取后需重放,可用gin.Recovery()前插入gin.BodyDump()(官方 contrib 包) - 线上禁用任何打印原始 body 的逻辑,尤其含密码、token 的字段
- 结构体字段加
json:"-"或json:"password,omitempty"避免敏感字段被误打日志


















