config在项目中的实现形式多种多样,小成本项目把项目存放在项目自身单独的文件中,文件格式既可以是程序文件,也可以是JSON、YAML这样的静态文件。具有相当大访问量又需要配置能热更新的项目则会使用远程配置中心。
本节我们来实现项目的配置的功能。这里我们需要用到一个Go语言写的开源库使用Viper,选用Viper主要原因是支持它支持从项目文件、远端ETCD和Consul中读取配置,兼容性更高一些。
本文主要内容如下:
本节代码版本为c2, 大家加入项目后可直接访问https://github.com/go-study-lab/go-mall/compare/c1...c2 对比与上次版本的代码变更更容易自己进行学习和动手实战。
项目加入方式:查看文章末尾海报 或者复制链接在浏览器中打开:https://xiaobot.net/p/golang 。
配置的规划
首先提到配置我首先想到的是应该怎么规划和设计
-
支持项目不同运行环境的配置切换
-
配置文件使用JSON还是YAML格式
-
配置应不应该保持只读
第一个问题很好理解,假如项目只有一个配置文件,程序不能按照环境自动加载相应的配置,那可就太麻烦了每次启动前还得先检查配置有没有切过来,而且还有误操作的风险,比如在开发环境直接连生产环境的数据库,不能连上还好说,要是开发环境能连上生产库那问题就更大了。
配置文件的格式,其实用JSON还是YAML都可以,只不过YAML的风格在配置项较多的时候会显得更清晰一些,这个其实没用特别要求必须用哪个,大家搭建项目时尽量选择支持读取多种文件格式配置的开源库,比如这里会用到的Viper。
配置加载到程序后尽量不要去进行更改,因为这些配置大部分都是全局的一旦更改很有可能会影响项目其他部分的程序。这个从程序的语法层面上限制比较困难,所以需要我们在团队内部先达成配置只能是只读的这种共识。
综合以上分析我们对项目的配置作出如下规划:
在项目目录下新建config目录,里面放置我们的应用日志,目录中的文件如下
go
.
|-- config
| `-- application.dev.yaml
| `-- application.prod.yaml
| `-- application.test.yaml
| `-- bootstrap.go
| `-- config.go
|-- main.go
|-- go.mod
|-- go.sum
application.dev.yaml 定义的是应用的配置,通过文件后缀就猜到,在开发环境运行应用时会加读取此文件中的配置
go
app:
env: dev
name: go-mall
database:
type: mysql
dsn: "reserved"
maxopen: 100
maxidle: 10
maxlifetime: 300
与此同时我们还会有application.test.yaml 和 application.prod.yaml 文件用来分别设置应用在测试和生产运行环境中的配置。
另外两个boostrap.go 和 config.go 是下面使用Viper读取加载配置时用到的。
使用 Viper 加载和读取配置
项目安装 Viper 依赖
go
go get github.com/spf13/viper@v1.12.0
为了兼容性,选择一个稍旧的版本,viper@v1.12.0 后面的版本会不支持Yaml 2 的语法
Viper 加载配置
bootstrap.go 是加载配置文件,把配置解析到配置对象中。
config.go中则是定义了我们会使用到的配置对象
go
var (
App *appConfig
Database *databaseConfig
)
type appConfig struct {
Name string `mapstructure:"name"`
Env string `mapstructure:"env"`
}
type databaseConfig struct {
Type string `mapstructure:"type"`
DSN string `mapstructure:"dsn"`
MaxOpenConn int `mapstructure:"maxopen""`
MaxIdleConn int `mapstructure:"maxidle"`
MaxLifeTime time.Duration `mapstructure:"maxlifetime"`
}
做好后,先写个方法快速测试一下对不对
go
g.GET("/config-read", func(c *gin.Context) {
database := config.Database
c.JSON(http.StatusOK, gin.H{
"type": database.Type,
"max_life": database.MaxLifeTime,
})
})
运行后,报错。。。
go
panic: Config file not found, file path: config/application.yaml
因为找不到配置文件 "config/application.yaml", 原因是我们没有设置这个环境变量,我们先在电脑的环境变量里先配置上ENV这个环境变量。
同理程序部署的服务器也要根据所在的运行环境设置ENV这个环境变量,这样程序发布到不同的运行环境才能自动采用对应环境的配置。
到这里你是不是以为配置的大体架子就好了?其实没有,在IDE里直接运行项目是用go run 运行的。 而真正运行程序的时候是先使用 go build 编译打包然后在执行打包后的程序。
Go项目编译后配置文件丢了?
这里就涉及到一个知识点,Go编译时默认只会把go 文件打包进二进制包中,YAML这样的静态文件不会被打包进去,这样编译之后只把二进制包部署到服务上Go程序是没办法读到配置文件的。
我们可以自己做个测验编译打包成执行文件后,把它放到其他目录下执行试试
go
go build ./
mv go-mall ~/Desktop
cd ~/Desktop && go-mall
go
panic: Config file not found, file path: config/application.dev.yaml
......
Process finished with the exit code 2
别看报错看似是我们程序里设置的配置文件是相对路径,其实即使程序里写的是绝对路径,当我们把文件部署到其他服务器上后运行时,也是会报这个错的。
关于这个问题怎么解决,怎么让Go程序真的变成"一次编译到处运行",以及如何让Viper使用Etcd作为远程配置中心,欢迎订阅专栏,加入我们的项目,项目中会通过Github Issue来记录和解答项目开发过程中大家提的各种问题,也有专属的读者群,欢迎加入一起学习。
专栏分为五大部分:
-
第一部分介绍让框架变得好用的诸多实战技巧,比如通过自定义日志门面让项目日志更简单易用、支持自动记录请求的追踪信息和程序位置信息、通过自定义Error在实现Go error接口的同时支持给给错误添加错误链,方便追溯错误源头。
-
第二部分:讲解项目分层架构的设计和划分业务模块的方法和标准,让你以后无论遇到什么项目都能按这套标准自己划分出模块和逻辑分层。后面几个部分均是该部分所讲内容的实践。
-
第三部分:设计实现一个套支持多平台登录,Token泄露检测、同平台多设备登录互踢功能的用户认证体系,这套用户认证体系既可以在你未来开发产品时直接应用
-
第四部分:商城app C端接口功能的实现,强化分层架构实现的讲解,这里还会讲解用责任链、策略和模版等设计模式去解决订单结算促销、支付方式支付场景等多种多样的实际问题。
-
第五部分:单元测试、项目Docker镜像、K8s部署和服务保障相关的一些基础内容和注意事项
具体的章节可扫描上面海报的二维码或者访问 https://xiaobot.net/p/golang
点击下方阅读原文即可跳转。