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. 高级功能:如热加载,提升工具的实时性。
相关推荐
研究司马懿5 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大19 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰1 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo