Gin脚手架应避免使用gin.Default()而选用gin.New(),因前者预设Logger和Recovery中间件,不利于环境定制;需按业务域组织路由、统一封装响应、显式加载配置并控制中间件顺序。

为什么 Gin 的默认路由注册方式不适合脚手架
因为 gin.Default() 自带 Logger 和 Recovery 中间件,开发阶段调试方便,但上线后日志格式、panic 捕获逻辑往往要重写——脚手架不该预设这些。直接用 gin.New() 更干净,中间件按需显式加载,比如只在 dev 环境加 gin.LoggerWithWriter(),prod 里换自定义日志器。
常见错误是复制粘贴示例代码后发现日志重复、panic 被吞掉没记录、或 CORS 头缺失导致前端跨域失败。这些问题根源都在中间件堆叠顺序和环境判断缺失。
-
gin.Default()=gin.New()+Logger()+Recovery(),不能拆开控制 - 中间件注册顺序很重要:
CORS必须在Logger前,否则 preflight 请求不带日志但会走 CORS - 环境变量建议用
GIN_MODE=release控制,而不是硬编码if env == "dev"
如何组织 handler 层避免“router.go 膨胀”
把所有 router.POST("/user", userHandler) 堆在 main.go 或 router.go 里,很快变成难以维护的面条代码。Gin 本身不强制分层,但脚手架必须提供可扩展结构。
推荐按业务域建 handlers/ 目录,每个文件对应一个资源(如 user_handler.go),暴露初始化函数:
立即学习“go语言免费学习笔记(深入)”;
func SetupUserRoutes(r *gin.Engine) {
group := r.Group("/api/v1/users")
group.GET("", listUsers)
group.POST("", createUser)
group.GET("/:id", getUser)
}
然后在 main.go 统一调用:
func main() {
r := gin.New()
handlers.SetupUserRoutes(r)
handlers.SetupOrderRoutes(r)
r.Run(":8080")
}
- 不要用闭包传参(如
func(userRepo UserRepo) gin.HandlerFunc),会导致路由注册时依赖未就绪 - handler 函数签名统一为
func(*gin.Context),依赖通过全局变量或注入容器传递(简单脚手架用包级变量即可) - 避免在 handler 里直接写 SQL 或 HTTP 调用,至少封装到
services/层,哪怕只是一层转发
JSON 响应统一封装与错误处理怎么不写两遍
Gin 默认的 c.JSON(200, data) 和 c.JSON(400, err) 散落在各 handler 里,状态码、字段名、错误结构不一致。脚手架必须提供统一响应体和错误出口。
定义标准响应结构:
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data,omitempty"`
}
再封装两个工具函数:
func Success(c *gin.Context, data interface{}) {
c.JSON(200, Response{Code: 0, Msg: "success", Data: data})
}
func Fail(c *gin.Context, code int, msg string) {
c.JSON(code, Response{Code: code, Msg: msg})
}
关键点:HTTP 状态码和业务 code 不要混用。比如用户不存在返回 Fail(c, 404, "user not found"),此时 HTTP 状态码是 404,业务 code 也是 404;但参数校验失败应返回 Fail(c, 400, "invalid param"),而非强行设业务 code=1001。
- 不要用 panic 触发错误响应,Gin 的
Recovery中间件会捕获但丢失上下文,改用c.Error(err)+ 自定义 error handler - 第三方库报错(如
db.QueryRow().Scan())要转成明确业务错误,而不是直接Fail(c, 500, err.Error()) - 如果用了 validator,
binding错误会自动返回 400,但消息格式难看,建议重写gin.Validator接口
为什么 .env 配置加载容易出错
很多人用 godotenv.Load() 加载 .env,但忽略它默认只读当前目录,而 Go build 后二进制运行时工作目录可能是任意路径。结果线上跑起来配置全空,端口还是 8080,数据库连 localhost。
正确做法是显式指定路径,并 fallback 到默认值:
if err := godotenv.Load(".env"); err != nil {
log.Printf("no .env file, using defaults")
}
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
- 不要在 init() 里加载 .env,main() 开头加载更可控
- 敏感配置(如 DB password)别放 .env,用环境变量直接传入,.env 只存非敏感默认值
- Gin 自身的
gin.SetMode(gin.ReleaseMode)应该由GIN_MODE环境变量驱动,而不是硬编码
脚手架真正的难点不在代码量,而在边界情况:配置加载时机、错误传播路径、中间件执行顺序。这些地方写错,调试时根本看不出哪行代码生效了。


















