GO语言入门实战——猜谜游戏、在线词典

猜谜游戏

实例介绍

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

代码展示

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

具体分析

L14:定义变量 maxNum,指定随机数的最大值

L15:用系统时间戳来初始化随机数的种子(保证每次生产的随机数不重复)                        

L16:调用 Intn函数,生成一个随机数并存储在 secretNumber变量中

L18:使用 os.Stdin 得到 stdin 执行文件,调用 bufio.NewReader 把一个文件转换成一个 reader 变量

L19:使用 for 循环完成用户猜谜的动作,成功猜出正确答案后程序退出

L20:用 read的 ReadString 方法来读取输入

L25:strings.Trim方法返回字符串的副本,忽略前导空白和尾部空白

L27:调用strconv.Atoi方法,返回值guess返回转换成int的值,err判断是否转换成功

L33:用if-else if-else语句判断用户猜的数字是否正确

在线词典

实例介绍

通过使用第三方API获取数据,达到读取用户输入的数据并返回其音标、汉译的目的。

前期准备

API接口

API(Application Programming Interface,应用程序编程接口)是软件系统之间互相访问和交换信息的一种方式。它定义了在一个应用程序中调用另一个应用程序的方式(或服务)。API接口可以让开发者无需了解程序内部的具体实现细节,仅通过API接口来进行交互,从而提高了软件的可重用性和互操作性。通常来说,API接口包括一些函数、协议、数据结构和工具类等。开放API可以帮助企业提供更好的产品和服务,同时也增加了服务的使用范围和互动性,是现代产品和服务中不可或缺的一部分。

从网页中获取API地址
  • 首先,我们需要打开浏览器的开发者工具。( F12 或右键网页页面后点击检查选项来打开)。
  • 在开发者工具中,找到网络标签页并打开。
  • 以本文示例而言,我们应当在原文文本框输入一个已知存在的单词,并在点击翻译后注意观察网络标签页下记录的网络请求。
  • 通过筛选网络请求记录,我们很容易找到需要的调用 API 获取翻译结果的请求。其中,General下的 Request URL便是我们需要的 API 地址。
HTTP协议

HTTP(Hyper Text Transfer Protocol): 全称超文本传输协议,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。HTTP 是一种应用层协议,是基于 TCP/IP 通信协议来传递数据的,其中 HTTP1.0、HTTP1.1、HTTP2.0 均为 TCP 实现,HTTP3.0 基于 UDP 实现。现主流使用 HTTP1.0 和 HTTP3.0。

协议: 为了使数据在网络上从源头到达目的,网络通信的参与方必须遵循相同的规则,这套规则称为协议,它最终体现为在网络上传输的数据包的格式。

HTTP报文的内容

HTTP报文的结构由报文首部、空行、报文主体组成。

组成部分作用
报文首部传递信息时必不可少的一些附加信息
空行用于分割报文首部和报文主体
报文主体信息的内容

报文首部类似于快递包裹表面的快递纸,报文主体类似于快递的物品本身,空行类似于包裹,将快递纸和物品隔离开。

与看得见摸得着的快递包裹不同,HTTP 报文的本质是一串字节序列,在网络流中由不同设备进行处理,因此 HTTP 报文的每一部分都有自己的格式要求,便于设备能够读懂。

请求报文与响应报文

回到查单词的例子,我的客户端想给彩云小译的服务器发送 pretty这个单词,就需要按照 HTTP 协议的规定,发送一个 HTTP 报文。彩云小译的服务器收到请求报文并处理后,也会返回一个报文当作结果。

客户端发送到服务器的报文,叫做请求报文
服务器返回给客户端的报文,叫做响应报文

利用Golang发送http请求

如果直接手动转换该请求到 Go 语言中显然是低效且不现实的。这时,我们可以合理利用开发者工具的功能,来方便快捷地进行代码转换。

  • 我们在请求列表中右键请求,点击 Copy as cURL,此时我们将获得一串很长的命令。打开终端/命令提示符并粘贴命令,执行,如果不出意外的话,我们将获得一串很长的 JSON 数据。
  • 打开cURL Converter,将刚刚拿到的命令粘贴到上面的输入框中,即可在下面查看转换后的Go语言代码。注意,由于部分请求头较为复杂,转换后的内容可能存在问题,直接删除报错的行即可。
  • 首先创建了一个 HTTP client,创建的时候可以指定很多参数,包括请求的超时时间、是否使用 cookie等。
  • 接下来是构造一个HTTP请求,这是一 post 请求,会用到 HTTP.NewRequest。第一个参数是 http 方法,第二个参数是 URL,最后一个参数是 body,body 可能很大,为了支持流式发送,是一个只读流。用 strings.NewReader来把字符串转换成一个流,这样我们就成功构造了一个HTTP request。
  • 接下来需要对这个 HTTP request 设置 header ,调用 client.do 就能得到 response 如果请求失败的话,error 会返回非 nil,会打印错误并且退出进程。
  • response中包括 HTTP状态码,response header 和 body。body同样是一个流,在 golang 里面,为了避免资源泄露,需要加一个 defer 来手动关闭这个流,这个defer会在这个函数运行结束之后去执行。
  • 最后用 io.ReadAll来读取这个流,就能得到整个 body,再用 print 打印出来。

代码展示

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

具体分析

L13:现在我们已经可以发送请求并且打印 json,但是请求参数是固定的,我们希望可以通过变量来输入,此时就需要创建一个结构体并序列化成为 json 字符串,使用到 json 序列化和反序列化软件包,并定义一个 DictRequest结构体。

L14:json:"trans_type",这个标签用于指定 json 序列化/反序列化时对应的名称。 通过这种方法,我们便可以根据传入的参数动态生成 RequestBody

L19-50:解析 RequestBody 如果请求正确的话,我们应该可以拿到一个很长的 json 数据。对于如此庞大且没有被格式化的 json 数据,人工分析显然是不可能的,这时候就要借助另一个工具:OKToolsJSON Go Struct功能。通过该方法,我们可以很便捷的拿到反序列化所需的结构体,然后我们将生成的结构体复制到代码中,并在末尾加入处理响应的逻辑。

L53:创建了一个 HTTP Client,创建的时候可以指定很多参数,包括请求的超时时间、是否使用Cookie 等。

L55-63:如果正确输出 json 字符串则说明程序正确,接下来我们用同样的方法处理响应。

L60:构造了一个 HTTP 请求,这是一个 POST 请求,然后会用到 http.NewRequest,第一个参数是 HTTP 请求方法 POST, 第二个参数是 URL, 最后一个参数是RequestBody。RequestBody可能很大,并且为了支持流式发送,它作为一个只读流实现。我们用 strings.NewReader把字符串转换成一个流,这样我们就成功构造了一个 HTTP Request

L64-81:我们对这个 HTTP Request 设置了一些 Request Header。接下来我们把我们调用client.Do,就能得到 Response,Response 有它的 HTTP 状态码, Response Header 和 Body。 Body 同样是一个流,在 golang 里面,为了避免资源泄露,我们需要加一个 defer 来手动关闭这个流,这个 defer 会在当前函数运行结束之后去执行。接下来我们使用 ioutil.ReadAll来读取这个流,就能得到整个 Body。

L94-98:通过可 json.Unmarshal方法对请求返回的JSON数据进行反序列化。

L99-102:我们只需要结构体中的部分字段,包括音标和单词翻译,因此我们选择性输入。

小结

到目前为止,我们做出了一个在线查词的 Golang 程序,能够构造请求报文,将报文发送给服务器,并以各种形式利用返回的响应报文,达成在线词典的目的,希望小伙伴看完后能有所收获。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值