这篇文章是青训营后端第二节课,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返回值的请求,如图所示
把curl命令转成Go
右键刚刚那个带有translation返回值的请求,复制为curl
然后打开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。下面我贴一下这个函数的完整代码:
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)
}
可以试试多运行几次,它可能调用不同的翻译引擎。