这是我参与「第五届青训营」伴学笔记的第1天
阅前提示: 由于该篇笔记为后期整理,所以里面有一些第一节课讲到了的基础语法并不一定会在该笔记中,但是可能会出现在后续笔记~
一、go语言基础语法
1.1 变量与数据类型
1.1.1 变量
//变量的四种声明方式:
//1.指定变量类型并赋值
var a int = 2
//2.指定变量类型但不赋值
var b int
//3.不指定变量类型,根据‘=’后面的值判定变量的类型(自动类型推断,和python有点相似)
var c = 3
//4.省略var,使用:=(这种赋值方式和Pascal语言有点相似)
d := 4
注:go语言还支持一次性声明多个变量(多变量声明)
var n1,n2,n3 int
var n4,name,n5 = 10,"jack",7.8
n6,height := 6.9,100.6
1.1.2 常量
常量和变量一样,将var改成const即可
1.1.3 数据类型
1.2 流程控制
1.2.1 分支
1.2.1.1 if else
基本语法:
if 条件表达式1 {
...
} else if 条件表达式2 {
...
}else { ... }
注: 1.Golang里面,{}是必须有的,只有一行代码也是如此 2.条件表达式外的()可以不写
1.2.1.2 switch
基本语法:
switch 表达式 {
case 值1,值2,.….:
语句块1
case 值3,值4,...:
语句块2
....
default:
语句块3
}
注: 1.switch后是一个表达式(常量值、变量、一个有返回值的函数等都可以) 2.表达式要求: 常量值:不能重复 变量:数据类型必须要switch的表达式数据类型一致 多个值:使用逗号间隔 3.case后面不需要break 4.switch穿透:添加fallthrough
1.2.2 循环
基本语法:
for 初始表达式; 布尔表达式; 迭代因子 {
循环体
}
键值循环:
for range结构是Go语言特有的一种的迭代结构,在许多情况下都非常有用,for range 可以遍历数组、切片、字符串、map 及通道,for range 语法上类似于其它语言中的 foreach 语句,一般形式为:
for key, val := range arr {
...
}
其中,key是arr中的下标,val是arr中下标对应的值
1.3 函数
基本语法:
func 函数名(形参列表)(返回值类型列表){
执行语句..
return + 返回值列表
}
例子:
//自定义函数:功能:两个数相加:
func cal (num1 int,num2 int) (int) { //如果返回值类型就一个的话,那么()是可以省略不写的
var sum int = 0
sum += num1
sum += num2
return sum
}
func main(){
//功能:10 + 20
//调用函数:
sum := cal(10,20)
}
注: 1.首字母大写该函数可以被本包文件和其它包文件使用(类似public) 2.首学母小写只能被本包文件使用,其它包文件不能使用(类似private) 3.如果有返回值不想接受,可以使用_忽略 4.函数不支持重载 5.支持可变参数,如下:
package main
import "fmt"
//定义一个函数,函数的参数为:可变参数 ... 参数的数量可变
//args...int 可以传入任意多个数量的int类型的数据 传入0个,1个,,,,n个
func test (args...int){
//函数内部处理可变参数的时候,将可变参数当做切片来处理
//遍历可变参数:
for i := 0; i < len(args); i++ {
fmt.Println(args[i])
}
}
func main(){
test()
test(3)
test(37,58,39,59,47)
}
6.传入变量的地址&,该变量的值如果在函数内修改,函数外也会修改,否则该变量的值仅在函数内修改,而函数外不会改变。如下:
package main
import "fmt"
//自定义函数:功能:交换两个数
func exchangeNum (num1 int,num2 int){
var t int
t = num1
num1 = num2
num2 = t
}
func realExchangeNum (num1 *int,num2 *int){
var t int
t = *num1
*num1 = *num2
*num2 = t
}
func main(){
//调用函数:交换10和20
var num1 int = 10
var num2 int = 20
fmt.Printf("交换前的两个数: num1 = %v,num2 = %v \n",num1,num2)//10 20
exchangeNum(num1,num2)
fmt.Printf("交换1后的两个数: num1 = %v,num2 = %v \n",num1,num2)//10 20
realExchangeNum(&num1,&num2)
fmt.Printf("交换2后的两个数: num1 = %v,num2 = %v \n",num1,num2)//20 10
}
7.函数也是一种数据类型,可以赋值给一个变量,通过该变量可以对函数调用 8.Golang支持函数作为形参被调用
package main
import "fmt"
//定义一个函数:
func test(num int){
fmt.Println(num)
}
//定义一个函数,把另一个函数作为形参:
func test02 (num1 int ,num2 float32, testFunc func(int)){
fmt.Println("-----test02")
}
func main(){
//函数也是一种数据类型,可以赋值给一个变量
a := test//变量就是一个函数类型的变量
fmt.Printf("a的类型是:%T,test函数的类型是:%T \n",a,test)//a的类型是:func(int),test函数的类型是:func(int)
//通过该变量可以对函数调用
a(10) //等价于 test(10)
//调用test02函数:
test02(10,3.19,test)
test02(10,3.19,a)
}
1.4 数组和切片
1.4.1 数组
注: 1.go里面数组是指指定长度大小的数组。不像C++里面,不指定长度大小也是数组。 2.数组在go里面,大小不一样,它的类型是不一样的。 3.数组在函数传参时采用的是传值拷贝。因此函数内部无法改变外部的数组大小。
基本语法:
var 数组名 [数组大小]数据类型
声明方式:
package main
import "fmt"
func main(){
//第一种:
var arr1 [3]int = [3]int{3,6,9}
fmt.Println(arr1)
//第二种:
var arr2 = [3]int{1,4,7}
fmt.Println(arr2)
//第三种:
var arr3 = [...]int{4,5,6,7}
fmt.Println(arr3)
//第四种: ':'前面的数字代表的是它们在数组中的下标
var arr4 = [...]int{2:66,0:33,1:99,3:88}
fmt.Println(arr4)
}
1.4.2 切片
注: 1.slice是切片,定义类似数组,但是定义的时候不带上固定大小 2.切片一定不加大小! 3.切片在函数传参时采用的是引用。因此函数内部可以改变外部的切片大小。
基本语法:
var 数组名 []数据类型
声明方式:
//第一种声明方式
slice1 := []int{1, 2, 3}
fmt.Printf("len = %d, slice = %v\n", len(slice1), slice1)
//第二种声明方式,没有分配空间
var slice2 []int
slice2 = make([]int, 3) //make可以为slice2分配空间,默认值为0
fmt.Printf("len = %d, slice = %v\n", len(slice2), slice2)
//第三种声明方式,直接用make
var slice3 = make([]int, 3)
fmt.Printf("len= %d, slice = %v\n", len(slice3), slice3)
//第四种:= + make,一般用这一种
slice4 := make([]int, 3)
fmt.Printf("len= %d, slice = %v\n", len(slice4), slice4)
1.4.2.1 追加和截取
1.4.2.1.1 追加
追加这里类似vector
a := make([]int, 1)
a[0] = 2
a = append(a, 3)
fmt.Println(a)// [2 3]
1.4.2.1.2 截取
a := make([]int, 1)
a[0] = 2
a = append(a, 3)
fmt.Println(a)// [2 3]
//左闭右开
t := a[0:1]
t[0] = 10
fmt.Println(a)// [10 3]
注:t和a是同一个切片。t只是a的引用而已。它们两个底层的数组是一样的。如果只是想拷贝slice里面的元素到新的slice,使用copy函数(copy(t,a),把a的值拷贝给t)
1.5 映射(map)
基本语法:
var 变量名 map[key类型]value类型
注: 1.key、value的类型:bool、数字、string、指针、channel 、还可以是只包含前面几个类型的 接口、结构体、数组 2.key:通常为int 、string类型;slice、map、function不可以 3.value:通常为数字(整数、浮点数)、string、map、结构体 4.map在函数传参时采用的是引用。因此函数内部可以改变外部的map。
声明方式:
第一种声明,这个map是空的
var mymap1 map[string]string
if mymap1 == nil {
fmt.Println("mymap1 is empty")
}
第二种声明,声明+make开辟空间
var mymap2 = make(map[string]string, 10)
mymap2["c++"] = "1"
mymap2["java"] = "2"
第三种:= + make
mymap3 := make(map[string]string) //make会自己给map一点空间的
mymap3["c++"] = "1"
mymap3["java"] = "2"
第四种:= + 大括号初始化定义
mymap4 := map[string]string{"c++": "one", "java": "two"}
fmt.Println(mymap4)`
1.5.1 删除
删除关键字,使用delete
delete(mymap4, "c++")
二、实战
2.1 猜谜游戏
2.1.1 原代码
-
生成随机数作为被猜的数字
代码如下:
package main import ( "fmt" "math/rand" ) func main() { maxNum := 100 secretNumber := rand.Intn(maxNum) fmt.Println("The secret number is :", secretNumber) }Q1.每一次运行程序,都只会生成同一个数字
原因:未设置初始的随机数种子,此时程序里面的随机数种子序列每一次都是固定的,所以会造成每一次重新启动程序,都只会生成同一个数字
解决方案:用时间戳初始化随机数种子
rand.Seed(time.Now().UnixNano()) -
读取用户输入
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) //读取一行输入 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 } } } -
最终效果:
2.2 在线词典
2.2.1 原代码
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 []interface{} `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, UserID: "63cd42721120a60014fbd804"}
//结构体转为json
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("authority", "api.interpreter.caiyunai.com")
req.Header.Set("pragma", "no-cache")
req.Header.Set("cache-control", "no-cache")
req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="8"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 SLBrowser/8.0.0.12022 SLBChan/25")
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("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")
//发送请求
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
//json转化成结构体
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() {
var word string
fmt.Scanf("%s", &word)
query(word)
}
有关在线词典的实现实际上是数据的爬取和清洗
前面设置请求头的地方实则是将程序伪装成一个浏览器,然后再向服务器发送请求(不然服务器会发现你是个爬虫,从而获取不到数据)
而后面将获取到的json数据转为结构体,则是为下面清洗数据做准备,方便我们找到需要的数据所在的位置
2.3 socks5代理
- TCP echo server
实现一个简版的TCP echo server,用来测试我们的server是否正确,该程序仅能做到我们发送什么,它就回复什么
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.Println("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
}
}
}
注:在运行完该代码以后需要使用nc命令,该命令需要下载
后面的步骤基于简版的TCP echo server框架进行
-
auth
基于TCP的客户端连接过程
-
客户端向代理服务器发送代理请求,其中包含了代理的版本和认证方式:
+----+----------+----------+ |VER | NMETHODS | METHODS | +----+----------+----------+ | 1 | 1 | 1 to 255 | +----+----------+----------+如果是socks5代理,第一个字段VER的值是0x05,表明是socks代理的第5个版本。
第二个字段NMETHODS表示支持的认证方式,第三个字段是一个数组,包含了支持的认证方式列表:
- 0x00: 不需要认证
- 0x01: GSSAPI认证
- 0x02: 用户名和密码方式认证
- 0x03: IANA认证
- 0x80-0xfe: 保留的认证方式
- 0xff: 不支持任何认证方式
-
服务端收到客户端的代理请求后,选择双方都支持的加密方式回复给客户端:
+----+--------+ |VER | METHOD | +----+--------+ | 1 | 1 | +----+--------+此时客户端收到服务端的响应请求后,双方握手完成,开始进行协议交互。
上述过程代码如下:
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 }与此同时,还需要修改process函数
func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) err := auth(reader, conn) if err != nil { log.Println("client %v auth failed:%v", conn.RemoteAddr(), err) return } log.Println("auto success") } -
-
请求阶段
-
请求:
握手完成后,客户端要把需要执行的操作指令发给服务端,表明自己要执行代理的请求。请求帧格式:
+----+-----+-------+------+----------+----------+ |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+各字段含义:
-
VER: 代理版本信息
-
CMD: 代理指令
- 0x01: connect指令,tcp代理时使用。
- 0x02: bind,很少使用,类似FTP协议中主动连接场景,服务端后服务端会主动连接到客户端。
- 0x03: udp代理时使用。
-
RSV: 保留字段
-
ATYP: 地址类型
- 0x01: IPv4地址类型
- 0x03: unix域socket类型代理
- 0x04: IPv6地址类型
-
DST.ADDR: 需要连接的目的地址
-
DST.PORT: 需要连接的目的端口
-
-
响应:
客户端发完上面的请求连接后,服务端会发起连接到DST.ADDR:DST.PORT,然后返回响应到客户端,响应格式:
+----+-----+-------+------+----------+----------+ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ 12345其中VER/RSV/ATYP的含义和上面相同,其他字段的意思:
-
REP: 请求响应
- 0x00: 成功
- 0x01-0x08: 失败
- 0x09-0xff: 未使用
-
BND.ADDR: 连接到的远程地址
-
BND.PORT: 连接到的远程端口
-
上述过程代码如下:
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]) 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) } return nil }与此同时,修改process函数
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 } } -
-
relay阶段
在请求阶段中的connect函数中第57行添加如下代码:
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()第75行添加如下代码:
ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { _, _ = io.Copy(dest, reader) cancel() }() go func() { _, _ = io.Copy(conn, dest) cancel() }() <-ctx.Done()
三、作业
3.1 修改第一个例子猜谜游戏里面的最终代码,使用fmt.Scanf来简化代码实现
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)
fmt.Println("Please input your guess")
var guess int
for {
fmt.Scanf("%d ", &guess)
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
}
}
}
Q1. 第二次走到fmt.Scanf时,程序跳过了输入
原因:
所以直接使用fmt.Scanf("%d", &guess)会返回错误信息,而这也导致了第二次走到fmt.Scanf时,程序会跳过输入
解决:
fmt.Scanf("%d ", &guess)
//或者
fmt.Scanf("%d\n", &guess)
3.2 修改第二个例子命令行词典里面的最终代码,增加另一种翻译引擎的支持
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 []interface{} `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"`
}
//沪江小D
type DDictRes struct {
Data struct {
Content string `json:"content"`
FromLang string `json:"fromLang"`
OperationDate string `json:"operationDate"`
OriginalText string `json:"original_text"`
Pronounce struct {
Audio []struct {
Length int `json:"length"`
OriginURL string `json:"originUrl"`
Start int `json:"start"`
URL string `json:"url"`
} `json:"audio"`
AudioOriginStatus int `json:"audioOriginStatus"`
AudioStatus int `json:"audioStatus"`
} `json:"pronounce"`
Source string `json:"source"`
ToLang string `json:"toLang"`
} `json:"data"`
Message string `json:"message"`
Status int `json:"status"`
Time string `json:"time"`
}
//设置请求头
func getRequestHead(interType string, req *http.Request) {
switch interType {
case "Color":
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 SLBrowser/8.0.0.12022 SLBChan/25")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("os-type", "web")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
case "D":
req.Header.Set("accept", "*/*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("content-type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Set("cookie", "HJ_UID=635ed610-2a74-89b3-6c2b-4ea12664ef04; HJ_CST=1; HJ_CSST_3=1; TRACKSITEMAP=3; _REF=https%3A%2F%2Fwww.google.com.hk%2F; _SREF_3=https%3A%2F%2Fwww.google.com.hk%2F; HJ_SID=i2xho7-efa7-44e1-b5db-18f0944def46; HJ_SSID_3=i2xho7-3b27-4710-995c-edc285bcd33d; _SREG_3=www.google.com.hk%7C%7Csearch%7Cdomain; _REG=www.google.com.hk%7C%7Csearch%7Cdomain")
req.Header.Set("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")
req.Header.Set("x-requested-with", "XMLHttpRequest")
}
}
//打印翻译结果
func print(interType string, bodyText []byte, word string) {
switch interType {
case "Color":
var dictResponse DictResponse
//json转化成结构体
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)
}
case "D":
var dDictRes DDictRes
//json转化成结构体
err := json.Unmarshal(bodyText, &dDictRes)
if err != nil {
log.Fatal(err)
}
fmt.Println(word, "\n", dDictRes.Data.Content)
}
}
func query(word string, interpretType string) {
client := &http.Client{}
var data *bytes.Reader
var url string
if interpretType == "Color" {
request := DictRequest{TransType: "en2zh", Source: word, UserID: "63cd42721120a60014fbd804"}
//结构体转为json
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
data = bytes.NewReader(buf)
url = "https://api.interpreter.caiyunai.com/v1/dict"
} else {
data = bytes.NewReader([]byte(`content=` + word))
url = "https://dict.hujiang.com/v10/dict/translation/en/cn"
}
//设置请求
req, err := http.NewRequest("POST", url, data)
if err != nil {
log.Fatal(err)
}
//设置请求头
getRequestHead(interpretType, req)
//发送请求
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))
}
//打印翻译结果
print(interpretType, bodyText, word)
}
func main() {
var word string
var interType string
fmt.Println("please input the interpretType and the word need to interpret:")
fmt.Scanf("%s %s", &interType, &word)
query(word, interType)
}
3.3 在上一步骤的基础上,修改代码实现并行请求两个翻译引擎来提高响应速度
仅需在3.2的代码基础上,修改main函数即可,代码如下:
Q1.如上我们可以发现,它并没有起到并发的作用,甚至都没有进行翻译器的功能
原因:主线程main比协程执行得快,主线程main执行完毕退出以后,协程即使还没有执行完毕也会退出,所以翻译器这部分的功能所在的协程未执行完毕即退出,导致翻译功能未实现
解决方法:使用WaitGroup控制协程退出,代码如下:
func main() {
var word string
fmt.Println("please input the interpretType and the word need to interpret:")
fmt.Scanf("%s", &word)
for i := 0; i < 2; i++ {
wg.Add(1)
go func(n int) {
if n == 0 {
query(word, "D")
} else {
query(word, "Color")
}
wg.Done()
}(i)
}
wg.Wait()
fmt.Println("the main has finished!\n")
}