config — 配置加载
这个示例把 pkg/core/config 的全部规则压在一个可运行的程序里:
结构体 tag 提供默认值、环境变量覆盖与可选键,YAML 里的 ${VAR} 在解析前展开。
看完你会知道一个配置段该怎么写,以及缺键时程序会在什么时候、以什么方式失败。
运行
go run ./examples/config
# 环境变量优先于文件:
DB_DSN='postgres://localhost/overridden' go run ./examples/config
没有外部依赖,不需要 make dev,也不监听端口——它是纯库演示。
-conf 默认指向 examples/config/config.yaml,
所以从仓库根目录直接跑就能读到那份文件。
它演示了什么
- tag 三段语义:
default:"…"给缺省值,env:"VAR"让环境变量覆盖文件值,optional:""标记可以缺省的键。 - 什么都没有的键就是必填:key 缺失即报错,不是悄悄留零值。
${VAR}与${VAR:default}展开:解析前先在 YAML 文本里做变量替换。- 字段名解析规则:优先
jsontag,退回小写字段名,key 大小写不敏感。 - 不落盘解析:
config.Parse直接吃一段[]byte,测试里最常用。 - 支持的类型:
time.Duration("5s")、[]string("a,b"或 YAML 列表)、[]int、map[string]T、嵌套结构体。
tag 是怎么写的
下面这段来自 examples/config/main.go 的 Config 定义。
注意 MaxIdleConns 是 optional——文件里故意没有这个键,
读出来就是零值;而 DSN 没有默认值,只能靠文件或 DB_DSN 提供。
type Config struct {
HTTP struct {
Addr string `json:"addr" default:":8080"`
Timeout time.Duration `json:"timeout" default:"10s"`
} `json:"http"`
DB struct {
DSN string `json:"dsn" env:"DB_DSN"` // env 优先于文件
MaxOpenConns int `json:"max_open_conns" default:"16"`
MaxIdleConns int `json:"max_idle_conns" optional:""` // 可缺省 → 零值
} `json:"db"`
Features []string `json:"features" default:"a,b"` // "a,b" 或 YAML 列表
Debug bool `json:"debug" default:"false"`
}
缺键就该在启动时炸
示例最后用一段最小的结构体证明这件事:没有 default 也没有
optional 的 key 缺失时,config.Parse 直接返回错误。
这来自 examples/config/main.go 的末尾几行。
// 没有 default 也没有 optional 的 key 缺失时会报错——配置错误在启动
// 时就该炸,而不是等到运行时才 nil panic。
var strict struct {
Required string `json:"required"`
}
err := config.Parse([]byte("other: 1"), &strict)
fmt.Printf("\n缺少必填 key:%v\n", err)
验证
程序不提供 HTTP 接口,直接看标准输出。默认跑(读 config.yaml):
go run ./examples/config
conf = examples/config/config.yaml
http = 0.0.0.0:8080 (timeout 5s)
db = file:examples-config.db (max_open=8 max_idle=0)
features = [cache events]
debug = true
缺少必填 key:config: missing key "required"
inline = from-bytes
其中 timeout 5s、max_open=8、[cache events]、
debug = true 全部来自 config.yaml;
max_idle=0 是因为文件里没有该键而字段标了 optional。
加环境变量后 db 行会换掉:
DB_DSN='postgres://localhost/overridden' go run ./examples/config
# db = postgres://localhost/overridden (max_open=8 max_idle=0)
flowchart TD
A["YAML 文本
${VAR} / ${VAR:default}"] --> B["环境变量展开"]
B --> C["按 json tag
定位结构体字段"]
C --> D{"key 在文件里吗?"}
D -- 在 --> E["用文件值"]
D -- 不在 --> F{"有 env: tag?"}
F -- "环境变量存在" --> G["用环境变量值"]
F -- "否则" --> H{"有 default: tag?"}
H -- 有 --> I["用默认值"]
H -- 无 --> J{"optional?"}
J -- "是" --> K["留零值"]
J -- "否" --> L["返回错误
启动失败"]
classDef bad fill:#fdf0f0,stroke:#a33,color:#2b2722
class L bad
测试
main_test.go 把 tag 语义当成接口来钉。它断言的具体事实:
TestLoadFromExampleFile:读真实的config.yaml后timeout == 5s、max_open_conns == 8、Features[0] == "cache"、Debug == true, 而optional的MaxIdleConns是 0。TestEnvOverridesFile:t.Setenv("DB_DSN", …)之后, DSN 是环境变量的值。TestDefaultsAndExpansionWithoutFile:只给http: {}与db: {dsn: x}时,Timeout是10s、MaxOpenConns是 16、features是a,b、Debug为 false。TestVarExpansionInYAML:设了HTTP_ADDR就用它, 取消后回落到${HTTP_ADDR:0.0.0.0:1}给出的默认值。TestMissingRequiredKeyFails:Parse必须返回错误。
go test ./examples/config
相关
相关示例
- examples/lifecycle:服务专属段怎么热更。
- examples/feature:
feature:段的取值优先级。 - examples/http:一份真实服务的
config.yaml长什么样。