golang 中 yaml 配置文件的加载

假如我们有以下配置文件( config.yml ):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
database:
  sqlite:
    path: ./main.db
  mysql:
    host: localhost
    port: 3306
    username: hello
    password: god.pwd
    database: dev
jwt:
  secret: ok-this-is-jst-sec
  expire_time: 24

使用 yaml 库加载

  1. 先使用 os.ReadFile 读取文件内容。
  2. 再使用 yaml.Unmarshal 反序列化文件内容。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
	"fmt"
	"log"
	"os"

	"gopkg.in/yaml.v3"
)

type Config struct {
	Database DatabaseConfig `yaml:"database"`
	JWT      JWTConfig      `yaml:"jwt"`
}

type DatabaseConfig struct {
	MySQL  MySQLConfig  `yaml:"mysql"`
	Sqlite SqliteConfig `yaml:"sqlite"`
}

type MySQLConfig struct {
	Host     string `yaml:"host"`
	Port     int    `yaml:"port"`
	Username string `yaml:"username"`
	Password string `yaml:"password"`
	Database string `yaml:"database"`
}

type SqliteConfig struct {
	Path string `yaml:"path"`
}

type JWTConfig struct {
	Secret     string `yaml:"secret"`
	ExpireTime int    `yaml:"expire_time"`
}

func main() {
	cfg := &Config{}
	data, err := os.ReadFile("config.yml")
	if err != nil {
		log.Fatal(err)
	}
	err = yaml.Unmarshal(data, cfg)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(cfg)
	fmt.Println(cfg.Database.MySQL.Username)

}

输出:

1
2
3
4
$ go run .
&{{{localhost 3306 hello god.pwd dev} {./main.db}} {ok-this-is-jst-sec 24}}
hello
$ 

使用 viper 加载

viper 加载与 yaml 加载类似,但支持的功能更多,以热更新为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

type Config struct {
	Database DatabaseConfig `yaml:"database"`
	JWT      JWTConfig      `yaml:"jwt"`
}

type DatabaseConfig struct {
	MySQL  MySQLConfig  `yaml:"mysql"`
	Sqlite SqliteConfig `yaml:"sqlite"`
}

type MySQLConfig struct {
	Host     string `yaml:"host"`
	Port     int    `yaml:"port"`
	Username string `yaml:"username"`
	Password string `yaml:"password"`
	Database string `yaml:"database"`
}

type SqliteConfig struct {
	Path string `yaml:"path"`
}

type JWTConfig struct {
	Secret     string `yaml:"secret"`
	ExpireTime int    `yaml:"expire_time"`
}

func main() {
	// 1. 设置配置文件名称(不带后缀)
	viper.SetConfigName("config")
	// 2. 设置配置文件类型:yml / yaml
	viper.SetConfigType("yml")
	// 3. 设置配置文件路径(当前目录)
	viper.AddConfigPath(".")

	// 读取配置
	cfg := Config{}
	err := viper.ReadInConfig()
	if err != nil {
		log.Fatal(err)
	}
	// 反序列化配置
	err = viper.Unmarshal(&cfg)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(cfg)
	fmt.Println(cfg.Database.MySQL.Username)

	// 开启配置文件监听热更新
	viper.WatchConfig()

	// 配置变化回调函数
	viper.OnConfigChange(func(e fsnotify.Event) {
		log.Println("检测到配置文件修改,重新加载配置...")
		// 重新反序列化到全局变量
		if err := viper.Unmarshal(&cfg); err != nil {
			log.Printf("热更新重新解析配置失败: %v\n", err)
		} else {
			log.Println("配置热更新成功!")
            os.Exit(0)
		}
	})
	// 循环打印,测试是否实时更新
	for {
		fmt.Println(cfg)
		time.Sleep(3 * time.Second)
	}

}

输出:

1
2
3
4
5
6
7
8
$ go run .
{{{localhost 3306 hello god.pwd dev2} {./main.db}} {ok-this-is-jst-sec 0}}
hello
{{{localhost 3306 hello god.pwd dev2} {./main.db}} {ok-this-is-jst-sec 0}}
{{{localhost 3306 hello god.pwd dev2} {./main.db}} {ok-this-is-jst-sec 0}}
2026/05/04 18:23:14 检测到配置文件修改,重新加载配置...
2026/05/04 18:23:14 配置热更新成功!
$ 

viper 还有一种快捷加载方式,无需事先编写结构体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
	"fmt"

	"github.com/spf13/viper"
)

func main() {
	// 1. 设置配置文件名称(不带后缀)
	viper.SetConfigName("config")
	// 2. 设置配置文件类型:yml / yaml
	viper.SetConfigType("yml")
	// 3. 设置配置文件路径(当前目录)
	viper.AddConfigPath(".")
	// 4. 读取配置
	viper.ReadInConfig()

	// 5. 访问具体的配置项
	db := viper.Get("database")
	host := viper.GetString("database.mysql.host")
    port := viper.GetInt("database.mysql.port")
	fmt.Println(db, host, port)

}

输出:

1
2
3
$ go run .
map[mysql:map[database:dev2 host:localhost password:god.pwd port:3306 username:hello] sqlite:map[path:./main.db]] localhost 3306
$ 
updatedupdated2026-05-042026-05-04