go工具类的封装——(一)配置文件工具类封装

使用方式

在后端开发中,我们经常会使用配置文件,所以我想封装出一个工具类出来,能够提供简洁的接口,让我们很方便地读取配置文件并从配置文件中提取信息。

我封装了一个工具类ConfigManager,主要有以下功能:

  • 根据配置文件的路径和文件名读取配置信息
  • 通过一系列Get函数,可以根据key查询配置项的值
  • 自动将配置项写入缓存,提高配置项查询速率
  • 自动监听配置文件变化,自动更新配置项查询结果,可以添加一个或多个钩子函数处理文件变化事件

首先来展示一下怎么使用我封装的工具类,我只能说非常非常简单,使用样例如下,先把代码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()
}

你可能感兴趣的:(golang,开发语言,后端)