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

207 阅读11分钟

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

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

什么是Go语言

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

GO语言入门

开发环境-安装Golang

  • Golang官网 go.dev 直接安装 或者 选择 studygolang.com/dlgoproxy.cn/
  • 配置集成开发环境
    • Vscode (跨平台,功能齐全,需要go插件)
    • Golang(智能化,收费)
    • 基于云开发环境

基础语法

第一个go程序-Hello World

package main
import (
    "fmt"
)
func main(){
    fmt.Println("hello world")
}
  1. 如何运行?
    1. 在项目所在位置 执行命令 : go run 程序路径
    2. 程序编译为可执行文件:go build 程序路径 编译完后直接双击运行可执行文件

变量

  1. Go是一门强类型语言,每个变量均有自己的数据类型
  2. 字符串类型是内置类型,可以用加号进行拼接,或者用等于号去比较字符串

变量的声明

  1. var 变量名 = 值
    1. 此方式自动推导数据类型
  2. 变量 := 值

常量

使用 const 进行声明

//Go的变量与常量
package main
import (
    "fmt"
)
func main(){
    //变量的声明
    var a = "initial" //字符串
    var b,c int = 1,2 //整型
    var d = true  //布尔型
    var e float64
    f := float32(4) //数据类型转化
    g := a + "foo" //字符串拼接
    fmt.Println(a,b,c,d,e,f,g)
    //常量
    const s string = "constant"
    const h = 5000000
    const i = 3e20 / h
    fmt.Println(s,h,i)
}

if-else

注意点

  1. if 后面的判断条件没有括号
  2. 判断条件后面必须带大括号

循环

注意点

  1. 没有while与do-while,只有for循环
  2. break跳出本层循环,continue跳出本次循环

switch

注意点

  1. switch 后面的变量不需要括号
  2. 默认下不需要break来阻止继续执行其下面的case
  3. switch与case后面可以为任何类型,如变量,常量函数等

数组

  1. 声明数组 var 数组名[长度] 数据类型 如:var a[5] int
  2. 读取 数据名[index] 如:a[4]
  3. 获取长度 len(a)
  4. 因为数组的长度固定,所以开发中更多的使用切片

切片

  1. 切片是一个可变长度的数组,可以改变切片的长度

  2. 声明:切片名 := make([]数据类型,长度)

  3. 使用append进行追加元素,注意append的结果必须赋值给原数组

    s := make([]string,3)
    s = append(s,"d") //[d]
    
  4. 切片拷贝 copy(切片1,切片2)

  5. 切片取值

    1. s[2:5] 表示输出索引从2到4(包括2不包括5)的元素
    2. s[:5] 表示输出索引从0到4的元素
    3. s[2:] 表示输出索引从2 到 len(s)-1 的元素

map

  1. 创建:m : = make (map[string] int) 需要两个类型 string 为key的类型,int为value的类型
  2. 写入键值对: m[key] = value
  3. 读取值 m[key] 返回两个值,第一个为key对应的value,第二个为是否存在key
  4. 删除键值对 delete(m,key)
  5. map完全无序,遍历不会按照顺序输出,而是随机的顺序

range

  1. 常用于map和切片等的快速遍历,返回两个值,索引与对应的值

    package main
    import "fmt"
    func main() {
    	nums := []int{2, 3, 4}
    	sum := 0
    	for i, num := range nums {
    		sum += num
    		if num == 2 {
    			fmt.Println("index:", i, "num:", num) // index: 0 num: 2
    		}
    	}
    	fmt.Println(sum) // 9
    
    	m := map[string]string{"a": "A", "b": "B"}
    	for k, v := range m {
    		fmt.Println(k, v) // b 8; a A
    	}
    	for k := range m {
    		fmt.Println("key", k) // key a; key b
    	}
    }
    

函数

  1. 注意变量类型需要后置
  2. 语法格式: func 函数名(变量1 数据类型)(返回值类型1){}
  3. 变量与返回值均可以为多个

指针

  1. 主要用途,对参数进行修改
  2. 指针类型 *数据类型

结构体

  1. 结构体是带类型的字段的集合

  2. 可以用结构体的名称初始化结构体的变量

    //user为结构体名称
    type user struct{
        name string
        password string
    }
    a := user{name:"wang" , password:"1234"}
    
  3. 可以用(.)去读取或写入结构体字段的内容 如user.name = "tom"

  4. 结构体可以作为函数的参数,如果使用指针可以对结构体进行修改,并且节省大结构体的开销

结构体方法

  1. 可以为结构体定义一些方法

  2. 格式 func (结构体名 数据类型) 方法名 (变量 数据类型){}

    //可以选择带指针或者不带指针
    func (u *user) resetPassword(password string) {
    	u.password = password
    }
    

错误处理(error)

  1. 通常用返回值来接收错误
  2. 在函数中,可以在返回值类型传入一个error 用来接收错误
  3. 创建错误 error.New() 可以输出错误的提示语句等

字符串操作

  1. 标准库strings
  2. 常用炒作
    1. Contains判断一个字符串中是否包含另一个字符串
    2. Count字符串计数某字符出现次数
    3. HasPrefix判断以某某开头
    4. Index查找某字符串位置
    5. Join连接多个字符串
    6. Repeat重复多个字符串
    7. Repalce替换字符
    8. len获取长度
      1. 对于中文而言,可能一个中文会对应多个字符

字符串格式化

  1. Println(value1,value2 ······) 打印多个值并换行
  2. Printf()格式化输出
    1. %v 打印任意类型变量(值)
    2. %+v 打印内部详细结构(字段名与值)
    3. %#v 打印更详细结构(结构体的类型名称以及字段名与值)
    4. %.2f保留两位小数的浮点数

JSON操作

  1. 导包 encoding/json

  2. 结构体中字段名首字母大写,可以用json.Marshal进行序列化成为一个byte数组 需转为字符串输出

    buf, err := json.Marshal(a)
    fmt.Println(string(buf)) // {"Name":"hong","age":18,"Hobby":["Golang","TypeScript"]}
    
  3. 反序列化 json.Unmarshal

    err = json.Unmarshal(buf, &b)
    
  4. 自定义字段名 ‘json:"别名"’

    Age   int `json:"age"`
    

时间处理

  1. 导包 time
  2. 当前时间 now:= time.Now()
  3. 格式化时间 time.Fotmat("2006-01-02 15:04:05") 需要用特定时间作为格式 "2006-01-02 15:04:05"
  4. 解析成时间 time.Parse("2006-01-02 15:04:05",time)
  5. 时间戳 now.Unix()

数字解析

  1. 导包 strconv
  2. 解析字符串 Parseint(字符串,进制,返回位数) 注:如果进制为0则自行推测
  3. 使用 strcov.Atoi 快速把一个字符串转为数字 返回值:数字,错误

进程信息

  1. 导包 os 与 os/exec
  2. os.Args获取进程进行的命令行参数
  3. os.Getenv 获取环境变量
  4. os.Setenv 写入环境变量
  5. exec.Command 快速启动子进程

实战

猜谜游戏

介绍

由程序生成0-100的随机数,由用户来进行猜测,程序返回输入的数字是大于小于或等于随机数,直到猜对为止

编写程序

第一步

生成0-100的随机数,使用math/rand包

package main
import(
    "fmt"
    "math/rand"
)
func main(){
    maxNum := 100
    secretNumber := rand.Init(maxNum) //生成随机数
}
  • 注意此时每次创建的随机数都是相同的数

    • 需要使用随机数种子来改变随机数

      package main
      import(
          "fmt"
          "math/rand"
          "time"
      )
      func main(){
          maxNum := 100
          //时间戳,精确到纳秒,使随机数改变更快速
          rand.Seed(time.Now().UnixNano())
          secretNumber := rand.Init(maxNum) //生成随机数
      }
      

第二步

获取用户输入并且解析为数字

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.TrimSuffix(input, "\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)

第三步

比较值的大小并且提示用户结果,直到猜对了

  • 所有猜数,生成随机数,读取用户输入均写在for循环中,直到用户猜对就break退出循环结束程序
  • 如果用户输入错误,则输出错误提示并且跳出当次循环,继续猜数

在线词典

介绍

实现一个命令行版本的在线词典即在命令行输入一个单词,打印输出单词的含义等信息

  • 使用的api
    • 抓包 fanyi.caiyunapp.com/ 右键网页页面检查
    • 随意输入一个单词,点击翻译按钮,在开发中页面的网络中找到POST请求(dist包)
    • 点击此包的负载和预览查看信息,即为单词对应的信息
    • source表示输入的英文,trans_type为翻译的中文
    • 需要实现此功能,必须在Go中去发送请求

编写程序

第一步:生成请求--代码生成

  • 右击浏览器的请求包(比如dist包) 点击复制,选择复制为cURL(bash)

  • 找一个终端(或记事本)去粘贴请求,为json格式

  • 代码生成网址 curlconverter.com/#go 打开之后,粘贴复制的请求,在语言选择Go,复制生成的代码,删掉一些编译错误的几行代码即可

  • 解读生成代码

    package main
    import (
    	"fmt"
    	"io/ioutil"
    	"log"
    	"net/http"
    	"strings"
    )
    func main() {
    	client := &http.Client{}
        //创建的data使用request生成
    	var data = strings.NewReader(`{"trans_type":"en2zh","source":"hello"}`)
        //创建请求,POST请求,url,data(一个流,减少内存的占用)
    	req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
    	if err != nil {
    		log.Fatal(err)
    	}
        //设置请求头
    	req.Header.Set("Accept", "application/json, text/plain, */*")
    	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
    	req.Header.Set("Connection", "keep-alive")
    	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
    	req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
    	req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
    	req.Header.Set("Sec-Fetch-Dest", "empty")
    	req.Header.Set("Sec-Fetch-Mode", "cors")
    	req.Header.Set("Sec-Fetch-Site", "cross-site")
    	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36 Edg/101.0.1210.39")
    	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", "Microsoft Edge";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)
    	}
        //打印响应
    	fmt.Printf("%s\n", bodyText)
    }
    

第二步:生成request body

用户需要用字符串输入,而不是用json输入,构造一个结构体,初始化结构体

type DictRequest struct {
	TransType string `json:"trans_type"`//翻译结果
	Source    string `json:"source"`	//需翻译内容
	UserID    string `json:"user_id"`
}
func main(){  
	request := DictRequest{TransType: "en2zh", Source: "good"}
	buf, err := json.Marshal(request)
	if err != nil {
		log.Fatal(err)
	}
    var data = bytes.NewReader(buf)
    //承接第一步
}

第三步:解析response body

生成的response为json格式,需要进行反序列化

写一个结构体,结构体保存输出的内容,将response反序列到结构体的字段名中并输出

为了减少繁琐以及出错

  • 使用代码生成
    • 打开 oktools.net/json2go
    • 复制dist包的响应中的json字符串到网站中
    • 点击转化-嵌套生成对应的结构体,点击复制
//承接第二步
//定义结构体变量
var dictResponse DictResponse
//反序列化
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
	log.Fatal(err)
}
//打印输出response结构体
fmt.Printf("%#v\n", dictResponse)

第四步:打印结果

提取需要的字段并打印输出

打印结构体中需要的字段名

fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
	fmt.Println(item)
}
//打印状态码,如果不是正常的(非200)打印出状态码方便解决出现的问题
if resp.StatusCode != 200 {
	log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}

最后提取方法即可

SOCKS5代理服务器

介绍

socks5协议相当于在防火墙开了一个口子,方便用户通过单个端口访问内部资源

运行程序之后,在浏览器配置socks,当访问某个网页时,会输出网页的请求

也可以执行命令 curl --socks5 ip:port -v url 注明:ip:IP地址 port:端口号 url:访问的地址

socks协议原理

  1. 浏览器和socks5代理服务器建立连接,然后代理服务器与真正的服务器建立连接
  2. 代理服务器与真正的服务器建立连接四个阶段
    1. 协商阶段 代理服务器选择一个支持认证的方式返回给服务器,告诉浏览器用什么建程方式
    2. 认证阶段
    3. 请求阶段 请求建立连接
    4. relay阶段 代理服务器收到请求到转到真正的服务器

编写程序

第一步:TCP echo 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.Printf("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 IP地址 端口号 如 nc 127.0.0.1 1080
//输入内容返回对应的相同内容

第二步:auth-认证阶段

选择建程方式0x00不需要认证

第三步:请求阶段

第四步:relay阶段

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

浏览器测试

  1. 安装插件 SwitchyOmega
  2. 点击新建情景模式,代理协议为socks5
  3. 选择应用选项