项目要求
在控制台获取用户输入的单词,随后在控制台打印出单词、音标以及释义
需求分析
通过对项目要求的分析,可以把项目大致分为 4 个部分
- 从控制台获取用户输入
- 封装一个http请求用于查询单词,并返回请求响应
- 解析收到的响应,封装成对象
- 控制查询,输出以及退出程序的逻辑
项目实现及分析
通过之前的需求分析,可以把项目拆分成多个函数来实现,拆分成多个函数的目的是为了让函数职责分明,便于阅读和维护
我使用的结构体
我把结构体放在了另一个包中,如果想利用我的代码复现效果的小伙伴可以在main包里面直接定义需要的结构体,我把代码放在下面
Go
// 请求参数结构体
type DictRequest struct {
// 翻译类型
TransType string `json:"trans_type"`
// 待翻译的单词
Source string `json:"source"`
}
//响应数据结构体
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
} `json:"wiki"`
Dictionary struct {
Prons struct {
EnUs string `json:"en-us"`
En string `json:"en"`
} `json:"prons"`
Explanations []string `json:"explanations"`
Synonym []string `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"`
}
main函数
Go
func main() {
// 基于标准输入流创建一个缓冲读取器
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("请输入要查询的单词,输入#退出程序:")
// 从控制台获取输入
input, err := reader.ReadString('\n')
// 错误处理
if err != nil {
log.Fatal(err)
}
// 去除多余的\r\n
input = strings.TrimSuffix(input, "\r\n")
// 判断是否要退出程序
if input == "#" {
fmt.Println("期待您的下次使用,再见!")
break
}
// 调用封装好的translate方法
result := translate(input)
dictionary := &result.Dictionary
fmt.Println()
// 打印单词以及音标
fmt.Println(dictionary.Entry, dictionary.Prons.En)
// 循环打印单词的释义
for _, explanation := range dictionary.Explanations {
fmt.Println(explanation)
}
fmt.Println()
}
}
main
函数里面主要负责整体的流程控制以及实现需求中的1 ,2部分,注释我也写的比较详细,这里做一些注释之外的解析,包括对用到的一些标准库进行解释
bufio
是Go语言的一个标准库,它实现了缓冲 I/O 功能。它封装了 io.Reader
和 io.Writer
接口,提供了一些有用的方法进行缓冲读写。
reader := bufio.NewReader(os.Stdin)
就是使用bufio
并且基于os.Stdin
来创建了一个*bufio.Reader
类型的变量reder
,os.Stdin
是操作系统的标准输入流,通常指向命令行终端的输入,这也符合我们的要求,我们就是要从命令行终端去读取用户输入的数据
这里我们通过input, err := reader.ReadString('\n')
方法来获取输入,意思就是从标准控输入读取字符,只到遇到特定的字符'\n'
,需要注意的是,这个方法会把'\n'也读取进去 ,也就是说这里的input
变量包含'\n'
,所以在在后面我们需要调用strings
库的方法来处理input
变量。我在代码里是去除了\r\n
的后缀,是因为在实际测试中我发现我读取进来的'\n'
之前还有一个字符'\r'
这里调用了一个自定义的方法translate
,他的函数签名是这样的func translate(word string) domain.DictResponse
把要查询的单词作为入参,返回一个我定义好的结构体,包含了单词相关的查询信息,如何快速生成代码已经在课程里面有了介绍,这里不做过多讲解,接下来解析一下这个自定义的translate
方法
translate函数
Go
// 翻译单词,返回自定义响应对象
func translate(word string) domain.DictResponse {
// 创建自定义的请求对象
dictRequest := domain.DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(dictRequest)
if err != nil {
log.Fatal(err)
}
// 从字节数组创建 *bytes.Reader
data := bytes.NewReader(buf)
// 调用自定义的请求方法,获取response
resp, err := sendHttp(data)
// 异常处理
if err != nil {
log.Fatal(err)
}
// 读取response中的数据
bodyText, err := io.ReadAll(resp.Body)
// 延迟关闭流
defer resp.Body.Close()
// 异常处理
if err != nil {
log.Fatal(err)
}
// 反序列化成对象
dictResponse := domain.DictResponse{}
err = json.Unmarshal(bodyText, &dictResponse)
// 异常处理
if err != nil {
log.Fatal(err)
}
return dictResponse
}
translate
函数负责翻译单词,接收一个单词作为入参,然后返回一个对象,对象包含了单词的解析,是不是很好理解。这里的domain.DictRequest
结构体也是根据老师教的代码生成的方法构造出来的,和老师讲的不同的是,我把这个结构体单独放在了一个包里面,这样是为了让main
包里面的代码更简洁
翻译单词肯定涉及到http
请求,因为我们需要利用别人的接口来查询单词,然后解析别人的响应,所以我们在net/http
库的基础上,有针对性的封装了一个sendHttp
方法,方法签名是这样的func sendHttp(body io.Reader) (*http.Response, error)
,传入请求的body
,然后把*http.Response
返回给调用者。现在translate
方法调用sendHttp
方法并拿到了网络请求的响应指针,再通过io
和json
标准库把网络响应的流解析成结构体对象domain.DictResponse
,并把解析好的结构体对象返回给translate
的调用者。还记得我们在main
方法里面调用了translate
方法吧,那里就是拿到了translate
方法返回的结构体对象,然后再输出到命令行终端。sendHttp
方法大部分都是由老师教的自动生成的代码改造而来,接下来解析一下httpSend
的代码
sendHttp方法
Go
// 发送封装好的http请求,并返回Response
func sendHttp(body io.Reader) (*http.Response, error) {
// 新建客户端
client := &http.Client{}
// 新建请求对象
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", body)
// 错误处理
if err != nil {
log.Fatal(err)
}
// 设置一堆参数
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8")
req.Header.Set("app-name", "xy")
req.Header.Set("cache-control", "no-cache")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "34fb7922cfdb90bab1c2676d847f1ece")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("pragma", "no-cache")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
req.Header.Set("sec-fetch-dest", "empty")
req.Header.Set("sec-fetch-mode", "cors")
req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
// 发起http请求
resp, err := client.Do(req)
return resp, err
}
这里面涉及到几个对象,client
是类型为*http.Client
的客户端对象,http请求就是由这个对象发出,所以才有下面的resp, err := client.Do(req)
。那么req
对象很明显就是网络请求了,req
对象的类型是*http.Request
。这里创建请求的时候设置了请求的方式为POST
以及指定了请求的地址为https://api.interpreter.caiyunai.com/v1/dict
,请求的参数为body
这个入参对象。大家下面看到的一堆req.Header.Set
就是在设置请求头的内容,设置完成后就可以发送请求了。
总结
这个项目涉及到很多知识点,包括io
操作,使用json
实现结构体对象的序列化与反序列化,net/http
实现网络请求,使用strings
或者bytes
来实现自身和流之间的转换。
这个项目里面的代码虽然是很初级的,但是对于像我这样才开始学习Go语言的同学来说也是受益匪浅。Go语言上手很简单,但是简单的前提是我们要在实践中去使用它,代码从来都不是背出来的,而是在使用的过程中,我们加深了对其的理解,然后自然而然的就能使用代码来完成自己的需求。这个项目做下来,我对于Go语言的基础语法以及一些常见的标准库又有了更深的理解,希望我们都能在学习的道路上越走越远。