DevopsCamp 第一期作业: 《cobra - 02 配置文件的读取与写入(简单)》 解题答案

原文链接: https://tangx.in/posts/2023/01/24/devopscamp-cobra-binding-config-file/

本文为 DevOpsCamp 实战训练作业 cobra - 02 配置文件的读取与写入(简单) 的解题答案

DevoOpsCamp 作业地址: https://www.devopscamp.cc/semi-plan-202301-2/posts/homework/cobra02/

devopscamp-logo

作业要求

  1. 使用 https://github.com/spf13/cobra 实现命令工具
  2. 命令具有以下参数
    1. --config , -c 配置文件

配置文件如下

1
2
3
# config.yml
name: zhangsan
age: 20
  1. 将配置文件保存为 JSON 格式
1
$ cat config.json

输出结果

1
2
3
4
{
    "name":"zhangsan",
    "age": 20
}

单个参数绑定的困境

之前我们使用了 单个参数独立绑定 的方式, 为我们的 greeting 应用绑定了 nameage 参数。

这种方式有个很明显的缺点, 如果应用参数的数量较多(比如说几十个) 的时候, 就会出现庞大的参数列表, 可读性维护性 都会变得很差。

1
2
3
4
5
6
7
8
9
var (
	name string
	age  int
)

func init() {
	root.Flags().StringVarP(&name, "name", "n", "", "名字")
	root.Flags().IntVarP(&age, "age", "a", 20, "名字")
}

因此在生产中, 我们常常会选择 配置 文件来进行 参数/变量 的管理。 例如作业要求中提到的 YAML 或者 JSON 文件。

配置文件

最常用 的的配置文件类型就是 YAMLJSON。 除此之外还有 toml, xml, ini 等, 这些类型的配置文件可以在实际碰到的时候去学习掌握, 大差不差。

通常 解析映射 方法名称分别为 MarshalUnmarshal

  1. Marshal(v any) ([]byte, error) 将结构体 解析[]byte 类型。
  2. Unmarshal(data []byte, v any) error[]byte 映射 到结构体中。 这里的 v 需要是 指针类型

但有些库在名字上可能具有一定变种,通常也有迹可循。

MarshalJSON
MarshalYAMl

不同库 对应的的 方法名称实现逻辑 也不尽相同, 如果凭经验使用遇到了问题, 一定要去看看官网文档。

最基本常用的解析库

  1. json: encoding/json
  2. yaml: gopkg.in/yaml.v3, gopkg.in/yaml.v2 v2 和 v3 有区别, 自己研究。

解题过程

1. 指定配置文件参数

由于我们的实际 参数/变量 通过配置文件管理了, 因此我们就需要指定一个配置文件参数。

1
2
3
4
5
var config string

func init() {
	root.Flags().StringVarP(&config, "config", "c", "config.yml", "配置文件")
}

有了上一篇作业的经验, 这个应该就很简单。

2. 读取配置

我们定义了一个名为 Person 的结构体, 包含两个字段 Name 和 Age, 用于接受参数/变量。

1
2
3
4
type Person struct {
	Name string `yaml:"name,omitempty"`
	Age  int    `yaml:"age,omitempty"`
}

我们在字段后面, 使用 tag 进行了信息补充, 提供 yaml 解析的行为参数。

golang-struct-tag

图片是之前 struct 的截图, 其中

  1. (1) 是字段名称
  2. (2-4)tag 内容。
    1. (2)tag name, 解析库依赖此字段进行判定。
    2. (3-4)tag value, 本质上是 字符串。 例如这里 nameomitempty 使用 分隔符, 进行分割。 不同的库的分隔符有所不同, 例如 gorm 使用的 分号;
    3. (3) 在这里对应的是 映射 的字段名称。 例如 yaml 文件中的 name 对应结构体中的 Name。 这里 name 也可以是其他值, 例如 MyName, 那么就会在 yaml文件中找对应的 MyName 字段。
    4. (4) omitempty 是 yaml 支持的操作符, 同时还支持 flow, inline。 具体功能描述可以参考 https://pkg.go.dev/gopkg.in/yaml.v3#pkg-functions
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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
}
  1. 初始化 person 指针对象, 其底层类型为 Person 结构体。
  2. readConfig 函数中, 我们使用 os.ReadFile 读取文件内容。
  3. 并通过 yaml.Unmarshal 将数据映射到 person 实例中。 需要 注意的是person 所在的参数未知, 必须是 指针 对象, 否则反射无法保存数据到 内存 中。

保存文件为 json

把配置信息保存为 json 文件, 就是之前 yaml 的反操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 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)
	}
}
  1. 使用 json.Marshal 将对象转换成 []byte。 由于是 读取操作person 所在的参数为止可以是结构体, 也可以是指针。
  2. 使用 os.WriteFile 进行文件写入操作。 需要注意的是 os.ModePerm 这个权限, 对于文件夹是 755, 对于文件是 644, 非常的灵活。
1
{"Name":"zhugeliang","Age":2600}

执行后,得到的结果与期望的结果有一点点不同。注意 ,这里的 NameAge 都是大写。

我们对 Person 结构体稍微做一点改造

1
2
3
4
type Person struct {
	Name string `yaml:"name,omitempty" json:"name,omitempty"`
	Age  int    `yaml:"age,omitempty" json:"age,omitempty"`
}

加上 json tag 之后, 得到的结果就与期望一致了。

1
{"name":"zhugeliang","age":2600}

思考题

留一点思考题吧。

  1. 为什么没有设置 json tag 也可以成功保存 json 配置?
  2. 设置了 json tag 之后, json 配置中的字段名字变了, 他们的优先级是什么?
  3. 要怎么 忽略 一个字段?

答案在官方文档中。

B站作业视频演示

本视频一切权利归 bilibili原作者所有 如果觉得好, 请点击跳转到 bilibili给予支持 BV1LT411f7Zq