golang基于结构体标签的配置管理插件GoCfg的使用

Golang基于结构体标签的配置管理插件GoCfg的使用

GoCfg简介

GoCfg是一个基于结构体标签的配置管理插件,支持从环境变量、.env文件等多种来源解析配置到结构体。

主要特性

  • 支持从环境变量、.env文件等来源解析配置到结构体
  • 可以通过标签设置字段的默认值
  • 支持自定义解析器
  • 支持自定义值提供器
  • 自动生成文档

快速开始

安装包

go get -u github.com/Jagerente/gocfg

基本用法

package main

import (
	"github.com/Jagerente/gocfg"
	"github.com/Jagerente/gocfg/pkg/parsers"
	"github.com/Jagerente/gocfg/pkg/values"
	"time"
)

type LoggerConfig struct {
	LogLevel string `env:"LOG_LEVEL" default:"debug"`
}

type RedisConfig struct {
	RedisHost     string `env:"REDIS_HOST" default:"localhost"`
	RedisPort     uint16 `env:"REDIS_PORT" default:"6379"`
	RedisUser     string `env:"REDIS_USER,omitempty"`
	RedisPassword string `env:"REDIS_PASS"`
	RedisDatabase string `env:"REDIS_DATABASE"`
}

type AppConfig struct {
	// 支持的标签:
	// - env: 指定环境变量名
	// - default: 指定字段的默认值
	// - example: 为文档生成指定示例值
	// - omitempty: 允许空字段
	//              如果解析值和默认值都为空,
	//              该字段将被设置为Go类型的零值
	// - description: 为文档生成描述字段
	// - title: 为嵌套结构体文档指定标题

	LogLevel          LoggerConfig
	RedisConfig       RedisConfig
	BoolField         bool          `env:"BOOL_FIELD"`
	StringField       string        `env:"STRING_FIELD"`
	IntField          int           `env:"INT_FIELD"`
	Int8Field         int8          `env:"INT8_FIELD"`
	Int16Field        int16         `env:"INT16_FIELD"`
	Int32Field        int32         `env:"INT32_FIELD"`
	Int64Field        int64         `env:"INT64_FIELD"`
	UintField         uint          `env:"UINT_FIELD"`
	Uint8Field        uint8         `env:"UINT8_FIELD"`
	Uint16Field       uint16        `env:"UINT16_FIELD"`
	Uint32Field       uint32        `env:"UINT32_FIELD"`
	Uint64Field       uint64        `env:"UINT64_FIELD"`
	Float32Field      float32       `env:"FLOAT32_FIELD"`
	Float64Field      float64       `env:"FLOAT64_FIELD"`
	TimeDurationField time.Duration `env:"TIME_DURATION_FIELD"`
	ByteSliceField    []byte        `env:"BYTE_SLICE_FIELD"`
	StringSliceField  []string      `env:"STRING_SLICE_FIELD" default:"string1,string2,string3"`
	IntSliceField     []int         `env:"INT_SLICE_FIELD" default:"3,2,1,0,-1,-2,-3"`
	EmptyField        string        `env:"EMPTY_FIELD,omitempty"`
	WithDefaultField  string        `env:"WITH_DEFAULT_FIELD" default:"ave"`
}

func main() {
	cfg := gocfg.NewDefault()

	// 等同于
	cfg = gocfg.NewEmpty().
		UseDefaults().
		AddParserProviders(parsers.NewDefaultParserProvider()).
		AddValueProviders(values.NewEnvProvider())

	appConfig := new(AppConfig)
	if err := cfg.Unmarshal(appConfig); err != nil {
		panic(err)
	}
}

默认类型解析器

默认支持以下类型解析:

  • time.Duration
  • bool
  • string
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • float32, float64
  • 切片: bytes, strings, ints

.env文件支持

package main

import (
	"github.com/Jagerente/gocfg"
	"github.com/Jagerente/gocfg/pkg/parsers"
	"github.com/Jagerente/gocfg/pkg/values"
)

type AppConfig struct {
	BoolField   bool   `env:"BOOL_FIELD"`
	StringField string `env:"STRING_FIELD"`
	IntField    int    `env:"INT_FIELD"`
}

func main() {
	// 使用默认的'.env'文件
	dotEnvProvider, _ := values.NewDotEnvProvider()

	// 使用自定义env文件路径
	dotEnvProvider, _ = values.NewDotEnvProvider("local.env")

	// 使用多个env文件
	dotEnvProvider, _ = values.NewDotEnvProvider("local.env", "dev.env")

	cfg := gocfg.NewDefault().
		AddValueProviders(dotEnvProvider)

	// 等同于
	cfg = gocfg.NewEmpty().
		UseDefaults().
		AddParserProviders(parsers.NewDefaultParserProvider()).
		AddValueProviders(
			values.NewEnvProvider(),
			dotEnvProvider,
		)

	appConfig := new(AppConfig)
	if err := cfg.Unmarshal(appConfig); err != nil {
		panic(err)
	}
}

自定义键标签

package main

import (
	"github.com/Jagerente/gocfg"
)

type AppConfig struct {
	BoolField   bool   `mapstructure:"BOOL_FIELD"`
	StringField string `mapstructure:"STRING_FIELD"`
	IntField    int    `mapstructure:"INT_FIELD"`
}

func main() {
	cfg := gocfg.NewDefault().
		UseCustomKeyTag("mapstructure")

	appConfig := new(AppConfig)
	if err := cfg.Unmarshal(appConfig); err != nil {
		panic(err)
	}
}

自定义解析器提供器

package main

import (
	"github.com/Jagerente/gocfg"
	"reflect"
	"time"
)

type CustomParserProvider struct {
}

func NewCustomParserProvider() *CustomParserProvider {
	return &CustomParserProvider{}
}

func (p *CustomParserProvider) Get(field reflect.Value) (func(v string) (any, error), bool) {
	switch field.Type() {
	case reflect.TypeOf(time.Duration(83)):
		return func(v string) (any, error) {
			return time.ParseDuration(v)
		}, true
	default:
		return nil, false
	}
}

type AppConfig struct {
	BoolField   bool   `env:"BOOL_FIELD"`
	StringField string `env:"STRING_FIELD"`
	IntField    int    `env:"INT_FIELD"`
}

func main() {
	customParserProvider := NewCustomParserProvider()

	cfg := gocfg.NewDefault().
		AddParserProviders(customParserProvider)

	appConfig := new(AppConfig)
	if err := cfg.Unmarshal(appConfig); err != nil {
		panic(err)
	}
}

自定义值提供器

package main

import (
	"github.com/Jagerente/gocfg"
	"os"
)

type CustomValueProvider struct {
}

func NewCustomValueProvider() *CustomValueProvider {
	return &CustomValueProvider{}
}

func (p *CustomValueProvider) Get(key string) string {
	return os.Getenv("CUSTOM_" + key)
}

type AppConfig struct {
	BoolField   bool   `env:"BOOL_FIELD"`
	StringField string `env:"STRING_FIELD"`
	IntField    int    `env:"INT_FIELD"`
}

func main() {
	customValueProvider := NewCustomValueProvider()

	cfg := gocfg.NewDefault().
		AddValueProviders(customValueProvider)

	appConfig := new(AppConfig)
	if err := cfg.Unmarshal(appConfig); err != nil {
		panic(err)
	}
}

文档生成

  1. 首先创建一个配置文件 /internal/config/config.go:
package config

import (
	"github.com/Jagerente/gocfg"
	"github.com/Jagerente/gocfg/pkg/values"
	"time"
	cache_factory "your_cool_app/internal/router/cache"
)

type LoggerConfig struct {
	LogLevel     int  `env:"LOG_LEVEL" default:"6" example:"4" description:"https://pkg.go.dev/github.com/sirupsen/logrus@v1.9.3#Level"`
	ReportCaller bool `env:"REPORT_CALLER" default:"true" example:"false"`
	LogFormatter int  `env:"LOG_FORMATTER" default:"0" example:"1"`
}

type CassandraConfig struct {
	CassandraHosts    string `env:"CASSANDRA_HOSTS" default:"127.0.0.1" example:"cassandra.example.com"`
	CassandraKeyspace string `env:"CASSANDRA_KEYSPACE" default:"user_data_service" example:"production_keyspace"`
}

type RouterConfig struct {
	ServerPort               uint16        `env:"SERVER_PORT" default:"8080" example:"3000"`
	Debug                    bool          `env:"ROUTER_DEBUG" default:"true" example:"false"`
	CacheAdapter             string        `env:"CACHE_ADAPTER,omitempty" example:"redis" description:"Leave blank to not use.\nPossible values:\n- redis\n- memcache"`
	CacheAdapterTTL          time.Duration `env:"CACHE_ADAPTER_TTL,omitempty" default:"1m" example:"5m"`
	CacheAdapterNoCacheParam string        `env:"CACHE_ADAPTER_NOCACHE_PARAM,omitempty" default:"no-cache" example:"skip-cache"`
}

type RedisCacheAdapterConfig struct {
	RedisAddr     string `env:"CACHE_ADAPTER_REDIS_ADDR,omitempty" default:":6379"`
	RedisDB       int    `env:"CACHE_ADAPTER_REDIS_DB,omitempty" default:"0"`
	RedisUsername string `env:"CACHE_ADAPTER_REDIS_USERNAME,omitempty"`
	RedisPassword string `env:"CACHE_ADAPTER_REDIS_PASSWORD,omitempty"`
}

type MemcacheCacheAdapterConfig struct {
	Capacity         int                     `env:"CACHE_ADAPTER_MEMCACHE_CAPACITY,omitempty" default:"10000000"`
	CachingAlgorithm cache_factory.Algorithm `env:"CACHE_ADAPTER_MEMCACHE_CACHING_ALGORITHM,omitempty" default:"LRU"`
}
type Config struct {
	LoggerConfig               `title:"Logger configuration"`
	RouterConfig               `title:"Router configuration"`
	RedisCacheAdapterConfig    `title:"Redis Cache Adapter configuration"`
	MemcacheCacheAdapterConfig `title:"Memcache Cache Adapter configuration"`
	CassandraConfig            `title:"Cassandra configuration"`
}

func New() (*Config, error) {
	var cfg = new(Config)

	cfgManager := gocfg.NewDefault()
	if dotEnvProvider, err := values.NewDotEnvProvider(); err == nil {
		cfgManager = cfgManager.AddValueProviders(dotEnvProvider)
	}

	if err := cfgManager.Unmarshal(cfg); err != nil {
		return nil, err
	}

	return cfg, nil
}
  1. 创建一个文档生成应用 /cmd/docs/main.go:
package main

import (
	"fmt"
	"github.com/Jagerente/gocfg"
	"github.com/Jagerente/gocfg/pkg/docgens"
	"os"
	"your_cool_app/internal/config"
)

const outputFile = ".env.dist.generated"

func main() {
	cfg := new(config.Config)

	file, err := os.Create(outputFile)
	if err != nil {
		panic(fmt.Errorf("error creating %s file: %v", outputFile, err))
	}

	cfgManager := gocfg.NewDefault()
	if err := cfgManager.GenerateDocumentation(cfg, docgens.NewEnvDocGenerator(file)); err != nil {
		panic(err)
	}
}
  1. 运行 go run cmd/docs/main.go 会生成 .env.dist.generated 文件:
# Auto-generated config

#############################
# Logger configuration
#############################

# Description:
#  https://pkg.go.dev/github.com/sirupsen/logrus@v1.9.3#Level
#
# Default: `6`
LOG_LEVEL=4

# Default: `true`
REPORT_CALLER=false

# Default: `0`
LOG_FORMATTER=1

#############################
# Router configuration
#############################

# Default: `8080`
SERVER_PORT=3000

# Default: `true`
ROUTER_DEBUG=false

# Allowed to be empty
# Description:
#  Leave blank to not use.
#  Possible values:
#  - redis
#  - memcache
CACHE_ADAPTER=redis

# Allowed to be empty
# Default: `1m`
CACHE_ADAPTER_TTL=5m

# Allowed to be empty
# Default: `no-cache`
CACHE_ADAPTER_NOCACHE_PARAM=skip-cache

#############################
# Redis Cache Adapter configuration
#############################

# Allowed to be empty
CACHE_ADAPTER_REDIS_ADDR=:6379

# Allowed to be empty
CACHE_ADAPTER_REDIS_DB=0

# Allowed to be empty
CACHE_ADAPTER_REDIS_USERNAME=

# Allowed to be empty
CACHE_ADAPTER_REDIS_PASSWORD=

#############################
# Memcache Cache Adapter configuration
#############################

# Allowed to be empty
CACHE_ADAPTER_MEMCACHE_CAPACITY=10000000

# Allowed to be empty
CACHE_ADAPTER_MEMCACHE_CACHING_ALGORITHM=LRU

#############################
# Cassandra configuration
#############################

# Default: `127.0.0.1`
CASSANDRA_HOSTS=cassandra.example.com

# Default: `user_data_service`
CASSANDRA_KEYSPACE=production_keyspace

更多关于golang基于结构体标签的配置管理插件GoCfg的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang基于结构体标签的配置管理插件GoCfg的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


GoCfg: 基于结构体标签的Go配置管理插件

GoCfg 是一个基于结构体标签的轻量级配置管理工具,它可以帮助开发者轻松地将各种配置源(如JSON、YAML、环境变量等)映射到Go结构体中。

基本特性

  1. 支持多种配置格式:JSON、YAML、TOML等
  2. 支持环境变量自动映射
  3. 支持默认值设置
  4. 支持配置校验
  5. 支持热加载

安装

go get github.com/gookit/gocfg

基本使用示例

1. 定义配置结构体

type ServerConfig struct {
    Host     string `cfg:"host" default:"localhost"`
    Port     int    `cfg:"port" default:"8080"`
    Debug    bool   `cfg:"debug" default:"false"`
    LogLevel string `cfg:"log_level" default:"info" validate:"oneof=debug info warn error"`
}

2. 从JSON文件加载配置

package main

import (
	"fmt"
	"github.com/gookit/gocfg"
)

func main() {
	cfg := &ServerConfig{}
	
	// 从JSON文件加载配置
	err := gocfg.Load(cfg, gocfg.WithFile("config.json"))
	if err != nil {
		panic(err)
	}
	
	fmt.Printf("Server running on %s:%d\n", cfg.Host, cfg.Port)
}

3. 从环境变量加载配置

// 假设有以下环境变量:
// APP_HOST=0.0.0.0
// APP_PORT=9090
// APP_DEBUG=true

type AppConfig struct {
    Host  string `cfg:"host" env:"APP_HOST" default:"localhost"`
    Port  int    `cfg:"port" env:"APP_PORT" default:"8080"`
    Debug bool   `cfg:"debug" env:"APP_DEBUG" default:"false"`
}

func main() {
	cfg := &AppConfig{}
	
	err := gocfg.Load(cfg, gocfg.WithEnv())
	if err != nil {
		panic(err)
	}
	
	fmt.Printf("App running on %s:%d, debug: %v\n", cfg.Host, cfg.Port, cfg.Debug)
}

高级用法

嵌套结构体配置

type DatabaseConfig struct {
    Host     string `cfg:"host" default:"localhost"`
    Port     int    `cfg:"port" default:"5432"`
    Username string `cfg:"username" required:"true"`
    Password string `cfg:"password" required:"true"`
}

type AppConfig struct {
    Server   ServerConfig   `cfg:"server"`
    Database DatabaseConfig `cfg:"database"`
}

func main() {
	cfg := &AppConfig{}
	
	err := gocfg.Load(cfg, 
		gocfg.WithFile("config.yaml"),
		gocfg.WithEnv(),
	)
	
	if err != nil {
		panic(err)
	}
	
	// 使用配置...
}

配置热加载

func main() {
	cfg := &ServerConfig{}
	
	// 启用热加载,每30秒检查一次配置变更
	err := gocfg.Load(cfg, 
		gocfg.WithFile("config.json"),
		gocfg.WithReload(30*time.Second),
	)
	
	if err != nil {
		panic(err)
	}
	
	// 主程序循环
	for {
		fmt.Printf("Current config: %+v\n", cfg)
		time.Sleep(10 * time.Second)
	}
}

自定义配置源

type CustomSource struct {
	data map[string]interface{}
}

func (c *CustomSource) Load() (map[string]interface{}, error) {
	return c.data, nil
}

func main() {
	cfg := &ServerConfig{}
	
	customSource := &CustomSource{
		data: map[string]interface{}{
			"host": "custom-host",
			"port": 9999,
		},
	}
	
	err := gocfg.Load(cfg, gocfg.WithSource(customSource))
	if err != nil {
		panic(err)
	}
	
	fmt.Printf("Loaded from custom source: %+v\n", cfg)
}

标签说明

GoCfg 支持以下结构体标签:

  • cfg - 指定配置字段的名称
  • default - 设置默认值
  • env - 指定环境变量名称
  • required - 设为"true"表示该字段必须提供
  • validate - 设置验证规则

最佳实践

  1. 为所有配置字段提供合理的默认值
  2. 对关键配置使用required:"true"确保配置完整性
  3. 使用validate标签添加简单的验证规则
  4. 生产环境推荐使用环境变量注入敏感配置
  5. 开发环境可以使用配置文件方便管理

GoCfg 通过简洁的API和强大的标签系统,为Go应用程序提供了灵活的配置管理方案,既适合小型项目也适用于大型分布式系统。

回到顶部