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
使用彩云翻译
使用开发者工具找到POST包的内容,比如
{
source:"good"
trans_type:"en2zh"
}
这样我们可以在Go中构造一个
但是直接写一个http包太复杂了,里面可能有很多Header需要自己写,我们可以在
中生成,首先用开发者工具中选择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很大,我们可以用代码生成来生成这个结构体:
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代理的原理
- 协商:客户端向代理协商认证,如果需要认证则需要有认证流程
- 认证通过后客户端发送报文,向服务建立连接
- 发送数据,返回结果
在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
课后作业:
- 修改第一个例子猜谜游戏,使用fmt.Scanf简化代码
- 修改第二个例子,增加一种翻译API
- 两个翻译API并行