go Gin框架学习 | 青训营

81 阅读8分钟

Gin介绍

Gin 是一个用于构建基于 Go 语言的 Web 应用程序的轻量级框架。具有良好的性能和生产力,可以用于快速构建 RESTful API、Web 服务以及其他网络应用。

具有以下特性:

快速

基于 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。

可扩展性

新建一个中间件非常简单。

RESTful API规范

可参考-->RESTful API 设计规范 - 掘金 (juejin.cn)

http动词 由之前的基本只使用 GET, POST 到基本使用

  • GET(SELECT):从服务器取出资源(一项或多项)
  • POST(CREATE):在服务器新建一个资源
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)
  • DELETE(DELETE):从服务器删除资源

下载并安装Gin

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

Gin 快速入门

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
func main() {
    r := gin.Default()
    // 相当于http://localhost:8080/hello
    r.GET("/hello", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "hello world",
        })
    })
    err := r.Run() 
    // 这里可以指定端口号,如果没有指定默认8080端口
    // 例如我们可以r.Run(":8082")来指定为8082端口
    if err != nil {
        panic(err)
    }
}

在这个程序中,我定义了一个 GET 请求的路由。当客户端发送 GET 请求到 "http://localhost:8080/hello" 路径时,Gin 框架将调用您提供的匿名函数(路由处理程序)来处理这个请求。

在这个匿名函数中,使用 c.JSON(200, gin.H{ "message": "hello world" }) 返回了一个 JSON 格式的响应。这意味着当客户端请求 "/hello" 路径时,服务器会返回一个状态码为 200(表示成功)的响应,其中包含一个 JSON 对象,该对象包含一个名为 "message" 的字段,其值为 "hello world"。

状态码的定义请参考 RESTful API规范

结果:

image.png

请求参数

URL拼接参数是一种常见的技术,可以有效地改善网络应用的性能和安全。通常的URL地址参数拼接格式是使用 ? 隔开地址与参数,使用&符号来拼接参数,参数名和参数值之间使用=号。如果需要在URL后面拼接参数,可以在URL后面先加上一个?,然后每个参数名前面加上&

我们会在URL中遇到很多参数,在服务端我们该如何获取到这些参数呢

GET

1. 普通参数

1.1 Query

例如:http://localhost:8080/hello?id=1&name=zhangsan

这里? 后面的idname都是参数名,我们现在需要通过GET来获取到这两个参数的值。(获取客户端的参数值)

现在我们利用Gin框架获取到用户输入的参数值,并把这些参数值返回到前端页面

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
func main() {
    r := gin.Default()
    r.GET("/hello", func(c *gin.Context) {
        id := c.Query("id")
        name := c.Query("name")
        c.JSON(200, gin.H{
            "id":   id,
            "name": name,
        })
    })
    err := r.Run()
    if err != nil {
        panic(err)
    }
}

GET : http://localhost:8080/hello?id=1&name=zhangsan

结果可以看到页面为:

image-20230820130101936

1.2 DefaultQuery

我们还可以给参数设置默认值,只需要使用DefaultQuery ,然后再参数名后加一个默认值即可

例如,设置默认性别为男性

r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
    id := c.Query("id")
    name := c.Query("name")
    sex := c.DefaultQuery("sex", "male") //设置默认值
    c.JSON(200, gin.H{
        "id":   id,
        "name": name,
        "sex":  sex,
    })
})
1.3 GetQuery

我们可以使用GetQuery来确定参数是否存在,该方法返回两个value,如果参数存在,则返回参数值和true, 如果参数不存在则返回空值和false

r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
    id := c.Query("id")
    name := c.Query("name")
    sex, ok := c.GetQuery("sex") // ok返回参数是否存在
    c.JSON(200, gin.H{
        "id":       id,
        "name":     name,
        "sex":      sex,、
        "sexExist": ok,
    })
})
1.4 BindQuery 与 ShouldBindQuery

因为结构体也可以和json数据一一对应,所以我们也可以用结构体对象来返回json数据给前端

上述方式获取的都是string类型的值,通过这种方法我们也可以根据类型需要来获取

还是这个链接 http://localhost:8080/hello?id=1&name=zhangsan

因为有两个参数,我们可以这样定义一个结构体

type User struct {
    Id   int    `form:"id"` //这里我们打标记来转成参数名
    Name string `form:"name"`
}

然后再这样使用ShouldBindQuery 或者 BindQuery

r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
    var user User
    err := c.ShouldBindQuery(&user) // 用结构体对象来装数据
    if err != nil {
        panic(err)
    }
    c.JSON(200, user)
})

我们可以在标记中使用binding:"required" 来说明一个参数为必须的

例如:

type User struct {
    Id   int    `form:"id"`
    Name string `form:"name"`
    Sex  string `form:"sex" binding:"required"`
}

这个时候如果我们输入的URL中没有sex这个参数的话,就会报错

BindQueryShouldBindQuery的区别:

当bind是必须的时候,ShouldBindQuery会报错,状态码不变还是200,错误由开发者自行处理。

BindQuery则在报错的同时,会将状态码改为400,意为失败。所以一般建议是使用Should开头的bind。

2. 数组参数

http://localhost:8080/hello?id=1&name=zhangsan&name=zhangsi&name=zhangwu

上面这个链接一个参数name的值却有三个,我们可以利用数组参数的方法来获取值,gin中有对应的array方法

2.1 QueryArray
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
    var names []string
    names = c.QueryArray("name")
    id := c.Query("id")
    c.JSON(200, gin.H{
        "id":    id,
        "names": names,
    })
})
2.2 GetQueryArray
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
    var names []string
    var ok bool
    names, ok = c.GetQueryArray("name")
    id := c.Query("id")
    c.JSON(200, gin.H{
        "id":         id,
        "names":      names,
        "namesExist": ok,
    })
})
2.3 BindQuery 与 ShouldBindQuery

把结构体中的Name的类型改为切片

type User struct {
    Id   int      `form:"id"`
    Name []string `form:"name"`
}

后面一样的操作就行了

r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
    var user User
    err := c.ShouldBindQuery(&user)
    if err != nil {
        log.Println(err)
    }
    c.JSON(200, user)
})

3. map参数

http://localhost:8080/hello?id=1&members[name1]=zhangsan&members[name2]=lisi

可以看到这个members参数是个map类型,怎么办呢?

其实也是一样的,Gin框架中有对应的 map方法

1. QueryMap
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
    id := c.Query("id")
    members := c.QueryMap("members")
    c.JSON(200, gin.H{
        "id":      id,
        "members": members,
    })
})
2. GetQueryMap
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
    id := c.Query("id")
    members, ok := c.GetQueryMap("members")
    c.JSON(200, gin.H{
        "id":           id,
        "members":      members,
        "membersExist": ok,
    })
})

map 参数不能通过 bind 绑定获取

POST

post请求一般是表单参数和json参数

1. 表单参数

现在我们从客户端发送表单数据到服务端,并从服务端获取表单数据返回到前端页面

表单中同样有普通参数、数组参数和map参数,这次统一获取

image.png

利用 Apifox 发送一个post请求,表单如上

Gin框架获取并返回到前端页面:

func main() {
    r := gin.Default()
    r.POST("/hello", func(c *gin.Context) {
        id := c.PostForm("id")
        members := c.PostFormArray("member")
        address, ok := c.GetPostFormMap("address")
        c.JSON(200, gin.H{
            "id":           id,
            "members":      members,
            "address":      address,
            "addressExist": ok,
        })
    })
    err := r.Run()
    if err != nil {
        panic(err)
    }
}

成功获取到数据:

image.png

可以看到,Gin 的这些方法还是十分通俗易懂并且通用的,让我们一眼就能看懂这个方法是干嘛的。

2. json参数

如果传的不是普通参数,而是像 json 一样的数据的话,我们可以使用 shouldbind 方法来绑定

json 为例,我们就发送之前获取到的json数据, 这里也是使用apifox实现

{
    "address": {
        "home": "jian",
        "school": "nanchang"
    },
    "addressExist": true,
    "id": "1",
    "members": [
        "zhangsan",
        "lisi"
    ]
}

我们先定义一个结构体

type User struct {
    Id           string            `form:"id"`
    Members      []string          `form:"members"`
    Address      map[string]string `form:"address"`
    AddressExist bool              `form:"addressExist"`
}

然后利用Gin 框架获取之前发送的json数据,并将获取到的数据返回前端

func main() {
    r := gin.Default()
    r.POST("/hello", func(c *gin.Context) {
        var user User
        err := c.ShouldBindJSON(&user)
        if err != nil {
            panic(err)
        }
        c.JSON(200, user)
    })
    err := r.Run()
    if err != nil {
        panic(err)
    }
}

结果:

image.png 但是我们发现这里的字段大写了,我们标记` form : " "`好像没有起到作用

事实上这里标记需要改成json的标记,像下面这样

type User struct {
    Id           string            `json:"id"`
    Members      []string          `json:"members"`
    Address      map[string]string `json:"address"`
    AddressExist bool              `json:"addressExist"`
}

再重新操作一次,发现结果ok了

image.png

响应

之前我们一直是使用的c.json 响应客户端,返回json数据,实际上我们能使用的方式有很多很多

我们以GET 请求和 http://localhost:8080/hello URL 为例,返回各种格式的数据

1. 字符串方式

r.GET("/hello", func(c *gin.Context) {
    c.String(200, "hello world")
})

2. json方式

r.GET("/hello", func(c *gin.Context) {
    c.JSON(200, gin.H{
        "json": "qwq",
    })
})

3. 文件

r.GET("/hello", func(c *gin.Context) {
    c.FileAttachment("./image/1.jpg", "有马加奈")
})

4. 重定向

r.GET("/hello", func(c *gin.Context) {
    c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
})

这里注意重定向的状态码不一样,是301,并且浏览器会有缓存,如果出不来页面可以换一个浏览器试试

5. http响应头

r.GET("/hello", func(c *gin.Context) {
    c.Header("test", "testqwq")
    c.String(200, "qwq")
})

这里看看 header里面,可以发现多了一条test testqwq

剩下还有类似XML 和 YAML, 可以参考官方文档或者源码。