长期认识并不会日积月累地成为恋爱,好比冬季每天的气候罢,你没法把今天的温度加在昨天的上面,好等明天积累成个和暖的春日。 —— 《围城》
前言
PS:为方便阅读,文末会附上最终代码,代码实现过程会在文章中体现。
2023.05.16 厚厚的云层挂在天上,挡住太阳,微风吹拂,悠然漂浮。令人悠闲、陶醉,在此情景,不学习点东西,岂不是愧对自己。
在迫切学习的心情中,开启浏览器,看了无数篇 Gin + Gorm 的文章,再无法抑制心里的冲动,于是开始落笔。
希望这篇能带给你学习的快乐,哈哈哈!
What is Gin?
-
Gin
是一种使用Go编程语言编写的Web框架,它基于HTTP/2协议设计,具有高效、快速、轻量级等特点。 -
Gin提供了路由、中间件、模板渲染、错误处理等基本功能,同时还支持插件机制,可以方便地扩展和定制。
-
Gin的设计目标是提供一个高性能的Web框架,它通过使用Go语言的高并发和协程特性,以及HTTP/2协议的优化,使得它可以处理高并发和大规模请求,并且响应速度快、资源占用少,适合用于构建高性能、可伸缩的Web应用程序。
因此,在Go语言社区中,Gin是一个非常受欢迎的Web框架之一。
Hello Gin
安装
在使用前,先要下载 Gin 包,然后在项目中导入
go get -u github.com/gin-gonic/gin
创建一个 main.go 文件,在文件中引入 Gin import "github.com/gin-gonic/gin"
基本路由
每一个接口,或者每一个页面都是一段 url 访问,要让对应的路由访问对应的方法或者页面,这里需要我们自己写逻辑代码,以常用的 POST
和 GET
为例:
func main() {
ginServer := gin.Default()
// GET方法
ginServer.GET("/getGin", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello Gin!",
})
})
// POST方法
ginServer.POST("/postGin", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello Gin!",
})
})
_ = ginServer.Run("127.0.0.1:8080") // 监听并在 0.0.0.0:8080 上启动服务
}
编码完成后,使用 go run main.go
执行代码,然后使用curl
工具模拟请求:
可以看到结果,返回了对应的数据。但是往往在使用到接口的时候,都是一个交互性,所以需要有数据的交互,也就是接受数据,返回数据(这里返回的数据是定时的数据,暂时不考虑数据库,后面说到 gorm 会完善
)。
接下来就是如何接受POST
,GET
上带的参数了。
解析url上的参数
在 Gin
官网中提供了内置方法 Query
和 PostForm
,分别用来解析 url 上拼接的数据与 POST 请求的数据体。将 POST
方法内容修改为:
ginServer.POST("/postGin", func(c *gin.Context) {
id := c.Query("id")
page := c.DefaultQuery("page", "0")
name := c.PostForm("name")
message := c.PostForm("message")
result := map[string]interface{}{
"id": id,
"page": page,
"name": name,
"message": message,
}
c.JSON(200, gin.H{
"data": result,
})
})
接下来使用 curl -X POST --data-urlencode "name=陪我去看海吧" --data-urlencode "message=Hello Gin" "http://127.0.0.1:8080/postGin?id=1"
模拟请求,有url拼接的数据 id,也有请求体中的数据 name 和 message。
可以看到,我们传递过去的参数都成功的被解析了。
到这里,基本的用法已经掌握了,现在就像一首美妙的乐曲,音符跳跃,旋律优美,但总觉得缺少了一段高潮,那一段令人心悸的瞬间。那就是少了交互,而不是自己写这些死数据,我们要赋予数据活力,让他成为真正的数据,所以需要使用数据库,将数据存入数据库。
Q:如何连接数据库呢?
Gorm:
有我呢!
What is Gorm?
GORM
是一个用于Go语言的ORM库
,它提供了一种简单且强大的方式来与数据库进行交互。通过使用GORM,您可以在Go应用程序中使用结构体(structs)来表示数据库表,并使用方法和查询语法来执行各种数据库操作。这样,您可以通过简洁的代码完成与数据库的交互,而无需手动编写大量的SQL语句。—— 来自ChatGPT
安装
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
初始化连接
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
func main() {
dsn := "user:password@tcp(127.0.0.1:3306)/dbName?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
if err != nil {
panic("failed to connect database")
}
}
在将 user
、password
、dbName
都替换成你自己的之后,执行main.go
文件,即可连接成功。
先说说这里面配置项的作用。
各配置项作用
-
charset=utf8mb4
: 这个选项指定了数据库连接的字符集。utf8mb4
是一种字符编码,支持存储和处理 Unicode 字符,包括一些特殊的表情符号和符号。 -
parseTime=True
: 这个选项告诉 GORM 将数据库中的时间类型字段解析为 Go 的time.Time
类型。默认情况下,MySQL
将时间字段作为字符串返回,而设置parseTime=True
可以让 GORM 自动将其转换为Go
的时间类型,便于在应用程序中进行处理和操作。 -
loc=Local
: 这个选项指定了时区。在数据库操作中,时区的设置对于正确地处理时间非常重要。通过将时区设置为Local
,GORM 将使用本地系统时间,就不会出现8小时时差的问题。 -
SingularTable: true
: 告诉 GORM 使用单数形式的表名。如果我们有一个名为User
的模型,GORM 将使用表名user
来进行数据库查询,而不是默认的users
。如果不设置,他每次查表的时候,这个名字会变成复数也就是会改变查询表名,可能会查不到表而报错。
CRUD 实现
CRUD
是 Create
(创建)、Retrieve
(读取)、Update
(更新)和 Delete
(删除)的缩写,它是用于描述对于持久化存储系统(如数据库)执行基本操作的常见术语。
Create(增)
接口实现:
// 新增
ginServer.POST("/label/add", func(c *gin.Context) {
var label Label
err := c.ShouldBindJSON(&label)
fmt.Printf("参数:%v \n", label)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"message": "添加失败",
"code": "400",
"data": gin.H{},
})
} else {
db.Create(&label)
c.JSON(http.StatusOK, gin.H{
"message": "添加成功",
"code": "200",
"data": label,
})
}
})
curl
命令测试接口:
这里可以在你对应的数据库表里看见数据已经被添加进去了,在这里多添加几条,方便后面的测试。
Retrieve(查)
接口实现:
// 查询
ginServer.GET("/label/getAll", func(c *gin.Context) {
var labels []Label
result := db.Find(&labels)
fmt.Printf("数据:%v \n", labels)
// 查询条数,如果为0,则说明表为空
if result.Error == nil {
c.JSON(http.StatusOK, gin.H{
"message": "查询成功",
"code": 200,
"data": labels,
})
} else {
c.JSON(http.StatusBadGateway, gin.H{
"message": "查询报错",
"code": 500,
"data": gin.H{},
})
}
})
curl
接口测试:
Update(改)
接口实现:
// 修改
ginServer.PUT("/label/update/:id", func(c *gin.Context) {
var label Label
id := c.Param("id")
findResult := db.First(&label, id)
if findResult.RowsAffected > 0 {
err := c.ShouldBindJSON(&label)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"message": "修改失败",
"code": 400,
"data": gin.H{},
})
} else {
db.Where("id = ?", id).Updates(&label)
c.JSON(http.StatusOK, gin.H{
"message": "修改成功",
"code": 200,
"data": label,
})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{
"message": "没有匹配的id",
"code": 400,
"data": gin.H{},
})
}
})
curl
测试接口:
Delete(删)
接口实现:
// 删除
ginServer.DELETE("/label/delete/:id", func(c *gin.Context) {
var label Label
id := c.Param("id")
deleteResult := db.Delete(&label, id)
if deleteResult.RowsAffected > 0 {
c.JSON(http.StatusOK, gin.H{
"message": "删除成功",
"code": 200,
"data": gin.H{},
})
} else {
c.JSON(http.StatusBadRequest, gin.H{
"message": "无该条数据,删除失败",
"code": 400,
"data": gin.H{},
})
}
})
curl
测试接口:
整体代码
// main.go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"net/http"
)
func main() {
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
if err != nil {
panic("failed to connect database")
}
type Label struct {
ID uint `gorm:"primaryKey"`
Label string `gorm:"column:label_title"`
}
// 接口
ginServer := gin.Default()
// 新增
ginServer.POST("/label/add", func(c *gin.Context) {
var label Label
err := c.ShouldBindJSON(&label)
fmt.Printf("参数:%v \n", label)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"message": "添加失败",
"code": "400",
"data": gin.H{},
})
} else {
db.Create(&label)
c.JSON(http.StatusOK, gin.H{
"message": "添加成功",
"code": "200",
"data": label,
})
}
})
// 删除
ginServer.DELETE("/label/delete/:id", func(c *gin.Context) {
var label Label
id := c.Param("id")
deleteResult := db.Delete(&label, id)
if deleteResult.RowsAffected > 0 {
c.JSON(http.StatusOK, gin.H{
"message": "删除成功",
"code": 200,
"data": gin.H{},
})
} else {
c.JSON(http.StatusBadRequest, gin.H{
"message": "无该条数据,删除失败",
"code": 400,
"data": gin.H{},
})
}
})
// 修改
ginServer.PUT("/label/update/:id", func(c *gin.Context) {
var label Label
id := c.Param("id")
findResult := db.First(&label, id)
if findResult.RowsAffected > 0 {
err := c.ShouldBindJSON(&label)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"message": "修改失败",
"code": 400,
"data": gin.H{},
})
} else {
db.Where("id = ?", id).Updates(&label)
c.JSON(http.StatusOK, gin.H{
"message": "修改成功",
"code": 200,
"data": label,
})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{
"message": "没有匹配的id",
"code": 400,
"data": gin.H{},
})
}
})
// 查询
ginServer.GET("/label/getAll", func(c *gin.Context) {
var labels []Label
result := db.Find(&labels)
fmt.Printf("数据:%v \n", labels)
// 查询条数,如果为0,则说明表为空
if result.Error == nil {
c.JSON(http.StatusOK, gin.H{
"message": "查询成功",
"code": 200,
"data": labels,
})
} else {
c.JSON(http.StatusBadGateway, gin.H{
"message": "查询报错",
"code": 500,
"data": gin.H{},
})
}
})
_ = ginServer.Run("127.0.0.1:8080") // 监听并在 0.0.0.0:8080 上启动服务
}
为了方便,所有的代码都放在了 main.go
中。
总结
- 在
gin
写接口非常简洁,内置了POST、GET、PUT、DELETE
等方法 - 在
gorm
中可以使用封装的方法简化sql
语句,也可以使用原生sql
语句来执行 - 在
gorm
中每一个结构体都可以表示一张表结构 curl
可以用来请求web服务器,功能很强大,使用熟练,在一些小地方调试更快,更方便
结束语
交谈终至,以文字相遇,也以文字结束。感谢相遇,文字对面的独一无二的你!