一.猜题游戏-生成随机数
1.0 生成随机数
package main
import (
"fmt"
"math/rand"
)
func main() {
maxNum := 100
secretNumber := rand.Intn(maxNum)
fmt.Println("The secret number is", secretNumber)
}
发现每次都会打印相同数字在屏幕上(81)
开发环境可能可以打印出不同的数字(但不推荐1.0)
我们可以查看 这个包的文档。文档的开头的例子告诉我们使用它之前需要设置随机数种子,否则的话每一次都会生成相同的随机数序列。一般惯例用法是在程序启动的时候,用启动的时间戳来初始化随机数种子,我们用 time.Now().UnixNano() 来初始化随机种子。
1.1
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano()) // 设置随机数种子
secretNumber := rand.Intn(maxNum)
fmt.Println("The secret number is ", secretNumber)
}
结果会是在 0 到 100 之间
2.0 读取用户输入
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) //把一个文件转换成一个 reader 变量
input, err := reader.ReadString('\n') //读取一行
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
return
}
input = strings.TrimSuffix(input, "\n") // 去掉换行符,注意Mac\n,windows自动加入的是\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)
}
每个程序执行的时候都会打开几个文件,stdin stdout stderr等,stdin 文件可以用 os.Stdin 来得到。
然后直接操作这个文件很不方便,我们会用 bufio.NewReader 把一个文件转换成一个 reader 变量。
reader 变量上会有很多用来操作一个流的操作,我们可以用它的 ReadString 方法来读取一行。
如果失败了的话,我们会打印错误并能退出。ReadString 返回的结果包含结尾的回车符和换行符,去掉,再用strconv.Atoi()转换成数字。如果转换失败,我们同样打印错误,退出。
3.0 实现判断逻辑
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!")
}
4.0 完整的游戏
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) //把一个文件转换成一个 reader 变量
for {
input, err := reader.ReadString('\n') //读取一行
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
continue
}
input = strings.TrimSuffix(input, "\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
}
}
}
二.在线词典 1.准备(以Google浏览器,Mac系统操作)
首先打开网址:fanyi.caiyunapp.com/#/
查询一个单词,通过调用第三方的 API 查询到单词的翻译并打印出来。
在这个项目里面,学习如何用 go 语言来发送 HTTP 请求、解析 json,学习了如何使用代码生成来提高开发效率
2.抓包
要用到的 API 是彩云在线翻译,以此为例
点击翻译,浏览器会发送一系列请求,找到查询单词的请求。这是一个 HTTP 的 post 的请求(相当复杂),请求的 header 是一个 json,里面有两个字段,代表要翻译的语言和查询的单词,source就是要查询的词。API 的返回结果里面会有 Wiki 和 dictionary 两个字段。需要用的结果主要在 dictionary.Explanations 字段里面,其他字段里面还包括音标等信息。
3.代码生成
curl 'https://api.interpreter.caiyunai.com/v1/translator' \
-X 'OPTIONS' \
-H 'authority: api.interpreter.caiyunai.com' \
-H 'accept: */*' \
-H 'accept-language: zh-CN,zh;q=0.9' \
-H 'access-control-request-headers: app-name,content-type,device-id,os-type,os-version,t-authorization,version,x-authorization' \
-H 'access-control-request-method: POST' \
-H 'origin: https://fanyi.caiyunapp.com' \
-H 'referer: https://fanyi.caiyunapp.com/' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: cross-site' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \
--compressed ;
curl 'https://api.interpreter.caiyunai.com/v1/translator' \
-H 'authority: api.interpreter.caiyunai.com' \
-H 'accept: application/json, text/plain, */*' \
-H 'accept-language: zh-CN,zh;q=0.9' \
-H 'app-name: xy' \
-H 'content-type: application/json;charset=UTF-8' \
-H 'device-id: 5107a8a7278db425e7bce568842b1261' \
-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="8", "Chromium";v="114", "Google Chrome";v="114"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: cross-site' \
-H 't-authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJicm93c2VyX2lkIjoiNTEwN2E4YTcyNzhkYjQyNWU3YmNlNTY4ODQyYjEyNjEiLCJpcF9hZGRyZXNzIjoiMjIzLjcyLjU1LjMxIiwidG9rZW4iOiJxZ2VtdjRqcjF5MzhqeXE2dmh2aSIsInZlcnNpb24iOjEsImV4cCI6MTY5MDM2NjQ1Nn0.CJyHVaSyAKSfVjtRlXx4trDK57TvhLAewH-rkhQWrag' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \
-H 'x-authorization: token:qgemv4jr1y38jyq6vhvi' \
--data-raw '{"source":"good","trans_type":"auto2zh","request_id":"web_fanyi","media":"text","os_type":"web","dict":true,"cached":true,"replaced":true,"style":"formal","detect":true,"browser_id":"5107a8a7278db425e7bce568842b1261"}' \
--compressed ;
curl 'https://api.interpreter.caiyunai.com/v1/dict' \
-X 'OPTIONS' \
-H 'authority: api.interpreter.caiyunai.com' \
-H 'accept: */*' \
-H 'accept-language: zh-CN,zh;q=0.9' \
-H 'access-control-request-headers: app-name,content-type,device-id,os-type,os-version,version,x-authorization' \
-H 'access-control-request-method: POST' \
-H 'origin: https://fanyi.caiyunapp.com' \
-H 'referer: https://fanyi.caiyunapp.com/' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: cross-site' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \
--compressed ;
curl 'https://api.interpreter.caiyunai.com/v1/dict' \
-H 'authority: api.interpreter.caiyunai.com' \
-H 'accept: application/json, text/plain, */*' \
-H 'accept-language: zh-CN,zh;q=0.9' \
-H 'app-name: xy' \
-H 'content-type: application/json;charset=UTF-8' \
-H 'device-id: 5107a8a7278db425e7bce568842b1261' \
-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="8", "Chromium";v="114", "Google Chrome";v="114"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: cross-site' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' \
-H 'x-authorization: token:qgemv4jr1y38jyq6vhvi' \
--data-raw '{"trans_type":"en2zh","source":"good"}' \
--compressed
打开代码生成网址:
将复制的粘贴过来,自动生成 Golang 代码
需要在 Golang 里面去发送这个请求。但是这个请求比较复杂,用代码构造很麻烦。可以用一种简单的方式来生成代码,右键浏览器里面的 copy as curl,在终端粘贴一下 curl 命令,可以返回一大串 json。
4.生成代码解读
5.生成request body/解析request body
在 Golang 里面,需要生成一段 JSON ,常用的方式是先构造出来一个结构体,这个结构体和需要生成的 JSON 的结构是一一对应的。
通过该网站转换结构体
将Preview中复制过来转换成结构体
完整代码展示:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"sync"
"time"
)
// -------------------------主函数------------------------------
func main() { //此在线词典程序需要在 Git Bash终端命令行运行,如输入 go run *.go hello
start := time.Now()
if len(os.Args) != 2 { // Args 保存命令行参数,以程序名称开头,这里第2个参数为要查询的单词
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
`) // Fprintf() 根据格式说明符格式化并写入 os.Stderr 。它返回写入的字节数和遇到的任何写入错误。
os.Exit(1)
}
word := os.Args[1]
wg := sync.WaitGroup{} // WaitGroup() 等待一组 goroutine 完成,主 goroutine 调用 Add() 来设置要等待的 goroutine 的数量,然后每个 goroutine 运行并在完成时调用Done(),同时, Wait()可以用来阻塞,直到所有的 goroutine 都完成
wg.Add(2) //将可能为负的增量添加到 WaitGroup() 计数器,如果计数器变为零,则所有在 Wait() 上阻塞的 goroutine 都会被释放,如果计数器变为负数,请添加错误处理
go QueryCaiYun(word, &wg)
go QueryBaiDu(word, &wg)
wg.Wait()
finish := time.Now()
fmt.Println("runtime:", finish.Sub(start))
}
//------------------------彩云------------------------------------------
type CaiYunDictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
type CaiYunDictResponse 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 QueryCaiYun(word string, wg *sync.WaitGroup) {
client := &http.Client{} //定义客户
// var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`) //把输入的字符串 data 转换成流 req
request := CaiYunDictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request) // Marshal() 返回 request 的 JSON 编码,序列化为一个 byte 数组。
if err != nil {
log.Fatal(err) //打印日志,退出程序
}
var data = bytes.NewReader(buf) //把输入的 byte 数组转换成流 req
/* 创建请求 */
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err) //打印日志,退出程序
}
/* 设置请求头 */
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("Connection", "keep-alive")
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
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/100.0.4896.127 Safari/537.36 Edg/100.0.1185.44")
req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
req.Header.Set("app-name", "xy")
req.Header.Set("os-type", "web")
req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="100", "Microsoft Edge";v="100"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
/* 发起请求 */
resp, err := client.Do(req)
if err != nil {
log.Fatal(err) //打印日志,退出程序
}
defer resp.Body.Close() //defer 会在函数结束后从后往前触发,Close() 手动关闭 Body流,防止内存资源泄露
/*读取响应*/
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 CaiYunDictResponse
err = json.Unmarshal(bodyText, &dictResponse) //Unmarshal()解析 bodyText的 JSON 编码的数据并反序列化将结果存储在 dictResponse 指向的值中。
if err != nil {
log.Fatal(err) //打印日志,退出程序
}
// fmt.Printf("%#v\n", dictResponse)
fmt.Println("--------------------彩云翻译-----------------------")
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
wg.Done()
}
//--------------------------百度---------------------------------------------
type BaiDuDictRequest struct {
Query string `json:"query"`
UserID string `json:"user_id"`
}
type BaiDuDictResponse struct {
TransResult struct {
Data []struct {
Dst string `json:"dst"`
PrefixWrap int `json:"prefixWrap"`
Result [][]interface{} `json:"result"`
Src string `json:"src"`
} `json:"data"`
From string `json:"from"`
Status int `json:"status"`
To string `json:"to"`
Type int `json:"type"`
Phonetic []struct {
SrcStr string `json:"src_str"`
TrgStr string `json:"trg_str"`
} `json:"phonetic"`
} `json:"trans_result"`
DictResult struct {
Edict struct {
Item []struct {
TrGroup []struct {
Tr []string `json:"tr"`
Example []interface{} `json:"example"`
SimilarWord []string `json:"similar_word"`
} `json:"tr_group"`
Pos string `json:"pos"`
} `json:"item"`
Word string `json:"word"`
} `json:"edict"`
Collins struct {
Entry []struct {
EntryID string `json:"entry_id"`
Type string `json:"type"`
Value []struct {
MeanType []struct {
InfoType string `json:"info_type"`
InfoID string `json:"info_id"`
Example []struct {
ExampleID string `json:"example_id"`
TtsSize string `json:"tts_size"`
Tran string `json:"tran"`
Ex string `json:"ex"`
TtsMp3 string `json:"tts_mp3"`
} `json:"example"`
} `json:"mean_type"`
Gramarinfo []interface{} `json:"gramarinfo"`
Tran string `json:"tran"`
Def string `json:"def"`
MeanID string `json:"mean_id"`
Posp []struct {
Label string `json:"label"`
} `json:"posp"`
} `json:"value"`
} `json:"entry"`
WordName string `json:"word_name"`
Frequence string `json:"frequence"`
WordEmphasize string `json:"word_emphasize"`
WordID string `json:"word_id"`
} `json:"collins"`
From string `json:"from"`
SimpleMeans struct {
WordName string `json:"word_name"`
From string `json:"from"`
WordMeans []string `json:"word_means"`
Exchange struct {
WordPl []string `json:"word_pl"`
WordEst []string `json:"word_est"`
WordEr []string `json:"word_er"`
} `json:"exchange"`
Tags struct {
Core []string `json:"core"`
Other []string `json:"other"`
} `json:"tags"`
Symbols []struct {
PhEn string `json:"ph_en"`
PhAm string `json:"ph_am"`
Parts []struct {
Part string `json:"part"`
Means []string `json:"means"`
} `json:"parts"`
PhOther string `json:"ph_other"`
} `json:"symbols"`
} `json:"simple_means"`
Lang string `json:"lang"`
Oxford struct {
Entry []struct {
Tag string `json:"tag"`
Name string `json:"name"`
Data []struct {
Tag string `json:"tag"`
Data []struct {
Tag string `json:"tag"`
P string `json:"p"`
PText string `json:"p_text"`
} `json:"data"`
} `json:"data"`
} `json:"entry"`
Unbox []struct {
Tag string `json:"tag"`
Type string `json:"type"`
Name string `json:"name"`
Data []struct {
Tag string `json:"tag"`
Text string `json:"text,omitempty"`
Words []string `json:"words,omitempty"`
Outdent string `json:"outdent,omitempty"`
Data []struct {
Tag string `json:"tag"`
EnText string `json:"enText"`
ChText string `json:"chText"`
} `json:"data,omitempty"`
} `json:"data"`
} `json:"unbox"`
} `json:"oxford"`
Sanyms []struct {
Tit string `json:"tit"`
Type string `json:"type"`
Data []struct {
P string `json:"p"`
D []string `json:"d"`
} `json:"data"`
} `json:"sanyms"`
Usecase struct {
Idiom []struct {
P string `json:"p"`
Tag string `json:"tag"`
Data []struct {
Tag string `json:"tag"`
Data []struct {
EnText string `json:"enText"`
Tag string `json:"tag"`
ChText string `json:"chText"`
Before []struct {
Tag string `json:"tag"`
Data []struct {
EnText string `json:"enText"`
Tag string `json:"tag"`
ChText string `json:"chText"`
} `json:"data"`
} `json:"before,omitempty"`
} `json:"data"`
} `json:"data"`
} `json:"idiom"`
} `json:"usecase"`
BaiduPhrase []struct {
Tit []string `json:"tit"`
Trans []string `json:"trans"`
} `json:"baidu_phrase"`
QueryExplainVideo struct {
ID int `json:"id"`
UserID string `json:"user_id"`
UserName string `json:"user_name"`
UserPic string `json:"user_pic"`
Query string `json:"query"`
Direction string `json:"direction"`
Type string `json:"type"`
Tag string `json:"tag"`
Detail string `json:"detail"`
Status string `json:"status"`
SearchType string `json:"search_type"`
FeedURL string `json:"feed_url"`
Likes string `json:"likes"`
Plays string `json:"plays"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
DuplicateID string `json:"duplicate_id"`
RejectReason string `json:"reject_reason"`
CoverURL string `json:"coverUrl"`
VideoURL string `json:"videoUrl"`
ThumbURL string `json:"thumbUrl"`
VideoTime string `json:"videoTime"`
VideoType string `json:"videoType"`
} `json:"queryExplainVideo"`
} `json:"dict_result"`
LijuResult struct {
Double string `json:"double"`
Tag []string `json:"tag"`
Single string `json:"single"`
} `json:"liju_result"`
Logid int64 `json:"logid"`
}
func QueryBaiDu(word string, wg *sync.WaitGroup) {
client := &http.Client{} //定义客户
// var data = strings.NewReader(`query=good`) //把输入的字符串 data 转换成流 req
request := BaiDuDictRequest{Query: word}
buf, err := json.Marshal(request) // Marshal() 返回 request 的 JSON 编码,序列化为一个 byte 数组。
if err != nil {
log.Fatal(err) //打印日志,退出程序
}
var data = bytes.NewReader(buf) //把输入的 byte 数组转换成流 req
/* 创建请求 */
req, err := http.NewRequest("POST", "https://fanyi.baidu.com/langdetect", data)
if err != nil {
log.Fatal(err) //打印日志,退出程序
}
/* 设置请求头 */
req.Header.Set("Accept", "*/*")
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("Connection", "keep-alive")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Set("Cookie", `BIDUPSID=2860D678CCB10886990D4D819CC24BAB; PSTM=1603262095; REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; __yjs_duid=1_3242d9ae5ec151bc4e7e9d908fe0e54c1620968286434; BDUSS=g3Tm93dWtraHZkRUdsdzlJeDEyalp2RmpZOW1RNmlDMUV3d2N0M0c4Z0pqNXhoRVFBQUFBJCQAAAAAAAAAAAEAAABffmtXxKnTsNChutpYSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkCdWEJAnVhfk; BDUSS_BFESS=g3Tm93dWtraHZkRUdsdzlJeDEyalp2RmpZOW1RNmlDMUV3d2N0M0c4Z0pqNXhoRVFBQUFBJCQAAAAAAAAAAAEAAABffmtXxKnTsNChutpYSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkCdWEJAnVhfk; APPGUIDE_10_0_2=1; BAIDUID=3A21E0D3A023E0BF48D5E6AB59882FC3:SL=0:NR=20:FG=1; MAWEBCUID=web_ZbMGPxbtNTJgOhwuypnUUKjyVeChjrWNsCSTdnAIkJyWEtWEYb; BDORZ=FFFB88E999055A3F8A630C64834BD6D0; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1652153403,1652175540,1652186130,1652278875; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1652506784; delPer=0; PSINO=1; BAIDUID_BFESS=3A21E0D3A023E0BF48D5E6AB59882FC3:SL=0:NR=20:FG=1; RT="z=1&dm=baidu.com&si=hqolag82j09&ss=l35hyf6s&sl=2&tt=56c&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=5ne&ul=2axq&hd=2b02"; BA_HECTOR=24052g04012lag8kt41h7ulf10q; BDRCVFR[OEHfjv-pq1f]=mk3SLVN4HKm; BDRCVFR[dG2JNJb_ajR]=mk3SLVN4HKm; BDRCVFR[-pGxjrCMryR]=mk3SLVN4HKm; H_PS_PSSID=`)
req.Header.Set("Origin", "https://fanyi.baidu.com")
req.Header.Set("Referer", "https://fanyi.baidu.com/?aldtype=16047")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Site", "same-origin")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.44")
req.Header.Set("X-Requested-With", "XMLHttpRequest")
req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="100", "Microsoft Edge";v="100"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
/* 发起请求 */
resp, err := client.Do(req)
if err != nil {
log.Fatal(err) //打印日志,退出程序
}
defer resp.Body.Close() //defer 会在函数结束后从后往前触发,Close() 手动关闭 Body流,防止内存资源泄露
/*读取响应*/
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 BaiDuDictResponse
err = json.Unmarshal(bodyText, &dictResponse) //Unmarshal()解析 bodyText的 JSON 编码的数据并反序列化将结果存储在 dictResponse 指向的值中。
if err != nil {
log.Fatal(err) //打印日志,退出程序
}
// fmt.Printf("%#v\n", dictResponse)
fmt.Println("--------------------百度翻译-----------------------")
fmt.Println(word, "UK:", dictResponse.DictResult.SimpleMeans.Symbols, "US:", dictResponse.DictResult.SimpleMeans.Symbols)
for _, item := range dictResponse.DictResult.SimpleMeans.WordMeans {
fmt.Println(item)
}
wg.Done()
}
三.SOCKS5 代理介绍
socks5是网络传输协议的一种,位于表示层与传输层之间,主要用于客户端与外网服务器之间通讯的中间传递。它是一种通用代理服务器,可以允许有权限的用户穿过防火墙的限制,使得高权限用户可以访问外部资源。大量网络应用程序都支持socks5代理。
原理:
Socks5是一种网络传输协议,位于OSI模型中的表示层与传输层之间,主要用于客户端与外网服务器之间通讯的中间传递。Socks5协议的工作原理包括以下步骤: 客户端向代理服务器发出请求信息,用于协商版本和认证方法。 代理服务器应答,将选择的方法发送给客户端。 客户端和代理服务器进入由选定认证方法所决定的子协商过程。 子协商过程结束后,客户端发送请求信息,其中包含目标服务器的IP地址和端口。 代理服务器验证客户端身份,通过后与目标服务器连接,目标服务器经过代理服务器向客户端返回状态响应。 连接完成后,代理服务器开始作为中转站中转数据。
SOCKS5代理- Tcp echo serve
package main
import (
"io"
"log"
"net"
"time"
)
func main() {
// 设置代理服务器配置
socks.SetDefaultProxy(socks.SOCKS5, "localhost", 1080)
// 创建TCP服务器套接字
serverSocket, err := net.Listen("tcp", "localhost:12345")
if err != nil {
log.Fatal("Failed to create server socket:", err)
}
// 监听客户端连接
go func() {
for {
conn, err := serverSocket.Accept()
if err != nil {
log.Println("Failed to accept client connection:", err)
continue
}
log.Println("New client connected")
// 设置客户端套接字为非阻塞模式
conn.SetDeadline(time.Now().Add(time.Hour))
// 建立到目标服务器的连接
target, err := socks.Dial(conn, "localhost", 1080)
if err != nil {
log.Println("Failed to establish connection to target server:", err)
continue
}
// 将客户端数据转发到目标服务器,并将目标服务器的响应回显给客户端
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil && err != io.EOF {
log.Println("Failed to read data from client:", err)
break
}
if n == 0 {
break
}
target.Write(buf[:n])
}
target.Close()
conn.Close()
}
}()
select {} // 阻塞程序,使其不会退出
}
该代码创建了一个基于Go语言的SOCKS5代理TCP回显服务器,它监听本地端口12345上的连接,并接受客户端请求。一旦客户端连接成功,服务器将通过SOCKS5代理建立到目标服务器的TCP连接,并将客户端发送的所有数据直接发送回客户端。当客户端关闭连接时,服务器将关闭相应的连接。 请注意,该代码中的代理服务器配置使用了默认的localhost地址和端口1080。如果您使用的是不同的代理服务器,请相应地更改这些值。此外,该代码中的套接字使用了非阻塞模式,这使得服务器可以处理多个客户端并发连接。
SOCKS5代理-请求阶段
SOCKS5代理是一种网络协议,用于在客户端和服务器之间建立一个代理连接。其请求阶段是SOCKS5代理通信中的重要阶段之一,主要包括以下步骤: 客户端发送SOCKS5命令:客户端首先向代理服务器发送一个SOCKS5命令,告诉代理服务器其要连接的目标服务器地址和端口号。 代理服务器进行身份验证:代理服务器收到客户端的SOCKS5命令后,会对客户端进行身份验证。如果客户端身份验证不通过,则代理服务器会拒绝建立连接。 代理服务器向目标服务器发送SOCKS5请求:如果客户端身份验证通过,则代理服务器会向目标服务器发送一个SOCKS5请求,告诉目标服务器其要连接的客户端地址和端口号。 目标服务器返回响应:目标服务器收到代理服务器的SOCKS5请求后,会向代理服务器返回一个响应,告诉代理服务器是否允许建立连接。 代理服务器将响应发送给客户端:代理服务器收到目标服务器的响应后,会将其发送给客户端,告诉客户端是否成功建立连接。 在请求阶段中,需要注意以下几点: 客户端必须支持SOCKS5协议,否则无法与代理服务器进行通信。 代理服务器需要对客户端进行身份验证,以防止恶意用户利用代理服务器进行攻击。 代理服务器需要将客户端的真实地址和端口号告诉目标服务器,以便目标服务器能够正确地与客户端进行通信。 目标服务器需要对代理服务器的SOCKS5请求进行响应,以告诉代理服务器是否允许建立连接。 总之,请求阶段是SOCKS5代理通信中非常重要的一步,需要仔细处理。
其中需要使用插件:
package main
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"net"
)
const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04
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)
err := auth(reader, conn)
if err != nil {
log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
return
}
err = connect(reader, conn)
if err != nil {
log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
return
}
}
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
// VER: 协议版本,socks5为0x05
// NMETHODS: 支持认证的方法数量
// METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
// X’00’ NO AUTHENTICATION REQUIRED
// X’02’ USERNAME/PASSWORD
ver, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read ver failed:%w", err)
}
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
methodSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read methodSize failed:%w", err)
}
method := make([]byte, methodSize)
_, err = io.ReadFull(reader, method)
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
func connect(reader *bufio.Reader, conn net.Conn) (err error) {
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER 版本号,socks5的值为0x05
// CMD 0x01表示CONNECT请求
// RSV 保留字段,值为0x00
// ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
// 0x01表示IPv4地址,DST.ADDR为4个字节
// 0x03表示域名,DST.ADDR是一个可变长度的域名
// DST.ADDR 一个可变长度的值
// DST.PORT 目标端口,固定2个字节
buf := make([]byte, 4)
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read header failed:%w", err)
}
ver, cmd, atyp := buf[0], buf[1], buf[3]
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", ver)
}
addr := ""
switch atyp {
case atypIPV4:
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read atyp failed:%w", err)
}
addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
case atypeHOST:
hostSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read hostSize failed:%w", err)
}
host := make([]byte, hostSize)
_, err = io.ReadFull(reader, host)
if err != nil {
return fmt.Errorf("read host failed:%w", err)
}
addr = string(host)
case atypeIPV6:
return errors.New("IPv6: no supported yet")
default:
return errors.New("invalid atyp")
}
_, err = io.ReadFull(reader, buf[:2])
if err != nil {
return fmt.Errorf("read port failed:%w", err)
}
port := binary.BigEndian.Uint16(buf[:2])
dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
if err != nil {
return fmt.Errorf("dial dst failed:%w", err)
}
defer dest.Close()
log.Println("dial", addr, port)
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER socks版本,这里为0x05
// REP Relay field,内容取值如下 X’00’ succeeded
// RSV 保留字段
// ATYPE 地址类型
// BND.ADDR 服务绑定的地址
// BND.PORT 服务绑定的端口DST.PORT
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
return fmt.Errorf("write failed: %w", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
_, _ = io.Copy(dest, reader)
cancel()
}()
go func() {
_, _ = io.Copy(conn, dest)
cancel()
}()
<-ctx.Done()
return nil
}