【Gin-Vue-Admin002】core.Viper()

代码解读

函数的目标和设计思路

  1. 构建一个用于加载配置文件的函数,并返回一个配置管理器实例(*viper.Viper)。
  2. 此函数需要支持多种方式指定配置文件的路径:命令行参数、环境变量以及默认值(读取gin自带的环境变量GIN_MODE,用于区分开发,测试,生产三个环境)。
  3. 在go中,可以使用 可变参数(variadic parameters),例如 path …string,让函数接收可选数量的参数

获取配置文件路径

  1. 如果用户传入了路径,我应该直接使用传入的第一个路径
    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)
    	}
    	.......
    }
    
  2. 当没有传入路径参数时,首先通过命令行参数获取配置文件路径(例如通过 -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
    		.......
    	}
    	......
    }
    
  3. 如果命令行参数也没有提供,再检查是否有特定的环境变量指定了配置文件路径。
    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
    		......
    	}
    ......
    }
    
    
  4. 如果环境变量也没有提供,则根据运行环境(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 支持三种主要的运行模式:
    gin.DebugMode:调试模式,通常用于开发环境。(通常通过设置环境变量GIN_MODE=debu或通过 gin.SetMode(gin.DebugMode))。如果没有明确设置,Gin 默认为调试模式 (gin.DebugMode)。
    gin.ReleaseMode:发布模式,通常用于生产环境。
    gin.TestMode:测试模式,通常用于单元测试。

命令行 > 环境变量 > 默认值

初始化 Viper 实例

创建一个新的 Viper 实例,并告知它要加载哪个配置文件以及配置文件的类型(例如 yaml)。

  1. 使用 viper.New() 创建一个新实例。
    v := viper.New()
    
  2. 调用 v.SetConfigFile(config) 将前面确定的配置路径传给 Viper。
    v.SetConfigFile(config)
    
  3. 设置配置文件类型,例如 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 的特性,监听配置文件的变化,并在变化时自动更新全局配置。

  1. 调用 v.WatchConfig() 开启文件监听。
    v.WatchConfig()
    
  2. 使用 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

  1. panic 主要用于在程序运行时遇到不可恢复的错误时,将错误信息打印出来并停止程序的执行。它会导致程序从当前函数返回,沿着调用栈向上层传播,直到程序终止或者被捕获。

  2. panic 语法非常简单,使用关键字 panic 后跟要传递的错误信息(通常是一个错误类型或字符串)。

  3. panic 的执行过程

    • 触发 panic: 当代码执行到 panic 语句时,程序会立刻停止当前函数的执行,开始在调用栈中沿着调用路径向外传播,直到最外层。
    • 调用栈的展开: 在 panic 触发后,程序会从当前函数开始回溯,逐层执行 defer 语句块,释放资源等。
      • defer:Go 中的 defer 语句是用来延迟执行某个函数或语句,直到当前函数返回时再执行。它通常用于资源清理、文件关闭等操作。
    • 终止程序: 如果没有任何机制捕获到这个 panic,程序最终会在控制台打印出堆栈信息,然后终止。
  4. 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.
    
  5. panic 用途
    严重错误:panic 通常用于程序中遇到无法恢复的严重错误(例如,空指针引用、数组越界等)。
    程序设计中的不正常状态:比如,某个操作返回了不符合预期的值,但程序设计上不允许出现这种值时,可以用 panic 来触发异常。

  6. panic 不建议用于:
    常规错误处理:Go 的错误处理机制(通过返回值来传递错误)应该用于常规的错误检查和处理。panic 通常不适合用来处理正常的、可以预料到的错误。

Go 标准库的 flag 包

  1. 使用 flag.StringVar 定义命令行标志
    flag.StringVar(&variable, "flagName", "defaultValue", "description")
    
    - &variable:指定一个指针,保存命令行传入的值。
    - "flagName":标志的名字,通常是命令行参数的短名称(如 -c)。
    - "defaultValue":如果命令行没有指定该标志的值,使用的默认值。
    - "description":这个标志的描述,用于帮助信息。
    
  2. 调用 flag.Parse()解析命令行参数
    • flag.Parse() 会从命令行获取所有参数并进行解析。它会逐个解析命令行中的标志
    • 每个定义的标志(通过 flag.StringVar、flag.IntVar、flag.BoolVar 等)都会与命令行中的参数进行匹配。如果命令行中包含某个标志(如 -c),flag.Parse() 会找到对应的变量并将参数值存储到这个变量中。如果命令行没有传入某个标志,变量会使用标志定义时指定的默认值
    • 处理未知标志:如果命令行中包含没有定义的标志,flag.Parse() 会自动输出错误信息,并显示所有已定义的标志的帮助信息。
    • 处理帮助标志:如果命令行中传入 -h 或 --help 参数,flag.Parse() 会自动显示程序的帮助信息,包括所有命令行标志及其说明。-h 是由 flag 包内置的标志,表示请求帮助。
  3. 总之,一旦调用 flag.Parse(),所有命令行标志都已被解析,并且对应的变量已经被赋值,程序可以继续使用这些变量来控制后续的行为。

Go 语言标准库 os 包

os.Getenv(key string)
它从当前进程的环境变量中查找对应 key 的值。这个进程是你运行 Go 程序时启动的进程。
如果环境变量存在,os.Getenv() 会返回该变量的值(字符串类型);如果该环境变量没有设置或找不到,它会返回空字符串 “”。
操作系统层级的环境变量: 操作系统允许在启动程序之前设置环境变量
运行时设置的环境变量: 环境变量可以在程序启动时由外部脚本或操作系统传递。
操作系统的环境配置文件: 在某些操作系统中,环境变量可能通过配置文件(如 .bashrc, .bash_profile, .zshrc, /etc/environment 等)进行设置,系统或用户登录时会加载这些文件并设置环境变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值