Go基础-项目实践 | 青训营笔记

115 阅读10分钟

wind-7595553_1280.png

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天

3.1 猜谜游戏

在这个游戏里面,程序首先会生成一个介于1到100之间的随机整数,然后提示玩家进行猜测。玩家每次输入一个数字,程序会告诉玩家这个猜测的值是高于还是低于那个秘密的随机数,并且让玩家再次猜测。如果猜对了,就告诉玩家胜利并且退出程序。

3.1.1 生成随机数

math/rand是 Go 语言中专门用来做与随机数相关操作的包,主要有生成随机数、随机种子等方法

  • rand.Intn(n)生成一个值在 [0, n) 区间的随机整数
  • rand.Seed(seed)以设置随机种子,seed 为 int64 类型

time.Now().UnixNano()是获取当前时间的时间戳(以纳秒为单位)

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    // 规定随机数的最大区间范围
    maxNum := 100
    // 随机生成[0, maxNum)区间中的一个整数
    secretNumber := rand.Intn(maxNum)
    // 打印该随机数字
    fmt.Println("The secret number is ", secretNumber)
    // 但是如果你多执行几次,会发现一直都是81这个数字,why?因为我们没有设置随机数种子
    // 使用math/rand之前需要设置随机数种子,否则的话每一次都会生成相同的随机数序列。
    // 一般惯例用法是在程序启动的时候,用启动的时间戳来初始化随机数种子。
}
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    // 规定随机数的最大区间范围
    maxNum := 100
    // 设置随机数种子,将种子设置为当前时间戳
    rand.Seed(time.Now().UnixNano())
    // 随机生成[0, maxNum)区间中的一个整数
    secretNumber := rand.Intn(maxNum)
    // 打印该随机数字
    fmt.Println("The secret number is ", secretNumber)
    // 多执行几次,就不再会是同一个数字了
}
3.1.2 读取用户输入

读取用户键盘输入

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("The secret number is ", secretNumber)
    // ------读取用户输入部分--------
    // 提示用户输入猜测数字
    fmt.Println("Please input your guess")
    reader := bufio.NewReader(os.Stdin)
    // 进行错误处理
    input, err := reader.ReadString('\n')
    // 如果err不为空,那么就说明执行过程中返回了错误信息,那就直接打印出来
    if err != nil {
        fmt.Println("An error occured while reading input. Please try again", err)
        return
    }
    // 去掉换行符,将字符串首尾的 \r 和 \n 去掉
    input = strings.Trim(input, "\r\n")
    // 将input转化为数字
    guess, err := strconv.Atoi(input)
    // 进行错误处理
    if err != nil {
        // 打印错误信息
        fmt.Println("Invalid input. Please enter an integer value")
        return
    }
    // 输出用户输入数字
    fmt.Println("You guess is", guess)
}
3.1.3 实现判断逻辑

就多加了一个if大小判断

package main

import (
    "bufio"
    "fmt"
    "math/rand"
    "os"
    "strconv"
    "strings"
    "time"
)

func main() {
    // 这里都是3.1.2中的代码
    // 下面是实现判断逻辑的代码
    // 将用户猜测数字与系统生成的随机数进行对比
    if guess > secretNumber {
        // 如果用户猜测较大,提示用户随机数应该较小一些
        fmt.Println("Your guess is bigger than the secret number. Please try again")
    } else if guess < secretNumber {
         // 如果用户猜测较小,提示用户随机数应该较大一些
        fmt.Println("Your guess is smaller than the secret number. Please try again")
    } else {
        // 最后剩下的情况就只有两者相等,这时直接输出结果即可
        fmt.Println("Correct, you Legend!")
    }
}
3.1.4 实现游戏循环

实现猜测的一直连续,直到猜中为止

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("The secret number is ", secretNumber)
    // ------读取用户输入部分-------
    fmt.Println("Please input your guess")
    reader := bufio.NewReader(os.Stdin)
    // 这里for不加任何东西就相当于while循环,且是while(true)死循环
    // 将之前所说的所有操作整合到下述for循环中
    for {
        input, err := reader.ReadString('\n')
        if err != nil {
            fmt.Println("An error occured while reading input. Please try again", err)
            // 将break换成了continue,因为考虑到break会终止for循环的,只需要跳过本次循环就行了
            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 the secret number. Please try again")
        } else if guess < secretNumber {
            fmt.Println("Your guess is smaller than the secret number. Please try again")
        } else {
            fmt.Println("Correct, you Legend!")
            break
        }
    }
}

3.2 在线词典

用户在命令行里面查询一个单词,通过调用第三方的 API 查询到单词的翻译并打印出来,在这个项目中我们将学会如何使用 Go 语言来发送 HTTP 请求、解析 JSON,还会学习如何使用代码生成来提高开发效率。

3.2.1 抓包

以彩云科技提供的在线翻译为例,直接右键检查打开浏览器的开发者工具(这个应该都懂吧)。

image-20230119163401893

这是 HTTP 的一个 post 请求,API 的返回结果里会有 dictionary 和 wiki 两个字段,我们想要的数据就在 dictionary.explanations 字段里,其他字典还包括该单词的其他信息,例如音标。。。

右键 dict,以 cURL 格式进行复制,在命令行粘贴出来,应该可以看到一大串的 json

 curl 'https://api.interpreter.caiyunai.com/v1/dict' \
  -H 'authority: api.interpreter.caiyunai.com' \
  -H 'sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"' \
  -H 'os-version: ' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36' \
  -H 'app-name: xy' \
  -H 'content-type: application/json;charset=UTF-8' \
  -H 'accept: application/json, text/plain, */*' \
  -H 'device-id: ' \
  -H 'os-type: web' \
  -H 'x-authorization: token:qgemv4jr1y38jyq6vhvi' \
  -H 'sec-ch-ua-platform: "Windows"' \
  -H 'origin: https://fanyi.caiyunapp.com' \
  -H 'sec-fetch-site: cross-site' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-dest: empty' \
  -H 'referer: https://fanyi.caiyunapp.com/' \
  -H 'accept-language: zh-CN,zh;q=0.9,zh-TW;q=0.8' \
  --data-raw '{"trans_type":"en2zh","source":"good"}' \
  --compressed
3.2.2 代码生成

这里要用到一个在线代码自动生成的网站curlconverter.com/go/,添加 curl 命令,自动生成相应命令操作的 go 代码,很大地提高了开发效率。

// 将上面的curl代码通过代码生成网站转化为的go代码
package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "strings"
)

func main() {
    // 创建HTTP client,可以指定很多参数,例如请求超时是否使用cookie。。。
    client := &http.Client{}
    // 构造HTTP请求的请求体,自定义相关参数
    // trans_type: 翻译方式,en2zh:英语翻译为中文
    // source: 想要查询的单词
    var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
    // 构造一个HTTP请求,并且的post请求,https://api.interpreter.caiyunai.com/v1/dict为发送请求的url
    req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
    // 错误处理,请求无异常err为nil,如果中途出现问题,err为返回错误信息
    if err != nil {
        // 直接返回错误信息并结束程序
        log.Fatal(err)
    }
    // 设置请求体中的一大堆参数
    req.Header.Set("authority", "api.interpreter.caiyunai.com")
    req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`)
    req.Header.Set("sec-ch-ua-mobile", "?0")
    req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) 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("os-type", "web")
    req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
    req.Header.Set("sec-ch-ua-platform", `"Windows"`)
    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,zh-TW;q=0.8")
    // 发送请求
    resp, err := client.Do(req)
    // 错误处理,判断请求是否发送成功并且服务器响应
    if err != nil {
        // 如果出现问题,打印错误信息并结束程序
        log.Fatal(err)
    }
    // 关闭client,释放资源
    defer resp.Body.Close()
    // 读取服务器的响应结果
    bodyText, err := ioutil.ReadAll(resp.Body)
    // 错误处理
    if err != nil {
        log.Fatal(err)
    }
    // 打印出服务器的响应结果,也就是输入单词的中文翻译
    fmt.Printf("%s\n", bodyText)
}
// 格式化后的bodyText
{
    "rc": 0,
    "wiki": {
        "known_in_laguages": 63,
        "description": {
            "source": "tangible and intangible thing, except labor tied services, that satisfies human wants and provides utility",
            "target": null
        },
        "id": "Q28877",
        "item": {
            "source": "good",
            "target": "\u5546\u54c1"
        },
        "image_url": "http://www.caiyunapp.com/imgs/link_default_img.png",
        "is_subject": "true",
        "sitelink": "https://www.caiyunapp.com/read_mode/?id=6354777915466339550246c5"
    },
    "dictionary": {
        "prons": {
            "en-us": "[g\u028ad]",
            "en": "[gud]"
        },
        "explanations": ["a.\u597d\u7684;\u5584\u826f\u7684;\u5feb\u4e50\u7684;\u771f\u6b63\u7684;\u5bbd\u5927\u7684;\u6709\u76ca\u7684;\u8001\u7ec3\u7684;\u5e78\u798f\u7684;\u5fe0\u5b9e\u7684;\u4f18\u79c0\u7684;\u5b8c\u6574\u7684;\u5f7b\u5e95\u7684;\u4e30\u5bcc\u7684", "n.\u5229\u76ca;\u597d\u5904;\u5584\u826f;\u597d\u4eba", "ad.=well"],
        "synonym": ["excellent", "fine", "nice", "splendid", "proper"],
        "antonym": ["bad", "wrong", "evil", "harmful", "poor"],
        "wqx_example": [
            ["to the good", "\u6709\u5229,\u6709\u597d\u5904"],
            ["good, bad and indifferent", "\u597d\u7684,\u574f\u7684\u548c\u4e00\u822c\u7684"],
            ["good innings", "\u957f\u5bff"],
            ["good and ...", "\u5f88,\u9887;\u5b8c\u5168,\u5f7b\u5e95"],
            ["do somebody's heart good", "\u5bf9\u67d0\u4eba\u7684\u5fc3\u810f\u6709\u76ca,\u4f7f\u67d0\u4eba\u611f\u5230\u6109\u5feb"],
            ["do somebody good", "\u5bf9\u67d0\u4eba\u6709\u76ca"],
            ["be good for", "\u5bf9\u2026\u6709\u6548,\u9002\u5408,\u80dc\u4efb"],
            ["be good at", "\u5728\u2026\u65b9\u9762(\u5b66\u5f97,\u505a\u5f97)\u597d;\u5584\u4e8e"],
            ["as good as one's word", "\u4fe1\u5b88\u8bfa\u8a00,\u503c\u5f97\u4fe1\u8d56"],
            ["as good as", "\u5b9e\u9645\u4e0a,\u51e0\u4e4e\u7b49\u4e8e"],
            ["all well and good", "\u4e5f\u597d,\u8fd8\u597d,\u5f88\u4e0d\u9519"],
            ["a good", "\u76f8\u5f53,\u8db3\u8db3"],
            ["He is good at figures . ", "\u4ed6\u5584\u4e8e\u8ba1\u7b97\u3002"]
        ],
        "entry": "good",
        "type": "word",
        "related": [],
        "source": "wenquxing"
    }
}
3.2.3 生成 request body

我们需要生成一段 JSON,常用的方式是我们先构造出来一个结构体,这个结构体和我们需要生成的JSON 的结构是——对应的

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)
// 创建构造体,类型后的`json:name`是JSON序列化后的名称
type DictRequest struct {
    TransType string `json:"trans_type"`
    Source    string `json:"source"`
    UserID    string `json:"user_id"`
}

func main() {
    client := &http.Client{}
    // 初始化请求结构体
    request := DictRequest{TransType: "en2zh", Source: "good"}
    // 使用json.Marshal得到结构体序列化后的字符串
    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)
    }
    // 后面代码和上面的设置请求体参数及以后代码一样
}
3.2.4 解析 response body

在线工具—OKTools => 点击 JSON 转 Go Struct,然后点击 转化-嵌套

// 将之前的bodyText转化后的结构体
type AutoGenerated 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"`
}

将自动生成的构造体替换掉之前我们假定的构造体,然后添加以下语句

// 定义结构体变量
var dictResponse DictRequest
// 将得到的字符串反序列化为结构体
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
    log.Fatal(err)
}
// 更加详细的打印
fmt.Printf("%#v\n", dictResponse)
3.2.5 打印结果
// 最终的完整代码
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)
    }
    // 这里返回的是byte数组
    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)
    }
    // 判断接口的状态码是否为200
    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 {
        // 不是2个,直接提示错误
        fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
		`)
        // 并结束程序
        os.Exit(1)
    }
    // 否则,开始查询单词意思
    word := os.Args[1]
    query(word)
}

预期结果

image-20230119193532646

未完待续。。。