golang实现配置继承与键生成的Viper封装插件piper的使用

golang实现配置继承与键生成的Viper封装插件piper的使用

简介

piper是一个简单的Viper封装插件,主要提供以下特性:

  • 单一配置来源:统一管理配置
  • 生成键结构体:避免拼写错误
  • 配置继承:支持类似Django的配置继承方式
  • 多种配置策略支持:灵活适应不同部署场景
  • 性能缓存:提高配置读取性能

为什么选择piper

如果你熟悉Django,你会发现Django的配置模块结构如下:

└── settings
    ├── base.py
    ├── dev.py
    ├── stage.py
    └── prod.py

dev.py会继承base.py,而stage.py又会继承dev.py。启动Django时可以选择特定的配置:

export DJANGO_SETTINGS_MODULE=mysite.settings.prod
django-admin runserver

在Django中访问配置的方式:

from django.conf import settings

author = settings.Author

piper提供了类似的体验,但用于Viper:

└── config
    ├── base.toml
    ├── dev.toml
    ├── stage.toml
    └── prod.toml

使用piper访问配置的方式:

import "your_project/config"

func main() {
	piper.Load("config/stage.toml")
	author = piper.GetString(config.Author)
}

安装

go get github.com/Yiling-J/piper/cmd

添加配置文件

将配置文件添加到项目的config文件夹中,通常位于项目根目录下。以下是TOML格式的示例(也支持YAML或JSON):

project
└── config
    ├── base.toml
    ├── dev.toml
    ├── stage.toml
    └── prod.toml

要支持配置继承,需要在配置文件中添加一个特殊的键pp_imports

pp_imports = ["base.toml", "dev.toml"]

piper会自动解析这个键。顺序很重要,这行表示先导入base.toml,然后导入dev.toml并合并。在所有pp_imports合并后,再导入当前文件。

配置键生成

从项目根目录运行以下命令生成代码:

go run github.com/Yiling-J/piper/cmd your_config_folder

在这个步骤中,piper会加载config文件夹中的所有文件并合并它们,然后在config文件夹下生成config.go文件,包含所有的配置键。同时你还会看到piper生成代码时显示的配置结构。

生成代码后,config文件夹应该如下所示:

└── config
    ├── base.toml
    ├── dev.toml
    ├── stage.toml
    ├── prod.toml
    └── config.go

使用piper

策略一 - 嵌入

将config文件夹嵌入到代码中,部署时生成单一可执行文件。

import (
	"github.com/Yiling-J/piper"
	"your_project/example/config"
)

//go:embed config/*
var configFS embed.FS

piper.SetFS(configFS)
piper.Load("config/stage.toml")
author := piper.GetString(config.Author)

策略二 - 嵌入并替换环境变量

将config文件夹嵌入到代码中,部署时生成单一可执行文件,并用环境变量替换敏感信息。

import (
	"github.com/Yiling-J/piper"
	"your_project/example/config"
)

//go:embed config/*
var configFS embed.FS

os.Setenv("SECRET", "qux")
piper.SetFS(configFS)
// 确保先开启AutomaticEnv再加载配置
// 这样环境变量也会被缓存,IGet*方法才能正常工作
piper.V().AutomaticEnv()
piper.Load("config/stage.toml")
secret := piper.GetString(config.Secret)

策略三 - 复制config目录

构建Docker镜像时复制config文件夹,使真实的config文件夹存在。

import (
	"github.com/Yiling-J/piper"
	"your_project/example/config"
)

piper.Load("config/stage.toml")
author := piper.GetString(config.Author)

策略四 - 混合嵌入和复制目录

嵌入config文件夹,但将一些敏感密钥保存在真实的配置文件中。

import (
	"github.com/Yiling-J/piper"
	"your_project/example/config"
)

//go:embed config/*
var configFS embed.FS

// "config/stage_with_secret.toml"不在源代码中
// 可能来自docker构建或k8s ConfigMap
piper.SetFS(configFS)
piper.Load("config/stage_with_secret.toml")
author := piper.GetString(config.Author)

真实目录优先于嵌入目录。

访问Viper

piper只是一个包装器,你可以随时获取包装的viper实例:

v := piper.V()

直接使用viper加载配置时要小心,piper可能无法正常工作。

单一piper还是多个piper?

piper开箱即用,不需要任何配置或初始化。由于大多数应用程序希望使用单一中心存储库来管理配置,piper包提供了这一功能。它类似于单例模式。

上面的所有示例都展示了使用piper的单例风格方法。

使用多个piper

你也可以为应用程序创建许多不同的piper实例。每个实例都有自己独特的配置和值集。每个实例可以从不同的配置文件读取。piper包支持的所有函数都作为piper实例的方法提供。

示例:

x := piper.New()
y := piper.New()

x.Load("config/x.toml")
y.Load("config/y.toml")

// 访问viper
// p := x.V

当使用多个piper时,需要用户自己跟踪不同的piper实例。

性能

有时你可能会发现viper有点慢,因为viper在Get时需要按以下顺序检查:flag、env、配置文件、键/值存储。如果你确定某些配置不会更改,可以使用piper的IGet*方法。piper会在Load时构建配置缓存,这些IGet*方法会直接从缓存中获取配置。

性能对比:

goos: darwin
goarch: amd64
pkg: github.com/Yiling-J/piper/integration
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz

BenchmarkGet-12           895533              1278 ns/op
BenchmarkIGet-12        19141876                61.35 ns/op

更多关于golang实现配置继承与键生成的Viper封装插件piper的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现配置继承与键生成的Viper封装插件piper的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang实现配置继承与键生成的Viper封装插件Piper

Piper简介

Piper是一个基于Viper的配置管理封装插件,提供了配置继承和动态键生成功能,使得配置管理更加灵活和强大。下面我将介绍如何使用Piper以及其核心功能实现。

安装Piper

go get github.com/spf13/viper
go get github.com/bingoohuang/piper

基本使用示例

package main

import (
	"fmt"
	"github.com/bingoohuang/piper"
)

func main() {
	// 初始化Piper
	p := piper.New("app") // app是配置文件名前缀

	// 设置默认值
	p.SetDefault("server.port", 8080)
	p.SetDefault("database.url", "localhost:3306")

	// 读取配置文件
	if err := p.ReadConfig(); err != nil {
		panic(fmt.Errorf("读取配置失败: %v", err))
	}

	// 获取配置值
	port := p.GetInt("server.port")
	fmt.Printf("Server port: %d\n", port)
}

配置继承功能

Piper支持配置继承,可以按照优先级从多个来源加载配置:

  1. 默认值
  2. 配置文件
  3. 环境变量
  4. 命令行参数
// 继承示例
p := piper.New("app").
    WithConfigType("yaml").
    WithConfigPaths("./configs", "/etc/app").
    WithEnvPrefix("APP").
    WithAutomaticEnv()

// 加载顺序:
// 1. 默认值
// 2. ./configs/app.yaml
// 3. /etc/app/app.yaml
// 4. 环境变量 APP_*
// 5. 命令行参数

动态键生成

Piper提供了动态键生成功能,可以根据模式生成配置键:

// 动态键生成示例
p.SetDefault("servers.default.port", 8080)

// 获取配置
port := p.GetInt("servers.{{.env}}.port", piper.WithVars(map[string]interface{}{
    "env": "production",
}))

fmt.Printf("Production port: %d\n", port)

完整示例

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/bingoohuang/piper"
)

func main() {
	// 模拟环境变量
	os.Setenv("APP_SERVER_PORT", "9090")
	os.Setenv("APP_ENV", "production")

	// 初始化Piper
	p := piper.New("app").
		WithConfigType("yaml").
		WithConfigPaths(".").
		WithEnvPrefix("APP").
		WithAutomaticEnv()

	// 设置默认值
	p.SetDefault("server.port", 8080)
	p.SetDefault("database.url", "localhost:3306")
	p.SetDefault("servers.development.port", 8081)
	p.SetDefault("servers.production.port", 8082)

	// 读取配置文件
	if err := p.ReadConfig(); err != nil {
		// 配置文件不存在不是错误
		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
			log.Fatalf("读取配置失败: %v", err)
		}
	}

	// 获取普通配置
	port := p.GetInt("server.port") // 从环境变量获取9090
	fmt.Printf("Server port: %d\n", port)

	// 获取动态配置
	env := p.GetString("env") // 从环境变量获取production
	dynamicPort := p.GetInt("servers.{{.env}}.port", piper.WithVars(map[string]interface{}{
		"env": env,
	}))
	fmt.Printf("Dynamic port for %s: %d\n", env, dynamicPort)

	// 获取所有设置
	all := p.AllSettings()
	fmt.Printf("All settings: %+v\n", all)
}

高级功能

配置监听与热更新

// 监听配置文件变化
p.WatchConfig()
p.OnConfigChange(func(e fsnotify.Event) {
    fmt.Println("配置已更新:", e.Name)
    // 重新加载配置逻辑
})

配置验证

// 定义配置结构体
type Config struct {
    Server struct {
        Port int `mapstructure:"port"`
    } `mapstructure:"server"`
}

// 解析到结构体并验证
var cfg Config
if err := p.Unmarshal(&cfg); err != nil {
    log.Fatalf("解析配置失败: %v", err)
}

if cfg.Server.Port == 0 {
    log.Fatal("server.port 必须配置")
}

多环境配置支持

// 根据环境加载不同配置
env := os.Getenv("APP_ENV")
if env == "" {
    env = "development"
}

p := piper.New("app").
    WithConfigName(fmt.Sprintf("app.%s", env)).
    WithConfigPaths("./configs")

总结

Piper作为Viper的封装插件,提供了以下优势:

  1. 简化的API接口
  2. 强大的配置继承机制
  3. 动态键生成功能
  4. 多环境支持
  5. 配置热更新

通过合理使用Piper,可以大大简化Golang项目的配置管理,特别是在微服务架构和云原生应用中,能够提供更加灵活和强大的配置管理能力。

注意:Piper是第三方库,实际使用时请参考其最新文档和版本,上述示例基于常见用法编写,可能需要根据实际库版本进行调整。

回到顶部