Go基础语法之实战 | 青训营笔记
系列介绍
哈哈哈,其实这个系列如题,就是黄同学也参加了这一届青训营😚。作为一名知识内容输出爱好者,我是非常喜欢这个活动的。在接下来的日子里我会持续更新这个系列,希望可以通过这个过程,将我在这次后端青训营的学习过程,尤其是针对课程中知识内容的课下实践,自己的心得体会,知识总结输出到掘金社区。💖
哈哈哈,其实也希望通过这个来对我这种懒狗的一种鞭策。🏅
目前该系列已经发布了:
感兴趣的可以看看🌹
本文摘要
- 解析官方的两个实战案例(代理的后面写,这篇先写前两个),快速的熟悉一些go开发运用的一些基础语法。
- 分析实践过程中的经验和使用技巧(工具)✨。
1. 猜数字游戏
1.1 案例介绍
-
这个案例可以说很经典了,可能我们在其他语言的学习中也会做过类似的案例。
-
还是简单介绍一下这个案例:
- 用户需要猜测从 0 到 99 的一个整数数字,用户可以输入多次。
- 用户如果猜对了,则会返回正确,然后退出游戏。
- 如果猜错了,则会根据用户输入数字和答案的大小关系,告知用户,让用户继续输入。
1.2 思路
以下是黄同学根据官方的讲解与代码,整理出来。
- 因为是猜,所以,不应该是每次的答案都一样,我们可以用随机生成,而且随机种子可以用时间。这样可以使得每次生成的答案都不一样。
- 由于可能会多次判断(即第一次不一定就猜中),所以需要一个循环结构。
- 需要对输入进行读取。
1.3 用到的api
- 👉time.Now().UnixNano() 👈:获取当前时间的
Unix时间戳,单位是纳秒。 - 👉rand.seed() 👈:设置随机种子。
- 👉rand.Intn(n) 👈:生成随机整数,范围是 0 到 n-1。
- 👉 bufio.NewReader(os.Stdin) 👈: 创建一个从标准输入中的缓冲流读取器,其中
os.Stdin就表示标准输入。 - 👉 ReadString(delim byte) 👈:这是上面接口返回的读取器的一个方法,用来从读取器中读取内容,读取到
delim表示的字节为止,我们可以用换行符\n作为每次循环的读取的终止。 - 👉 strings.Trim(input, "\r\n") 👈:去除字符串的末尾的指定字符(串),这一行就表示,如果读取到了
\r\n在末尾的位置,就移除这些,避免在做字符串转数字的时候产生影响。
1.3 Go-Code
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano()) /*设置随机种子,*/
secretNumber := rand.Intn(maxNum) /*获取随机数作为答案*/
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin) /*创建从标准输入读取的缓冲读取器*/
for true {
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) /*转换为数字*/
// 出错则说明输入了非法数字,比如非数字的字符
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
fmt.Println("You guess is", guess)
// 执行条件判断分支
if guess > secretNumber {
fmt.Println("Your guess is bigger than secret number. Please try again")
} else if guess < secretNumber {
fmt.Println("Your guess is smaller than secret number. Please try again")
} else {
fmt.Println("Correct, you Legend")
break
}
}
}
2. 在线词典
2.1 案例介绍
- 这个其实就是一个做一个 命令行 词典翻译,利用的是彩云翻译的api,这里主要是通过http请求来调取对应的api。
- 在这个案例的过程中,涉及到对彩云翻译网页的使用浏览器自带的开发工具(
F12)获取请求以及返回,转化为Go代码转换(详见下面的 小工具)。
2.2 浏览器开发工具
-
我们在浏览器可以通过
F12键进入 开发者模式,通过这个模式,我们不但可以看到当前网页的前端代码,还可以捕获一些http包。这一点,黄同学觉得比使用专门的抓包软件如Wireshark要方便一些。 -
我们可以在网络 选项中,通过点击网页中的指定区域,可以看到浏览器发送的一些请求报文,并进行解析。
-
操作过程可如下图(以edge为例):
- 先按 F12 进入开发者模式。
- 然后选择网络
- 点击选择具体的包,比如我在图中选择的
dict,我们可以看 预览 还有 响应,判断是否要选择。(有响应,且里边包含翻译的结果就是)。
2.3 四个版本的迭代
- 官方一共是给出了v1 到 v4 四个版本的实现,以下是黄同学对于这四个版本的迭代的看法。
v1:这个比较简单,而且主要是使用了本文4.1中的工具,将curl指令转为Go代码。v2:在v1的基础上,编写了特定的请求结构体,使用json 序列化与反序列化 数据。v3:针对返回报文的json格式,用工具(本文4.2),转化为go 结构体代码,然后基本原理同v2,也进行序列化。v4:将请求发送与对响应内容的提取,这个过程封装后在进行调用。
2.4 Go-Code
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
KnownInLaguages int `json:"known_in_laguages"`
Description struct {
Source string `json:"source"`
Target interface{} `json:"target"`
} `json:"description"`
ID string `json:"id"`
Item struct {
Source string `json:"source"`
Target string `json:"target"`
} `json:"item"`
ImageURL string `json:"image_url"`
IsSubject string `json:"is_subject"`
Sitelink string `json:"sitelink"`
} `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 query(word string) {
client := &http.Client{}
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
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)
}
req.Header.Set("Connection", "keep-alive")
req.Header.Set("DNT", "1")
req.Header.Set("os-version", "")
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
req.Header.Set("app-name", "xy")
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("device-id", "")
req.Header.Set("os-type", "web")
req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
req.Header.Set("Sec-Fetch-Site", "cross-site")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
if resp.StatusCode != 200 {
log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
`)
os.Exit(1)
}
word := os.Args[1]
query(word)
}
熟悉的同学可能知道,这个代码都是运行时直接翻译,比较死板
4. 小工具⚙️
这一部分的工具都是来自 2. 在线词典 案例中,主要有:
- curl 转 go 代码
- json 转 go 代码
4.1 curl 转Go
-
Convert curl commands to Go (curlconverter.com) 这是一个很棒的工具,可以根据你输入的curl 命令来生成对应的 go代码。
-
操作流程(以edge 为例):
- 先 F12,选择网络,点击(右键)指定包,选择复制,选择复制为 cURL(bash) ,一定要选择这个
bash,而不是cmd,这对应这两种不同的命令行(linux和windows),而其在转义符的处理会不一样,bash是, `cmd` 是 `^`。而转换工具支持的是作为转义符。如果你只能复制cmd(一般都可以复制bash),你可以用像vscode等文本编辑工具将转义符进行替换。 - 将内容粘贴到工具中即可,稍微等待即可得到代码,如果curl指令中有问题,界面会返回告知哪里有问题。
- 先 F12,选择网络,点击(右键)指定包,选择复制,选择复制为 cURL(bash) ,一定要选择这个
4.2 JSON转Golang Struct
-
JSON转Golang Struct - 在线工具 - OKTools 这是在课程中学到的一个很棒的小工具,我们执行代码后,会收到来自服务器的一堆
json数组,如果我们要写专门的structgo代码显然是一件痛苦的事情,而像前面将curl 指令转化为go 代码,针对这种json 数组,也有对应的工具将其生成go 的结构体代码。 -
小bug⚠️:我们将运行台的结果复制,如下图:
粘贴到工具网站发现,会立即跳出一个提示框,如下
翻译过来就是第1184个字符不符合规范的意思,那我们可以找找第1184个字符是啥(你可以数,但黄同学不建议这么做),这里我黄同学用到的是
vscode(goland也可以,但是黄同学更熟悉vs),如下:按
Ctrl+G进入,然后输入:1:1184表示转到第1行第1184个字符,然后发现光标悬停到如下的字符\u5b9e中,但是要考虑到一开始的{可能是工具是没有计入的,这里悬停到字符是一个中文字符,而且一般来说,一个json数组为什么要换行呢,所以黄同学问题出在了换行符上。在尝试删除换行符后,再复制粘贴到工具网站后,就可以了:
一些API💎❇️
👉 json.MarshalIndent 👈
-
先看原型
func MarshalIndent(v any, prefix, indent string) ([]byte, error)参数第一个就是传入的值,在字节课程中的例子中可以是json 结构体的对象值,
prefix是前缀,indent是缩进的意思,从返回值来看,如果正常返回是一个字节数组(切片)。 -
再看官方文档中的描述: “MarshalIndent is like Marshal but applies Indent to format the output. Each JSON element in the output will begin on a new line beginning with prefix followed by one or more copies of indent according to the indentation nesting.”,意思就是和
Marshal类似,只不过用缩进indent来格式化输出,每个JSON元素将会从一个新行开始,且会实现缩进嵌套。 -
示例代码(偷个懒,直接用官方的了)
package main import ( "encoding/json" "fmt" ) type userInfo struct { Name string Age int Hobby []string } func main() { a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}} buf, err := json.Marshal(a) if err != nil { panic(err) } fmt.Println(buf) fmt.Println(string(buf)) buf, err = json.MarshalIndent(a, "", "\t") if err != nil { panic(err) } fmt.Println(string(buf)) var b userInfo err = json.Unmarshal(buf, &b) if err != nil { panic(err) } fmt.Printf("%v\n", b) } -
运行结果
参考资料
黄同学在编写这篇文章,除了自己的实践外,还参考了不少资料。如果朋友想要通过我的这篇简陋笔记文章去探索那些可以称为宝玉或者 💎 般的知识,不妨通过下面的链接看看: