golang环境变量、配置文件和标签初始化配置结构体插件库configuration的使用

Golang环境变量、配置文件和标签初始化配置结构体插件库configuration的使用

概述

Configuration是一个用于将值递归注入结构体的库,是设置配置对象的便捷方式。主要特性包括:

  • 为结构体字段设置默认值 - NewDefaultProvider()
  • 从环境变量设置值 - NewEnvProvider()
  • 从命令行标志设置值 - NewFlagProvider()
  • 从JSON文件设置值 - NewJSONFileProvider("./testdata/input.json")

支持的类型

  • string, *string, []string, []*string
  • bool, *bool, []bool, []*bool
  • 所有整数类型及其切片
  • 所有无符号整数类型及其切片
  • 浮点类型及其切片
  • time.Duration (支持如12ms, 2s等格式)
  • 嵌入式结构体和结构体指针
  • 任何实现了FieldSetter接口的自定义类型

快速开始

导入路径:github.com/BoRuDar/configuration/v5

示例代码

// 定义配置结构体
type Conf struct {
    Name     string `flag:"name"`
    LastName string `default:"defaultLastName"`
    Age      byte   `env:"AGE_ENV"               default:"-1"`
    BoolPtr  *bool  `default:"false"`
    ObjPtr   *struct {
        F32       float32       `default:"32"`
        StrPtr    *string       `default:"str_ptr_test"`
        HundredMS time.Duration `default:"100ms"` // nolint:stylecheck
    }
    Obj struct {
        IntPtr     *int16   `default:"123"`
        Beta       int      `file_json:"inside.beta"   default:"24"`
        StrSlice   []string `default:"one;two"`
        IntSlice   []int64  `default:"3; 4"`
        unexported string   // 未导出字段会被忽略
    }
    URLs   []*string `default:"http://localhost:3000;1.2.3.4:8080"`
    HostIP ipTest    `default:"127.0.0.3"`
}

// 初始化配置,执行顺序会被保留
cfg, err := New[Conf](  // 指定返回的结构体类型[T]
    NewFlagProvider(),             // 第1个执行
    NewEnvProvider(),              // 第2个执行
    NewJSONFileProvider(fileName), // 第3个执行
    NewDefaultProvider(),          // 第4个执行
)
if err != nil {
    t.Fatalf("unexpected error: %v", err)
}

如果只需要环境变量和默认值,可以使用简写形式:

cfg, err := configuration.FromEnvAndDefault[T]()

提供者(Providers)

可以指定一个或多个提供者,它们会按照定义的顺序执行:

[]Provider{
    NewFlagProvider(),     // 1
    NewEnvProvider(),      // 2
    NewDefaultProvider(),  // 3
}

重要:如果提供者成功设置了值,后续的提供者将不会执行。如果没有任何提供者能设置值,将返回错误。

自定义提供者

可以定义自定义提供者,需要实现以下接口:

type Provider interface {
    Name() string
    Tag() string
    Init(ptr any) error
    Provide(field reflect.StructField, v reflect.Value) error
}

默认提供者

查找default标签并从中设置值:

struct {
    // ...
    Name string `default:"defaultName"`
    // ...
}

环境变量提供者

查找env标签并尝试从环境变量中获取值:

struct {
    // ...
    Age      byte   `env:"AGE"`
    // ...
}

环境变量名称必须全部大写:

bad_env_var_name=bad
Also_Bad_Env_Var_Name=bad
GOOD_ENV_VAR_NAME=good

标志提供者

查找flag标签并尝试从命令行标志设置值:

struct {
    // ...
    Name     string `flag:"first_name|default_value|Description"`
    // ...
}

default_valuedescription部分是可选的,可以省略。

JSON文件提供者

需要file_json:"<path_to_json_field>"标签:

NewJSONFileProvider("./testdata/input.json")

例如,标签file_json:"cache.retention"假设你的JSON文件结构如下:

{
  "cache": {
    "retention": 1
  }
}

FieldSetter接口

可以定义如何设置任何自定义类型的字段:

type FieldSetter interface {
    SetField(field reflect.StructField, val reflect.Value, valStr string) error
}

示例:

type ipTest net.IP

func (it *ipTest) SetField(_ reflect.StructField, val reflect.Value, valStr string) error {
    i := ipTest(net.ParseIP(valStr))

    if val.Kind() == reflect.Pointer {
        val.Set(reflect.ValueOf(&i))
    } else {
        val.Set(reflect.ValueOf(i))
    }

    return nil
}

更多关于golang环境变量、配置文件和标签初始化配置结构体插件库configuration的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang环境变量、配置文件和标签初始化配置结构体插件库configuration的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 配置管理:环境变量、配置文件和标签初始化

在 Go 项目中,管理配置通常涉及多种方式,包括环境变量、配置文件和结构体标签。下面我将介绍这些方法以及如何使用流行的配置管理库。

1. 环境变量配置

使用标准库 os 可以轻松读取环境变量:

package main

import (
	"fmt"
	"os"
)

func main() {
	// 设置环境变量(通常在系统或容器中设置)
	os.Setenv("APP_PORT", "8080") // 仅为演示,实际不应在代码中设置

	// 获取环境变量
	port := os.Getenv("APP_PORT")
	if port == "" {
		port = "3000" // 默认值
	}
	fmt.Printf("Server will run on port: %s\n", port)
}

2. 配置文件解析

JSON 配置文件

package main

import (
	"encoding/json"
	"fmt"
	"os"
)

type Config struct {
	Database struct {
		Host     string `json:"host"`
		Port     int    `json:"port"`
		Username string `json:"username"`
		Password string `json:"password"`
	} `json:"database"`
	Server struct {
		Port int `json:"port"`
	} `json:"server"`
}

func main() {
	file, err := os.Open("config.json")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	var config Config
	decoder := json.NewDecoder(file)
	err = decoder.Decode(&config)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Database host: %s\n", config.Database.Host)
	fmt.Printf("Server port: %d\n", config.Server.Port)
}

YAML 配置文件

需要先安装 gopkg.in/yaml.v3 包:

package main

import (
	"fmt"
	"os"

	"gopkg.in/yaml.v3"
)

type Config struct {
	Database struct {
		Host     string `yaml:"host"`
		Port     int    `yaml:"port"`
		Username string `yaml:"username"`
		Password string `yaml:"password"`
	} `yaml:"database"`
	Server struct {
		Port int `yaml:"port"`
	} `yaml:"server"`
}

func main() {
	data, err := os.ReadFile("config.yaml")
	if err != nil {
		panic(err)
	}

	var config Config
	err = yaml.Unmarshal(data, &config)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Database host: %s\n", config.Database.Host)
	fmt.Printf("Server port: %d\n", config.Server.Port)
}

3. 使用结构体标签初始化配置

结合环境变量和结构体标签可以创建灵活的配置系统:

package main

import (
	"fmt"
	"os"
	"reflect"
	"strconv"
	"strings"
)

type Config struct {
	Database struct {
		Host     string `env:"DB_HOST" default:"localhost"`
		Port     int    `env:"DB_PORT" default:"5432"`
		Username string `env:"DB_USER" default:"postgres"`
		Password string `env:"DB_PASS" default:""`
	}
	Server struct {
		Port int `env:"APP_PORT" default:"8080"`
	}
}

func LoadConfig(cfg interface{}) error {
	val := reflect.ValueOf(cfg).Elem()
	return parseConfig(val)
}

func parseConfig(val reflect.Value) error {
	typ := val.Type()

	for i := 0; i < val.NumField(); i++ {
		field := val.Field(i)
		fieldType := typ.Field(i)

		// 如果是嵌套结构体,递归处理
		if field.Kind() == reflect.Struct {
			if err := parseConfig(field); err != nil {
				return err
			}
			continue
		}

		// 获取标签
		envTag := fieldType.Tag.Get("env")
		defaultTag := fieldType.Tag.Get("default")

		// 从环境变量获取值
		envValue := os.Getenv(envTag)
		if envValue == "" {
			envValue = defaultTag
		}

		// 根据字段类型设置值
		switch field.Kind() {
		case reflect.String:
			field.SetString(envValue)
		case reflect.Int:
			intValue, err := strconv.Atoi(envValue)
			if err != nil {
				return fmt.Errorf("invalid integer value for %s: %v", envTag, err)
			}
			field.SetInt(int64(intValue))
		case reflect.Bool:
			boolValue, err := strconv.ParseBool(strings.ToLower(envValue))
			if err != nil {
				return fmt.Errorf("invalid boolean value for %s: %v", envTag, err)
			}
			field.SetBool(boolValue)
		}
	}

	return nil
}

func main() {
	var config Config
	if err := LoadConfig(&config); err != nil {
		panic(err)
	}

	fmt.Printf("Database config: %+v\n", config.Database)
	fmt.Printf("Server config: %+v\n", config.Server)
}

4. 使用配置管理库

使用 viper 库

Viper 是一个流行的配置解决方案,支持多种配置源:

package main

import (
	"fmt"
	"log"

	"github.com/spf13/viper"
)

type Config struct {
	Database struct {
		Host     string `mapstructure:"host"`
		Port     int    `mapstructure:"port"`
		Username string `mapstructure:"username"`
		Password string `mapstructure:"password"`
	} `mapstructure:"database"`
	Server struct {
		Port int `mapstructure:"port"`
	} `mapstructure:"server"`
}

func main() {
	viper.SetConfigName("config") // 配置文件名称(不带扩展名)
	viper.SetConfigType("yaml")   // 配置文件类型
	viper.AddConfigPath(".")      // 配置文件路径

	// 读取环境变量
	viper.AutomaticEnv()
	viper.SetEnvPrefix("APP") // 设置环境变量前缀
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

	// 设置默认值
	viper.SetDefault("database.host", "localhost")
	viper.SetDefault("database.port", 5432)
	viper.SetDefault("server.port", 8080)

	// 读取配置文件
	if err := viper.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			log.Println("Config file not found, using defaults and environment variables")
		} else {
			log.Fatalf("Error reading config file: %v", err)
		}
	}

	var config Config
	if err := viper.Unmarshal(&config); err != nil {
		log.Fatalf("Unable to decode into struct: %v", err)
	}

	fmt.Printf("Database config: %+v\n", config.Database)
	fmt.Printf("Server config: %+v\n", config.Server)
}

使用 koanf 库

Koanf 是另一个灵活的配置管理库:

package main

import (
	"fmt"
	"log"

	"github.com/knadh/koanf"
	"github.com/knadh/koanf/parsers/yaml"
	"github.com/knadh/koanf/providers/env"
	"github.com/knadh/koanf/providers/file"
	"github.com/knadh/koanf/providers/structs"
)

type Config struct {
	Database struct {
		Host     string `koanf:"host"`
		Port     int    `koanf:"port"`
		Username string `koanf:"username"`
		Password string `koanf:"password"`
	} `koanf:"database"`
	Server struct {
		Port int `koanf:"port"`
	} `koanf:"server"`
}

func main() {
	var k = koanf.New(".")

	// 加载默认值
	if err := k.Load(structs.Provider(Config{
		Database: struct {
			Host     string
			Port     int
			Username string
			Password string
		}{
			Host:     "localhost",
			Port:     5432,
			Username: "postgres",
		},
		Server: struct {
			Port int
		}{
			Port: 8080,
		},
	}, "koanf"), nil); err != nil {
		log.Fatalf("Error loading defaults: %v", err)
	}

	// 加载配置文件
	if err := k.Load(file.Provider("config.yaml"), yaml.Parser()); err != nil {
		log.Printf("Error loading config file: %v", err)
	}

	// 加载环境变量
	if err := k.Load(env.Provider("APP_", ".", func(s string) string {
		return strings.Replace(strings.ToLower(
			strings.TrimPrefix(s, "APP_")), "_", ".", -1)
	}), nil); err != nil {
		log.Printf("Error loading env vars: %v", err)
	}

	var config Config
	if err := k.Unmarshal("", &config); err != nil {
		log.Fatalf("Error unmarshaling config: %v", err)
	}

	fmt.Printf("Database config: %+v\n", config.Database)
	fmt.Printf("Server config: %+v\n", config.Server)
}

最佳实践建议

  1. 配置优先级:通常环境变量 > 配置文件 > 默认值
  2. 敏感信息:密码等敏感信息应通过环境变量或密钥管理系统提供
  3. 配置验证:加载配置后应进行验证
  4. 热重载:生产环境中可能需要配置热重载功能
  5. 文档:为所有配置选项提供清晰的文档

以上方法可以根据项目需求组合使用,构建灵活可靠的配置管理系统。

回到顶部