动态where条件需按需拼装:空值字段须校验后添加,时间范围用utc统一时区,like查询由go层拼接通配符,分页时count与find必须共享同一查询条件构建逻辑。

动态 WHERE 条件怎么加才不报错?
Gin 接收的查询参数(比如 name、status、start_time)往往不是全都有,直接写死 Where("name = ?", name) 容易空值导致 SQL 错误或逻辑漏洞。GORM 的链式调用允许条件“按需拼装”,但必须注意:*所有 Where、Or、And 调用都作用于同一个 gorm.DB 实例,且顺序影响最终 SQL 逻辑**。
- 空字符串、零值字段(如
0、""、time.Time{})不能直接丢进Where,否则会查出意外结果 - 多个条件用
Where连续调用是安全的,等价于 AND;但混用Or时要注意括号逻辑,建议用Where("status = ? OR type = ?", s1, t1)显式控制 - 切忌在循环里反复赋值
db = db.Where(...)—— GORM 是不可变设计,每次调用返回新实例,原db不变
// 正确:构建可变查询
db := DB.Model(&User{})
if name != "" {
db = db.Where("name LIKE ?", "%"+name+"%")
}
if status > 0 {
db = db.Where("status = ?", status)
}
if !startTime.IsZero() {
db = db.Where("created_at >= ?", startTime)
}
var users []User
db.Find(&users)
时间范围查询为什么总查不到数据?
time.Time 零值(time.Time{})和数据库里的 NULL 不等价,直接判 !t.IsZero() 只能过滤掉未传时间的请求,但若前端传了非法时间(如 "0001-01-01T00:00:00Z"),IsZero() 仍为 true,容易漏条件。
- Gin 绑定
time.Time时默认使用 RFC3339,但 MySQL 常用Y-m-d H:i:s格式,时区不一致会导致时间偏移 - 推荐统一转为 UTC 再比较:
startTime.UTC(),并确保数据库字段类型是DATETIME或TIMESTAMP - 范围查询务必用
BETWEEN或两个Where,避免Where("created_at >= ? AND created_at 写成 <code>Where("created_at BETWEEN ? AND ?", s, e)更清晰
// 注意:MySQL 的 NOW() 默认带时区,Go time.Time 也带 Location
// 所以要么都转 UTC,要么显式指定时区
endOfDay := startTime.Add(24 * time.Hour).Add(-time.Second)
db = db.Where("created_at BETWEEN ? AND ?", startTime, endOfDay)
LIKE 模糊查询怎么防 SQL 注入?
Gin 的 c.Query("keyword") 直接拼进 Like 很危险。GORM 的 Where 占位符(?)本身能防注入,但通配符 % 必须由 Go 字符串拼接,而非 SQL 侧拼 —— 否则用户输入 %admin% 就变成双重模糊,可能越权。
- 不要用
Where("name LIKE %?%", keyword)——?不能出现在引号外 - 正确做法是 Go 层拼好
"%" + keyword + "%",再传给占位符 - 如果支持前缀/后缀/全匹配,可定义枚举或参数标识,避免前端随意传
%
keyword := c.Query("keyword")
if keyword != "" {
// ✅ 安全:通配符由 Go 控制
db = db.Where("name LIKE ?", "%"+keyword+"%")
// ❌ 危险:SQL 层解析,无法防注入
// db = db.Where("name LIKE ?", "%"+keyword+"%") // 看似一样,但若 keyword 包含单引号就崩
}
分页 + 动态条件组合时 count 总是不准?
用 Limit/Offset 分页时,如果先 Count(&total) 再 Find(&list),两次查询的 WHERE 条件必须完全一致。常见错误是某次忘了加某个 Where,或用了不同变量(比如一个用 status,另一个用 req.Status 但没更新)。
-
Count()返回的是满足条件的总行数,不是当前页数量,别和len(list)混淆 - 若用
Scopes封装条件逻辑,确保Count和Find都应用同一 scope - 大表慎用
Count,可考虑用估算值或缓存总数,尤其当动态条件常变时
// ✅ 共享条件构建逻辑
buildQuery := func(db *gorm.DB) *gorm.DB {
if name != "" {
db = db.Where("name LIKE ?", "%"+name+"%")
}
if status > 0 {
db = db.Where("status = ?", status)
}
return db
}
<p>db := buildQuery(DB.Model(&User{}))
db.Count(&total) // total 是完整符合条件的总数</p><p>db.Offset((page - 1) * size).Limit(size).Find(&users)
</p>
Gin + GORM 动态查询真正的坑不在语法,而在条件分支的覆盖完整性、时间/字符串的隐式转换、以及两次查询间状态的一致性 —— 这些地方一漏,线上就静默错。











