在实际开发中,我们往往会给一个逻辑设计一套配置文件,用于根据不同环境加载不同配置。
比如生产环境和测试环境数据库的地址不一样,我们就需要在配置文件中设置不同的值。但是配置文件中又有一些相同值的配置项,比如数据库的名称等。难道相同的配置要像下面这样写多次吗?
version: 1
pro:
write_pool:
url: pro-write.com
port: 11
port: 5432
user_name: write_pool_user_name
password: write_pool_password
dbname: dbname
max_idle_Conns: 1
max_open_conns: 1
conn_max_lifetime_seconds: 3
user:
username: un
password: pwd
read_pool:
url: pro-read.com
port: 5432
user_name: read_pool_user_name
password: read_pool_password
dbname: dbname
max_idle_Conns: 1
max_open_conns: 1
conn_max_lifetime_seconds: 3
user:
username: un
password: pwd
dev:
write_pool:
url: dev-write.com
port: 11
port: 5432
user_name: write_pool_user_name
password: write_pool_password
dbname: dbname
max_idle_Conns: 1
max_open_conns: 1
conn_max_lifetime_seconds: 3
user:
username: un
password: pwd
read_pool:
url: dev-read.com
port: 5432
user_name: read_pool_user_name
password: read_pool_password
dbname: dbname
max_idle_Conns: 1
max_open_conns: 1
conn_max_lifetime_seconds: 3
user:
username: un
password: pwd
一种简单的办法是:我们设置一个默认项(default)用于填充相同的值,然后在不同环境中填充不同的值。比如下例:
# db.yaml
version: 1
pro:
write_pool:
url: pro-write.com
port: 11
read_pool:
url: pro-read.com
pre:
write_pool:
url: pre-write.com
read_pool:
url: pre-read.com
test:
write_pool:
url: test-write.com
read_pool:
url: test-read.com
dev:
write_pool:
url: dev-write.com
read_pool:
url: dev-read.com
default:
write_pool:
port: 5432
user_name: write_pool_user_name
password: write_pool_password
dbname: dbname
max_idle_Conns: 1
max_open_conns: 1
conn_max_lifetime_seconds: 3
user:
username: un
password: pwd
read_pool:
port: 5432
user_name: read_pool_user_name
password: read_pool_password
dbname: dbname
max_idle_Conns: 1
max_open_conns: 1
conn_max_lifetime_seconds: 3
user:
username: un
password: pwd
这样我们在取pro、pre、dev和test环境的配置时,会让它们和default取合集,从而变成一个完整的配置。
具体实现如下:
package configparser
import (
"fmt"
"os"
"reflect"
"gopkg.in/yaml.v3"
)
type Config struct {
Version string `yaml:"version"`
Pro interface{} `yaml:"pro"`
Pre interface{} `yaml:"pre"`
Dev interface{} `yaml:"dev"`
Test interface{} `yaml:"test"`
Default interface{} `yaml:"default"`
}
const (
ErrorEnvNotfound = "env [%s] not found"
KeyFieldTag = "yaml"
)
func LoadConfigFromFile(filePath string, env string) (string, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
return LoadConfigFromMemory(data, env)
}
func LoadConfigFromMemory(configure []byte, env string) (string, error) {
var config Config
err := yaml.Unmarshal(configure, &config)
if err != nil {
return "", err
}
configReflectType := reflect.TypeOf(config)
for i := 0; i < configReflectType.NumField(); i++ {
structTag := configReflectType.Field(i).Tag.Get(KeyFieldTag)
if structTag == env {
envConfigReflect := reflect.ValueOf(config).Field(i).Interface()
defauleConfigReflectType := config.Default
if envConfigReflect == nil && defauleConfigReflectType == nil {
return "", fmt.Errorf(ErrorEnvNotfound, env)
}
if envConfigReflect == nil {
defaultConf, err := yaml.Marshal(config.Default)
if err != nil {
return "", err
}
return string(defaultConf), nil
}
if defauleConfigReflectType == nil {
envConf, err := yaml.Marshal(reflect.ValueOf(config).Field(i).Interface())
if err != nil {
return "", err
}
return string(envConf), nil
}
merged := mergeMapStringInterface(reflect.ValueOf(config).Field(i).Interface().(map[string]interface{}), config.Default.(map[string]interface{}))
mergedConf, err := yaml.Marshal(merged)
if err != nil {
return "", err
}
return string(mergedConf), nil
}
}
return "", fmt.Errorf(ErrorEnvNotfound, env)
}
func mergeMapStringInterface(cover map[string]interface{}, base map[string]interface{}) map[string]interface{} {
for k, v := range cover {
switch v.(type) {
case map[string]interface{}:
if base[k] == nil {
base[k] = v
} else {
mergeMapStringInterface(v.(map[string]interface{}), base[k].(map[string]interface{}))
}
default:
base[k] = v
}
}
return base
}
package main
import (
"fmt"
configparser "gconf"
"os"
"path"
"gopkg.in/yaml.v3"
)
type PostgresSqlConnConfigs struct {
WritePool PostgresSqlConnPoolConf `yaml:"write_pool"`
ReadPool PostgresSqlConnPoolConf `yaml:"read_pool"`
}
type PostgresSqlConnPoolConf struct {
Url *string `yaml:"url"`
Port *string `yaml:"port"`
UserName *string `yaml:"user_name"`
Password *string `yaml:"password"`
DbName *string `yaml:"dbname"`
MaxIdleConn *int `yaml:"max_idle_Conns"`
MaxOpenConn *int `yaml:"max_open_conns"`
ConnMaxLifetimeSeconds *int64 `yaml:"conn_max_lifetime_seconds"`
UserA *User `yaml:"user"`
}
type User struct {
Username *string `yaml:"username"`
Password *string `yaml:"password"`
}
func main() {
runPath, _ := os.Getwd()
confPath := path.Join(runPath, "conf/db.yaml")
env := []string{"dev", "test", "pre", "pro"}
for _, v := range env {
var conf ExampleConfig
curConfig, err := configparser.LoadConfigFromFile(confPath, v)
if err != nil {
fmt.Printf("load config file failed, err: %v", err)
}
err = yaml.Unmarshal([]byte(curConfig), &conf)
if err != nil {
fmt.Printf("unmarshal config file failed, err: %v", err)
}
fmt.Printf("%s\nconfig: %v\n", v, conf)
}
}