mycommon/myconf/conf.go

406 lines
8.1 KiB
Go

package myconf
import (
"fmt"
"git.makemake.in/kzkzzzz/mycommon"
pjson "github.com/knadh/koanf/parsers/json"
ptoml "github.com/knadh/koanf/parsers/toml"
pyaml "github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/confmap"
kfile "github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
"github.com/knadh/koanf/v2"
"github.com/spf13/cast"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"log"
"os"
"path/filepath"
"strings"
"sync"
)
const (
Default = "default"
envConfigFile = "APP_CONFIG_FILE"
)
var (
configInstanceMap = &sync.Map{} // 记录配置实例, 使用conf.Conf("name") 获取
defaultConf *Config
defaultConfigFile = []string{"config.yaml", "config.yml"} // 兼容yaml和yml格式
flagConfigFile string // 命令行传参指定的配置文件
)
type Config struct {
InstanceName string
ConfigFile string
kof *koanf.Koanf
lock *sync.RWMutex
koanfOpt []koanf.Option
}
type Opt func(c *Config)
func WithKoanfOpt(v ...koanf.Option) Opt {
return func(c *Config) {
c.koanfOpt = v
}
}
func WithLoadOverwrite(v bool) Opt {
return func(c *Config) {
if v == true {
c.koanfOpt = append(c.koanfOpt, koanf.WithMergeFunc(func(src, dest map[string]interface{}) error {
dest = src
return nil
}))
}
}
}
func init() {
defaultConf = New(Default)
log.SetOutput(os.Stdout)
// --config 指定配置文件
pflag.StringVar(&flagConfigFile, "config", "", "set config file")
}
// New 初始化配置 name 只是一个实例标识区分名字, 不一定是文件名称, 文件名称在loadConf的时候定义
// 由于viper在访问key时不区分大小写(会强制转为小写, https://github.com/spf13/viper/issues/373)
// 如 Get("mysql.Dsn"), Get("mysql.dsn"),
// 或者把 Unmarshal解析到 map[string]string等map格式时, key也是转为小写, 导致部分场景判断可能会有问题
// 改为使用 koanf 这个库 https://github.com/knadh/koanf 可以区分大小写
func New(name string, opts ...Opt) *Config {
cfg := &Config{
InstanceName: name,
kof: koanf.New("."),
lock: &sync.RWMutex{}, // 远程更新用到
koanfOpt: make([]koanf.Option, 0),
}
for _, opt := range opts {
opt(cfg)
}
configInstanceMap.Store(name, cfg)
return cfg
}
// Conf 根据name获取对应的实例
func Conf(name ...string) *Config {
if len(name) == 0 {
v, ok := configInstanceMap.Load(Default)
if !ok {
panic(fmt.Errorf("conf [%s] not exists", Default))
}
//vv, ok := v.(*Config)
return v.(*Config)
}
v, ok := configInstanceMap.Load(name[0])
if !ok {
panic(fmt.Errorf("conf [%s] not exists", name[0]))
}
return v.(*Config)
}
// FromViper 转化viper配置
func FromViper(v *viper.Viper) *Config {
kof := koanf.New(".")
keys := v.AllKeys() // key是 mysql.dsn, http.port 这种展平的格式, 中间默认是.分割
data := make(map[string]any, len(keys))
for _, key := range keys {
data[key] = v.Get(key)
}
// 把viper的数据转化到koanf
err := kof.Load(confmap.Provider(data, "."), nil)
if err != nil {
log.Printf("load viper map err: %s", err)
}
cfg := &Config{
kof: kof,
lock: &sync.RWMutex{}, // 远程更新用到
}
return cfg
}
// Load 加载命令行参数, 以及默认配置文件
func Load() *Config {
parseFlag()
// 命令行参数指定文件 --config 指定配置文件
if flagConfigFile != "" {
LoadFile(flagConfigFile)
} else {
if v := getDefaultConfigFile(); v != "" {
LoadFile(v)
}
}
// 命令行参数后加载, 可以覆盖文件配置
LoadFlag()
return defaultConf
}
// LoadFlag 加载命令行参数
func LoadFlag() {
parseFlag()
err := defaultConf.kof.Load(posflag.Provider(pflag.CommandLine, ".", defaultConf.kof), nil)
//err := defaultConf.kof.BindPFlags(pflag.CommandLine)
if err != nil {
panic(fmt.Errorf("load command line fail: %s", err))
}
// 如果有环境变量定义, 则覆盖掉
if v := os.Getenv(envConfigFile); v != "" {
flagConfigFile = v
}
}
// 解析命令行参数
func parseFlag() {
if pflag.Parsed() {
return
}
pflag.Parse()
}
// LoadFile 加载指定的文件
func LoadFile(configFile string) *Config {
return defaultConf.LoadFile(configFile)
}
func getDefaultConfigFile() string {
var cf = ""
// 兼容yaml, yml
for _, v := range defaultConfigFile {
if mycommon.ExistFile(v) {
cf = v
break
}
}
return cf
}
func (c *Config) LoadFile(configFile string) *Config {
c.ConfigFile = configFile
log.Printf("read local config file: %s", configFile)
err := c.kof.Load(kfile.Provider(configFile), GetKoanfParserByFileExt(configFile), c.koanfOpt...)
if err != nil {
panic(fmt.Errorf("read file fail: %s", err))
}
return c
}
func (c *Config) All() map[string]any {
c.RLock()
defer c.RUnLock()
return c.kof.All()
}
func (c *Config) Raw() map[string]any {
c.RLock()
defer c.RUnLock()
return c.kof.Raw()
}
func (c *Config) Get(key string) any {
c.RLock()
defer c.RUnLock()
return c.kof.Get(key)
}
func (c *Config) GetString(key string) string {
c.RLock()
defer c.RUnLock()
return c.kof.String(key)
}
func (c *Config) GetInt(key string) int {
c.RLock()
defer c.RUnLock()
return c.kof.Int(key)
}
func (c *Config) GetInt64(key string) int64 {
c.RLock()
defer c.RUnLock()
return c.kof.Int64(key)
}
func (c *Config) GetBool(key string) bool {
c.RLock()
defer c.RUnLock()
return c.kof.Bool(key)
}
func (c *Config) GetStringMap(key string) map[string]string {
c.RLock()
defer c.RUnLock()
return c.kof.StringMap(key)
}
func (c *Config) GetStringsMap(key string) map[string][]string {
c.RLock()
defer c.RUnLock()
return c.kof.StringsMap(key)
}
func (c *Config) GetMap(key string) map[string]any {
c.RLock()
defer c.RUnLock()
return cast.ToStringMap(c.kof.Get(key))
}
func (c *Config) GetIntMap(key string) map[string]int {
c.RLock()
defer c.RUnLock()
return c.kof.IntMap(key)
}
func (c *Config) GetStringSlice(key string) []string {
c.RLock()
defer c.RUnLock()
return c.kof.Strings(key)
}
func (c *Config) GetIntSlice(key string) []int {
c.RLock()
defer c.RUnLock()
return c.kof.Ints(key)
}
func (c *Config) WithRead(fn func(k *koanf.Koanf)) {
c.RLock()
defer c.RUnLock()
fn(c.kof)
}
func (c *Config) WithDo(fn func(k *koanf.Koanf)) {
c.Lock()
defer c.UnLock()
fn(c.kof)
}
// Sub 获取子路径的配置
func (c *Config) Sub(key string) *Config {
c.RLock()
defer c.RUnLock()
newCfg := &Config{
InstanceName: fmt.Sprintf("%s.%s", c.InstanceName, key),
kof: c.kof.Cut(key),
lock: &sync.RWMutex{},
}
return newCfg
}
func (c *Config) UnmarshalKey(key string, toVal interface{}) error {
c.RLock()
defer c.RUnLock()
return c.kof.Unmarshal(key, toVal)
}
func (c *Config) Unmarshal(toVal interface{}) error {
c.RLock()
defer c.RUnLock()
return c.kof.Unmarshal("", &toVal)
}
func (c *Config) Set(key string, value interface{}) {
c.Lock()
defer c.UnLock()
c.kof.Set(key, value)
}
func (c *Config) GetStringDefault(key, defaultVal string) string {
c.RLock()
defer c.RUnLock()
v := c.kof.String(key)
if v == "" {
return defaultVal
}
return v
}
func (c *Config) GetIntDefault(key string, defaultVal int) int {
c.RLock()
defer c.RUnLock()
v := c.kof.String(key) // 未设置 空字符串
if v == "" {
return defaultVal
}
return c.kof.Int(key)
}
func (c *Config) GetBoolDefault(key string, defaultVal bool) bool {
c.RLock()
defer c.RUnLock()
v := c.kof.String(key) // 未设置 空字符串
if v == "" {
return defaultVal
}
return c.kof.Bool(key)
}
func (c *Config) RLock() {
if c.lock != nil {
c.lock.RLock()
}
}
func (c *Config) RUnLock() {
if c.lock != nil {
c.lock.RUnlock()
}
}
func (c *Config) Lock() {
if c.lock != nil {
c.lock.Lock()
}
}
func (c *Config) UnLock() {
if c.lock != nil {
c.lock.Unlock()
}
}
// GetKoanfParserByFileExt 根据文件后缀文件类型 .json .yaml .toml 获取 parser
func GetKoanfParserByFileExt(configFile string) koanf.Parser {
ext := strings.TrimLeft(filepath.Ext(configFile), ".")
ext = strings.ToLower(ext)
switch ext {
case "yaml", "yml":
return pyaml.Parser()
case "json":
return pjson.Parser()
case "toml":
return ptoml.Parser()
default:
return pyaml.Parser()
}
}