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. 高级功能:如热加载,提升工具的实时性。
相关推荐
GetcharZp7 小时前
告别OOM!用Go+libvips实现30000×50000超大图片的流式瓦片服务
后端·go
妙码生花3 天前
从 PHP 到 AI + Golang,程序员自救转型手记(八):设计管理员模型、热重载配置
前端·后端·go
tyung4 天前
Go 手写 Wait-Free MPSC 无界队列:SwapPointer 实现多生产者无锁入队
后端·go
陈明勇5 天前
Go 1.26 新特性回顾:语言增强、工具升级与 Green Tea GC 默认启用
后端·go
妙码生花6 天前
从 PHP 到 AI + Golang,程序员自救转型手记(二):目录结构、初始化 GIT、设计并开发配置系统
前端·后端·go
leeyi6 天前
Deer-Go:字节 Deer-Flow 的 Go 移植,深度研究 Agent 全拆解
go·aigc·agent
Bolt6 天前
TypeScript 7.0 来了:当 tsc 用 Go 重写之后
javascript·typescript·go
Go_error7 天前
Datatypes:Go 轻松支持数据库JSON类型
后端·go
任沫7 天前
Agent之Function Call
javascript·人工智能·go
唐青枫8 天前
别再把 interface 当万能盒子:Go 接口从隐式实现到项目解耦
go