1. 功能需求分析
-
配置格式支持
- 支持 YAML、JSON、TOML 等格式(示例中使用 YAML)。
-
默认配置与环境配置分离
- 默认配置文件(如
config.yaml
),环境专属配置文件(如config.dev.yaml
、config.prod.yaml
)。
- 默认配置文件(如
-
命令行参数与环境变量覆盖
- 允许通过命令行参数或环境变量来指定环境或覆盖配置项。
-
配置合并策略
- 先加载默认配置,再根据环境加载对应文件,最后合并命令行/环境变量覆盖。
-
热加载(可选)
- 如果需要支持配置文件实时更新,可以加入文件监听,自动重新加载配置。
2. 分阶段规划与示例代码
阶段1:基本配置读取功能
阶段2:支持多环境配置文件加载
在加载默认配置的基础上,根据传入环境参数加载对应的环境配置文件,并进行合并覆盖。
阶段3:支持命令行和环境变量覆盖配置
在前面的基础上,允许通过命令行参数或环境变量对配置项进行覆盖。例如,可以通过环境变量 APP_NAME
来修改应用名称。
阶段4:配置热加载(高级可选功能)
使用 fsnotify 库监听配置文件变化,检测到修改后重新加载配置。
小工具目录结构
主程序入口
go
package main
import (
"fmt"
"configload/tools"
"github.com/spf13/cobra"
)
var configFile, appName string
var port int
func main() {
var rootCmd = &cobra.Command{
Use: "configload",
Short: "这是一个配置加载小工具",
PreRunE: func(cmd *cobra.Command, args []string) error {
if configFile == "" {
return fmt.Errorf("配置文件路径不能为空")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
filecfg, err := tools.LoadConfig(configFile)
if err != nil {
fmt.Printf("load config failed, err:%v\n", err)
return
}
envCfg := tools.LoadConfigFromEnv()
cfg := tools.MergeConfig(filecfg, envCfg)
tools.LoadConfigFromFlags(appName, port, cfg)
fmt.Printf("load config success, config:%v\n", cfg)
tools.WatchConfig(configFile)
},
}
rootCmd.Flags().StringVarP(&configFile, "config", "c", "", "配置文件路径")
rootCmd.Flags().StringVarP(&appName, "appname", "a", "", "应用名称")
rootCmd.Flags().IntVarP(&port, "port", "p", 0, "端口号")
if err := rootCmd.Execute(); err != nil {
fmt.Printf("执行命令失败, err:%v\n", err)
}
}
tools文件夹下的各个文件
loadconfig.go
go
package tools
import (
"fmt"
"os"
"strconv"
"gopkg.in/yaml.v3"
)
type Config struct {
AppName string `yaml:"app_name"`
Port int `yaml:"port"`
}
// 从文件加载配置
func LoadConfig(file string) (*Config, error) {
data, err := os.ReadFile(file)
if err != nil {
return nil, err
}
var config Config
if err = yaml.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
// 从环境变量加载配置
func LoadConfigFromEnv() *Config {
appName, err := GetEnv("APP_NAME")
if err != nil {
appName = ""
}
port, err := GetEnv("PORT")
if err != nil {
port = "0"
}
portInt, err := strconv.Atoi(port)
if err != nil {
portInt = 0
}
return &Config{
AppName: appName,
Port: portInt,
}
}
// 从用户输入flags加载配置
func LoadConfigFromFlags(appname string, port int, fileconfig *Config) {
if appname != "" {
fileconfig.AppName = appname
}
if port != 0 {
fileconfig.Port = port
}
}
// 使用环境配置覆盖默认配置
func MergeConfig(defaultCfg, envCfg *Config) *Config {
if envCfg.AppName != "" {
defaultCfg.AppName = envCfg.AppName
}
if envCfg.Port != 0 {
defaultCfg.Port = envCfg.Port
}
return defaultCfg
}
// 从文件重新加载配置
func ReloadConfig(filename string) {
data, err := os.ReadFile(filename)
if err != nil {
fmt.Printf("reload config failed, err:%v\n", err)
return
}
var config Config
if err = yaml.Unmarshal(data, &config); err != nil {
fmt.Printf("reload config failed, err:%v\n", err)
return
}
fmt.Printf("reload config success, config:%v\n", config)
}
watcher.go
go
package tools
import (
"fmt"
"log"
"time"
"github.com/fsnotify/fsnotify"
)
func WatchConfig(filename string) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write == fsnotify.Write {
fmt.Println("配置文件被修改:", event.Name)
// 此处调用重新加载配置的函数
ReloadConfig(event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("Watcher 错误:", err)
}
}
}()
if err := watcher.Add(filename); err != nil {
log.Fatal(err)
}
// 保持进程运行
<-time.After(10 * time.Minute)
}
getenv.go
go
package tools
import (
"fmt"
"os"
)
// 获取环境变量
func GetEnv(key string) (string, error) {
if value, ok := os.LookupEnv(key); ok {
return value, nil
}
return "", fmt.Errorf("环境变量 %s 未设置", key)
}
配置事例config.yaml
yaml
app_name: app1
port: 8081
总结
- 需求分析与设计:明确支持的配置格式、环境、覆盖方式。
- 模块划分:将配置加载、合并、热加载、测试等功能模块化。
- 基本功能实现:先实现从文件加载配置,再逐步扩展到环境配置、命令行覆盖。
- 高级功能:如热加载,提升工具的实时性。