安全提取 db 标签需先用 strings.TrimSpace 判断有效性:空则跳过,"-" 或 "-,inline" 显式忽略,含逗号时按语义解析列名与行为标记;传参前须解包指针并确认结构体类型;SQL 构建仅生成列名与 ? 占位符,值通过参数绑定传递;inline 嵌套字段需小写化前缀拼接,优先使用子字段 db 标签。

怎么用 reflect.StructTag.Get 安全提取 db 标签
直接调 field.Tag.Get("db") 返回空字符串,不代表没标签——可能是 db:"" 或只有空格。必须先用 strings.TrimSpace 判断是否有效:
- 若结果为空,跳过该字段(不参与 SQL 构建)
- 若结果为
"-"或"-,inline",显式忽略,比空标签更明确 - 若含逗号分隔符(如
"id,pk,auto"),别用strings.Split粗暴拆,要按语义解析:前段是列名,后续是行为标记
常见错误是把 db:"name" 当成纯列名,却没处理 db:"created_at,default:now()" 里的 default: 前缀,导致生成的 INSERT 语句漏掉函数调用。
reflect.TypeOf 传值还是传指针?字段遍历前必须确认
传 &User{} 得到的是 *User 类型,.NumField() 会 panic;必须传 User{} 或先调 t.Elem() 解包指针:
- 判断
t.Kind() == reflect.Ptr,是则t = t.Elem() - 再确认
t.Kind() == reflect.Struct,否则不是结构体 - 私有字段(首字母小写)不会出现在
Field(i)中,CanInterface()为 false,反射自动跳过 - 嵌套结构体字段需递归进入,但别忘了检查
field.Anonymous和field.Tag.Get("db") == ",inline"
漏掉解包指针是最常见的 panic 来源,尤其在封装工具函数时,输入参数类型不统一就容易栽在这里。
立即学习“go语言免费学习笔记(深入)”;
生成 SQL 字段列表时,为什么不能直接拼值?
反射只能提供字段名和标签,它不知道哪个是主键、是否允许 NULL、时间要不要转格式、字符串要不要转义。硬拼 "INSERT INTO user (name) VALUES ('" + v.String() + "')" 是典型 SQL 注入温床:
- 字段值应只通过
db.Query/Exec的参数绑定传递,反射只负责生成列名和?占位符序列 - 对
time.Time字段,反射拿到的是reflect.Value,必须显式转成time.Time才能被database/sql正确识别 - 自定义类型(如
type UserID int64)若没实现driver.Valuer,v.Interface()返回的是int64,但驱动不认识这个别名
ORM 底层真正干活的是预编译语句 + 参数绑定,反射只干一件事:把结构体字段映射成元数据。混淆这两层职责,就会写出脆弱又危险的“伪 ORM”。
嵌套结构体 inline 展开时字段名怎么拼?
当遇到 Extra Info `db:",inline"` 这种匿名嵌入字段,它的子字段 CreatedAt 不该变成 extra.CreatedAt,而应扁平化为 extra_created_at:
- 检查
field.Anonymous && field.Tag.Get("db") == ",inline" - 递归遍历其
field.Type,对每个子字段名加前缀:prefix + "_" + childName - 前缀用外层字段名(
Extra→extra小写化),不是标签值 - 若子字段自己也有
db标签,优先用它;没标签才 fallback 到拼接名
这里最容易被忽略的是大小写转换规则:Go 字段名 CreatedAt 转成数据库列名是 created_at,不是 extra_CreatedAt,大小写处理必须在拼接前完成,而不是依赖标签 fallback。


















