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

134 阅读15分钟

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

中文go标准库文档:studygolang.com/pkgdoc

入门

安装环境

  • SDK下载:studygolang.com/dl
  • IDE,推荐下载Golang(和idea都是同一家公司的产品),也可以直接在idea里安装go插件进行使用

配置环境变量

  • GOPATH配置,我们以后可以直接在这个目录下的src下创建我们的项目,后面使用go.mod可以不使用这种方式
  • GOROOT配置,这个必须配置

最后在path里面配置一下sdk的bin目录

基础语法

编写一个HelloWorld

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

我们要执行的main必须要写package main才行

变量

第一种:声明后不赋值,使用默认值

 package main
 import "fmt"
 func main() {
     var i int
     fmt.Println(i)//int 默认值是0
 }

第二种:类型推导(根据值自行判断类型)

 package main
 import "fmt"
 func main() {
     var i = 100.1
     fmt.Println(i)
 }

第三种:省略var

 package main
 import "fmt"
 func main() {
     /*
       等价于:
           var name string
           name = "Ernie"
        := 不能被省略,否则报错
     */
     name := "Ernie"
     fmt.Println(name)
 }

注意:":="左侧的变量不应该是已经声明过的,否则会导致编译错误。

选择

类型1

注意:条件不用加括号,然后就行无论什么情况都要加大括号包住我们的语句

if

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var score float64
     fmt.Scanln(&score)
     if score == 100 {
         fmt.Println("奖励一辆BMW")
     }
 }

if else

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var score float64
     fmt.Scanln(&score)
     if score == 100 {
         fmt.Println("奖励一辆BMW")
     }else {
         fmt.Println("无奖励")
     }
 }

if else if ... else

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var score float64
     fmt.Scanln(&score)
     if score == 100 {
         fmt.Println("奖励一辆BMW")
     }else if score > 80 {
         fmt.Println("奖励一台iphone7plus")
     }else if score >= 60 {
         fmt.Println("奖励一个iPad")
     }else {
         fmt.Println("无奖励")
     }
 }

类型2

有两种使用方式

switch01

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var a = 100
     switch {
     case a>10 :
         fmt.Println(a)
         fallthrough
     case a>20 :
         fmt.Println(a)
     default :
         fmt.Println("NONE")
     }
 }

fallthrough:可以穿透一层循环的

switch02

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var c int32
     fmt.Scanf("%c",&c)
 ​
     switch c {
     case 'a':
         fmt.Printf("%c",'A')
     case 'b':
         fmt.Printf("%c",'B')
     case 'c':
         fmt.Printf("%c",'C')
     case 'd':
         fmt.Printf("%c",'D')
     case 'e':
         fmt.Printf("%c",'E')
     default:
         fmt.Printf("%c",c)
     }
 }

这个case可以通过逗号多加,比如: case 'a','b','c' { }

循环

三种形式,我们可以通过给for上面加标签,然后"break 标签"的方式跳出循环,和java的类似

第一种

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var counts int
     var sum int
 ​
     for i := 1; i <= 100; i++ {
         if i%9 == 0 {
             counts ++ 
             sum += i
         }
     }
     fmt.Printf("个数:%d,总和:%d\n",counts,sum)
 ​
     for i := 0; i <= 6; i++ {
         fmt.Printf("%d + %d = 6\n",i,6-i)
     }
 }

第二种:

 for {
     //循环体
 }

第三种:

 for 条件 {
     //循环体
 }

数组

定义:var 变量名 [数组大小]类型 要获取数组地址,使用"&变量名" 没有初始化,默认值是类型的默认值

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var arr [5]int
     arr[0] = 1
     arr[1] = 2
     arr[2] = 3
     arr[3] = 4
     arr[4] = 5
     //遍历
     for idx,val := range arr {
         fmt.Println("idx:",idx,",val:",val)
         fmt.Println("val:",arr[idx])
     }
 }

切片

它是引用类型 使用和数组类似 它是一个可以动态变化的数组、 基本语法:var 变量名 []类型,我们需要使用make([]类型,初始数量)来分配内存

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
 ​
     //方式1
     var arr = [...]int{0,1,2,3,4,5}
     var slice1 []int = arr[1:4]
     fmt.Println(slice1)
     arr[1] = 20
     fmt.Println(slice1)
 ​
     //方式2:make
     slice2 := make([]int,10,15)
     fmt.Println(slice2)
     slice2[3] = 1
     fmt.Println(slice2)
     // slice2[10] = 1//index out of range
     // fmt.Println(slice2)
 ​
     //方式3
     slice3 := []int{1,2,3,4,5}
     fmt.Println(slice3)
 }

map

也需要make一下才能分配内存 key不可以为slice、map、function(函数) 基本语法:var 变量名 map[类型]类型

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var a map[int]string //还没有分配内存
     a = make(map[int]string,10)
     
     a[100] = "Hello"
     a[11] = "world"
     a[1] = "随风"
     a[1] = "叶子" //会覆盖前面的"随风",因为它们的key一样
 ​
     fmt.Println(a)
 }

range

遍历数组、切片

for index,item := range 变量名 {

}

遍历map

for key,value := range 变量名 {

}

函数

基础语法

func 函数名 (形参列表) (返回值列表) { //执行语句 return 返回值列表 //使用逗号隔开,比如:"return 1,2,3" }

注意事项

如果不传入指针,函数的参数会拷贝进来的数据进行操作 函数名首字母大写,表示这个方法外界引入可以使用 函数可以没有返回值和参数列表

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     // operator := '+'//这样搞默认是int32
     // fmt.Printf("%T",operator)
     // var operator = '+'//cannot use operator (type rune) as type byte in argument to cal
     
     var operator byte = '-'
     fmt.Println(cal(1,2,operator))
 ​
     operator = '+'
     fmt.Println(cal(1,2,operator))
 ​
     operator = '*'
     fmt.Println(cal(1,2,operator))
 ​
     operator = '/'
     fmt.Println(cal(1,2,operator))
 ​
     operator = '%'
     fmt.Println(cal(1,2,operator))
 }
 ​
 func cal(n1 float64, n2 float64, operator byte) float64 {
     var res float64
     switch operator {
         case '+':
             res = n1 + n2
         case '-':
             res = n1 - n2
         case '*':
             res = n1 * n2
         case '/':
             res = n1 / n2
         default:
             fmt.Printf("操作符不在处理范围内!")
     }
     return res
 }

指针

可以使用:&变量名的形式获取 通过指针,我们可以直接操作那块内存

结构体

基本语法

type 名称 struct { 属性名1 type 属性名2 type 属性名2 type ..... }

基本使用

  1. 定义:var 变量名 结构体名称
  2. 使用:变量名.属性

注意事项

结构体名称首字母大写,表示这个结构体别的导入之后可以使用。

结构体方法

有两种写法,一种是和对象绑定,一种是和对象指针绑定,后者在方法中改变对象属性,前者相当于拷贝一份对象进行操作。

方式1

 package main
 ​
 import (
     "fmt"
     "encoding/json"
 )
 ​
 type Student struct {
     Name string `json:"name"`
     Gender string `json:"gender"`
     Age int `json:"age"`
     Id int `json:"id"`
     Score float32 `json:"score"`
 }
 ​
 func (s *Student) say() string {
     //两种传参方式都行
     // res,_ := json.Marshal(*s)
     res,_ := json.Marshal(s)
     return string(res)
 }
 ​
 func main() {
     var s = Student{"随风","男",20,202011,89.5}
     fmt.Println(s.say())//{"name":"随风","gender":"男","age":20,"id":202011,"score":89.5}
 }

方式2

 package main
 ​
 import (
     "fmt"
     "encoding/json"
 )
 ​
 type Student struct {
     Name string `json:"name"`
     Gender string `json:"gender"`
     Age int `json:"age"`
     Id int `json:"id"`
     Score float32 `json:"score"`
 }
 ​
 func (s Student) say() string {
     //两种传参方式都行
     // res,_ := json.Marshal(*s)
     res,_ := json.Marshal(s)
     return string(res)
 }
 ​
 func main() {
     var s = Student{"随风","男",20,202011,89.5}
     fmt.Println(s.say())//{"name":"随风","gender":"男","age":20,"id":202011,"score":89.5}
 }

错误处理

  • errors.New("not found"),这个允许我们自定义信息
 package main
 ​
 import (
     "errors"
     "fmt"
 )
 ​
 type user struct {
     name     string
     password 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() {
     u, err := findUser([]user{{"wang", "1024"}}, "wang")
     if err != nil {
         fmt.Println(err)
         return
     }
     fmt.Println(u.name) // wang
 ​
     if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
         fmt.Println(err) // not found
         return
     } else {
         fmt.Println(u.name)
     }
 }
 ​

字符串操作

  • "strings"包
  • Contains:一个字符串是否包含某个字符串
  • Count:一个字符串包含某个字符串多少个
  • HasPrefix:前缀
  • HasSuffix:后缀
  • strings.Index
  • 。。。。。。。
 package main
 ​
 import (
     "fmt"
     "strings"
 )
 ​
 func main() {
     a := "hello"
     fmt.Println(strings.Contains(a, "ll"))                // true
     fmt.Println(strings.Count(a, "l"))                    // 2
     fmt.Println(strings.HasPrefix(a, "he"))               // true
     fmt.Println(strings.HasSuffix(a, "llo"))              // true
     fmt.Println(strings.Index(a, "ll"))                   // 2
     fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
     fmt.Println(strings.Repeat(a, 2))                     // hellohello
     fmt.Println(strings.Replace(a, "e", "E", -1))         // hEllo
     fmt.Println(strings.Split("a-b-c", "-"))              // [a b c]
     fmt.Println(strings.ToLower(a))                       // hello
     fmt.Println(strings.ToUpper(a))                       // HELLO
     fmt.Println(len(a))                                   // 5
     b := "你好"
     fmt.Println(len(b)) // 6
 }
 ​

字符串格式化

  • fmt.Println(s, n),逗号会出现一个空格
  • fmt.Printf("%.2f\n", f),和c语言的printf差不多
 package main
 ​
 import "fmt"
 ​
 type point struct {
     x, y int
 }
 ​
 func main() {
     s := "hello"
     n := 123
     p := point{1, 2}
     fmt.Println(s, n) // hello 123
     fmt.Println(p)    // {1 2}
 ​
     fmt.Printf("s=%v\n", s)  // s=hello
     fmt.Printf("n=%v\n", n)  // n=123
     fmt.Printf("p=%v\n", p)  // p={1 2}
     fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
     fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
 ​
     f := 3.141592653
     fmt.Println(f)          // 3.141592653
     fmt.Printf("%.2f\n", f) // 3.14
 }
 ​

JSON处理

  • buf, err := json.Marshal(a),返回[]byte切片
  • err = json.Unmarshal(buf, &b),传入[]byte切片和对应实体地址
 package main
 ​
 import (
     "encoding/json"
     "fmt"
 )
 ​
 type userInfo struct {
     Name  string
     Age   int `json:"age"`
     Hobby []string
 }
 ​
 func main() {
     a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
     buf, err := json.Marshal(a)
     if err != nil {
         panic(err)
     }
     fmt.Println(buf)         // [123 34 78 97...]
     fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
 ​
     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) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
 }

时间处理

  • time.now()

  • time.Date()

  • t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()

  • diff := t2.Sub(t),时间减法

  • t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")

    • 这个比较特殊一点:2006-01-02 15:04:05,就好像Java的yyyy-MM-dd hh:mm:ss这样
    • 记忆小方法:612345这样记忆就行了,6:2006,3:15
 package main
 ​
 import (
     "fmt"
     "time"
 )
 ​
 func main() {
     now := time.Now()
     fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
     t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
     t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
     fmt.Println(t)                                                  // 2022-03-27 01:25:36 +0000 UTC
     fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
     fmt.Println(t.Format("2006-01-02 15:04:05"))                    // 2022-03-27 01:25:36
     diff := t2.Sub(t)
     fmt.Println(diff)                           // 1h5m0s
     fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
     t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
     if err != nil {
         panic(err)
     }
     fmt.Println(t3 == t)    // true
     fmt.Println(now.Unix()) // 1648738080
 }
 ​

数字解析

可以使用strconv包下的来

 package main
 ​
 import (
     "fmt"
     "strconv"
 )
 ​
 func main() {
     //1. func ParseBool(str string) (value bool, err error)
     flag := "true"
     var b bool
     b,_ = strconv.ParseBool(flag)//必须这样写
     fmt.Printf("%T,%t\n",b,b)
 ​
     //2. func ParseInt(s string, base int, bitSize int) (i int64, err error)
     //base:说明这个字符串的数字是什么进制的(或者说按什么进制转换)
     i := "120"
     var num int64
     num,_ = strconv.ParseInt(i,16,32)
     var realNum int32 = int32(num)
     fmt.Printf("%T,%d\n",realNum,realNum)
     //3. func ParseUint(s string, base int, bitSize int) (n uint64, err error)
 ​
     //4. func ParseFloat(s string, bitSize int) (f float64, err error)
     f := "123.2"
     var t float64
     t,_ = strconv.ParseFloat(f,64)
     fmt.Printf("%T,%f\n",t,t)
 }

进程信息

os包下操作

  • os.Args:Args保管了命令行参数,第一个是程序名
  • func Getenv(key string) stringGetenv检索并返回名为key的环境变量的值。如果不存在该环境变量会返回空字符串。
  • func Setenv(key, value string) error:Setenv设置名为key的环境变量。如果出错会返回该错误。

exec.Commond

func Command

func Command(name string, arg ...string) Cmd 函数返回一个Cmd,用于使用给出的参数执行name指定的程序。返回值只设定了Path和Args两个参数。 如果name不含路径分隔符,将使用LookPath获取完整路径;否则直接使用name。参数arg不应包含命令名。 func (c *Cmd) CombinedOutput() ((studygolang.com/static/pkgd…), error):执行命令并返回标准输出和错误输出合并的切片。

 package main
 ​
 import (
     "fmt"
     "os"
     "os/exec"
 )
 ​
 func main() {
     fmt.Println(os.Args)
 ​
     fmt.Println(os.Getenv("PATH"))
 ​
     fmt.Println(os.Setenv("AA", "BB"))
 ​
     fmt.Println(os.Args)
 ​
     //这个方法在Windows测不行的
     buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
 ​
     if err != nil {
         panic(err)
     }
 ​
     fmt.Println(string(buf))
 }
 ​

猜谜游戏

思路

  1. 使用标准库"math/rand"生成一个范围在[0,100]的数字,注意要注入随机种子才行,不然每次获取的随机数都是一样的
  2. 通过标准库"bufio"读取用户输入
  3. 处理用户输入的字符串,并转成数字和秘密树进行比较
  4. 比较正确,游戏结束

代码

 package main
 ​
 import (
     "bufio"
     "fmt"
     "math/rand"
     "os"
     "strconv"
     "strings"
     "time"
 )
 ​
 func main() {
     //1 设置要猜测的数字
     //1.1 随机种子
     rand.Seed(time.Now().UnixNano())
     //1.2 获取要猜测的数字[0,101)
     secretNum := rand.Intn(101)
 ​
     //2 用户循环猜测,最后退出
     reader := bufio.NewReader(os.Stdin)
     userNum := -1
     fmt.Println("数字范围:0-100")
     for {
         fmt.Print("请输入你猜测的数字:")
         numStr, err := reader.ReadString('\n')
         if err != nil {
             fmt.Println("输入有误,请重新输入!err =", err)
             continue
         }
         //去掉后缀的\n,不加\r下面报错
         numStr = strings.TrimSuffix(numStr, "\r\n")
         userNum, err = strconv.Atoi(numStr)
         if err != nil {
             fmt.Println("输入有误,请重新输入!err =", err)
             continue
         }
         if userNum > secretNum {
             fmt.Println("你输入的数字过大")
         } else if userNum < secretNum {
             fmt.Println("你输入的数字过小")
         } else {
             fmt.Println("恭喜你输入正确!")
             break
         }
     }
     fmt.Println("猜数游戏结束")
 }
 ​

作业

使用fmt的scanln或者scanf替代bufio的NewReader

 package main
 ​
 import (
     "fmt"
     "math/rand"
     "time"
 )
 ​
 func main() {
     rand.Seed(time.Now().UnixNano())
     secretNum := rand.Intn(101)
     fmt.Println("数字范围:0-100")
     var userNum int
     for {
         fmt.Print("请输入你的猜测:")
 ​
         //不加'\n'会:err = unexpected newline
         _, err := fmt.Scanf("%d\n", &userNum)
 ​
         if err != nil {
             fmt.Println("输入有误,请重新输入!err =", err)
             continue
         }
 ​
         if userNum > secretNum {
             fmt.Println("你输入的数字过大")
         } else if userNum < secretNum {
             fmt.Println("你输入的数字过小")
         } else {
             fmt.Println("恭喜你输入正确!")
             break
         }
     }
     fmt.Println("猜数游戏结束!")
 }
 ​

词典项目

任务:启动程序,读取用户在启动程序时输入的英文,将它翻译成英文 如:go run xxx\xxxx\main.go hello 我们的程序就将hello翻译

工具

思路

  1. 进入彩云翻译

  2. 将复制到的东西粘贴到”curl转Go代码“那个网站,然后我们就得到对应的Go代码了

  3. 我们运行这个Go程序,可以获取JSON格式数据

  4. 将这个数据粘贴到”JSON转GO结构体“网站获取对应的结构体,然后我们就可以进行代码的修改了

  5. 首先:生成请求体:var reqData = DictRequest{TransType: "en2zh", Source: word}

  6. 然后:解析JSON:err = json.Unmarshal(bodyText, &res)

  7. 最后:输出我们要的数据就行了

代码

 package main
 ​
 import (
     "encoding/json"
     "fmt"
     "io/ioutil"
     "log"
     "net/http"
     "os"
     "strings"
 )
 ​
 type DictRequest struct {
     TransType string `json:"trans_type"`
     Source    string `json:"source"`
 }
 ​
 type AutoGenerated 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 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{}
     //生成请求体
     var reqData = DictRequest{TransType: "en2zh", Source: word}
     reqJson, _ := json.Marshal(reqData)
     var data = strings.NewReader(string(reqJson))
     //创建请求
     req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
     if err != nil {
         log.Fatal(err)
     }
     //设置请求头
     req.Header.Set("Connection", "keep-alive")
     req.Header.Set("Pragma", "no-cache")
     req.Header.Set("Cache-Control", "no-cache")
     req.Header.Set("sec-ch-ua", `" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"`)
     req.Header.Set("sec-ch-ua-mobile", "?0")
     req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36")
     req.Header.Set("app-name", "xy")
     req.Header.Set("Content-Type", "application/json;charset=UTF-8")
     req.Header.Set("Accept", "application/json, text/plain, */*")
     req.Header.Set("os-type", "web")
     req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
     req.Header.Set("sec-ch-ua-platform", `"Windows"`)
     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)
     }
     //解析json
     var res AutoGenerated
     err = json.Unmarshal(bodyText, &res)
     if err != nil {
         log.Fatal(err)
     }
     //打印结果
     //fmt.Printf("%#v\n", res)
     fmt.Println(word, "UK:", res.Dictionary.Prons.En, "US:", res.Dictionary.Prons.EnUs)
     for _, item := range res.Dictionary.Explanations {
         fmt.Println(item)
     }
 }

作业

我这里做的是:中文转英文,步骤和上面的是一样的步骤

 package main
 ​
 import (
     "encoding/json"
     "fmt"
     "io/ioutil"
     "log"
     "net/http"
     "os"
     "strings"
 )
 ​
 type DictRequest struct {
     TransType string `json:"trans_type"`
     Source    string `json:"source"`
 }
 ​
 type AutoGenerated 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 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) {
     //创建一个发请求的http客户端
     client := &http.Client{}
     //生成请求体
     var reqData = DictRequest{TransType: "zh2en", Source: word}
     reqJson, _ := json.Marshal(reqData)
     var data = strings.NewReader(string(reqJson))
     //创建请求(三个参数:请求方式、请求URL、io.Reader流)
     req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
     if err != nil {
         //打印、退出、不执行defer内容了
         log.Fatal(err)
     }
     //设置请求头
     req.Header.Set("Connection", "keep-alive")
     req.Header.Set("Pragma", "no-cache")
     req.Header.Set("Cache-Control", "no-cache")
     req.Header.Set("sec-ch-ua", `" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"`)
     req.Header.Set("sec-ch-ua-mobile", "?0")
     req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36")
     req.Header.Set("app-name", "xy")
     req.Header.Set("Content-Type", "application/json;charset=UTF-8")
     req.Header.Set("Accept", "application/json, text/plain, */*")
     req.Header.Set("os-type", "web")
     req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
     req.Header.Set("sec-ch-ua-platform", `"Windows"`)
     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")
     //发起请求(上面已经出现了err的 := 了,这里没报错说明什么)
     resp, err := client.Do(req)
     if err != nil {
         log.Fatal(err)
     }
     defer resp.Body.Close()
     //读取请求响应(Body=>io.ReadCloser,ReaderAll需要=>io.Reader)
     bodyText, err := ioutil.ReadAll(resp.Body)
     if err != nil {
         log.Fatal(err)
     }
     //解析json(这里:=会报错,可能是因为这个err已经在上面使用过了)
     var res AutoGenerated
     err = json.Unmarshal(bodyText, &res)
     if err != nil {
         log.Fatal(err)
     }
     //打印结果
     fmt.Println(word)
     for _, item := range res.Dictionary.Explanations {
         fmt.Println(item)
     }
 }

代理项目

实现socks5代理

代码

  1. 使用tcp协议,监听本地的1080端口
  2. 每次有一个客户端来,我们就开启一个协程来处理,连接的周期==process函数周期
  3. 在process函数里面,我们进行两个操作,一个是认证,一个是连接为客户端做代理
  4. 在认证方法auth中,我们读取一个字节
 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
 }
 ​

作业

我是没搞懂这个,也花了大量时间,还是我太菜了。