Viper是一个用于Go语言应用程序的配置管理库。它提供了一种简单而灵活的方式来处理应用程序的配置,支持多种格式的配置文件,并提供了一组API来读取和使用这些配置。
Viper支持多种配置文件格式,包括JSON、TOML、YAML和HCL等。它还支持环境变量和命令行标志等配置方式。这使得Viper非常适合需要在不同环境中运行(如开发、测试和生产环境)的应用程序,因为可以使用不同的配置文件和设置来管理应用程序的行为。
优点:使用Viper,可以轻松地将配置信息加载到应用程序中,并在需要时获取这些信息。Viper还提供了一些方便的功能,例如默认值、类型转换和键名重映射等,使得配置管理变得更加简单和灵活。
在根目录下创建一个core文件,然后在core文件中创建多一个interal文件,这个interal
文件的方法仅能让core里进行调用的,一般可以将一些仅给core内方法使用并且是需要封装的方法放置在内(调取internal包中的方法不需要添加core包名 )
。如图大概就是这样的,然后就是定义一个viper的interface
,实现内部的GetFile
和GetFiles
两个方法,前者是配置信息,后者是文件夹路径。当然我们也可以使用embed
这样方式实现,但是最好本地也实现一个比较稳妥一点!
core/interal/viper_interface.go
type IViper interface {
// GetFile 获取文件信息
GetFile(path, filename string) io.Reader
// GetFiles 获取配置文件夹信息
GetFiles(dir string) ([]fs.DirEntry, error)
}
core/interal/viper.go
var Viper = new(viper)
type viper struct{}
func (v *viper) GetFile(path, filename string) io.Reader {
file, err := os.Open(filepath.Join(path, filename))
if err != nil {
return nil
}
defer func() {
_ = file.Close()
}()
all, err := io.ReadAll(file)
if err != nil {
return nil
}
return bytes.NewReader(all)
}
func (v *viper) GetFiles(dir string) ([]os.DirEntry, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, errors.Wrapf(err, "[viper][path:%s]获取配置文件夹信息失败!", dir)
}
return entries, nil
}
core/interal/vuper_embed.go (实现本地embed标记,当然推荐使用下面本地实现的方法)
var Viper = new(viper)
type viper struct{}
func (v *viper) GetFile(path, filename string) io.Reader {
file, err := global.Configs.Open(filepath.Join(path, filename))
if err != nil {
fmt.Printf("[viper][path:%s][filename:%s]文件不存在!\n", path, filename)
return nil
}
return file
}
func (v *viper) GetFiles(dir string) ([]fs.DirEntry, error) {
entries, err := global.Configs.ReadDir(dir)
if err != nil {
return nil, errors.Wrapf(err, "[viper][embed][dir:%s]获取配置文件夹信息失败!", dir)
}
return entries, nil
}
core/viper.go
我这里的config文件夹中的配置yaml格式如:gorm.debug.yaml
var Viper = new(_viper)
type _viper struct{}
// Initialization .
// 优先级: 命令行 > 环境变量 > 默认值
func (c *_viper) Initialization(path ...string) {
var configs string
if len(path) == 0 {
flag.StringVar(&configs, "c", "", "choose configs dir.")
flag.Parse()
if configs == "" {
env := os.Getenv(internal.ConfigsEnv)
if env == "" { // 判断 internal.ConfigEnv 常量存储的环境变量是否为空
configs = internal.ConfigsPath
fmt.Printf("您正在使用配置默认文件夹:%s,configs的文件夹路径为%s\n", internal.ConfigsPath, configs)
} else {
configs = env
fmt.Printf("您正在使用%s环境变量,configs的文件夹路径为%s\n", internal.ConfigsEnv, configs)
}
} else { // 命令行参数不为空 将值赋值于configs
fmt.Printf("您正在使用命令行的-c参数传递的值,configs的文件夹路径为%s\n", configs)
}
} else { // path 这个切片大于0,取第一个值赋值到configs
configs = path[0]
}
v := viper.New()
v.AddConfigPath(configs)
entries, err := internal.Viper.GetFiles(configs)
if err != nil {
fmt.Printf("%+v\n", err)
return
}
for i := 0; i < len(entries); i++ {
if entries[i].IsDir() { // 忽略配置文件夹里的文件夹
continue
}
filename := entries[i].Name()
// 分割文件名
names := strings.Split(filename, ".")
if len(names) == 3 {
config := names[0] // 文件名
mode := names[1] // 模式
yaml := names[2] // 文件后缀
if mode != gin.Mode() {
continue
}
// 拼接
v.SetConfigName(strings.Join([]string{config, mode}, "."))
v.SetConfigType(yaml)
reader := internal.Viper.GetFile(configs, filename)
err = v.MergeConfig(reader)
if err != nil {
fmt.Printf("[viper][filename:%s][err:%v]配置文件读取失败!\n", filename, err)
return
}
// 读取配置文件
err = v.ReadInConfig()
if err != nil {
fmt.Printf("[viper][filename:%s][err:%v]配置文件读取失败!\n", filename, err)
continue
}
// 反序列化config
err = v.Unmarshal(&global.Config)
if err != nil {
fmt.Printf("[viper][err:%v]反序列化失败!\n", err)
continue
}
v.OnConfigChange(func(in fsnotify.Event) {
fmt.Printf("[viper][filename:%s]配置文件更新\n", in.Name)
err = v.Unmarshal(&global.Config)
if err != nil {
fmt.Printf("[viper][err:%v]反序列化失败!\n", err)
}
})
v.WatchConfig()
}
}
// 注册到全局
global.Viper = v
}
如果在自己练习的时候已在编辑器中配置了这个embed标记可以忽略上述本地的viper_embed.go
文件(如下图配置所示)
,但是建议不忽略上述的viper_embed.go
文件
package main
// 这些有import导报
// import (......)
func init() {
global.Configs = configs
}
var (
//go:embed configs
configs embed.FS
)
global/global.go
var (
Viper *viper.Viper
)