当前位置: 首页 > news >正文

Golang Gin框架基础与实践指南

Golang Gin框架

文章目录

  • Golang Gin框架
    • 一. Gin框架介绍
    • 二. Gin 环境搭建
    • 三. Gin框架中RESTfunAPI
      • RESTful API概述
      • RESTfulAPI使用
    • 四. Gin 框架格式渲染
      • HTML渲染
      • 静态文件处理
      • JSON渲染
      • XML渲染
      • 其他渲染
    • 五. Gin框架参数获取
      • 获取QueryString参数
      • 获取PostForm参数
      • 参数绑定
    • 六. Gin框架路由详解
      • GET POST 以及获取 Get Post 传值
      • 路由分组
      • 路由分离
    • 七. Gin框架中的自定义控制器
      • 控制器分组
      • 控制器的继承
    • 八. Gin框架中间件
      • 路由中间件
    • 九. 全局中间件
      • 路由单独注册
      • 中间件注意事项
      • Gin中间件中使用goroutine

在这里插入图片描述

一. Gin框架介绍

Gin 是一个用 Golang编写的 高性能的web 框架, 由于http路由的优化,速度提高了近 40 倍。 Gin的特点就是封装优雅、API友好。

Gin的一些特性:

  • 快速 基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。
  • 支持中间件 传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。
  • Crash 处理 Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!
  • JSON 验证 Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。
  • 路由组 更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。
  • 错误管理 Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。
  • 内置渲染 Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。
  • 可扩展性 新建一个中间件非常简单。

Go世界里最流行的Web框架,Github上有32K+star。 基于httprouter开发的Web框架。

  • Gin 的官网:https://gin-gonic.com/zh-cn/
  • Github 地址:https://github.com/gin-gonic/gin

二. Gin 环境搭建

要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区(demo)
下载并安装 gin

go get -u github.com/gin-gonic/gin

如果无法下载,访问不到网络,请求执行下面的命令

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

就可以下载没法下载的包了

环境测试:

创建go文件:

package mainimport ("github.com/gin-gonic/gin"
)func main() {//创建一个默认的路由引擎r := gin.Default()//配置路由r.GET("/", func(c *gin.Context) {c.JSON(200, gin.H{"username": "name1","data":     "data1",})})r.Run(":81") //开放端口
}

运行测试:

C:\Users\15509>curl -i 192.168.100.10:81
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 25 May 2025 07:35:48 GMT
Content-Length: 35{"data":"data1","username":"name1"}
C:\Users\15509>

环境搭建完成。

三. Gin框架中RESTfunAPI

RESTful API概述

RESTful API 是目前比较成熟的一套互联网应用程序的 API 设计理论,所以我们设计我们的路由的时候建议参考 RESTful API 指南。

REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源。

只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互。

例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:

请求方法URL含义
GET/book查询书籍信息
POST/create_book创建书籍记录
POST/update_book更新书籍信息
POST/delete_book删除书籍信息

同样的需求我们按照RESTful API设计如下:

请求方法URL含义
GET/book查询书籍信息
POST/book创建书籍记录
PUT/book更新书籍信息
DELETE/book删除书籍信息

Gin框架支持开发RESTful API的开发。

总结:

请求资源说明
GET (SELECT)从服务器取资源
POST(CREATE)新建资源
PUT (UPDATE)更新资源
DELETE删除资源

RESTfulAPI使用

使用代码格式:

package mainimport "github.com/gin-gonic/gin"func main() {//创建默认路由引擎r := gin.Default()//GET方法r.GET("/book", func(c *gin.Context) {c.JSON(200, gin.H{"message": "GET",})})//POST方法r.POST("/book", func(c *gin.Context) {c.JSON(200, gin.H{"message": "POST",})})//PUT方法r.PUT("/book", func(c *gin.Context) {c.JSON(200, gin.H{"message": "PUT",})})//DELETE方法r.DELETE("/book", func(c *gin.Context) {c.JSON(200, gin.H{"message": "DELETE",})})//端口设置r.Run(":81")
}

使用Postman可以进行测试:

测试后台显示:

[GIN-debug] Listening and serving HTTP on :81
[GIN] 2025/05/25 - 16:10:56 | 200 |       53.42µs |   192.168.100.1 | GET      "/book"
[GIN] 2025/05/25 - 16:10:59 | 200 |       16.18µs |   192.168.100.1 | POST     "/book"
[GIN] 2025/05/25 - 16:11:07 | 200 |      19.967µs |   192.168.100.1 | PUT      "/book"
[GIN] 2025/05/25 - 16:11:10 | 200 |      11.161µs |   192.168.100.1 | DELETE   "/book"

四. Gin 框架格式渲染

HTML渲染

首先定义一个存放模板文件的templates文件夹,然后在其内部按照业务分别定义一个posts文件夹和一个users文件夹。 posts/index.html文件的内容如下:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>posts/index</title>
</head>
<body>
{{.title}}
</body>
</html>

users/index.html文件的内容如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>users/index</title>
</head>
<body>{{.title}}
</body>
</html>

Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行HTML模板渲染

package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()// 自动加载所有子目录模板(根据自己定义的目录设置相应路径)r.LoadHTMLGlob("Gin/templates/**/*")r.GET("/posts/index", func(c *gin.Context) {c.HTML(http.StatusOK, "index.html", gin.H{"title": "posts index",})})r.GET("/users/index", func(c *gin.Context) {c.HTML(http.StatusOK, "index.html", gin.H{"title": "users index",})})r.Run(":81")
}

使用Postman进行测试,测试后端结果:

[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] Loaded HTML Templates (2): - index.html- [GIN-debug] GET    /posts/index              --> main.main.func1 (3 handlers)
[GIN-debug] GET    /users/index              --> main.main.func2 (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] Listening and serving HTTP on :81
[GIN] 2025/05/25 - 18:34:11 | 200 |     293.919µs |   192.168.100.1 | GET      "/posts/index"
[GIN] 2025/05/25 - 18:34:26 | 200 |     143.057µs |   192.168.100.1 | GET      "/users/index"

静态文件处理

当我们渲染的HTML文件中引用了静态文件时,我们只需要按照以下方式在渲染页面前调用gin.Static方法即可。

func main() {r := gin.Default()r.Static("/static", "./static")r.LoadHTMLGlob("templates/**/*")// ...r.Run(":8080")
}

JSON渲染

package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()//方法一:自己拼接JSON//gin.H 是是map[string]interface{}的缩写r.GET("/someJSON", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"Message": "helloworld",})})//方法二:使用结构体的形式r.GET("/moreJSON", func(c *gin.Context) {var msg = struct {Name    string `json:"user"`Message stringAge     int}{}msg.Name = "zhangsan"msg.Age = 19msg.Message = "helloworld"c.JSON(http.StatusOK, msg)})r.Run(":81")
}

进行测试:

C:\Users\15509>curl -i 192.168.100.10:81/someJSON
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 25 May 2025 10:55:57 GMT
Content-Length: 24{"Message":"helloworld"}
C:\Users\15509>curl -i 192.168.100.10:81/moreJSON
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 25 May 2025 10:55:59 GMT
Content-Length: 51{"user":"zhangsan","Message":"helloworld","Age":19}
C:\Users\15509>

后台结果显示:

GOROOT=/usr/local/go #gosetup
GOPATH=/root/go #gosetup
/usr/local/go/bin/go build -o /root/.cache/JetBrains/GoLand2025.1/tmp/GoLand/___go_build_students_Gin students/Gin #gosetup
/root/.cache/JetBrains/GoLand2025.1/tmp/GoLand/___go_build_students_Gin #gosetup
[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    /someJSON                 --> main.main.func1 (3 handlers)
[GIN-debug] GET    /moreJSON                 --> main.main.func2 (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] Listening and serving HTTP on :81
[GIN] 2025/05/25 - 18:55:57 | 200 |      49.973µs |   192.168.100.1 | GET      "/someJSON"
[GIN] 2025/05/25 - 18:55:59 | 200 |      94.536µs |   192.168.100.1 | GET      "/moreJSON"

XML渲染

注意需要使用具名的结构体类型。

package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()//方法一:自己拼接JSON//gin.H 是是map[string]interface{}的缩写r.GET("/someXML", func(c *gin.Context) {c.XML(http.StatusOK, gin.H{"Message": "helloworld",})})//方法二:使用结构体的形式r.GET("/moreXML", func(c *gin.Context) {type MessageRecord struct {Name    stringMessage stringAge     int}var msg MessageRecordmsg.Name = "zhangsan"msg.Message = "helloworld"msg.Age = 20c.XML(http.StatusOK, msg)})r.Run(":81")
}

进行测试:

C:\Users\15509>curl -i 192.168.100.10:81/someXML
HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8
Date: Sun, 25 May 2025 11:02:28 GMT
Content-Length: 40<map><Message>helloworld</Message></map>
C:\Users\15509>curl -i 192.168.100.10:81/moreXML
HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8
Date: Sun, 25 May 2025 11:02:31 GMT
Content-Length: 94<MessageRecord><Name>zhangsan</Name><Message>helloworld</Message><Age>20</Age></MessageRecord>
C:\Users\15509>

其他渲染

YMAL渲染

r.GET("/someYAML", func(c *gin.Context) {c.YAML(http.StatusOK, gin.H{"message": "ok", "status": http.StatusOK})
})

protobuf渲染

r.GET("/someProtoBuf", func(c *gin.Context) {reps := []int64{int64(1), int64(2)}label := "test"// protobuf 的具体定义写在 testdata/protoexample 文件中。data := &protoexample.Test{Label: &label,Reps:  reps,}// 请注意,数据在响应中变为二进制数据// 将输出被 protoexample.Test protobuf 序列化了的数据c.ProtoBuf(http.StatusOK, data)
})

五. Gin框架参数获取

在使用Gin处理HTTP请求时,经常需要从客户端接收各种类型的参数(如查询字符串、表单数据或JSON等)。

以下是继红常用方法:

获取QueryString参数

当你想从URL的查询部分(例如:http://192.168.100.10:81/user/search?username=lisi&address=beijing)获取参数时,可以使用c.Query()方法。

实现代码:

package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("/user/search", func(c *gin.Context) {username := c.Query("username")//可以添加默认值//username := c.DefaultQuery("username", "zhangsan")address := c.Query("address")c.JSON(http.StatusOK, gin.H{"message":  "ok","username": username,"address":  address,})})r.Run(":81")
}

输入对应的URL就能获取到对应的参数了。

//浏览器测试:
测试地址:http://192.168.100.10:81/user/search?username=zhangsan&adress=shenyang
测试输出:{"address":"","message":"ok","username":"zhangsan"}

获取PostForm参数

请求的数据通过form表单来提交,例如向/user/search发送一个POST请求,获取请求数据的方式如下:

package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.POST("/user/search", func(c *gin.Context) {// DefaultPostForm取不到值时会返回指定的默认值username := c.DefaultPostForm("username", "zhangsan")//username := c.Query("username")address := c.PostForm("address")c.JSON(http.StatusOK, gin.H{"message":  "ok","username": username,"address":  address,})})r.Run(":81")
}

进行POST测试:

C:\Users\15509>curl -X POST http://192.168.100.10:81/user/search
{"address":"","message":"ok","username":"zhangsan"}
C:\Users\15509>curl -X POST http://192.168.100.10:81/user/search -d "username=hongzhenyao&address=shenyang"
{"address":"shenyang","message":"ok","username":"hongzhenyao"}

参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryStringform表单JSONXML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSONform表单QueryString类型的数据,并把值绑定到指定的结构体对象。

package mainimport ("fmt""github.com/gin-gonic/gin""github.com/gin-gonic/gin/binding""net/http"
)type Login struct {User     string `form:"user" json:"user" binding:"required"`Password string `form:"password" json:"password" binding:"required"`
}func main() {route := gin.Default()//绑定JSON的示例,({"user": "q1mi", "password": "123456"})route.POST("/loginJSON", func(c *gin.Context) {var login Loginif err := c.ShouldBindJSON(&login); err == nil {fmt.Printf("login info :%#v\n", login)c.JSON(http.StatusOK, gin.H{"user":     login.User,"password": login.Password,})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})//绑定form表单(user=zhangsan&password=123456)route.POST("/loginForm", func(c *gin.Context) {var login Login//明确使用ShouldBindWith指定表单绑定if err := c.ShouldBindWith(&login, binding.Form); err == nil {c.JSON(http.StatusOK, gin.H{"user":     login.User,"password": login.Password,})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})// 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)route.GET("/loginQuery", func(c *gin.Context) {var login Loginif err := c.ShouldBindQuery(&login); err == nil {c.JSON(http.StatusOK, gin.H{"user":     login.User,"password": login.Password,})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})route.Run(":8081")
}

进行测试:测试JSON绑定 (/loginJSON)

测试1[root@master goproject]# curl -X POST http://localhost:8081/loginJSON \
>   -H "Content-Type: application/json" \
>   -d '{"user":"zhangsan","password":"123456"}'
{"password":"123456","user":"zhangsan"}
测试点:
如果缺少字段会返回验证错误,例如尝试去掉password字段:
[root@master goproject]# curl -X POST http://localhost:8081/loginJSON -H "Content-Type: application/json" -d '{"user":"zhangsan"}'
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}

测试表单绑定 (/loginForm)

[root@master goproject]# curl -X POST http://localhost:8081/loginForm \
>   -d "user=zhangsan&password=123456" \
>   -H "Content-Type: application/x-www-form-urlencoded"
{"password":"123456","user":"zhangsan"}特殊测试案例:
测试multipart表单上传(会自动处理):
[root@master goproject]# curl -X POST http://localhost:8081/loginForm \
>   -F "user=wangwu" \
>   -F "password=7890"
{"password":"7890","user":"wangwu"}

测试查询参数绑定 (/loginQuery):

[root@master goproject]# curl -X GET "http://localhost:8081/loginQuery?user=lisi&password=abcd"
{"password":"abcd","user":"lisi"}

六. Gin框架路由详解

路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等) 组成的,涉及到应用如何响应客户端对某个网站节点的访问。

GET POST 以及获取 Get Post 传值

GET请求传值

func main() {route := gin.Default()route.GET("/user", func(c *gin.Context) {uid := c.Query("uid")page := c.DefaultQuery("page", "0")c.String(200, uid, page)})route.Run(":8081")
}

测试:/user?uid=10086&page=20

[root@master goproject]# curl -i http://localhost:8081/user?uid=10086&page=20
[1] 41757
[root@master goproject]# HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Mon, 26 May 2025 01:26:24 GMT
Content-Length: 2310086%!(EXTRA string=0)

GET动态路由传值

func main() {route := gin.Default()route.GET("/user/:id", func(c *gin.Context) {id := c.Param("id")c.JSON(200, gin.H{"news": id,})})route.Run(":8081")
}

测试:/user/20

[root@master goproject]# curl -i http://localhost:8081/user/250
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Mon, 26 May 2025 01:34:53 GMT
Content-Length: 14{"news":"250"}

Post 请求传值 获取 form 表单数据

  • 通过 c.PostForm 接收表单传过来的数据
  • postman进行数据提交
package mainimport ("github.com/gin-gonic/gin"
)func main() {route := gin.Default()route.POST("/doAddUser", func(c *gin.Context) {username := c.PostForm("username")password := c.PostForm("password")age := c.DefaultPostForm("age", "20")c.JSON(200, gin.H{"username": username, "password": password, "Age": age})})route.Run(":8081")
}

进行测试:

[root@master goproject]# curl -X POST http://localhost:8081/doAddUser \
>   -d "username=admin" \
>   -d "password=123456" \
>   -H "Content-Type: application/x-www-form-urlencoded"
{"Age":"20","password":"123456","username":"admin"}
[root@master goproject]# 
[root@master goproject]# curl -X POST http://localhost:8081/doAddUser \
>   -d "username=test" \
>   -d "password=654321" \
>   -d "age=25" \
>   -H "Content-Type: application/x-www-form-urlencoded"
{"Age":"25","password":"654321","username":"test"}

Get 传值绑定到结构体

package mainimport ("github.com/gin-gonic/gin""net/http"
)// 定义一个结构体
type Userlogin struct {Username string `form:"username" json:"username"`Password string `form:"password" json:"password"`
}func main() {route := gin.Default()route.GET("/", func(c *gin.Context) {var userlogin Userlogin//请求类型判断阶段,Gin 会通过 Content-Type 自动选择绑定器//将请求参数绑定到 userlogin 结构体if err := c.ShouldBind(&userlogin); err == nil {c.JSON(http.StatusOK, userlogin)} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})route.Run(":8081")
}

测试:/?username=zhangsan&password=123456

测试地址:http://192.168.100.10:8081/?username=zhangsan&password=123456
测试结果:{"username":"zhangsan","password":"123456"}

POST传值到结构体

	route.POST("doLogin", func(c *gin.Context) {var userpostlogin Userloginif err := c.ShouldBind(&userpostlogin); err == nil {c.JSON(http.StatusOK, userpostlogin)} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})

测试:/doLogin

测试地址:http://192.168.100.10:8081/doLogin
测试参数:username:admin password:123456
测试结果:{"username":"admin","password":"123456"}

获取 Post Xml 数据

通过ctx.ShouldBindXML获取xml中的数据

package mainimport ("github.com/gin-gonic/gin""net/http"
)// 定义一个结构体
type Article struct {Title   string `json:"title" xml:"title"`Content string `json:"content" xml:"content"`
}func main() {route := gin.Default()route.POST("/xml", func(ctx *gin.Context) {var article Articleif err := ctx.ShouldBindXML(&article); err == nil {ctx.JSON(http.StatusOK, article)} else {ctx.JSON(http.StatusBadRequest, gin.H{"err": err.Error(),})}})route.Run(":8081")
}

测试结果:/xml

请求体:
<?xml version="1.0" encoding="UTF-8"?>
<article><content type="string">I am zhangsan</content><title type="string">zhangsan</title>
</article>请求结果:
{"title":"zhangsan","content":"I am zhangsan"}

路由分组

路由分组的概念

Gin的路由分组基于路径前缀继承机制,通过Group()方法创建的子路由组会自动继承父级路径前缀。当注册路由时,系统会将组前缀与具体路由路径进行字符串拼接,形成完整访问路径。例如group := router.Group("/api")创建基础路径,组内路由group.GET("/users")的实际路径为/api/users

分组的核心价值在于共享配置,同一组内的路由可共用中间件、路径前缀等设置。路径拼接时需注意:若组路径以/结尾或子路径以/开头,会自动合并重复斜杠(如/api//v1会规范为/api/v1),但若路径片段无斜杠连接(如group.GET("users")),会直接拼接成/apiusers,需显式用/分隔层级。

package mainimport ("github.com/gin-gonic/gin"
)func main() {route := gin.Default()userGroup := route.Group("user"){// 访问路径: /user/indexuserGroup.GET("/index", func(c *gin.Context) {})// 访问路径: /user/loginuserGroup.GET("/login", func(c *gin.Context) {})// 访问路径: /user/loginuserGroup.POST("/login", func(c *gin.Context) {})}shopGroup := route.Group("/shop"){// 访问路径: /shop/indexshopGroup.GET("/index", func(c *gin.Context) {})// 访问路径: /shop/cartshopGroup.GET("/cart", func(c *gin.Context) {})// 访问路径: /shop/helloshopGroup.POST("/hello", func(c *gin.Context) {})}route.Run(":8081")
}

路由分离

路由分离是指将不同业务模块的路由配置拆分成独立文件的管理方式

在项目新建文件夹router,然后在router目录下创建apiRouter.goadminRouter.go,内容如下:

package route//apiRoute.goimport "github.com/gin-gonic/gin"func ApiRoute(r *gin.Engine) {apiRoute := r.Group("/api"){apiRoute.GET("/", func(ctx *gin.Context) {ctx.String(200, "api")})apiRoute.GET("articles", func(ctx *gin.Context) {ctx.String(200, "/api/articles")})}
}
package route//adminRoute.goimport ("github.com/gin-gonic/gin""net/http"
)func AdminRoute(r *gin.Engine) {adminRoute := r.Group("/admin"){adminRoute.GET("/", func(ctx *gin.Context) {ctx.String(200, "admin")})adminRoute.GET("list", func(ctx *gin.Context) {ctx.String(http.StatusOK, "admin/list")})}
}

main.go中引入路由模块:

package mainimport ("github.com/gin-gonic/gin""students/Gin/route"
)func main() {r := gin.Default()route.ApiRoute(r)route.AdminRoute(r)r.Run(":8081")
}

进行测试:

C:\Users\15509>curl  192.168.100.10:8081/api/
api
C:\Users\15509>curl  192.168.100.10:8081/api/articles
/api/articlesC:\Users\15509>curl  192.168.100.10:8081/admin/
admin
C:\Users\15509>curl  192.168.100.10:8081/admin/list
admin/list

七. Gin框架中的自定义控制器

控制器分组

当我们的项目比较大的时候有必要对我们的控制器进行分组 , 业务逻辑放在控制器中.
在项目文件夹下面新建controller\api\文件夹,创建userController.go
userController.go 内容:

package apiimport "github.com/gin-gonic/gin"func UserIndex(c *gin.Context) {c.String(200, "api UserIndex")
}func UserAdd(c *gin.Context)  {c.String(200, "api UserAdd")
}func UserList(c *gin.Context) {c.String(200, "api UserList")
}func UserDelete(c *gin.Context) {c.String(200, "api UserDelete")
}
  • apiRouter.go中调用userController.go 的函数
import ("gin/controller/api""github.com/gin-gonic/gin"
)func ApiRouter(r *gin.Engine) {apiRouter := r.Group("/api"){apiRouter.GET("/", )apiRouter.GET("users", api.UserIndex)apiRouter.GET("users/:id", api.UserList)apiRouter.POST("users", api.UserAdd)apiRouter.DELETE("users/:id", api.UserDelete)}
}
  • 为了使方法能够继承,我们修改userController.go 内容
package apiimport "github.com/gin-gonic/gin"//结构体封装控制器
type UserController struct {}func (con UserController) Index(c *gin.Context) {c.String(200, "api Index")
}func (con UserController) Add(c *gin.Context)  {c.String(200, "api Add")
}func (con UserController) List(c *gin.Context) {c.String(200, "api List")
}func (con UserController) Update(c *gin.Context) {c.String(200, "api update")
}func (con UserController) Delete(c *gin.Context) {c.String(200, "api Delete")
}
  • 结构体作用:将同一业务模块的处理方法组织在一起,实现逻辑聚合
  • 方法接收器(con UserController) 表明这些方法属于该结构体,虽未使用实例变量,但为后续功能扩展预留空间

完善apiRouter.go

func ApiRouter(r *gin.Engine) {apiRouter := r.Group("/api"){apiRouter.GET("/")// api.UserController{} 实例化后才可以使用结构体的方法apiRouter.GET("users", api.UserController{}.List)apiRouter.GET("users/:id", api.UserController{}.List)apiRouter.POST("users", api.UserController{}.Add)apiRouter.DELETE("users/:id", api.UserController{}.Delete)}
}
  • 实例化过程UserController{} 创建结构体实例,通过实例调用方法
  • 方法绑定:Gin 路由直接接收结构体方法作为处理函数

控制器的继承

继承后就可以调用控制器里面的公共方法:

  • api目录下新建baseController.go,内容如下:
package apiimport "github.com/gin-gonic/gin"type BaseController struct{}func (con BaseController) success(c *gin.Context) {c.String(200, "success")
}func (con BaseController) error(c *gin.Context) {c.String(200, "failed")
}

修改userController.go:

package apiimport "github.com/gin-gonic/gin"// 继承BaseController
// 嵌入结构体实现组合
type UserController struct {BaseController
}func (con UserController) Index(c *gin.Context) {//c.String(200, "api UserIndex")con.success(c)// 调用基类方法
}func (con UserController) Add(c *gin.Context) {c.String(200, "api UserAdd")
}func (con UserController) List(c *gin.Context) {c.String(200, "api UserList")
}func (con UserController) Delete(c *gin.Context) {c.String(200, "api UserDelete")
}

通过这种控制器组织方式,可以构建出高可维护性的 Gin 项目结构。核心思想是通过 Go 语言的组合特性实现代码复用,同时利用路由组的层级关系管理不同业务模块的访问路径。

八. Gin框架中间件

Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、 记录日志、耗时统计等操作。通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作

路由中间件

Gin中的中间件必须是一个gin.HandlerFunc类型,配置路由的时候可以传递多个 func 回调函数。中间件要放在最后一个回调函数的前面 ,触发的方法都可以称为中间件。

//中间件函数
func InitMiddleWare(c *gin.Context) {fmt.Println("init middle ware")
}func ApiRoute(r *gin.Engine) {apiRoute := r.Group("/api"){// 中间件要放在最后一个回调函数的前面apiRoute.GET("/",InitMiddleWare,api.UserController{}.Index)}
}

ctx.Next()调用该请求的剩余处理程序

中间件里面加上 ctx.Next()后,c.Next()的语句后面先不执行,跳转到后面的中间件和回调函数中执行完后,才执行c.Next()后面的语句可以让我们在路由匹配完成后执行一些操作。比如我们统计一个请求的执行时间:

func InitMiddleWare(c *gin.Context) {fmt.Println("1 - init middle ware")start := time.Now().UnixNano()// 调用c.Next()请求的剩余处理程序// c.Next()的语句后面先不执行,先跳转路由匹配的最后一个回调函数执行后,// 才执行c.Next()后面的语句//说白了,就是等最后一个函数执行完在执行c.Next后面的c.Next()fmt.Println("3-程序执行完成.计算时间")end := time.Now().UnixNano()//  计算耗时,Go 语言中的 Since()函数保留时间值,并用于评估与实际时间的差异fmt.Println(end - start)
}func ApiRoute(r *gin.Engine) {apiRoute := r.Group("/api"){apiRoute.GET("/", InitMiddleWare, func(ctx *gin.Context) {fmt.Println("2 - 中间件")ctx.String(200, "/api")})}
}

测试结果:

1 - init middle ware
3-程序执行完成.计算时间
1603

个路由配置多个中间件的执行顺序,多个中间件执行顺序:

package mainimport ("fmt""github.com/gin-gonic/gin"
)func InitMiddleWareOne(c *gin.Context) {fmt.Println("one - init middleware start")c.Next()fmt.Println("one - init middleware end")
}func InitMiddleWareTwo(c *gin.Context) {fmt.Println("Two - init middleware start")c.Next()fmt.Println("Two - init middleware end")
}func ApiRoute(r *gin.Engine) {apiRoute := r.Group("/api"){//One先开始,然后Two开始//Two后面的函数执行完毕,执行Two//One后面的Two执行完毕,执行OneapiRoute.GET("/", InitMiddleWareOne, InitMiddleWareTwo, func(ctx *gin.Context) {fmt.Println("2 - 中间件")ctx.String(200, "/api")})}
}func main() {r := gin.Default()ApiRoute(r)r.Run(":8081")
}

测试结果:

[GIN-debug] redirecting request 301: /api/ --> /api/
one - init middleware start
Two - init middleware start
2 - 中间件
Two - init middleware end
one - init middleware end

Abort是终止的意思,ctx.Abort()表示终止调用该请求的剩余处理程序,Abort()后,中间件后面的回调函数(包括后面的中间件)不执行了,直接执行该中间件这里面的语句

九. 全局中间件

设置方法:

  • r.Use(中间件1,中间件2…)
func InitMiddleWareOne(c *gin.Context) {fmt.Println("one - init middleware start")c.Next()fmt.Println("one - init middleware end")
}func InitMiddleWareTwo(c *gin.Context) {fmt.Println("Two - init middleware start")c.Next()fmt.Println("Two - init middleware end")
}func main() {r := gin.Default()//全局中间件配置r.Use(InitMiddleWareOne)r.Use(InitMiddleWareTwo)ApiRoute(r)r.Run(":8081")
}

路由单独注册

// 给/test2路由单独注册中间件(可注册多个)r.GET("/test2", StatCost(), func(c *gin.Context) {name := c.MustGet("name").(string) // 从上下文取值log.Println(name)c.JSON(http.StatusOK, gin.H{"message": "Hello world!",})})

为路由组注册中间件

为路由组注册中间件有以下两种写法。

写法1:字段:StatCost()

shopGroup := r.Group("/shop", StatCost())
{shopGroup.GET("/index", func(c *gin.Context) {...})...
}

写法2:

shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{shopGroup.GET("/index", func(c *gin.Context) {...})...
}

中间件注意事项

gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

Gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)func LoginMiddleWare(c *gin.Context) {fmt.Println("login middle ware")c.Set("username", "张三")// 定义一个goroutine统计日志cCp := c.Copy()go func() {time.Sleep(2 * time.Second)fmt.Println("Done in path " + cCp.Request.URL.Path)}()
}func main() {r := gin.Default()r.Use(LoginMiddleWare)r.Run(":8081")
}

测试结果:

[GIN-debug] Listening and serving HTTP on :8081
login middle ware
[GIN] 2025/05/26 - 15:28:41 | 404 |      22.382µs |   192.168.100.1 | GET      "/"
Done in path /
http://www.xdnf.cn/news/657361.html

相关文章:

  • 【学习笔记】GitLab 下载安装与配置
  • 算力服务器的应用场景都有哪些
  • 学习python day8
  • 超临界机组协调控制系统建模项目开发笔记
  • git 删除某个远程库的分支
  • 【Redis】第1节|Redis服务搭建
  • 【freertos-kernel】queue(创建)
  • 企业网络综合实训
  • Zephyr OS: periodic_adv_rsp代码架构和实现
  • GPT-4o 风格提示词案例大全(持续更新 ing...)
  • 小白成长之路-计算机网络(二)
  • 前后端分离项目之新增编辑功能
  • 4800H 低负载黑屏或者蓝屏
  • JS逆向【抖查查】逆向分析 | sign | secret签名验证
  • 亚马逊竞争指数下降20%?这些类目正成新蓝海
  • linux centos 服务器性能排查 vmstat、top等常用指令
  • 算法-二进制运算
  • 将 Docker 镜像从服务器A迁移到服务器B的方法
  • DNS 详情 新增 DNS 自适应服务器 ip
  • AI时代新词-AI驱动的自动化(AI - Driven Automation)
  • 【Sqoop基础】Sqoop定位:关系型数据库与Hadoop生态间的高效数据桥梁
  • Coze教程:10分钟打造你的AI智能管家
  • 使用 `.inl` 文件和 `#pragma once` 解决模板函数头文件膨胀问题指南
  • linux 1.0.2
  • Web字体本地化的一种方案
  • 基于谷歌浏览器的Web Crypto API生成一对2048位的RSA密钥(公钥+私钥),并以JSON格式(JWK)打印到浏览器控制台
  • rocky linux-系统基本管理
  • uniapp 配置本地 https 开发环境(基于 Vue2 的 uniapp)
  • Maven-概述-介绍安装
  • 数字ic后端设计从入门到精通5(含fusion compiler, tcl教学)def详解