gin框架学习-Gin框架和Gorm框架搭建一个简单的API微服务

Posted lin钟一

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了gin框架学习-Gin框架和Gorm框架搭建一个简单的API微服务相关的知识,希望对你有一定的参考价值。

目录

前言

感谢开源项目gin-vue-admin,以及1010工作室的视频教程
本人学识尚浅,如有错误,请评论指出,谢谢!
详细可见个人博客:https://linzyblog.netlify.app/

本章会用到的技术,不太熟悉的可以去看看我以前的文章

  1. 快速安装gin框架:https://blog.csdn.net/weixin_46618592/article/details/125540869
  2. HTTP请求:https://blog.csdn.net/weixin_46618592/article/details/125565789
  3. Gorm入门指南:https://blog.csdn.net/weixin_46618592/article/details/125647217
  4. GORM框架进阶之CRUD接口(数据库增删改查操作):https://blog.csdn.net/weixin_46618592/article/details/125740498

一、设计思路

这章动手把前面学过的Gin框架内容和Gorm框架结合做一个简单的API 微服务

我们需要通过前端来完成对数据库数据的增删改查即可,需要如下这样的 API :

  1. POST todos/ 添加数据
  2. GET todos/ 获取数据库所有数据
  3. GET todos/id 获取指定 id 的数据
  4. PUT todos/id 修改指定 id 的数据
  5. DELETE todos/id 删除指定 id 的数据

二、编写后端请求

创建一个 todos 文件夹,用VSCode打开当前文件夹

1、在Terminal终端输入 go mod

go mod init todo

创建包管理工具,此命令会在当前目录中初始化和创建一个新的go.mod文件,手动创建go.mod文件再包含一些module声明也等同该命令,而go mod init命令便是帮我们简便操作,可以帮助我们自动创建。

2、下载并安装所需要的包

go get -u github.com/gin-gonic/gin
go get -u gorm.io/driver/mysql
go get -u gorm.io/gorm

3、创建路由

在 todo 文件夹下创建一个名为 main.go 的文件,在 main 文件中创建一个类似下面的路由,将同一个功能模块放到同一个路由分组中,代码更美观易懂

package main

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

func main() 
	router := gin.Default()
	v1 := router.Group("/api/v1/todos")
	
		v1.POST("/", createTodo)
		v1.GET("/", fetchAllTodo)
		v1.GET("/:id", fetchSingleTodo)
		v1.PUT("/:id", updateTodo)
		v1.DELETE("/:id", deleteTodo)
	
	router.Run()

4、数据库连接

现在我们要实现数据库连接就要用到GORM框架,GORM框架是go的一个数据库连接及交互框架,主要是把struct类型数据库记录进行映射,数据库语句复杂的情况下可以直接手写语句,一般用于连接关系型数据库,这里我主要使用MySQL数据库。

1)设计数据表

//数据库字段
type todoModel struct 
	gorm.Model
	Title     string `json:"title"`
	Completed int    `json:"completed"`


//处理返回字段
type transformedTodo struct 
	ID        int    `json:"id"`
	Title     string `json:"title"`
	Completed bool   `json:"completed"`

2)数据库连接


package main

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var db *gorm.DB

func init() 
	dsn := "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8&parseTime=True"
	var err error
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config)
	if err != nil 
		panic(err.Error())
	
	//自动创建数据表
	db.AutoMigrate(&todoModel)

三、实现路由方法

1、创建一条记录

使用到了 Gin 框架中的 Context 上下文来接收 POST 方式传过来的参数,用 Gorm框架 连接数据库使用Save方法或者Create方法都可以来保存相关的数据到数据库,之后会给前端用户返回一个 JSON信息。

//创建一条todo记录
func createTodo(c *gin.Context) 
	completed, err := strconv.Atoi(c.PostForm("completed"))
	if err != nil 
		panic(err.Error())
	
	todo := todoModel
		Title:     c.PostForm("title"),
		Completed: completed,
	
	db.Save(&todo)
	c.JSON(http.StatusCreated, gin.H
		"status":     http.StatusCreated,
		"message":    "Todo item created successfully",
		"resourceId": todo.ID,
	)

  • POST请求

  • 数据库

2、查询所有记录

使用到了 Gin 框架中的 GET 请求,没有参数,用 Gorm 框架连接数据库使用Find方法查询所有记录,封装在JSON里返回给前端用户

//获取数据库所有todo
func selectAllTodo(c *gin.Context) 
	var todos []todoModel
	var rq_todos []transformedTodo
	db.Find(&todos)
	if len(todos) <= 0 
		c.JSON(http.StatusNotFound, gin.H
			"status":  http.StatusNotFound,
			"message": "No todo found!",
		)
		return
	
	for _, item := range todos 
		completed := false
		if item.Completed == 1 
			completed = true
		
		rq_todos = append(rq_todos, transformedTodo
			ID:        int(item.ID),
			Title:     item.Title,
			Completed: completed,
		)
	
	c.JSON(http.StatusOK, gin.H
		"status": http.StatusOK,
		"data":   rq_todos,
	)

  • GET请求

  • 数据库

3、查找指定id的记录

使用到了 Gin 框架中的 Context 上下文来接收 GET 方式存放在URI里传过来的参数,用 Gorm 框架连接数据库使用Find方法或者First方法查询指定记录,封装在JSON里返回给前端用户

//查找指定todo
func selectSingleTodo(c *gin.Context) 
	var todo todoModel
	todoID := c.Param("id")
	db.First(&todo, todoID)
	if todo.ID == 0 
		c.JSON(http.StatusNotFound, gin.H
			"status":  http.StatusNotFound,
			"message": "No todo found!",
		)
		return
	
	completed := false
	if todo.Completed == 1 
		completed = true
	
	rp_todo := transformedTodo
		ID:        int(todo.ID),
		Title:     todo.Title,
		Completed: completed,
	
	c.JSON(http.StatusOK, gin.H
		"status": http.StatusOK,
		"data":   rp_todo,
	)

  • GET请求

4、修改指定id记录

使用到了 Gin 框架中的 Context 上下文来接收 PUT 方式传过来的参数,用 Gorm 框架连接数据库使用Find方法或者First方法查询指定记录,然后通过Update方法修改记录,返回给前端用户修改成功信息。

//修改指定id todo
func updateTodo(c *gin.Context) 
	var todo todoModel
	todoID := c.Param("id")
	db.First(&todo, todoID)
	if todo.ID == 0 
		c.JSON(http.StatusNotFound, gin.H
			"status":  http.StatusNotFound,
			"message": "No todo found!",
		)
		return
	
	completed, _ := strconv.Atoi(c.PostForm("completed"))
	db.Model(&todo).Updates(&todoModel
		Title:     c.PostForm("title"),
		Completed: completed,
	)
	c.JSON(http.StatusOK, gin.H
		"status":  http.StatusOK,
		"message": "Todo updated successfully",
	)

  • PUT请求

  • 数据库

5、删除指定id记录

使用到了 Gin 框架中的 Context 上下文来接收 DELETE 方式存放在URI里传过来的参数,用 Gorm 框架连接数据库使用First方法或者Find查询指定id记录,用Delete删除指定id记录,返回给前端用户删除成功信息。

//删除指定id todo
func deleteTodo(c *gin.Context) 
	var todo todoModel
	todoID := c.Param("id")
	db.First(&todo, todoID)
	if todo.ID == 0 
		c.JSON(http.StatusNotFound, gin.H
			"status":  http.StatusNotFound,
			"message": "No todo found!",
		)
		return
	
	db.Delete(&todo)
	c.JSON(http.StatusOK, gin.H
		"status":  http.StatusOK,
		"message": "todo Deleted successfully",
	)

  • DELETE请求

  • 数据库

四、结论

这章只是把前面的基础的知识Gin框架和Gorm框架结合一起实现,日志、JWT认证和Casbin框架我还没用到,后面我将会学习gva框架把之前所学全部展现并结合出来

如何使用 Gin 和 Gorm 搭建一个简单的 API 服务

创建 API

  我们之前已经跑过 Gin 框架的代码,现在是时候加些功能进去了。

读取全部信息

  我们先从"增删改查"中的"查"入手,查询我们之前添加的信息。我接下来要删除几行代码,并把 Gin 的框架代码加回来。

package main

import (
        "fmt"
        "github.com/gin-gonic/gin"
        "github.com/jinzhu/gorm"
        _ "github.com/jinzhu/gorm/dialects/sqlite"
)

var db *gorm.DB
var err error

type Person struct {
        ID        uint   `json:"id”`
        FirstName string `json:"firstname”`
        LastName  string `json:"lastname”`
}

func main() {
        // NOTE: See we’re using = to assign the global var
        // instead of := which would assign it only in this function
        db, err = gorm.Open("sqlite3", "./gorm.db")
        if err != nil {
                fmt.Println(err)
        }
        defer db.Close()
        db.AutoMigrate(&Person{})
        r := gin.Default()
        r.GET("g/", GetProjects)
        r.Run("g:8080")
}

func GetProjects(c *gin.Context) {
        var people []Person
        if err := db.Find(&people).Error; err != nil {
                c.AbortWithStatus(404)
                fmt.Println(err)
        } else {
                c.JSON(200, people)
        }
}

  那么运行程序,并在浏览器中访问 http://localhost:8080,你应该看到:

[{“id”: 1,”firstname”: “John”,”lastname”: “Doe”}]

  喔,几行代码我们就可以拿到 API 服务器的响应了,而且大部分代码都是用来错误处理的。

读取特定信息

  好,为了把 API 接口写的更符合 REST 规范,我们加入查询特定信息的借口

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

var db *gorm.DB
var err error

type Person struct {
    ID        uint   `json:"id”`
    FirstName string `json:"firstname”`
    LastName  string `json:"lastname”`
}

func main() {
    // NOTE: See we’re using = to assign the global var
    // instead of := which would assign it only in this function
    db, err = gorm.Open("sqlite3", "./gorm.db")
    if err != nil {
        fmt.Println(err)
    }
    defer db.Close()
    db.AutoMigrate(&Person{})
    r := gin.Default()

    r.GET("g/", GetProjects)
    r.GET("/people/:id", GetPerson)

    r.Run("g:8080")
}

func GetProjects(c *gin.Context) {
    var people []Person
    if err := db.Find(&people).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    } else {
        c.JSON(200, people)
    }
}

func GetPerson(c *gin.Context) {
    id := c.Params.ByName("id")
    var person Person
    if err := db.Where("id = ?", id).First(&person).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    } else {
        c.JSON(200, person)
    }
}

  现在运行程序,但请注意,如果要访问全部信息,你需要访问的地址变成了 http://localhost:8080/people/ ,如果 在 URL 的末尾加入了 ID,你就会得到特定的信息 http://localhost:8080/people/1

{"id": 1, "firstname": "John", "lastname": "Doe"}

添加信息

  只有一条记录是看不大出来查询全部信息和查询单条信息的区别的,所以咱们来把添加信息的功能加上吧。

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

var db *gorm.DB
var err error

type Person struct {
    ID        uint   `json:"id”`
    FirstName string `json:"firstname”`
    LastName  string `json:"lastname”`
}

func main() {
    // NOTE: See we’re using = to assign the global var
    // instead of := which would assign it only in this function
    db, err = gorm.Open("sqlite3", "./gorm.db")
    if err != nil {
        fmt.Println(err)
    }
    defer db.Close()
    db.AutoMigrate(&Person{})
    r := gin.Default()

    r.GET("g/", GetProjects)
    r.GET("/people/:id", GetPerson)
    r.POST("/people", CreatePerson)

    r.Run("g:8080")
}

func GetProjects(c *gin.Context) {
    var people []Person
    if err := db.Find(&people).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    } else {
        c.JSON(200, people)
    }
}

func GetPerson(c *gin.Context) {
    id := c.Params.ByName("id")
    var person Person
    if err := db.Where("id = ?", id).First(&person).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    } else {
        c.JSON(200, person)
    }
}

func CreatePerson(c *gin.Context) {
    var person Person
    c.BindJSON(&person)
    db.Create(&person)
    c.JSON(200, person)
}

  接下来让我们从终端运行 curl 命令测试一下新加的功能是不是可用,当然还是先要把程序运行起来。
  在终端运行:

$ curl -i -X POST http://localhost:8080/people -d ‘{ "FirstName": "Elvis", "LastName": "Presley"}‘

  应该会看到成功的响应消息:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 03 Dec 2016 00:14:06 GMT
Content-Length: 50
{"id":2,"firstname":"Elvis","lastname":"Presley"}

  现在我们访问一下查询全部信息的接口,http://localhost:8080/people/

[{"id": 1,"firstname": "John","lastname": "Doe"},{"id": 2,"firstname": "Elvis","lastname": "Presley"}]

  太棒啦,代码没问题。这回我们只发送 Person 结构体的部分信息,看看程序会如何处理。

$ curl -i -X POST http://localhost:8080/people -d ‘{ "FirstName": "Madison"}‘

  刷新一下浏览器,发现只添加了我们发送的信息。

[{"id": 1,"firstname": "John","lastname": "Doe"},{"id": 2,"firstname": "Elvis","lastname": "Presley"},{"id": 3,"firstname": "Madison","lastname": ""}]

  这就是 Gin 如何工作的了,留意一下 c.BindJSON(&person) 这行,它会自动匹配请求消息中的数据信息。
  虽然请求消息里可能缺某些信息,就比如刚才那个例子,而且大小写不匹配也没有关系,Gin 的容错性非常高。非常简单!

更新信息

  我们不能把 Madison 这条记录没有姓氏啊,是时候加入更新功能了。

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

var db *gorm.DB
var err error

type Person struct {
    ID        uint   `json:"id"`
    FirstName string `json:"firstname"`
    LastName  string `json:"lastname"`
}

func main() {
    // NOTE: See we‘re using = to assign the global var
    // instead of := which would assign it only in this function
    db, err = gorm.Open("sqlite3", "./gorm.db")
    if err != nil {
        fmt.Println(err)
    }
    defer db.Close()
    db.AutoMigrate(&Person{})
    r := gin.Default()

    r.GET("g/", GetProjects)
    r.GET("/people/:id", GetPerson)
    r.POST("/people", CreatePerson)
    r.PUT("/people/:id", UpdatePerson)

    r.Run("g:8080")
}

func GetProjects(c *gin.Context) {
    var people []Person
    if err := db.Find(&people).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    } else {
        c.JSON(200, people)
    }
}

func GetPerson(c *gin.Context) {
    id := c.Params.ByName("id")
    var person Person
    if err := db.Where("id = ?", id).First(&person).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    } else {
        c.JSON(200, person)
    }
}

func CreatePerson(c *gin.Context) {
    var person Person
    c.BindJSON(&person)
    db.Create(&person)
    c.JSON(200, person)
}

func UpdatePerson(c *gin.Context) {
    var person Person
    id := c.Params.ByName("id")
    if err := db.Where("id = ?", id).First(&person).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    }
    c.BindJSON(&person)
    db.Save(&person)
    c.JSON(200, person)
}

  这次我们用类似的 curl 命令 进行测试,但不同的是用 PUT 方法,而且是用在特定的信息上。

$ curl -i -X PUT http://localhost:8080/people/3 -d ‘{ "FirstName": "Madison", "LastName":"Sawyer" }‘
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 03 Dec 2016 00:25:35 GMT
Content-Length: 51
{"id":3,"firstname":"Madison","lastname":"Sawyer"}

  当然更新浏览器后,我们就可以看见 "sawyer" 添加到了 "LastName" 一栏里。

[{"id": 1,"firstname": "John","lastname": "Doe"},{"id": 2,"firstname": "Elvis","lastname": "Presley"},{"id": 3,"firstname": "Madison","lastname": "Sawyer"}]

  这次我们只更新 "FirstName" 字段试试。

$ curl -i -X PUT http://localhost:8080/people/3 -d ‘{ "FirstName": "Tom" }‘

  显示如下

[{"id": 1,"firstname": "John","lastname": "Doe"},{"id": 2,"firstname": "Elvis","lastname": "Presley"},{"id": 3,"firstname": "Tom","lastname": "Sawyer"}]

删除

  这次轮到删除功能了

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

var db *gorm.DB
var err error

type Person struct {
    ID        uint   `json:"id"`
    FirstName string `json:"firstname"`
    LastName  string `json:"lastname"`
}

func main() {
    // NOTE: See we‘re using = to assign the global var
    // instead of := which would assign it only in this function
    db, err = gorm.Open("sqlite3", "./gorm.db")
    if err != nil {
        fmt.Println(err)
    }
    defer db.Close()
    db.AutoMigrate(&Person{})
    r := gin.Default()

    r.GET("g/", GetProjects)
    r.GET("/people/:id", GetPerson)
    r.POST("/people", CreatePerson)
    r.PUT("/people/:id", UpdatePerson)
    r.DELETE("/people/:id", DeletePerson)

    r.Run("g:8080")
}

func GetProjects(c *gin.Context) {
    var people []Person
    if err := db.Find(&people).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    } else {
        c.JSON(200, people)
    }
}

func GetPerson(c *gin.Context) {
    id := c.Params.ByName("id")
    var person Person
    if err := db.Where("id = ?", id).First(&person).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    } else {
        c.JSON(200, person)
    }
}

func CreatePerson(c *gin.Context) {
    var person Person
    c.BindJSON(&person)
    db.Create(&person)
    c.JSON(200, person)
}

func UpdatePerson(c *gin.Context) {
    var person Person
    id := c.Params.ByName("id")
    if err := db.Where("id = ?", id).First(&person).Error; err != nil {
        c.AbortWithStatus(404)
        fmt.Println(err)
    }
    c.BindJSON(&person)
    db.Save(&person)
    c.JSON(200, person)
}

func DeletePerson(c *gin.Context) {
    id := c.Params.ByName("id")
    var person Person
    d := db.Where("id = ?", id).Delete(&person)
    fmt.Println(d)
    c.JSON(200, gin.H{"id #" + id: "deleted"})
}

  我们用 curl 的 Delete 方法测试一下

$ curl -i -X DELETE http://localhost:8080/people/1

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 03 Dec 2016 00:32:40 GMT
Content-Length: 20

{"id #1":"deleted"}

  刷新浏览器,John Doe 这条记录已经删掉了。

[{"id": 2,"firstname": "Elvis","lastname": "Presley"},{"id": 3,"firstname": "Tom","lastname": "Sawyer"}]
 
 
 
 



作者:blackpiglet
链接:https://www.jianshu.com/p/443766f0e796
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

以上是关于gin框架学习-Gin框架和Gorm框架搭建一个简单的API微服务的主要内容,如果未能解决你的问题,请参考以下文章

Casbin + Gin + Gorm 学习探索

Gin框架快速入门

Gin框架快速入门

Golangg gin框架学习

基于gin web框架搭建RESTful API服务

基于 Gin 进行模块化设计的 API 框架,致力于进行快速的业务研发