教程:使用 Go 和 Gin 开发 RESTful API

By | 2021年9月17日

本教程介绍使用 Go 和Gin Web 框架(Gin)编写 RESTful Web 服务 API 的基础知识。

如果您对 Go 及其工具有基本的了解,您将从本教程中获得最大收益。

如果这是您第一次接触 Go,请参阅 教程:Go 入门以获取 快速介绍。

Gin 简化了与构建 Web 应用程序(包括 Web 服务)相关的许多编码任务。

在本教程中,您将使用 Gin 来路由请求、检索请求详细信息并编组 JSON 以获取响应。

在本教程中,您将构建一个具有两个端点的 RESTful API 服务器。您的示例项目将是有关复古爵士乐唱片的数据存储库。

本教程包括以下部分:

  1. 设计 API 路径。
  2. 为您的代码创建一个文件夹。
  3. 创建数据。
  4. 编写一个处理程序来返回所有项目。
  5. 编写一个处理程序来添加一个新项目。
  6. 编写一个处理程序来返回一个特定的项目。

注意:有关其他教程,请参阅教程

要尝试将此作为您在 Google Cloud Shell 中完成的交互式教程,请点击下面的按钮。

先决条件

  • Go 1.16 或更高版本的安装。有关安装说明,请参阅 安装 Go
  • 一种编辑代码的工具。您拥有的任何文本编辑器都可以正常工作。
  • 命令终端。Go 适用于 Linux 和 Mac 上的任何终端,以及 Windows 中的 PowerShell 或 cmd。
  • curl工具。在 Linux 和 Mac 上,这应该已经安装了。在 Windows 上,它包含在 Windows 10 Insider build 17063 及更高版本中。对于较早的 Windows 版本,您可能需要安装它。有关更多信息,请参阅 Tar 和 Curl 来到 Windows

设计 API 路劲

您将构建一个 API,该 API 提供对销售黑胶唱片老式唱片商店的访问。

因此,您需要提供url,客户端可以通过这些url为用户获取和添加专辑。

在开发 API 时,您通常从设计url开始。如果路径易于理解,您的 API 用户将获得更多成功。

以下是您将在本教程中创建的端点。

/albums

  • GET – 获取所有专辑的列表,以 JSON 形式返回。
  • POST – 从以 JSON 形式发送的请求数据中添加新专辑。

/albums/:id

  • GET – 通过 ID 获取专辑,以 JSON 形式返回专辑数据。

接下来,您将为您的代码创建一个文件夹。

为您的代码创建一个文件夹

首先,为您将编写的代码创建一个项目。

  1. 打开命令提示符并切换到您的主目录。

    在 Linux 或 Mac 上:

    $ cd
    

    在 Windows 上:

    C:\> cd %HOMEPATH%
    
  2. 使用命令提示符为您的代码创建一个名为 web-service-gin 的目录。
    $ mkdir web-service-gin
    $ cd web-service-gin
    
  3. 创建一个模块,您可以在其中管理依赖项。

    运行该go mod init命令,为其指定代码所在模块的路径。

    $ go mod init example/web-service-gin
    go: creating new go.mod: module example/web-service-gin
    

    此命令创建一个 go.mod 文件,您添加的依赖项将在其中列出以进行跟踪。有关使用模块路径命名模块的更多信息,请参阅 管理依赖项

接下来,您将设计用于处理数据的数据结构。

创建数据

为使本教程简单,您将数据存储在内存中。更典型的 API 会与数据库交互。

请注意,将数据存储在内存中意味着每次停止服务器时专辑集都会丢失,然后在启动时重新创建。

编写代码

  1. 使用您的文本编辑器,在 web-service 目录中创建一个名为 main.go 的文件。您将在此文件中编写 Go 代码。

  2. 在 main.go 文件的顶部,粘贴以下包声明。

    package main
    

    独立程序(与库相对)始终在 package 中main

  3. 在包声明下方,粘贴以下album结构声明 。您将使用它在内存中存储专辑数据。

    结构标记,例如json:"artist"指定当结构的内容序列化为 JSON 时字段的名称应该是什么。如果没有它们,JSON 将使用结构体的大写字段名称——这种样式在 JSON 中并不常见。

    // album represents data about a record album.
    type album struct {
       ID     string  `json:"id"`
       Title  string  `json:"title"`
       Artist string  `json:"artist"`
       Price  float64 `json:"price"`
    }
    
  4. 在您刚刚添加的结构声明下方,粘贴以下album结构片段, 其中包含您将用于启动的数据。
    // albums slice to seed record album data.
    var albums = []album{
       {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
       {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
       {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
    }
    

接下来,您将编写代码来实现您的第一个接口。

编写一个处理程序来返回所有项目

当客户端在 发出请求时GET /albums,您希望以 JSON 形式返回所有专辑。

为此,您将编写以下内容:

  • 准备响应的逻辑
  • 将请求路径映射到逻辑的代码

请注意,这与它们在运行时的执行方式相反,但您首先添加依赖项,然后添加依赖它们的代码。

编写代码

  1. 在上一节中添加的结构体代码下,粘贴以下代码以获取专辑列表。

    getAlbums函数从album结构切片创建 JSON ,将 JSON 写入响应。

    // getAlbums responds with the list of all albums as JSON.
    func getAlbums(c *gin.Context) {
       c.IndentedJSON(http.StatusOK, albums)
    }
    

    在此代码中,您:

  • 编写一个getAlbumsgin.Context 参数的函数 。请注意,您可以为该函数指定任何名称——Gin 和 Go 都不需要特定的函数名称格式。

    gin.Context是Gin最重要的部分。它携带请求详细信息、验证和序列化 JSON 等。(尽管名称相似,但这与 Go 的内置 context包不同。)

  • 调用Context.IndentedJSON 以将结构序列化为 JSON 并将其添加到响应中。

    该函数的第一个参数是您要发送给客户端的 HTTP 状态代码。在这里,您StatusOKnet/http包中传递常量以指示200 OK.

    请注意,您可以替换 为发送更紧凑的 JSONContext.IndentedJSON的调用 Context.JSON。在实践中,缩进形式在调试时更容易使用,并且大小差异通常很小。

  1. 在 main.go 的顶部附近,就在albums slice声明的下方,粘贴下面的代码以将处理程序函数分配给接口路径。

    这会建立一个关联,在该关联中getAlbums处理对/albums端点路径的请求 。

    func main() {
       router := gin.Default()
       router.GET("/albums", getAlbums)
    
       router.Run("localhost:8080")
    }
    

    在此代码中,您:

  • 使用 Default.

  • 使用该GET 函数将GETHTTP 方法和/albums路径与处理程序函数相关联。

    请注意,你传递的名称的的getAlbums功能。这与传递函数的结果不同,传递函数是传递getAlbums()(注意括号)。

  • 使用Run 功能将路由器连接到一个http.Server并启动服务器。

  1. 在 main.go 的顶部附近,就在包声明的下方,导入支持刚刚编写的代码所需的包。

    第一行代码应该是这样的:

    package main
    
    import (
       "net/http"
    
       "github.com/gin-gonic/gin"
    )
    
  2. 保存 main.go。

运行代码

  1. 开始将 Gin 模块作为依赖项进行跟踪。

    在命令行中,使用go get 将 github.com/gin-gonic/gin 模块添加为您的模块的依赖项。使用点参数表示“获取当前目录中代码的依赖项”。

    $ go get .
    go get: added github.com/gin-gonic/gin v1.7.2
    

    解决并下载此依赖项以满足import 您在上一步中添加的声明。

  2. 从包含 main.go 的目录中的命令行,运行代码。使用点参数表示“在当前目录中运行代码”。

    $ go run .
    

    代码运行后,您就有了一个正在运行的 HTTP 服务器,您可以向其发送请求。

  3. 在新的命令行窗口中,用于curl向正在运行的 Web 服务发出请求。

    $ curl http://localhost:8080/albums
    

    该命令应显示您为服务提供种子的数据。

    [
           {
                   "id": "1",
                   "title": "Blue Train",
                   "artist": "John Coltrane",
                   "price": 56.99
           },
           {
                   "id": "2",
                   "title": "Jeru",
                   "artist": "Gerry Mulligan",
                   "price": 17.99
           },
           {
                   "id": "3",
                   "title": "Sarah Vaughan and Clifford Brown",
                   "artist": "Sarah Vaughan",
                   "price": 39.99
           }
    ]
    

您已经启动了一个 API!在下一部分中,您将使用代码创建另一个url来处理POST添加项目的请求。

编写一个处理程序来添加一个新项目

当客户端POST在 处发出请求时/albums,您希望将请求正文中描述的专辑添加到现有专辑数据中。

为此,您将编写以下内容:

  • 将新专辑添加到现有列表的逻辑。
  • POST请求路由到您的逻辑的一些代码。

编写代码

  1. 添加代码以将专辑数据添加到专辑列表中。

    import语句之后的某处粘贴以下代码。(文件的末尾是这段代码的好地方,但 Go 不强制你声明函数的顺序。)

    // postAlbums adds an album from JSON received in the request body.
    func postAlbums(c *gin.Context) {
       var newAlbum album
    
       // Call BindJSON to bind the received JSON to
       // newAlbum.
       if err := c.BindJSON(&newAlbum); err != nil {
           return
       }
    
       // Add the new album to the slice.
       albums = append(albums, newAlbum)
       c.IndentedJSON(http.StatusCreated, newAlbum)
    }
    

    在此代码中,您:

  • 使用Context.BindJSON 请求体结合newAlbum
  • album将从 JSON 初始化的结构附加到albums slice。
  • 201向响应添加状态代码,以及表示您添加的专辑的 JSON。
  1. 更改您的main函数,使其包含该router.POST函数,如下所示。

    func main() {
       router := gin.Default()
       router.GET("/albums", getAlbums)
       router.POST("/albums", postAlbums)
    
       router.Run("localhost:8080")
    }
    

    在此代码中,您:

  • POST/albums路径中的方法与postAlbums 函数相关联。

    使用 Gin,您可以将处理程序与 HTTP 方法和路径组合相关联。通过这种方式,您可以根据客户端使用的方法单独路由发送到单个路径的请求。

运行代码

  1. 如果服务器从上一节开始仍在运行,请停止它。

  2. 从包含 main.go 的目录中的命令行,运行代码。

    $ go run .
    
  3. 从不同的命令行窗口,用于curl向正在运行的 Web 服务发出请求。
    $ curl http://localhost:8080/albums \
       --include \
       --header "Content-Type: application/json" \
       --request "POST" \
       --data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'
    

    该命令应显示添加专辑的标题和 JSON。

    HTTP/1.1 201 Created
    Content-Type: application/json; charset=utf-8
    Date: Wed, 02 Jun 2021 00:34:12 GMT
    Content-Length: 116
    
    {
       "id": "4",
       "title": "The Modern Sound of Betty Carter",
       "artist": "Betty Carter",
       "price": 49.99
    }
    
  4. 与上一节一样,使用curl检索专辑的完整列表,您可以使用它来确认新专辑已添加。
    $ curl http://localhost:8080/albums \
       --header "Content-Type: application/json" \
       --request "GET"
    

    该命令应显示专辑列表。

    [
           {
                   "id": "1",
                   "title": "Blue Train",
                   "artist": "John Coltrane",
                   "price": 56.99
           },
           {
                   "id": "2",
                   "title": "Jeru",
                   "artist": "Gerry Mulligan",
                   "price": 17.99
           },
           {
                   "id": "3",
                   "title": "Sarah Vaughan and Clifford Brown",
                   "artist": "Sarah Vaughan",
                   "price": 39.99
           },
           {
                   "id": "4",
                   "title": "The Modern Sound of Betty Carter",
                   "artist": "Betty Carter",
                   "price": 49.99
           }
    ]
    

在下一部分中,您将添加代码来处理GET特定项目。

编写处理程序以返回特定项目

当客户端向 发出请求时GET /albums/[id],您希望返回 ID 与idpath 参数匹配的相册。

为此,您将:

  • 添加逻辑以检索请求的专辑。
  • 将路径映射到逻辑。

编写代码

  1. postAlbums您在上一节中添加的函数下方,粘贴以下代码以检索特定专辑。

    getAlbumByID函数将提取请求路径中的ID,然后定位匹配的专辑。

    // getAlbumByID locates the album whose ID value matches the id
    // parameter sent by the client, then returns that album as a response.
    func getAlbumByID(c *gin.Context) {
       id := c.Param("id")
    
       // Loop over the list of albums, looking for
       // an album whose ID value matches the parameter.
       for _, a := range albums {
           if a.ID == id {
               c.IndentedJSON(http.StatusOK, a)
               return
           }
       }
       c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
    }
    

    在此代码中,您:

  • 用于从 URLContext.Param 检索id路径参数。当您将此处理程序映射到路径时,您将在路径中包含参数的占位符。

  • 循环album切片中的结构,查找ID 字段值与id参数值匹配的结构。如果找到,则将该album结构序列化为JSON 并将其作为带有200 OK HTTP 代码的响应返回。

    如上所述,现实世界的服务可能会使用数据库查询来执行此查找。

  • 如果找不到专辑404http.StatusNotFound则返回 HTTP错误。

  1. 最后,更改您的main使其包含对 的新调用router.GET,路径现在为/albums/:id,如以下示例所示。

    func main() {
       router := gin.Default()
       router.GET("/albums", getAlbums)
       router.GET("/albums/:id", getAlbumByID)
       router.POST("/albums", postAlbums)
    
       router.Run("localhost:8080")
    }
    

    在此代码中,您:

  • /albums/:id路径与getAlbumByID函数关联。在 Gin 中,路径中项目前面的冒号表示该项目是路径参数。

运行代码

  1. 如果服务器从上一节开始仍在运行,请停止它。

  2. 从包含 main.go 的目录中的命令行,运行代码以启动服务器。

    $ go run .
    
  3. 从不同的命令行窗口,用于curl向正在运行的 Web 服务发出请求。
    $ curl http://localhost:8080/albums/2
    

    该命令应显示您使用其 ID 的专辑的 JSON。如果未找到专辑,您将收到带有错误消息的 JSON。

    {
           "id": "2",
           "title": "Jeru",
           "artist": "Gerry Mulligan",
           "price": 17.99
    }
    

结论

恭喜!您刚刚使用 Go 和 Gin 编写了一个简单的 RESTful Web 服务。

完整代码

本部分包含您使用本教程构建的应用程序的代码。

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

// album represents data about a record album.
type album struct {
    ID     string  `json:"id"`
    Title  string  `json:"title"`
    Artist string  `json:"artist"`
    Price  float64 `json:"price"`
}

// albums slice to seed record album data.
var albums = []album{
    {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)
    router.GET("/albums/:id", getAlbumByID)
    router.POST("/albums", postAlbums)

    router.Run("localhost:8080")
}

// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, albums)
}

// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
    var newAlbum album

    // Call BindJSON to bind the received JSON to
    // newAlbum.
    if err := c.BindJSON(&newAlbum); err != nil {
        return
    }

    // Add the new album to the slice.
    albums = append(albums, newAlbum)
    c.IndentedJSON(http.StatusCreated, newAlbum)
}

// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
func getAlbumByID(c *gin.Context) {
    id := c.Param("id")

    // Loop through the list of albums, looking for
    // an album whose ID value matches the parameter.
    for _, a := range albums {
        if a.ID == id {
            c.IndentedJSON(http.StatusOK, a)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}

请关注公众号获取更多资料

发表评论

电子邮件地址不会被公开。 必填项已用*标注