您现在的位置是:网站首页> 编程资料编程资料

Gin与Mysql实现简单Restful风格API实战示例详解_Golang_

2023-05-26 499人已围观

简介 Gin与Mysql实现简单Restful风格API实战示例详解_Golang_

我们已经了解了Golang的Gin框架。对于Webservice服务,restful风格几乎一统天下。Gin也天然的支持restful。下面就使用gin写一个简单的服务,麻雀虽小,五脏俱全。我们先以一个单文件开始,然后再逐步分解模块成包,组织代码。

It works

使用Gin的前提是安装,我们需要安装gin和mysql的驱动,具体的安装方式就不在赘述。

参考Golang 微框架Gin简介Golang持久化

创建一个文件夹用来为项目,新建一个文件main.go:

 ☁ newgin tree . └── main.go

main.go

 package main import ( "gopkg.in/gin-gonic/gin.v1" "net/http" ) func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "It works") }) router.Run(":8000") }

编译运行

 ☁ newgin go run main.go [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET / --> main.main.func1 (3 handlers) [GIN-debug] Listening and serving HTTP on :8000 

访问 /即可看见我们返回的字串It works

数据库

安装完毕框架,完成一次请求响应之后。接下来就是安装数据库驱动和初始化数据相关的操作了。首先,我们需要新建数据表。一个及其简单的数据表:

 CREATE TABLE `person` ( `id` int(11) NOT NULL AUTO_INCREMENT, `first_name` varchar(40) NOT NULL DEFAULT '', `last_name` varchar(40) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建数据表之后,初始化数据库连接池:

 func main() { db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true") if err != nil{ log.Fatalln(err) } defer db.Close() db.SetMaxIdleConns(20) db.SetMaxOpenConns(20) if err := db.Ping(); err != nil{ log.Fatalln(err) } router := gin.Default() router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "It works") }) router.Run(":8000") }

使用sql.Open方法会创建一个数据库连接池db。这个db不是数据库连接,它是一个连接池,只有当真正数据库通信的时候才创建连接。例如这里的db.Ping的操作。db.SetMaxIdleConns(20)db.SetMaxOpenConns(20)分别设置数据库的空闲连接和最大打开连接,即向Mysql服务端发出的所有连接的最大数目。

如果不设置,默认都是0,表示打开的连接没有限制。我在压测的时候,发现会存在大量的TIME_WAIT状态的连接,虽然mysql的连接数没有上升。设置了这两个参数之后,不在存在大量TIME_WAIT状态的连接了。而且qps也没有明显的变化,出于对数据库的保护,最好设置这连个参数。

CURD 增删改查

Restful的基本就是对资源的curd操作。下面开启我们的第一个api接口,增加一个资源。

 func main() { ... router.POST("/person", func(c *gin.Context) { firstName := c.Request.FormValue("first_name") lastName := c.Request.FormValue("last_name") rs, err := db.Exec("INSERT INTO person(first_name, last_name) VALUES (?, ?)", firstName, lastName) if err != nil { log.Fatalln(err) } id, err := rs.LastInsertId() if err != nil { log.Fatalln(err) } fmt.Println("insert person Id {}", id) msg := fmt.Sprintf("insert successful %d", id) c.JSON(http.StatusOK, gin.H{ "msg": msg, }) }) ... } 

执行非query操作,使用db的Exec方法,在mysql中使用?做占位符。最后我们把插入后的id返回给客户端。请求得到的结果如下:

 ☁ ~ curl -X POST http://127.0.0.1:8000/person -d "first_name=hello&last_name=world" | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 62 100 30 100 32 5054 5391 --:--:-- --:--:-- --:--:-- 6400 { "msg": "insert successful 1" }

下面可以随意增加几条记录。

查询列表 Query

上面我们增加了一条记录,下面就获取这个记录,查一般有两个操作,一个是查询列表,其次就是查询具体的某一条记录。两种大同小异。

为了给查询结果绑定到golang的变量或对象,我们需要先定义一个结构来绑定对象。在main函数的上方定义Person结构:

 type Person struct { Id int `json:"id" form:"id"` FirstName string `json:"first_name" form:"first_name"` LastName string `json:"last_name" form:"last_name"` }

然后查询我们的数据列表

 router.GET("/persons", func(c *gin.Context) { rows, err := db.Query("SELECT id, first_name, last_name FROM person") if err != nil { log.Fatalln(err) } defer rows.Close() persons := make([]Person, 0) for rows.Next() { var person Person rows.Scan(&person.Id, &person.FirstName, &person.LastName) persons = append(persons, person) } if err = rows.Err(); err != nil { log.Fatalln(err) } c.JSON(http.StatusOK, gin.H{ "persons": persons, }) })

读取mysql的数据需要有一个绑定的过程,db.Query方法返回一个rows对象,这个数据库连接随即也转移到这个对象,因此我们需要定义row.Close操作。然后创建一个[]Person的切片。

使用make,而不是直接使用var persons []Person的声明方式。还是有所差别的,使用make的方式,当数组切片没有元素的时候,Json会返回[]。如果直接声明,json会返回null

接下来就是使用rows对象的Next方法,遍历所查询的数据,一个个绑定到person对象上,最后append到persons切片。

 ☁ ~ curl http://127.0.0.1:8000/persons | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 113 100 113 0 0 101k 0 --:--:-- --:--:-- --:--:-- 110k { "persons": [ { "first_name": "hello", "id": 1, "last_name": "world" }, { "first_name": "vanyar", "id": 2, "last_name": "elves" } ] }

查询单条记录 QueryRow

查询列表需要使用迭代rows对象,查询单个记录,就没这么麻烦了。虽然也可以迭代一条记录的结果集。因为查询单个记录的操作实在太常用了,因此golang的database/sql也专门提供了查询方法

 router.GET("/person/:id", func(c *gin.Context) { id := c.Param("id") var person Person err := db.QueryRow("SELECT id, first_name, last_name FROM person WHERE id=?", id).Scan( &person.Id, &person.FirstName, &person.LastName, ) if err != nil { log.Println(err) c.JSON(http.StatusOK, gin.H{ "person": nil, }) return } c.JSON(http.StatusOK, gin.H{ "person": person, }) })

查询结果为:

 ☁ ~ curl http://127.0.0.1:8000/person/1 | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 60 100 60 0 0 20826 0 --:--:-- --:--:-- --:--:-- 30000 { "person": { "first_name": "hello", "id": 1, "first_name": "world" } }

查询单个记录有一个小问题,当数据不存在的时候,同样也会抛出一个错误。粗暴的使用log退出有点不妥。返回一个nil的时候,万一真的是因为错误,比如sql错误。这种情况如何解决。还需要具体场景设计程序。

增删改查,下面进行更新的操作。前面增加记录我们使用了urlencode的方式提交,更新的api我们自动匹配绑定content-type

 router.PUT("/person/:id", func(c *gin.Context) { cid := c.Param("id") id, err := strconv.Atoi(cid) person := Person{Id: id} err = c.Bind(&person) if err != nil { log.Fatalln(err) } stmt, err := db.Prepare("UPDATE person SET first_name=?, last_name=? WHERE id=?") defer stmt.Close() if err != nil { log.Fatalln(err) } rs, err := stmt.Exec(person.FirstName, person.LastName, person.Id) if err != nil { log.Fatalln(err) } ra, err := rs.RowsAffected() if err != nil { log.Fatalln(err) } msg := fmt.Sprintf("Update person %d successful %d", person.Id, ra) c.JSON(http.StatusOK, gin.H{ "msg": msg, }) }) 

使用 urlencode的方式更新:

 ☁ ~ curl -X PUT http://127.0.0.1:8000/person/2 -d "first_name=noldor&last_name=elves" | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 72 100 39 100 33 3921 3317 --:--:-- --:--:-- --:--:-- 4333 { "msg": "Update person 2 successful 1" }

使用json的方式更新:

 ☁ ~ curl -X PUT http://127.0.0.1:8000/person/2 -H "Content-Type: application/json" -d '{"first_name": "vanyar", "last_name": "elves"}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 85 100 39 100 46 4306 5079 --:--:-- --:--:-- --:--:-- 5750 { "msg": "Update person 2 successful 1" }

最后一个操作就是删除了,删除所需要的功能特性,上面的例子都覆盖了。实现删除也就特别简单了:

 router.DELETE("/person/:id", func(c *gin.Context) { cid := c.Param("id") id, err := strconv.Atoi(cid) if
                
                

-六神源码网