Golang-Gin详解 发表于 2024-03-02 更新于 2024-03-06
阅读量: 北京
一、快速入门 go版本需求:go1.13及以上
环境:windows 11
1 2 3 4 5 6 7 8 9 D:\go \project >mkdir ginlearn D :\go \project >cd ginlearn D :\go \project \ginlearn >go work init D :\go \project \ginlearn >mkdir helloworld D :\go \project \ginlearn >cd helloworld D :\go \project \ginlearn \helloworld >go mod init test.com /helloworld go : creating new go.mod : module test.com /helloworld D :\go \project \ginlearn \helloworld >cd ..D :\go \project \ginlearn >go work use ./helloworld
1 2 3 PS D:\go\project\ginlearn> cd .\helloworld\ # 下载并安装gin PS D:\go\project\ginlearn\helloworld> go get -u github.com/gin-gonic/gin
示例程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "github.com/gin-gonic/gin" func main () { r := gin.Default() r.GET("/ping" , func (c *gin.Context) { c.JSON(200 , gin.H{ "message" : "pong" , }) }) r.Run() }
运行后,postman进行测试
二、路由 路由是URI到函数的映射。
一个URI含: http://localhost:8080/user/find?id=11
协议,比如http,https等
ip端口或者域名,比如127.0.0.1:8080或者www.test.com
path,比如 /path
query,比如 ?query
同时访问的时候,还需要指明HTTP METHOD,比如
GET
GET方法请求一个指定资源的表示形式. 使用GET的请求应该只被用于获取数据.
POST
POST方法用于将实体提交到指定的资源,通常会导致在服务器上的状态变化
HEAD
HEAD方法请求一个与GET请求的响应相同的响应,但没有响应体.
PUT
PUT方法用请求有效载荷替换目标资源的所有当前表示
DELETE
DELETE方法删除指定的资源
CONNECT
CONNECT方法建立一个到由目标资源标识的服务器的隧道。
OPTIONS
OPTIONS方法用于描述目标资源的通信选项。
TRACE
TRACE方法沿着到目标资源的路径执行一个消息环回测试。
PATCH
PATCH方法用于对资源应用部分修改。
使用的时候,应该尽量遵循其语义
1. RESTful API规范 RESTful API 的规范建议我们使用特定的HTTP方法来对服务器上的资源进行操作。
比如:
GET,表示读取服务器上的资源
POST,表示在服务器上创建资源
PUT,表示更新或者替换服务器上的资源
DELETE,表示删除服务器上的资源
PATCH,表示更新/修改资源的一部分
2. 请求方法 比如
1 2 3 4 5 6 7 8 9 10 11 12 r.GET("/get" , func (ctx *gin.Context) { ctx.JSON(200 , "get" ) }) r.POST("/post" , func (ctx *gin.Context) { ctx.JSON(200 , "post" ) }) r.DELETE("/delete" , func (ctx *gin.Context) { ctx.JSON(200 , "delete" ) }) r.PUT("/put" , func (ctx *gin.Context) { ctx.JSON(200 , "put" ) })
如果想要支持所有:
1 2 3 r.Any("/any" , func (ctx *gin.Context) { ctx.JSON(200 , "any" ) })
如果想要支持其中的几种:
1 2 3 4 5 6 7 8 9 10 11 12 r.GET("/hello" , func (ctx *gin.Context) { ctx.JSON(200 , gin.H{ "name" : "hello world" , }) }) r.POST("/hello" , func (ctx *gin.Context) { ctx.JSON(200 , gin.H{ "name" : "hello world" , }) })
3. URI URI书写的时候,我们不需要关心scheme和authority这两部分,我们主要通过path和query两部分的书写来进行资源的定位。
4. 处理函数 定义:
1 type HandlerFunc func (*Context)
通过上下文的参数,获取http的请求参数,响应http请求等。
5. 分组路由 在进行开发的时候,我们往往要进行模块的划分,比如用户模块,以user开发,商品模块,以goods开头。
或者进行多版本开发,不同版本之间路径是一致的,这种时候,就可以用到分组路由了。
比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ug := r.Group("/user" ) { ug.GET("find" , func (ctx *gin.Context) { ctx.JSON(200 , "user find" ) }) ug.POST("save" , func (ctx *gin.Context) { ctx.JSON(200 , "user save" ) }) } gg := r.Group("/goods" ) { gg.GET("find" , func (ctx *gin.Context) { ctx.JSON(200 , "goods find" ) }) gg.POST("save" , func (ctx *gin.Context) { ctx.JSON(200 , "goods save" ) }) }
三、请求参数 1. Get请求参数 使用Get请求传参时,类似于这样 http://localhost:8080/user/save?id=11&name=zhangsan
。
如何获取呢?
1.1 普通参数 request url: http://localhost:8080/user/save?id=11&name=zhangsan
1 2 3 4 5 6 7 8 r.GET("/user/save" , func (ctx *gin.Context) { id := ctx.Query("id" ) name := ctx.Query("name" ) ctx.JSON(200 , gin.H{ "id" : id, "name" : name, }) })
如果参数不存在,就给一个默认值:
1 2 3 4 5 6 7 8 9 10 r.GET("/user/save" , func (ctx *gin.Context) { id := ctx.Query("id" ) name := ctx.Query("name" ) address := ctx.DefaultQuery("address" , "北京" ) ctx.JSON(200 , gin.H{ "id" : id, "name" : name, "address" : address, }) })
判断参数是否存在:
1 2 3 4 5 6 7 8 9 10 r.GET("/user/save" , func (ctx *gin.Context) { id, ok := ctx.GetQuery("id" ) address, aok := ctx.GetQuery("address" ) ctx.JSON(200 , gin.H{ "id" : id, "idok" : ok, "address" : address, "aok" : aok, }) })
id是数值类型,上述获取的都是string类型,根据类型获取:
1 2 3 4 5 6 7 8 9 10 11 12 type User struct { Id int64 `form:"id"` Name string `form:"name"` } r.GET("/user/save" , func (ctx *gin.Context) { var user User err := ctx.BindQuery(&user) if err != nil { log.Println(err) } ctx.JSON(200 , user) })
也可以:
1 2 3 4 5 6 7 8 r.GET("/user/save" , func (ctx *gin.Context) { var user User err := ctx.ShouldBindQuery(&user) if err != nil { log.Println(err) } ctx.JSON(200 , user) })
区别:
1 2 3 4 5 type User struct { Id int64 `form:"id"` Name string `form:"name"` Address string `form:"address" binding:"required"` }
当bind是必须的时候,ShouldBindQuery会报错,开发者自行处理,状态码不变。
BindQuery则报错的同时,会将状态码改为400。所以一般建议是使用Should开头的bind。
1.2 数组参数 请求url:http://localhost:8080/user/save?address=Beijing&address=shanghai
1 2 3 4 r.GET("/user/save" , func (ctx *gin.Context) { address := ctx.QueryArray("address" ) ctx.JSON(200 , address) })
1 2 3 4 5 r.GET("/user/save" , func (ctx *gin.Context) { address, ok := ctx.GetQueryArray("address" ) fmt.Println(ok) ctx.JSON(200 , address) })
1 2 3 4 5 6 r.GET("/user/save" , func (ctx *gin.Context) { var user User err := ctx.ShouldBindQuery(&user) fmt.Println(err) ctx.JSON(200 , user) })
1.3 map参数 请求url:http://localhost:8080/user/save?addressMap[home]=Beijing&addressMap[company]=shanghai
1 2 3 4 r.GET("/user/save" , func (ctx *gin.Context) { addressMap := ctx.QueryMap("addressMap" ) ctx.JSON(200 , addressMap) })
1 2 3 4 r.GET("/user/save" , func (ctx *gin.Context) { addressMap, _ := ctx.GetQueryMap("addressMap" ) ctx.JSON(200 , addressMap) })
map参数 bind并没有支持
2. Post请求参数 post请求一般是表单参数和json参数
2.1 表单参数
1 2 3 4 5 6 7 8 9 10 11 12 r.POST("/user/save" , func (ctx *gin.Context) { id := ctx.PostForm("id" ) name := ctx.PostForm("name" ) address := ctx.PostFormArray("address" ) addressMap := ctx.PostFormMap("addressMap" ) ctx.JSON(200 , gin.H{ "id" : id, "name" : name, "address" : address, "addressMap" : addressMap, }) })
1 2 3 4 5 6 7 8 r.POST("/user/save" , func (ctx *gin.Context) { var user User err := ctx.ShouldBind(&user) addressMap, _ := ctx.GetPostFormMap("addressMap" ) user.AddressMap = addressMap fmt.Println(err) ctx.JSON(200 , user) })
2.2 json参数 1 2 3 4 5 6 7 8 9 10 11 { "id" : 1111 , "name" : "zhangsan" , "address" : [ "beijing" , "shanghai" ] , "addressMap" : { "home" : "beijing" } }
1 2 3 4 5 6 r.POST("/user/save" , func (ctx *gin.Context) { var user User err := ctx.ShouldBindJSON(&user) fmt.Println(err) ctx.JSON(200 , user) })
其他类型参数注入xml,yaml等和json道理一样
3. 路径参数 请求url:http://localhost:8080/user/save/111
1 2 3 r.POST("/user/save/:id" , func (ctx *gin.Context) { ctx.JSON(200 , ctx.Param("id" )) })
4. 文件参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 r.POST("/user/save" , func (ctx *gin.Context) { form, err := ctx.MultipartForm() if err != nil { log.Println(err) } files := form.File for _, fileArray := range files { for _, v := range fileArray { ctx.SaveUploadedFile(v, "./" +v.Filename) } } ctx.JSON(200 , form.Value) })
四、响应 1. 字符串方式 1 2 3 r.GET("/user/save" , func (ctx *gin.Context) { ctx.String(http.StatusOK, "this is a %s" , "ms string response" ) })
2. JSON方式 1 2 3 4 5 r.GET("/user/save" , func (ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{ "success" : true , }) })
3. XML方式 1 2 3 4 5 6 7 8 9 10 11 type XmlUser struct { Id int64 `xml:"id"` Name string `xml:"name"` } r.GET("/user/save" , func (ctx *gin.Context) { u := XmlUser{ Id: 11 , Name: "zhangsan" , } ctx.XML(http.StatusOK, u) })
4. 文件格式 1 2 3 4 r.GET("/user/save" , func (ctx *gin.Context) { ctx.FileAttachment("./1.png" , "2.png" ) })
5. 设置http响应头 1 2 3 r.GET("/user/save" , func (ctx *gin.Context) { ctx.Header("test" , "headertest" ) })
6. 重定向 1 2 3 r.GET("/user/save" , func (ctx *gin.Context) { ctx.Redirect(http.StatusMovedPermanently, "http://www.baidu.com" ) })
7. YAML方式 1 2 3 r.GET("/user/save" , func (ctx *gin.Context) { ctx.YAML(200 , gin.H{"name" : "ms" , "age" : 19 }) })
五、模板渲染 模板是golang语言的一个标准库,使用场景很多,gin框架同样支持模板。由于现在开发大部分为亲啊后端分离开发,因此几乎不再使用模板。
1. 基本使用 定义一个存放模板文件的templates
文件夹
1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html > <head > <meta charset ="utf-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1" > <title > gin_templates</title > </head > <body > {{.title}} </body > </html >
后端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "github.com/gin-gonic/gin" "net/http" ) func main () { r := gin.Default() r.LoadHTMLFiles("templates/index.tmpl" ) r.GET("/index" , func (c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl" , gin.H{ "title" : "hello 模板" , }) }) r.Run(":9090" ) }
2. 多个模板渲染 如果有多个模板,可以统一进行渲染
1 2 r.LoadHTMLGlob("templates/**" )
如果目录为templates/post/index.tmpl
和templates/user/index.tmpl
这种,可以
1 2 router.LoadHTMLGlob("templates/**/*" )
3. 自定义模板函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 r.SetFuncMap(template.FuncMap{ "safe" : func (str string ) template.HTML { return template.HTML(str) }, }) r.LoadHTMLGlob("templates/**" ) r.GET("/index" , func (c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl" , gin.H{ "title" : "<a href='http://baidu.com'>跳转到其他地方</a>" , }) })
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <meta name="viewport" content="width=device-width, initial-scale=1" > <title>gin_templates</title> </head> <body> {{.title | safe}} </body> </html>
4. 静态文件处理 如果在模板中引入静态文件,比如样式文件
index.css
1 2 3 body { background-color : aqua; }
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <meta name="viewport" content="width=device-width, initial-scale=1" > <title>gin_templates</title> <link rel="stylesheet" href="/css/index.css" > </head> <body> {{.title}} </body> </html>
1 2 r.Static("/css" , "./static/css" )
六、会话 会话控制涉及到cookie和session的使用
1. cookie
HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出
Cookie就是解决HTTP协议无状态的方案之一
Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求
Cookie由服务器创建,并发送给浏览器,最终由浏览器保存
1.1 设置cookie 1 func (c *Context) SetCookie(name, value string , maxAge int , path, domain string , secure, httpOnly bool )
参数说明:
参数名
类型
说明
name
string
cookie名字
value
string
cookie值
maxAge
int
有效时间,单位是秒,MaxAge=0 忽略MaxAge属性,MaxAge<0 相当于删除cookie, 通常可以设置-1代表删除,MaxAge>0 多少秒后cookie失效
path
string
cookie路径
domain
string
cookie作用域
secure
bool
Secure=true,那么这个cookie只能用https协议发送给服务器
httpOnly
bool
设置HttpOnly=true的cookie不能被js获取到
1 2 3 4 r.GET("/cookie" , func (c *gin.Context) { c.SetCookie("site_cookie" , "cookievalue" , 3600 , "/" , "localhost" , false , true ) })
1.2 读取cookie 1 2 3 4 5 6 7 8 9 10 r.GET("/read" , func (c *gin.Context) { data, err := c.Cookie("site_cookie" ) if err != nil { c.String(200 ,data) return } c.String(200 ,"not found!" ) })
1.3 删除cookie 通过将cookie的MaxAge设置为-1, 达到删除cookie的目的。
1 2 3 4 5 r.GET("/del" , func (c *gin.Context) { c.SetCookie("site_cookie" , "cookievalue" , -1 , "/" , "localhost" , false , true ) c.String(200 ,"删除cookie" ) })
2. Session 在Gin框架中,我们可以依赖gin-contrib/sessions 中间件处理session。
安装session包
1 go get github.com/gin-contrib/sessions
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 27 28 29 30 31 32 33 package mainimport ( "fmt" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" ) func main () { r := gin.Default() store := cookie.NewStore([]byte ("secret" )) r.Use(sessions.Sessions("mysession" , store)) r.GET("/hello" , func (c *gin.Context) { session := sessions.Default(c) if session.Get("hello" ) != "world" { fmt.Println("没读到" ) session.Set("hello" , "world" ) session.Save() } c.JSON(200 , gin.H{"hello" : session.Get("hello" )}) }) r.Run(":8080" ) }
2.1 多session 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 27 28 29 30 31 32 33 34 35 36 package mainimport ( "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" ) func main () { r := gin.Default() store := cookie.NewStore([]byte ("secret" )) sessionNames := []string {"a" , "b" } r.Use(sessions.SessionsMany(sessionNames, store)) r.GET("/hello" , func (c *gin.Context) { sessionA := sessions.DefaultMany(c, "a" ) sessionB := sessions.DefaultMany(c, "b" ) if sessionA.Get("hello" ) != "world!" { sessionA.Set("hello" , "world!" ) sessionA.Save() } if sessionB.Get("hello" ) != "world?" { sessionB.Set("hello" , "world?" ) sessionB.Save() } c.JSON(200 , gin.H{ "a" : sessionA.Get("hello" ), "b" : sessionB.Get("hello" ), }) }) r.Run(":8080" ) }
2.2 基于redis存储引擎的session 如果我们想将session数据保存到redis中,只要将session的存储引擎改成redis即可。
使用redis作为存储引擎的例子:
首先安装redis存储引擎的包
1 go get github.com/gin-contrib/sessions/redis
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 27 28 29 30 31 32 33 34 35 36 package mainimport ( "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/redis" "github.com/gin-gonic/gin" ) func main () { r := gin.Default() store, _ := redis.NewStore(10 , "tcp" , "localhost:6379" , "" , []byte ("secret" )) r.Use(sessions.Sessions("mysession" , store)) r.GET("/incr" , func (c *gin.Context) { session := sessions.Default(c) var count int v := session.Get("count" ) if v == nil { count = 0 } else { count = v.(int ) count++ } session.Set("count" , count) session.Save() c.JSON(200 , gin.H{"count" : count}) }) r.Run(":8080" ) }
七、中间件 在Gin框架中,中间件 (Middleware)指的是可以拦截http请求-响应 生命周期的特殊函数,在请求-响应生命周期中可以注册多个中间件,每个中间件执行不同的功能,一个中间执行完再轮到下一个中间件执行。
中间件的常见应用场景如下:
请求限速
api接口签名处理
权限校验
统一错误处理
Gin支持设置全局中间件和针对路由分组设置中间件,设置全局中间件意思就是会拦截所有请求,针对分组路由设置中间件,意思就是仅对这个分组下的路由起作用。
1. 中间件使用 1 2 3 4 5 6 7 8 9 10 r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) r.GET("/test" , func (ctx *gin.Context) { panic (errors.New("test error" )) }) r.Run(":8080" )
2. 自定义中间件 使用Use可以使用gin自带的中间件或者其他第三方中间件,也可以自己开发中间件
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ("github.com/gin-gonic/gin" "log" "time" ) func Logger () gin.HandlerFunc { return func (c *gin.Context) { t := time.Now() c.Set("example" , "12345" ) c.Next() latency := time.Since(t) log.Print(latency) status := c.Writer.Status() log.Println(status) } } func main () { r := gin.New() r.Use(Logger()) r.GET("/test" , func (c *gin.Context) { example := c.MustGet("example" ).(string ) log.Println(example) }) r.Run(":8080" ) }