在go的项目中,运行配置经常需要修改的,比如端口号,地址,各种变动的token,这些值都是随时可能根据需求变动的。如果写死在程序里,要修改的时候会很不方便。
同时,一些敏感的数据比如各种用户名,或者密码,也需要集中存放,便于查询也便于保护。
这一般都通过配置文件来实现,json
或者yaml
。本人比较喜欢使用yaml
的配置,比较美观易读。
不重要的配置数据可以直接在配置文件中明写,敏感的可以写密文,然后把密钥写环境变量中。
对于一些典型的示例代码,都是直接在main函数中完成的,但是实际中,我们的项目会启动多种服务,比如一个简单的API后端,可能就会使用Gin框架,使用数据库,使用redis等,启动多个服务就会涉及到各个服务的参数怎么设置。
如果每个都在相应服务的内部申明,这显然是不科学的,把配置集中设置才能更有效的管理服务。
我们可以在项目最外层(和main函数同级)设置一个config文件夹,里面存放一个app.yaml
文件和一个config.go
文件。
前者存放配置,后者写一个解析配置的函数,用于读取配置。
我写一个示例的文件:
server:
env: "debug"
address: "localhost"
port: 3000
db:
port: 3306
host: "localhost"
name: "digbgm"
user: "root"
password: "xxxxxxxxxx"
migrate: true
redis:
enable: true
port: 6379
host: "localhost"
password: "xxxxxx"
之后在config.go文件中定义配置文件的结构体,以及一个函数,使用go提供的一个yaml包对yaml文件读取:
package config
import (
"os"
"gopkg.in/yaml.v3"
)
type Config struct {
Server ServerConfig `yaml:"server"`
DB DBConfig `yaml:"db"`
Redis RedisConfig `yaml:"redis"`
}
type ServerConfig struct {
Env string `yaml:"env"`
Address string `yaml:"address"`
Port int `yaml:"port"`
}
type DBConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Name string `yaml:"name"`
User string `yaml:"user"`
Password string `yaml:"password"`
Migrate bool `yaml:"migrate"`
}
type RedisConfig struct {
Enable bool `yaml:"enable"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Password string `yaml:"password"`
}
// 读取配置文件 app.yaml
func ConfigParse(appConfig *string) (*Config, error) {
config := &Config{}
file, err := os.Open(*appConfig)
if err != nil {
return nil, err
}
defer file.Close()
if err := yaml.NewDecoder(file).Decode(&config); err != nil {
return nil, err
}
return config, nil
}
完成后我们就可以通过调用该函数的方式读取配置文件,从而将这些值传递给需要的对象。
我们在main函数中可以使用flag
包来加载yaml
文件
var (
port string
ginmode string
)
func main() {
appConfig := flag.String("config", "config/app.yaml", "application config path")
conf, _ := config.ConfigParse(appConfig)
if conf != nil {
port = fmt.Sprint(conf.Server.Port)
ginmode = conf.Server.Env
} else {
log.Println("Error:config file is nil")
}
// ... ...
这样我们就可以顺利通过配置文件来读取端口号以及gin的运行模式这两个参数了。
同时,main中得到的conf的值也可以作为参数,传递给其他需要配置文件的地方,但是注意,conf最好作为指针传递。
//其他包中定义一个获取配置的函数
func NewWxConfig(conf *config.WxConfig) {
wxToken = conf.WxToken
appID = conf.AppID
appSecret = conf.AppSecret
}
... ...
util.NewWxConfig(&conf.Wx) //传入conf的地址,得到配置文件中的参数
使用配置文件作为启动参数,这让我们的main函数变的很简练。
如果我们使用了GIN框架创建了一个后端,监听3000端口,我们通常会在main函数中使用:
gin.SetMode(ginmode)
router := gin.Default()
router.GET("/", handle1)
router.POST("/",handle2)
log.Fatalln(router.Run(":" + port))
这样来创建一个gin服务。
但是如果我们有多个服务要创建,都挤在一个main函数中是不合理的,所以我们可以新建一个server.go
文件,用来读取配置文件,启动多种服务,而main只需要调用server提供的接口就可以了。
server
函数中需要面向对象的思想,新建一个server
的结构体,我做个示例:
type Server struct {
engine *gin.Engine
config *config.Config
db *gorm.DB
rdb *redis.Client
}
写一个new函数,用来读取配置,并将配置信息分发给各个服务:
func New(conf *config.Config) (*Server, error) {
if conf != nil {
var err error
db, err = database.Newdb(&conf.DB)
if err != nil {
return nil, errors.Wrap(err, "db init failed")
}
rdb, err = database.NewRedisClient(&conf.Redis)
if err != nil {
return nil, errors.Wrap(err, "redis client failed")
}
} else {
log.Println("Error:config file is nil")
}
// 自动迁移模型
if conf.DB.Migrate {
db.AutoMigrate(&database.User{})
}
//设置Gin的模式
gin.SetMode(conf.Server.Env)
// 创建一个 Gin 引擎
r := gin.Default()
return &Server{
engine: r,
config: conf,
db: db,
rdb: rdb,
}, nil
}
其中database.Newdb
和database.NewRedisClient
是数据库中接受配置信息的函数,传入后即可加载服务了。
然后再给server一个run的方法,用于启动主服务–Gin:
func (s *Server) Run() error {
s.initRouter()
//读取服务器地址
addr := fmt.Sprintf("%s:%d", s.config.Server.Address, s.config.Server.Port)
// 启动Gin服务器
err := s.engine.Run(addr)
if err != nil {
fmt.Println("Failed to start Gin server")
}
return err
}
func (s *Server) initRouter() {
r := s.engine
// 设置路由
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello, Gin!",
})
})
}
在main函数中读取配置s, err := server.New(conf)
,然后调用s.Run()
,就可以将配置文件作为启动参数加载到项目中了
请注意yaml文件的格式,只能用空格,不能用tab,并且冒号后要有一个空格,yaml的严格缩进需要仔细填写,否则很容易在读取yaml出现空指针报错。请自行查看yaml文件的规范,或者参考已有示例。