go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南
go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南
往期回顾
- go go go 出发咯 - go web开发入门系列(一) helloworld
前言
前一节我们使用了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 mainimport "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 mainimport ("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.MethodreqUri := c.Request.RequestURI// 获取状态码statusCode := c.Writer.Status()// 获取客户端 IPclientIP := 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 mainimport ("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 mainimport ("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.Filenameif 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 mainimport ("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-pathr.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 是一个强大而有趣的工具,现在就开始用它来构建你的下一个项目吧!
有用的链接:
- Gin 官方文档
- Go 官方网站