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

2023-09-14 19:43:30

使用方式

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

我封装了一个工具类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()
}

更多推荐

什么是物联网(IoT),解释物联网的架构和应用场景

1、什么是物联网(IoT),解释物联网的架构和应用场景。物联网(IoT,InternetofThings)是一种通过互联网进行连接和监控的网络,它将各种物理设备和系统集成在一起,使得数据可以远程传输和分析。物联网的应用场景非常广泛,它可以帮助人们更好地理解和管理周围的环境和事物。物联网的架构通常由三个部分组成:感知层:

后端中间件安装与启动(Redis、Nginx、Nacos、Kafka)

后端中间件安装与启动RedisNginxNacosKafkaRedis1.打开cmd终端,进入redis文件目录2.输入redis-server.exeredis.windows.conf即可启动,不能关闭cmd窗口(端口配置方式:redis目录下的redis.windows.conf配置文件,打开文件,修改NETWO

从零开始训练大模型

Task05从零开始训练大模型目录1.预训练阶段1.1TokenizerTraining1.2LanguageModelPreTraining1.3数据集清理1.4模型效果评测2.指令微调阶段(InstructionTuningStage)2.1SelfInstruction2.2开源数据集整理2.3模型的评测方法3.

C++之容器std::queue类empty、size、front、back、push、emplace、pop、swap应用总结(二百二十四)

简介:CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长!优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.更多原创,欢迎关注:Android系统攻城狮1.前言本篇目的:C++之

【云原生】kubernetes中pod(进阶)

目录一、资源限制业务cpu内存1.1CPU资源单位1.2内存资源单位示例1示例2:二、健康检查:又称为探针(Probe)2.1探针的三种规则2.2Probe支持三种检查方法2.3示例示例1:exec方式示例3:tcpSocket方式示例4:就绪检测示例5:就绪检测2示例:启动、退出动作扩展pod的状态Container

Element树形控件使用过程中遇到的问题及解决方法

1.需求1点击编辑按钮,出现修改组织弹窗,且将点击时的组织名称返现在输入框中。思路是点击编辑按钮,取到节点点击时返回的data信息中的label进行赋值即可。<el-treestyle="margin-top:20px":data="organizationTreeData"node-key="id"default-e

mysql死锁排查及解决

MySQL死锁是在多个并发事务同时请求相同资源时发生的一种情况,其中每个事务都在等待对方释放资源,从而导致数据库无法继续执行。死锁的排查和解决通常需要以下步骤:1.检测死锁:MySQL通常会在错误日志中记录死锁信息。可以通过以下方式检测死锁:SHOWENGINEINNODBSTATUS;查找"InnoDB"部分,寻找"

Win10编译chrome

一、系统准备windows10以上版本硬盘空余空间100G以上,磁盘格式为NTFS内存8G以上,推荐32G需要科学上网卸载杀毒软件(注意重启系统)二、安装VisualStudio2022VisualStudio2022(>=17.0.0)编译chromium时需要VisualStudio的支持。在windows操作系统

Android Media3 ExoPlayer 开启缓存功能

ExoPlayer开启播放缓存功能,在下次加载已经播放过的网络资源的时候,可以直接从本地缓存加载,实现为用户节省流量和提升加载效率的作用。方法一:采用ExoPlayer缓存策略第1步:实现Exoplayer参考Exoplayer官网Releasenotes:对应关系:2.19.0(2023-07-05)--Androi

黑马JVM总结(十一)

(1)垃圾回收概述前面我们学了堆,里面有一个垃圾回收的机制(2)判断垃圾_引用计数指只要有一个对象被其他变量所引用,我们就让这个对象的计数加1,有个一变量不在引用,让它的计数减一,当这个对象的计数变为0的时候,说明没有变量引用它了,那么他就可以作为一个垃圾进行一个回收,但是引用计数存在一个弊端:存在循环引用问题:a对象

Pytorch实现MNIST字符识别

1.下载mnist.pkl.gz网址:http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz数据集文件夹路径是data2/mnist/mnist.pkl.gz2.读取数据frompathlibimportPathimportmatplotlib.py

热文推荐