go gin框架使用总结

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

路由

gin使用RESTful API,所以在设计时gin的路由请求方式通过HTTP动词进行分发。如:

func main() {
  // 禁用控制台颜色
  // gin.DisableConsoleColor()

  // 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由
  router := gin.Default()

  router.GET("/someGet", getting)
  router.POST("/somePost", posting)
  router.PUT("/somePut", putting)
  router.DELETE("/someDelete", deleting)
  router.PATCH("/somePatch", patching)
  router.HEAD("/someHead", head)
  router.OPTIONS("/someOptions", options)
  
  // 匹配所有的请求方式
  router.Any("/test", any)
  // 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。
  router.Run()
  // router.Run(":3000") hardcode 端口号
}

需要注意的是,这些GET、POST等函数的第二个参数是回调函数,这个回调函数有一个参数的类型为*gin.Context,如以下这个简单的例子:

package main

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

func main() {
  router := gin.Default()

  router.GET("/book", bookGet)
  //router.Any("/book", bookGet)

  err := router.Run(":9000")
  if err != nil {
    log.Fatalln("start run error", err)
  }

}

func bookGet(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
    "status": 200,
    "msg":    "",
    "data": []string{
      "xxx", "abb",
    },
  })
}

回调函数也可以写成匿名函数

在测试出其他请求方式时,可以使用Postman工具验证。

路由组

一个路由组就是拥有相同的url前缀而划分的组,定义好路由组后,我们在为请求方式绑定url时,只需要写后面的路径即可。
要实现这个功能,使用的是gin.Rroup这个方法。注意要接收返回值,后面要用到。比如以下这个例子:

package main

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

func main() {
  router := gin.Default()
  bookRouter := router.Group("/book")
  {
    bookRouter.GET("/index", bookGet)
  }
  
  musicRouter := router.Group("/music")
  {
    musicRouter.GET("/index", musicGet)
  }

  err := router.Run(":9000")
  if err != nil {
    log.Fatalln("start run error", err)
  }

}

func bookGet(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
    "status": 200,
    "msg":    "",
    "data": []string{
      "MySQL", "斗气化马",
    },
  })
}
func musicGet(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
    "status": 200,
    "msg":    "",
    "data": []string{
      "aaa", "bbb",
    },
  })
}

Group下面的{}只是为了更加明显划分group,可加可不加。

返回

字符串

假如想要返回的结果是一个字符串,就是使用c.String方法

package  main

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

func main() {
  r := gin.Default()
  r.GET("/", index)
  _ = r.Run(":9000")
}

func index(c *gin.Context)  {
  c.String(http.StatusOK, "ok")

}

JSON

返回一个json数据,在上面的例子中也有用到,调用的是gin.ContextJSON方法:func (c *Context) JSON(code int, obj interface{})。其第一个参数是状态码,可以使用http中的状态码;第二个参数是要转成json格式的数据。
举个例子:

package main

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

func main() {
  router := gin.Default()

  router.GET("/index", index)
  router.POST("/login", login)
  err := router.Run(":9000")
  if err != nil {
    log.Fatalln("start run error", err)
  }

}

func index(c *gin.Context) {
  // 方式一, 自定义数据,使用gin.H包裹
  c.JSON(http.StatusOK, gin.H{
    "status": 200,
    "msg":    "ok",
  })
}
func login(c *gin.Context) {
  // 方式二,使用结构体,注意首字母大写,可以添加tag
  res := struct {
    Status int      `json:"status"`
    Msg    string   `json:"msg"`
    Data   []string `json:"data"`
  }{Status: http.StatusOK, Msg: "success", Data: []string{"aaa", "bbb"}}

  c.JSON(http.StatusOK, res)
}

HTML

返回一个HTML文件需要做一下步骤:

  1. 创建一个HTML文件,最后把文件根据业务逻辑放在templates中的不同文件夹下
  2. 使用gin.LoadHTMLGlobgin.LoadHTMLFiles引入刚才的文件
  3. 最后c.HTML返回数据

用到方法的签名:

func (engine *Engine) LoadHTMLFiles(files ...string)
func (engine *Engine) LoadHTMLGlob(pattern string)
func (c *Context) HTML(code int, name string, obj interface{})

1
比如在templates下创建文件:

../templates/
├── music
│   └── index.tmpl
└── videos
    └── index.tmpl

分别写入利用html/template中的语法进行定义:

  • music/index.tmpl:

    {{define  "music/index.tmpl"}}
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>{{ .title }}</title>
      </head>
      <body>
        <h1>music</h1>
      </body>
      </html>
    {{end}}
    
  • videos/index.tmpl:

    {{define  "videos/index.tmpl"}}
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>videos</title>
      </head>
      <body>
        <h1>video</h1>
      </body>
      </html>
    {{end}}
    

2
文件较多的情况下,建议使用LoadHTMLGlob

func main() {
  r := gin.Default()
  r.LoadHTMLGlob("templates/**/*")
  //r.LoadHTMLFiles("templates/music/index.tmpl", "templates/videos/index.tmpl")
  r.GET("/music/index", musicIndex)

  r.GET("/videos/index", videoIndex)

  _ = r.Run(":9000")
}

3
c.HTML的第二个参数传define后面的字符串,否则找不到文件,第三个参数是模板中的.,里面的键可以小写,因为gin.Hmap[string]interface{}

func musicIndex (c *gin.Context) {
    c.HTML(http.StatusOK, "music/index.tmpl", gin.H{
      "title": "music/index",

    })
}
func videoIndex(c *gin.Context) {
    c.HTML(http.StatusOK, "videos/index.tmpl", gin.H{})
  }

添加模板函数

调用SetFuncMap即可,使用方式和html/template很像:

router := gin.Default()
router.SetFuncMap(template.FuncMap{
    "safe": func(str string) template.HTML{
      return template.HTML(str)
    },
  })

静态文件

只需要为当前路由绑定一个静态文件目录即可


r.Static("/static", "./static")

第一个参数对应url的路径,第二个路径对应文件夹的路径

其他渲染(XML/YAML/ProtoBuf)查看官方文档

重定向

HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。比如下面这个例子:

package  main

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

func main() {
  router := gin.Default()
  router.GET("/user", user)
  router.GET("/login", login)
  _ = router.Run(":9000")
}

func user(c *gin.Context)  {
  c.Redirect(http.StatusMovedPermanently, "/login")

}

func login(c *gin.Context)  {
  c.String(http.StatusOK, "ok!")

}

路由重定向

路由重定向,通过c.Request.URL.Path设置跳转的指定的路径。再使用HandleContext完成重定向:


package main

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

// 使用匿名函数就没必要定义全局的router
var router *gin.Engine

func main() {
  router = gin.Default()
  router.GET("/user", user)
  router.GET("/login", login)
  _ = router.Run(":9000")
}

func user(c *gin.Context) {
  c.Request.URL.Path = "/login"
  router.HandleContext(c)
}

func login(c *gin.Context) {
  c.String(http.StatusOK, "ok!")

}

获取客户端的数据

get参数

要获取url的get参数一般有两个方法:c.Queryc.DefaultQuery两个方法,既然有两个方法,那么这两个方法有什么不同呢?前者在url中没有参数时返回的时一个空字符串,而后者返回的是你自定义的一个默认值。
如这样一个例子:

package main

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

func main() {
  r := gin.Default()
  r.GET("/music", func(c *gin.Context) {
    name := c.Query("name")
    id := c.Query("id")
    age := c.DefaultQuery("age", "18")
    addr := c.DefaultQuery("addr", "beijing")
    
    fmt.Printf("name = %v\n", name)
    fmt.Printf("id = %v\n", id)
    fmt.Printf("age = %v\n", age)
    fmt.Printf("addr = %v\n", addr)
    
    c.JSON(http.StatusOK, gin.H{"status": 200, "msg": "succeed"})
  })
  _ = r.Run(":9000")
}

访问http://127.0.0.1:9000/music?name=123&age=12,结果:

name = 123
id = 
age = 12
addr = beijing

post参数

以上在获取url参数的是GET请求,假如要获取form表单或ajax的POST请求时需要用到c.PostFormc.DefaultPostForm,这两个的区别就和c.Queryc.DefaultQuery的区别一样。
如下面这个例子:

package main

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

func main() {
  r := gin.Default()
  r.POST("/music", func(c *gin.Context) {
    name := c.PostForm("name")
    id := c.PostForm("id")
    age := c.DefaultPostForm("age", "18")
    addr := c.DefaultPostForm("addr", "beijing")
    c.JSON(http.StatusOK, gin.H{
      "status": 200, "msg": "succeed",
      "data": map[string]string{
        "name": name,
        "id":   id,
        "age":  age,
        "addr": addr}})
  })
  _ = r.Run(":9000")
}

使用Postman发送POST请求,带上参数:
postman提交数据

动态url

在一些场景中,我们的url有可能是动态的,所以url的一部分也应该可以获取,gin提供了:*来解决问题。

  • : 假如绑定url使用:xxx,表示xxx是一个键,我们可以在代码中,用c.Param("xxx")来接收url的值,如:

    
    package main
    
    import (
      "fmt"
      "github.com/gin-gonic/gin"
      "net/http"
    )
    
    func main() {
      r := gin.Default()
      r.GET("/user/:name", user)
    
      _ = r.Run(":9000")
    
    }
    func user(c *gin.Context) {
      name := c.Param("name")
      fmt.Println("name", name)
      c.String(http.StatusOK, "success")
    }
    

    以上这个例子可以处理形如这样的url:
    /user/xxx/user/xxx/
    但是不能处理/user//user/user/xxx/xxx

  • * 使用*绑定url就可以解决上面这个问题,如*xxx,其表示/*后边的一切url都归属xxx(包含/)
    如这样绑定:

    r.GET("/user/*name", user)
    

    那么其可以匹配:/user/name/user/name/xxx等以/user/开头的url
    注意:
    在没有绑定/user时,访问/user会跳转成/user/

参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中get参数、post参数、form表单、url、JSON、XML等参数到结构体中。
定义结构体的tag的意思:

  • form: get参数的key或post参数的key(form表单的name)
  • json:json数据的key
  • uri:uri参数
  • binding:"required":表示当前字段是必须的,数据中没有该tag对应的值时,就会panic

实现这个功能需要用到ShouldBind方法 **(绑定uri时使用的是c.ShouldBindUri)**,
c.ShouldBind

func (c *Context) ShouldBind(obj interface{}) error {
  b := binding.Default(c.Request.Method, c.ContentType())
  return c.ShouldBindWith(obj, b)
}

获取上传的文件

gin中上传文件有上传一个文件和上传多个文件之分
上传一个文件时使用:

func (c *Context) FormFile(name string) (*multipart.FileHeader, error)

它会返回name对应值的第一个文件和error类型
上传多个文件时使用:

  1. 一个文件

    
    package main
    
    import (
      "fmt"
      "github.com/gin-gonic/gin"
    )
    
    func main()  {
      r := gin.Default()
      r.MaxMultipartMemory = 10 << 20  // 允许最大上传10MB
      r.POST("/user", userPost)
      _ = r.Run(":9000")
    
    }
    
    func userPost(c *gin.Context)  {
      file, _ := c.FormFile("file")
      fmt.Println(file.Filename)
      c.String(200, file.Filename)
    }
    
  2. 多个文件
    上传多个文件需要用到c.MultipartForm: func (c *Context) MultipartForm() (*multipart.Form, error)
    得到的*multipart.Form是一个字典,拿到后可以遍历,最终拿到文件。

    package main
    
    import (
      "fmt"
      "github.com/gin-gonic/gin"
      "net/http"
    )
    
    func main() {
      r := gin.Default()
      r.MaxMultipartMemory = 10 << 20 // 允许最大上传 10MB
      r.POST("/user", userPost)
      _ = r.Run(":9000")
    
    }
    
    func userPost(c *gin.Context) {
      form, _ := c.MultipartForm()
      files := form.File["file"]
      for index, file := range files {
        fmt.Printf("index:%d  file name: %s\n", index, file.Filename)
      }
      c.String(http.StatusOK, fmt.Sprintf("uploaded %d files", len(files)))
    }
    

文件保存

把文件保存到磁盘的方法是SaveUploadedFile
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error

  • file:单个文件
  • dst:路径

如:

package main

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

func main() {
  r := gin.Default()
  r.MaxMultipartMemory = 10 << 20 // 10MB
  r.POST("/user", userPost)
  _ = r.Run(":9000")

}


/*
// 单个文件
func userPost(c *gin.Context)  {
  file, _ := c.FormFile("file")
  err := c.SaveUploadedFile(file, fmt.Sprintf("./temp/%s", file.Filename))
  if err != nil {
    c.String(500, "error: save file failed",)
  }
  fmt.Println(file.Filename)
  c.String(200, file.Filename)
}
*/


// 多个文件
func userPost(c *gin.Context) {
  form, _ := c.MultipartForm()
  files := form.File["file"]
  for index, file := range files {
    fmt.Printf("index:%d  file name: %s\n", index, file.Filename)
    // 注意./temp/需要存在
    err := c.SaveUploadedFile(file, fmt.Sprintf("./temp/%s", file.Filename))
    if err != nil{
      fmt.Println("error: save file failed", err)
    }
  }
  c.String(http.StatusOK, fmt.Sprintf("uploaded %d file", len(files)))
}

cookies

获取cookie和设置cookie分别使用c.Cookiec.SetCookie,一下为这两个方法的签名:

// 找到就返回对应的值和nil,没有找到返回""和error
func (c *Context) Cookie(name string) (string, error)


// 设置cookie
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
// 参数
//    name, value: 对应的键和值
//    maxAge(源码的内容):
//      MaxAge=0 means no 'Max-Age' attribute specified.
//       MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
//       MaxAge>0 means Max-Age attribute present and given in seconds

//  其他参数的作用见:https://tools.ietf.org/html/rfc6265#page-8

例子:


package main

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

func main() {
  r := gin.Default()
  r.GET("/index", index)
  _ = r.Run(":9000")

}

func index(c *gin.Context) {
  // 获取cookie
  name, err := c.Cookie("name")
  fmt.Println("name", name)
  if err != nil {
    // 设置cookie
    c.SetCookie("name", "lczmx", 0, "/", "127.0.0.1", false, true)
    c.String(http.StatusOK, "set cookie")
  }
  c.String(http.StatusOK, fmt.Sprintf("get cookie %s", name))
}

作者: 忞翛

出处: https://www.lczmx.top/Golang/0eeca9cab910/

版权: 本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。

在线工具