前言

学习来源极客兔兔Gin简明教程&ChatGPT

(老师的文章已经很全了)。本文是作为一个纯小白在学习兔老师教程的时候一些思考和资料查询。
需要先安装go和gin,这里建议直接看老师的教程,很细。

Gin是什么

Gin 是一个用 Go 语言编写的轻量级、高性能的 Web 框架,因其简洁的设计和极高的处理性能,在开发 RESTful API 和 Web 服务时非常流行

Gin 的特点

  • 轻量级
    • 代码量少,启动速度快,适合构建微服务。
    • 没有包含过多的插件,保持核心简单,用户可以按需扩展。
  • 高性能
    • Gin 使用 Go 的 net/http 标准库作为底层,性能表现极为优越。
    • 使用 Radix 树 和 字符串查找优化 实现路由匹配,高效解析 HTTP 请求。
  • 中间件机制
    • 提供了中间件支持,用户可以灵活定义和扩展功能,例如日志、认证、错误处理等。
  • 开发友好
    • 提供了简洁的 API 和丰富的功能,如路由分组、参数绑定、JSON 数据处理等。
    • 对于错误处理和调试有专门支持,如 Context 对象方便统一处理请求数据和响应。

快速上手一个Gin程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//writen by geektutu

package main

import "github.com/gin-gonic/gin"
//导入 gin 包,该包提供了构建 Web 应用所需的所有功能。

func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello, Geektutu")
})
r.Run() // listen and serve on 0.0.0.0:8080
}

创建Gin实例

1
r := gin.Default()

gin.Default():

  • 返回一个 Gin 引擎实例,用于管理路由和中间件。
  • 默认情况下,包含两个中间件:
    • Logger:记录请求日志。
    • Recovery:自动处理程序中的崩溃并返回 500 错误。

定义路由

1
2
3
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello, Geektutu")
})

分段讲解

  • r.GET("/")
  • func(c *gin.Context){}
    • 定义了一个匿名函数作为请求处理器
    • c是一个*gin.Context的对象,封装了 HTTP 请求和响应的相关信息。(后文会细讲,这里作用是返回我们想要在浏览器上显示的信息)
  • c.String(200, "Hello, Geektutu")
    • 使用c.String方法向客户端返回纯文本相应
    • 200是HTTP状态码,表示请求成功

启动web服务器

1
r.Run() // listen and serve on 0.0.0.0:8080
  • 用 r.Run()函数来让应用运行在本地服务器上,默认监听端口是 8080,可以传入参数设置端口,例如r.Run(“:9999”)即运行在 9999端口。

结果

运行

1
2
3
4
5
6
7
8
9
10
11
12
go run main.go
[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 / --> 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] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

浏览器访问 http://localhost:8080

alt text

路由

在 Gin 中,路由(Router)是处理 HTTP 请求并根据请求的不同路径和方法(如 GET、POST)来调用特定的处理函数(handler)的核心机制。Gin 路由通过 *gin.Engine 实例来管理和匹配这些请求。我们可以定义不同的路径和方法,并为每个请求指定对应的处理逻辑。

主要方法

  • GET:处理 GET 请求(从服务器获取数据)。
  • POST:处理 POST 请求(向服务器提交数据)。
  • PUT:处理 PUT 请求(更新服务器上的数据)。
  • DELETE:处理 DELETE 请求(删除服务器上的数据)。
  • PATCH:处理 PATCH 请求(局部更新资源)。

无参数

1
2
3
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Who are you?")
})

解析路径参数

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

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
r := gin.Default()
r.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
r.Run() // listen and serve on 0.0.0.0:8080
}

主要代码解释

  • r.GET("/user/:name", ...)
    • r.GET 定义了一个 GET 请求 的路由。
    • “/user/:name” 中的 :name 是一个 动态路径参数。这意味着,name 不是一个固定的值,而是从请求的 URL 中动态提取的部分。
    • 例如,URL http://localhost:8080/user/flypiggy 会使 name 变成 flypiggy。任何符合 /user/{name} 格式的请求都会匹配这个路由,并将 {name} 作为参数传递给处理函数。
  • name := c.Param("name")
    • c.Param(“name”) 从请求的 URL 中提取名为 name 的路径参数。
    • 假设你访问的 URL 是 http://localhost:9999/user/flypiggy ,c.Param(“name”) 会获取到 “flypiggy” 这个值,并将其赋值给变量 name。
  • http.Statusok
    • 就是HTTP状态码200,表示请求成功

获取Query参数

1
2
3
4
5
r.GET("/users", func(c *gin.Context) {
name := c.Query("name")
role := c.DefaultQuery("role", "teacher")
c.String(http.StatusOK, "%s is a %s", name, role)
})
  • name := c.Query("name")
    • c.Query(“name”) 是从 URL 查询字符串中提取名为 name 的参数。
    • 若没有name,则返回空字符串
    • 查询字符串
      • 是 URL 中用来传递参数的一部分,通常位于 URL 的路径后面,紧跟着 ? 符号。它由一系列键值对组成,多个键值对之间通过 & 符号分隔。
      • http://example.com/path?key1=value1&key2=value2,?后面就是查询字符串
  • role := c.DefaultQuery("role","teacher)
    • 从URL查询字符串中提取名为role的参数
    • 若没有role,则默认值为teacher

获取POST参数

1
2
3
4
5
6
7
8
r.POST("/form", func(c *gin.Context) {
username := c.PostForm("username")
password := c.DefaultPostForm("password", "000000")
c.JSON(http.StatusOK, gin.H{
"username": username,
"password": password,
})
})

详细解释

  • r.POST("/form", ...)
    • r.POST 定义了一个 POST 请求 的路由。
    • 路由的路径是 “/form”,这意味着它会匹配对 http://localhost:8080/form 的 POST 请求。
  • username := c.PostForm("username")
    • c.PostForm(“username”) 用来获取 POST 请求中的表单数据中名为 username 的参数。
    • 假设客户端发送的请求体中包含 username=geektutu,那么 c.PostForm(“username”) 的值将是 “geektutu”。
    • 如果请求中没有该参数,则返回空字符串。
  • password := c.DefaultPostForm("password", "000000")
    • c.DefaultPostForm(“password”, “000000”) 用来获取 POST 请求中的表单数据中名为 password 的参数。如果没有该参数,则使用默认值 “000000”。
  • c.JSON(http.StatusOK, gin.H{...})
    • c.JSON 用来返回一个 JSON 响应,http.StatusOK 表示 HTTP 状态码 200,表示请求成功。
    • gin.H 是 Gin 提供的一个便捷方法,用于创建一个键值对(map)。在这个例子中,返回的 JSON 包含 username 和 password 两个字段,分别对应从 POST 请求中获取的参数值。

注意事项

  • 浏览器地址栏无法直接构造 POST 请求。
  • 推荐使用命令行的 cURL 或工具(如 Postman)来发送复杂的 POST 请求。
  • 如果一定要用浏览器,最简单的方法是创造HTMl表单,这里不细说

Map参数(字典参数)

1
2
3
4
5
6
7
8
9
r.POST("/post", func(c *gin.Context) {
ids := c.QueryMap("ids")
names := c.PostFormMap("names")

c.JSON(http.StatusOK, gin.H{
"ids": ids,
"names": names,
})
})
  • ids := c.QueryMap("ids")
    • 这是从 查询字符串 中解析一个以 ids 为前缀的嵌套参数,返回值是一个 map[string]string
    • 查询字符串格式类似: ?ids[Jack]=001&ids[Tom]=002
      解析后,ids 将是:map[string]string{ "Jack": "001", "Tom": "002", }
  • names := c.PostFormMap("name")
    • 这是从 表单数据 中解析一个以 names 为前缀的嵌套参数,返回值是一个map[string]string
    • 表单数据格式类似:names[a]=Sam&names[b]=David,解析后,names 将是:map[string]string{ "a": "Sam", "b": "David", }

重定向(Redirect)

在 Gin 框架中,重定向 是指服务器收到一个请求后,告诉客户端(通常是浏览器)去访问另一个 URL。这样,客户端会再次发起请求到新的 URL。

重定向的类型

301永久重定向

1
2
3
r.GET("/old", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/new")
})
  • 客户端访问 /old 时,服务器告诉它目标地址是 /new。
  • 浏览器可能会缓存这个重定向,下次直接访问 /new。
  • 浏览器缓存重定向是指浏览器在收到一个重定向响应后,将这个重定向的信息保存在缓存中,并在之后访问相同的 URL 时自动按照重定向规则跳转到目标 URL,而无需再次向服务器发送请求。

302临时重定向

1
2
3
r.GET("/temp", func(c *gin.Context) {
c.Redirect(http.StatusFound, "/another")
})
  • 客户端访问 /temp 时,被引导到 /another。
  • 浏览器不会缓存重定向,下次依然会访问 /temp。

303重定向(常用于表单提交之后)

1
2
3
r.POST("/submit", func(c *gin.Context) {
c.Redirect(http.StatusSeeOther, "/result")
})
  • 适用于表单提交后引导用户到另一个页面。
  • 如果表单通过 POST 提交,重定向后浏览器会使用 GET 方法访问 /result。

示例

1
2
3
4
5
//内部重定向,请求转发
r.GET("/goindex", func(c *gin.Context) {
c.Request.URL.Path = "/"
r.HandleContext(c)
})
  • 解释:
    • 当用户访问 /goindex 路由时:
      • 将 c.Request.URL.Path 的值从 /goindex 改为 /。
      • 使用 r.HandleContext(c) 将修改后的请求重新交给 Gin 的路由引擎处理。
  • 内部重定向的作用:
    • 不返回 HTTP 重定向响应。这与 c.Redirect 不同,HandleContext 是将当前请求直接路由到另一个处理函数。
    • 在此例中,最终会路由到 / 的处理函数,并返回该处理函数的结果。

分组路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
defaultHandler := func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"path": c.FullPath(),
})
}
// group: v1
v1 := r.Group("/v1")
{
v1.GET("/posts", defaultHandler)
v1.GET("/series", defaultHandler)
}
// group: v2
v2 := r.Group("/v2")
{
v2.GET("/posts", defaultHandler)
v2.GET("/series", defaultHandler)
}

不需要每个路由都单独加上 /api/v1 前缀。你可以通过 路由分组 来实现对路由前缀的统一管理。路由分组会为组内的所有路由自动加上一个共同的前缀,这样你只需在创建路由组时定义一次前缀,然后在组内定义具体的路由,无需重复加前缀。

上传文件

单个文件

1
2
3
4
5
r.POST("/upload1", func(c *gin.Context) {
file, _ := c.FormFile("file")
// c.SaveUploadedFile(file, dst)
c.String(http.StatusOK, "%s uploaded!", file.Filename)
})
  • file, _ := c.FormFile("file")
    • c.FormFile(“file”) 用于从 HTTP 请求的 form-data 中提取文件。”file” 是表单字段的名称,它是客户端上传文件时指定的字段名。
    • 这行代码获取到的 file 是一个 *multipart.FileHeader 类型的对象,包含了上传文件的详细信息(如文件名、大小、类型等)。
    • file.Filename:上传文件的文件名。
    • file.Size:上传文件的大小。
    • file.Header:上传文件的额外信息,如文件的 MIME 类型。

注意:c.FormFile() 返回的 file 对象是文件的元数据,并不是文件本身的数据流。如果要保存文件,可以使用 c.SaveUploadedFile() 方法。

  • c.String(http.StatusOK, "%s uploaded!", file.Filename)

    • 这行代码通过 c.String 返回一个字符串响应,表示文件上传成功,并且展示上传文件的名称。
    • http.StatusOK 表示 HTTP 状态码 200,表示请求成功。
    • file.Filename 变量会替换字符串中的 %s,显示上传的文件名。

多个文件

1
2
3
4
5
6
7
8
9
10
11
r.POST("/upload2", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["upload[]"]

for _, file := range files {
log.Println(file.Filename)
// c.SaveUploadedFile(file, dst)
}
c.String(http.StatusOK, "%d files uploaded!", len(files))
})

渲染html模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type student struct {
Name string
Age int8
}

r.LoadHTMLGlob("templates/*")
//这行代码告诉 Gin 加载 templates/ 目录下的所有模板文件。LoadHTMLGlob 会加载符合指定模式的文件,这里是 templates/ 文件夹中的所有文件。

stu1 := &student{Name: "Geektutu", Age: 20}
stu2 := &student{Name: "Jack", Age: 22}
r.GET("/arr", func(c *gin.Context) {
c.HTML(http.StatusOK, "arr.tmpl", gin.H{
//"arr.tmpl" 是要渲染的模板文件名,假设该文件存在于 templates/ 文件夹中。
//gin.H{} 是 Gin 提供的一个便捷方法,表示一个 map[string]interface{},用于将数据传递到模板中。在这里传递了两个字段
"title": "Gin",
"stuArr": [2]*student{stu1, stu2},
})
})

中间件

在 Gin 中,中间件(Middleware)是用于在请求处理过程中的“请求-响应”链条中间进行拦截和处理的函数。它通常用于执行一些通用操作,例如日志记录、认证、授权、错误处理等。中间件可以在请求到达路由处理函数之前,或者在响应返回客户端之前,执行一些特定的操作。

中间件的定义

Gin 中的中间件是一个函数类型,签名为

1
func(c *gin.Context)

其中,*gin.Context 是 Gin 中的上下文对象,提供了访问请求和响应的功能。

如何使用中间件

在 Gin 中,可以使用 r.Use() 来为路由或路由组添加中间件。一个应用可以有多个中间件,Gin 会按添加顺序依次执行这些中间件。

示例

全局中间件

通过 r.Use() 可以添加全局中间件,意味着所有路由都会经过这些中间件。

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 (
"fmt"
"github.com/gin-gonic/gin"
)

func main() {
// 创建一个 Gin 路由引擎
r := gin.Default()

// 定义一个简单的中间件,记录请求的路径
r.Use(func(c *gin.Context) {
fmt.Println("Request path:", c.Request.URL.Path)
c.Next() // 调用下一个中间件
})

// 定义路由
r.GET("/hello", func(c *gin.Context) {
c.String(200, "Hello, World!")
})

// 启动服务
r.Run(":8080")
}

局部中间件

如果只想为某些路由添加中间件,可以在路由或路由组上使用 Use()。

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
package main

import (
"fmt"
"github.com/gin-gonic/gin"
)

func main() {
// 创建一个 Gin 路由引擎
r := gin.Default()

// 路由组,只有该组下的路由才会使用该中间件
api := r.Group("/api")
api.Use(func(c *gin.Context) {
fmt.Println("API request")
c.Next() // 调用下一个中间件或处理函数
})

// 定义路由
api.GET("/users", func(c *gin.Context) {
c.String(200, "User list")
})

// 启动服务
r.Run(":8080")
}