Go语言上手——基础语言 | 青训营笔记

109 阅读12分钟

  这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记。

  《Go语言上手——基础语言》课程由王克纯老师讲授,根据王老师讲解的课程内容,我总结梳理出了如下笔记内容。

见微知著——课程重点一览

Go语言上手——基础语言.png

步步为营——知识点详细剖析

简介

  Go语言是Google出品的一门通用型计算机编程语言,该语言具有高性能、高并发等8大基本特性,是一门语法简单易上手的编程语言。

Go语言的8大基本特性

  Go语言有如下8大基本特性:

流程图 (3).jpg

  • 高性能、高并发:Go语言是一门具有高性能、高并发特性的编程语言,具有与C++/Java媲美的性能。语言本身内建对高并发的支持,而不是以库的形式进行支持。
  • 语法简单、学习曲线平缓:Go语言的语法简单易懂,其语法风格类似于C语言,并在C语言基础之上进行大幅度简化。学习周期短,上手快。
  • 丰富的标准库:Go语言具有功能完善、质量可靠的标准库。大部分的基础功能的开发使用标准库即可完成,无需三方库的支持。另外,标准库可以提供较高的稳定性和兼容性保障,持续享受语言迭代带来的性能优化。
  • 完善的工具链:Go语言具有丰富且完善的工具链。Go语言提供了针对“编译”、“代码格式化”、“错误检查”、“帮助文档”、“包管理”、“代码补充提示”等的开发工具;还提供了完整的单元测试框架,支持单元测试、性能测试、代码覆盖率、数据渐增检测、性能优化等开发流程。
  • 静态链接:在Go语言中所有的编译结果默认都是静态链接的。在部署运行时,只需要拷贝编译执行之后的唯一的一个可执行文件即可,不需要附加任何其他文件。
  • 快速编译:Go语言具有静态语言中几乎最快的编译速度。
  • 跨平台:Go语言可在常见的操作系统平台上运行,也可以用来开发Android/IOS软件,还具有方便的交叉编译特性。
  • 垃圾回收:Go语言支持垃圾回收,开发时只需专注于业务功能即可,无需考虑内存的分配释放。

哪些公司在使用Go语言

  使用Go语言的公司如下表所示:

流程图 (4).jpg

字节跳动为什么全面拥抱Go语言

  • 公司最初使用Python语言,由于性能问题换成了Go语言
  • C++语言不太适合在线Web业务
  • 早期团队非Java背景
  • Go语言性能比较好
  • Go语言部署简单,学习成本低
  • 公司使用Go语言进行内部RPC和HTTP框架的推广

入门

开发环境

安装Golang

  在Golang官网下载安装包安装即可。相关参考网站如下:

配置集成开发环境

  Golang程序的开发可以选择使用VSCode或Goland集成开发工具。

基于云的开发环境

  利用github账号进行云端示例编码:gitpod.io/#github.com…

基础语法

第一个Go语言程序——Hello World

// 第一个Go语言程序——Hello World

package main

import "fmt"

func main(){
    fmt.Println("Hello World")
}

  上面的代码是利用Go语言编写的Hello World程序。package后声明程序的包名,import后声明程序所需导入的包名,main函数为程序的入口,利用fmt.Println来实现Hello World的打印。

  运行程序可以使用“go run 文件名”来实现运行;如果想要获得二进制文件可以使用“go build 文件名”来实现.exe文件的生成。

变量

  下面的代码中使用到了Go语言中常见的变量类型:

// Go语言常见变量类型

package main

import (
    "fmt"
    "math"
)

func main(){
    var a = "initial"
    var b, c int = 1, 2
    var d = true
    var e float64
    
    f := float32(e)
    g := a + "foo"
    
    fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
    fmt.Println(g) // initialfoo
    
    const s string = "constant"
    const h = 500000000
    const i = 3e20 / h
    
    fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}

  在Go语言中常用的变量基本数据类型有整型、浮点型、字符串类型和布尔类型。在变量声明时,可以采用var关键字进行变量的声明,也可以采用:=来进行赋值声明。

条件分支

  Go语言中的条件分支结构有两种:一种是if-else条件分支结构;另一种是switch条件分支结构。

// Go语言if-else条件分支语句

if 7 % 2 == 0 {
    fmt.Println("7 is even")
} else {
    fmt.Println("7 is odd")
}

  对于if-else条件分支结构来说,在if语句后的判断条件不需要使用括号进行包裹,并且if条件下的代码块必须使用大括号进行包裹。

// Go语言switch条件分支语句

t := time.Now()
switch {
    case t.Hour < 12 :
        fmt.Println("It's before noon")
    default :
        fmt.Println("It's after noon")
}

  对于switch条件分支结构来说,每一个case语句都会单独进行执行,且不需要break语句防止“穿透”。另外,Go语言中的switch条件分支结构更加灵活,每一个case语句可以当作if条件来进行使用。

循环

  在Go语言中只有for循环唯一的一种循环结构。

// Go语言for循环语句

// 死循环
for {
    fmt.Println("loop")
}

// 经典C循环
for i := 1; i <= 10; i++ {
    fmt.Printf("第 %v 轮循环\n", i)
}

// for-range循环
array := [5]int{1, 2, 3, 4, 5}
for key, value := range array {
    fmt.Prinf("第 %v 个数为 %v \n", key, value)
}

  for后什么都不写用来表示死循环,也可以使用经典的C循环。另外,对应于Java语言中的增强for循环,Go语言提供了for-range循环。

数组与切片

  和大多数语言一样,Go语言也提供了数组这样的数据结构,同时提供了一个可变长度的数组——“切片”。

// Go语言数组

var a [5]int
b := [5]int{1, 2, 3, 4, 5}

数组可以通过var来进行声明,也可以通过:=来进行赋值声明。

// Go语言切片

s := make([]int, 3, 5) // 长度为3, 容量为5的一个切片
s[0] = 2
s[1] = 4
s[2] = 12
s = append(s, 15) // [2, 4, 12, 15]

  切片可以通过make语句进行创建,并且可以通过append语句进行添加。每一个切片由“切片长度”、“切片容量”和“一个指向数组的指针”三个部分构成。

字符串

  和大多数语言不同的是,在Go语言中,字符串属于基本数据类型。字符串类型的标准库中提供了很多强大的API:

// Go语言字符串

s := "hello"
fmt.Println(strings.Contains(s, "ll")) // true
fmt.Println(strings.Count(s, "l")) // 2
fmt.Println(strings.Index(s, "ll")) // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(s, 2)) // hellohello
fmt.Println(strings.Replace(s, "e", "E", -1)) // hEllo
  • 判断是否包含字串的contains函数
  • 用于字符串计数的count函数
  • 用于定位字符串的index函数
  • 连接多个字符串的join函数
  • 重复多个字符串的repeat函数
  • 替换字符串的replace函数

Map

  Go语言中的Map与其它语言中的哈希表相同,是实际使用过程中最频繁用到的数据结构。

// Go语言Map

m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m["two"]) // 2
r, ok := m["unknow"] // 0 false
delete(m, "one") // 删除

  创建一个Map可以使用make语句进行创建。Map可以实现查询、添加、删除等功能,查询的第二个返回值表示是否查询到。

函数

  在Go语言中,函数原生支持返回多个值。

// Go语言函数

func add(a int, b int) int {
    return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
    v, ok := m[k]
    return v, ok
}

  在实际的业务逻辑代码里几乎所有的函数都返回两个值,第一个值为真正的返回结果,第二个是错误信息。

指针

  Go语言中支持指针的使用,与C/C++语言中的指针相比,Go语言中的指针更加的简单一些。在Go语言中的指针不支持指针的运算操作,主要的用途是对于函数中传入的参数进行修改。

// Go语言指针

package main

import "fmt"

func add(n int) {
    n += 2
}

func addPtr(n *int){
    n += 2
}

func main(){
    n := 5
    add(n)
    fmt.Println(n) // 5
    addPtr(&n)
    fmt.Println(n) // 7
}

  和大多数语言一样,Go语言中的函数是值传递,在调用函数时传递的参数会被复制一份传递到函数中。如果想要修改传进的变量的值,则需要传递变量的地址,即“指针”。

结构体

  结构体是带类型的字段的集合,他与数组相对。数组是一组相同数据类型的数据构成的集合,而结构体则是一组数据类型不同的数据构成的集合。Go语言中的结构体可以类比于Java语言中的类的概念。

// Go语言结构体

type user struct {
    name string
    password string
}

func checkPassword(u user, password string) bool {
    return u.password == password
}

func checkPassword2(u *user, password string) bool {
    return u.password == password
}

  在函数中传递结构体的方式有两种,一种是传递结构体本身,这种方式对结构体的操作是只读的,无法修改结构体的值;另一种是传递结构体的指针,这种方式支持对结构体的修改。

// Go语言结构体方法

type user struct {
    name string
    password string
}

func (u user) checkPassword(password string) bool {
    return u.password == password
}

func (u *user) checkPassword2(password string) bool {
    return u.password == password
}

  在Go语言中,可以为结构体定义一些方法,这些方法被称为结构体方法。结构体方法类似于其他语言中类成员函数的概念。我们可以将参数列表中的结构体提取到函数名的前面来做结构体方法的改造。

标准库

  Go语言中提供了很多标准库,标准库中具有很多强大的API供开发人员使用,下面介绍几个常用的Go语言标准库。

字符串处理

  Go语言中针对字符串的处理提供了"string"包,该包下提供了contains、count、index、join、repeat、replace等函数。由于上文中提到,因此不再赘述。

错误处理

  Go语言中针对错误处理提供了"errors"包。

// Go语言错误处理

func findUser(users user[], name string) (v *user, err error) {
    for _, u := range users {
        if u.name == name {
            return &u, nil
        }
    }
    return nil, errors.New("not found")
}

  Go语言的语言习惯是使用一个单独的返回值来传递错误信息,我们只需在可能会发生错误的函数返回值中添加error返回值即可实现后续的错误处理。

时间处理

  Go语言中针对时间处理提供了"time"包。

// Go语言时间处理

now := time.Now()
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
fmt.Println(t.Year(),t.Month(),t.Day(),t.Hour(),t.Minute(),t.Second())
fmt.Println(t.Format("2006-01-02 15:04:05"))

  对于时间的操作无非就是“获取当前时间”,“指定某一时间”,“获取年-月-日 时:分:秒”,以及“时间格式化”操作。相应地,Go语言中都有对应的API供开发人员使用:

  • 获取当前时间:可以使用time.Now()来获取当前时间
  • 指定某一时间:可以使用time.Date()来构造一个带时区的时间
  • 获取年-月-日 时:分:秒 :可以使用t.Year(),t.Month(),t.Day(),t.Hour(),t.Minute(),t.Second()分别获取
  • 时间格式化:Go语言的时间格式化操作需要记住Go语言诞生的时间:2006-01-02 15:04:05(06年的1月2号 下午3点4分5秒 - "612345"),根据这个时间可以任意指定所需的格式化形式。

数字解析

  所谓的数字解析就是数字与字符串间的互相转换。Go语言提供了"strconv"包来实现这一功能。strconv实际上是string和convert两个单词的缩写,那么用它来表示字符串和数字间的转换就再合适不过了。

// Go语言数字解析

f, _ := strconv.ParseFloat("1.234", 64)
n, _ := strconv.ParseInt("111", 10, 64)
s1, _ := strconv.Atoi("123")
s2, err := strconv.Atoi("AAA")

  strconv包提供ParseInt、ParseFloat函数实现字符串转数字的操作,提供Atoi函数实现数字转字符串的操作。

JSON处理

  在项目开发中,尤其是Web开发中,JSON极大地方便了数据的传递处理。在Go语言中,同样提供了针对JSON操作的标准库,即"encoding/json"包。

// Go语言JSON处理

a := userInfo{Name : "wang", Age : 18, Hobby : []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)

var b userInfo
err = json.Unmarshal(buf, &b)

  该包可以实现对结构体的序列化,也能实现反序列化为结构体的操作。

进程信息

  最后是关于进程信息的库函数,"os"和"os/exec"包。通过这两个包,Go语言为我们提供了获取命令行参数的功能。

// Go语言进程信息

fmt.Println(os.Args)
fmt.Println(os.Getenv("PATH"))
fmt.Println(os.Setenv("AA", "BB"))

buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()

实战

实战一:猜谜游戏

  • 实战简介:编写一个由系统生成随机数,用户猜测数字的小程序。

  • 实战步骤分解:

    • 生成随机数
    • 读取用户输入
    • 实现判断逻辑
    • 实现猜谜的循环
  • 实战成果展示:

  • 实战代码:
// 实战一:猜谜游戏

package main

import (
    "bufio"
    "fmt"
    "math/rand"
    "os"
    "strconv"
    "strings"
    "time"
)

func main() {
    maxNumber := 100
    rand.Seed(time.Now().UnixNano())
    secretNumber := rand.Intn(maxNumber)
    
    for {
        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 input your guess. Please try again.")
            continue
        }

        //input = strings.TrimSuffix(input, "\r\n") // windows
        input = strings.TrimSuffix(input, "\n")
        
        guess, err := strconv.Atoi(input)
        
        if err != nil {
            fmt.Println("Invalid input! Please try again.")
            continue
        }

        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查询单词的翻译,并在本地打印出结果。

  • 实战步骤分解:

    • 在线词典抓包

    • 在线词典代码生成

      • 生成代码解读(创建请求 >> 设置请求头 >> 发起请求 >> 读取响应)
      • 生成请求体
      • 解析响应体
    • 在线词典结果打印

    • 代码完善

  • 实战成果展示:

  • 实战代码:
// 实战二:命令行词典

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

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("Accept", "application/json, text/plain, */*")
    req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
    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/101.0.4951.54 Safari/537.36")
    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="101", "Google Chrome";v="101"`)
    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()
    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)
    }
}

实战三:SOCKS5代理

  • 实战简介:完成一个SOCKS5代理服务器。

  • SOCKS5原理介绍:

    • SOCKS5简介:

  SOCKS5协议是一个代理协议,由于该协议是明文传输,因此不能用来翻墙。它的主要用途是让授权用户通过单个端口访问企业内网的内部资源。

  • SOCKS5工作原理:

  正常访问一个网站的流程是:先和对方网站建立TCP连接 >> 然后三次握手 >> 发起HTTP请求 >> 等待HTTP响应。

  如果设置了代理服务器,则浏览器会先和SOCKS5代理建立连接,代理再和服务器建立连接。整个过程分为四个阶段:握手阶段、认证阶段、请求阶段和relay阶段。

流程图 (5).jpg

  • 握手阶段:浏览器会向SOCKS5代理发送请求,包的内容包含一个协议的版本号和支持的认证种类。
  • 认证阶段:SOCKS5会选择一个认证方式返回给浏览器。若返回00则无需认证;返回其他类型则开启相应认证流程。
  • 请求阶段:认证通过后,浏览器会向SOCKS5发起请求,传输对应的版本号、请求类型(主要为Connection),代表代理服务器要和某个域名或IP地址的某个端口建立TCP连接。代理服务器收到响应后会和真正的服务器建立连接,并返回一个响应。
  • relay阶段:浏览器发送正常请求,代理服务器接收请求并转发给真正的服务器。真正的服务器响应后,代理服务器将响应信息转发回浏览器。
  • 实战步骤分解:

    • 编写TCP echo server
    • 实现认证阶段
    • 实现请求阶段
    • 实现relay阶段
  • 实战成果展示:

    • 编写TCP echo server

  • 实现认证阶段

    • 客户端

  • 服务端

  • 实现请求阶段

    • 客户端

  • 服务端

  • 实现relay阶段

    • 客户端

  • 服务端

  • 实战代码:
// 实战三:SOCKS5代理

package main

import (
    "bufio"
    "context"
    "encoding/binary"
    "errors"
    "fmt"
    "io"
    "log"
    "net"
)

const *socks5Ver* = 0x05
const *cmdBind* = 0x01
const *atypeIPV4* = 0x01
const *atypeHOST* = 0x03
const *atypeIPV6* = 0x04

func main() {
    server, err := net.Listen("tcp", "192.168.138.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, err := reader.ReadByte()
    if err != nil {
        return fmt.Errorf("read ver failed:%w", err)
    }
    if ver != *socks5Ver* {
        return fmt.Errorf("not supported ver:%v", err)
    }
    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)
    }
    log.Println("ver", ver, "method", method)
    
    _, 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) {
    buf := make([]byte, 4)
    _, err = io.ReadFull(reader, buf)
    if err != nil {
        return fmt.Errorf("read header failed:%w", err)
    }
    ver, cmd, atype := buf[0], buf[1], buf[3]
    if ver != *socks5Ver* {
        return fmt.Errorf("not supported ver:%v", err)
    }
    if cmd != *cmdBind* {
        return fmt.Errorf("not support cmd:%w", err)
    }
    addr := ""
    switch atype {
    case *atypeIPV4*:
        _, err = io.ReadFull(reader, buf)
        if err != nil {
            return fmt.Errorf("read atype 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 headSize 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: not support yet")
    default:
        return errors.New("invalid atype")
    }
    _, 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)
    
    _, 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(dest, reader)
        cancel()
    }()
    
    <-ctx.Done()
    return nil
}

小试牛刀——课后实践

问题描述

  1. 修改第一个例子猜谜游戏里面的最终代码,使用 fmt.Scanf 来简化代码实现
  1. 修改第二个例子命令行词典里面的最终代码,增加另一种翻译引擎的支持
  1. 在上一步骤的基础上,修改代码实现并行请求两个翻译引擎来提高响应速度

实践成果

问题 1 解答:

  读取用户输入的方式更改为fmt.Scanf()函数即可。

package main

import (
   "fmt"
   "math/rand"
   "time"
)

func main() {
   maxNum := 100
   rand.Seed(time.Now().UnixNano())
   secretNum := rand.Intn(maxNum)

   for {
      fmt.Println("Please input your guess")
      var guess int
      _, err := fmt.Scanf("%d\n", &guess)
      if err != nil {
         fmt.Println("An error occured while input your guess. Please try again.")
         continue
      }
      if guess > secretNum {
         fmt.Println("Your guess is bigger than the secret number. Please try again.")
      } else if guess < secretNum {
         fmt.Println("Your guess is smaller than the secret number. Please try again.")
      } else {
         fmt.Println("Correct, you Legend!")
         break
      }
   }
}
  • 运行效果:

image.png

问题 2 解答:

  在火山翻译官网进行抓包,获取火山翻译对外暴露的API接口,利用curl在 curlconverter.com/#go 网站获取请求框架代码。

client := &http.Client{}
request := DictResquest{Text: queryWord, Language: "en"}
buf, err := json.Marshal(request)
if err != nil {
   log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://translate.volcengine.com/web/dict/match/v1/?msToken=&X-Bogus=DFSzswVLQDc7EiQrSWOwhKXAIQ-z&_signature=_02B4Z6wo00001G5yNMQAAIDBDXj0r1F3FuhucjBAAHnq65SNGmQvrjg9h9dB1sm.wQMFEDHqy1jGXupUU1s5mIWCZ0hM3Zx9vi1PpMKWfPXlXV8ZwbfhiOLfyEjG7gu80q4b14rshro4kPfRd9", data)
if err != nil {
   log.Fatal(err)
}
req.Header.Set("authority", "translate.volcengine.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("content-type", "application/json")
req.Header.Set("cookie", "x-jupiter-uuid=16524467489791958; i18next=zh-CN; s_v_web_id=verify_520cdfb9fe88041f23346d60bd5ae8ed; _tea_utm_cache_2018=undefined; ttcid=b558367f6df942cdb7e9395a76d8226f11; tt_scid=FHRg4V8.3uNvj9Uxnb3ybQ1Svak-ng0CrABDyyN-oLexvwZoSHGjP1QhQg13qSLC7328")
req.Header.Set("origin", "https://translate.volcengine.com")
req.Header.Set("referer", "https://translate.volcengine.com/translate?category=&home_language=zh&source_language=en&target_language=zh&text=good")
req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"`)
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", "same-origin")
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36")
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)
}

  将响应的json数据复制到 oktools.net/json2go 官网上,获得响应结构体代码。

type DictResquest struct {
   Language string `json:"language"`
   Text     string `json:"text"`
}

type DictResponse struct {
   Words []struct {
      Source  int    `json:"source"`
      Text    string `json:"text"`
      PosList []struct {
         Type      int `json:"type"`
         Phonetics []struct {
            Type int    `json:"type"`
            Text string `json:"text"`
         } `json:"phonetics"`
         Explanations []struct {
            Text     string `json:"text"`
            Examples []struct {
               Type      int `json:"type"`
               Sentences []struct {
                  Text      string `json:"text"`
                  TransText string `json:"trans_text"`
               } `json:"sentences"`
            } `json:"examples"`
            Synonyms []interface{} `json:"synonyms"`
         } `json:"explanations"`
         Relevancys []interface{} `json:"relevancys"`
      } `json:"pos_list"`
   } `json:"words"`
   Phrases  []interface{} `json:"phrases"`
   BaseResp struct {
      StatusCode    int    `json:"status_code"`
      StatusMessage string `json:"status_message"`
   } `json:"base_resp"`
}

  根据响应的结构体的具体结构,处理最终的输出操作。

for _, word := range dictResponse.Words {
   fmt.Println(queryWord, "UK: [", word.PosList[0].Phonetics[0].Text, "] US: [", word.PosList[0].Phonetics[1].Text, "]")
   for _, item := range word.PosList {
      for _, explanation := range item.Explanations {
         fmt.Println(explanation.Text)
         for _, example := range explanation.Examples {
            for _, sentence := range example.Sentences {
               fmt.Println(sentence.Text, sentence.TransText)
            }
         }
      }
   }
}
  • 运行效果:

image.png

问题 3 解答:

  针对本问题,采用了同包和分包两种方式进行编写。

image.png

  将问题2和课堂练习中的代码分别保存在caiyun.go和volcengine.go两个文件中。在main.go文件中,利用goroutine和waitgroup分别调用两个查询引擎的查询方法,最终将输出打印在控制台上。

func main() {
   if len(os.Args) != 2 {
      fmt.Fprintf(os.Stderr, `usage: simpleDict WORD 
example: simpleDict hello
      `)
      os.Exit(1)
   }
   var wg sync.WaitGroup
   queryWord := os.Args[1]
   wg.Add(2)
   go func() {
      defer wg.Done()
      //CaiyunQuery(queryWord)
      caiyun.Query(queryWord)
   }()
   go func() {
      defer wg.Done()
      //VolcengineQuery(queryWord)
      volcengine.Query(queryWord)
   }()
   wg.Wait()
}
  • 运行效果:

    • 同包下运行:

    image.png

    • 分包下运行:

    image.png

温故知新——总结与感悟

  《Go语言上手——基础语言》课程是我在「第三届青训营 -后端场」中学习的第一课,这门课上老师主要介绍了Go语言相关的一些常用的语法规则和使用中的注意事项。在课堂中,老师带着我们进行了“猜数字”、“命令行词典”和“SOCKS5”三个小型项目的实践,让我们从零基础学起,感受到了Go语言编程的简洁特点和其独特的编码风格。

  其中,让我印象最深刻的是“命令行词典”项目的实践。在实践中,我学习到了如何进行抓包获取API接口,如何通过使用好用的工具来生成请求代码框架来提升工程开发的效率,整个项目跟着老师实践下来可谓是收获满满。

  在课后,通过自己独自动手实现,巩固了对Go语言编程的使用。在实现的过程中也学会了如何在运行同包下的项目和分包下的项目。能够顺利完成作业,也给了我极大的鼓励和信心。

参考链接