这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
上一篇我们介绍了第一个实战项目——猜谜游戏,通过不同的 5 个 Version 最终实现了整个项目。此篇我们接着来介绍第二个项目——在线词典,通过击破三个难点来实现此次实战项目。
项目介绍
用户可以在命令行里面查询一个单词,能通过调用第三方的 API (以彩云翻译为例)查询到单词的翻译并打印出来。 这个例子中,我们会学会如何使用 Go 语言来发送 HTTP请求、解析
项目难点
- 进行抓包,对其进行分析
- 生成请求 request body
- 解析 response body
下面就针对以上难点,逐一击破并实现本次实战项目——在线词典。
项目实现
01. 抓包
- 首先打开 彩云翻译 ,右键检查打开浏览器的开发者工具(以Chorme为例)
-
输入查询的单词,点击翻译按钮,浏览器会发送一系列请求,我们就能抓住用来查询单词的请求
- 请求头是一个 JSON 里面包括两个字段,一个是 source 表示查询的单词,另一个是 trans_type 表示从什么语言翻译成什么语言
- API 的返回结果中有 dictionary 和 wiki 两个字段,项目所需要的结果主要在 dictionary.explanations字段里面。此外,还包括音标、例句等信息的字段。
02. 生成请求代码
一般情况下,我们需要在 Golang 中去发送这个请求,过程比较复杂,使用代码构造十分麻烦。实际上有一种简单的方式来生成代码。 1. 右击浏览器里面的以 cURL(bush) 格式复制
2. 打开 代码生成网页 在 curl command 中粘贴并选择编程语言 Go ,生成的代码中有编译错误的话删除即可
3. 运行生成的代码,能看到我们已经能够成功地发出请求,也打印出来返回的 JSON 串。
但是目前输入是固定的,我们需要的是用户输入,即要从一个变量来输入,此时就需要用到 JSON 序列化。
4. 生成 request body
在Golang里面,需要生成一段JSON,常用的方式是先构造出来一个结构体,这个结构体和需要生成的JSON的结构是一一对应的
在我们这个例子中,结构体包含三个字段。我们再定义一个变量,初始化每个结构体成员,再调用 JSON.marshaler 来得到这个序列化之后的字符串。 不同于之前是字符串,我们这里是字节数组,所以我们把 strings.NewReader 改成 tytes.NewReader 然后再构造 request body ,接不来代码不变。然后我们就能成功地进入一个变量来发HTTP请求。这一步完成之后,结果应该是完全不变的。
03. 解析 response body
在 js/Python 这些脚本语言里面, body是一个字典或者 map 的结构,可以直接从里面取值。但是 Golang 是个强类型语言,这种做法并不是最佳的。 更常用的方式是和request的一样,写一个结构体,把返回的JSON反序列化到结构体里面。但是我们在浏览器里面可以看到这个 API 返回的结构非常复杂,如果要一一定义结构体字段,非常繁琐并且容易出错。
这里介绍一种简单的方式
在这个网页中 oktools.net/json2go 将JSON字符串粘贴进去,便可以生成对应的结构体。
转化-展开 独立的多个结构体
转化-嵌套 一个巨大的结构体,代码更加紧凑
这样我们就得到了一个 response 结构体,如下所示:
type AutoGenerated struct {
Rc int `json:"rc"`
Wiki struct {
KnownInLaguages int `json:"known_in_laguages"`
Description struct {
Source string `json:"source"`
Target interface{} `json:"target"`
} `json:"description"`
ID string `json:"id"`
Item struct {
Source string `json:"source"`
Target string `json:"target"`
} `json:"item"`
ImageURL string `json:"image_url"`
IsSubject string `json:"is_subject"`
Sitelink string `json:"sitelink"`
} `json:"wiki"`
Dictionary struct {
Prons struct {
EnUs string `json:"en-us"`
En string `json:"en"`
} `json:"prons"`
Explanations []string `json:"explanations"`
Synonym []interface{} `json:"synonym"`
Antonym []interface{} `json:"antonym"`
WqxExample [][]string `json:"wqx_example"`
Entry string `json:"entry"`
Type string `json:"type"`
Related []interface{} `json:"related"`
Source string `json:"source"`
} `json:"dictionary"`
}
运行代码,打印的使用 %#v, 这样可以让打印的结果比较容易读。
此时我们离最终版本已经很近了,接下来我们修改代码来打印 response 里面的特定字段
04. 打印结果
观察返回结果的 JSON,可以看出我们所需要的结果在 dictionary.explanations 字段。可以采用 for range 循环迭代它,然后参照一些词典的显示方式直接打印,同时也可以打印其他想要的东西,例如单词、音标和例句等等。
fmt.Printf("%#v\n", dictResponse)
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
- 注:思考下列代码的作用? “防御式编程”
if resp.StatusCode != 200 {
log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
05. 完善程序
现在我们的程序的输入还是写死的。我们把的主体改成一个 query 函数,查询的单同作为参数传进来,然后写一个简单的 main 函数,这个main函数首先会判断命令和参数的个数是否正确,若不正确就打印错误信息并退出游戏。若正确就获取到用户输入的单词然后执行 query 函数。
06. 测试程序
这样我们的项目——在线词典就算完成了,我们可以在 cmd命令行 或 终端 测试——输入命令 go run main.go employment ,执行结果如下所示:
个人总结
顾名思义,实战项目就需要动手练习,如果只是纸上谈兵那仅仅是不够的,我们需要在实践中发现自己的不足,同时在实践中还会发现自己在这方面的薄弱点。与此同时,我也打算再进一步对代码进行重构,使代码更加规范。
如果笔记中有错误的地方也希望掘友们可以及时的提出纠正。