注:本文搬运了一些我青训营期间听课的笔记(我一般直接用注释或者记笔记本上),不保证正确性,而且有些地方可能比较乱,大家看个乐就行了。
另外这篇文章中的源代码贼长,为方便阅读我“折叠”了一部分代码。
实例部分
简易命令行词典
这个程序的大致流程是:用户输入一个要翻译的单词,然后通过query()函数的前半段折叠代码向第三方翻译网站发起翻译的请求,然后接受从第三方网站返回的翻译结果(以json字符串形式),然后将收到的json字符串反序列化,并打印出部分需要的翻译结果。
package main
import (
// 【已折叠】
)
type DictRequest struct {
// 【已折叠】这部分是翻译请求的具体信息,与query函数前半段折叠代码配合使用
}
type DictResponse struct {
// 【已折叠】这是一个自动生成的结构体,用于存储返回的json字符串反序列化后所得信息
}
func query(word string) {
// 【已折叠】这段代码可以将用户输入的word传递给翻译网站,并以json字符串的形式返回翻译结果,可自动生成
if resp.StatusCode != 200 {
log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
// 将返回的json字符串反序列化到一个设定好的结构体,转化为方便输出的内容
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
// 【已折叠】这段代码输出了dictResponse中部分有用的翻译结果
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
`) // 把错误信息写到os.Stderr里,exit后会打印出来
os.Exit(1)
}
word := os.Args[1] // 读入要翻译的单词(用户输入)
query(word)
}
在main()函数的输入部分(第29行),官方对os.Args解释如下:
Args hold the command-line arguments, starting with the program name.
所以第29行表示command line后面并没有接一个单词这一异常情况,于是打印错误+退出。第35行表示如果正常输入,那么就读取os.Args的后面那个内容,也就是用户输入的要翻译的单词。
query()函数中的前半段代码可以将用户输入的word传递给翻译网站,并以json字符串的形式返回翻译结果,其生成方式如下:
- 打开第三方翻译app:fanyi.caiyunapp.com/
- 输入一个单词->检查->network->name中找到dict请求(method为post)->有一堆复杂的请求头和两个payload(trans_type和source)
- 接下来需要在go里发送请求,有一个简单的请求生成方法:右键dict->copy->copy as cURL(选bash)(->找个终端paste->正常访问一大串json)
- 打开:curlconverter.com/go/ (Only bash commands are supported),paste代码即转化为go语言
- 复制粘贴上述go代码到程序中,如果有几条复杂的代码编译错误,直接删掉即可
按照上述操作得到的代码中,我们要发给第三方网站的json字符串(翻译请求)是固定的,不能由用户通过输入来自定义,这部分可以做一个改进:利用json.Marshal()将用户输入的要翻译的单词转化为json字符串,以此来取代自动生成代码的对应部分。
为了将网站返回的json数据转化为方便输出的数据,python/js等脚本语言可以直接从body(是一个map)里面取值,但golang是强类型语言,这种做法不是最佳实践(也可以做),常用方法是准备一个对应的结构体,把信息反序列化到这个结构体中。由于这个结构体一般很复杂,所以可以考虑自动生成。
DictResponse结构体的生成方式如下:
- 打开oktools.net/json2go
- 把翻译网站dict请求里的preview部分的json字符串(选择最顶层的对象,copy object/value)粘贴进去,点击“转换-嵌套”生成对应结构体(生成的代码更紧凑,因为不需要对结果做过多操作)
- 将生成的代码复制粘贴到程序中
在这个程序中,在对返回的json字符串进行反序列化之前,不仅检查了err是否为nil,而且还增加了一步检查StatusCode是否为200(正常),如果不是200,则把code和对应的body(问题)打印出来(由于resp.body返回的是json字符串,所以在呈现错误信息时要转化成string)。这一步属于防御式编程,排除了StatusCode埋下的隐患,有利于后续排查问题,因为不正常的StatusCode也能返回response。如果不检查StatusCode,遇到错误后面可能会得到空结构体,不利于后面排查问题,具体原因参考官方给出的解释:
If a JSON value is not appropriate for a given target type, or if a JSON number overflows the target type, Unmarshal skips that field and completes the unmarshaling as best it can. If no more serious errors are encountered, Unmarshal returns an UnmarshalTypeError describing the earliest such error. In any case, it's not guaranteed that all the remaining fields following the problematic one will be unmarshaled into the target object.
码字好累啊,在活动的最后也算写了一篇没前几篇这么水的笔记了。