go mod init 必须在项目根目录执行,否则 go build 会报错;gin.Default() 自动加载 Logger 和 Recovery 中间件;router.Run() 阻塞主线程,后续代码不执行;c.Query() 和 c.Param() 用途不同,不可混用。

go mod init 必须在项目根目录执行,否则 go build 会报错
很多新手在初始化模块时,cd 进错目录(比如进了 internal 或 cmd 子目录),再执行 go mod init myapp,结果生成的 go.mod 路径和实际代码结构不匹配。后续 go build 或 go run main.go 会提示 cannot load myapp/internal/xxx: cannot find module providing package myapp/internal/xxx。
正确做法是:确保终端当前路径就是项目最外层文件夹(即包含 main.go 的那一级),再运行:
go mod init mywebapp
验证方式:go list ./... 应能列出所有子包;go mod graph | head -n 5 应显示主模块依赖关系。
gin.Default() 自动加载了 Logger 和 Recovery 中间件
用 gin.Default() 创建路由实例时,它已经内置了日志打印(访问路径、状态码、耗时)和 panic 捕获(避免服务崩溃)。如果你手动调用 gin.New() 却忘了加这两个中间件,会导致:
立即学习“go语言免费学习笔记(深入)”;
- 请求无任何日志输出,调试困难
- handler 中 panic 后整个服务直接退出(而不是返回 500 并继续运行)
若需自定义行为(比如把日志写入文件或屏蔽某些路径),可显式注册:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
router := gin.New()
router.Use(gin.LoggerWithConfig(gin.LoggerConfig{SkipPaths: []string{"/health"}}))
router.Use(gin.Recovery())
router.Run(":8080") 会阻塞主线程,后面代码永不执行
router.Run() 内部调用的是 http.ListenAndServe(),这是一个同步阻塞调用。常见错误是在它之后写清理逻辑、数据库关闭或信号监听:
router.Run(":8080")
db.Close() // ← 这行永远不会运行
fmt.Println("server stopped") // ← 也不会打印
正确做法是用 goroutine 启动服务,并监听系统信号(如 SIGINT)来优雅关闭:
server := &http.Server{Addr: ":8080", Handler: router}
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// 等待信号...
GET 参数解析别混用 c.Query() 和 c.Param(),路径匹配规则完全不同
这是 Gin 中最容易混淆的点之一:
-
c.Query("id")对应 URL 查询参数:/users?id=123 -
c.Param("id")对应路由定义中的命名参数:router.GET("/users/:id", handler)→ 请求必须是/users/123
如果路由写成 /users/:id 却用 c.Query("id") 取值,永远拿不到;反之,如果路由是 /users 却用 c.Param("id"),返回空字符串且无报错,极易埋下静默 bug。
建议统一约定:动态路径段一律用 :name 定义 + c.Param() 获取;过滤/分页等非路径语义参数,走 c.Query() 或结构体绑定(c.ShouldBindQuery(&req))。
真正难的不是写完第一个接口,而是当路由超过 20 个、中间件叠加 5 层、数据库连接池开始超时的时候,你还清不清楚 router.Run() 底层到底调了谁、c.Param() 的值是从哪一层解析出来的、panic 是被哪个 Recovery 捕获的——这些细节,不看源码或不调试一次根本记不住。

















