go的一些基本语法和案例

25 阅读17分钟

一. 简介

什么是 Go 语言?它的特点是啥

  1. 高性能、高并发
  2. 语法简单、学习曲线平缓
  3. 丰富的标准库
  4. 完善的工具链
  5. 静态链接
  6. 快速编译
  7. 跨平台
  8. 垃圾回收

字节为什么会全面使用 Go?

  1. 最初使用的是 Python,但是随着业务体量的增长,python 具有一些性能的问题,所以换成了 Go
  2. C++不适合在线 Web 业务
  3. 早起的团队不是 Java 背景
  4. Go 的性能比较好
  5. 部署简单、学习成本低、开发效率高,也解决了 Python 带来的很头疼的依赖库版本问题
  6. 内部 RPC 和 HTTP 框架的推广

二. 入门

基本语法-变量

Go 是一门强类型的语言,每一个变量都有自己的变量类型(和 java 类似),常见的变量类型包括 字符串、整数、浮点数、布尔值等。

Go 语言的字符串是内置类型,可以直接使用加号("+")拼接,也可以直接使用等号去比较两个字符串。

在 Go 语言中,大部分运算符的使用和优先级会和其它语言类似。

在 Go 中变量的声明有两种方式,一种是通过 var name string = "" 这种方式来进行声明,声明变量的时候一般会自动去推导变量的类型。如何需要,也可以显式写出变量类型,另一种方式是使用冒号(":"), a := 10。

如果要声明常量,只需要将 var 改成 const 即可,go 中的常量没有确定的类型,会根据使用的上下文来自动确定类型。

package main
import (
    "fmt"
    "math"
)

func main() {

    // 声明一个字符串变量
    var a = "initial"

    // 同时声明两个整型
    var b, c int = 1, 2

    var d = true

    // go中没有double, 高精度浮点数为float64,低精度为float32
    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 = 5000000
    const i = 3e20 / h
    
}

基本语法-if else

Go 中的 if else 与 java 类似,但是不同的是 if 后面没有括号,如果写了括号,编译器会自动给你去掉。此外,go 语言在 if 后面必须要跟有大括号,不能像 java 一样,如果只有单个语句可以不需要大括号。

package main
import "fmt"
func main(){
    if 7 % 2 == 0 {
        fmt.Println("7 is even")
    }else{
        fmt.Pringln("7 i sodd")
    }

    if num := 9; num < 0 {
        fmt.Println(num, "is negetive")
    }else if num < 10 {
        fmt.Println(num, "has 1 digit")
    }else {
        fmt.Println(num, "has multiple digits")
    }
}

基本语法-循环

在 go 中没有 while、do-while 循环,只有唯一的一个 for 循环。最简单的 for 循环就是 for 后面什么都不写,代表一个死循环。和 if 一样,for 循环在判断条件处不需要写括号。循环中途可以使用 break 跳出,也可以使用 continue 来继续循环。

package main
import "fmt"
func main(){
    i := 1
    for {
        fmt.Println("lool")
        break;
    }
    for j := 7;j < 9;j ++ {
        fmt.Println(j)
    }
    for n:= 0; n < 5; n++ {
        if n % 2 == 0 {
            continue
        }
        fmt.Println(n)
    }
    for i <= 3 {
        fmt.Println(i)
        i = i + 1
    }
}

基本语法-switch

与其它的一样,go 中的 switch 后面的变量名不需要括号。此外,switch 中的每一个 case 都不需要添加 break,它不会继续往下跑完所有的 case。此外,go 的 switch 功能更加强大,可以使用任意的变量类型,甚至可以用来取代任意的 if else 语句。也可以在 switch 后面不加任何变量,然后在 case 里面写条件分支,这样的代码相比使用多个 if else 代码,在逻辑上会更加清晰

package main
import (
    "fmt"
    "time"
)

func main(

    a := 2
    switch a {
    case 1:
        fmt.Println("one")
    case 2:
        fmt.Println("two")
    case 3:
        fmt.Println("three")
    default:
        fmt.Println("other")
    }

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

基本语法-数组

数组就是一个具有编号且长度固定的元素序列。比如下面示例的代码。对于一个数组,我们可以很方便地去除特定索引地值或者往特定索引取存储值,然后也能够直接打印一个数组。但是,在实际的业务代码中,我们会很少直接使用数组,因为它的长度是固定的,我们使用的更多的是切片。

var a [5]int
a[4] = 100
fmt.Println(a[4], len(a))

b := [5]int{1,2,3,4,5}
fmt.Println(b)

var twoD [2][3]int
for i := 0;i < 2;i++ {
    for j := 0; j < 3;j++ {
        twoD[i][j] = i + j
    }
}
fmt.Println("2d: ", twoD)

基本语法-切片

切片不同于数组,它可以任意更改长度,然后也有更多的操作。比如我们可以使用 make 去创造一个切片,可以像数组一样去取值,用 appen 去追加元素。(使用 append 的用法的话,必须把 append 的结果赋值给原数组)。

因为 slice 的原理实际上是它存储了一个长度和一个容量,加一个指向一个数组的指针,在你执行 append 操作的时候,如果容量不够的话,会扩容并且返回新的 slice。

slice 在初始化的时候也可以指定长度,拥有像 python 一样的切片操作,比如取出第二个到第五个元素(不含第五个),不过与 python 不同的是,这里不支持负索引。

s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get: ", s[2])
fmt.Println("len: ", len(s))

s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s)

c := make([]string, len(s))
copy(c, s)
fmt.Println(c)

fmt.Println(s[2:5])
fmt.Println(s[:5])
fmt.Println(s[2:])

goo := []string{"g", "o", "o", "d"}
fmt.Println(good)

基本语法-map

map 是实际使用过程中最频繁用到的数据结构。我们可以用 make 来创建一个空 map,这里会需要两个类型,第一个是 key 的类型,另一个是 value 的类型。我们可以从里面去存储或者取出键值对。可以用 delete 从里面删除键值对。

golang 的 map 是完全无序的,遍历的时候不会按照字母排序,也不会按照插入顺序输出,而是随机顺序。

m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m)
fmt.Println(len(m))
fmt.Println(m["one"])

r, ok := m["unknow"]
fmt.Println(r, ok)

delete(m, "one")

m2 := map[stirng]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)

基本语法-range

对于一个 slice 或者一个 map 而言,可以使用 range 来快速遍历,能够让代码更加简洁。

range 遍历的时候,对于数组会返回两个值,第一个是索引,第二个是对应位置的值。如果不需要索引的话,可以使用下划线来忽略。

nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
    sum += num
    if num == 2 {
        fmt.Println("index: ", i, " num: ", num)
    }
}
fmt.Println(sum)

m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
    fmt.Println(k, v)
}
for k := range m {
    fmt.Println("key: ", k)
}

基本语法-函数

下面是一个简单的实现两个变量相加的函数。与其它语言不同的是,变量类型是后置的。

go 语言里面的函数原生支持返回多个值,在实际的业务逻辑代码中几乎所有的函数都返回两个值,第一个是真正的返回结果,第二个是错误信息。

func add(a int, b int) int {
    return a + b
}

func add2(a,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 中的指针,支持的操作很邮箱。指针的一个主要用途就是对于传入的参数进行修改。

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

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

基本语法-结构体

结构体是带类型的字段的集合,与 C 中的结构体类似。

我们可以用结构体的名称去初始化一个结构体变量,构造的时候需要传入每个字段的初始值。也可以用这种键值对的方式去指定初始值,这样可以只对一部分字段进行初始化。同样的结构体我们也能支持指针,这样能够实现对于结构体的修改,也可以在某些情况下避免一些大结构体的拷贝开销。

type user struct{
    name string
    password string
}

func main(){
    a := user{name: "mt", password: "1234"}
    b := user{"mt", "1234"}
    c := user{name: "mt"}
    c.password = "1234"
    var d user
    d.name = "mt"
    d.password = "1234"

    fmt.Println(a, b, c, d)
    fmt.Println(checkPassword(a, "haha"))
    fmt.Println(checkPassword2(b, "haha"))
}

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

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

基本语法-结构体方法

golang 中可以为结构体定义一些方法(可以类比于 java 中的类)。会有一点类似于其它语言里面的类成员函数。比如下面可以将上一个例子中的 checkPassword 从一个普通函数改成了结构体方法。这样用户可以直接 a.checkPassword("xx")来调用。

修改方法就是将第一个参数带上括号,写到函数名前面。在实现结构体的方法的时候也有两种写法,一种是带指针,一种是不带指针。区别在于如果带指针的话,那么就可以对这个结构体做修改,如果不带指针的话,实际上操作的是一个拷贝,无法对结构体进行修改

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

func (u *user) resetPassword(password string) {
    u.password = password
}

func main(){
    a := user{name:"mt", password: "1234"}
    a.resetPassword("2048")
    fmt.Println(a.checkPassword("2048"))
}

基本语法-错误处理

错误处理在 go 语言中符合语言习惯的做法就是使用一个贩毒的返回值来传递错误信息。

不同于 java 使用的异常,go 语言的处理方式,能够清晰地知道哪个函数返回了错误,并且能用简单的 if else 来处理错误。

在函数里面,我们可以在函数的返回值类型里面,后面加一个 error,就代表函数看你会返回错误。那么在函数实现的时候,return 需要同时返回两个值,要么就是如果出现错误的话,可以返回一个 nil 和一个 error。如果没有的话,那么返回原本的结果和 nil。

type user struct{
    name string 
    passowrd string
}

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

func main(){
    if u, err := findUser([]user{{"mt","123456"}},"li"); err != nil{
        fmt.Println(err)
        return
    }else{
        fmt.Println(u.name)
    }  

}

基本语法-字符串操作

在标准库 strings 包里面有很多常用的字符串工具函数,比如 contains 判断一个字符串里面是否包含另一个字符串,count 字符串计数,index 查找某个字符串的位置,join 连接多个字符串,repeat 重复多个字符串,replace 替换字符串。

a := "hello"
fmt.Pringln(strnigs.Contains(a, "ll"))
fmt.Pringln(strnigs.Count(a, "l"))
fmt.Pringln(strnigs.HasPrefix(a,"he"))
fmt.Pringln(strnigs.HasSuffix(a,"llo"))
fmt.Pringln(strnigs.Index(a,"ll"))
fmt.Pringln(strnigs.Join([]stirng{"he","llo"}, "-"))
fmt.Pringln(strnigs.Repeat(a, 2)))
fmt.Pringln(strnigs.Repace(a, "e", "E", -1))
fmt.Pringln(strnigs.Split("a-b-c","-"))
fmt.Pringln(strnigs.ToLower(a))
fmt.Pringln(strnigs.ToUpper(a))
fmt.Pringln(len(a))

基本语法-字符串格式化

在标准库的 FMT 包中有很多字符串格式化相关的方法,比如 printf 这个类似于 c 语言里面的 printf 函数。在 go 语言中,可以轻松的使用%v 来打印任意类型的变量,而不需要区分数字字符串。也可以使用%+v 打印详细结果,%#v 则更详细

type point struct{
    x, y int
}
func main(){
    s := "hello"
    n := 123
    p := point{1, 2}
    fmt.Println(s, n)
    fmt.Println(p)

    fmt.Printf("s=%v", s)
    fmt.Printf("n=%v", n)
    fmt.Printf("p=%v", p)
    fmt.Printf("p=%+v", p)
    fmt.Printf("p=%#v", p)

    f := 3.1415592653
    fmt.Println(f)
    fmt.Printf("%.2f\n", f)
}

基本语法-json 处理

go 语言中对于一个已有的结构体,我们可以什么都不做,只需要保证每个字段的第一个字母是大写,也就是这个字段是公开字段。那么这个结构体就能用 JSON.marshaler 去序列化,变成一个 JSON 的字符串。

序列化之后的字符串也可以用 JSON.unmarshaler 去反序列化到一个空的变量里面。这样默认序列化出来的字符串的话,它的风格是大写字母开头,而不是下划线。我们可以在后面用 json tag 等语法去修改输出 JSON 结果里面的字段名。

type userInfo struct {
    Name string
    Age int `json:"age"`
    Hobby []string
}
func main() {
    a := userInfo{
        Name: "mt"
        Age: 20
        Hobby: []string{"Golang", "Java"}
    }
    buf, err := json.Marshal(a)
    if err != nil {
        panic(err)
    }
    fmt.Println(buf)
    fmt.Println(string(buf))

    buf, err = json.MarshalIndent(a, "", "\t")
    if err != nil {
        panic(err)
    }
    fmt.Println(string(buf))

    var b userInfo
    err = json.Unmarshal(buf, &b)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%#v\n", b)
}

基本语法-时间处理

在 go 语言里面最常用的就是 time.now()来获取当前时间,然后也可以用 time.date 去构造一个带时区的时间,构造完的时间。有很多方法来获取这个时间点的年月日、时分秒,也可以用.sub 去对两个时间进行减法,得到一个时间段。时间段又可以去拆分得到时分秒。

在和某些系统进行交互的时候,我们经常会用到时间戳。可以使用.unix 来获取时间戳。

now := time.Now()
fmt.Println(now)
t := time.Date(2024, 11, 9, 0, 22, 12, time.UTC)
t2 := time.Date(2024, 11, 8, 5, 22, 12, time.UTC)
fmt.Println(t)
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute())
fmt.Println(t.Format("2006-01-02 15:04:05"))	// 需要注意,里面的内容是固定的
diff := t2.sub(t)
fmt.Println(diff)
fmt.Println(diff.Minutes(), diff.Seconds())
t3, err := time.Parse("2006-01-02 15:04:05","2024-11-09 00:22:12")
if err != nil {
    panic(err)
}
fmt.Println(t3 == t)
fmt.Println(now.Unix)

基本语法-数字转换

在 go 语言中,关于字符串和数字类型之间的转换都在 STR conv 这个包下,这个包是 string convert 这两个单词的缩写。我们可以用 parseInt 或者 parseFloat 来解析一个字符串。我们也可以用 Atoi 把一个十进制字符串转成数字。用 itoA 把数字转成字符串。如果输入不合法,那么这些函数都会返回 error

f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f)

n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n)

n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n)

n2, _ := strconv.Atoi("123")
fmt.Println(n2)

n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err)

实战

猜数字游戏

第一个例子,我们使用 golang 来构建一个猜数字的游戏。在这个游戏里面,程序首先用 rand 生成一个介于 1 到 100 的随机整数,然后提示玩家进行猜测。下面先给出源码,然后一一介绍。

package main

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

func main() {

	maxNum := 100
	rand.Seed(time.Now().UnixNano())
	guessNumber := rand.Intn(maxNum)
	//fmt.Println(guessNumber)
	fmt.Println("Guess a number between 0 and 100")

	for {
		var guess int
		_, err := fmt.Scanln(&guess)
		if err != nil {
			return
		}
		if guess > guessNumber {
			fmt.Println("Too high")
		} else if guess < guessNumber {
			fmt.Println("Too low")
		} else {
			fmt.Println("You guessed it!")
			break
		}
	}
}

与其它语言一样,golang 中的随机数也需要给出"种子",不然每次的随机数都是固定的,如 12 行中的代码。

课件中读取用户输入的逻辑是使用 bufio.NewReader(os.Stdin)。但实际上,如果规范输入的话,可以直接如 19 行代码一样使用 Scanln 来读取。

使用 for 死循环来完成该游戏是可以的,当然也可以使用逻辑判断来实现。

在线词典

课件中只给出了中译英的代码,课后自己实操了一下英译中,发现效果还行,如下:

下面先来介绍一个课件中的代码

package main

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

type Translator struct {
	Translate string `json:"trans_type"`
	Source    string `json:"source"`
	UserID    string `json:"user_id"`
}
type JSONData struct {
	Rc   int `json:"rc"`
	Wiki struct {
	} `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 Translate(word string) {
    
	client := &http.Client{}

	request := Translator{
		Translate: "en2zh",
		Source:    word,
	}
	buf, err := json.Marshal(request)

	if err != nil {
		log.Fatal(err)
	}
	var data = bytes.NewBuffer(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("accept", "application/json, text/plain, */*")
	req.Header.Set("accept-language", "zh")
	req.Header.Set("app-name", "xiaoyi")
	req.Header.Set("authorization", "Bearer")
	req.Header.Set("content-type", "application/json;charset=UTF-8")
	req.Header.Set("device-id", "eea42e79c653d75a30a69e4f9817365d")
	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="99", "Chromium";v="112"`)
	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("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 QuarkPC/1.9.0.151")
	req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	bodyText, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	if resp.StatusCode != http.StatusOK {
		log.Fatalf("status code error: %d %s", resp.StatusCode, resp.Status)
	}

	//fmt.Printf("%s\n", bodyText)

	var jsonData JSONData
	err = json.Unmarshal(bodyText, &jsonData)
	if err != nil {
		fmt.Println("json unmarshal err: ", err)
	}

	fmt.Printf("%#v\n", jsonData)

	fmt.Println(word, jsonData.Dictionary.Prons.En, "US:", jsonData.Dictionary.Prons.EnUs)

	for _, item := range jsonData.Dictionary.Explanations {
		fmt.Println(item)
	}
}

func main() {

	var word string

	fmt.Println("Enter a word: ")
	_, err := fmt.Scanln(&word)
	if err != nil {
		log.Fatal(err)
	}

	Translate(word)

}

首先创建了一个 HTTP client,创建的时候可以指定部分参数,比如请求超时的时候是否使用 cookie 等。然后构造了一个 HTTP 请求,这是一个 post 请求,然后用到 HTTP.NewRequest,第一个参数是 http 方法的 POST,第二个参数是 URL,最后一个参数是 body,body 因为可能很大,为了支持流式发送,是一个只读流。我们用了 strings.NewReader 来把字符串转换成一个流,这样就成功构造了一个 HTTP request,接下来我们需要对这个 HTTP request 来设置一堆 header。

接下来把我们调用的 client.do request,就能得到 response,如果请求失败的话,那么这个 error 会返回非 nil,会打印错误并且退出进程。response 有它的 HTTP 状态码,response header 和 body。

body 同样是一个流,在 golang 里面,为了避免资源泄露,需要加一个 defer 来手动关闭这个流,这个 defer 会在这个函数运行结果之后去执行。接下来我们使用 ioutil.ReadAll 来读取这个流,能得到整个 body。我们再用 print 打印出来。

跟上面流程一样,下面给出英译中的代码

package main

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

type Translator2 struct {
	Translate string `json:"trans_type"`
	Source    string `json:"source"`
	UserID    string `json:"user_id"`
}

type JSONData2 struct {
	Rc   int `json:"rc"`
	Wiki struct {
	} `json:"wiki"`
	Dictionary struct {
		Entry        string        `json:"entry"`
		Explanations []string      `json:"explanations"`
		Related      []interface{} `json:"related"`
		Source       string        `json:"source"`
		Prons        struct {
		} `json:"prons"`
		Type string `json:"type"`
	} `json:"dictionary"`
}

func Translate2(word string) {
	client := &http.Client{}

	request := Translator2{
		Translate: "zh2en",
		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("authority", "api.interpreter.caiyunai.com")
	req.Header.Set("accept", "application/json, text/plain, */*")
	req.Header.Set("accept-language", "zh")
	req.Header.Set("app-name", "xiaoyi")
	req.Header.Set("authorization", "Bearer")
	req.Header.Set("content-type", "application/json;charset=UTF-8")
	req.Header.Set("device-id", "eea42e79c653d75a30a69e4f9817365d")
	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="99", "Chromium";v="112"`)
	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("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 QuarkPC/1.9.0.151")
	req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	bodyText, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	if resp.StatusCode != http.StatusOK {
		log.Fatalf("status code error: %d %s", resp.StatusCode, resp.Status)
	}
	var jsonData2 JSONData2
	err = json.Unmarshal(bodyText, &jsonData2)
	if err != nil {
		fmt.Println("json unmarshal err: ", err)
	}
	//fmt.Printf("%#v\n", jsonData2)

	//fmt.Println(word, jsonData2.Dictionary.Entry)

	for _, item := range jsonData2.Dictionary.Explanations {
		fmt.Println(item)
	}

}

func main() {

	Translate2("你好")

}

为了方便,将翻译整合到一个模块中去,下面给出模块代码,可以直接调用函数来执行

package model

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

type Translator struct {
	Translate string `json:"trans_type"`
	Source    string `json:"source"`
	UserID    string `json:"user_id"`
}
type ENData struct {
	Rc   int `json:"rc"`
	Wiki struct {
	} `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"`
}

type CNData struct {
	Rc   int `json:"rc"`
	Wiki struct {
	} `json:"wiki"`
	Dictionary struct {
		Entry        string        `json:"entry"`
		Explanations []string      `json:"explanations"`
		Related      []interface{} `json:"related"`
		Source       string        `json:"source"`
		Prons        struct {
		} `json:"prons"`
		Type string `json:"type"`
	} `json:"dictionary"`
}

func setHeader(req *http.Request) {
	req.Header.Set("authority", "api.interpreter.caiyunai.com")
	req.Header.Set("accept", "application/json, text/plain, */*")
	req.Header.Set("accept-language", "zh")
	req.Header.Set("app-name", "xiaoyi")
	req.Header.Set("authorization", "Bearer")
	req.Header.Set("content-type", "application/json;charset=UTF-8")
	req.Header.Set("device-id", "eea42e79c653d75a30a69e4f9817365d")
	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="99", "Chromium";v="112"`)
	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("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 QuarkPC/1.9.0.151")
	req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
}

func EnToZh(word string) {

	client := &http.Client{}

	request := Translator{
		Translate: "en2zh",
		Source:    word,
	}
	buf, err := json.Marshal(request)

	if err != nil {
		log.Fatal(err)
	}
	var data = bytes.NewBuffer(buf)
	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
	if err != nil {
		log.Fatal(err)
	}
	setHeader(req)
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	bodyText, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	if resp.StatusCode != http.StatusOK {
		log.Fatalf("status code error: %d %s", resp.StatusCode, resp.Status)
	}

	var jsonData ENData
	err = json.Unmarshal(bodyText, &jsonData)
	if err != nil {
		fmt.Println("json unmarshal err: ", err)
	}

	//fmt.Printf("%#v\n", jsonData)

	fmt.Println(word, jsonData.Dictionary.Prons.En, "US:", jsonData.Dictionary.Prons.EnUs)

	for _, item := range jsonData.Dictionary.Explanations {
		fmt.Println(item)
	}

}

func ZhToEn(word string) {

	client := &http.Client{}

	request := Translator{
		Translate: "zh2en",
		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)
	}

	setHeader(req)
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	bodyText, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	if resp.StatusCode != http.StatusOK {
		log.Fatalf("status code error: %d %s", resp.StatusCode, resp.Status)
	}
	var jsonData2 CNData
	err = json.Unmarshal(bodyText, &jsonData2)
	if err != nil {
		fmt.Println("json unmarshal err: ", err)
	}

	for _, item := range jsonData2.Dictionary.Explanations {
		fmt.Println(item)
	}
}