深入理解与使用go之配置--实现

深入理解与使用go之配置--实现

目录

引子

配置

定义

原理

简单实现

优化

扩展

使用viper读取配置


引子

构建项目工程的时候,我们经常要提供一些配置

  • 数据库: 比如mysql连接的dsn(host/username/password/port/db)

  • Redis: 比如host/username/password/port/d

  • 工程服务:服务名称,服务启动端口号

  • 日志:日志存放路径,存放大小等

作为gopher,你最快想到的是啥,看看是不是这样

package config
var ConfigMap = map[string]interface{}{
  "server.name":"user",
  "server.port":8080,
  "mysql.host":"localhost",
  "mysql.db":"user",
  "mysql.user":"aliyun",
  // other config here ---
}

然后使用的时候

serverName := config.ConfigMap["server.name"].(string)
serverPort := config.ConfigMap["server.port"].(int)
  • 用这么多的断言,合适么

  • 如果配置越来越多,这个map是不是越来越大,如果我们想有一条切换到别的语言,重新写一遍么?

配置

定义

配置说白了就是你需要依赖的资源、路径常量,可能随着环境的变化而变化

原理

资源、路径常量, 还要随环境(测试/预发/生产)变化而变化

  • 配置文件常量一般都是放在常见格式的 json / yaml / toml 等

  • 随环境而变化,环境那就得从 go启动环境变量配置而来

    export ENV=test && /data/www/user-service-go
  • 那么常量如何解析呢,使用go结构体

简单实现

比如我们设置 app.json

{
    "server":{
      "name":"user",
      "port":8080
    }
}

然后解码 config/server.go

type ServerConfig struct {
   Name string
   Port int
}
​
func GetServerConfig() *ServerConfig {
    fHandle, err := os.Open("config/app.json")
    if err != nil {
      panic("config file open error")
    }
    defer fHandle.Close()
    content, err := ioutil.ReadAll(fHandle)
    if err != nil {
      panic("config file read error")
    }
    type ServerConfig struct {
      Name string
      Port int
    }
    sc := &ServerConfig{}
    err = json.Unmarshal(content, sc)
    if err != nil {
      panic("config file unmarshal error")
    }
    return sc
}

使用

sc := config.GetServerConfig()
fmt.Println(sc.Name)
优化

如上,有两个问题没有解决

  • 如果我切换到生产环境,配置还是使用测试环境的 app.json ?

  • 凡是调用配置的地方,我们每次都要打开文件,关闭文件进行频繁io操作

第一个问题,我们好解决,配置多环境配置,如下

├── config
│   └── json
│       ├── app-pre.json
│       ├── app-prod.json
│       └── app-test.json

然后,我们在读取配置文件之前,读取环境变量

env := os.Getenv("ENV")
if env == "" {
   env = "test"
}
fHandle, err := os.Open("config/json/app-" + env + ".json")

第二个问题,环境一旦确定,配置不会再更改,那么我们想从根本上解决问题,就要考虑到设计模式--- 单例模式

这里我们直接使用饥饿模式(直接在文件中初始化全局变量),其他还有懒汉模式等等,感兴趣的可以查询相关资料

我们在 config/server.go 顶部添加

var Server = GetServerConfig()

那么后续的调用中可以直接使用, 配置只读取一次

fmt.Println(config.Server.Name)

扩展

使用viper读取配置

这里我们更换一下使用 yaml 配置(也可以使用json 或其他),具体怎么实现,我们不深究,我们讲下怎么使用

  • 文件目录

    ├── config
    │   ├── conf.go
    │   ├── mysql.go
    │   ├── server.go
    │   └── yaml
    │       ├── app-pre.yaml
    │       ├── app-prod.yaml
    │       └── app-test.yaml
  • 配置 app-test.yaml

    server:
      name: "user"
      port: 8080
    mysql:
      host: "localhost"
      port: 3306
      user: "aliyun"
      password: "123456"
      db: "user"
  • 初始化配置 conf.go

    import (
      "fmt"
      "github.com/spf13/viper"
      "os"
    )
    ​
    var C = initConfig()
    ​
    type config struct {
      viper *viper.Viper
    }
    ​
    func initConfig() *config {
      v := viper.New()
      conf := &config{viper: v}
      env := os.Getenv("ENV")
      if env == "" {
        env = "test"
      }
      workDir, _ := os.Getwd()
      conf.viper.SetConfigName("app-" + env)              // name of config file (without extension)
      conf.viper.SetConfigType("yaml")                    // REQUIRED if the config file does not have the extension in the name
      conf.viper.AddConfigPath(workDir + "/config/yaml/") // path to look for the config file in
      err := conf.viper.ReadInConfig()                    // Find and read the config file
      if err != nil {                                     // Handle errors reading the config file
        panic(fmt.Errorf("fatal error config file: %w", err))
      }
      return conf
    }
  • 初始化服务 server.go

    package config
    ​
    var Server = InitServerConfig()
    ​
    type ServerConfig struct {
      Name string
      Port int
    }
    ​
    func InitServerConfig() *ServerConfig {
      return &ServerConfig{
        Name: C.viper.GetString("server.name"),
        Port: C.viper.GetInt("server.port"),
      }
    }
  • 初始化 mysql.go

    var MySQL = InitMySQLConfig()
    ​
    type MySQLConfig struct {
      Host     string
      Port     int
      User     string 
      Password string
      Db       string
    }
    ​
    func InitMySQLConfig() *MySQLConfig {
      return &MySQLConfig{
        Host:     C.viper.GetString("mysql.host"),
        Port:     C.viper.GetInt("mysql.port"),
        User:     C.viper.GetString("mysql.user"),
        Password: C.viper.GetString("mysql.password"),
        Db:       C.viper.GetInt("mysql.db"),
      }
    }
  • main.go打印

    func main() {
      fmt.Println(config.Server)
      fmt.Println(config.Redis)
    }
  • 启动

    export ENV=test && /data/www/user-service-go

是不是调用有种很清爽的感觉,如果有更好的方式,欢迎评论留言讨论

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值