Gin框架
安装
-
下载安装gin
go get -u github.com/gin-gonic/gin
-
将gin库导入到代码中(如果需要使用
http.StatusOk
之类的常量还需要引入net/http
包)import "github.com/gin-gonic/gin"
-
运行测试
package main import "github.com/gin-gonic/gin" func main() { engine := gin.Default() engine.GET("/ping", func(context *gin.Context) { //返回JSON数据 context.JSON(200,gin.H{ "message":"pong", }) }) engine.Run() //默认运行在8080端口 }
RestFul风格API
非RestFul风格如果编写一个管理书籍的系统,
/book
查询书籍信息、/create_book
创建书籍、/update_book
更新书籍信息、/delete_book
删除书籍信息。RestFul风格都是
/book
使用HTTP协议中的4个请求方法代表不同的动作
- GET方法用来获取资源
- POST方法用来新建资源
- PUT方法用来更新下资源
- DELETE方法用来删除资源
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渲染
HTML渲染
-
首先创建一个存放模版文件的template文件夹,然后创建两个子文件夹为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") }
自定义模版函数
-
定义一个不转义相应内容的safe模版函数如下:
package main import ( "github.com/gin-gonic/gin" "html/template" "net/http" ) func main() { engine := gin.Default() engine.SetFuncMap(template.FuncMap{ "safe": func(str string) template.HTML{ return template.HTML(str) }, }) engine.LoadHTMLFiles("./index.tmpl") engine.GET("/index", func(context *gin.Context) { context.HTML(http.StatusOK,"index.tmpl","<a href='https://www.baidu.com'>百度</href>") }) engine.Run(":8080") }
-
创建一个index.tmpl文件使用定义的
safe
模板函数<!DOCTYPE html> <html lang="zh-CN"> <head> <title></title> </head> <body> <div>{{.|safe}}</div> </body> </html>
静态文件处理
-
当渲染HTML文件引用了静态文件时,我们只需要在渲染页面前调用
gin.Static
方法即可func main(){ r := gin.Default() r.Static("/static","./static") r.LoadHTMLGlob("templates/**/*") //... r.Run(":8080") }
JSON渲染
-
JSON渲染示例
package main import "github.com/gin-gonic/gin" func main() { engine := gin.Default() //方式一:自己拼接JSON engine.GET("/someJSON", func(context *gin.Context) { context.JSON(200,gin.H{"message":"Hello JSON"}) }) //方式二:使用结构体 engine.GET("/moreJSON", func(context *gin.Context) { var msg struct{ Name string `json:"user"` Message string Age int } msg.Name="大小" msg.Message="Hello JSON" msg.Age = 22 context.JSON(200,msg) }) engine.Run(":8080") }
XML渲染
-
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}) })
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) })
获取参数
-
获取querystring参数
querystring参数
指的事?
后面携带的参数,例如:/user/search?username=小小&address=北京
。 获取请求的querystring参数的方法如下package main import "github.com/gin-gonic/gin" func main() { engine := gin.Default() engine.GET("/user/search", func(context *gin.Context) { username := context.DefaultQuery("username", "大小") address := context.Query("address") context.JSON(200,gin.H{ "message":"ok", "username":username, "address":address, }) }) engine.Run(":8080") }
-
获取form参数
当前端请求的数据通过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请求,则获取请求参数的方式如下:package main import ( "encoding/json" "github.com/gin-gonic/gin" ) func main() { engine := gin.Default() engine.POST("/json", func(context *gin.Context) { data, _ := context.GetRawData() //定义一个map存放反序列化后的json数据 var m map[string]interface{} _ := json.Unmarshal(data, &m) context.JSON(200,m) }) engine.Run(":8080") }
-
获取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")
-
参数绑定
为了能够更方便获取请求相关参数,提高开发效率,可以基于请求的Content-Type识别请求数据类型并利用反射机制自动获取请求中的
QueryString
、form表单
、JSON
、XML
等参数到结构体中。下面示例代码演示了.ShouldBind()
强大的功能,它能够基于请求自动获取JSON
、form表单
和QueryString
类型的数据,并把值绑定到指定的结构体对象。package main import ( "fmt" "github.com/gin-gonic/gin" "net/http" ) type LoginUser struct { User string `form:"user" json:"user" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } func main() { engine := gin.Default() //绑定JSON engine.POST("/loginJSON", func(context *gin.Context) { var login LoginUser // ShouldBind()会根据请求的Content-Type自行选择绑定器 err := context.ShouldBind(&login) if err == nil { fmt.Printf("login info:%#v\n",login) context.JSON(http.StatusOK,gin.H{ "user":login.User, "password":login.Password, }) }else { context.JSON(http.StatusBadRequest,gin.H{"error":err.Error()}) } }) //绑定form表单 engine.POST("/loginForm", func(context *gin.Context) { var login LoginUser // ShouldBind()会根据请求的Content-Type自行选择绑定器 if err:=context.ShouldBind(&login);err==nil { context.JSON(http.StatusOK,gin.H{ "user":login.User, "password":login.Password, }) }else { context.JSON(http.StatusBadRequest,gin.H{ "error":err.Error()}) } }) //绑定QueryString示例 engine.GET("/login", func(context *gin.Context) { var login LoginUser // ShouldBind()会根据请求的Content-Type自行选择绑定器 err := context.ShouldBind(&login) if err == nil { context.JSON(http.StatusOK,gin.H{ "user":login.User, "password":login.Password, }) }else { context.JSON(http.StatusBadRequest,gin.H{"error":err.Error()}) } }) engine.Run(":8080") }
ShouldBind
会按照下面的顺序解析请求中的数据完成绑定:
- 如果是
GET
请求,只使用Form
绑定引擎(query
)。- 如果是
POST
请求,首先检查content-type
是否为JSON
或XML
,然后再使用Form
(form-data
)。
文件上传
单个文件上传
前端页面
<!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重定向
func main() { engine := gin.Default() engine.GET("/test", func(context *gin.Context) { context.Redirect(http.StatusMovedPermanently,"https://www.baidu.com") }) engine.Run() }
路由重定向
func main() { engine := gin.Default() engine.GET("/test", func(context *gin.Context) { //指定重定向的URL context.Request.URL.Path="/test2" engine.HandleContext(context) }) engine.GET("/test2", func(context *gin.Context) { context.JSON(http.StatusOK,gin.H{ "hello":"world"}) }) engine.Run() }
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) })
路由组
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框架中的路由使用的是httprouter这个库。其基本原理就是构造一个路由地址的前缀树。
Gin中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志等。
-
定义中间件
-
Gin的中间件必须是一个
gin.HandlerFunc
类型//统计耗时的中间件 func StatCost() gin.HandlerFunc { return func(context *gin.Context) { start := time.Now() context.Set("name","小小") context.Next() since := time.Since(start) log.Println(since) } }
-
-
跨域中间件cors
推荐使用社区的https://github.com/gin-contrib/cors 库,一行代码解决前后端分离架构下的跨域问题。
注意:这个中间件需要注册在业务处理函数前面
import ( "github.com/gin-gonic/gin" "github.com/gin-contrib/cors" "time" ) func main() { engine := gin.Default() engine.Use(cors.New(cors.Config{ AllowOrigins: []string{"https://test.com"}, // 允许跨域发来请求的网站 AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, // 允许的请求方法 AllowHeaders: []string{"Origin", "Authorization", "Content-Type"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, AllowOriginFunc: func(origin string) bool { // 自定义过滤源站的方法 return origin == "https://github.com" }, MaxAge: 12 * time.Hour, })) engine.Run() }
下面使用默认配置,允许所有的跨域请求
func main() { router := gin.Default() // same as // config := cors.DefaultConfig() // config.AllowAllOrigins = true // router.Use(cors.New(config)) router.Use(cors.Default()) router.Run() }
-
注册中间件
-
为全局路由注册
func main() { engine := gin.Default() //注册一个全局的中间件 engine.Use(StatCost()) engine.GET("/test", func(context *gin.Context) { name := context.MustGet("name").(string) log.Println(name) context.JSON(200,gin.H{ "message":"Hello world!", }) }) engine.Run() }
-
为单个路由注册
engine.GET("/test2",StatCost(), func(context *gin.Context) { name := context.MustGet("name").(string) log.Println(name) context.JSON(200,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.Default()
默认使用了Logger
和Recovery
中间件,其中:Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会recover任何panic
。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用
gin.New()
新建一个没有任何默认中间件的路由。 -
中间件使用goroutine
当在中间件或
handler
中启动新的goroutine
时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy()
)。
-
Gin运行多个服务
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
var (
g errgroup.Group
)
func router01() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 01",
},
)
})
return e
}
func router02() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 02",
},
)
})
return e
}
func main() {
server01 := &http.Server{
Addr: ":8080",
Handler: router01(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
server02 := &http.Server{
Addr: ":8081",
Handler: router02(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
// 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
g.Go(func() error {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!