1. 接口分层(config/config.go)
scss
type Configer interface { // :56-86 实例(一个已加载文件)
Set(key, val string) error
String(key) (string, error)
Strings(key) ([]string, error) // ";" 分隔
Int/Int64/Bool/Float (key)
DefaultString/DefaultInt/... // 带默认值
DIY(key) (interface{}, error)
GetSection(section) (map[string]string, error)
Unmarshaler(prefix string, obj interface{}, opts ...DecodeOption) error
Sub(key string) (Configer, error)
OnChange(key string, fn func(value string))
SaveConfigFile(filename string) error
}
type Config interface { // :200-208 格式解析器
Parse(key string) (Configer, error) // 从文件
ParseData(data []byte) (Configer, error) // 从内存
}
两层抽象:Configer = 一份配置的视图;Config = 一种格式的解析器。
2. BaseConfiger:模板方法的 Go 写法(config.go:88-197)
go
type BaseConfiger struct {
reader func(ctx context.Context, key string) (string, error) // :88-91 唯一的注入点
}
基类实现全部类型转换方法,全部基于 reader:
go
func (c *BaseConfiger) Int(key) (int, error) {
sv, err := c.reader(context.Background(), key)
return strconv.Atoi(sv)...
}
func (c *BaseConfiger) Strings(key) ([]string, error) {
sv, _ := c.String(key)
return strings.Split(sv, ";"), nil
}
子类(IniConfigContainer:71、EtcdConfiger:45)只提供 reader,覆盖需要差异化的方法(Set/DIY/GetSection/Unmarshaler/Sub/SaveConfigFile)。新增一种格式的成本 = 一个 reader + 少量覆盖,这就是 ini 适配器只有百来行的原因。
默认实现里 Sub 返回 unsupported、OnChange 空操作------接口方法带默认行为,避免强迫 12 个实现都写一遍。
3. 注册机制(config.go:205-238)
go
var adapters = make(map[string]Config) // :205
func Register(name string, adapter Config) // :210 重名/nil panic
func NewConfig(adapterName, filename) // :222
func NewConfigData(adapterName, data) // :232
各格式包 init 自注册:
| 包 | 注册名 | 存储结构 | Unmarshaler |
|---|---|---|---|
| ini.go | "ini" | map[string]map[string]string + BaseConfiger |
ini.go:513 |
| json/ | "json" | map(自实现全接口,未用 Base) | mapstructure.Decode(json.go:83) |
| yaml/ | "yaml" | map | mapstructure |
| toml/ | "toml" | map | mapstructure |
| xml/ | "xml" | map | mapstructure(xml.go:102) |
| etcd/ | "etcd" | 前缀+clientv3 + BaseConfiger | mapstructure(etcd/config.go:112) |
| fake.go | -(测试) | 内存 map | 配合 base_config_test |
json 特点:getData 支持 a::b::c 多级 key(json.go:279-306);顶层数组包 "rootArray"。
env 包(特殊)
不是 Configer!独立的环境变量工具(sync.Map 全量缓存 os.Environ,env.go:28-37),Get/MustGet/Set/MustSet/GetAll;GetGOBIN/GetGOPATH 还会解析 go env -w 的 GOENV 文件(:111)。
etcd 包(远程配置)
- reader 每次读都是实时 get(config.go:50-61)------天然"读最新"
- Set/SaveConfigFile 返回 Unsupported(:65,94)
- Sub 只是拼新 prefix(:116)
- OnChange 基于 etcd watch------唯一实现 OnChange 的地方(热更新)
4. 解析流程(以 NewConfig("json","app.conf") 为例)
scss
NewConfig
└─ adapters["json"].Parse("app.conf") (json.go:37-49)
├─ os.ReadFile
└─ ParseData(data) (json.go:52-69)
├─ json.Unmarshal → map
├─ ExpandValueEnvForMap (config.go:241-308)
│ 展开 ${ENV} 与 ${ENV||default}
└─ 返回 JSONConfigContainer
读取:conf.String("redis::host") → getData 按 :: 逐级下钻
5. Unmarshaler 与 mapstructure
go
func (c *JSONConfigContainer) Unmarshaler(prefix, obj, opts...) error {
// 取子树 map → config.md 结构体 tag 处理 → mapstructure.Decode
}
DecodeOption(:约 90-100)可定制 key 处理(如大小写不敏感)。为什么不用 json tag :配置树在运行时是 mapstringinterface{},mapstructure 专门做 map→struct,支持弱类型转换(字符串 "8080" → int 8080),WeaklyTypedInput 类容错比标准库 json.Unmarshal 更适合"人写的配置"。
6. 全局实例(global.go)
go
var globalInstance Configer // :19
func InitGlobalInstance(name, cfg string) error { // :25
globalInstance, err = NewConfig(name, cfg)
...
}
// 之后所有包级函数代理:
func String(key string) string { return globalInstance.String(key) } # :32 起
注意依赖关系:子包(json 等)在自己的 init 里可能调 InitGlobalInstance 初始化默认全局------import 顺序影响全局配置指向,框架自己的 AppConfig(web)是独立实例,不受影响。
7. web 侧:BConfig 与 beegoAppConfig(server/web/config.go)
7.1 加载(config.go:458-483)
bash
init:
找 conf/app.conf(BEEGO_RUNMODE → conf/<mode>.app.conf)
parseConfig(读文件→Configer)
assignConfig:
parseConfigForV1 # 1.x 扁平 key 兼容(appname/httpport...)
ac.Unmarshaler("", BConfig) # map → Config 结构体
按 BConfig.Log.Outputs 初始化日志
7.2 RunMode 覆盖(beegoAppConfig:754-872)
go
// beegoAppConfig 内嵌 AppConfig(内层 ini/json 实例)
func (b *beegoAppConfig) String(key string) string {
// 1. 先试 "runmode::key"(如 "prod::httpport")
// 2. miss 再回退全局 key
}
所有 web.AppConfig.Xxx 都经过这层------分环境配置的真相只是"读时优先换个 section 名" 。
7.3 默认值(newBConfig:523-603)
全部字面量集中初始化,值得对照阅读:如 SessionProvider:"memory"、HTTPPort:8080、FlashName:"BEEGO_FLASH"。
8. 设计赏析
- 模板方法+注入函数:BaseConfiger 不暴露给用户,只服务于"实现者",接口仍然干净。
- 注册表统一 12 种引擎:配置/日志/缓存/session 四大模块同构(对照 S01 表)。
- 读写不对称:ini/json 全支持 SaveConfigFile;etcd 只读+watch------接口承认"不是所有配置源可写",用默认错误实现兜底。
- ExpandValueEnv 在解析期:环境变量展开只发生一次,运行期改 ENV 不影响已加载配置(文档要提醒用户的点)。