乐趣区

关于go:Go-Web实战之如何增加应用配置模块

1 介绍

当咱们为本人编写程序时,通常会将一些重要的配置项间接写在源代码里,比方:服务器监听的端口、数据库应用的名称和端口号、HTTP 申请超时的持续时间 …

然而,如果咱们尝试将这个我的项目开源分享给别人应用,用户应用的数据库的用户名和名称可能与你不雷同,甚至你还要为他们的服务器应用另一个端口。

如果你还设置了数据库的明码的话,为了平安,更不可能在代码中信息泄露进去。因而,本节,将介绍如何减少咱们的 sports 利用的配置模块。

2 减少配置模块

在许多的开源我的项目中,配置都是通过键值 (key-value) 数据结构来解决的。在实在利用中,你常常会发现一个公开配置选项的类(或者是构造体),这个类常常会将文件解析进去,将每个抉择赋值。应用程序通常会提出命令行选项以调整配置。

2.1 定义 Configuration 接口

接下来,咱们为应用程序减少配置的能力,这样下面说的很多配置就不必在代码文件中定义。
1、创立 sports/config 文件夹,而后新建一个 config.go 文件,写入如下的代码:

package config

type Configuration interface {GetString(name string) (configValue string, found bool)
    GetInt(name string) (configValue int, found bool)
    GetBool(name string) (configValue bool, found bool)
    GetFloat(name string) (configValue float64, found bool)

    GetStringDefault(name, defVal string) (configValue string)
    GetIntDefault(name string, defVal int) (configValue int)
    GetBoolDefault(name string, defVal bool) (configValue bool)
    GetFloatDefault(name string, defVal float64) (configValue float64)

    GetSection(sectionName string) (section Configuration, found bool)
}

能够看到,Configuration 接口定义了检索配置设置的办法,反对获取字符串 string、数字 int、浮点型 float64、布尔型 bool 的值:

  • GetString()
  • GetInt()
  • GetBool()
  • GetFloat()

还有一组办法容许提供一个默认值:

  • GetStringDefault()
  • GetIntDefault()
  • GetBoolDefault()
  • GetFloatDefault()

配置数据将容许嵌套的配置局部,这个将应用 GetSection() 办法实现。

2.2 来看一个根本的 JSON 配置文件

配置能够从命令行中获取,当然更好的形式是将配置保留在一个文件中,由应用程序主动解析。

文件的格局取决于应用程序的需要。如果你须要一个简单的配置,有级别和档次(以 Windows 注册表的形式)关系的话,那么你可能须要思考 JSON、YAML 或 XML 等格局。

让咱们看一个 JSON 配置文件的例子:

{
    "server": {
        "host": "localhost",
        "port": 80
    },
    "database": {
        "host": "localhost",
        "username": "myUsername",
        "password": "abcdefgh"
    }
}

下面的 JSON 配置文件中定义了服务器 server 和数据库 database 的信息。但在本文中,咱们基于上一节介绍的日志性能来看,为了简化操作,只简略配置咱们的日志和主函数的信息。

2、在 sports 目录下,创立一个 config.json 文件,写入如下内容:

{
    "logging": {"level": "debug"},
    "main": {"message": "Hello, Let's Go! Hello from the config file"}
}

这个配置文件定义了两个配置局部,别离命名为 loggingmain

  • logging 局部蕴含一个繁多的字符串配置设置,名称为 level
  • main 局部蕴含一个繁多的字符串配置设置,名称为 message

这个文件显示了配置文件应用的根本构造,在 JSON 配置文件中,要留神引号和逗号合乎 JSON 文件的格局要求,很多人常常搞错。

2.3 实现 Configuration 接口

为了可能实现 Configuration 接口,咱们将在 sports/config 文件夹下创立一个 config_default.go 文件,而后写入如下代码:

package config

import "strings"

type DefaultConfig struct {configData map[string]interface{}}

func (c *DefaultConfig) get(name string) (result interface{}, found bool) {

    data := c.configData
    for _, key := range strings.Split(name, ":") {result, found = data[key]
        if newSection, ok := result.(map[string]interface{}); ok && found {data = newSection} else {return}
    }
    return
}

func (c *DefaultConfig) GetSection(name string) (section Configuration, found bool) {value, found := c.get(name)
    if found {if sectionData, ok := value.(map[string]interface{}); ok {section = &DefaultConfig{configData: sectionData}
        }
    }
    return
}

func (c *DefaultConfig) GetString(name string) (result string, found bool) {value, found := c.get(name)
    if found {result = value.(string)
    }
    return
}

func (c *DefaultConfig) GetInt(name string) (result int, found bool) {value, found := c.get(name)
    if found {result = int(value.(float64))
    }
    return
}

func (c *DefaultConfig) GetBool(name string) (result bool, found bool) {value, found := c.get(name)
    if found {result = value.(bool)
    }
    return
}

func (c *DefaultConfig) GetFloat(name string) (result float64, found bool) {value, found := c.get(name)
    if found {result = value.(float64)
    }
    return
}

DefaultConfig 构造体用 map 实现了 Configuration 接口,嵌套配置局部也同样用 maps 示意。即下面的代码中的:

type DefaultConfig struct {configData map[string] interface{}}

一个独自的配置能够通过将 section 名称和 setting 名称离开,例如:logging:level,或者应用 map 映射来依据键的名称或者值,例如 logging

2.4 定义接管默认值的办法

为了解决来自配置文件的值,咱们在 sports/config 文件夹下创立一个 config_default_fallback.go 文件:

package config

func (c *DefaultConfig) GetStringDefault(name, val string) (result string) {result, ok := c.GetString(name)
    if !ok {result = val}
    return
}

func (c *DefaultConfig) GetIntDefault(name string, val int) (result int) {result, ok := c.GetInt(name)
    if !ok {result = val}
    return
}

func (c *DefaultConfig) GetBoolDefault(name string, val bool) (result bool) {result, ok := c.GetBool(name)
    if !ok {result = val}
    return
}

func (c *DefaultConfig) GetFloatDefault(name string, val float64) (result float64) {result, ok := c.GetFloat(name)
    if !ok {result = val}
    return
}

2.5 定义从配置文件加载数据的函数

sports/config 文件夹下新建一个加载 JSON 数据的 config_json.go 文件,写入如下代码:

package config

import (
    "encoding/json"
    "os"
    "strings"
)

func Load(filename string) (config Configuration, err error) {var data []byte
    data, err = os.ReadFile(filename)
    if err == nil {decoder := json.NewDecoder(strings.NewReader(string(data)))
        m := map[string]interface{}{}
        err = decoder.Decode(&m)
        if err == nil {config = &DefaultConfig{configData: m}
        }
    }
    return
}

Load 函数读取一个文件的内容,将其蕴含的 JSON 文件解析为一个映射,并应用该映射创立一个 DefaultConfig 的值。

对于 Go 如何解决 JSON 文件,感兴趣能够搜寻我之前的文章:《Go 语言入门很简略:Go 语言解析 JSON》

3 应用 Configuration 配置零碎

为了从刚刚减少的配置零碎中获取日志级别的信息,咱们将回到上一节中 logging 文件夹中的 default_create.go 文件中,写入如下代码:

package logging

import (
    "log"
    "os"
    "strings"

    "sports/config"
)

// func NewDefaultLogger(level LogLevel) Logger {func NewDefaultLogger(cfg config.Configuration) Logger {

    // 应用 Configuration
    var level LogLevel = Debug
    if configLevelString, found := cfg.GetString("logging:level"); found {level = LogLevelFromString(configLevelString)
    }

    flags := log.Lmsgprefix | log.Ltime
    return &DefaultLogger{
        minLevel: level,
        loggers: map[LogLevel]*log.Logger{Trace:       log.New(os.Stdout, "TRACE", flags),
            Debug:       log.New(os.Stdout, "DEBUG", flags),
            Information: log.New(os.Stdout, "INFO", flags),
            Warning:     log.New(os.Stdout, "WARNING", flags),
            Fatal:       log.New(os.Stdout, "FATAL", flags),
        },
        triggerPanic: true,
    }
}

func LogLevelFromString(val string) (level LogLevel) {switch strings.ToLower(val) {
    case "debug":
        level = Debug
    case "information":
        level = Information
    case "warning":
        level = Warning
    case "fatal":
        level = Fatal
    case "none":
        level = None
    }
    return
}

在 JSON 中没有很好的办法来示意 iota 值,所以咱们应用一个字符串并定义了 LogLevelFromString() 函数,以此来将配置设置转换为 LogLevel 的值。

最初,咱们更新 main() 函数来加载和利用配置数据,并应用配置零碎来读取它所输入的信息,更改 main.go 文件如下。

package main

import (
    // "fmt"
    "sports/config"
    "sports/logging"
)

// func writeMessage(logger logging.Logger) {//     // fmt.Println("Let's Go")
//     logger.Info("Let's Go, logger")
// }

// func main() {//     var logger logging.Logger = logging.NewDefaultLogger(logging.Information)
//     writeMessage(logger)
// }

func writeMessage(logger logging.Logger, cfg config.Configuration) {section, ok := cfg.GetSection("main")
    if ok {message, ok := section.GetString("message")
        if ok {logger.Info(message)
        } else {logger.Panic("Cannot find configuration setting")
        }
    } else {logger.Panic("Config section not found")
    }
}

func main() {

    var cfg config.Configuration
    var err error
    cfg, err = config.Load("config.json")
    if err != nil {panic(err)
    }

    var logger logging.Logger = logging.NewDefaultLogger(cfg)
    writeMessage(logger, cfg)
}

至此,咱们的配置是从 config.json 文件中获取,通过 NewDefaultLogger() 函数来传递 Configuration 的实现,最终读取到 log 日志级别设置。

writeMessage() 函数显示了配置局部的应用,提供了组件所需的设置,特地是在须要多个具备不同配置的实例时,每一个设置都能够在本人的局部进行定义。

最初的我的项目构造如图:

最终,咱们在终端中编译并运行咱们整个代码:

$ go run .
17:20:46 INFO Hello, Let's Go! Hello from the config file

整个代码会输入并打印出配置文件中的信息,如图所示:

4 总结

本文介绍了我的项目配置文件的由来和重要性,并从零到一编写代码,胜利在咱们的 Web 我的项目中减少了利用配置性能。并联合上一节的日志性能进行了测试。

其实在 Go 开源我的项目中,有个十分驰名的开源配置包:Viper,提供针对 Go 利用我的项目的残缺配置解决方案,帮忙咱们疾速解决所有类型的配置需要和配置文件格式。目前 GitHub Stars 数量高达 21k,今后将在后续的文章中介绍这个我的项目。

明天的内容到此结束。更多配置相干的内容等着大家自行摸索!

心愿本文能对你有所帮忙,如果喜爱本文,能够点个赞或关注。

这里是宇宙之一粟,下一篇文章见!

宇宙古今无有穷期,毕生不过顷刻,当思奋争。

参考链接:

  • Application Configuration – Practical Go Lessons
退出移动版