必须用reflect.TypeOf获取类型再遍历字段调Tag.Get,且字段须导出(首字母大写);指针传入时需.Type().Elem(),非导出字段反射不可见导致tag为空。

struct 字段反射获取不到 tag 怎么办
常见原因是没用 reflect.TypeOf 获取类型,而是用了 reflect.ValueOf 直接拿值 —— 后者只能拿到字段值,Tag 信息只存在类型元数据里。必须先用 reflect.TypeOf 拿到 reflect.Type,再用 .Field(i) 取字段的 StructField 才能读 .Tag.Get("gorm") 或 .Tag.Get("db")。
另外注意:结构体字段必须是导出的(首字母大写),否则 reflect 根本看不到该字段,tag 自然也读不到。
- 用
reflect.TypeOf(obj).Elem()处理指针传入场景(比如传&User{}) - 遍历时检查
field.PkgPath != ""可快速跳过非导出字段 - 别直接对
interface{}做reflect.ValueOf后调.Type(),容易误判底层类型
怎么把 gorm tag 映射成 SQL 类型
gorm 的 gorm:"type:varchar(255);size:255" 这类 tag 没有统一解析器,得自己拆。重点不是“全兼容”,而是覆盖高频字段:比如 int64 → BIGINT,string 默认 VARCHAR(255),但若 tag 里有 size 或 type 就优先用它。
别硬编码所有 gorm tag(如 uniqueIndex、index),建表语句只关心列定义部分;索引、约束等应单独生成,混在一起会让逻辑变脆。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
strings.Split(field.Tag.Get("gorm"), ";")拆 tag,再用strings.HasPrefix(s, "type:")提取类型 - 没指定
type时,按 Go 类型兜底:time.Time→DATETIME,bool→TINYINT(1) - 注意 MySQL 的
TEXT不支持默认值,如果 tag 里写了default:xxx但类型是text,得主动忽略 default 部分
生成 CREATE TABLE 时主键和自增怎么处理
gorm 默认把名为 ID 的字段当主键,且加 primary_key tag 就生效。但纯反射不认这个逻辑,得手动识别:field.Name == "ID" || strings.Contains(field.Tag.Get("gorm"), "primary_key")。
自增(AUTO_INCREMENT)不能只看 primary_key,还得确认类型是整数型(int、int64 等),否则 MySQL 会报错。另外,MySQL 8.0+ 要求主键必须 NOT NULL,所以即使 tag 没写 not null,主键字段也得强制加上。
- 检测到主键后,SQL 列定义末尾拼上
PRIMARY KEY,整数类型再加AUTO_INCREMENT - 多个字段组合主键?反射能拿到,但标准建表语句不推荐,
PRIMARY KEY (a,b)写法需额外收集字段名再拼,容易漏 - 别忘了给主键字段补
NOT NULL,哪怕原始 struct 字段是*int64(此时实际应为 NULLABLE,但 gorm 行为是强主键 NOT NULL)
空 struct 或嵌套匿名字段怎么避免 panic
反射遇到 struct{} 或 type T struct{ A struct{} } 时,.NumField() 是 0,循环直接跳过,不会 panic;但更危险的是匿名嵌入的 struct 字段,比如 type User struct { BaseModel; Name string },其中 BaseModel 本身含字段 —— 这时得递归处理,否则漏字段。
不过递归要设深度限制(比如 ≤3),否则遇到循环嵌入(A embeds B, B embeds A)会栈溢出。简单做法:只展开一级匿名字段(field.Anonymous == true),且只处理 struct 类型,其他类型(map、slice)直接跳过。
- 用
field.Type.Kind() == reflect.Struct && field.Anonymous判断是否该展开 - 展开时用
field.Type而非field.Type.Elem(),后者对非指针 struct 会 panic - 字段名重复(如两个匿名 struct 都有
ID)不自动去重,SQL 会报错,得提前校验并报明确错误,而不是静默覆盖
最麻烦的其实是 time.Time 的时区处理 —— MySQL 的 DATETIME 没有时区,但 Go 的 time.Time 有。如果业务依赖本地时区,建表时就得加注释提醒开发者在 Scan/Value 方法里做转换,光靠反射生成 SQL 解决不了。

















