前言
首先写这系列文章,是为了记录我自己学习 Gin 框架的过程。 我并没有打算一开始就搭建一个成熟的项目骨架,毕竟对整个框架还不是特别熟悉,也没有用什么高级插件或第三方库。就是从最基础的内容开始,一步步去实现功能,比如路由怎么写、JSON 怎么接、参数怎么校验、数据库怎么操作等等,边学边练,慢慢摸索。 如果文章中有理解不准确的地方,欢迎各位大佬指出,我会持续更新、不断改进 ~ 同时也希望这些内容能帮到和我一样正在入门 Go 或 Gin 框架的朋友们,作为一份更贴近实战、按模块推进的学习参考。
1. 项目初始化
首先第一步就是新建一个 Go 项目,初始化 mod 模块:
bash
mkdir gin-learn-notes
cd gin-learn-notes
go mod init gin-learn-notes
关于 go mod init gin-learn-notes
我们使用 Go Module 来管理项目依赖,因此在项目初始化时,需要先执行:
csharp
go mod init gin-learn-notes
这条命令会创建一个 go.mod
文件,告诉 Go 编译器:
- 当前这个项目的模块名是
gin-learn-notes
- 未来在这个模块下安装的依赖、引入的包,都会基于这个路径来进行管理
模块名的作用
比如我们后面会在代码中引用:
go
import "gin-learn-notes/controller"
这就依赖于我们 go.mod
里定义的模块名一致,否则导包会出错。
这一步做了什么?
- 创建了
go.mod
文件,记录模块名与依赖; - 之后我们使用
go get
安装 Gin 或其他库时,依赖都会记录在这个文件中,便于管理。
然后这里我也是大概了解了Go Modules 的一些小知识:
Go Modules 常用命令整理
命令 | 作用说明 |
---|---|
go mod init |
初始化项目模块,生成 go.mod 文件 |
go mod tidy |
清理无用依赖,同时自动添加缺失的依赖 |
go mod download |
下载 go.mod 中声明的依赖到本地缓存 |
go mod verify |
校验依赖包是否被修改或篡改,确保安全性 |
go mod why |
查看当前项目为什么会依赖某个包(依赖链分析) |
安装 Gin 框架:
go
go get -u github.com/gin-gonic/gin
-u
表示更新依赖(首次安装也适用)- 安装完成后会在
go.mod
里自动添加依赖 go.sum
文件也会生成,用来校验依赖的完整性
这时 go.mod
会自动添加 Gin 的依赖,go.sum
文件也会自动生成用于版本校验。
安装完成后,go.mod
文件中会多出类似这样的内容:
go
require github.com/gin-gonic/gin v1.10.0
如果我们不确定当前安装了哪些依赖,也可以查看
go.sum
文件,它记录了每个依赖包的版本和校验信息。
创建入口文件 main.go
项目入口文件是 main.go
,用来启动 Gin 引擎和运行服务。
先写一个最简单的版本:
go
// main.go
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default() // 初始化 Gin 引擎,带 Logger + Recovery 中间件
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run(":8080") // 启动服务,监听 8080 端口
}
每行代码说明:
代码行 | 含义解释 |
---|---|
gin.Default() |
返回一个带 Logger 和 Recovery 的默认路由引擎 |
r.GET(...) |
定义一个 GET 路由,访问 /ping 时返回 JSON |
c.JSON(...) |
返回一个 JSON 响应 |
r.Run(":8080") |
启动服务,监听本地 8080 端口 |
启动服务测试
go
go run main.go
浏览器访问:
bash
http://localhost:8080/ping
我们会看到返回的 JSON 响应:
json
{
"message": "pong"
}
这就代表我们的 Gin 项目已经成功跑起来了! 这也是我们初次学习Gin框架的第一步。
gin.Default() 和 gin.New() 有什么区别?
我在初始化 Gin 的时候,遇到的第一个疑问就是:
go
r := gin.Default()
那它和 gin.New()
有什么区别呢?后来我查了文档和源码,又问了下AI,大概理解如下 👇
方法 | 含义 | 自动加载的中间件 | 适合场景 |
---|---|---|---|
gin.Default() |
快速启动 | ✅ Logger(日志) ✅ Recovery(异常捕获) | 开发环境,快速启动调试 |
gin.New() |
空壳引擎 | ❌ 不加载任何中间件 | 生产环境 / 自定义中间件 |
举个例子
go
// Default() 自动注册中间件(推荐开发时使用)
r := gin.Default()
// New() 是最"干净"的引擎,需要自己添加中间件
r := gin.New()
r.Use(gin.Logger(), gin.Recovery()) // 手动注册中间件
总结理解
- 如果是刚开始练手,建议用
gin.Default()
,它更"开箱即用" - 如果打算完全控制中间件、日志格式、安全策略等,
gin.New()
更灵活
因为我刚学,所以个人目前用的是 gin.Default()
,等后期熟悉后,会尝试自己封装中间件并切换为 gin.New()
自定义配置。
路由拆分
main.go
是程序的启动入口,它应该只负责初始化引擎、启动服务,而不应该承担"注册所有接口"的职责 。否则随着接口增多,main.go
会变得非常臃肿,不利于维护和扩展。
所以我们可以把路由注册逻辑单独放在 router/router.go
,这样做的好处是:
- 遵守单一职责原则,结构清晰
- 后面如果接口很多,可以按模块拆成多个路由文件
虽然这只是个小项目,但从一开始就保持良好的结构习惯,对我们后续开发非常有帮助。
路由拆分结构如下:
go
gin-learn-notes/
├── main.go // 启动入口,只负责引擎初始化
├── router/
│ └── router.go // 路由注册逻辑集中处理
我们在router/router.go
里面用一个函数返回 *gin.Engine
,让 main.go
调用它
router/router.go
go
package router
import (
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
// 创建默认的 Gin 引擎
r := gin.Default()
// 定义一个基本的路由
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
return r
}
修改 main.go
修改前的样子:
go
func main() {
r := gin.Default()
r.GET("/ping", ...) // 👈 现在我们要把这一块拿出去
r.Run(":8080")
}
修改后的样子:
go
package main
import (
"gin-learn-notes/router" // ⬅️ 导入我们自己的路由包
)
func main() {
r := router.InitRouter() // ⬅️ 使用封装好的函数
r.Run(":8080")
}
⚠️ 注意:
"go-learn-notes/router"
中的go-learn-notes
是我们go mod init
时填的模块名,确保一致。
这样,项目结构就开始变得"分工明确"了:
main.go
专注启动router.go
专注路由注册
后面我们还可以继续拆出控制器、服务层、数据库模型等等,形成完整的分层架构。
我们改完后在终端中执行:
go
go run main.go
然后访问:
如果能看到 {"message": "pong"}
,说明我们拆分路由没啥问题。
从内联逻辑到控制器分离
在我们之前的测试路由中,我们是直接这样写的:
go
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
虽然写起来很方便,但这个处理函数是直接写在路由注册里的,随着项目逐渐复杂,每个接口都有自己的业务逻辑,如果都塞在路由里,维护起来非常困难。
路由的职责应该只是"分发请求",而不是处理具体的业务逻辑。
所以,我们继续遵循单一职责原则 ,把处理逻辑从路由中抽离,放到 controller/
目录中的控制器文件中。
拆分之后的结构:
go
// router/router.go
r.GET("/ping", controller.Ping)
go
// controller/ping.go
func Ping(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
}
这样做的好处是:
- 路由只负责定义"谁负责处理",代码更清晰
- 业务逻辑集中管理,方便测试和维护
- 日后可以继续往下拆分(service 层、model 层),形成 MVC 分层结构
后面我们还会进一步把控制器逻辑抽出 service 层、数据库抽出 model 层,逐步形成可维护的项目架构。
Gin 接收参数 + 返回 JSON 示例
我们刚刚把处理逻辑从路由中拆到了控制器里。接下来,我们可以通过一个简单的接口,来了解一下 Gin 如何接收参数并输出 JSON。
第一步:编写控制器 hello.go
在 controller
目录下创建一个新的文件 hello.go
,内容如下:
go
// controller/hello.go
package controller
import (
"github.com/gin-gonic/gin"
"net/http"
)
// HelloHandler 处理 /hello 路由
func HelloHandler(c *gin.Context) {
// 获取 name 参数,默认为 "World"
name := c.DefaultQuery("name", "World")
// 返回 JSON 响应
c.JSON(http.StatusOK, gin.H{
"message": "Hello, " + name + "!",
})
}
这里我们使用了 c.DefaultQuery() 方法,从 URL 中获取 name 参数,如果没有传入,则使用默认值 "World"。
第二步:在路由中注册 hello 接口
打开 router/router.go
,添加以下路由注册代码:
go
// router/router.go
r.GET("/hello", controller.HelloHandler) // 支持访问 /hello?name=Gin
第三步:运行项目进行测试
bash
go run main.go
在浏览器或 Postman 中访问:
http://localhost:8080/hello
返回:{"message": "Hello, World!"}
http://localhost:8080/hello?name=Gin
返回:{"message": "Hello, Gin!"}
思考:为什么路由函数里总是用 c *gin.Context
?
我们在上面的代码中会发现无论我们把处理逻辑直接写在路由里,还是拆分到控制器函数中,处理函数的签名都是这样的:
go
func(c *gin.Context)
这个就是Gin 框架的设计核心 ------ Context
对象(全名:*gin.Context
) 。 Gin 所有处理器函数的参数都是:
go
func(c *gin.Context)
它是 Gin 最重要的对象,包含:
方法 | 作用 |
---|---|
c.Param() |
路径参数 |
c.Query() |
查询参数 |
c.ShouldBindJSON() |
绑定 JSON |
c.JSON() |
返回 JSON |
c.Set() / c.Get() |
设置和获取上下文变量(常用于中间件) |
返回响应的常用方式:
方法 | 说明 |
---|---|
c.JSON(200, data) |
返回 JSON 数据 |
c.String(200, "text") |
返回纯文本响应 |
c.HTML(200, "tpl.html", data) |
渲染 HTML 模板 |
c.File("file.zip") |
直接下载文件 |
后面我们还会经常通过 c
对象来获取请求头、设置响应头、操作 Cookies、获取 token、设置上下文用户信息等,可以说 Context
是 Gin 应用的"灵魂级对象"。
小结
- 使用
c.DefaultQuery()
可以方便地从 URL 查询字符串中获取参数并设置默认值; - 使用
c.JSON()
可以快速返回 JSON 响应; - 控制器结构清晰,让每个接口的逻辑单独维护,项目更易扩展
现在我们的项目结构像这样:
go
gin-learn-notes/
├── controller/
│ └── hello.go
| └── index.go
├── router/
│ └── router.go
├── main.go
├── go.mod
最后
在本篇中,我们主要完成了以下内容:
- 初始化 Gin 项目,理解了
go mod init
和go get
的作用 - 编写了项目入口
main.go
,并学习了gin.Default()
与gin.New()
的区别 - 介绍了项目结构的基本划分,遵循单一职责原则
- 将路由从
main.go
拆分到router/router.go
,实现结构解耦 - 将逻辑从路由中抽出,创建了控制器
hello.go
,实现了参数接收与 JSON 输出
通过这些步骤,我们为后续的开发打下了清晰、规范的基础。Gin 项目的结构感,正在逐渐建立起来。
本篇对应代码提交记录
commit: 2146589efa7fb67fd60a64c5bc0e2cf4177363f0
👉 GitHub 源码地址:github.com/luokakale-k...
下一篇我们将继续深入学习:如何通过 POST 接口接收 JSON 参数并实现参数绑定。