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

发布于:2025-07-09 ⋅ 阅读:(23) ⋅ 点赞:(0)

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的童鞋,参见以下代码:

# 创建一个项目文件夹
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() 方法来处理。

这个方法接受两个参数:

  1. HTTP 状态码:常用的有 http.StatusMovedPermanently (301, 永久重定向) 和 http.StatusFound (302, 临时重定向)。
  2. 目标 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 是一个强大而有趣的工具,现在就开始用它来构建你的下一个项目吧!

有用的链接:



网站公告

今日签到

点亮在社区的每一天
去签到