Go语言的实战案例|青训营笔记

80 阅读6分钟

Go语言的实战案例

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

猜谜游戏

首先程序生成0到100的整数,玩家猜,程序告诉玩家是大了还是小了

package main
import(
"fmt"
"math/rand"
)

func main(){
maxNum := 100
rand.Seed(time.Now().UnixNano()))  //必须要随机数种子,不然每次都一样
secretNumber := rand.Intn(maxNum)
}

这样,生成随机数的部分就完成了。接下来我们处理用户的输入和输出

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)		//这里用stdin构造一个输入流,可以从这个输入流中读信息
	input, err := reader.ReadString('\n')		//每次读一行
	if err != nil {
		fmt.Println("An error occured while reading input. Please try again", err)
		return
	}
	input = strings.Trim(input, "\r\n")

	guess, err := strconv.Atoi(input)
	if err != nil {
		fmt.Println("Invalid input. Please enter an integer value")
		return
	}
	fmt.Println("You guess is", guess)
}

最后再进入一个循环,把猜测放到循环里面:

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

命令行词典

该词典可以在命令行后面放一个单词,输出注释,我们需要调用第三方API然后解析Json

使用彩云翻译

fanyi.caiyunapp.com/

使用开发者工具找到POST包的内容,比如

{
source:"good"
trans_type:"en2zh"
}

这样我们可以在Go中构造一个

但是直接写一个http包太复杂了,里面可能有很多Header需要自己写,我们可以在

curlconverter.com/#go

中生成,首先用开发者工具中选择copy as Curl,复制到这个网址中。

这样就能生成下面的代码

package main

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

func main() {
	client := &http.Client{}
	var data = strings.NewReader(`{"source":["good",""],"trans_type":"auto2zh","request_id":"web_fanyi","media":"text","os_type":"web","dict":true,"cached":true,"replaced":true,"detect":true,"browser_id":"8209aec3752a54a24d0179ce7e5726d0"}`)
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/translator", 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-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
	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("t-authorization", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJicm93c2VyX2lkIjoiODIwOWFlYzM3NTJhNTRhMjRkMDE3OWNlN2U1NzI2ZDAiLCJpcF9hZGRyZXNzIjoiMzYuMTQ4Ljc1LjIwMyIsInRva2VuIjoicWdlbXY0anIxeTM4anlxNnZodmkiLCJ2ZXJzaW9uIjoxLCJleHAiOjE2NzM3NTI1NDZ9.bE6ALAf-q616FP1xxEd9Z-mUxr3Q6t_SNNmP0sozjvU")
	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)
}
复制代码

这里因为有一些Header比较复杂,所以可能有错误,直接删掉就行。我们写词典只需要把string换成自己输入的就行了,还要把json解析出来

我们不能直接用json输入,而是要把串序列化成json,go可以直接把结构体序列化成json,此外,我们还需要将返回的response反序列化,从返回来看,我们可以直接把主要的含义、音标等解析出来,go可以写一个结构体,结构体的字段和返回的json是一致的,这里的json很大,我们可以用代码生成来生成这个结构体:

oktools.net/json/go

type AutoGenerated struct {
	Rc int `json:"rc"`
	Wiki Wiki `json:"wiki"`
	Dictionary Dictionary `json:"dictionary"`
}
type Description struct {
	Source string `json:"source"`
	Target interface{} `json:"target"`
}
type Item struct {
	Source string `json:"source"`
	Target string `json:"target"`
}
type Wiki struct {
	KnownInLaguages int `json:"known_in_laguages"`
	Description Description `json:"description"`
	ID string `json:"id"`
	Item Item `json:"item"`
	ImageURL string `json:"image_url"`
	IsSubject string `json:"is_subject"`
	Sitelink string `json:"sitelink"`
}
type Prons struct {
	EnUs string `json:"en-us"`
	En string `json:"en"`
}
type Dictionary struct {
	Prons Prons `json:"prons"`
	Explanations []string `json:"explanations"`
	Synonym []string `json:"synonym"`
	Antonym []string `json:"antonym"`
	WqxExample []WqxExample[]string `json:"wqx_example"`
	Entry string `json:"entry"`
	Type string `json:"type"`
	Related []interface{} `json:"related"`
	Source string `json:"source"`
}
复制代码

这样的话把json串用反序列化放到结构体中,这几把结构体打印出来:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

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 main() {
	client := &http.Client{}
	request := DictRequest{TransType: "en2zh", Source: "good"}
	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)
	}
	var dictResponse DictResponse
	err = json.Unmarshal(bodyText, &dictResponse)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%#v\n", dictResponse)
}
复制代码

最后我们只需要把音标、含义给打印出来。然后读用户参数或者命令行参数即可!

SOCKS5代理服务

Socks5是一种代理协议,一些服务需要内部才能访问,Socks5代理会开放一个端口来访问这些内容。

可以用一个简单的方法验证代理是否存在

curl -socks 127.0.0.1:1080 www.baidu.com
复制代码

socks代理的原理

  1. 协商:客户端向代理协商认证,如果需要认证则需要有认证流程
  2. 认证通过后客户端发送报文,向服务建立连接
  3. 发送数据,返回结果

在go中实现一个服务很简单,比如实现一个echo server,直接用net包来实现即可:

package main

import (
	"bufio"
	"log"
	"net"
)

func main() {
	server, err := net.Listen("tcp", "127.0.0.1:1080")
	if err != nil {
		panic(err)
	}
	for {
		client, err := server.Accept()
		if err != nil {
			log.Printf("Accept failed %v", err)
			continue
		}
		go process(client)
	}
}

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	for {
		b, err := reader.ReadByte()
		if err != nil {
			break
		}
		_, err = conn.Write([]byte{b})
		if err != nil {
			break
		}
	}
}

复制代码

listen监听,accept接受然后用协程处理

注意这里看上来是一个个字节,但是随实际上编译时会进行合并

代理和echo实际上差不多,只不过要把process改一下。首先需要实现auth阶段,auth就是认证,客户会发送一个报文,详见v2中的代码,大致是读认证包和返回支持的认证方式,这些协议都在socks5中有规定,只需要在连接中读写信息就行了

下一个阶段试图读用户需要访问的ip和端口,具体协议和代码看v3,注意,这个阶段只是获得了需要连接的ip和端口,还没有实际连接

最后,我们需要建立一个tcp连接然后进行交互

在io包中存在一个Copy函数,可以实现单向转发

fun Copy(dst Writer, src Reader)(written int64, err error)

使用两个Copy即可实现两个连接之间的数据交换,代码见v4

课后作业:

  1. 修改第一个例子猜谜游戏,使用fmt.Scanf简化代码
  2. 修改第二个例子,增加一种翻译API
  3. 两个翻译API并行