使用Go和Gin开发RESTFul API
对应的代码仓库地址:gocode
本篇内容介绍如何使用Go和 Gin Web Framework来编写RESTFul API 服务的基础知识。如果你还对Go的基础操作不熟悉的话最好还是先看一下: 入门教程
Gin是一个Go语言的Web开发框架,它简化了构建Web应用的编码。在下面的介绍和代码中,我们将通过Gin来路由请求、处理请求数据和返回JSON响应。
设计API端点
这里我们根据官方给出的示例来进行,我们将建立一个API,它可以访问一家出售古董黑胶唱片的商店。因此,我们需要创建可以查询和增加唱片的端点(官网用 endpoint 来表达,其实就是我们平常的接口路由的概念),下面是教程中会创建的两个端点:
/albums
- GET: 用来查询所有的专辑,返回的是JSON数据
- POST: 发送JSON数据的请求来添加专辑
/albums/:id
- GET: 用来获取指定ID的专辑
创建文件夹和代码
首先我们创建存放我们代码的文件夹,这里命名为 web-service-gin ,然后进入到该文件夹,执行 go mod init
进行初始化:
go mod init example/web-service-gin
创建数据
下面我们来设计数据结构,教程中为了简单演示,因此将数据是存储到了内存中进行处理,通常情况下是要结合数据库来一起使用的。因此本教程的数据在每次服务的关闭和启动后数据会被重新初始化为内存中的数据。
下面,我们创建一个名为 main.go 的文件,然后进行编码:
package main
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artis"`
Price float64 `json:"price"`
}
一个独立的程序(相对库来说)它始终位于main包中,然后我们定义了 album 结构体,这里需要注意的是json:"id"
这样的表示当被序列化为JSON时的字段名为什么,上面显是将序列化后的字段统一为小写,如果没有它们,在返回的JSON和传递参数的JSON都需要将首字母大写。
下面我们来创建一些数据,大家可以直接粘贴下面的数据到你的结构提下面
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},
}
编写处理代码
下面,我们将实现处理数据的代码。
编写返回所有数据项代码
当客户端对/albums
发起GET请求时,我们要把所有的数据项以JSON的形式返回给客户端,实现这个我们需要关注下面两点:
- 处理响应的逻辑
- 映射请求路径的逻辑
接下来,我们在代码中增加一个方法,名为 getAlbums
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
在上面的代码中,我们首先创建了一个 getAlbums 方法,这里的方法名可以随意,然后将gin.Context
作为参数。gin.Context
是Gin最终要的部分,它携带了请求的详情、验证和序列化JSON等等。然后Context.IndentedJSON
的作用是将数据序列化为JSON后作为响应,这个方法的第一个参数是请求的状态,这里我们复制为了请求成功的状态,也就是200,第二个参数就是我们要序列化返回给客户端的数据。
注意:我们可以使用
Context.JSON
来代替Context.IndentedJSON
,两者的区别是Context.JSON
会是序列化后的JSON更紧凑,Context.IndentedJSON
是带缩进的格式化后的JSON。
下面我们再添加主方法,它的作用是将请求和对应的处理进行关联
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.Run("localhost:8080")
}
上面的代码中我们首先使用Default
方法初始化了路由,然后使用路由的GET方法将请求路径为/albums
和处理方法getAlbums
进行关联。最后使用Run
函数启动了一个host为localhost端口为8080的http服务。
添加完上面的代码后我们需要添加对应的依赖库,需要在 package main 下加入下面代码后保存文件:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
运行代码
在运行代码前我们首先获取我们需要的相关依赖,这里我们使用 go get
命令去添加,执行如下代码:
go get .
然后我们在 main.go 的目录下运行代码
go run .
一旦运行,现在就有一个HTTP服务在运行,我么可以向它发送请求,你可以在浏览器输入地址: http://localhost:8080/albums
来访问数据,或者使用curl命令在终端中请求
curl http://localhost:8080/albums
请求到的数据如下:
[
{
"id": "1",
"title": "Blue Train",
"artis": "John Coltrane",
"price": 56.99
},
{
"id": "2",
"title": "Jeru",
"artis": "Gerry Mulligan",
"price": 17.99
},
{
"id": "3",
"title": "Sarah Vaughan and Clifford Brown",
"artis": "Sarah Vaughan",
"price": 39.99
}
]
编写添加新数据项代码
下面我们编写另外一个端点,当用户对/albums
发起POST请求时,我们将接收到的JSON数据反序列化后添加到albums
数据中,同样的,我们下面的代码关注两个点:
- 向存在的列表添加新数据项的逻辑
- 处理POST请求的逻辑代码
首先,我们在 main.go 文件中增加下面的方法:
func postAlbums(c *gin.Context) {
var newAlbum album
//使用BindJSON来绑定接收到的JSON数据到newAlbum
if err := c.BindJSON(&newAlbum); err != nil {
return
}
//添加新的albm到slice中
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
上面的代码中使用Context.BindJSON
来绑定请求体到newAlbum
,然后将新的album添加到albums slice中,最后添加201状态码,并将新添加的内容返回给客户端。下面我们修改main方法中的代码
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
运行代码
go run .
接下来我们测试一下,如果你使用的是 Linux/MacOS 直接使用 curl 命令发起请求:
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}'
如果是Windows的话使用Postman或其他的请求工具,发送如下的JSON数据
{
"id": "4",
"title": "The Modern Sound of Betty Carter",
"artist": "Betty Carter",
"price": 49.99
}
我这里用客户端请求工具请求的结果如下:
然后再次访问GET请求发现新增成功,结果如下:
[
{
"id": "1",
"title": "Blue Train",
"artis": "John Coltrane",
"price": 56.99
},
{
"id": "2",
"title": "Jeru",
"artis": "Gerry Mulligan",
"price": 17.99
},
{
"id": "3",
"title": "Sarah Vaughan and Clifford Brown",
"artis": "Sarah Vaughan",
"price": 39.99
},
{
"id": "4",
"title": "The Modern Sound of Betty Carter",
"artis": "",
"price": 49.99
}
]
编写返回指定的数据项代码
当客户端发起一个 /albums/[id]
这样的GET请求时,我们需要从albums中找到匹配的ID然后返回给客户端,下面我们来编写代码,我们在postAlbums方法下面增加getAlbumByID的方法,代码如下:
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
//循环albums列表,然后找打匹配的数据项,如果没有找到返回Not Found状态和信息
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message":"album not found"})
}
上面的代码中使用Context.Param
方法来检索请求路径中的对应占位符的参数,然后通过循环albums数据对比ID来找出对应的album数据,如果没有找到则返回404状态码,然后我们再修改main函数中的代码
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
运行代码
再次运行代码,这次在之前GET请求的基础上修改为 http://localhost:8080/albums/1
,这表示我们想查询ID为1的album数据项,请求结果如下:
{
"id": "1",
"title": "Blue Train",
"artis": "John Coltrane",
"price": 56.99
}
完整代码
下面是本篇教程中的完整代码
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artis"`
Price float64 `json:"price"`
}
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 getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
func postAlbums(c *gin.Context) {
var newAlbum album
if err := c.BindJSON(&newAlbum); err != nil {
return
}
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not fond"})
}
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)
router.GET("/albums/:id", getAlbumByID)
router.Run("localhost:8080")
}