全部文章 笔记与思考

Gin 框架零基础教程:从入门到实战

前言

欢迎来到 Gin 框架的零基础教程!无论你是刚接触 Go 语言的新手,还是想要快速上手 Web 开发的开发者,这篇教程都会带你从零开始,一步步掌握 Go 语言中最流行的轻量级 Web 框架 ——Gin。

什么是 Gin?

Gin 是一个用 Go (Golang) 编写的高性能 HTTP Web 框架。它提供了类似 Express.js 的简洁 API,同时凭借其基于 Radix 树的路由算法,实现了极致的性能,速度比标准的 net/http 库快了多达 40 倍。

为什么选择 Gin?

  • 🚀 极致性能:零分配路由,内存占用极低,支持高并发

  • 📦 轻量简洁:核心代码精简,无冗余依赖,上手成本低

  • 🔌 丰富中间件:内置日志、恢复、CORS 等中间件,支持自定义扩展

  • ✅ 参数校验:自动绑定并验证请求参数,减少重复代码

  • 🎯 路由分组:灵活的路由组织方式,支持 API 版本管理

  • 🛡️ panic 恢复:内置异常捕获,防止单个请求崩溃导致整个服务挂掉

前置知识

在开始之前,你只需要:

  1. 了解基本的计算机操作(会用终端 / 命令行)

  2. 了解 Go 语言的基础语法(变量、函数、结构体等)

  3. 了解基本的 HTTP 协议概念(请求、响应、GET/POST 方法等)


第一章:环境准备与安装

1.1 安装 Go 语言环境

Gin 框架依赖 Go 语言环境,首先我们需要安装 Go。

1.1.1 下载 Go 安装包

访问 Go 官方下载页面:https://go.dev/dl/

根据你的操作系统选择对应的安装包:

  • Windows:选择 go1.2X.X.windows-amd64.msi(推荐 64 位)

  • MacOS:选择 go1.2X.X.darwin-amd64.pkg(Intel 芯片)或 go1.2X.X.darwin-arm64.pkg(M1/M2 芯片)

  • Linux:选择 go1.2X.X.linux-amd64.tar.gz

💡 提示:Gin v1.10+ 要求 Go 版本至少为 1.25,建议安装最新的稳定版。

1.1.2 安装 Go

Windows 用户:

  • 双击下载的 go.msi 文件,一路点击 Next 即可。安装程序会自动配置环境变量,无需手动设置。

MacOS 用户:

  • 双击下载的 .pkg 文件,按照提示完成安装。同样会自动配置环境变量。

Linux 用户:

1
2
3
4
5
6
# 解压到 /usr/local 目录
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.25.0.linux-amd64.tar.gz

# 配置环境变量,编辑 ~/.bashrc 或 ~/.zshrc
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc

1.1.3 验证安装

打开终端,输入以下命令验证是否安装成功:

1
go version

如果输出类似下面的内容,说明安装成功:

1
go version go1.25.0 linux/amd64

1.2 配置 Go 模块(Go Modules)

从 Go 1.11 开始,Go 引入了模块(Modules)来管理依赖,这是现在官方推荐的依赖管理方式,我们必须启用它。

💡 国内用户注意:由于网络原因,直接下载依赖可能会超时,我们先配置国内代理:

1
2
3
4
5
6
7
8
9
# 设置 GOPROXY 代理(七牛云镜像,国内访问速度快)
# Windows PowerShell
$env:GOPROXY = "https://goproxy.cn,direct"

# Windows CMD
set GOPROXY=https://goproxy.cn,direct

# Linux/Mac
export GOPROXY=https://goproxy.cn,direct

如果你想永久生效,可以把上面的命令添加到你的系统环境变量配置文件中。

1.3 安装 Gin 框架

现在我们来创建第一个 Gin 项目,并安装 Gin 框架。

1.3.1 创建项目目录

首先,创建一个项目文件夹,比如 my-gin-app:

1
2
3
4
5
# 创建项目目录
mkdir my-gin-app

# 进入项目目录
cd my-gin-app

1.3.2 初始化 Go 模块

在项目目录下,初始化 Go 模块:

1
go mod init my-gin-app

这个命令会创建一个 go.mod 文件,用来管理你的项目依赖。执行成功后,你会看到输出:

1
2
3
go: creating new go.mod: module my-gin-app
go: to add module requirements and sums:
	go mod tidy

1.3.3 安装 Gin

执行以下命令安装 Gin 框架:

1
go get -u github.com/gin-gonic/gin

这个命令会:

  1. 下载 Gin 框架的最新版本

  2. 自动把依赖信息添加到 go.mod 文件中

  3. 生成 go.sum 文件,用来校验依赖的版本

等待命令执行完成,没有报错的话,Gin 就安装好了!


第二章:你的第一个 Gin 应用

2.1 编写 Hello World

现在我们来写第一个最简单的 Gin 程序,实现一个 ping 接口。

在项目根目录下,创建一个 main.go 文件,写入以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

// 导入必要的包
import (
	"net/http" // 标准库,用于 HTTP 状态码
	"github.com/gin-gonic/gin" // 导入 Gin 框架
)

func main() {
	// 1. 创建 Gin 引擎
	// gin.Default() 会创建一个默认的引擎,自带两个中间件:
	// - Logger:打印请求日志
	// - Recovery:捕获 panic,防止服务崩溃
	r := gin.Default()

	// 2. 定义路由
	// 当客户端用 GET 方法访问 /ping 路径时,执行后面的处理函数
	r.GET("/ping", func(c *gin.Context) {
		// c *gin.Context 是 Gin 的核心,它包含了请求和响应的所有信息

		// 3. 返回 JSON 响应
		// 第一个参数是 HTTP 状态码,http.StatusOK 就是 200
		// 第二个参数是响应数据,gin.H 是 map[string]interface{} 的快捷方式
		c.JSON(http.StatusOK, gin.H{
			"message": "pong",
			"status":  "success",
		})
	})

	// 4. 启动服务器
	// 默认监听 0.0.0.0:8080,你也可以指定端口,比如 r.Run(":9090")
	r.Run() 
}

2.2 运行程序

在终端中,执行以下命令运行程序:

1
go run main.go

如果一切正常,你会看到类似下面的输出:

1
2
3
4
5
6
7
8
9
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080

这说明你的 Gin 服务已经成功启动了,正在监听 8080 端口!

2.3 测试接口

现在打开你的浏览器,访问 http://localhost:8080/ping,或者用 curl.exe 命令测试:

1
curl.exe http://localhost:8080/ping

你会看到返回的 JSON 数据:

1
{"message":"pong","status":"success"}

🎉 恭喜!你已经成功运行了你的第一个 Gin 应用!


第三章:核心功能:路由管理

路由是 Web 框架的基础,它决定了如何响应客户端的请求。Gin 提供了非常强大灵活的路由功能。

3.1 基础路由

Gin 支持所有标准的 HTTP 方法:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS 等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// GET请求:获取资源
	r.GET("/users", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "获取用户列表"})
	})

	// POST请求:创建资源
	r.POST("/users", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "创建新用户"})
	})

	// PUT请求:更新资源(完整更新)
	r.PUT("/users/:id", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "更新用户信息"})
	})

	// DELETE请求:删除资源
	r.DELETE("/users/:id", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "删除用户"})
	})

	// Any:匹配所有请求方法
	r.Any("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "支持所有请求方法:%s", c.Request.Method)
	})

	r.Run()
}

可以用 Postman 或者 curl.exe 来测试这些接口:

1
2
3
4
5
6
curl.exe -X GET http://localhost:8080/users
curl.exe -X POST http://localhost:8080/users
curl.exe -X PUT http://localhost:8080/users/123
curl.exe -X DELETE http://localhost:8080/users/123
curl.exe -X PATCH http://localhost:8080/users/123
curl.exe -X OPTIONS http://localhost:8080/test

3.2 路径参数(Route Parameters)

有时候我们需要从 URL 路径中获取动态参数,比如用户 ID、文章 ID 等。Gin 支持用 :param 的方式定义动态参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.GET("/users/:id", func(c *gin.Context) {
		// 使用 c.Param() 获取路径参数
		userID := c.Param("id")

		c.JSON(http.StatusOK, gin.H{
			"user_id": userID,
		})
	})

	// 多个参数
	r.GET("/articles/:category/:article_id", func(c *gin.Context) {
		category := c.Param("category")
		articleID := c.Param("article_id")

		c.JSON(http.StatusOK, gin.H{
			"category":  category,
			"articleID": articleID,
		})
	})

	r.Run()
}

测试一下:

1
2
3
4
5
curl.exe http://localhost:8080/users/123
# 输出:{"user_id":"123"}

curl.exe http://localhost:8080/articles/tech/456
# 输出:{"article_id":"456","category":"tech"}

3.3 查询参数(Query Parameters)

查询参数是 URL 中 ? 后面的键值对,比如 /search?query=gin&page=1。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
r.GET("/search", func(c *gin.Context) {
	// 获取查询参数,如果参数不存在,返回空字符串
	query := c.Query("query")
	
	// 获取查询参数,带默认值,如果参数不存在,返回默认值
	page := c.DefaultQuery("page", "1")
	limit := c.DefaultQuery("limit", "10")
	
	c.JSON(http.StatusOK, gin.H{
		"query": query,
		"page":  page,
		"limit": limit,
	})
})

测试一下:

1
2
curl.exe "http://localhost:8080/search?query=golang&page=2"
# 输出:{"limit":"10","page":"2","query":"golang"}

3.4 路由分组(Route Grouping)

当你的 API 越来越多的时候,你需要对路由进行分类管理。比如 API 版本、模块划分。Gin 的路由分组可以帮你轻松实现这一点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// 创建 v1 版本的 API 分组
	v1 := r.Group("/api/v1")
	{
		// 实际路径是 /api/v1/users
		v1.GET("/users", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"message": "v1 用户列表"})
		})

		// 实际路径是 /api/v1/users/:id
		v1.GET("users/:id", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"message": "v1 用户详情"})
		})
	}

	// 创建 v2 版本的 API 分组
	v2 := r.Group("/api/v2")
	{
		// 实际路径是 /api/v2/users
		v2.GET("/users", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"message": "v2 用户列表"})
		})

		// 实际路径是 /api/v2/users/:id
		v2.GET("users/:id", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"message": "v2 用户详情"})
		})
	}

	// 嵌套分组:比如 /api/v1/admin
	admin := v1.Group("/admin")
	{
		admin.GET("/dashboard", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"message": "管理员面板"})
		})
	}

	r.Run()
}

路由分组的好处是:

  1. 代码结构更清晰,便于维护

  2. 可以给整个分组统一应用中间件(后面会讲)

  3. 支持无限嵌套,满足复杂的路由组织需求

3.5 404 处理

当用户访问不存在的路由时,Gin 默认会返回 404 页面。我们可以自定义 404 的处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.NoRoute(func(c *gin.Context) {
		c.JSON(http.StatusNotFound, gin.H{
			"code":    404,
			"message": "接口被陈导偷走了,去问问陈导吧",
		})
	})

	r.Run()
}


第四章:请求处理与参数绑定

处理客户端发来的请求数据是后端开发的核心任务。Gin 提供了非常便捷的参数绑定和验证功能。

4.1 获取表单数据

处理 HTML 表单提交(application/x-www-form-urlencoded):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.POST("/login", func(c *gin.Context) {
		// 获取表单数据
		username := c.PostForm("username")
		password := c.DefaultPostForm("password", "123456")

		c.JSON(http.StatusOK, gin.H{
			"username": username,
			"password": password,
		})
	})

	r.Run()
}

测试:

1
2
curl.exe -X POST http://localhost:8080/login -d "username=test&password=123"
# 输出:{"password":"123","username":"test"}

4.2 JSON 参数绑定

现代 API 开发最常用的就是 JSON 格式的请求体。Gin 可以自动把 JSON 请求体绑定到 Go 的结构体上,还能自动校验参数!

首先,我们定义一个结构体,用来接收请求参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// 定义注册请求的结构体
type RegisterRequest struct {
	// json:"username"	表示 JSON 中的字段名
	// binding:"required"	表示这个字段是必填的
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required,min=6"` // 密码至少6位
	Email    string `json:"email" binding:"required,email"`    // 必须是邮箱格式
	Age      int    `json:"age" binding:"gte=18,lte=60"`       // 年龄必须在18-60之间
}

func main() {
	r := gin.Default()

	// 然后在处理函数中绑定参数:
	r.POST("/register", func(c *gin.Context) {
		var req RegisterRequest

		// ShouldBindJSON 会自动解析JSON请求体,绑定到结构体,并执行校验
		if err := c.ShouldBindJSON(&req); err != nil {
			// 如果绑定或校验失败,返回400错误
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}

		// 绑定成功
		c.JSON(http.StatusOK, gin.H{
			"message": "注册成功",
			"data":    req,
		})
	})

	r.Run()
}

测试一下:

1
2
3
4
# 正确的请求
curl.exe -X POST http://localhost:8080/register -H "Content-Type: application/json" -d '{\"username\":\"test\",\"password\":\"123456\",\"email\":\"test@example.com\",\"age\":20}'

# 输出:{"data":{"username":"test","password":"123456","email":"test@example.com","age":20},"message":"注册成功"}
  • -X POST 表示使用 POST 方法
  • -H “Content-Type: application/json” 表示请求体是 JSON 格式
  • -d 后面是 JSON 数据。

1
2
3
4
# 错误的请求(年龄太小)
curl.exe -X POST http://localhost:8080/register -H "Content-Type: application/json" -d '{\"username\":\"test\",\"password\":\"123456\",\"email\":\"test@example.com\",\"age\":10}' 

# 输出:{"error":"Key: 'RegisterRequest.Age' Error:Field validation for 'Age' failed on the 'gte' tag"}

💡 常用的 binding 校验规则:

  • required:必填

  • email:邮箱格式

  • min=6:最小值(数字)/ 最小长度(字符串)

  • max=60:最大值

  • gte=18:大于等于

  • lte=60:小于等于

  • oneof=male female:必须是其中一个值

4.3 Query 参数绑定

除了 JSON,我们也可以把 URL 的查询参数绑定到结构体:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

type UserQuery struct {
	Name string `form:"name" binding:"required"`
	Age  int    `form:"age" binding:"min=18"`
}

func main() {
	r := gin.Default()

	r.GET("/users", func(c *gin.Context) {
		var query UserQuery
		if err := c.ShouldBindQuery(&query); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		c.JSON(http.StatusOK, gin.H{"query": query})
	})

	r.Run()
}

测试:

1
2
curl.exe "http://localhost:8080/users?name=Alice&age=25"
# 输出:{"query":{"Name":"Alice","Age":25}}

4.4 文件上传

Gin 也支持文件上传,非常简单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
	"log"
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.POST("/upload", func(c *gin.Context) {
		// 获取上传的文件,参数是表单的name
        // c.FormFile() 会返回一个 *multipart.FileHeader 类型的对象,包含了文件的基本信息,参数是表单的 name 属性值
		file, err := c.FormFile("file")
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		// 打印文件信息
		log.Printf("上传文件名: %s, 文件大小: %d KB", file.Filename, file.Size/1024)

		// 文件保存到本地
		dst := "./uploads/" + file.Filename
		// 确保 upload 目录存在。os.ModePerm 是 0777,表示所有用户都有读写执行权限,系统会根据 umask 自动调整权限。
		if err := os.MkdirAll("./uploads", os.ModePerm); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "目录创建失败"})
		}

		if err := c.SaveUploadedFile(file, dst); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "文件保存失败"})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"message":  "上传成功",
			"filename": file.Filename,
		})
	})

	r.Run()
}

测试:

1
curl.exe -X POST http://localhost:8080/upload -F "file=@这是个测试文件.txt"


第五章:响应处理

Gin 支持多种格式的响应,满足不同的场景需求。

5.1 JSON 响应(最常用)

这是 API 开发最常用的响应格式,我们之前已经用过很多次了:

1
2
3
4
5
c.JSON(http.StatusOK, gin.H{
	"code":    200,
	"message": "success",
	"data":    data,
})

5.2 字符串响应

简单的文本响应:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.GET("/hello", func(c *gin.Context) {
		name := c.DefaultQuery("name", "Guest")
		c.String(http.StatusOK, "Hello, %s!", name)
	})

	r.Run()
}

测试:

1
curl.exe "http://localhost:8080/hello"  

5.3 HTML 模板渲染

如果你需要服务端渲染 HTML 页面,Gin 也支持 HTML 模板:

首先,创建 templates 目录,然后创建 index.html 文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!DOCTYPE html>
<html>
<head>
    {{.title}}
</head>
<body>
    <h1>{{.message}}</h1>
    <p>欢迎你,{{.name}}</p>
</body>
</html>

然后在 Gin 中加载模板:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// 加载 templates 目录下的所有 html 文件
	r.LoadHTMLGlob("templates/*")

	r.GET("/index", func(c *gin.Context) {
		// 渲染模板,传递参数
		c.HTML(http.StatusOK, "index.html", gin.H{
			"title":   "我的主页",
			"message": "欢迎来到 Gin 的世界!",
			"name":    "Gopher",
		})
	})

	r.Run()
}

测试:

1
curl.exe http://localhost:8080/index

也可以在浏览器中访问 http://localhost:8080/index,你会看到渲染后的 HTML 页面。

5.4 静态文件服务

当你有静态资源(比如 CSS、JS、图片,或者前端打包后的文件),可以用 Gin 快速托管:

1
2
3
4
// 把 /static 路径映射到本地的 ./static 目录
r.Static("/static", "./static")

// 比如你有 ./static/logo.png,就可以通过 http://localhost:8080/static/logo.png 访问

如果是前后端分离的项目,前端打包后的 dist 目录可以这样托管:

1
r.Static("/", "./dist")

5.5 文件下载

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()

	r.GET("/download", func(c *gin.Context) {
		// 直接返回文件给客户端下载
		c.File("./test.txt")
	})

	r.Run()
}

5.6 重定向

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.GET("/old", func(c *gin.Context) {
		// 301 永久重定向(不推荐)
		// c.Redirect(http.StatusMovedPermanently, "https://symphonyw.github.io/")

		// 302 临时重定向(默认)
		c.Redirect(http.StatusFound, "https://symphonyw.github.io/")

		// 307 临时重定向,保持请求方法不变(推荐)
		c.Redirect(http.StatusTemporaryRedirect, "https://symphonyw.github.io/")
	})

	r.Run()
}

测试:

1
curl.exe -I http://localhost:8080/old

或者直接在浏览器中访问 http://localhost:8080/old,你会被重定向到我的博客主页。


第六章:中间件(Middleware)

中间件是 Gin 中非常强大的特性,它可以在请求到达处理函数之前或之后执行一些通用的逻辑。比如日志记录、身份认证、跨域处理、权限校验等。

6.1 中间件的工作原理

中间件本质上就是一个拦截器,请求会先经过中间件,然后才到你的业务处理函数,处理完之后,响应还会再经过中间件。

你可以把它想象成安检:乘客(请求)先过安检(中间件),然后才能进去坐飞机(业务处理),出来的时候还要再过安检。

6.2 默认中间件

还记得我们之前用的 gin.Default() 吗?它默认就帮我们加了两个中间件:

  • gin.Logger():打印请求日志,比如请求方法、路径、耗时、状态码等

  • gin.Recovery():捕获 panic,防止因为一个请求的错误导致整个服务崩溃

如果你想要一个完全干净的引擎,没有任何中间件,可以用 gin.New():

1
2
3
4
r := gin.New()
// 然后你可以手动添加你需要的中间件
r.Use(gin.Logger())
r.Use(gin.Recovery())

6.3 自定义中间件

我们来写一个自定义的日志中间件,看看中间件是怎么工作的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 自定义日志中间件
func LoggerMiddleware() gin.HandlerFunc {
	// 返回一个 gin.HandlerFunc 类型的函数
	return func(c *gin.Context) {
		// 请求处理之前执行的逻辑
		startTime := time.Now()

		// 调用下一个处理函数(可能是下一个中间件,也可能是最终的业务处理函数)
		c.Next()

		// 请求处理之后执行的逻辑
		endTime := time.Now()
		latency := endTime.Sub(startTime) // 计算耗时

		// 获取请求信息
		method := c.Request.Method
		path := c.Request.URL.Path
		statusCode := c.Writer.Status() // 响应状态码
		clientIP := c.ClientIP()       // 客户端 IP

		// 打印日志
		log.Printf("[%s] %s | %d | %s | %v", method, path, statusCode, clientIP, latency)
	}
}

然后我们就可以使用这个中间件了:

1
2
3
4
5
6
7
8
func main() {
	r := gin.Default()
	
	// 全局使用:对所有路由生效
	r.Use(LoggerMiddleware())

	// ... 定义路由 ...
}

6.4 中间件的作用范围

中间件可以作用在不同的范围:

  1. 全局中间件:对所有路由生效,用 r.Use()

  2. 分组中间件:只对某个路由分组生效,用 group.Use()

  3. 单个路由中间件:只对某个特定的路由生效,直接在路由定义时添加中间件参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 认证中间件示例
func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 从请求头获取 Token
		token := c.GetHeader("Authorization")
		
		// 简单的验证(实际项目中要验证 JWT)
		if token != "Bearer my-secret-token" {
			// 验证失败,终止请求链,返回 401
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"error": "未授权访问,请先登录",
			})
			return
		}

		// 验证通过,把用户信息存到 Context 中,供后续处理函数使用
		c.Set("userID", 123)
		c.Set("username", "test")

		// 继续处理请求
		c.Next()
	}
}

func main() {
	r := gin.Default()

	// 1. 全局中间件
	r.Use(LoggerMiddleware())

	// 公开路由,不需要认证
	r.POST("/login", loginHandler)

	// 2. 分组中间件:需要认证的接口
	authGroup := r.Group("/api")
	authGroup.Use(AuthMiddleware()) // 这个分组下的所有路由都需要认证
	{
		authGroup.GET("/profile", profileHandler)
		authGroup.POST("/update", updateHandler)
	}

	// 3. 单个路由中间件
	r.GET("/special", AuthMiddleware(), specialHandler)

	r.Run()
}

💡 关键方法说明:

  • c.Next():调用下一个处理函数,继续执行链

  • c.Abort():终止执行链,后面的处理函数不会再执行

  • c.Set(key, value) / c.Get(key):在中间件和处理函数之间传递数据

6.5 常用社区中间件:解决跨域问题

开发前后端分离项目的时候,最常见的问题就是跨域(CORS)。

什么是跨域?当你的前端页面和后端 API 不在同一个域名、协议或端口下时,浏览器会出于安全考虑阻止前端访问后端接口,这就是跨域问题。

我们可以用 gin-contrib/cors 这个官方的中间件来解决。

首先安装:

1
go get github.com/gin-contrib/cors

然后使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import "github.com/gin-contrib/cors"

func main() {
	r := gin.Default()

	// 配置 CORS 中间件
	r.Use(cors.New(cors.Config{
		// 允许的来源,比如前端的域名
		AllowOrigins:     []string{"http://localhost:3000", "https://your-frontend.com"},
		// 允许的请求方法
		AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
		// 允许的请求头
		AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
		// 是否允许携带 Cookie
		AllowCredentials: true,
		// 预检请求的缓存时间
		MaxAge:           12 * time.Hour,
	}))

	// 如果你是开发环境,想快速测试,可以用默认配置(允许所有来源,不推荐生产环境)
	// r.Use(cors.Default())

	// ... 路由 ...
}

这样就完美解决了跨域问题!


第七章:配置管理

在实际项目中,我们不会把配置(比如数据库地址、端口、密钥)硬编码在代码里,而是用配置文件来管理。最常用的就是 Viper 库。

7.1 安装 Viper

1
go get github.com/spf13/viper

7.2 编写配置文件

在项目根目录创建 config.yaml 文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 服务配置
server:
  port: 8080
  mode: debug # debug/release

# 数据库配置
database:
  host: localhost
  port: 3306
  user: root
  password: 123456
  dbname: myapp

# JWT 配置
jwt:
  secret: my-secret-key
  expire: 24 # 过期时间,小时

7.3 读取配置

创建 config/config.go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package config

import (
	"log"

	"github.com/spf13/viper"
)

// 定义配置结构体
type Config struct {
	Server   ServerConfig
	Database DatabaseConfig
	JWT      JWTConfig
}

type ServerConfig struct {
	Port int
	Mode string
}

type DatabaseConfig struct {
	Host     string
	Port     int
	User     string
	Password string
	DBName   string
}

type JWTConfig struct {
	Secret string
	Expire int
}

var GlobalConfig Config

// 加载配置
func InitConfig() {
	// 设置配置文件名(不带后缀)
	viper.SetConfigName("config")
	// 设置配置文件路径
	viper.AddConfigPath(".")
	// 设置配置文件类型
	viper.SetConfigType("yaml")

	// 读取配置文件
	if err := viper.ReadInConfig(); err != nil {
		log.Fatalf("读取配置文件失败: %v", err)
	}

	// 把配置反序列化到结构体
	if err := viper.Unmarshal(&GlobalConfig); err != nil {
		log.Fatalf("解析配置失败: %v", err)
	}

	log.Println("配置加载成功")
}

然后在 main.go 中初始化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
	// 加载配置
	config.InitConfig()

	r := gin.Default()

	// 从配置中获取端口
	port := config.GlobalConfig.Server.Port
	r.Run(fmt.Sprintf(":%d", port))
}

这样,你就可以轻松管理不同环境的配置了,开发环境用 config.dev.yaml,生产环境用 config.prod.yaml。


第八章:身份认证:JWT

在 API 开发中,JWT(JSON Web Token)是最常用的身份认证方式。

JWT 是一种基于 Token 的认证机制,客户端登录成功后会获得一个 JWT Token,之后每次请求都携带这个 Token,服务器验证 Token 的合法性来判断用户是否有权限访问资源。

我们来看看怎么在 Gin 中使用 JWT。

8.1 安装依赖

1
go get github.com/golang-jwt/jwt/v5

8.2 实现 JWT 工具函数

创建 utils/jwt.go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package utils

import (
	"errors"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"my-gin-app/config"
)

// 定义 Claims,就是 Token 中存储的信息
type Claims struct {
	UserID   uint   `json:"user_id"`
	Username string `json:"username"`
	jwt.RegisteredClaims
}

// 生成 Token
func GenerateToken(userID uint, username string) (string, error) {
	// 设置过期时间
	expireTime := time.Now().Add(time.Duration(config.GlobalConfig.JWT.Expire) * time.Hour)

	// 创建 Claims
	claims := Claims{
		UserID:   userID,
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(expireTime),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    "my-gin-app",
		},
	}

	// 生成 Token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	signedToken, err := token.SignedString([]byte(config.GlobalConfig.JWT.Secret))

	return signedToken, err
}

// 验证 Token
func ParseToken(tokenString string) (*Claims, error) {
	// 解析 Token
	token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(config.GlobalConfig.JWT.Secret), nil
	})

	if err != nil {
		return nil, err
	}

	// 验证 Claims
	if claims, ok := token.Claims.(*Claims); ok && token.Valid {
		return claims, nil
	}

	return nil, errors.New("无效的 Token")
}

8.3 JWT 认证中间件

然后我们就可以写 JWT 认证中间件了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func JWTAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 获取 Token
		tokenString := c.GetHeader("Authorization")
		if tokenString == "" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "请先登录"})
			return
		}

		// 去掉 Bearer 前缀
		tokenString = strings.TrimPrefix(tokenString, "Bearer ")

		// 验证 Token
		claims, err := utils.ParseToken(tokenString)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Token 无效或已过期"})
			return
		}

		// 把用户信息存到 Context 中
		c.Set("userID", claims.UserID)
		c.Set("username", claims.Username)

		c.Next()
	}
}

8.4 登录接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func LoginHandler(c *gin.Context) {
	var req struct {
		Username string `json:"username" binding:"required"`
		Password string `json:"password" binding:"required"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 这里简化了,实际要去数据库查询用户,验证密码
	if req.Username == "admin" && req.Password == "123456" {
		// 生成 Token
		token, err := utils.GenerateToken(1, "admin")
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "生成 Token 失败"})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"token": token,
		})
		return
	}

	c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
}

这样,你的 API 就有了完整的身份认证功能!


第九章:实战:用户管理 API

现在我们把前面学的所有知识整合起来,做一个完整的用户管理 API 项目。

9.1 项目结构

我们来规划一下项目的目录结构,这是一个标准的分层架构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
my-gin-app/
├── main.go              # 程序入口
├── go.mod               # 依赖管理
├── go.sum
├── config.yaml          # 配置文件
├── config/              # 配置相关
│   └── config.go
├── controllers/         # 控制器,处理请求
│   └── user_controller.go
├── middleware/          # 中间件
│   ├── auth.go
│   └── logger.go
├── models/              # 数据模型
│   └── user.go
├── utils/               # 工具函数
│   └── jwt.go
└── uploads/             # 上传文件目录

9.2 完整代码实现

我们来实现一个完整的用户管理 API,包含:

  • 登录接口

  • 获取用户列表

  • 获取用户详情

  • 创建用户

  • 更新用户

  • 删除用户

完整的代码就不全部贴在这里了,你可以参考前面的知识点,把它们组合起来。最终的效果是:

  • 公开接口:登录

  • 私有接口:需要 JWT 认证才能访问

  • 统一的参数校验

  • 统一的错误处理

  • 日志记录


第十章:单元测试

写完代码之后,我们需要测试我们的接口。Gin 提供了很方便的测试方式。

创建 main_test.go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func TestPingRoute(t *testing.T) {
	// 设置为测试模式
	gin.SetMode(gin.TestMode)

	// 初始化路由
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "pong"})
	})

	// 创建测试请求
	req, _ := http.NewRequest("GET", "/ping", nil)
	w := httptest.NewRecorder()

	// 执行请求
	r.ServeHTTP(w, req)

	// 断言结果
	assert.Equal(t, 200, w.Code)
	assert.Equal(t, `{"message":"pong"}`, w.Body.String())
}

然后运行测试:

1
go test -v

第十一章:生产环境部署

当你的项目开发完成,就可以部署到生产环境了。

11.1 切换到 Release 模式

Gin 默认是 Debug 模式,会打印很多调试信息。生产环境要切换到 Release 模式:

1
2
// 在 main 函数最开始
gin.SetMode(gin.ReleaseMode)

或者通过环境变量:

1
export GIN_MODE=release

11.2 编译二进制文件

Go 可以编译成单个二进制文件,非常方便部署:

1
2
# 编译
go build -o myapp main.go

编译完成后,你会得到一个 myapp 文件,这就是你的整个应用,不需要任何依赖,直接运行就可以了。

11.3 后台运行

在 Linux 服务器上,你可以用 systemd 来管理你的服务,让它后台运行,开机自启。

创建 /etc/systemd/system/myapp.service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[Unit]
Description=My Gin App
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/root/my-gin-app
ExecStart=/root/my-gin-app/myapp
Restart=always
Environment=GIN_MODE=release

[Install]
WantedBy=multi-user.target

然后:

1
2
3
4
5
6
7
8
# 重载配置
systemctl daemon-reload

# 启动服务
systemctl start myapp

# 设置开机自启
systemctl enable myapp

第十二章:常见问题排查

1. go get 下载依赖超时

  • 解决:设置 GOPROXY 代理,前面已经讲过了。

2. 端口被占用

  • 错误:listen tcp :8080: bind: address already in use

  • 解决:换个端口,或者杀掉占用端口的进程:lsof -i :8080 然后 kill -9 PID

3. 跨域问题

  • 解决:使用 CORS 中间件,前面已经讲过。

4. 参数绑定失败

  • 检查:JSON 格式是否正确,结构体的字段是否是大写(Go 的反射只能访问大写字段),tag 是否正确。

5. 静态文件访问不到

  • 检查:静态文件的路径是否正确,r.Static 的第一个参数是 URL 路径,第二个是本地路径。

总结

恭喜你!读到这里,你已经掌握了 Gin 框架的核心用法:

✅ 环境搭建:Go 安装、Gin 安装、依赖管理
✅ 基础入门:第一个 Hello World 程序
✅ 路由管理:各种 HTTP 方法、参数、路由分组
✅ 请求处理:表单、JSON、文件上传、参数校验
✅ 响应处理:JSON、HTML、静态文件、重定向
✅ 中间件:自定义中间件、CORS、JWT 认证
✅ 配置管理:用 Viper 管理配置文件
✅ 项目实战:完整的用户管理 API
✅ 部署运维:生产环境配置、部署

Gin 是一个非常强大而简洁的框架,它把 Go 语言的高性能和 Web 开发的便捷性完美结合在了一起。希望这篇教程能帮助你快速入门,开启你的 Go Web 开发之旅!

接下来,你可以继续深入学习:

  • 数据库集成(GORM)

  • 限流、熔断等高级中间件

  • 日志系统

  • 监控与链路追踪

  • 容器化部署(Docker)

祝你学习愉快!🚀