go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南
往期回顾
前言
前一节我们使用了go语言简单的通过net/http
搭建了go web服务,但是仅使用 Go 的标准库 net/http
来构建复杂的 Web 应用可能会有些繁琐。这时,一个优秀的 Web 框架就显得至关重要。Gin 就是其中最受欢迎的选择之一。它是一个用 Go 编写的高性能 Web 框架,以其极快的速度和丰富的功能集而闻名。
在本篇博客中,我们将带你从零开始,一步步学习如何使用 Gin 框架搭建你的第一个 Web 应用。
为什么选择 Gin?
在众多 Go Web 框架中,Gin 脱颖而出,主要有以下几个原因:
- 极致性能:Gin 的路由基于 Radix 树,内存占用极小,性能表现卓越,是 Go 社区中最快的框架之一。
- 中间件支持:Gin 的中间件(Middleware)机制非常强大,可以将一系列可插拔的组件(如日志、授权、Gzip 压缩)串联起来,作用于请求处理的生命周期中。
- 错误管理:Gin 提供了一种便捷的方式来收集和处理 HTTP 请求期间发生的所有错误,让你的应用更加健壮。
- JSON 验证:Gin 可以轻松地解析和验证请求中的 JSON 数据,这对于构建 RESTful API 至关重要。
- 路由组:通过路由组(Route Grouping),你可以更好地组织你的路由,例如将需要相同授权中间件的路由归为一组。
- 上手简单:Gin 的 API 设计非常直观,学习曲线平缓,文档齐全,对新手非常友好。
准备工作
在开始之前,请确保你已经完成了以下准备:
- 安装 Go 环境:访问 Go 官方网站 下载并安装适合你操作系统的 Go 版本(建议 1.18 或更高版本,我使用的是1.24.4)。
- 配置你的工作区:设置好你的
GOPATH
和GOROOT
环境变量。 - 一个代码编辑器:推荐使用 VS Code 并安装 Go 扩展,或者使用 GoLand。
第一个 Gin 应用:“Hello, Gin!”
第一步:创建项目
首先,创建一个新的项目目录,并使用 Go Modules 初始化项目。
如果是使用vscode的童鞋,参见以下代码:
# 创建一个项目文件夹
mkdir gin-hello-world
cd gin-hello-world
# 初始化 Go 模块
go mod init gin-hello-world
`go mod init` 命令会创建一个 `go.mod` 文件,用于跟踪和管理项目的依赖。
第二步:下载Gin 依赖
在当前项目目录下,键入如下命令,安装Gin依赖
go get -u github.com/gin-gonic/gin
如果你使用的是 Goland
存在拉取 Gin依赖失败的情况,请配置GoProxy
GOPROXY=https://goproxy.cn,direct.
第二步:编写main.go
main.go
package main
import "github.com/gin-gonic/gin"
func main() {
// 1. 创建一个默认的 Gin 引擎
r := gin.Default()
// 2. 定义一个路由和处理函数
// 当客户端以 GET 方法请求 /hello 路径时,执行后面的匿名函数
r.GET("/hello", func(c *gin.Context) {
// c.JSON 是一个辅助函数,可以序列化给定的结构体为 JSON 并写入响应体
c.JSON(200, gin.H{
"message": "Hello, Gin!",
})
})
// 3. 启动 HTTP 服务器
r.Run(":8888")
}
直接运行main.go,访问localhost:8888/hello
或者使用请求工具
curl http://localhost:8888/hello
可以收到一个json的返回
{"message":"Hello, Gin!"}
Gin 核心概念深入
掌握了基础之后,让我们来探索 Gin 的一些核心功能。
1. 路由(Routing)
Gin 提供了非常灵活的路由功能。
路由参数
你可以定义包含动态参数的路由。
r.GET("/users/:name", func(c *gin.Context) {
// 使用 c.Param() 获取路由参数
name := c.Param("name")
c.String(200, "Hello, %s", name)
})
测试:访问 http://localhost:8888/users/jerry
,你会看到 Hello, jerry
。
查询字符串参数
获取 URL 中的查询参数(如 ?page=1&size=10
)。
r.GET("/articles", func(c *gin.Context) {
// 使用 c.DefaultQuery() 获取参数,如果不存在则使用默认值
page := c.DefaultQuery("page", "1")
// 使用 c.Query() 获取参数
size := c.Query("size") // 如果不存在,返回空字符串
c.JSON(200, gin.H{
"page": page,
"size": size,
})
})
测试:访问 http://localhost:8888/articles?page=2&size=20
。
处理 POST 请求和数据绑定
构建 API 不可避免地要处理 POST 请求,通常是 JSON 格式的数据。Gin 的数据绑定功能可以极大地简化这个过程。
首先,定义一个与 JSON 结构匹配的 Go 结构体:
// 定义一个 Article 结构体
type Article struct {
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
}
binding:"required"
标签表示这个字段是必需的。
然后,创建一个处理 POST 请求的路由:
r.POST("/articles", func(c *gin.Context) {
// 声明一个 Article 类型的变量
var article Article
// 使用 ShouldBindJSON 将请求的 JSON body 绑定到 article 变量上
// ShouldBindJSON 会在绑定失败时返回错误,但不会中止请求
if err := c.ShouldBindJSON(&article); err != nil {
// 如果绑定失败,返回一个 400 错误和错误信息
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 绑定成功,返回一个 200 成功响应和接收到的数据
c.JSON(200, gin.H{
"status": "received",
"title": article.Title,
"content": article.Content,
})
})
使用 curl
或者 postman
来测试这个 POST 端点:
curl -X POST http://localhost:8888/articles \
-H "Content-Type: application/json" \
-d '{"title":"aaaa", "content":"123456789"}'
你会收到成功的响应。如果尝试发送不完整的数据(例如缺少 title
),你会收到一个 400 错误。
2. 路由组 (Router Grouping)
当应用变大时,路由会变得复杂。路由组可以帮助你更好地组织代码,并为一组路由共享中间件。
类似于 SpringBoot 中
@RequestMapping("/api/v1")
设置公共请求路径。
func main() {
r := gin.Default()
// 创建一个 /api/v1 的路由组
v1 := r.Group("/api/v1")
{
// 在 v1 组下定义路由
v1.GET("/users", func(c *gin.Context) {
c.JSON(200, gin.H{"users": []string{"Alice", "Bob", "Charlie"}})
})
v1.GET("/products", func(c *gin.Context) {
c.JSON(200, gin.H{"products": []string{"Laptop", "Mouse"}})
})
}
// 创建一个 /api/v2 的路由组
v2 := r.Group("/api/v2")
{
v2.GET("/users", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "v2 users endpoint"})
})
}
r.Run()
}
现在,你可以通过 /api/v1/users
和 /api/v2/users
访问不同版本的 API。
3. 中间件 (Middleware)
中间件是 Gin 的精髓所在。它是一个在请求被处理之前或之后执行的函数。gin.Default()
就默认使用了 Logger 和 Recovery 中间件。
类似于SpringBoot aop切面实现的全局请求拦截器。只不过再go中被叫做中间件。
让我们来创建一个自定义的日志中间件。
package main
import (
"log"
"time"
"github.com/gin-gonic/gin"
)
// 自定义日志中间件
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 请求开始时间
startTime := time.Now()
// 调用 c.Next() 执行后续的处理函数
c.Next()
// 请求结束时间
endTime := time.Now()
// 计算执行时间
latencyTime := endTime.Sub(startTime)
// 获取请求方法和路径
reqMethod := c.Request.Method
reqUri := c.Request.RequestURI
// 获取状态码
statusCode := c.Writer.Status()
// 获取客户端 IP
clientIP := c.ClientIP()
// 打印日志
log.Printf("| %3d | %13v | %15s | %s | %s |",
statusCode,
latencyTime,
clientIP,
reqMethod,
reqUri,
)
}
}
func main() {
r := gin.New() // 使用 gin.New() 创建一个没有任何中间件的引擎
// 全局使用我们的自定义日志中间件
r.Use(LoggerMiddleware())
// 使用 Gin 默认的 Recovery 中间件,防止 panic 导致程序崩溃
r.Use(gin.Recovery())
r.GET("/test-middleware", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Middleware test successful!"})
})
r.Run()
}
这个中间件会记录每个请求的状态码、耗时、IP、方法和路径。你可以用它来替换 Gin 默认的 Logger,或者为特定路由组添加认证中间件。
关于使用r := gin.New() 的代码解释,
通过使用 gin.New() 创建一个没有任何中间件的引擎,相比 gin.default()创建的更纯净,因为gin.default()自带了两个中间件:
gin.Logger()
: Gin 自带的日志中间件,会在控制台打印每条请求的日志。gin.Recovery()
: 一个恢复(Recovery)中间件,它能捕获任何panic
,防止整个程序因此崩溃,并会返回一个500
错误。- 即 gin.default() 等价于 r := gin.New();r.Use(gin.Logger(), gin.Recovery())
所以为了体验更纯粹的gin并设置自定义的日志中间件,使用了gin.new(),当然也可以使用gin.default,只不过日志信息会有重叠
4. HTML 模板渲染
虽然 Gin 常用于构建 API,但它同样能出色地渲染 HTML 页面。
第一步:创建模板文件
在你的项目根目录下创建一个 templates
文件夹,并在其中创建一个 index.html
文件。
templates/index.html
:
类似于java 中的Springboot thymeleaf 模板的方式,在模板之间进行传递参数进行渲染
<!DOCTYPE html>
<html>
<head>
<title>{{ .title }}</title>
</head>
<body>
<h1>Hello, {{ .name }}!</h1>
<p>Welcome to our website rendered by Gin.</p>
</body>
</html>
第二步:编写 Go 代码
修改 main.go
来加载并渲染这个模板。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 告诉 Gin 模板文件在哪里
r.LoadHTMLGlob("templates/*")
r.GET("/index", func(c *gin.Context) {
// 使用 c.HTML 渲染模板
// 我们传递一个 gin.H 对象,模板中可以通过 .key 的方式访问数据
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "Gin Template Rendering",
"name": "Guest",
})
})
r.Run()
}
运行程序并访问 http://localhost:8888/index
,你将看到一个动态渲染的 HTML 页面。
5. 文件上传
处理文件上传是 Web 应用的常见需求。Gin 让这个过程变得异常简单。
第一步:更新 HTML 模板
在 templates
目录下创建一个 upload.html
。
templates/upload.html
:
<!DOCTYPE html>
<html>
<head>
<title>File Upload</title>
</head>
<body>
<h2>Upload a File</h2>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
</body>
</html>
第二步:编写后端处理逻辑
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.LoadHTMLGlob("templates/*")
// 为上传文件设置一个较低的内存限制 (默认是 32 MiB)
r.MaxMultipartMemory = 8 << 20 // 8 MiB
// 提供上传页面的路由
r.GET("/upload", func(c *gin.Context) {
c.HTML(http.StatusOK, "upload.html", nil)
})
// 处理文件上传的路由
r.POST("/upload", func(c *gin.Context) {
// 从表单中获取文件
file, err := c.FormFile("file")
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
return
}
// 将上传的文件保存到服务器上的指定位置
// 这里我们保存在项目根目录下的 "uploads/" 文件夹中
// 请确保你已经手动创建了 "uploads" 文件夹
dst := "./uploads/" + file.Filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.String(http.StatusInternalServerError, fmt.Sprintf("upload file err: %s", err.Error()))
return
}
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded successfully!", file.Filename))
})
r.Run()
}
在运行前,请务必在项目根目录手动创建一个名为 uploads
的文件夹。然后运行程序,访问 http://localhost:8888/upload
,你就可以选择并上传文件了。
6. 重定向 (Redirection)
HTTP 重定向也是一个常用功能。Gin 使用 c.Redirect()
方法来处理。
这个方法接受两个参数:
- HTTP 状态码:常用的有
http.StatusMovedPermanently
(301, 永久重定向) 和http.StatusFound
(302, 临时重定向)。 - 目标 URL:你想要重定向到的地址,可以是相对路径或绝对路径。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 示例1: 临时重定向
// 访问 /test-redirect 会被重定向到外部网站
r.GET("/test-redirect", func(c *gin.Context) {
c.Redirect(http.StatusFound, "https://www.google.com")
})
// 示例2: 永久重定向内部路由
// 访问 /old-path 会被永久重定向到 /new-path
r.GET("/old-path", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/new-path")
})
r.GET("/new-path", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Welcome to the new path!"})
})
r.Run()
}
当你访问 http://localhost:8888/test-redirect
时,你的浏览器地址栏会变成 https://www.google.com
。当你访问 http://localhost:8888/old-path
时,浏览器会跳转到 http://localhost:8888/new-path
并显示 JSON 消息。
总结
通过这篇终极指南,我们系统地学习了如何使用 Gin 框架构建一个功能完善的 Web 应用。我们从基础的 “Hello, World” 出发,深入探索了路由、数据绑定、路由组、自定义中间件、HTML 模板渲染、文件上传和重定向等一系列核心功能。
这已经为你打下了坚实的基础。接下来,你可以继续探索更高级的主题,例如:
- 与数据库集成:将你的 Gin 应用连接到 MySQL、PostgreSQL 或 GORM。
- WebSocket 支持:构建实时通信应用。
- 部署:将你的 Gin 应用打包成 Docker 镜像并部署到服务器。
希望这篇博客能为你打开 Go Web 开发的大门。Gin 是一个强大而有趣的工具,现在就开始用它来构建你的下一个项目吧!
有用的链接: