教程:使用 go 的 gin 和 gorm 框架来构建 RESTful API 微服务

https://learnku.com/golang/t/24598
翻译 / 3个月前 / 3277 / 2 / 更新于 3个月前 / 1 个改进

file

今天我将用 golang 编程语言来为我们的 Todo 应用搭建一套简单的 API。我将会使用 Golang 中简便 / 快捷的 gin-gonic 框架配合用于操作我们数据库,优雅漂亮的 ORM gorm 来完成这项工作。要想安装这些包,你应当在工作目录 $GOPATH/src 下运行如下命令:

  1. $ go get gopkg.in/gin-gonic/gin.v1
  2. $ go get -u github.com/jinzhu/gorm
  3. $ go get github.com/go-sql-driver/mysql

在一般的 crud 应用中我们需要如下这样的 API :

  1. POST todos/
  2. GET todos/
  3. GET todos/{id}
  4. PUT todos/{id}
  5. DELETE todos/{id}

我们开始编码吧,去到你的 $GOPATH/src 目录下,创建一个 todo 文件夹。在 todo 文件夹下创建一个名为 main.go 的文件。引入 “gin framework” 到我们的项目中,然后在 main 方法中创建一个类似下面的路由。我比较倾向把 api 前缀写成类似 “api/v1/“ 这样,这就是我们使用 Group 方法的原因。

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. )
  5. func main() {
  6. router := gin.Default()
  7. v1 := router.Group("/api/v1/todos")
  8. {
  9. v1.POST("/", createTodo)
  10. v1.GET("/", fetchAllTodo)
  11. v1.GET("/:id", fetchSingleTodo)
  12. v1.PUT("/:id", updateTodo)
  13. v1.DELETE("/:id", deleteTodo)
  14. }
  15. router.Run()
  16. }

我们已经创建了五个路由,它们对应的功能处理函数类似 createTodo, fetchAllTodo 等这样。接下来我们会讨论它们。

现在我们需要配置数据库连接。使用数据库前需要引入 gorm 和 mysql dialects 2 个软件包。 如下代码:

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "github.com/jinzhu/gorm"
  5. _ "github.com/jinzhu/gorm/dialects/mysql"
  6. )
  7. var db *gorm.DB
  8. func init() {
  9. //open a db connection
  10. var err error
  11. db, err = gorm.Open("mysql", "root:12345@/demo?charset=utf8&parseTime=True&loc=Local")
  12. if err != nil {
  13. panic("failed to connect database")
  14. }
  15. //Migrate the schema
  16. db.AutoMigrate(&todoModel{})
  17. }

上面代码中 mysql 是数据库驱动,root 是数据库用户名,12345 是密码,demo 则是数据库名称。请修改你的数据库连接信息即可。

接下来我们开始编写建立数据库连接相关的代码。首先我们创建 2 个结构体,命名为 todoModeltransformedTodo ,第一个结构体代表原始的 Todo 数据库字段,第二个结构体用来定义向 api 返回的字段。我们之所以在第二个结构体中重新定义返回的字段主要考虑到数据库中数据的安全性,我们不希望将数据库中的原始字段名(如:updated_at , created_at)直接暴露客户端。

  1. type (
  2. // 定义原始的数据库字段
  3. todoModel struct {
  4. gorm.Model
  5. Title string `json:"title"`
  6. Completed int `json:"completed"`
  7. }
  8. // 处理返回的字段
  9. transformedTodo struct {
  10. ID uint `json:"id"`
  11. Title string `json:"title"`
  12. Completed bool `json:"completed"`
  13. }
  14. )

看上述代码有同学可能会有疑问,第一个结构体中多出来一个 gorm.Model 这个额外的字段时表示什么意思呢?好吧,这里我们来解释一下,这个字段将为我们把 IDCreatedAtUpdatedAtDeletedAt 这四个字段嵌入到我们定义好的 todoModel 结构体中,一般数据表中都会用到这四个字段。

Gorm 有迁移工具 ,在调用 ‘init’ 函数初始化的时候已经初始化了。当我们运行应用程序时,它将创建一个连接然后进行迁移。

  1. //迁移 schema
  2. db.AutoMigrate(&todoModel{})

file

使用 phpmyadmin 可视化工具

你能记得我们前面提到过的五种路由方式吗?现在我们挨个去实现这五种路由方式。

当一个用户用 ‘title 和 completed’ 字段向 ‘api/v1/todos/’ 路径发送一个 PSOT 请求,它将由此路由 ‘v1.POST(「/」,createTodo)‘处理。

接下来让我们继续看一下 createTodo 方法相关的代码书写

  1. // 创建todo
  2. func createTodo(c *gin.Context) {
  3. completed, _ := strconv.Atoi(c.PostForm("completed"))
  4. todo := todoModel{Title: c.PostForm("title"), Completed: completed}
  5. db.Save(&todo)
  6. c.JSON(http.StatusCreated, gin.H{"status": http.StatusCreated, "message": "Todo item created successfully!", "resourceId": todo.ID})
  7. }

上面的代码中我们使用到了 gin 框架中的 Context 上下文来接收 POST 方式传过来的参数,利用 gorm 连接数据库来保存相关的数据到数据库,之后会给用户返回一个 resourceId

接下来让我们实现剩下的方法

  1. // fetchAllTodo 获取所有Todo
  2. func fetchAllTodo(c *gin.Context) {
  3. var todos []todoModel
  4. var _todos []transformedTodo
  5. db.Find(&todos)
  6. if len(todos) <= 0 {
  7. c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
  8. return
  9. }
  10. //对todos的属性做一些转换以构建更好的响应体
  11. for _, item := range todos {
  12. completed := false
  13. if item.Completed == 1 {
  14. completed = true
  15. } else {
  16. completed = false
  17. }
  18. _todos = append(_todos, transformedTodo{ID: item.ID, Title: item.Title, Completed: completed})
  19. }
  20. c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "data": _todos})
  21. }
  22. // fetchSingleTodo 获取单个Todo
  23. func fetchSingleTodo(c *gin.Context) {
  24. var todo todoModel
  25. todoID := c.Param("id")
  26. db.First(&todo, todoID)
  27. if todo.ID == 0 {
  28. c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
  29. return
  30. }
  31. completed := false
  32. if todo.Completed == 1 {
  33. completed = true
  34. } else {
  35. completed = false
  36. }
  37. _todo := transformedTodo{ID: todo.ID, Title: todo.Title, Completed: completed}
  38. c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "data": _todo})
  39. }
  40. // updateTodo 更新单个todo
  41. func updateTodo(c *gin.Context) {
  42. var todo todoModel
  43. todoID := c.Param("id")
  44. db.First(&todo, todoID)
  45. if todo.ID == 0 {
  46. c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
  47. return
  48. }
  49. db.Model(&todo).Update("title", c.PostForm("title"))
  50. completed, _ := strconv.Atoi(c.PostForm("completed"))
  51. db.Model(&todo).Update("completed", completed)
  52. c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "Todo updated successfully!"})
  53. }
  54. // deleteTodo 删除一个todo
  55. func deleteTodo(c *gin.Context) {
  56. var todo todoModel
  57. todoID := c.Param("id")
  58. db.First(&todo, todoID)
  59. if todo.ID == 0 {
  60. c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
  61. return
  62. }
  63. db.Delete(&todo)
  64. c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "Todo deleted successfully!"})
  65. }

fetchAllTodo 方法中,我们获取了所有的 todos 并且构建了经过转换的响应体,其中包含 id, title, completed 。我们移除了 CreatedAt, UpdatedAt, DeletedAt 字段并将 int 类型转换为 bool 类型。

到这里基本业务逻辑的代码已经完成,现在让我们试着构建程序并测试它,我将使用 Chrome 的 Postman 扩展来进行测试(你可以使用任何 REST 客户端进行测试,比如 curl)。

要构建应用程序,先打开你的终端,进入项目目录,执行如下命令:

  1. $ go build main.go

上面的命令将构建一个可执行的二进制文件 main ,你可以使用 $ ./main 命令来运行构建号的应用。 哇,我们的 todo 应用现在运行在了本地的 8080 端口了。 它将在终端显示调试日志,因为 gin 默认以 debug 模式运行在 8080 端口。

要测试 API,请先运行 Postman 并依次测试

创建一个 todo:

file

获取 todos 列表:

file

获取单个 todo:

file

更新单个 todo 内容:

file

删除一个 todo:

file

全部的源代码:

  1. package main
  2. import (
  3. "net/http"
  4. "strconv"
  5. "github.com/gin-gonic/gin"
  6. "github.com/jinzhu/gorm"
  7. _ "github.com/jinzhu/gorm/dialects/mysql"
  8. )
  9. var db *gorm.DB
  10. func init() {
  11. //创建一个数据库的连接
  12. var err error
  13. db, err = gorm.Open("mysql", "root:12345@/demo?charset=utf8&parseTime=True&loc=Local")
  14. if err != nil {
  15. panic("failed to connect database")
  16. }
  17. //迁移the schema
  18. db.AutoMigrate(&todoModel{})
  19. }
  20. func main() {
  21. router := gin.Default()
  22. v1 := router.Group("/api/v1/todos")
  23. {
  24. v1.POST("/", createTodo)
  25. v1.GET("/", fetchAllTodo)
  26. v1.GET("/:id", fetchSingleTodo)
  27. v1.PUT("/:id", updateTodo)
  28. v1.DELETE("/:id", deleteTodo)
  29. }
  30. router.Run()
  31. }
  32. type (
  33. // todoModel 包括了 todoModel 的字段类型
  34. todoModel struct {
  35. gorm.Model
  36. Title string `json:"title"`
  37. Completed int `json:"completed"`
  38. }
  39. // transformedTodo 代表格式化的 todo 结构体
  40. transformedTodo struct {
  41. ID uint `json:"id"`
  42. Title string `json:"title"`
  43. Completed bool `json:"completed"`
  44. }
  45. )
  46. // createTodo 方法添加一条新的 todo 数据
  47. func createTodo(c *gin.Context) {
  48. completed, _ := strconv.Atoi(c.PostForm("completed"))
  49. todo := todoModel{Title: c.PostForm("title"), Completed: completed}
  50. db.Save(&todo)
  51. c.JSON(http.StatusCreated, gin.H{"status": http.StatusCreated, "message": "Todo item created successfully!", "resourceId": todo.ID})
  52. }
  53. // fetchAllTodo 返回所有的 todo 数据
  54. func fetchAllTodo(c *gin.Context) {
  55. var todos []todoModel
  56. var _todos []transformedTodo
  57. db.Find(&todos)
  58. if len(todos) <= 0 {
  59. c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
  60. return
  61. }
  62. //转化 todos 数据,用来格式化
  63. for _, item := range todos {
  64. completed := false
  65. if item.Completed == 1 {
  66. completed = true
  67. } else {
  68. completed = false
  69. }
  70. _todos = append(_todos, transformedTodo{ID: item.ID, Title: item.Title, Completed: completed})
  71. }
  72. c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "data": _todos})
  73. }
  74. // fetchSingleTodo方法返回一条 todo 数据
  75. func fetchSingleTodo(c *gin.Context) {
  76. var todo todoModel
  77. todoID := c.Param("id")
  78. db.First(&todo, todoID)
  79. if todo.ID == 0 {
  80. c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
  81. return
  82. }
  83. completed := false
  84. if todo.Completed == 1 {
  85. completed = true
  86. } else {
  87. completed = false
  88. }
  89. _todo := transformedTodo{ID: todo.ID, Title: todo.Title, Completed: completed}
  90. c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "data": _todo})
  91. }
  92. // updateTodo 方法 更新 todo 数据
  93. func updateTodo(c *gin.Context) {
  94. var todo todoModel
  95. todoID := c.Param("id")
  96. db.First(&todo, todoID)
  97. if todo.ID == 0 {
  98. c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
  99. return
  100. }
  101. db.Model(&todo).Update("title", c.PostForm("title"))
  102. completed, _ := strconv.Atoi(c.PostForm("completed"))
  103. db.Model(&todo).Update("completed", completed)
  104. c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "Todo updated successfully!"})
  105. }
  106. // deleteTodo 方法依据 id 删除一条todo 数据
  107. func deleteTodo(c *gin.Context) {
  108. var todo todoModel
  109. todoID := c.Param("id")
  110. db.First(&todo, todoID)
  111. if todo.ID == 0 {
  112. c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
  113. return
  114. }
  115. db.Delete(&todo)
  116. c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "Todo deleted successfully!"})
  117. }

注意:当你使用代码生成时,你必须谨慎操作以下步骤:

  1. 不要从 todos 中查询所有数据,如: select * from todos ,使用分页
  2. 不要相信用户输入,你必须验证输入内容,这里有几种工具来验证输入。 @thedevsaddam/an-easy-way-to-validate-go-request-c15182fd11b1">阅读文章 进行验证过程
  3. 检查每个可能的错误
  4. 应根据需要来使用日志和用户认证

RESTful API 微服务 gin

*

Practice makes perfect.

原文地址:@thedevsaddam/build-restful-api-service-in-golang-using-gin-gonic-framework-85b1a6e176f3"">https://medium.com/@thedevsaddam/build-r…

译文地址:https://learnku.com/golang/t/24598

ft_authoradmin  ft_create_time2019-06-09 14:07
 ft_update_time2019-06-09 14:07