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. 高级功能:如热加载,提升工具的实时性。
相关推荐
代码扳手4 小时前
Go + gRPC + HTTP/3:解锁下一代高性能通信
go
mit6.8247 小时前
[OP-Agent] `opa run` | 交互模式(REPL) | 服务模式(HTTP API) | runtime包
go·1024程序员节
梁梁梁梁较瘦2 天前
边界检查消除(BCE,Bound Check Elimination)
go
梁梁梁梁较瘦2 天前
指针
go
梁梁梁梁较瘦2 天前
内存申请
go
半枫荷2 天前
七、Go语法基础(数组和切片)
go
梁梁梁梁较瘦3 天前
Go工具链
go
半枫荷3 天前
六、Go语法基础(条件控制和循环控制)
go
半枫荷4 天前
五、Go语法基础(输入和输出)
go
小王在努力看博客4 天前
CMS配合闲时同步队列,这……
go