根本原因是请求未满足三条件:方法非post/put、缺失content-type: application/json头、或body非合法utf-8 json;推荐用shouldbindjson自主错误处理,而非bindjson自动中止。

为什么 Gin 的 BindJSON 总是报 EOF 或 invalid character
多数人在写学生信息新增接口时,c.BindJSON(&student) 直接 panic 或返回 400,根本原因不是结构体写错了,而是前端发来的请求没带 Content-Type: application/json,或者 POST body 是空、含 BOM、或用了 form-data 提交 JSON 字符串。
实操建议:
- 用
curl -H "Content-Type: application/json" -d '{"name":"张三"}' http://localhost:8080/students验证服务端是否正常接收 - 检查前端 axios/fetch 是否漏了
headers: { 'Content-Type': 'application/json' } - 若用 Postman,选
Body → raw → JSON,别选form-data - Gin 默认不自动处理带 BOM 的 UTF-8,Windows 记事本保存的 JSON 文件容易触发
invalid character 'ï' looking for beginning of value,改用 VS Code 保存为 UTF-8 无 BOM
学生列表接口该用 Query 还是 GetQuery 做分页参数解析
两者都能取 URL 查询参数,但语义和容错不同:c.Query("page") 返回 string,c.GetQuery("page") 也是 string,真正该用的是 c.DefaultQuery("page", "1") —— 它在参数缺失时给默认值,避免后续 strconv.Atoi("") panic。
常见错误:直接 strconv.Atoi(c.Query("page")),一访问 /students(无 page 参数)就崩溃。
实操建议:
- 分页参数统一用
c.DefaultQuery("page", "1")和c.DefaultQuery("size", "10") - 数值转换务必加错误检查:
if page, err := strconv.Atoi(c.DefaultQuery("page", "1")); err == nil && page > 0 { ... } - 不要信任前端传来的
page,需做范围限制(如最大 1000),防止恶意拉取全量数据
用 gin.Context 的 AbortWithStatusJSON 还是 JSON 返回错误
c.JSON(400, gin.H{"error": "xxx"}) 只是返回响应,中间件还会继续执行;而 c.AbortWithStatusJSON(400, gin.H{"error": "xxx"}) 会终止后续 handler,适合校验失败后立刻退出。
比如学生 ID 为空时,你肯定不想再查数据库或写日志——这时候必须用 AbortWithStatusJSON。
实操建议:
- ID 校验、参数缺失、权限拒绝等前置判断,一律用
AbortWithStatusJSON - 业务逻辑执行中发生的错误(如 DB 更新失败),用
JSON即可,因为 handler 已进入主流程 - 别混用:
AbortWithStatusJSON后再调JSON会 panic,Gin 明确禁止重复写响应
MySQL 插入学生记录后如何安全获取自增 ID
Gin 本身不处理数据库,但很多人用 db.Exec("INSERT ...") 后直接 result.LastInsertId(),结果在 MySQL 8.0+ 上可能返回 0 —— 因为 Exec 不保证返回可用的 LastInsertId,尤其当使用连接池或预编译时。
正确做法是用 db.QueryRow("INSERT ... RETURNING id", ...).Scan(&id)(PostgreSQL),但 MySQL 没 RETURNING,只能靠 db.Prepare + QueryRow 绕过。
实操建议:
- 用
stmt, _ := db.Prepare("INSERT INTO students(name, age) VALUES(?, ?)"),再row := stmt.QueryRow(name, age) - 然后
err := row.Scan(&id),这是 MySQL 驱动唯一能稳定拿到自增 ID 的方式 - 别用
Exec+LastInsertId,它在事务、多语句、连接复用场景下不可靠 - 如果用 GORM,直接
db.Create(&student)后student.ID就是新 ID,底层已封装处理
字段校验、ID 返回、错误中断、参数解析——这四点不踩坑,接口就能稳住。真要加 JWT 或文件上传,得单独看中间件生命周期和 buffer 控制,别一股脑塞进同一个 handler 里。











