应显式构造gin.Engine并按需注册中间件,而非使用gin.Default();初始化只需go mod init和go get固定版本;路由须分离到独立函数;配置用viper多路径多格式加载;HTTP服务必须实现优雅关机。

直接用 go mod init 初始化项目,别碰 gin.Default() 一键式入口
Gin 的 gin.Default() 看似省事,但会自动注入 recovery 和 logger 中间件,导致日志格式不可控、panic 捕获逻辑无法定制,测试时还容易漏掉中间件副作用。真实项目里,你应该显式构造 gin.Engine 并按需注册中间件。
初始化命令只需两步:
-
go mod init example.com/myapi(模块名必须是合法域名格式,否则viper加载远程配置或go get依赖会失败) -
go get -u github.com/gin-gonic/gin@v1.12.0(固定 minor 版本,避免go mod tidy自动升到 v1.13+ 引入的gin.Context.Copy()兼容问题)
后续所有依赖(viper、zerolog、go-redis)都走 go get 显式安装,不靠 gin.Default() 隐式拉取——这样你才能清楚知道每个中间件的执行顺序和 context 生命周期。
路由注册必须分离到独立函数,别塞进 main.go
把所有 r.GET、r.POST 写在 main.go 里,会导致后期无法按业务模块做单元测试、无法复用路由逻辑、更没法给不同环境(dev/staging/prod)加载不同路由组。
立即学习“go语言免费学习笔记(深入)”;
正确做法是每个业务域定义自己的注册函数:
- 在
internal/user目录下写RegisterHandlers(r *gin.Engine)或RegisterHandlers(r *chi.Mux)(选 chi 的话返回http.Handler更干净) -
main.go只负责调用user.RegisterHandlers(r)、order.RegisterHandlers(r),不碰具体路径和 handler 实现 - 如果用
chi,记得用r.Route("/api/v1", func(r chi.Router) { ... })做前缀隔离,避免跨模块路由冲突
这样改一个用户模块的路由,完全不影响订单模块的测试覆盖率,CI 也能单独跑 go test ./internal/user。
配置加载必须支持多格式 + 多环境,viper 别只读 config.yaml
硬编码 viper.SetConfigFile("config.yaml") 是最常见错误:本地开发用 YAML,K8s ConfigMap 给的是 JSON,Secret 注入又是 ENV,三者格式不统一就会 panic。
viper 正确用法是:
-
viper.SetConfigName("config")(不带后缀) -
viper.AddConfigPath("./config")、viper.AddConfigPath("/etc/myapp/")、viper.AddConfigPath(".")(多路径 fallback) -
viper.SetEnvPrefix("APP")+viper.AutomaticEnv()(支持APP_HTTP_PORT=8080覆盖配置) - 最后调用
viper.ReadInConfig(),它会自动尝试.json、.yaml、.toml、.env等格式
特别注意:viper.Unmarshal(&conf) 前要先 viper.Get("log.level") 测试是否加载成功,否则静默失败——很多线上事故就卡在这一步没校验。
HTTP 服务启动必须带优雅关机,http.Server 别裸奔
直接 r.Run(":8080") 启动等于放弃进程管理能力:SIGTERM 来了直接 kill,正在处理的请求被中断,连接池没释放,Redis 连接泄漏,MySQL 事务没回滚。
标准姿势是:
- 用
http.Server封装gin.Engine(Handler: r) - 启动 goroutine 调
srv.ListenAndServe(),主 goroutine 等os.Signal - 收到
SIGINT或SIGTERM后,调srv.Shutdown(ctx),传入带 timeout 的context.WithTimeout - 所有数据库/缓存连接的
Close()必须放在defer里,且在srv.Shutdown之后执行(否则可能关 server 时连接已断)
最容易忽略的是:shutdown context 的 timeout 必须短于 K8s 的 terminationGracePeriodSeconds(默认 30s),否则 pod 强制 terminate,照样丢请求。


















