Gin 框架的基本使用
Gin框架介绍
Go世界里最流行的Web框架,Github上有32K+
star。 基于httprouter开发的Web框架。 中文文档齐全,简单易用的轻量级框架。
Gin框架安装与使用
安装
go get -u github.com/gin-gonic/gin
第一个Gin示例:
package main import ( "github.com/gin-gonic/gin" ) func main() { // 创建一个默认的路由引擎 r := gin.Default() // GET:请求方式;/hello:请求的路径 // 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数 r.GET("/hello", func(c *gin.Context) { // c.JSON:返回JSON格式的数据 c.JSON(200, gin.H{ "message": "Hello world!", }) }) // 启动HTTP服务,默认在0.0.0.0:8080启动服务 r.Run() }
将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello
就能看到一串JSON字符串。
RESTful API
请求方法 | URL | 含义 |
---|---|---|
GET | /book | 查询书籍信息 |
POST | /book | 创建书籍记录 |
PUT | /book | 更新书籍信息 |
DELETE | /book | 删除书籍信息 |
Gin框架支持开发RESTful API的开发。
func main() { r := gin.Default() r.GET("/book", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "GET", }) }) r.POST("/book", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "POST", }) }) r.PUT("/book", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "PUT", }) }) r.DELETE("/book", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "DELETE", }) }) }
Gin渲染
1 HTML渲染
我们首先定义一个存放模板文件的templates
文件夹,然后在其内部按照业务分别定义一个posts
文件夹和一个users
文件夹。 posts/index.html
文件的内容如下:
{{define "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> {{end}}
users/index.html
文件的内容如下:
{{define "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> {{end}}
Gin框架中使用LoadHTMLGlob()
或者LoadHTMLFiles()
方法进行HTML模板渲染。
func main() { r := gin.Default() r.LoadHTMLGlob("templates/**/*") //r.LoadHTMLFiles("templates/posts/index.html", "templates/users/index.html") r.GET("/posts/index", func(c *gin.Context) { c.HTML(http.StatusOK, "posts/index.html", gin.H{ "title": "posts/index", }) }) r.GET("users/index", func(c *gin.Context) { c.HTML(http.StatusOK, "users/index.html", gin.H{ "title": "users/index", }) }) r.Run(":8080") }
gin.H 是map[string]interface{}的缩写
func main() {
// 1. 创建一个默认的路由引擎
r := gin.Default()
r.GET("/someJson", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello world",
"status": 200,
})
})
// 启动 HTTP 服务, 默认在127.0.0.1:8080 启动服务
r.Run()
}
方法二 使用结构体
func main() {
// 1. 创建一个默认的路由引擎
r := gin.Default()
// 定义一个结构体
var msg struct {
Name string `json:"name"`
Message string `json:"message"`
Age int64 `json:"age"`
}
msg.Age = 18
msg.Message = "小王子"
msg.Name = "hello, world"
r.GET("/someJson", func(c *gin.Context) {
c.JSON(http.StatusOK, msg)
})
// 启动 HTTP 服务, 默认在127.0.0.1:8080 启动服务
r.Run()
}
XML渲染
func main() { r := gin.Default() // gin.H 是map[string]interface{}的缩写 r.GET("/someXML", func(c *gin.Context) { // 方式一:自己拼接JSON c.XML(http.StatusOK, gin.H{"message": "Hello world!"}) }) r.GET("/moreXML", func(c *gin.Context) { // 方法二:使用结构体 type MessageRecord struct { Name string Message string Age int } var msg MessageRecord msg.Name = "小王子" msg.Message = "Hello world!" msg.Age = 18 c.XML(http.StatusOK, msg) }) r.Run(":8080") }
YMAL渲染
r.GET("/someYAML", func(c *gin.Context) { c.YAML(http.StatusOK, gin.H{"message": "ok", "status": http.StatusOK}) })
获取参数
获取querystring参数 【get请求】
querystring
指的是URL中?
后面携带的参数,例如:/user/search?username=小王子&address=沙河
。 获取请求的querystring参数的方法如下:
func main() { //Default返回一个默认的路由引擎 r := gin.Default() r.GET("/user/search", func(c *gin.Context) { username := c.DefaultQuery("username", "小王子") //username := c.Query("username") address := c.Query("address") //输出json结果给调用方 c.JSON(http.StatusOK, gin.H{ "message": "ok", "username": username, "address": address, }) }) r.Run() }
获取form参数 【post 请求】
当前端请求的数据通过form表单提交时,例如向/user/search
发送一个POST请求,获取请求数据的方式如下:
func main() { //Default返回一个默认的路由引擎 r := gin.Default() r.POST("/user/search", func(c *gin.Context) { // DefaultPostForm取不到值时会返回指定的默认值 //username := c.DefaultPostForm("username", "小王子") username := c.PostForm("username") address := c.PostForm("address") //输出json结果给调用方 c.JSON(http.StatusOK, gin.H{ "message": "ok", "username": username, "address": address, }) }) r.Run(":8080") }
获取json参数
当前端请求的数据通过JSON提交时,例如向/json
发送一个POST请求,则获取请求参数的方式如下:
r.POST("/json", func(c *gin.Context) { // 注意:下面为了举例子方便,暂时忽略了错误处理 b, _ := c.GetRawData() // 从c.Request.Body读取请求数据 // 定义map或结构体 var m map[string]interface{} // 反序列化 _ = json.Unmarshal(b, &m) c.JSON(http.StatusOK, m) })
获取path参数
请求的参数通过URL路径传递,例如:/user/search/小王子/沙河
。 获取请求URL路径中的参数的方式如下。
func main() { //Default返回一个默认的路由引擎 r := gin.Default() r.GET("/user/search/:username/:address", func(c *gin.Context) { username := c.Param("username") address := c.Param("address") //输出json结果给调用方 c.JSON(http.StatusOK, gin.H{ "message": "ok", "username": username, "address": address, }) }) r.Run(":8080") }
参数绑定
ShouldBind
想要使用 bind 那么就必须设置 tag
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type
识别请求数据类型并利用反射机制自动提取请求中QueryString
、form表单
、JSON
、XML
等参数到结构体中。 下面的示例代码演示了.ShouldBind()
强大的功能,它能够基于请求自动提取JSON
、form表单
和QueryString
类型的数据,并把值绑定到指定的结构体对象。
// Binding from JSON type Login struct { User string `form:"user" json:"user" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } func main() { router := gin.Default() // 绑定JSON的示例 ({"user": "q1mi", "password": "123456"}) router.POST("/loginJSON", func(c *gin.Context) { var login Login if err := c.ShouldBind(&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=q1mi&password=123456) router.POST("/loginForm", func(c *gin.Context) { var login Login // ShouldBind()会根据请求的Content-Type自行选择绑定器 if err := c.ShouldBind(&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()}) } }) // 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456) router.GET("/loginForm", func(c *gin.Context) { var login Login // ShouldBind()会根据请求的Content-Type自行选择绑定器 if err := c.ShouldBind(&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()}) } }) // Listen and serve on 0.0.0.0:8080 router.Run(":8080") }
ShouldBind
会按照下面的顺序解析请求中的数据完成绑定:
- 如果是
GET
请求,只使用Form
绑定引擎(query
)。 - 如果是
POST
请求,首先检查content-type
是否为JSON
或XML
,然后再使用Form
(form-data
)。
ShouldBindJSON
传入一个json的body
package main import "github.com/gin-gonic/gin" type PostParams struct { Name string `json:"name"` Age int `json:"age"` Sex bool `json:"sex"` } func main() { r := gin.Default() r.POST("/testBind", func(c *gin.Context) { var p PostParams err := c.ShouldBindJSON(&p) //将传过来的json赋给p if err != nil { c.JSON(200, gin.H{ "msg": "bindjson报错了", "data": gin.H{}, }) } else { c.JSON(200, gin.H{ "msg": "请求成功", "data": p, //如果请求成功则把p的值打印出来 }) } }) r.Run(":8080") }
在postman发送一个请求,得到的结果如下
ShouldBindUri
shouldbinduri的请求参数应该写在uri里
package main import "github.com/gin-gonic/gin" type PostParams struct{ Name string `json:"name" uri:"name"` Age int `json:"age" uri:"age"` Sex bool `json:"sex" uri:"sex"` } func main(){ r := gin.Default() r.POST("/testBind/:name/:age/:sex", func(c *gin.Context){ var p PostParams err := c.ShouldBindUri(&p) if err !=nil{ c.JSON(200, gin.H{ "msg": "报错了", "data": gin.H{}, }) }else{ c.JSON(200, gin.H{ "msg": "binduri请求成功", "data": p, }) } }) r.Run(":8080") }
这里记得要在PostParams struct的定义里写上uri: “xxx”
uri中按顺序写入name,age和sex的参数
ShouldBindQuery
shouldbindquery的请求参数也是写在uri里,跟shouldbinduri稍微有些区别
package main import "github.com/gin-gonic/gin" type PostParams struct{ Name string `json:"name" uri:"name" form:"name"` Age int `json:"age" uri:"age" form:"age"` Sex bool `json:"sex" uri:"sex" form:"sex"` } //http://localhost:8080/testBind?age=18&name=kaka&sex=true func main(){ r := gin.Default() r.POST("/testBind", func(c *gin.Context){ var p PostParams err := c.ShouldBindQuery(&p) if err !=nil{ c.JSON(200, gin.H{ "msg": "报错了", "data": gin.H{}, }) }else{ c.JSON(200, gin.H{ "msg": "bindquery请求成功", "postdata": p, }) } }) r.Run(":8080") }
总结
bind就是先创立一个结构体,然后把扔过来的参数通过一种绑定的形式直接映射到某一个结构体的实例上去
bind模式如何使用(一般用shouldbind)
- bind模式一定要设置tag
- bind可以绑json、form、uri
shouldbind
表单验证
- 即binding:“required”
自定义验证
- 即上面的那个限制18岁的传参
shouldbind
ShouldBindJSON使用方法
- post一个json的
ShouldBindJQuery使用方法
- post http://127.0.0.1:8080/testBind?name=kaka&age=19&sex=false
ShouldBindUri使用方法
- post http://127.0.0.1:8080/testBind/jack/18/true
ShouldBind使用方法
- shouldbind会根据header里面的Content-Type来自动选择绑定器
文件上传
单个文件上传
文件上传前端页面代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <title>上传文件示例</title> </head> <body> <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="f1"> <input type="submit" value="上传"> </form> </body> </html>
后端gin框架部分代码:
func main() { router := gin.Default() // 处理multipart forms提交文件时默认的内存限制是32 MiB // 可以通过下面的方式修改 // router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // 单个文件 file, err := c.FormFile("f1") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "message": err.Error(), }) return } log.Println(file.Filename) dst := fmt.Sprintf("C:/tmp/%s", file.Filename) // 上传文件到指定的目录 c.SaveUploadedFile(file, dst) c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("'%s' uploaded!", file.Filename), }) }) router.Run() }
多个文件上传
func main() { router := gin.Default() // 处理multipart forms提交文件时默认的内存限制是32 MiB // 可以通过下面的方式修改 // router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // Multipart form form, _ := c.MultipartForm() files := form.File["file"] for index, file := range files { log.Println(file.Filename) dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index) // 上传文件到指定的目录 c.SaveUploadedFile(file, dst) } c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("%d files uploaded!", len(files)), }) }) router.Run() }
重定向
HTTP重定向
HTTP 重定向很容易。 内部、外部重定向均支持。
r.GET("/test", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/") })
路由重定向
路由重定向,使用HandleContext
:
r.GET("/test", func(c *gin.Context) { // 指定重定向的URL c.Request.URL.Path = "/test2" r.HandleContext(c) }) r.GET("/test2", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"hello": "world"}) })
Gin路由
普通路由
r.GET("/index", func(c *gin.Context) {...}) r.GET("/login", func(c *gin.Context) {...}) r.POST("/login", func(c *gin.Context) {...})
此外,还有一个可以匹配所有请求方法的Any
方法如下:
r.Any("/test", func(c *gin.Context) {...})
为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html
页面。
r.NoRoute(func(c *gin.Context) { c.HTML(http.StatusNotFound, "views/404.html", nil) })
路由组
我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}
包裹同组的路由,这只是为了看着清晰,你用不用{}
包裹功能上没什么区别。
func main() { r := gin.Default() userGroup := r.Group("/user") { userGroup.GET("/index", func(c *gin.Context) {...}) userGroup.GET("/login", func(c *gin.Context) {...}) userGroup.POST("/login", func(c *gin.Context) {...}) } shopGroup := r.Group("/shop") { shopGroup.GET("/index", func(c *gin.Context) {...}) shopGroup.GET("/cart", func(c *gin.Context) {...}) shopGroup.POST("/checkout", func(c *gin.Context) {...}) } r.Run() }
路由组也是支持嵌套的,例如:
shopGroup := r.Group("/shop") { shopGroup.GET("/index", func(c *gin.Context) {...}) shopGroup.GET("/cart", func(c *gin.Context) {...}) shopGroup.POST("/checkout", func(c *gin.Context) {...}) // 嵌套路由组 xx := shopGroup.Group("xx") xx.GET("/oo", func(c *gin.Context) {...}) }
Gin中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
定义中间件
Gin中的中间件必须是一个gin.HandlerFunc
类型。例如我们像下面的代码一样定义一个统计请求耗时的中间件。
// StatCost 是一个统计耗时请求耗时的中间件 func StatCost() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值 // 调用该请求的剩余处理程序 c.Next() // 不调用该请求的剩余处理程序 // c.Abort() // 计算耗时 cost := time.Since(start) log.Println(cost) } }
注册中间件
在gin框架中,我们可以为每个路由添加任意数量的中间件。
为全局路由注册
func main() { // 新建一个没有任何默认中间件的路由 r := gin.New() // 注册一个全局中间件 r.Use(StatCost()) r.GET("/test", func(c *gin.Context) { name := c.MustGet("name").(string) // 从上下文取值 log.Println(name) c.JSON(http.StatusOK, gin.H{ "message": "Hello world!", }) }) r.Run() }
为某个路由单独注册
// 给/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:
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()
默认使用了Logger
和Recovery
中间件,其中:
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会recover任何panic
。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何默认中间件的路由。
中间件注册的顺序 【中间件与 handler 的顺序】
r := gin.New() // 接收gin框架默认的日志 // recover掉项目可能出现的panic,并使用zap记录相关日志 r.Use(logger.GinLogger(), logger.GinRecovery(true)) // 注册业务路由 r.POST("/signup", controller.SignUpHandler) r.Use(middlewares.JWTAuthMiddleware()) // 登录 r.GET("/login", controller.LoginHandler)
路由 signup,有两个中间件 GinLogger 和 GinRecovery
路由 login,有三个中间件 GinLogger 和 GinRecovery 以及 JWTAuthMiddleware
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南