0

我是 Go 新手,想知道如何以惯用的方式解决以下问题:

我正在使用 viper 将配置文件加载到程序中。我选择 toml 格式是因为我想要一个配置文件,它可以指定几种不同格式的所需输入:例如,Alpha 提供程序需要一个 apikey,而 Beta 提供程序需要用户名和密码。

[alpha]
apikey = "123"
domain = "example.com"

# [beta]
# username = ""
# password = ""
# domain = ""
type ProviderService interface {                                 
    PrintDomain()                                                
}                                                                
                                                                 
type Provider struct {                                           
    Alpha `mapstructure:"alpha"`                                 
    Beta  `mapstructure:"beta"`                                                     
}                                                                
                                                                 
type Alpha struct {                                              
    Apikey string `mapstructure:"apikey"`                        
    Domain string `mapstructure:"domain"`                        
}                                                                
                                                                 
type Beta struct {                                               
    Username string `mapstructure:"username"`                    
    Password string `mapstructure:"password"`                    
    Domain   string `mapstructure:"domain"`                      
}                                                                
                                                                 
func main() {                                                    
    provider := loadConfig()                                     
    fmt.Printf("%+v\n", provider)                                
    // provider.DoAThing()  # <==== Want to do this; currently results in "ambiguous selector provider.DoAThing"                              
}                                                                
                                                                 
func (a Alpha) DoAThing() {                                   
    fmt.Println("domain", a.Domain)                              
}                                                                
func (b Beta) DoAThing() {                                    
    fmt.Println("domain", b.Domain)                              
}                                                                
                                                                 
func loadConfig() (p Provider) {                                 
    viper.AddConfigPath("./")                                    
    viper.SetConfigName("config")                                
    viper.SetConfigType("toml")                                  
                                                                 
    err := viper.ReadInConfig()                                  
    if err != nil {                                              
        panic(fmt.Errorf("Fatal error config file: %w \n", err)) 
    }                                                            
                                                                 
    err = viper.Unmarshal(&p)                                    
    if err != nil {                                              
        log.Fatal("unable to decode into struct", err)           
    }                                                            
                                                                 
    return p                                                     
}

上面的代码导致{Alpha:{Apikey:123 Domain:example.com} Beta:{Username: Password: Domain:}}空/未使用的结构仍然存在。

最终,我希望 ProviderService 接口与提供者无关,因此我可以简单地调用provider.PrintDomain()而不是provider.Alpha.PrintDomain()让代码中散布着 if/else 语句。我也对构建代码以实现这一结果的其他方式持开放态度。

提前致谢!

4

2 回答 2

1

一种可能的方法是使用反射

func main() {
    provider := loadConfig()
    fmt.Printf("%+v\n", provider)

    v := reflect.ValueOf(provider)
    for i := 0; i < v.NumField(); i++ {
        field := v.Field(i)
        fmt.Println("domain:", field.FieldByName("Domain"))
    }
}

它将打印 Alpha 或 Beta 结构的“域”字段。

去游乐场

于 2022-01-27T21:46:21.873 回答
0

这就是我最终得到的结果,它允许我根据 toml 文件中指定的内容有条件地加载结构,并且仍然使用接口将其视为结构不可知论。

如果有人有任何关于将其重构为更惯用的技巧,请告诉我!

type ProviderService interface {
    DoAThing()
}

type Alpha struct {
    Apikey string `mapstructure:"apikey"`
    Domain string `mapstructure:"domain"`
}

type Beta struct {
    Username string `mapstructure:"username"`
    Password string `mapstructure:"password"`
    Domain   string `mapstructure:"domain"`
}

func main() {
    provider := loadConfig()
    if provider == nil {
        log.Fatal("unable to parse config file")
    }

    provider.DoAThing()
}

func (a Alpha) DoAThing() {
    fmt.Println("domain", a.Domain)
}
func (b Beta) DoAThing() {
    fmt.Println("domain", b.Domain)
}

func loadConfig() ProviderService {
    viper.AddConfigPath("./")
    viper.SetConfigName("config")
    viper.SetConfigType("toml")

    err := viper.ReadInConfig()
    if err != nil {
        panic(fmt.Errorf("Fatal error config file: %w \n", err))
    }

    var a Alpha
    _ = viper.UnmarshalKey("alpha", &a)
    if a != (Alpha{}) {
        return a
    }

    var b Beta
    _ = viper.UnmarshalKey("beta", &b)
    if b != (Beta{}) {
        return b
    }

    return nil
}
于 2022-01-31T20:26:19.133 回答