Go泛型不支持struct{}约束,必须用any加运行时校验;反射需处理指针解引用、跳过未导出字段、手动解析db tag、避免SQL注入,嵌套结构需递归展开并加前缀。

泛型函数必须用 any 接收参数,不能写 struct{}
Go 的泛型不支持 struct{} 作为类型约束,写 func Query[T struct{}](...) 会直接报 syntax error: unexpected struct。唯一可行路径是用 T any,再在函数内做运行时校验:
- 先用
reflect.ValueOf(entity)获取反射值 - 如果是指针,必须调用
.Elem()才能读取 struct tag;传值类型(如User)会导致Tag.Get("db")返回空字符串 - 接着判断
v.Kind() == reflect.Struct,否则返回明确错误,比如"entity must be a struct or pointer to struct"
字段名提取必须跳过未导出字段,FieldByName 对小写字母字段无效
reflect.Value.FieldByName("name") 在字段是 name string(小写开头)时永远返回 IsValid() == false,不是 bug,是 Go 语言设计——反射无法访问未导出字段。
- 正确做法:用
reflect.TypeOf(v).NumField()遍历所有字段,逐个比对field.Tag.Get("db") - 若 tag 为空或等于
"-"(注意前后空格),跳过该字段 - 字段名默认用 Go 字段名(
field.Name),仅当dbtag 非空且非"-"时覆盖为列名
db:"id,pk auto_increment" 这类复合 tag 要手动解析,标准库不拆分
reflect.StructTag.Get("db") 只返回原始字符串,不会自动识别 pk、auto_increment 或 omitempty。它也不 panic,但后续切片操作(如 strings.Split(tag, " ")[0])容易因空字符串 panic。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 第一步永远做
tag := strings.TrimSpace(field.Tag.Get("db")),再判空 - 推荐用逗号分隔语义,比如
db:"created_at,notnull,default:now()",比空格更可靠 - 别依赖
omitempty自动跳过字段——那是json包的行为,dbtag 没这逻辑,需业务层显式判断值是否零值
SQL 构建不能靠 reflect.Value.Interface() 直接拼字符串
把 v.Interface() 塞进 fmt.Sprintf("INSERT INTO ... VALUES (%v)", v.Interface()) 看似能跑,但埋了三颗雷:SQL 注入、NULL 写入、自定义类型失效。
立即学习“go语言免费学习笔记(深入)”;
- 真正安全的做法是只用反射提取元数据(字段名 → 列名映射),SQL 拼的是占位符(
?或$1),参数走database/sql原生绑定 - 对实现了
driver.Valuer的类型(如time.Time、自定义 ID 类型),应显式调用v.Interface().(driver.Valuer).Value() - 嵌套结构体(如
type User struct { Profile Info `db:",inline"` })不会自动展开,必须手动递归处理并加前缀,且要检查field.Anonymous && tag == ",inline"
pk)、哪些交给 driver 处理(如 default:now())。这些没法靠一行反射解决,得靠明确的约定和防御性代码。

















