我正在尝试为我的 go 程序创建自定义属性标签。这些标签将与字段一起使用,这些字段将从保管库中提取它们的值,例如
Password string \
金库:“密码”``。下面描述的函数应该遍历结构体的所有字段(包括嵌套结构体),并将所有标签记录在 TagParser.ParsedTagMap 中,并带有指向目标值的指针。 ParseTags 函数将接收 ServerConfiguration 结构,并填充一些值。
自定义结构如下所示:
type ServerConfiguration struct {
Server1 EndpointConfiguration `yaml:"server1"`
Server2 EndpointConfiguration `yaml:"server2"`
}
type EndpointConfiguration struct {
Rest RestEndpoint `yaml:"rest"`
Login LoginCredentials `yaml:"login"`
}
type LoginCredentials struct {
Username string `vault:"keycloak_username"`
Password string `vault:"keycloak_password"`
}
应该解析自定义结构的代码:
type TagParser struct {
SourceStruct interface{}
ParsedTagMap map[string]interface{}
}
func ParseTags(tagName string, data interface{}) map[string]interface{} {
var tagParser TagParser
tagParser.ParsedTagMap = make(map[string]interface{})
tagParser.GetNestedTag(tagName, data)
slog.Debug("TagParser[" + tagName + "]: Parsed " + strconv.Itoa(len(tagParser.ParsedTagMap)) + " tags")
return tagParser.ParsedTagMap
}
func (tp *TagParser) GetNestedTag(tagName string, data interface{}) {
val := reflect.ValueOf(data)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return
}
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
tagValue := val.Type().Field(i).Tag.Get(tagName)
if tagValue != "" {
tp.ParsedTagMap[tagValue] = field.Interface()
}
if field.Kind() == reflect.Struct || (field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct) {
tp.GetNestedTag(tagName, field.Interface())
}
}
}
提取字段后,一个单独的函数将调用vault REST API,并检索它们各自的值(带有值断言);
// var secret *vault.KVSecret retrieved before
for key, value := range vaultSecretMapping {
secretValue, ok := secret.Data["data"].(map[string]interface{})[key].(string)
if !ok {
panic("Unable to parse " + key + " from vault")
}
switch p := value.(type) {
case *[]byte:
*p = []byte(secretValue)
case *string:
*p = secretValue
default: // Should never happen
panic("Unknown type for secret value")
}
}
我需要解析函数是动态的(不仅仅是字符串),因为我将来必须使用其他数据类型(sych as []byte)。
我尝试摆弄 go 的反射库,但运气不佳。在
TagParser.ParsedTagMap
中,使用当前设置,我需要类似的东西:
{"password": *interface{metadata:{type: string}, data: {*"pointer to the proper ServerConfiguration nested struct"}}}
我可以手动实现这个结果:
TagParser.ParsedTagMap\["password"\]= &ServerConfiguration.Server1.LoginCredentials.Password
但是使用反射会混淆调试,并且 go 的按值传递取消引用reflect.Value 在函数之间传递(我认为)。调用 field.Addr() 会产生恐慌。
你可以这样做:
type TagParser struct {
// 1. use [] to collect all fields that have the same key
// 2. use reflect.Value so that you can utilize f.Set()
tags map[string][]reflect.Value
}
func ParseTags(tag string, data interface{}) map[string][]reflect.Value {
var p TagParser
p.tags = make(map[string][]reflect.Value)
p.parse(tag, reflect.ValueOf(data))
return p.tags
}
func (p *TagParser) parse(tag string, rv reflect.Value) {
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
if rv.Kind() != reflect.Struct {
return
}
rt := rv.Type()
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
if k := rt.Field(i).Tag.Get(tag); k != "" {
p.tags[k] = append(p.tags[k], f)
}
p.parse(tag, f)
}
}
然后像这样使用它:
var cfg ServerConfiguration
tags := ParseTags("vault", &cfg)
vaultSecretMapping := map[string]any{
// ..
}
for k, v := range vaultSecretMapping {
if ff, ok := tags[k]; ok && len(ff) > 0 {
for i := range ff {
ff[i].Set(reflect.ValueOf(v))
}
}
}
https://go.dev/play/p/-lS262VUjpG
请注意,如果
f.Set
不可寻址或通过使用 unexported结构字段获得,
f
将会出现恐慌。