2. 配置文件读取与写入
- 使用 https://github.com/spf13/cobra 实现命令工具
- 命令具有以下参数
--config
,-c
配置文件
配置文件如下
# config.yml
name: zhangsan
age: 20
- 将配置文件保存为
JSON
格式
$ cat config.json
输出结果
{
"name":"zhangsan",
"age": 20
}
之前我们使用了 单个参数独立绑定 的方式, 为我们的 greeting 应用绑定了 name
和 age
参数。
这种方式有个很明显的缺点, 如果应用参数的数量较多(比如说几十个) 的时候, 就会出现庞大的参数列表, 可读性 和 维护性 都会变得很差。
var (
name string
age int
)
func init() {
root.Flags().StringVarP(&name, "name", "n", "", "名字")
root.Flags().IntVarP(&age, "age", "a", 20, "名字")
}
因此在生产中, 我们常常会选择 配置 文件来进行 参数/变量 的管理。 例如作业要求中提到的 YAML
或者 JSON
文件。
最常用 的的配置文件类型就是 YAML
和 JSON
。 除此之外还有 toml, xml, ini
等, 这些类型的配置文件可以在实际碰到的时候去学习掌握, 大差不差。
通常 解析 和 映射 方法名称分别为 Marshal
和 Unmarshal
Marshal(v any) ([]byte, error)
将结构体 解析 成[]byte
类型。Unmarshal(data []byte, v any) error
将[]byte
映射 到结构体中。 这里的v
需要是 指针类型
但有些库在名字上可能具有一定变种,通常也有迹可循。
MarshalJSON
MarshalYAMl
不同库 对应的的 方法名称 和 实现逻辑 也不尽相同, 如果凭经验使用遇到了问题, 一定要去看看官网文档。
最基本常用的解析库
json
:encoding/json
yaml
:gopkg.in/yaml.v3
,gopkg.in/yaml.v2
v2 和 v3 有区别, 自己研究。
由于我们的实际 参数/变量 通过配置文件管理了, 因此我们就需要指定一个配置文件参数。
var config string
func init() {
root.Flags().StringVarP(&config, "config", "c", "config.yml", "配置文件")
}
有了上一篇作业的经验, 这个应该就很简单。
我们定义了一个名为 Person
的结构体, 包含两个字段 Name 和 Age, 用于接受参数/变量。
type Person struct {
Name string `yaml:"name,omitempty"`
Age int `yaml:"age,omitempty"`
}
我们在字段后面, 使用 tag
进行了信息补充, 提供 yaml
解析的行为参数。
图片是之前 struct 的截图, 其中
- (1) 是字段名称
- (2-4) 是
tag
内容。- (2) 是
tag name
, 解析库依赖此字段进行判定。 - (3-4) 是
tag value
, 本质上是 字符串。 例如这里name
和omitempty
使用 分隔符,
进行分割。 不同的库的分隔符有所不同, 例如gorm
使用的 分号;
。 - (3) 在这里对应的是 映射 的字段名称。 例如 yaml 文件中的
name
对应结构体中的Name
。 这里name
也可以是其他值, 例如MyName
, 那么就会在 yaml文件中找对应的 MyName 字段。 - (4)
omitempty
是 yaml 支持的操作符, 同时还支持flow, inline
。 具体功能描述可以参考 https://pkg.go.dev/gopkg.in/yaml.v3#pkg-functions
- (2) 是
func readConfig(name string) *Person {
person := &Person{}
// 1. 读取文件
b, err := os.ReadFile(config)
if err != nil {
panic(err)
}
// 2. 绑定参数
err2 := yaml.Unmarshal(b, person)
if err2 != nil {
panic(err)
}
return person
}
- 初始化
person
指针对象, 其底层类型为 Person 结构体。 - 在
readConfig
函数中, 我们使用os.ReadFile
读取文件内容。 - 并通过
yaml.Unmarshal
将数据映射到person
实例中。 需要 注意的是,person
所在的参数未知, 必须是 指针 对象, 否则反射无法保存数据到 内存 中。
把配置信息保存为 json 文件, 就是之前 yaml 的反操作。
// dumpConfig 保存文件
func dumpConfig(person *Person) {
// 将结构体解析成 []byte
b, err := json.Marshal(person)
if err != nil {
panic(err)
}
// os.ModePerm => folder 755, file 644
err2 := os.WriteFile("config.json", b, os.ModePerm)
if err2 != nil {
panic(err)
}
}
- 使用
json.Marshal
将对象转换成 []byte。 由于是 读取操作,person
所在的参数为止可以是结构体, 也可以是指针。 - 使用
os.WriteFile
进行文件写入操作。 需要注意的是os.ModePerm
这个权限, 对于文件夹是 755, 对于文件是 644, 非常的灵活。
{"Name":"zhugeliang","Age":2600}
执行后,得到的结果与期望的结果有一点点不同。注意 ,这里的 Name
和 Age
都是大写。
我们对 Person
结构体稍微做一点改造
type Person struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Age int `yaml:"age,omitempty" json:"age,omitempty"`
}
加上 json tag
之后, 得到的结果就与期望一致了。
{"name":"zhugeliang","age":2600}
留一点思考题吧。
- 为什么没有设置
json tag
也可以成功保存 json 配置? - 设置了
json tag
之后, json 配置中的字段名字变了, 他们的优先级是什么? - 要怎么 忽略 一个字段?
答案在官方文档中。