Go 语言入门指南:Gin框架的使用 | 豆包MarsCode AI刷题

117 阅读9分钟

Gin使用

Gin框架安装与使用

安装

下载并安装Gin:

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

-u 选项的作用是更新指定包及其依赖项到最新版本

第一个Gin示例:

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
func Test(c *gin.Context) {
    c.JSON(200, gin.H{
        "msg": "test",
    })
}
​
func main() {
    // 创建一个默认的路由引擎
    r := gin.Default()
    // GET:请求方式;/ping:请求的路径
    // 当客户端以GET方法请求/ping路径时,会执行后面的匿名函数
    // r.GET("/ping", func(c *gin.Context) {
    //   //  c.JSON:返回JSON格式的数据
    //  c.JSON(200, gin.H{
    //      "msg": "test",
    //  })
    // })
    // 也可以在main函数外创建一个函数,直接在GET方法里调用这个函数
    r.GET("/test", Test)
    // 启动HTTP服务,默认在0.0.0.0:8080启动服务
    r.Run("xx.xx.xx.xx:10005")
}

"xx.xx.xx.xx:10005"中,xx.xx.xx.xx是你自己运行的地址,可以是localhost也可以是服务器地址,在浏览器中运行时使用xx.xx.xx.xx:10005

RESTful API

REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。

一、资源(Resources)

REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。

所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。 它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。

所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。

、表现层(Representation)

"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。

比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。

URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。

三、状态转化(State Transfer)

访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。

互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。

四、综述

综合上面的解释,总结一下什么是RESTful架构:

  (1)每一个URI代表一种资源;

  (2)客户端和服务器之间,传递这种资源的某种表现层;

  (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

最常见的一种设计错误,就是URI包含动词。 因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。

举例来说,某个URI是/posts/show/1,其中show是动词,这个URI就设计错了,正确的写法应该是/posts/1,然后用GET方法表示show。

另一个设计误区,就是在URI中加入版本号:因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URI。版本号可以在HTTP请求头信息的Accept字段中进行区分

只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互。

开发RESTful API的时候我们通常使用Postman来作为客户端的测试工具。

渲染

JSON渲染

func main() {
    r := gin.Default()
​
    // gin.H 是map[string]interface{}的缩写
    r.GET("/someJSON", func(c *gin.Context) {
        // 方式一:自己拼接JSON
        c.JSON(http.StatusOK, gin.H{"message": "Hello world!"})
    })
    r.GET("/moreJSON", func(c *gin.Context) {
        // 方法二:使用结构体
        var msg struct {
            Name    string `json:"user"`
            Message string
            Age     int
        }
        msg.Name = "小王子"
        msg.Message = "Hello world!"
        msg.Age = 18
        c.JSON(http.StatusOK, msg)
    })
    r.Run(":8080")
}
​

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参数

querystring指的是URL中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。 获取请求的querystring参数的方法如下:

key=value格式,多个key-value用&连接

r.GET("/web", func(c *gin.Context) {
        // 获取浏览器发请求携带的query string 参数
        name := c.Query("name")
        age := c.Query("age")
        // name := c.DefaultQuery("name", "test")  // 没传值就取默认值test
        // name, ok := c.GetQuery("name")   // 取到返回(值,true),取不到返回("",false)
        // if !ok {
        //  c.JSON(http.StatusBadRequest, gin.H{
        //      "err": http.StatusBadRequest,
        //      "msg": "name is required",
        //  })
        //  return
        // }
        c.JSON(http.StatusOK, gin.H{
            "name": name,
            "age":  age,
        })
    })

获取form参数

当前端请求的数据通过form表单提交时,例如向/login发送一个POST请求,获取请求数据的方式如下:

r.POST("/login", func(c *gin.Context) {
        // 获取form表单提交的数据
        username := c.PostForm("username")
        password := c.PostForm("password")
        // DefaultPostForm取不到值时会返回指定的默认值
        //username := c.DefaultPostForm("username", "小王子")  // 没传值就取默认值test
        // username, ok := c.GetPostForm("name") // 取到返回(值,true),取不到返回("",false)
        // if !ok {
        //  c.JSON(http.StatusBadRequest, gin.H{
        //      "err": http.StatusBadRequest,
        //      "msg": "username is required",
        //  })
        //  return
        // }
        c.JSON(http.StatusOK, gin.H{
            "username": username,
            "password": password,
            "msg":      "login success",
        })
    })

获取JSON参数

当前端请求的数据通过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/小王子/沙河。 获取请求URL路径中的参数的方式如下。

r.GET("/user/:username/:address", func(c *gin.Context) {
        // 获取路径参数
        username := c.Param("username")
        address := c.Param("address")
        c.JSON(http.StatusOK, gin.H{
            "username": username,
            "address":  address,
            "msg":      "success",
        })
    })

参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryStringform表单JSONXML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSONform表单QueryString类型的数据,并把值绑定到指定的结构体对象。

type UserInfo struct {
    Username string `json:"username"`
    Password string `json:"password"`
}
​
r.POST("/loginJSON", func(c *gin.Context) {
        var u UserInfo
        // err := c.ShouldBind(&u)
        // if err != nil {
        //  c.JSON(http.StatusBadRequest, gin.H{
        //      "err": err.Error(),
        //  })
        // } else {
        //  fmt.Printf("%#v\n", u)
        //  c.JSON(http.StatusOK, gin.H{
        //      "msg": "login success",
        //  })
        // }
        if err := c.ShouldBind(&u); err == nil {
            fmt.Printf("login info:%#v\n", u)
            c.JSON(http.StatusOK, gin.H{
                "user":     u.Username,
                "password": u.Password,
            })
        } else {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        }
    })

ShouldBind会按照下面的顺序解析请求中的数据完成绑定:

  1. 如果是 GET 请求,只使用 Form 绑定引擎(query)。
  2. 如果是 POST 请求,首先检查 content-type 是否为 JSONXML,然后再使用 Formform-data)。

重定向

HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。

r.GET("/redirect", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")  // 跳转到百度一下页面
    })

路由重定向

路由重定向,使用HandleContext

r.GET("/handlecontext", func(c *gin.Context) {
    // 跳转到/handletest对应的路由处理函数
    c.Request.URL.Path = "/handletest"  // 把请求的URI修改
    r.HandleContext(c)  // 继续后续的处理
})
r.GET("/handletest", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "msg": "handle test",
    })
})

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) {
    switch c.Request.Method {
        case "GET":
            c.JSON(http.StatusOK, gin.H{"method":"GET"})
        case http.MethodPost:
            c.JSON(http.StatusOK, gin.H{"method":"POST"})
        //...
    }
})

为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。

r.NoRoute(func(c *gin.Context) {
        c.JSON(http.StatusNotFound, gin.H{
            "msg": "page not found",
        })
    })

路由组

我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。

// 路由组
// 把公用的前缀提取出来,创建一个路由组
userGroup := r.Group("/usergroup")
{
    userGroup.GET("/index", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "user index",
        })
    })
    userGroup.POST("/username", func(c *gin.Context) {
        username := c.PostForm("username")
        c.JSON(http.StatusOK, gin.H{
            "msg":      "user username",
            "username": username,
        })
    })
}

路由组也支持嵌套

路由原理

Gin框架中的路由使用的是httprouter这个库。

其基本原理就是构造一个路由地址的前缀树。

MVC设计模式

模型

视图

控制器

项目目录

基于MVC设计模式,设计项目目录

目录 说明

conf 项目配置文件及读取配置文件的相关功能

controllers 控制器目录,包括项目各个模块的控制器及业务处理

view 视图目录,主要包含首页前端文件

model 数据实体目录,主要定义了项目中各业务模块的实体对象

service 服务层目录,用于各个模块基础功能接口的定义和实现,是各个模块的数据层

static 配置项目的静态资源目录

util 提供通用的方法封装

main.go 项目程序主入口

学习总结

在学习Gin框架使用过程中,我觉得最重要的是动手实操,只有动手做,才会发现什么情况需要用什么样的方法。同时在实践的过程中可以熟悉各种方法的使用,也可以知道该在什么地方使用。可以从一个简单的后端项目出发,学习如何使用Gin框架完成一个项目,在这个项目中各种目录有什么作用,如何正确的组织一个项目的流程。