Golang如何通过名称动态访问结构体属性?配置方案咨询
问题背景
我现在用Go写了一套配置结构体,简化版如下:
type Config struct { Environment string Service1 Service Service2 Service } type Service struct { CRC string Cards Cards } type Cards struct { GBP CardCfg USD CardCfg } type CardCfg struct { CRC string } func Cfg() *Config { return &Config{ Environment: os.Getenv("ENVIRONMENT"), Service1: Service{ CRC: os.Getenv("Service1_CRC"), Cards: Cards{ GBP: CardCfg{ CRC: os.Getenv("Service1_CARD_GBP_CRC"), }, USD: CardCfg{ CRC: os.Getenv("Service1_CARD_USD_CRC"), }, }, }, Service2: Service{ CRC: os.Getenv("Service2_CRC"), Cards: Cards{ GBP: CardCfg{ CRC: os.Getenv("Service2_CARD_GBP_CRC"), }, USD: CardCfg{ CRC: os.Getenv("Service2_CARD_USD_CRC"), }, }, }, } }
我想通过变量动态访问服务或对应币种的CRC,比如:
variable := "Service1" currency := "EUR" cfg := config.Cfg() crc := cfg[variable].cards[currency] // 这样写不行
我试过用map[string]interface{}实现,虽然能跑但代码很不优雅:
package main import "fmt" type Config map[string]interface{} func main() { config := Config{ "field": "value", "service1": Config{ "crc": "secret1", "cards": Config{ "crc": "secret2", }, }, } fmt.Println(config["WT"].(Config)["cards"].(Config)["crc"]) // 能运行但要强制类型断言 }
我是从Ruby转Go的,Ruby里动态访问属性很方便,想知道Go里有没有更优的写法,能不能用结构体实现动态访问?
补充:我需要接收RabbitMQ消息来创建支付,不同支付方式要对应授权信息(CRC、MerchantId),调用示例如下:
trn, err := p24Client.RegisterTrn(context.Background(), &p24.RegisterTrnReq{ CRC: cfg[payinRequested.Service].cards[payinRequested.Currency].CRC, MerchantId: cfg[payinRequested.Service].cards[payinRequested.Currency].MerchantId, PosId: cfg[payinRequested.Service].cards[payinRequested.Currency].MerchantId, SessionId: payinRequested.PaymentId, Amount: payinRequested.Amount, Currency: payinRequested.Currency, Description: payinRequested.Desc, Email: payinRequested.Email, Method: payinRequested.BankId, UrlReturn: payinRequested.ReturnUrl, UrlStatus: cfg.StatusUri, UrlCardPaymentNotification: cfg.CardStatusUri, })
解决方案
方案1:重构配置结构为Map(最推荐,类型安全+简洁)
Go是静态类型语言,直接用结构体字段动态访问不现实,但可以把固定命名的Service1/Service2、GBP/USD改成Map,这样既能保留类型安全,又支持动态键访问。
修改后的结构体和配置加载代码:
import "os" type Config struct { Environment string Services map[string]Service // 用map存所有服务 } type Service struct { CRC string Cards map[string]CardCfg // 用map存所有币种的配置 } type CardCfg struct { CRC string MerchantId string // 补充你需要的MerchantId字段 } func Cfg() *Config { return &Config{ Environment: os.Getenv("ENVIRONMENT"), Services: map[string]Service{ "Service1": { CRC: os.Getenv("Service1_CRC"), Cards: map[string]CardCfg{ "GBP": { CRC: os.Getenv("Service1_CARD_GBP_CRC"), MerchantId: os.Getenv("Service1_CARD_GBP_MERCHANT_ID"), }, "USD": { CRC: os.Getenv("Service1_CARD_USD_CRC"), MerchantId: os.Getenv("Service1_CARD_USD_MERCHANT_ID"), }, }, }, "Service2": { CRC: os.Getenv("Service2_CRC"), Cards: map[string]CardCfg{ "GBP": { CRC: os.Getenv("Service2_CARD_GBP_CRC"), MerchantId: os.Getenv("Service2_CARD_GBP_MERCHANT_ID"), }, "USD": { CRC: os.Getenv("Service2_CARD_USD_CRC"), MerchantId: os.Getenv("Service2_CARD_USD_MERCHANT_ID"), }, }, }, }, } }
动态访问示例
现在你可以直接用字符串变量访问,完全类型安全,不需要类型断言:
// 从RabbitMQ消息里拿到的参数 serviceName := payinRequested.Service currency := payinRequested.Currency cfg := Cfg() // 先检查服务和币种是否存在,避免panic service, serviceExists := cfg.Services[serviceName] if !serviceExists { // 处理服务不存在的错误 } cardCfg, currencyExists := service.Cards[currency] if !currencyExists { // 处理币种不存在的错误 } // 然后直接用 trn, err := p24Client.RegisterTrn(context.Background(), &p24.RegisterTrnReq{ CRC: cardCfg.CRC, MerchantId: cardCfg.MerchantId, PosId: cardCfg.MerchantId, // 其他字段... })
方案2:用反射实现结构体动态访问(不推荐,仅兼容旧结构时用)
如果不想改现有结构体结构,可以用Go的反射包reflect来实现动态访问,但反射性能差,代码繁琐,容易出错,仅适合必须兼容旧代码的场景。
示例工具函数:
import ( "fmt" "reflect" ) // GetServiceFromConfig 根据服务名从Config结构体中获取Service func GetServiceFromConfig(cfg *Config, serviceName string) (*Service, error) { val := reflect.ValueOf(cfg).Elem() field := val.FieldByName(serviceName) if !field.IsValid() || field.Type() != reflect.TypeOf(Service{}) { return nil, fmt.Errorf("service %s not found", serviceName) } service := field.Interface().(Service) return &service, nil } // GetCardCfgFromService 根据币种从Service中获取CardCfg func GetCardCfgFromService(service *Service, currency string) (*CardCfg, error) { val := reflect.ValueOf(service).Elem() cardsField := val.FieldByName("Cards") if !cardsField.IsValid() { return nil, fmt.Errorf("cards field not found") } cardField := cardsField.FieldByName(currency) if !cardField.IsValid() || cardField.Type() != reflect.TypeOf(CardCfg{}) { return nil, fmt.Errorf("currency %s not found", currency) } cardCfg := cardField.Interface().(CardCfg) return &cardCfg, nil }
使用示例
cfg := Cfg() service, err := GetServiceFromConfig(cfg, payinRequested.Service) if err != nil { // 处理错误 } cardCfg, err := GetCardCfgFromService(service, payinRequested.Currency) if err != nil { // 处理错误 } // 调用支付接口 trn, err := p24Client.RegisterTrn(context.Background(), &p24.RegisterTrnReq{ CRC: cardCfg.CRC, MerchantId: cardCfg.MerchantId, // 其他字段... })
方案3:封装访问方法(结合方案1更优雅)
在Config结构体上封装方法,把错误处理逻辑封装起来,上层调用更简洁:
import "fmt" func (c *Config) GetCardCfg(serviceName, currency string) (*CardCfg, error) { service, ok := c.Services[serviceName] if !ok { return nil, fmt.Errorf("service %s does not exist", serviceName) } cardCfg, ok := service.Cards[currency] if !ok { return nil, fmt.Errorf("currency %s not found for service %s", currency, serviceName) } return &cardCfg, nil }
使用示例
cfg := Cfg() cardCfg, err := cfg.GetCardCfg(payinRequested.Service, payinRequested.Currency) if err != nil { // 统一处理错误 } trn, err := p24Client.RegisterTrn(context.Background(), &p24.RegisterTrnReq{ CRC: cardCfg.CRC, MerchantId: cardCfg.MerchantId, // 其他字段... })
内容的提问来源于stack exchange,提问作者Marinso404
相关产品推荐
相关产品推荐

