go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南

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 设计非常直观,学习曲线平缓,文档齐全,对新手非常友好。

准备工作

在开始之前,请确保你已经完成了以下准备:

  1. 安装 Go 环境 :访问 Go 官方网站 下载并安装适合你操作系统的 Go 版本(建议 1.18 或更高版本,我使用的是1.24.4)。
  2. 配置你的工作区 :设置好你的 GOPATHGOROOT 环境变量。
  3. 一个代码编辑器:推荐使用 VS Code 并安装 Go 扩展,或者使用 GoLand。

第一个 Gin 应用:"Hello, Gin!"

第一步:创建项目

首先,创建一个新的项目目录,并使用 Go Modules 初始化项目。

如果是使用vscode的童鞋,参见以下代码:

bash 复制代码
# 创建一个项目文件夹
mkdir gin-hello-world
cd gin-hello-world

# 初始化 Go 模块
go mod init gin-hello-world
`go mod init` 命令会创建一个 `go.mod` 文件,用于跟踪和管理项目的依赖。

第二步:下载Gin 依赖

在当前项目目录下,键入如下命令,安装Gin依赖

bash 复制代码
 go get -u github.com/gin-gonic/gin

如果你使用的是 Goland 存在拉取 Gin依赖失败的情况,请配置GoProxy

bash 复制代码
GOPROXY=https://goproxy.cn,direct.

第二步:编写main.go

main.go

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或者使用请求工具

bash 复制代码
curl http://localhost:8888/hello

可以收到一个json的返回

json 复制代码
{"message":"Hello, Gin!"}

Gin 核心概念深入

掌握了基础之后,让我们来探索 Gin 的一些核心功能。

1. 路由(Routing)

Gin 提供了非常灵活的路由功能。

路由参数

你可以定义包含动态参数的路由。

go 复制代码
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)。

go 复制代码
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 结构体:

go 复制代码
// 定义一个 Article 结构体
type Article struct {
    Title   string `json:"title" binding:"required"`
    Content string `json:"content" binding:"required"`
}

binding:"required" 标签表示这个字段是必需的。

然后,创建一个处理 POST 请求的路由:

go 复制代码
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 端点:

bash 复制代码
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") 设置公共请求路径。

go 复制代码
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中被叫做中间件。

让我们来创建一个自定义的日志中间件。

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 模板的方式,在模板之间进行传递参数进行渲染

html 复制代码
<!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 来加载并渲染这个模板。

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:

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>

第二步:编写后端处理逻辑

go 复制代码
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() 方法来处理。

这个方法接受两个参数:

  1. HTTP 状态码 :常用的有 http.StatusMovedPermanently (301, 永久重定向) 和 http.StatusFound (302, 临时重定向)。
  2. 目标 URL:你想要重定向到的地址,可以是相对路径或绝对路径。
go 复制代码
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 是一个强大而有趣的工具,现在就开始用它来构建你的下一个项目吧!

有用的链接:


相关推荐
却尘1 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare2 分钟前
浅浅看一下设计模式
前端
Lee川5 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix32 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人35 分钟前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl39 分钟前
OpenClaw 深度技术解析
前端
崔庆才丨静觅42 分钟前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空1 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust