在后端开发中,我们经常会使用配置文件,所以我想封装出一个工具类出来,能够提供简洁的接口,让我们很方便地读取配置文件并从配置文件中提取信息。
我封装了一个工具类ConfigManager
,主要有以下功能:
首先来展示一下怎么使用我封装的工具类,我只能说非常非常简单,使用样例如下,先把代码copy到本地,接下来调用工厂函数CreateConfigManager
,传入文件类型“yaml”、文件目录“…/config"、文件名(不包含后缀)“yaml_config”,然后就可以得到工具类的实例对象,可以获取各种配置项的值,并且实现了上述的所有功能。
func Test1(t *testing.T) {
cm, _ := config_manager.CreateConfigManager("yaml", []string{"../config"}, "yaml_config")
fmt.Println(cm.Get("Mysql"))
fmt.Println(cm.GetInt("Mysql.Port"))
fmt.Println(cm.GetString("Mysql.UserName"))
fmt.Println(cm.GetBool("Mysql.Writable"))
fmt.Println(cm.GetDuration("Mysql.Timeout").Minutes())
// 输出内容:
// map[port:3106 timeout:0.5h username:root writable:false]
// 3106
// root
// false
// 30
}
# /config/yaml_config.yml
Mysql:
Port: 3106
UserName: "root"
Writable: false
Timeout: 0.5h
面向接口编程是个好习惯,事先花点心思设计接口,可以使代码结构更清晰,也可以使自己在写代码时少走弯路,减少无意义的增增改改。
我所编写的工具类的接口如下,如果只是想使用这个工具类的话,只看接口就够了,所有的功能都在接口中得到了体现:
// CreateConfigManager 创建ConfigManager对象的工厂函数,
// configType是配置文件类型,可选json,yaml,下层用的是 github.com/spf13/viper包,更多支持的文件类型可去它文档查看
// configPath是配置文件所在目录,configName是配置文件名(不包含后缀),程序会自动扫描configPath包括的所有目录
// 但程序只会添加一个文件并读取文件里的配置,所以一个 ConfigManager 对象只能读取一个配置文件
type CreateConfigManager func(configType string, configPath []string, configName string) (ConfigManagerInterface, error)
// ConfigManagerInterface 是ConfigManager的借口,定义了所有会用到的方法
type ConfigManagerInterface interface {
// Get GetString GetBool ...... 根据key获取值
Get(key string) interface{}
GetString(key string) string
GetBool(key string) bool
GetInt(key string) int
GetInt32(key string) int32
GetInt64(key string) int64
GetFloat64(key string) float64
GetDuration(key string) time.Duration
GetStringSlice(key string) []string
// AddConfigWatcher callback在文件发生变化时会被调用
AddConfigWatcher(callback func(changeEvent fsnotify.Event))
}
代码只是实现各个接口所规定功能的过程,结合接口来读实现代码,很容易就能理解,我就不再细说了。
代码依赖两个非标准库"github.com/fsnotify/fsnotify"和"github.com/spf13/viper",想要复制到本地运行一下的,注意要安装好依赖包。
package config_manager
import (
ci "demo01/util/config_manager/config_manager_interface"
"errors"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"strings"
"sync"
"time"
)
func CreateConfigManager(configType string, configPath []string, configName string) (ci.ConfigManagerInterface, error) {
if !isTypeValid(configType) {
return nil, errors.New("unknown config type : " + configType)
}
if len(configPath) == 0 || configName == "" {
return nil, errors.New("configPath or configName can not be empty")
}
cm := &configManager{
configType: configType,
configPath: configPath,
configName: configName,
watchers: make([]func(changeEvent fsnotify.Event), 0),
vp: viper.New(),
}
cm.vp.SetConfigFile(configType)
for _, v := range configPath {
cm.vp.AddConfigPath(v)
}
cm.vp.SetConfigName(configName)
// 每次文件发生变化时,都清空缓存
cm.AddConfigWatcher(func(in fsnotify.Event) {
cm.clearCache()
})
if err := cm.vp.ReadInConfig(); err == nil {
return cm, nil
} else {
return nil, err
}
}
func isTypeValid(configType string) bool {
var allTypes = map[string]bool{
"yaml": true,
"json": true,
"toml": true,
}
return allTypes[strings.ToLower(configType)]
}
type configManager struct {
configType string
configPath []string
configName string
watchers []func(changeEvent fsnotify.Event)
cache sync.Map
vp *viper.Viper
}
// isCache doCache getCache clearCache四个函数的左右分别是:
// 判断该键值是否已缓存 缓存该键值 获取缓存值 清除所有缓存
func (c *configManager) isCache(key string) bool {
_, b := c.cache.Load(key)
return b
}
func (c *configManager) doCache(key string, value interface{}) {
c.cache.Store(key, value)
}
func (c *configManager) getCache(key string) interface{} {
v, _ := c.cache.Load(key)
return v
}
func (c *configManager) clearCache() {
var newSyncMap sync.Map
c.cache = newSyncMap
}
// Get GetString .....
// 下面这几个get函数功能大同小异,都是先查看键值是否已缓存,如果已缓存那就从缓存里读,如果没有那就从viper对象里读,读完之后写入缓存
func (c *configManager) Get(key string) interface{} {
if c.isCache(key) {
return c.getCache(key)
} else {
v := c.vp.Get(key)
c.doCache(key, v)
return v
}
}
func (c *configManager) GetString(key string) string {
if c.isCache(key) {
return c.getCache(key).(string)
} else {
v := c.vp.GetString(key)
c.doCache(key, v)
return v
}
}
func (c *configManager) GetBool(key string) bool {
if c.isCache(key) {
return c.getCache(key).(bool)
} else {
v := c.vp.GetBool(key)
c.doCache(key, v)
return v
}
}
func (c *configManager) GetInt(key string) int {
if c.isCache(key) {
return c.getCache(key).(int)
} else {
v := c.vp.GetInt(key)
c.doCache(key, v)
return v
}
}
func (c *configManager) GetInt32(key string) int32 {
if c.isCache(key) {
return c.getCache(key).(int32)
} else {
v := c.vp.GetInt32(key)
c.doCache(key, v)
return v
}
}
func (c *configManager) GetInt64(key string) int64 {
if c.isCache(key) {
return c.getCache(key).(int64)
} else {
v := c.vp.GetInt64(key)
c.doCache(key, v)
return v
}
}
func (c *configManager) GetFloat64(key string) float64 {
if c.isCache(key) {
return c.getCache(key).(float64)
} else {
v := c.vp.GetFloat64(key)
c.doCache(key, v)
return v
}
}
func (c *configManager) GetDuration(key string) time.Duration {
if c.isCache(key) {
return c.getCache(key).(time.Duration)
} else {
v := c.vp.GetDuration(key)
c.doCache(key, v)
return v
}
}
func (c *configManager) GetStringSlice(key string) []string {
if c.isCache(key) {
return c.getCache(key).([]string)
} else {
v := c.vp.GetStringSlice(key)
c.doCache(key, v)
return v
}
}
// 为配置对象添加一个钩子函数,监听数据的变化,可以添加多个
func (c *configManager) AddConfigWatcher(callback func(changeEvent fsnotify.Event)) {
c.watchers = append(c.watchers, callback)
c.vp.OnConfigChange(func(in fsnotify.Event) {
for _, v := range c.watchers {
v(in)
}
})
c.vp.WatchConfig()
}