生产环境不可直接用 gin.default() 上线,因其默认启用 logger(泄露敏感信息)和 recovery(暴露源码路径),且监听 0.0.0.0:8080 暴露内网端口;须改用 gin.new() + 自定义中间件 + 显式绑定地址。

直接用 gin.Default() 启动服务并写一堆 router.GET 是能跑通,但上线前一定会踩坑:路由混乱、参数校验缺失、错误码不标准、结构体字段解码为空、ID 类型转换 panic——这些不是“写完再改”的问题,而是设计阶段就该堵死的口子。
RESTful 路由必须分组 + 严格顺序
Gin 的 Group() 不只是路径拼接,它决定了中间件作用域和路由匹配优先级。比如 /users/:id 和 /users/me 必须按静态路径在前、动态参数在后的顺序注册,否则 :id 会吞掉所有后续路由:
-
api.GET("/users/me", getMyProfile)—— 必须放前面 -
api.GET("/users/:id", getUserByID)—— 必须放后面 - 若 ID 仅接受数字,务必加正则约束:
api.GET("/users/:id/[0-9]+", getUserByID),否则/users/123/xxx也会命中 - 不同资源(如
/orders和/users)建议用独立Group(),避免中间件误作用或 Prometheus 标签混用
参数绑定必须用结构体 + binding 标签
别用 map[string]interface{} 接 JSON 请求体,它绕过所有校验,且字段零值会覆盖 DB 原值。正确做法是定义强类型结构体:
type CreateUserReq struct {
Name string `json:"name" binding:"required,min=2,max=20"`
Email string `json:"email" binding:"required,email"`
Age uint8 `json:"age" binding:"gte=0,lte=120"`
}
-
c.ShouldBindJSON(&req)会自动返回422 Unprocessable Entity及具体错误字段 - 所有字段首字母必须大写,否则
json标签无效,解码后全为空 - 可选字段用指针(
*string)+omitempty,避免前端传空字符串清空 DB 值 - 路径参数(
c.Param("id"))永远是字符串,必须显式转类型:id, err := strconv.ParseUint(c.Param("id"), 10, 64),并检查err
HTTP 状态码不能只靠 200 和 500
前端依赖状态码做逻辑分支,返回全用 200 + 自定义 {"code": 404} 会导致 fetch().then() 永远不进 catch,错误流控失效:
- 资源不存在 →
http.StatusNotFound (404) - 创建成功 →
http.StatusCreated (201),并设Locationheader:c.Header("Location", "/users/"+idStr) - 业务校验失败(如手机号格式错)→
http.StatusUnprocessableEntity (422) - 数据库连接失败 →
http.StatusServiceUnavailable (503),500只留给未捕获 panic - 禁止手动调用
json.Unmarshal(),否则绕过 Gin 错误拦截,400 不进日志、不触发统一 recovery 中间件
中间件要隔离鉴权与日志,别堆在 Default() 里
gin.Default() 自带 Logger 和 Recovery,但生产环境需拆开控制:
- 日志中间件应记录耗时、IP、路径、查询参数、状态码,但**不记录请求体和响应体**(隐私/性能)
- 鉴权中间件(如检查
Authorizationheader)应注册在具体路由组上,而非全局,避免对/health或/metrics拦截 - 自定义 recovery 中间件必须返回标准格式(如
Rsp{Code: 500, Msg: "internal error"}),且不暴露 panic 堆栈到响应中 - 所有中间件函数签名必须是
gin.HandlerFunc,且调用c.Next()控制执行时机
最常被忽略的是路径参数校验和状态码语义——它们不报错,但会让前端调试抓狂、监控告警失灵、安全审计翻车。设计接口时,先想清楚这个请求“成功/失败分别该返回什么状态码”,再动手写 handler。











