代码解读
函数的目标和设计思路
- 构建一个用于加载配置文件的函数,并返回一个配置管理器实例(*viper.Viper)。
- 此函数需要支持多种方式指定配置文件的路径:命令行参数、环境变量以及默认值(读取gin自带的环境变量GIN_MODE,用于区分开发,测试,生产三个环境)。
- 在go中,可以使用 可变参数(variadic parameters),例如 path …string,让函数接收可选数量的参数
获取配置文件路径
- 如果用户传入了路径,我应该直接使用传入的第一个路径
func Viper(path ...string) *viper.Viper { var config string // 用于存储配置文件的路径 if len(path) == 0 { ....... } else { // 函数传递的可变参数的第一个值赋值于config config = path[0] fmt.Printf("您正在使用func Viper()传递的值,config的路径为%s\n", config) } ....... }
- 当没有传入路径参数时,首先通过命令行参数获取配置文件路径(例如通过 -c 参数)
func Viper(path ...string) *viper.Viper { var config string // 用于存储配置文件的路径 if len(path) == 0 { // 如果没有传递 path 参数,则通过命令行解析器 flag 获取 -c 参数作为配置文件路径。 flag.StringVar(&config, "c", "", "choose config file.") flag.Parse() if config == "" { // 判断命令行参数是否为空 ...... } else { // 命令行参数不为空 将值赋值于config fmt.Printf("您正在使用命令行的-c参数传递的值,config的路径为%s\n", config) } } else { // 函数传递的可变参数的第一个值赋值于config ....... } ...... }
- 如果命令行参数也没有提供,再检查是否有特定的环境变量指定了配置文件路径。
func Viper(path ...string) *viper.Viper { var config string // 用于存储配置文件的路径 if len(path) == 0 { // 如果没有传递 path 参数,则通过命令行解析器 flag 获取 -c 参数作为配置文件路径。 flag.StringVar(&config, "c", "", "choose config file.") flag.Parse() if config == "" { // 判断命令行参数是否为空 // 如果命令行参数为空,接着检查环境变量 internal.ConfigEnv 是否有值 if configEnv := os.Getenv(internal.ConfigEnv); configEnv == "" { // 判断 internal.ConfigEnv 常量存储的环境变量是否为空 ...... } else { // internal.ConfigEnv 常量存储的环境变量不为空 将值赋值于config config = configEnv fmt.Printf("您正在使用%s环境变量,config的路径为%s\n", internal.ConfigEnv, config) } } else { // 命令行参数不为空 将值赋值于config ...... } } else { // 函数传递的可变参数的第一个值赋值于config ...... } ...... }
- 如果环境变量也没有提供,则根据运行环境(Gin 模式)选择一个默认的配置文件路径。
Gin 支持三种主要的运行模式:func Viper(path ...string) *viper.Viper { var config string // 用于存储配置文件的路径 if len(path) == 0 { // 如果没有传递 path 参数,则通过命令行解析器 flag 获取 -c 参数作为配置文件路径。 flag.StringVar(&config, "c", "", "choose config file.") flag.Parse() if config == "" { // 判断命令行参数是否为空 // 如果命令行参数为空,接着检查环境变量 internal.ConfigEnv 是否有值 if configEnv := os.Getenv(internal.ConfigEnv); configEnv == "" { // 判断 internal.ConfigEnv 常量存储的环境变量是否为空 switch gin.Mode() { case gin.DebugMode: config = internal.ConfigDefaultFile case gin.ReleaseMode: config = internal.ConfigReleaseFile case gin.TestMode: config = internal.ConfigTestFile } fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), config) } else { // internal.ConfigEnv 常量存储的环境变量不为空 将值赋值于config ...... } } else { // 命令行参数不为空 将值赋值于config ...... } } else { // 函数传递的可变参数的第一个值赋值于config ...... } ...... }
gin.DebugMode:调试模式,通常用于开发环境。(通常通过设置环境变量GIN_MODE=debu或通过 gin.SetMode(gin.DebugMode))。如果没有明确设置,Gin 默认为调试模式 (gin.DebugMode)。
gin.ReleaseMode:发布模式,通常用于生产环境。
gin.TestMode:测试模式,通常用于单元测试。
命令行 > 环境变量 > 默认值
初始化 Viper 实例
创建一个新的 Viper 实例,并告知它要加载哪个配置文件以及配置文件的类型(例如 yaml)。
- 使用 viper.New() 创建一个新实例。
v := viper.New()
- 调用 v.SetConfigFile(config) 将前面确定的配置路径传给 Viper。
v.SetConfigFile(config)
- 设置配置文件类型,例如 v.SetConfigType(“yaml”),这样 Viper 知道如何解析文件。
v.SetConfigType("yaml")
调用 Viper 的方法来读取配置文件,并在出错时处理错误。
err := v.ReadInConfig()
if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
在配置文件加载失败时使用 panic 是因为这是一个致命错误,程序无法在没有配置的情况下正确运行。
利用 Viper 的特性,监听配置文件的变化,并在变化时自动更新全局配置。
- 调用 v.WatchConfig() 开启文件监听。
v.WatchConfig()
- 使用 v.OnConfigChange(func(e fsnotify.Event) { … }) 定义当配置文件变化时要执行的回调函数。在回调中,可以打印出文件变化信息,并调用 v.Unmarshal() 将最新配置映射到全局配置对象中。
v.OnConfigChange(func(e fsnotify.Event) { fmt.Println("config file changed:", e.Name) if err = v.Unmarshal(&global.GVA_CONFIG); err != nil { fmt.Println(err) } })
程序运行时可以实时响应配置变更,而不需要重启服务。
进行启动时的将配置映射到全局配置对象
使用 v.Unmarshal() 将读取到的配置数据转换并存储到全局变量(例如 global.GVA_CONFIG)中,以便整个程序都能访问配置。
if err = v.Unmarshal(&global.GVA_CONFIG); err != nil {
panic(err)
}
调用 v.Unmarshal(&global.GVA_CONFIG) 并检查是否有错误。如果出错,可以选择 panic 终止程序。
处理根路径适配问题
使用 filepath.Abs(“…”) 计算上级目录的绝对路径。
将这个路径赋值给全局配置中对应的字段(例如 global.GVA_CONFIG.AutoCode.Root)
// root 适配性 根据root位置去找到对应迁移位置,保证root路径有效
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
根据当前工作目录,确定一个合适的根路径(例如项目根目录),以便后续功能(例如代码生成)能正确工作。
将配置管理器实例返回,方便其他部分的代码调用。
return v
go语言语法/特性/技巧总结
Viper
Viper 是 Go 中最流行的配置管理库之一。很多项目使用 Viper 来加载配置文件,因为它简洁、功能强大,并且支持 YAML、JSON、TOML 等多种格式,支持从文件、环境变量、远程存储等多种源加载配置。
在 Go 的很多项目中,你会看到配置加载被封装为一个单独的函数(通常是类似 GetConfig 或 LoadConfig 的函数),这些函数负责读取配置文件、环境变量、命令行参数等,返回一个配置管理器对象(如 Viper 或自定义的配置结构体)。
这种做法有助于将所有配置的管理集中在一个地方,简化了程序的启动和配置管理。
另外,在更复杂的系统中,可能会使用配置中心(例如 Consul、etcd、Zookeeper 等)来集中管理配置,而不仅仅依赖本地文件和环境变量。
短变量声明(:=)
:= 是 Go 语言中的短变量声明语法,它用于声明并初始化一个变量。通过这种方式,变量会根据右侧的表达式类型自动推导其类型。
使用 if 语句并声明变量
Go 语言的 if 语句有一个独特的功能:你可以在 if 语句的条件中声明变量。具体来说,if 语句可以在条件部分进行变量声明,并直接使用该变量。
panic
-
panic 主要用于在程序运行时遇到不可恢复的错误时,将错误信息打印出来并停止程序的执行。它会导致程序从当前函数返回,沿着调用栈向上层传播,直到程序终止或者被捕获。
-
panic 语法非常简单,使用关键字 panic 后跟要传递的错误信息(通常是一个错误类型或字符串)。
-
panic 的执行过程
- 触发 panic: 当代码执行到 panic 语句时,程序会立刻停止当前函数的执行,开始在调用栈中沿着调用路径向外传播,直到最外层。
- 调用栈的展开: 在 panic 触发后,程序会从当前函数开始回溯,逐层执行 defer 语句块,释放资源等。
- defer:Go 中的 defer 语句是用来延迟执行某个函数或语句,直到当前函数返回时再执行。它通常用于资源清理、文件关闭等操作。
- 终止程序: 如果没有任何机制捕获到这个 panic,程序最终会在控制台打印出堆栈信息,然后终止。
-
panic 和 recover 配合使用
package main import "fmt" // 一个触发 panic 的函数 func mayPanic() { fmt.Println("Before panic") panic("Something went wrong!") // 触发 panic fmt.Println("After panic") // 这行不会被执行 } // 捕获 panic 的函数 func recoverPanic() { // 在 defer 中调用 recover 来捕获 panic defer func() { if r := recover(); r != nil { fmt.Println("Recovered from panic:", r) } }() mayPanic() // 调用可能会触发 panic 的函数 fmt.Println("After mayPanic") // 这行会被执行,因为 panic 被 recover 捕获了 } func main() { recoverPanic() fmt.Println("Program continues after recovery.") }
main 调用 recoverPanic()。
在 recoverPanic 中,defer 语句被用来捕获 panic。
mayPanic() 被调用,触发了 panic。
因为 panic 被 defer 中的 recover 捕获,程序不会崩溃,recover 输出了 panic 信息,并且程序继续执行。
输出会显示:Before panic Recovered from panic: Something went wrong! After mayPanic Program continues after recovery.
-
panic 用途
严重错误:panic 通常用于程序中遇到无法恢复的严重错误(例如,空指针引用、数组越界等)。
程序设计中的不正常状态:比如,某个操作返回了不符合预期的值,但程序设计上不允许出现这种值时,可以用 panic 来触发异常。 -
panic 不建议用于:
常规错误处理:Go 的错误处理机制(通过返回值来传递错误)应该用于常规的错误检查和处理。panic 通常不适合用来处理正常的、可以预料到的错误。
Go 标准库的 flag 包
- 使用 flag.StringVar 定义命令行标志
flag.StringVar(&variable, "flagName", "defaultValue", "description")
- &variable:指定一个指针,保存命令行传入的值。 - "flagName":标志的名字,通常是命令行参数的短名称(如 -c)。 - "defaultValue":如果命令行没有指定该标志的值,使用的默认值。 - "description":这个标志的描述,用于帮助信息。
- 调用 flag.Parse()解析命令行参数
- flag.Parse() 会从命令行获取所有参数并进行解析。它会逐个解析命令行中的标志
- 每个定义的标志(通过 flag.StringVar、flag.IntVar、flag.BoolVar 等)都会与命令行中的参数进行匹配。如果命令行中包含某个标志(如 -c),flag.Parse() 会找到对应的变量并将参数值存储到这个变量中。如果命令行没有传入某个标志,变量会使用标志定义时指定的默认值
- 处理未知标志:如果命令行中包含没有定义的标志,flag.Parse() 会自动输出错误信息,并显示所有已定义的标志的帮助信息。
- 处理帮助标志:如果命令行中传入 -h 或 --help 参数,flag.Parse() 会自动显示程序的帮助信息,包括所有命令行标志及其说明。-h 是由 flag 包内置的标志,表示请求帮助。
- 总之,一旦调用 flag.Parse(),所有命令行标志都已被解析,并且对应的变量已经被赋值,程序可以继续使用这些变量来控制后续的行为。
Go 语言标准库 os 包
os.Getenv(key string)
它从当前进程的环境变量中查找对应 key 的值。这个进程是你运行 Go 程序时启动的进程。
如果环境变量存在,os.Getenv() 会返回该变量的值(字符串类型);如果该环境变量没有设置或找不到,它会返回空字符串 “”。
操作系统层级的环境变量: 操作系统允许在启动程序之前设置环境变量
运行时设置的环境变量: 环境变量可以在程序启动时由外部脚本或操作系统传递。
操作系统的环境配置文件: 在某些操作系统中,环境变量可能通过配置文件(如 .bashrc, .bash_profile, .zshrc, /etc/environment 等)进行设置,系统或用户登录时会加载这些文件并设置环境变量。