连接数据库
这里我们使用的是MySQL数据库,如果不会安装MySQL或者懒得安装MySQL,可以使用一下命令快速运行一个MySQL8.0.19实例,当然前提是你要有docker环境…
在本地的13306端口运行一个名为mysql8019,root用户名密码为root1234的MySQL容器环境:
docker run --name mysql8019 -p 13306:3306 -e MYSQL_ROOT_PASSWORD=root1234 -d mysql:8.0.19
在另外启动一个MySQL Client连接上面的MySQL环境,密码为上一步指定的密码root1234:
docker run -it --network host --rm mysql mysql -h 127.0.0.1 -P 13306 --default-character-set=utf8mb4 -uroot -p
在使用GORM前手动创建数据库mydb:
create database mydb;
首先引入一下GORM中MySQL的包。
_ "github.com/jinzhu/gorm/dialects/mysql"
前面加下划线是因为只使用了其中的一部分,不加的话会报错。
定义一个全局的DB变量,方便后续的使用。
var (
DB *gorm.DB
)
初始化数据库方法:
func initMySQL() (err error) {
dsn := "root:root1234@(127.0.0.1:13306)/mydb?charset=utf8mb4&parseTime=True&loc=Local"
DB, err = gorm.Open("mysql", dsn)
if err != nil {
return
}
return DB.DB().Ping()
}
在main函数中调用initMySQL方法。
err := initMySQL()
if err != nil {
panic(err)
}
defer DB.Close()
连接好数据库之后要进行模型的绑定,也就是让代码中的结构体(模型)能够对应数据库中的表,绑定完成后,GORM会自动在数据库创建一个名叫'todos'的表(自动给我们的结构体名加了复数),这个操作使用下面这行代码进行实现。
//模型绑定
DB.AutoMigrate(&Todo{})
完整代码:
package main
import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"net/http"
)
var (
DB *gorm.DB
)
// Todo model
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Status bool `json:"status"`
}
func initMySQL() (err error) {
dsn := "root:root1234@(127.0.0.1:13306)/mydb?charset=utf8mb4&parseTime=True&loc=Local"
DB, err = gorm.Open("mysql", dsn)
if err != nil {
return
}
return DB.DB().Ping()
}
func main() {
//创建数据库
//SQL: create database mydb;
//连接数据库
err := initMySQL()
if err != nil {
panic(err)
}
defer DB.Close()
//模型绑定
DB.AutoMigrate(&Todo{})
r := gin.Default()
//告诉gin框架模板文件引用的静态文件去哪里找
r.Static("/static", "static")
//告诉Gin框架去哪找模板文件
r.LoadHTMLGlob("templates/*") //其中/*表示templates文件夹下的所有文件
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})
//v1
v1Group := r.Group("v1")
{
//待办事项
//添加
v1Group.POST("/todo", func(c *gin.Context) {
})
//查看
v1Group.GET("/todo", func(c *gin.Context) {
})
//修改
v1Group.PUT("/todo/:id", func(c *gin.Context) {
})
//删除
v1Group.DELETE("/todo/:id", func(c *gin.Context) {
})
}
r.Run()
}
ok,到这里我们就完成了数据库的连接和模型的绑定,现在我们可以开始对功能进行实现了。
功能实现
添加
v1Group.POST("/todo", func(c *gin.Context) {
//前端页面填写待办事项,点击提交,会发请求到这里
//1、从请求中把数据拿出来
var todo Todo
c.BindJSON(&todo)
//2、存入数据库
//3、返回响应
if err = DB.Create(&todo).Error;err != nil {
c.JSON(http.StatusOK,gin.H{"error" : err.Error()})
return
}else {
c.JSON(http.StatusOK,todo)
}
})
查看
v1Group.GET("/todo", func(c *gin.Context) {
//查询todo这个表里的所有数据
var todoList []Todo
if err = DB.Find(&todoList).Error;err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
}else {
c.JSON(http.StatusOK,todoList)
}
})
修改
v1Group.PUT("/todo/:id", func(c *gin.Context) {
//获取路径参数
id, ok := c.Params.Get("id")
if !ok {
c.JSON(http.StatusOK, gin.H{"error": "无效id"})
return
}
var todo Todo
if err = DB.Where("id=?", id).First(&todo).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
}
c.BindJSON(&todo)
if err = DB.Save(&todo).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, todo)
}
})
删除
v1Group.DELETE("/todo/:id", func(c *gin.Context) {
//获取路径参数
id, ok := c.Params.Get("id")
if !ok {
c.JSON(http.StatusOK, gin.H{"error": "无效id"})
return
}
if err = DB.Where("id=?", id).Delete(Todo{}).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
} else {
c.JSON(http.StatusOK, gin.H{id: "删除成功"})
}
})
项目完整代码:
package main
import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"net/http"
)
var (
DB *gorm.DB
)
// Todo model
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Status bool `json:"status"`
}
func initMySQL() (err error) {
dsn := "root:root1234@(127.0.0.1:13306)/mydb?charset=utf8mb4&parseTime=True&loc=Local"
DB, err = gorm.Open("mysql", dsn)
if err != nil {
return
}
return DB.DB().Ping()
}
func main() {
//创建数据库
//SQL: create database mydb;
//连接数据库
err := initMySQL()
if err != nil {
panic(err)
}
defer DB.Close()
//模型绑定
DB.AutoMigrate(&Todo{})
r := gin.Default()
//告诉gin框架模板文件引用的静态文件去哪里找
r.Static("/static", "static")
//告诉Gin框架去哪找模板文件
r.LoadHTMLGlob("templates/*") //其中/*表示templates文件夹下的所有文件
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})
//v1
v1Group := r.Group("v1")
{
//待办事项
//添加
v1Group.POST("/todo", func(c *gin.Context) {
//前端页面填写待办事项,点击提交,会发请求到这里
//1、从请求中把数据拿出来
var todo Todo
c.BindJSON(&todo)
//2、存入数据库
//3、返回响应
if err = DB.Create(&todo).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
} else {
c.JSON(http.StatusOK, todo)
}
})
//查看
v1Group.GET("/todo", func(c *gin.Context) {
//查询todo这个表里的所有数据
var todoList []Todo
if err = DB.Find(&todoList).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
} else {
c.JSON(http.StatusOK, todoList)
}
})
//修改
v1Group.PUT("/todo/:id", func(c *gin.Context) {
//获取路径参数
id, ok := c.Params.Get("id")
if !ok {
c.JSON(http.StatusOK, gin.H{"error": "无效id"})
return
}
var todo Todo
if err = DB.Where("id=?", id).First(&todo).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
}
c.BindJSON(&todo)
if err = DB.Save(&todo).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusOK, todo)
}
})
//删除
v1Group.DELETE("/todo/:id", func(c *gin.Context) {
//获取路径参数
id, ok := c.Params.Get("id")
if !ok {
c.JSON(http.StatusOK, gin.H{"error": "无效id"})
return
}
if err = DB.Where("id=?", id).Delete(Todo{}).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
} else {
c.JSON(http.StatusOK, gin.H{id: "删除成功"})
}
})
}
r.Run()
}
ok,到这里我们就实现了全部的功能我们现在来进行一下测试。
还是在Terminal中输入go run main.go。成功启动之后会显示程序正在监听8080端口。
打开浏览器,输入127.0.0.1:8080/。
添加测试
在文本框中输入待办事项之后点击右侧的加号。
提示添加成功并且下方显示添加的待办事项。
修改测试
点击下方待办事项右侧的绿色按钮,可以将该待办事项的状态进行更改,表示该事项已完成。
删除测试
点击下方待办事项右侧的红色按钮,可以将待办事项进行删除。删除成功会进行提示并将下方列表中的待办事项删除。
ok,到这里我们就完成了对这个小项目所有功能的实现。
当然,这里还存在一个问题,那就是我们在这个项目中把所有的代码都写到了main.go一个文件中,这一点对于我们这样一个代码量较小的项目来说是无关紧要的,但是如果是一个需要团队协作的大项目,那么这样做就会不利于开发和测试了,这时候我们需要对其进行项目拆分,将不同的逻辑拆分到不同的目录的不同文件中,具体的拆分方式会因为项目的不同或者个人的偏好不同而不同,所以这里不做赘述,只要是适合的就是最好的。