实战案例 | 青训营笔记

73 阅读9分钟

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


上次我学习了Go 语言的基础,包括基础的语法、语言变量、条件语句、数组以及Go 语言的切片(Slice)和范围(Range)还有Map(集合)等等。


今天则是对于上面的学习做出实践,本次课程主要从三个案例出发,加强对基础的灵活运用。

  • 猜谜游戏
  • 在线词典
  • SOCKS5 代理

1. 猜谜游戏

介绍:
我们要在控制台猜测并输入0~100的数字,去和代码随机生成的答案比较,如果相同,则游戏成功,否则会给与相对应的提示,直至猜出正确答案。


1.1 生成随机数

首先,我们要实现随机生成一个0~100的数字作为答案以供用户猜测。

这里我们要import `math/rand`以及`fmt` 包来实现生成随机数和基本输出。
package main

import (
    "fmt"
    "math/rand"
)

func main() {
    maxNum := 100 //定义最大数为100
    secretNumber := rand.Intn(maxNum) //生成随机数

    fmt.Println("随机数为:", secretNumber) //控制台输出随机数
}

运行结果:

image.png

这里运行后我们发现每次运行结果都为81


为什么呢?

我们看一下rand.Intn(n)这个函数,我发现代码注释说到每次运行时都是相同的随机序列。

所以我用rand.Seed(86)给它一个种子来看看是否能让其产生不断变化的数字。

发现随机数改变了但还是没有随机生成,发现要实现不断变化的随机数还是得使用time.Now().UnixNano()作为随机种子。

修改后代码:
package main

import (
    "fmt"
    "math/rand"
)

func main() {
    maxNum := 100 //定义最大数为100
    secretNumber := rand.Intn(maxNum)
    secretNumber := rand.Intn(maxNum) //生成随机数

    fmt.Println("随机数为:", secretNumber) //控制台输出随机数
}

1.2 读取用户输入

要实现用户输入,我们要使用"os"包以及"bufio"包来实现控制台输入以及读取
通过"strings"包和"strconv"包实现去换行符以及将字符串转换为数字

代码实现:

        fmt.Println("请输入你要猜的数字")
        reader := bufio.NewReader(os.Stdin) // 获取控制台输入
        input, err := reader.ReadString('\r\n') //读取一行输入

        if err != nil {
            fmt.Println("输入错误,请重试", err)
            return
        }
        input = strings.TrimSuffix(input, "\r\n") //去掉换行符

        guess, err := strconv.Atoi(input) //转换成数字

        if err != nil {
            fmt.Println("无效输入,请输入一个数字")
            return
        }

        fmt.Println("你猜测的是 ", guess)

这里去掉换行符的地方可能是老师和我使用的系统不同,Windows系统换行需要去掉的还有/r,所以应该填入\r\n才对。

代码中的方法:
NewReader(rd io.Reader):返回一个新的Reader,其缓冲区具有默认大小

ReadString(delim byte):ReadString读取,直到输入中第一次出现delim,返回一个包含直到
分隔符的数据的字符串。

TrimSuffix(s string, suffix string):TrimSuffix返回不带所提供后缀字符串的s。如果s
不以后缀结尾,则s不变地返回。

Atoi(s string):Atoi等价于ParseInt(s, 10,0),转换为int类型

1.3 实现逻辑判断

代码实现

    if guess > secretNumber {
        fmt.Println("你猜测的数字大于secret number,请再试一次")
    } else if guess < secretNumber {
        fmt.Println("你猜测的数字小于secret number,请再试一次")
    } else {
        fmt.Println("恭喜你,猜对了!")
    }

逻辑判断较为简单,就不过多分析


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("随机数为:", secretNumber) 不让用户知道答案

	fmt.Println("请输入你要猜的数字")
	reader := bufio.NewReader(os.Stdin)
	for {
		input, err := reader.ReadString('\n') //读取一行输入

		if err != nil {
			fmt.Println("输入错误,请重试", err)
            //return 使循环不退出而是进入下一个循环
			continue
		}
		input = strings.TrimSuffix(input, "\r\n") //去掉换行符

		guess, err := strconv.Atoi(input) //转换成数字

		if err != nil {
			fmt.Println("无效输入,请输入一个数字")
		   //return 
			continue
		}
		fmt.Println("你猜测的是 ", guess)

		if guess > secretNumber {
			fmt.Println("你猜测的数字大于secret number,请再试一次")
		} else if guess < secretNumber {
			fmt.Println("你猜测的数字小于secret number,请再试一次")
		} else {
			fmt.Println("恭喜你,猜对了!")
			break
		}
	}

}

这里用for {} 死循环 实现用户直到猜对数字否则一直猜测
最终实现效果: image-20230112231713837


2. 在线词典

介绍:
可以实现在控制台查询单词的意思以及音标等信息

如何实现:
1.通过调用第三方API:彩云小译 - 在线翻译 (caiyunapp.com)
2.Convert curl to Go (curlconverter.com):在线将curl命令转换为各种语言
3.JSON转Golang Struct - 在线工具 - OKTools


2.1 抓包

在在彩云小译 - 在线翻译 (caiyunapp.com)翻译后打开开发者控制台复制cURL image-20230112234530848

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

2.2 生成代码解读

打开Convert curl to Go (curlconverter.com) 获取Go 语言代码 image-20230112234657801

获取代码如下:

package main

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

func main() {
        //创建一个使用DefaultTransport的可用客户端对象。
	client := &http.Client{}
        //定义一个请求数据
	var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
	//创建请求,请求方式为POST,地址为https://api.interpreter.caiyunai.com/v1/dict,请求数据为data
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
	if err != nil {
		log.Fatal(err)
	}
        //设置请求头
	req.Header.Set("authority", "api.interpreter.caiyunai.com") //设置请求头
	req.Header.Set("accept", "application/json, text/plain, */*")
	req.Header.Set("accept-language", "zh")
	req.Header.Set("app-name", "xy")
	req.Header.Set("content-type", "application/json;charset=UTF-8")
	req.Header.Set("device-id", "")
	req.Header.Set("origin", "https://fanyi.caiyunapp.com")
	req.Header.Set("os-type", "web")
	req.Header.Set("os-version", "")
	req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
	req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Microsoft Edge";v="108"`)
	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/108.0.0.0 Safari/537.36 Edg/108.0.1462.76")
	req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
	//发送请求
	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)
	}
	fmt.Printf("%s\n", bodyText)
}

运行后会发现有一大堆复杂的JSON数据,格式化后

{
    "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"
    }
}

不必担心,接下来会简化数据获取我们想要的数据


2.3 生成request body

  1. 先创建结构体 DictRequest
// DictRequest 1.创建结构体
type DictRequest struct {
	TransType string `json:"trans_type"`
	Source    string `json:"source"`
	UserId    string `json:"user_id"`
}
  1. 为结构体赋值
    //去除字符串数据改为结构体
    //var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
    //2.为结构体赋值
    request := DictRequest{TransType: "en2zh", Source: "good"}
  1. 将请求序列化
buf, err := json.Marshal(request)
  1. 因为json.Marshal返回的是一个byte数组,所以相应的Reader流替换掉
//var data = strings.NewReader(buf)
var data = bytes.NewReader(buf)

这一步就完成了


2.4 解析 request body

bodyText, err := ioutil.ReadAll(resp.Body)返回的是请求后返回的JSON数据

通过 JSON转Golang Struct - 在线工具 - OKTools 将 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"`
}

改动后续代码

    //fmt.Printf("%s\n")
    var dictResponse DictResponse
    err = json.Unmarshal(bodyText, &dictResponse)//反序列化关键,记得&符号,取地址符
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%#v\n", dictResponse)//更加详细的进行打印信息 后面也将注释

再将请求提取为方法

func query(word string) {
   client := &http.Client{}
   //var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
   //2.为结构体赋值
   request := DictRequest{TransType: "en2zh", Source: word}
   //3.将请求序列化
   buf, err := json.Marshal(request)
   //4.因为json.Marshal返回的是一个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("authority", "api.interpreter.caiyunai.com") //设置请求头
   req.Header.Set("accept", "application/json, text/plain, */*")
   req.Header.Set("accept-language", "zh")
   req.Header.Set("app-name", "xy")
   req.Header.Set("content-type", "application/json;charset=UTF-8")
   req.Header.Set("device-id", "")
   req.Header.Set("origin", "https://fanyi.caiyunapp.com")
   req.Header.Set("os-type", "web")
   req.Header.Set("os-version", "")
   req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
   req.Header.Set("sec-ch-ua", `"Not?A_Brand";v="8", "Chromium";v="108", "Microsoft Edge";v="108"`)
   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/108.0.0.0 Safari/537.36 Edg/108.0.1462.76")
   req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
   //发送请求
   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))
   }
   //fmt.Printf("%s\n", bodyText)
   var dictResponse DictResponse
   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 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)
}

2.5 完成项目

运行时须在终端或运行时增加命令单词

go run 代码目录 查询的单词

例如:go run .\onlineWord\onlineWord.go colloct image.png

3.SOCKS5 代理

这里没听懂,因为还没学习,后续会补上。

3.1 介绍

image-20230113003421026

SOCKS5 是一个代理协议,它在使用TCP/IP协议通讯的前端机器和服务器机器之间扮演一个中介角色,使得内部网中的前端机器变得能够访问Internet网中的服务器,或者使通讯更加安全。SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器, 模拟了一个前端的行为。在这里,前端和SOCKS5之间也是通过TCP/IP协议进行通讯,前端将原本要发送给真正服务器的请求发送给SOCKS5服务器,然后SOCKS5服务器将请求转发给真正的服务器。

3.2 原理

image-20230113003742561