Golang数据库字段变更记录的灵活类型转换方案咨询
问题
我有数据库表MyTable和历史记录表HistoryRecord,定义如下:
type MyTable struct { MyInt int `gorm:"my_int"` MyString string `gorm:"my_string"` MyPolicy Policy `gorm:"my_policy"` } type Policy struct { Maximum *int `json:"maximum,omitempty"` Minimum *int `json:"minimum,omitempty"` Avail []int `json:"avail,omitempty"` } type HistoryRecord struct { Field string Value any }
其中MyPolicy字段以JSON格式存储在数据库中。我的需求是生成字段变更记录:比如修改MyInt时,生成HistoryRecord{Field: "my_int", Value: <new_myint_value>}并存入数据库,Field存变更列名,Value存新值(支持int、string、Policy等类型)。
现在需要将记录值转换为DTO提供给用户查看,目前用类型断言的静态代码如下:
switch record.Field { case "my_policy": record.Value.(MyPolicy).Maximum // 转换为DTO的相关代码 case "my_int": record.Value.(int) // ... case "my_string": record.Value.(string) // ... }
这种方式不够灵活且过于静态,想通过接口、泛型等方式实现更灵活的类型值获取,求可行方案。
可行方案
方案一:反射+字段标签映射
利用Go的反射机制,结合MyTable的结构体标签预构建字段与类型的映射关系,后续通过反射完成类型转换和DTO生成,新增字段时只需更新映射表即可。
代码示例
// 定义统一的DTO转换接口 type DTOConverter interface { ToDTO() any } // Policy实现DTO转换逻辑 func (p Policy) ToDTO() any { return map[string]any{ "maximum": p.Maximum, "minimum": p.Minimum, "avail": p.Avail, } } // 为基础类型定义包装类并实现接口 type DBInt int func (i DBInt) ToDTO() any { return int(i) } type DBString string func (s DBString) ToDTO() any { return string(s) } // 预缓存字段名到目标类型的映射 var fieldTypeMap = map[string]reflect.Type{ "my_int": reflect.TypeOf(DBInt(0)), "my_string": reflect.TypeOf(DBString("")), "my_policy": reflect.TypeOf(Policy{}), } // 统一转换入口 func ConvertToDTO(record HistoryRecord) any { targetType, exists := fieldTypeMap[record.Field] if !exists { return record.Value // 未知字段直接返回原始值 } val := reflect.ValueOf(record.Value) // 直接匹配类型的情况 if val.Type() == targetType { if converter, ok := val.Interface().(DTOConverter); ok { return converter.ToDTO() } } // 基础类型可转换的情况(比如int转DBInt) if val.Type().ConvertibleTo(targetType) { convertedVal := val.Convert(targetType) if converter, ok := convertedVal.Interface().(DTOConverter); ok { return converter.ToDTO() } } return record.Value }
方案二:泛型+注册表模式
通过泛型定义通用转换函数,将每个字段的转换逻辑提前注册到全局注册表,处理历史记录时直接根据字段名调用对应转换逻辑,扩展性强,新增字段只需添加注册项。
代码示例
// 定义转换函数类型 type ConvertFunc func(any) any // 全局转换注册表 var convertRegistry = map[string]ConvertFunc{} // 初始化注册所有字段的转换逻辑 func init() { convertRegistry["my_int"] = func(v any) any { return v.(int) // 可扩展为复杂DTO结构 } convertRegistry["my_string"] = func(v any) any { return v.(string) } convertRegistry["my_policy"] = func(v any) any { p := v.(Policy) return map[string]any{ "maximum": p.Maximum, "minimum": p.Minimum, "avail": p.Avail, } } } // 统一转换入口 func GetDTO(record HistoryRecord) any { if fn, exists := convertRegistry[record.Field]; exists { return fn(record.Value) } return record.Value // 兼容未注册字段 }
方案三:接口驱动的多态转换
让所有可能存入HistoryRecord.Value的类型实现统一的DTO转换接口,存储时直接使用实现接口的类型,转换时无需判断字段名,直接断言接口调用转换方法,完全实现多态解耦。
代码示例
// 定义统一的DTO转换接口 type ToDTOer interface { ToDTO() any } // Policy实现接口 func (p Policy) ToDTO() any { return map[string]any{ "maximum": p.Maximum, "minimum": p.Minimum, "avail": p.Avail, } } // 基础类型包装类实现接口 type DBInt int func (i DBInt) ToDTO() any { return int(i) } type DBString string func (s DBString) ToDTO() any { return string(s) } // 生成HistoryRecord时使用包装类型 // 示例:修改MyInt时生成记录 // HistoryRecord{Field: "my_int", Value: DBInt(newMyInt)} // 转换DTO时直接调用接口方法 func ConvertRecord(record HistoryRecord) any { if dtoer, ok := record.Value.(ToDTOer); ok { return dtoer.ToDTO() } return record.Value // 兼容未实现接口的类型 }
内容的提问来源于stack exchange,提问作者osflw
相关产品推荐
相关产品推荐

