这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。 今天听了第一次课,学到了超多东西,我的基础比较薄弱,就在笔记里记录了很多课堂知识点,打算日后温故而知新。
本堂课重点内容:
Go语言基础语法
三个项目的实战(猜谜游戏、在线词典、Socks5 代理)
什么是go语言
- 高性能、高并发
- 语法简单、学习曲线平缓(容易学)
- 丰富的标准库
- 完善的工具链
- 静态链接
- 快速编译
- 跨平台
- 垃圾回收
哪些公司在使用Go语言
字节、Google、腾讯、facebook、美团、七牛云、滴滴······
字节为什么会全面拥抱Go语言
-
一开始是Python,性能问题换成Go
-
C++不太适合在线Web业务
-
早期团队非Java背景
-
性能比较好
-
部署简单、学习成本低
-
内部RPC和HTTP框架的推广
入门
开发环境 - 安装golang
配置集成开发环境(VSCode+go插件、Golang)
我用的是VSCode+插件,是跟着B站视频配置成功的:【Go】三分钟搭建VSCode的Go语言开发环境-2020.9版
基于云的开发环境(github)
基础语法
1 Hello World
文件属于main包的一部分,是程序的入口文件
导入标准库中的fmt包,fmt包主要作用:往屏幕输入输出字符串、格式化字符串
package main//文件属于main包的一部分,是程序的入口文件
import (
"fmt"//导入标准库中的fmt包,fmt包主要作用:往屏幕输入输出字符串、格式化字符串
)
func main() {
fmt.Println("hello world")//输出
}
两种方法来运行本文件
2 变量
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) // initialapple
const s string = "constant"//常量(根据上下文自动确定类型)
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
3 if else
//if后面的条件是没有括号的 //语句一定要加括号
func main() {
//if后面的条件是没有括号的
//语句一定要加括号
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
}
4 循环
Go里面只有for循环
//for后面什么都不写代表死循环
格式跟if else的很像条件是没有括号的,语句一定要加括号
5 switch
括号习惯跟前两个一样
case里不用加break也能自动跳出
6 数组
具有编号,且长度固定的元素序列
func main() {
var a [5]int//数组a
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
}
7 切片slice
切片 = 长度+容量+指向数组的指针
append追加元素,要把它赋值回原数组(会扩容,要更新数组)
copy用于在两个切片里拷贝数据
还有类似于Python的切片操作
8 map
类似于其他语言里的哈希/字典
要注意输出时的顺序是随机的
package main
import "fmt"
func main() {
m := make(map[string]int)//(map[key的类型]val的类型)
m["one"] = 1//写入
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"]//ok——有没有key存在
fmt.Println(r, ok) // 0 false
delete(m, "one")//删除
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)//输出顺序随机
}
9 range
快速遍历slice、map
类似于for(auto& k,v : map)
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
}
}
10 函数
函数的变量类型是后置的
有的能返回两个返回值,返回 结果+错误信息(是否存在)
11 指针
指针的重要作用在于对传入的参数进行修改
12 结构体
带类型的字段的集合
13 结构体方法
类成员函数
package main
import "fmt"
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: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
14 字符串操作
//字符串里是否包含另一个字符串
fmt.Println(strings.Contains(a, "ll"))
//字符串计数
fmt.Println(strings.Count(a, "l"))
//查找某个字符串的位置
fmt.Println(strings.Index(a, "ll"))
//连接多个字符串
fmt.Println(strings.Join([]string{"he", "llo"}, "-"))
//重复多个字符串
fmt.Println(strings.Repeat(a, 2))
//一个中文会对应多个字符
b := "你好"
fmt.Println(len(b)) // 6
15 字符串格式化
类似于C语言的printf,但类型都能用%v搞定
%+v打印出详细的值
%#v打印出更加详细的值
16 时间处理
用特定时间格式化一个时间到时间字符串 // 2022-03-27 01:25:36
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//获取时间戳
}
17 数字解析
strconv.ParseInt("111", 10, 64)//(字符串,进制,64位精度的整数)
Atoi:十进制->数字 Itoa:数字->字符串
package main
import (
"fmt"
"strconv"//string convert
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
n, _ := strconv.ParseInt("111", 10, 64)//(字符串,进制,64位精度的整数)
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123")//Atoi:十进制->数字 Itoa:数字->字符串
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
18 进程信息
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...//获取环境变量 fmt.Println(os.Setenv("AA", "BB"))//写入环境变量
实战
1 猜谜游戏
完整游戏代码
修改的内容都在注释里
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.TrimSuffix(input, "\r\n")//win系统要这样写
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 在线词典
输入单词,输出音标、解释
抓包
进去之后翻译good,然后右键点检测
代码生成
我们需要生成代码,这里有个巧妙的办法
右键找到的dict->COPY->COPY as cURL(bash) 复制粘贴到网站:curlconverter.com/#go
生成下面的go语言代码
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
func main() {
//创建http.Client
client := &http.Client{}
var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)//字符串转化成流
//创建请求
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)//(method,url,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 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 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="100", "Google Chrome";v="100"`)
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()//防止资源泄漏,defer手动关闭流,defer在函数结束后从下往上触发
//读取响应
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", bodyText)
}
上面代码运行之后会产生下面的东西
生成 request body
构造一个json构造体,字段与json一致,实现json序列化
解析 response body
一开始翻译网页的Preview
右键copy object复制到 oktools.net/json2go 转换-嵌套成go,并且将改名成 DictResponse
修改代码
将此时的DictResponse结构体添加到上面的代码中,并且将最后一行输出(90行)删掉,加上91-96行代码
//打印结构体
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%#v\n", dictResponse)
输出结果
打印结果
//出错处理
if resp.StatusCode != 200 {
log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
//留下音标、解释的信息
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
完善代码
- import添加 “os”
- 将main()函数修改名称为query(word string)函数,并且Source原先的"good"更改成变量word
- 重新写个main()函数
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)
}
执行结果:
3 Socks5 代理
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)//go类似于启动子线程
}
}
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 127.0.0.1 1080如果你出现了以下报错
那是因为win不自带netcat,要自己下载,感谢有同学分享的链接,让我成功运行此程序
之后你输入,它就会输出同样的内容
auth
请求阶段
relay阶段
后面的部分我还没搞定,思路是这样的,但具体实现我还没弄好
课后总结
青训营的内容太丰富了!我记了好久好久的笔记,那三个实战我还没完全理解,之后一定要找时间自己试着实现一遍