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. 高级功能:如热加载,提升工具的实时性。
相关推荐
yz1.3 小时前
GolangTCP通信解决粘包问题
网络·go·tcp
zhuyasen4 小时前
Go错误码规范化指南:构建优雅的HTTP & gRPC错误处理体系
后端·go
梦兮林夕11 小时前
04 学会 Gin 中间件,这些操作你也能随心所欲!
go·gin
大鹏dapeng13 小时前
Gone 从v1升级v2 有哪些变化?
后端·go
一个热爱生活的普通人15 小时前
Gin 响应渲染:JSON、HTML与模板引擎
后端·go·gin
寻月隐君1 天前
深入剖析 Go 接口底层实现:从 eface 到 iface(基于 Go 1.24 源码)
后端·go·github
DemonAvenger2 天前
Go sync.Pool 的陷阱与正确用法:从踩坑到最佳实践
架构·go
GISer_Jing2 天前
[高阶技术了解]WebRPC详解
react.js·go·web3
大鹏dapeng2 天前
Gone v2 goner/gin——试试用依赖注入的方式打开gin-gonic/gin
后端·go