还好数据库服务器有每日备份,也只是测试服务器,恢复到昨天的也就 OK 了,下面复盘下为什么用户表会被我删掉。


公司的项目之前是用 PHP 开发的,框架使用的是 Laravel ,Laravel 开发起来效率想到不错,只是运行的话,并发比较一般,一直有考虑用其他语言去重写一下公司项目,之前有考虑使用 Python ,但是在实际中因为后台主要是作为 restful api 提供接口使用,动态类型语言开发起来很快捷,但是在和客户端交互的时候总是会遇到字段类型不统一的问题,所以这一次考虑换一个静态语言类型,最后就选中了 go 。

项目这次选用的开发框架是 gin, orm 框架是 gorm 是两个在 go 语言项目开发中都非常流行的两个库。


下面直接贴问题代码上来看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func UserDelete(c *gin.Context) {
var user model.User
id, hasID := c.GetQuery("id")
if !hasID {
c.Abort()
}
// lib.DB 是我初始化的一个 gorm struct
lib.DB.Where("id = ?", id).First(&user)
if user.ID == 0 {
c.Abort()
}
lib.DB.Debug().Unscoped().Delete(&user)
c.JSON(http.StatusOK, gin.H{
"code": 100000,
"message": "OK",
})
}

这段代码有两个地方是出问题前我没有注意到的:

  1. c.Abort(),c.JSON() gin 的这些方法并不会自动返回,如果不在后边添加 return 代码会继续往下执行,并且返回的数据会合并到一起。
    因为这个所以就算判断到 user.ID == 0 但是程序也不会停止,继续往下进行。
  2. lib.DB.Debug().Unscoped().Delete(&user) 这行代码最后有可能会生成以下两种 sql 语句:
  1. user 初始化了,生成的最终语句为:
    1
    2
    > DELETE FROM `user`  WHERE `user`.`id` = '1282'
    >
  1. user 只声明了,没有被初始化,生成的最终语句为:
    1
    2
    > DELETE FROM `user`
    >

这样两个问题最终就导致了用户表被删除,在以后的开发中我就不去使用

lib.DB.Delete(&user)

这样一种调用,而改为

1
lib.DB.Delete(model.User{},"id = ?",user.ID)

这样一种方式来避免 user 未初始化而带来的恶略结果。

PS:GORM Delete 其实在 gorm 的文档中有这样一个声明:

WARNING When delete a record, you need to ensure it’s primary field has value, and GORM will use the primary key to delete the record, if primary field’s blank, GORM will delete all records for the model

不过当时我并不太理解问什么会有这样一个声明,你们也看到了在结构体被声明的时候里边的每一个字段根据类型都会有一个默认值,所以我最初以为就算 user 在数据库中不存在 gorm 顶多会生成

1
DELETE FROM `user`  WHERE `user`.`id` = '0'

这样一个语句,然而现实并不是,我需要重新去看一下 gorm 这一块儿的代码了。