写在最前面
刚学了 gorm ,想着完善一下之前的四大请求方式,加上数据库,做一个简单的 crud 。
连接数据库 – init
通过 mysql.Open(dsn)
来连接数据库,然后 gorm.Open()
用于初始化 GORM 数据库连接,当然也还有别的连接方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| var DB *gorm.DB
func init() { username := "root" password := "thisIsAPassword" host := "127.0.0.1" port := 3306 Dbname := "crud_demo" timeout := "10s"
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ NamingStrategy: schema.NamingStrategy{ SingularTable: true, NoLowerCase: true, }, }) if err != nil { fmt.Println("数据库连接失败,err=" + err.Error()) }
fmt.Println("创建成功: ", db) DB = db
}
|
结构体和API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| type ArticleList struct { Author string `gorm:"type:varchar(10)" json:"author" binding:"required"` Title string `gorm:"type:varchar(10)" json:"title" binding:"required"` Brief string `gorm:"type:varchar(30)" json:"brief" binding:"required"` }
type ArticleModel struct { gorm.Model ArticleList Content string `gorm:"type:varchar(1000)" json:"content" binding:"required"` }
type Response struct { Code int `json:"code"` Date any `json:"date"` Message string `json:"msg"` }
|
这里把 ArticleList 从 ArticleModel 中拆分出来,作为 API 来用,接着往下看就知道了~
测试 – Main函数
先初始化了几条数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| func main() { DB.Debug().AutoMigrate(&ArticleModel{}) DB.Create(&[]ArticleModel{ { ArticleList: ArticleList{ Title: "go语言入门", Brief: "本书是go语言入门基础 balabala...", Author: "作者a", }, Content: "第一章:认识go语言.............", }, { ArticleList: ArticleList{ Title: "c语言入门到入土", Brief: "本书是c语言入门教程,面向入土", Author: "作者b", }, Content: "你的第一个C程序: printf(\"hello world\");", }, { ArticleList: ArticleList{ Title: "数据库从删库到跑路", Brief: "本书教你如何从删库到跑路", Author: "作者c", }, Content: "没别的,直接跑路吧", }, })
router := gin.Default() router.GET("/articles/", _getList) router.GET("/articles/:id", _getDetails) router.POST("/articles/", _post) router.PUT("/articles/:id", _put) router.DELETE("/articles/:id", _delete)
err := router.Run(":80") if err != nil { return } }
|
查
文章列表
不设置条件,通过 Find()
查找所有内容,然后放入到 ArticleList 切片中,这个可以作为一个 api 来限制返回给结果哪些属性。
此处仅返回文章简要信息即可。
1 2 3 4 5 6 7 8 9 10 11 12
| func _getList(arg *gin.Context) {
var list []ArticleList DB.Debug().Model(&ArticleModel{}).Find(&list)
if len(list) == 0 { arg.JSON(http.StatusOK, Response{0, "内容为空", "无结果"}) } else { arg.JSON(http.StatusOK, Response{http.StatusOK, list, "响应成功"}) } }
|
文章详情
从请求中拿到 id 然后去数据库中查找,并通过 ret.RowsAffected == 0
来判断是否查找到对应文章。一开始我用 ret.Error != nil
作为判断,但测试时输入错误的 id 没有提示,还是会返回,不过返回的是空值。
控制台也没有提示:
1 2
| 获取到id: 9999 [0.626ms] [rows:0] SELECT * FROM `ArticleModel` WHERE id = '9999' AND `ArticleModel`.`DeletedAt` IS NULL
|
可见是未查找到时没有进入第一个分支。后来知道 Find 方法不会返回 gorm.ErrRecordNotFound
错误,当没有匹配的记录时,它将返回一个零值对象。
有两种解决方式: 1. 可以改用 First 2. 把条件判断改为 ret.RowsAffected == 0
修改后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func _getDetails(arg *gin.Context) {
bookId := arg.Param("id") fmt.Printf("获取到id: %v\n", bookId)
var article ArticleModel ret := DB.Debug().Where("id = ?", bookId).Find(&article) if ret.RowsAffected == 0 { fmt.Println("未查找到id为", bookId, "的文章") arg.JSON(http.StatusOK, Response{0, "内容为空", "查找失败,文章不存在!"}) } else { arg.JSON(http.StatusOK, Response{http.StatusOK, article, "响应成功"}) } }
|
增 & 改
常用的几种类型都可以直接 shouldBind()
来处理,所以删除了之前文章用的 _bindJson()
同前面一样用 ret.RowsAffected == 0
作为判断条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| func _put(arg *gin.Context) { bookId := arg.Param("id") fmt.Printf("获取到id: %v\n", bookId)
var article ArticleModel err := arg.ShouldBind(&article) if err != nil { fmt.Println(err) arg.JSON(http.StatusBadRequest, Response{0, nil, "请求数据验证失败"}) return }
ret := DB.Debug().Where("id = ?", bookId).Updates(&article) if ret.RowsAffected == 0 { fmt.Println("不存在id为", bookId, "的文章") arg.JSON(http.StatusOK, Response{0, "内容为空", "查找失败,文章不存在!"}) } else { fmt.Println("id为", bookId, "文章已被修改") arg.JSON(200, Response{http.StatusOK, article.UpdatedAt, "修改成功"}) }
}
func _delete(arg *gin.Context) { bookId := arg.Param("id") fmt.Printf("获取到id: %v\n", bookId)
var data ArticleModel ret := DB.Debug().Where("id = ?", bookId).Find(&data)
if ret.RowsAffected == 0 { fmt.Println("未查找到id为", bookId, "的文章") arg.JSON(http.StatusOK, Response{0, "不存在该文章", "删除失败!"}) } else { DB.Debug().Delete(&data) deleteTime := data.DeletedAt DB.Debug().Model(&ArticleModel{}).Select("DeletedAt").Where("id = ?", bookId).Find(&deleteTime) arg.JSON(200, Response{http.StatusOK, gin.H{"删除时间": deleteTime}, "删除成功"}) fmt.Println("id为", bookId, "文章已被修改") }
}
|
- 一开始我设置的 Content 字段有默认值
default:"这个人很懒,没有写任何内容..."
但它又是一个必填项。
结果如下:
实际上,由于接收请求是发生在将数据添加到数据库之前,所以默认值就不起作用了。
- 另外我犯的错误是在第 10 行的 if 分支里面,只是输出错误,而没有返回终止后面的程序,所以在绑定参数时,对于必填项但没有填的只会输出错误,然后继续将这条数据添加到数据库
删
在执行删除操作时,实际上,不会真的删除数据,只会标记一个删除时间,每次查找时,都会加上条件 DeletedAt IS NULL
,即没有标记过删除时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| func _delete(arg *gin.Context) { bookId := arg.Param("id") fmt.Printf("获取到id: %v\n", bookId)
var data ArticleModel ret := DB.Debug().Where("id = ?", bookId).Find(&data)
if ret.RowsAffected == 0 { fmt.Println("未查找到id为", bookId, "的文章") arg.JSON(http.StatusOK, Response{0, "不存在该文章", "删除失败!"}) } else { DB.Debug().Delete(&data) deleteTime := data.DeletedAt DB.Debug().Model(&ArticleModel{}).Select("DeletedAt").Where("id = ?", bookId).Find(&deleteTime) arg.JSON(200, Response{http.StatusOK, gin.H{"删除时间": deleteTime}, "删除成功"}) fmt.Println("id为", bookId, "文章已被修改") }
}
|
写在最后
可以说这次简单的crud主要时间就花在修bug,各种各样的bug。`(>﹏<)′
算是大概修完了吧。等以后学了更多东西再迭代一次(写bug,修bug)。
再补一个图:
感谢夏日口袋陪伴我度过了这个夏天,夏日结束了,我的冒险还未结束~