反射拼WHERE条件必须先检查nil指针和零值,再取Interface();字段名、操作符需白名单校验,值一律用?绑定,禁拼SQL字符串。

直接用反射拼 WHERE 条件极易 panic 或生成错误 SQL,核心问题不是“会不会写”,而是“没做空指针和零值防护”就调 v.Interface()。
反射取字段值前必须检查 nil 指针
结构体字段是 *string、*int64 等指针类型时,若值为 nil,直接调 v.Interface() 会触发 reflect: call of reflect.Value.Interface on zero Value panic。
- 正确做法:先判断
v.Kind() == reflect.Ptr && v.IsNil(),再决定跳过该字段,或生成col IS NULL -
v.IsValid()不可靠——对nil指针返回true,但后续取值仍 panic - 若字段是
interface{}类型,需先v.Elem()解一层,否则v.Interface()可能返回空interface{}而非真实值
用结构体 + db tag 比 map[string]interface{} 更安全
用 map 看似灵活,但丢失类型信息,导致参数绑定错位(比如把 nil 当成 ""),且无法区分 IS NULL 和 = '' 语义。
- 优先定义带
dbtag 的结构体:type UserFilter struct { Name *string `db:"user_name"` } - 反射时读
v.Tag.Get("db")获取列名;空 tag 或"-"显式忽略 - 嵌入匿名字段(如
BaseFilter)需手动展开,并按规则拼前缀(如"base_created_at")
动态拼 IN 子句必须白名单校验 + 占位符数量匹配
IN (?) 不能靠一个 ? 代表多个值,硬拼字符串又易注入。错误示例:fmt.Sprintf("id IN (%s)", strings.Join(vals, ",")) —— 字符串值未转义,字段名未校验。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 只允许操作符在白名单中:
map[string]bool{"=": true, "!=": true, "IN": true, "LIKE": true} - 字段名须经正则校验:
^[a-zA-Z_][a-zA-Z0-9_.]*$,禁含空格、括号 -
v.Kind() == reflect.Slice时,用v.Len()生成等量?,并用make([]interface{}, v.Len())构建参数切片 - 空 slice(
[]int{})必须整体跳过,否则生成IN ()是语法错误
别在运行时拼完整 SQL 字符串喂给 database/sql
database/sql 的 Query 和 Exec 依赖预编译,传入反射拼出的动态字符串(如 "SELECT * FROM " + tableName)会导致占位符失效、无报错但查不到数据。
- 若用
sqlx,保持传入 struct 或 map,由sqlx.Named处理命名参数(:name) - 若用原生
database/sql,反射阶段就得拆出两个切片:args = append(args, v.Interface())和placeholders = append(placeholders, "?") - 永远不把字段名、表名、操作符直接拼进 SQL 字符串——它们必须走白名单校验,值一律走
?绑定
最常被忽略的是:反射只负责元数据映射(字段→列名、tag 解析),真正的 SQL 安全性来自参数绑定与白名单控制,不是字符串拼接越“通用”就越安全。

















