go实现配置热加载小工具

1. 功能需求分析

  • 配置格式支持

    • 支持 YAML、JSON、TOML 等格式(示例中使用 YAML)。
  • 默认配置与环境配置分离

    • 默认配置文件(如 config.yaml),环境专属配置文件(如 config.dev.yamlconfig.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

总结

  1. 需求分析与设计:明确支持的配置格式、环境、覆盖方式。
  2. 模块划分:将配置加载、合并、热加载、测试等功能模块化。
  3. 基本功能实现:先实现从文件加载配置,再逐步扩展到环境配置、命令行覆盖。
  4. 高级功能:如热加载,提升工具的实时性。
相关推荐
我的golang之路果然有问题3 小时前
快速上手GO的net/http包,个人学习笔记
笔记·后端·学习·http·golang·go·net
用户16849371443114 小时前
通过 goat 工具对 golang 应用进行增量代码的埋点和监控
go
旅人CS4 小时前
用Go语言理解单例设计模式
设计模式·go
用户0142260029845 小时前
Go(Golang)类型断言
go
用户0142260029846 小时前
golang方法指针接收者和值接收者
go
纪元A梦7 小时前
华为OD机试真题——通过软盘拷贝文件(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
java·javascript·c++·python·华为od·go·华为od机试题
孔令飞11 小时前
彻底学会 gRPC:用 Go 实现一个迷你考试服务
人工智能·云原生·go
GetcharZp13 小时前
FileBrowser:用浏览器轻松管理服务器文件,简洁又强大
后端·go
帽儿山的枪手1 天前
如何使用socket系统调用创建TCP三次握手呢?
网络协议·tcp/ip·go
行者无疆xcc1 天前
【Go】重难点知识汇总
go