Skip to content

配置管理 原创

在应用程序的运行生命周期中,应用配置的读取和更新可以直接改变应用程序,比如:

  • 在启动时,可以读取配置进行一系列初始化操作,如数据库的配置信息
  • 在运行时,可以通过监听文件的方式热加载配置,达到灰度的效果

本项目主要使用 viper 和 cast 来实现配置管理。其中:

  • viper 主要用来管理配置
  • cast 主要用来类型转换

1、安装依赖包

go
$ go get github.com/spf13/viper
$ go get github.com/spf13/cast

2、添加配置文件

在项目的configs目录下创建config.yaml文件,写入以下配置:

yaml
Server:
  RunMode: debug
  Port: 8080
  ReadTimeout: 3600
  WriteTimeout: 3600
  IdleTimeout: 300

在配置文件中,我们编写的配置详情如下:

  • Server:服务配置,设置运行 WEB 服务所需的信息

3、编写组件

编写完配置之后,我们需要读取配置,这时候就要对读取配置的行为进行封装。

首先,在 pkg/setting 目录中新建 setting.go 文件,写入如下代码:

go
package setting

import "github.com/spf13/viper"

type Setting struct {
	vp *viper.Viper
}

// NewSetting 初始化配置文件的基础属性
func NewSetting() (*Setting, error) {
	vp := viper.New()
	vp.SetConfigName("config")
	vp.AddConfigPath("configs")
	vp.SetConfigType("yaml")
	if err := vp.ReadInConfig(); err != nil {
		return nil, err
	}
	return &Setting{
		vp: vp,
	}, nil
}

在上述代码中,我们编写了 NewSetting 方法,用于初始化本项目配置的基础属性,即设定配置文件的名称为 config、配置类型为 yaml,并且设置其配置路径为相对路径 configs/,以确保在项目目录下能够成功启动编写组件。

接下来再在 pkg/setting 目录下创建 section.go 文件,用于声明配置属性的结构体,如下:

go
package setting

import "time"

// ServerSettingS 服务端配置结构体
type ServerSettingS struct {
	RunMode      string
	Port         string
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
	IdleTimeout  time.Duration
}

// ReadSection 读取配置信息
//  k: 模块名
//	v: 配置的结构体
//	example:
//		ServerSettingS.ReadSection("Server",&ServerSettingS)
func (s *Setting) ReadSection(k string, v interface{}) error {
	if err := s.vp.UnmarshalKey(k, v); err != nil {
		return err
	}
	return nil
}

由于数据库和应用配置现在没有使用,所以现在只配置了服务配置,其他配置在使用的时候再慢慢添加。

4、添加全局变量

仅读取文件的配置信息是不够的,我们还需将配置信息和应用程序关联起来,这样才能使用它。下面在项目目录的global目录下新建setting.go文件,写入如下代码:

go
package global

import "github.com/joker-bai/hawkeye/pkg/setting"

var (
	Setting *setting.ServerSettingS
)

5、初始化配置

上面我们已经做完了关于配置的前置准备,我们的最终需求是要在服务中使用到配置,所以现在需要在服务启动的时候初始化配置信息,以便能正常读取配置。

initialize包中新增setting.go文件,写入以下内容:

go
package initialize

import (
	"github.com/joker-bai/hawkeye/global"
	"github.com/joker-bai/hawkeye/pkg/setting"
)

// SetupSetting 初始化配置
func SetupSetting() error {
	s, err := setting.NewSetting()
	if err != nil {
		return err
	}
	// 加载配置
	if err := s.ReadSection("Server", &global.ServerSetting); err != nil {
		return err
	}

	return nil
}

该方法主要是实例化配置文件,将配置信息和实例关联起来。

然后将初始化配置的操作写入 main.go 入口函数中,并将服务配置改成从配置文件读取,如下:

go
package main

import (
	"log"
	"net/http"
	"time"

	"github.com/joker-bai/hawkeye/global"
	"github.com/joker-bai/hawkeye/initialize"
)

func init() {
	// 初始化配置
	if err := initialize.SetupSetting(); err != nil {
		log.Fatalf("Failed to init setting, error: %s", err)
	}
}

func main() {

	// 初始化
	engine := initialize.NewEngine()
	server := &http.Server{
		Addr:         ":" + global.ServerSetting.Port,
		WriteTimeout: time.Second * global.ServerSetting.WriteTimeout,
		ReadTimeout:  time.Second * global.ServerSetting.ReadTimeout,
		IdleTimeout:  time.Second * global.ServerSetting.IdleTimeout,
		Handler:      engine,
	}

	// 运行服务
	if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		log.Fatalf("Failed to start http server, error: %s", err)
	}
}

这里,我们引入了 init 方法,该方法的逻辑如下:

  • 如果一个包定义了 init 函数,Go 运行时会负责在该包初始化时调用它的 init 函数;
  • init 不能被显式调用 ,否则会在编译期间报错;
  • 多个包的情况,在初始化该包时,Go 运行时会按照一定的次序逐一顺序地调用该包的 init 函数;
  • 每个 init 函数在整个 Go 程序生命周期内仅会被执行一次;
  • 一般来说,先被传递给 Go 编译器的源文件中的 init 函数先被执行(main.go 作为起点);
  • 同一个源文件中的多个 init 函数按声明顺序依次执行。

43ec7718e251d89a9e3eae67bc673a33 MD5

6、测试一下

上面已经把配置相关的信息处理完毕,现在来验证一下。

首先,看服务启动是否正常,如下表示正常:

bdfda1525c93cdcf62e42a1831238d51 MD5

再次,用apifox测试业务是否正常,如下表示正常:

397d28576b8d7cb237c897a290ea2e09 MD5

7、代码版本

本节开发完,为代码做个标记,如下:

go
$ git add .
$ git commit -m "add config manage"
最近更新