GO语言工程实践课后作业:实现思路、代码以及路径记录

43 阅读13分钟

这篇文章是青训营后端第二节课,Go语言工程实践得课后作业,一共有三个题目,第一个是用scanf对课上的猜数字游戏代码进行优化

猜数字游戏优化

先看看原始代码

	reader := bufio.NewReader(os.Stdin)
	for {
		input, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("An error occured while reading input. Please try again", err)
			continue
		}
		input = strings.Trim(input, "\r\n")

		guess, err := strconv.Atoi(input)
	}
}

其实要解决的就是输入数字的过程比较啰嗦的问题,可以看到,我们先创建了一个reader流,然后用ReadString方法读取标准输入,但这样只能读入字符串,即使我们输入的是数字,input这里实际上也是字符串,并且是带换行符的,windows里面换行符是\r\n,所以后面使用string.Trim函数,\r\n作为分割符把输入分割开了,因此input就变成了数字字符串,再使用Atoi转成字符串作为我们输入的数字guess。 但是我们可以用fmt.Scanf函数来简化这个过程,代码如下

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	maxNum := 100
	secretNum := rand.Intn(maxNum)
	fmt.Println("The secretNum is", secretNum)
	//reader := bufio.NewReader(os.Stdin)
	var guess int
	for {
		fmt.Println("请输入你猜的数字")
		// input, err := reader.ReadString('\n')
		// if err != nil {
		// 	fmt.Println("valid input")
		// 	break
		// }
		// input = strings.Trim(input, "\r\n")
		// fmt.Println("Your input num is ", input)
		// guess, err := strconv.Atoi(input)

		fmt.Scanf("%d", &guess)
		if guess > secretNum {
			fmt.Println("Too large")
		} else if guess < secretNum {
			fmt.Println("Too small")
		} else {
			fmt.Println("bingo")
			break
		}
		fmt.Scanf("%d", &guess)
	}
}

我把原始方法注释掉了,可以对比一下,使用scanf只需要两行代码,更加简洁,这个fmt.Scanf的用法可以说和c语言里面非常类似,有个需要注意的就是go语言里面这个Scanf需要自己手动处理换行符,所以我在循环最后加了一句Scanf,实际上这行代码就是把换行符从缓冲区读出来,以防它影响我下次输入数字。

那其实Scanf还需要处理换行符,使用Scanln更简洁,fmt.Scanln(&guess)这一行代码就能解决问题了,只是Scanln没有Scanf那么强的格式化输入的能力,Scanf能格式化输入,常用格式:

  • %d:用于读取整数。

  • %f:用于读取浮点数。

  • %s:用于读取字符串(不包含空格)。

  • %c:用于读取单个字符。

而Scanln适合一次输入一行,它每次只能接受一种格式。

命令行词典增加翻译引擎

第二个作业是在原来那个词典代码基础上增加一个翻译引擎,我这里选择了火山翻译,但其实我也没太搞懂这个实现过程,因为还存在一些问题,先看看代码把,步骤和实现彩云翻译的步骤都是一样的。

抓包

就是这个抓包这里我没怎么搞懂,彩云翻译那个网页抓包很轻松就找到了想要的东西,但是火山翻译这里我找了很久,没找到那种返回有explanations的请求,因此最后我选择了那个带有translation返回值的请求,如图所示

image.png

把curl命令转成Go

右键刚刚那个带有translation返回值的请求,复制为curl

image.png 然后打开Convert curl commands to Go (curlconverter.com),把curl命令粘贴上去,下面就会自动生成对应的Go代码:

  client := &http.Client{}
	type DictRequest2 struct {
		Source_language      string   `json:"source_language"`
		Target_language      string   `json:"target_language"`
		Text                 string   `json:"text"`
		Home_language        string   `json:"home_language"`
		Category             string   `json:"category"`
		Glossary_list        []string `json:"glossary_list"`
		Enable_user_glossary bool     `json:"enable_user_glossary"`
	}
	//var data = strings.NewReader(`{"source_language":"detect","target_language":"zh","text":"hello","home_language":"zh","category":"","glossary_list":[],"enable_user_glossary":false}`)

	request := DictRequest2{Source_language: "detect", Target_language: "zh", Text: word, Home_language: "zh", Category: "", Glossary_list: []string{}, Enable_user_glossary: false}
	buf, err := json.Marshal(request) //返回的是json
	if err != nil {
		log.Fatal(err)
	}
	data := bytes.NewReader(buf) //把json转成流
	req, err := http.NewRequest("POST", "https://translate.volcengine.com/web/translate/v1/?msToken=&X-Bogus=DFSzswVLQDGOQOq/tsi-YAD4OF1S&_signature=_02B4Z6wo00001CbEbqgAAIDA0K1njaYQhewmxGoAAG6kietndZ0sfFg5OETS99vuV9E6Ph64AVRPPMW8.lgyvf4WMQKfVfPjsJncr89uum.6jHW3ej3FzhxJDp8ZRmV8j.ay8aYPkkgjacLbba", data)
  ...
  ...

我这里只贴了一部分代码,因为这里需要作出一些修改,直接复制过来的代码data直接是用手动创建的json定义的,就是var data那一行,但这样扩展性比较差,主要看"text":"hello",我们要把这个"hello"变成一个变量,这样更加方便一些,命令行想查询哪个单词就能翻译哪个单词,所以就需要自己创建一个结构体DictRequest2,里面的成员就是data那一行的json里面的,注意大小写,然后需要把这个结构体序列化为json,就是第14行代码,最后在把这个json转为流,把字符串转为流,可以防止body过大,用流可以占用很小内存,也就是18行代码,剩下的就和直接复制过来的一样了。

反序列化json

最终结果就是创建了一个resp.body,但是这个是json格式的,如果我们想拿到里面每一项的内容,最好再把json反序列化成结构体,再从这个结构体里面去取出想要的东西,这样更加清晰明了。代码如下:

type DictResponse2 struct {
		Translation      string `json:"translation"`
		DetectedLanguage string `json:"detected_language"`
		Probability      int    `json:"probability"`
		BaseResp         struct {
			StatusCode    int    `json:"status_code"`
			StatusMessage string `json:"status_message"`
		} `json:"base_resp"`
	}

	var dictResponse2 DictResponse2
	//把response反序列化,方便输出
	err = json.Unmarshal(bodyText, &dictResponse2) //注意加取地址符号,这样才能把数据写入结构体
	if err != nil {
		log.Fatal(err) //退出请求
	}

	fmt.Printf("%#v\n", dictResponse2) //详细输出
	fmt.Printf("%v\n", dictResponse2.Translation)

思路就是创建一个json对应的结构体,保证成员变量相同,且大写,这一步可以在JSON转Golang Struct - 在线工具 - DreamTools (ysboke.cn)轻松获得结构体,也就是DictResponse2, 使用unmarshal函数把json反序列化到这个结构体,最后从结构体拿到我们想要的东西,也就是translation。下面我贴一下这个函数的完整代码:

image.png

func query2(word string) {
	client := &http.Client{}
	type DictRequest2 struct {
		Source_language      string   `json:"source_language"`
		Target_language      string   `json:"target_language"`
		Text                 string   `json:"text"`
		Home_language        string   `json:"home_language"`
		Category             string   `json:"category"`
		Glossary_list        []string `json:"glossary_list"`
		Enable_user_glossary bool     `json:"enable_user_glossary"`
	}
	//var data = strings.NewReader(`{"source_language":"detect","target_language":"zh","text":word,"home_language":"zh","category":"","glossary_list":[],"enable_user_glossary":false}`)

	request := DictRequest2{Source_language: "detect", Target_language: "zh", Text: word, Home_language: "zh", Category: "", Glossary_list: []string{}, Enable_user_glossary: false}
	buf, err := json.Marshal(request) //返回的是json
	if err != nil {
		log.Fatal(err)
	}
	data := bytes.NewReader(buf) //把json转成流
	req, err := http.NewRequest("POST", "https://translate.volcengine.com/web/translate/v1/?msToken=&X-Bogus=DFSzswVLQDGOQOq/tsi-YAD4OF1S&_signature=_02B4Z6wo00001CbEbqgAAIDA0K1njaYQhewmxGoAAG6kietndZ0sfFg5OETS99vuV9E6Ph64AVRPPMW8.lgyvf4WMQKfVfPjsJncr89uum.6jHW3ej3FzhxJDp8ZRmV8j.ay8aYPkkgjacLbba", data)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Accept", "application/json, text/plain, */*")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Cookie", "csrfToken=8b13c7ddf17a645320fff792b453c01c; csrfToken=8b13c7ddf17a645320fff792b453c01c; ve_doc_history=4640; VOLCFE_im_uuid=1730965668240794482; isIntranet=0; monitor_huoshan_web_id=7434440843232249398; monitor_session_id_flag=1; hasUserBehavior=1; __tea_cache_tokens_3569={%22web_id%22:%227434440843232249398%22%2C%22user_unique_id%22:%227434440843232249398%22%2C%22timestamp%22:1730965677815%2C%22_type_%22:%22default%22}; referrer_title=API%E6%8E%A5%E5%85%A5%E6%B5%81%E7%A8%8B%E6%A6%82%E8%A7%88--%E6%9C%BA%E5%99%A8%E7%BF%BB%E8%AF%91-%E7%81%AB%E5%B1%B1%E5%BC%95%E6%93%8E; i18next=zh-CN; s_v_web_id=verify_m37374h8_5qLejC3f_m5QI_47Lu_ASlG_SiMBFGvjyRW2; ttcid=57f8de93bbc54980a9e41f625c8b041852; tt_scid=8TWZF121CydrvDSpm4msQdXMhU7TYZAkdOJuRaTYjOdtj9A1gCCQog78GwwpXEyN9ada")
	req.Header.Set("Origin", "https://translate.volcengine.com")
	req.Header.Set("Referer", "https://translate.volcengine.com/?category=&home_language=zh&source_language=detect&target_language=zh&text=hello")
	req.Header.Set("Sec-Fetch-Dest", "empty")
	req.Header.Set("Sec-Fetch-Mode", "cors")
	req.Header.Set("Sec-Fetch-Site", "same-origin")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0")
	req.Header.Set("sec-ch-ua", `"Chromium";v="122", "Not(A:Brand";v="24", "Microsoft Edge";v="122"`)
	req.Header.Set("sec-ch-ua-mobile", "?0")
	req.Header.Set("sec-ch-ua-platform", `"Windows"`)
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	fmt.Println(resp.Body)
	bodyText, err := io.ReadAll(resp.Body)
	fmt.Printf("Response body: %s\n", bodyText)

	if resp.StatusCode != 200 {
		log.Fatal("bad statusCode : ", resp.StatusCode, "body:", string(bodyText))
	}
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Status Code: %d\n", resp.StatusCode)
	fmt.Printf("%s\n", bodyText)
	type DictResponse2 struct {
		Translation      string `json:"translation"`
		DetectedLanguage string `json:"detected_language"`
		Probability      int    `json:"probability"`
		BaseResp         struct {
			StatusCode    int    `json:"status_code"`
			StatusMessage string `json:"status_message"`
		} `json:"base_resp"`
	}

	var dictResponse2 DictResponse2
	//把response反序列化,方便输出
	err = json.Unmarshal(bodyText, &dictResponse2) //注意加取地址符号,这样才能把数据写入结构体
	if err != nil {
		log.Fatal(err) //退出请求
	}

	fmt.Printf("%#v\n", dictResponse2) //详细输出
	fmt.Printf("%v\n", dictResponse2.Translation)

}

query2就是使用火山翻译的函数,但其实是存在问题的目前,只能翻译一个单词,我在网上搜了一下,原因可能出在第20行,msToken=&X-Bogus这个可能是动态参数,每翻译一个单词这个就会变,因此假如我现在要翻译good单词,那我还得重新去抓包,然后通过curl生成resp.body。而彩云翻译是没有这些加密的,因此不用这么麻烦。

并发执行两个翻译引擎

这是第三个作业,目的就是让这两个翻译引擎同时执行,这样给用户的响应速度快一些,使用cancel就能实现,这个在老师将socks5代理的时候用过,代码如下,主要在main函数调用翻译引擎的时候修改一下

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go func() {
		query(word)
		cancel()
	}()
	go func() {
		query2(word)
		cancel()
	}()
	<-ctx.Done()

要知道go routine几乎不消耗时间,因此这两个go routine会并发执行,一个调用彩云翻译query,一个调用query2,如果不加限制,main函数就直接退出了,因为go routine几乎不消耗时间,你可以理解为main函数里面没有这段代码,所以我们要加限制,必须要让一个go routine执行完之后在结束main函数,这里就使用了cancel,两个query函数,谁先执行完,谁先调用cancel(),这个会触发下面ctx.Done,然后退出函数,因此这两个翻译引擎谁响应快就谁先执行完,另一个还没结束的会提前退出。

完整代码

package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

type DictRequest struct {
	Trans_type string `json:"trans_type"`
	Source     string `json:"source"`
	UserId     string `json:"userid"`
}

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      []string      `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"`
}

func main() {
	//args是一个切片
	//os.Args[0] 是程序本身的名字,os.Args[1] 是传递的第一个参数(即用户输入的单词)
	if len(os.Args) != 2 {
		//该行将用法提示输出到标准错误输出 (os.Stderr)
		fmt.Fprintf(os.Stderr, `usage:simpleDict WORD
		example: simpleDict hello
		`)
		os.Exit(1)
	}
	word := os.Args[1]
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go func() {
		query(word)
		cancel()
	}()
	go func() {
		query2(word)
		cancel()
	}()
	<-ctx.Done()


}

func query(word string) {
	client := &http.Client{} //创建http client,可以指定timeout参数
	//先把data转为流类型,把字符串转为流,防止body过大,用流可以占用很小内存
	//var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)   //参数是手动定义的json,这种不好,因为我们通常传的是变量

	//把变量序列化成json,结构体和json的转换
	request := DictRequest{Trans_type: "en2zh", Source: word}
	buf, err := json.Marshal(request) //返回的是json
	if err != nil {
		log.Fatal(err)
	}
	data := bytes.NewReader(buf) //把json转成流
	//data是一个流类型,不是字符串
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) //创建http请求,method\url\data
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("accept", "application/json, text/plain, */*")
	req.Header.Set("accept-language", "zh")
	req.Header.Set("app-name", "xiaoyi")
	req.Header.Set("authorization", "Bearer")
	req.Header.Set("content-type", "application/json;charset=UTF-8")
	req.Header.Set("device-id", "ca61e24f0360675d2448690ff53a5b32")
	req.Header.Set("origin", "https://fanyi.caiyunapp.com")
	req.Header.Set("os-type", "web")
	req.Header.Set("os-version", "")
	req.Header.Set("priority", "u=1, i")
	req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
	req.Header.Set("sec-ch-ua", `"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"`)
	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/130.0.0.0 Safari/537.36")
	req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
	//真正发送请求
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err) //退出请求
	}
	//defer在函数结束之后从下往上触发
	defer resp.Body.Close() //返回的response body也是一个流,为了避免资源泄露,需要我们手动关闭
	//把流变为byte数组,返回response
	bodyText, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	//判断刚刚返回的response是否有问题,防止404或者403造成resp空结构,导致后面输出也是空
	if resp.StatusCode != 200 {
		log.Fatal("bad statusCode : ", resp.StatusCode, "body:", string(bodyText))
	}
	var dictResponse DictResponse
	//把response反序列化,方便输出
	err = json.Unmarshal(bodyText, &dictResponse) //注意加取地址符号,这样才能把数据写入结构体
	if err != nil {
		log.Fatal(err) //退出请求
	}

	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)
	}
}

func query2(word string) {
	client := &http.Client{}
	type DictRequest2 struct {
		Source_language      string   `json:"source_language"`
		Target_language      string   `json:"target_language"`
		Text                 string   `json:"text"`
		Home_language        string   `json:"home_language"`
		Category             string   `json:"category"`
		Glossary_list        []string `json:"glossary_list"`
		Enable_user_glossary bool     `json:"enable_user_glossary"`
	}
	//var data = strings.NewReader(`{"source_language":"detect","target_language":"zh","text":word,"home_language":"zh","category":"","glossary_list":[],"enable_user_glossary":false}`)

	request := DictRequest2{Source_language: "detect", Target_language: "zh", Text: word, Home_language: "zh", Category: "", Glossary_list: []string{}, Enable_user_glossary: false}
	buf, err := json.Marshal(request) //返回的是json
	if err != nil {
		log.Fatal(err)
	}
	data := bytes.NewReader(buf) //把json转成流
	req, err := http.NewRequest("POST", "https://translate.volcengine.com/web/translate/v1/?msToken=&X-Bogus=DFSzswVLQDGOQOq/tsi-YAD4OF1S&_signature=_02B4Z6wo00001CbEbqgAAIDA0K1njaYQhewmxGoAAG6kietndZ0sfFg5OETS99vuV9E6Ph64AVRPPMW8.lgyvf4WMQKfVfPjsJncr89uum.6jHW3ej3FzhxJDp8ZRmV8j.ay8aYPkkgjacLbba", data)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Accept", "application/json, text/plain, */*")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Cookie", "csrfToken=8b13c7ddf17a645320fff792b453c01c; csrfToken=8b13c7ddf17a645320fff792b453c01c; ve_doc_history=4640; VOLCFE_im_uuid=1730965668240794482; isIntranet=0; monitor_huoshan_web_id=7434440843232249398; monitor_session_id_flag=1; hasUserBehavior=1; __tea_cache_tokens_3569={%22web_id%22:%227434440843232249398%22%2C%22user_unique_id%22:%227434440843232249398%22%2C%22timestamp%22:1730965677815%2C%22_type_%22:%22default%22}; referrer_title=API%E6%8E%A5%E5%85%A5%E6%B5%81%E7%A8%8B%E6%A6%82%E8%A7%88--%E6%9C%BA%E5%99%A8%E7%BF%BB%E8%AF%91-%E7%81%AB%E5%B1%B1%E5%BC%95%E6%93%8E; i18next=zh-CN; s_v_web_id=verify_m37374h8_5qLejC3f_m5QI_47Lu_ASlG_SiMBFGvjyRW2; ttcid=57f8de93bbc54980a9e41f625c8b041852; tt_scid=8TWZF121CydrvDSpm4msQdXMhU7TYZAkdOJuRaTYjOdtj9A1gCCQog78GwwpXEyN9ada")
	req.Header.Set("Origin", "https://translate.volcengine.com")
	req.Header.Set("Referer", "https://translate.volcengine.com/?category=&home_language=zh&source_language=detect&target_language=zh&text=hello")
	req.Header.Set("Sec-Fetch-Dest", "empty")
	req.Header.Set("Sec-Fetch-Mode", "cors")
	req.Header.Set("Sec-Fetch-Site", "same-origin")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0")
	req.Header.Set("sec-ch-ua", `"Chromium";v="122", "Not(A:Brand";v="24", "Microsoft Edge";v="122"`)
	req.Header.Set("sec-ch-ua-mobile", "?0")
	req.Header.Set("sec-ch-ua-platform", `"Windows"`)
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	fmt.Println(resp.Body)
	bodyText, err := io.ReadAll(resp.Body)
	fmt.Printf("Response body: %s\n", bodyText)

	if resp.StatusCode != 200 {
		log.Fatal("bad statusCode : ", resp.StatusCode, "body:", string(bodyText))
	}
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Status Code: %d\n", resp.StatusCode)
	fmt.Printf("%s\n", bodyText)
	type DictResponse2 struct {
		Translation      string `json:"translation"`
		DetectedLanguage string `json:"detected_language"`
		Probability      int    `json:"probability"`
		BaseResp         struct {
			StatusCode    int    `json:"status_code"`
			StatusMessage string `json:"status_message"`
		} `json:"base_resp"`
	}

	var dictResponse2 DictResponse2
	//把response反序列化,方便输出
	err = json.Unmarshal(bodyText, &dictResponse2) //注意加取地址符号,这样才能把数据写入结构体
	if err != nil {
		log.Fatal(err) //退出请求
	}

	fmt.Printf("%#v\n", dictResponse2) //详细输出
	fmt.Printf("%v\n", dictResponse2.Translation)

}

可以试试多运行几次,它可能调用不同的翻译引擎。