这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
这篇笔记主要记录我在青训营第一堂课所作实验以及遇到的问题和思考,实验包括:
- 猜谜游戏
- 在线词典
- SOCKS5代理
这三个项目的源代码都可以从 github.com/wangkechun/… 找到,主要要学习的点是每个项目处理的逻辑以及环境的配置问题使得代码能够运行。
其中前两个使用在Windows上直接运行,第三个实验由于windows使用nc和curl命令比较复杂,改用linux环境(如何配置后文细说)。
1. 前期准备
1.1. 安装Go
前往Go官网 go.dev/ 安装Go,安装过程没有复杂的地方,和平时安装其他软件一样,略。安装完成后打开cmd控制台输入go version,如果能显示版本号则安装成功。
1.2. 安装代码编辑器 Vscode
其实也可以用其他的,不过我习惯使用Vscode,安装后需要下载Go拓展插件。这篇文章希望对你有帮助 juejin.cn/post/712189…
2. 项目实战
2.1. 猜谜游戏
第一个例子里面,我们会使用Golang来构建一个猜数字游戏。在这个游戏里面,程序首先会生成一个介于1到100之间的随机整数,然后提示玩家进行猜测。玩家每次输入一个数字,程序会告诉玩家这个猜测的值是高于还是低于那个秘密的随机数,并且让玩家再次猜测。如果猜对了,就告诉玩家胜利并且退出程序。
生成随机数
maxNum := 100 // 设置随机数的最大范围
rand.Seed(time.Now().UnixNano()) // 以程序运行的时间戳初始化随机种子
secretNumber := rand.Intn(maxNum) // 获得随机数
读取命令行中的一行
把stdin文件转换成一个reader变量,reader变量有很多操作一个流的方法。接下来的很多操作都会返回一个err变量,表示操作是否成功,如果该变量不为空表示运行出错。
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n') // 读取一行输入
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
return
}
字符串转数字
reader.ReadString('\n')方法会包含结尾的换行符,我们需要去掉换行符并将输入转化成数字。
input = strings.TrimSuffix(input, "\r\n") // 去掉换行符
guess, err := strconv.Atoi(input) // 转换成数字
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
return
}
2.2. 在线词典
用户可以在命令行里面查询一个单词。我们能通过调用第三方的API查询到单词的翻译并打印出来。 这个例子里面,我们会学习如何用go语言来来发送HTTP请求、解析json过来,还会学习如何使用代码生成来提高开发效率。
以彩云科技提供的在线翻译为例,先打开彩云翻译的网页,然后右键检查打开浏览器的开发者工具。
输入一个想要查询的单词,比如说 good,点击翻译按钮,浏览器会发送一系列请求,我们在刚刚的开发者工具能很轻松地找到那个用来查询单词的请求。这是一个HTTP的post的请求,请求的header的相当的复杂,有十来个。
然后请求参数信息payload是一个json里面有两个字段,一个是代表你要你是从什么语言转化成什么语言,source就是你要查询的单词。
API的返回结果里面会有Wiki和dictionary 两个字段。
我们需要用的结果主要是dictionary.Explanations 字段里面。其他有些字段里面还包括音标等信息。
我们需要在Golang里面去发送这个请求。因为这个请求比较复杂,用代码构造很麻烦,实际上我们有一种非常简单的方式来生成代码,我们可以右键浏览器里面的copy as curl。copy完成之后可以在终端粘贴一下curl命令,应该可以成功返回一大串json。注意:这里要选择copy as cURL(bash)。
接下来我们打开一个自动代码生成网站 curlconverter.com/go/ ,该网站可以根据cURL自动生成指定语言的请求。打开网站粘贴curl请求,在语言选项选Go就能够看到一串很长的代码,我们直接把它copy到我们的编辑器里面。有几个header比较复杂,生成代码有转义导致的编译错误,删掉这几行即可(PS. 这个问题我没遇到)。
上述代码重点
创建Http客户端
client := &http.Client{}
接下来是构造一个HTTP请求,这是一个post请求,会用到http.NewRequest,第一个参数是http 方法POST,第二个参数是URL,最后一个参数是body,body因为可能很大,为了支持流式发送,是一个只读流。我们用了strings.NewReader来把字符串转换成一个流。这样我们就成功构造了一个HTTP request。
var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
我们需要对这个HTTP request 来设置一堆header。
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")
...
接下来Http客户端调用client.Do(req)就能得到 response
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() // 关闭流
接下来我们是用ioutil.ReadAll来读取这个流,能得到整个body。
bodyText, err := ioutil.ReadAll(resp.Body)
运行代码得到如下结果:
我们已经能够成功地发出请求,把返回的JSON打印出来,其中中文以\u开头的字符串表示。
但是现在输入是固定的,我们是要从一个变量来输入,我们需要用到JSON序列化。在Golang里面,我们需要生成一段JSON,常用的方式是我们先构造出来一个结构体,这个结构体和我们需要生成的JSON的结构是——对应的。
// 结构体
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
// 核心代码
client := &http.Client{} // 创建一个HTTP客户端
// var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request) // 结构体的json序列化,得到字节数组
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf) // 将传入字节流转换为一个变量
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
接下来我们要做的是把这个 response body 来解析出来。在 js/Python 这些脚本语言里面,body是一个字典或者map的结构,可以直接从里面取值。但是golang是个强类型语言,这种做法并不是最佳实践。 更常用的方式是和request的一样,写一个结构体,把返回的JSON反序列化到结构体里面。但是我们在浏览器里面可以看到这个API返回的结构非常复杂,如果要——定义结构体字段,非常繁琐并且容易出错。
此时有一个小技巧是,网上有对应的代码生成工具,我们可以打开这个网站 oktools.net/json2go ,把json字符串粘贴进去,这样我们就能够生成对应结构体。 在某些时刻,我们如果不需要对这个返回结果,做很多精细的操作,我们可以选择转换嵌套,能让生成的代码更加紧凑。这样我们就得到了一个response结构体。
修改结构体名,这里我改为 DictResponse,然后我们用json.Unmarshal把body反序列化到这个结构体里面,再试图打印出来。
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
var dictResponse DictResponse // 定义结构体变量
err = json.Unmarshal(bodyText, &dictResponse)
fmt.Printf("%#v\n", dictResponse)
运行结果如下,这时可以看到所有响应的内容包括中文都显示出来。
观察输出的 json 可以看出我们需要的结果是在Dictionary.explanations。 我们用for range循环来迭代它,然后直接打印结构,参照一些词典的显示方式,我们可以在那个前面打印出这个单词和它的音标。这里有英式音标和美式音标。
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
运行结果:
现在我们可以通过下列语句读取想要查询的单词
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n') // 读取一行输入
老师在这里介绍了另一种方法,即通过命令行将想要查询的单词作为参数传递给程序。首先判断一下命令和参数的个数,如果它不是两个,那么我们就打印出错误信息,退出程序。否则就获取到用户输入的单词,然后继续执行程序。
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD example: simpLeDict hello`)
os.Exit(1)
}
word := os.Args[1]
实现后的运行结果:
2.3. SOCKS5 代理
前文提到 由于windows使用nc和curl命令比较复杂,改用linux环境。接下来另写一篇文章讲解一下如何在笔记本搭建Linux虚拟机并通过Windows的Vscode连接到Linux环境,实现在Windows操作界面操作Linux系统。文章链接为:Go语言基础2 | 青训营笔记 - 掘金 (juejin.cn)