Go语言上手 | 青训营笔记

Posted C+G

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言上手 | 青训营笔记相关的知识,希望对你有一定的参考价值。

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

文章目录

语法速览

基础语法

基础语法有几点需要注意:

第一:类型

有值类型,有指针,指针只能作为引用的替代品,无法指针直接运算。

go语言有值类型,可以直接像下面这样定义变量:

type Student struct 
	name string
	sid  string

func main()
	var student = Studentname: "John", sid: "1001"   //student为值类型
    var student = &Studentname: "John", sid: "1001" //student为指针类型(注意由于go有垃圾回收机制,所以这里会自动为我们开辟堆内存
    student := new(Student) //也可通过内置的new()函数直接开辟堆内存,而不立马初始化,得到一个指针


go语言的切片

同样切片类型也有上述两种获得内存的定义方式,也可通过内置的make函数对内部的cap和len进行初始的控制。

nums := make([]int,2,10)//得到一个底层数组长度为2,cap为10的初始切片
nums1 := nums2[0:3] //第二种切片方式

第二:内置库部分

json库的使用

通过在字段后面跟着的字符串进行序列化的定义,后面跟着的称为域标签。

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct 
	Name string `json:"name"`
	Sid  string `json:"sid"`


func main() 
	s := StudentName: "jonh" ,Sid: "10323"
	//序列化
	p ,err := json.Marshal(s)
	if err!=nil 
		panic(err)
	
	fmt.Println(string(p))
	
	//反序列化
	err = json.Unmarshal(p,&s)
	if err!=nil 
		panic(err)
	
	fmt.Println(s)


官方对域标签有以下说明:

// Field appears in JSON as key "myName".
Field int `json:"myName"`

// Field appears in JSON as key "myName" and
// the field is omitted from the object if its value is empty,
// as defined above.
Field int `json:"myName,omitempty"`

// Field appears in JSON as key "Field" (the default), but
// the field is skipped if empty.
// Note the leading comma.
Field int `json:",omitempty"`

// Field is ignored by this package.
Field int `json:"-"`

// Field appears in JSON as key "-".
Field int `json:"-,"`
时间库的使用

时间的获取

获取当前时间:

package main

import (
    "fmt"
    "time"
)

func main() 
    now := time.Now() //获取当前时间
    fmt.Printf("current time:%v\\n", now)
    year := now.Year()     //年
    month := now.Month()   //月
    day := now.Day()       //日
    hour := now.Hour()     //小时
    minute := now.Minute() //分钟
    second := now.Second() //秒
    fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\\n", year, month, day, hour, minute, second)

获取时间戳

package main

import (
    "fmt"
    "time"
)

func main() 
    now := time.Now()            //获取当前时间
    timestamp1 := now.Unix()     //时间戳
    timestamp2 := now.UnixNano() //纳秒时间戳
    fmt.Printf("现在的时间戳:%v\\n", timestamp1)
    fmt.Printf("现在的纳秒时间戳:%v\\n", timestamp2)

时间戳与时间的转换

package main

import (
    "fmt"
    "time"
)

func main() 
    now := time.Now()                  //获取当前时间
    timestamp := now.Unix()            //时间戳
    timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式
    fmt.Println(timeObj)
    year := timeObj.Year()     //年
    month := timeObj.Month()   //月
    day := timeObj.Day()       //日
    hour := timeObj.Hour()     //小时
    minute := timeObj.Minute() //分钟
    second := timeObj.Second() //秒
    fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\\n", year, month, day, hour, minute, second)

获取星期几

package main

import (
    "fmt"
    "time"
)

func main() 
    t := time.Now()
    fmt.Println(t.Weekday().String())

时间的操作

(1)Add(during)函数实现某个时间 + 时间间隔

package main
import (
    "fmt"
    "time"
)
func main() 
    now := time.Now()
    later := now.Add(time.Hour) // 当前时间加1小时后的时间
    fmt.Println(later)

(2)Sub(Time)获取时间差值

返回一个时间段 t - u 的值。如果结果超出了 Duration 可以表示的最大值或最小值,将返回最大值或最小值,要获取时间点 t - d(d 为 Duration),可以使用 t.Add(-d)。

(3)Equal(Time)判断时间是否相同

(4)Before 和 After某个时间是否在他之前或之后

定时任务

使用 time.Tick(时间间隔) 可以设置定时器,定时器的本质上是一个通道(channel)

package main
import (
    "fmt"
    "time"
)
func main() 
    ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器
    for i := range ticker 
        fmt.Println(i) //每秒都会执行的任务
    

解析字符串格式的时间

Parse 函数可以解析一个格式化的时间字符串并返回它代表的时间。

func Parse(layout, value string) (Time, error)

与 Parse 函数类似的还有 ParseInLocation 函数。

func ParseInLocation(layout, value string, loc *Location) (Time, error)

ParseInLocation 与 Parse 函数类似,但有两个重要的不同之处:

  • 第一,当缺少时区信息时,Parse 将时间解释为 UTC 时间,而 ParseInLocation 将返回值的 Location 设置为 loc;
  • 第二,当时间字符串提供了时区偏移量信息时,Parse 会尝试去匹配本地时区,而 ParseInLocation 会去匹配 loc。

示例代码如下:

package main
import (
    "fmt"
    "time"
)
func main() 
    var layout string = "2006-01-02 15:04:05"
    var timeStr string = "2019-12-12 15:22:12"
    timeObj1, _ := time.Parse(layout, timeStr)
    fmt.Println(timeObj1)
    timeObj2, _ := time.ParseInLocation(layout, timeStr, time.Local)
    fmt.Println(timeObj2)

字符串和数字互转

字符串与数字互转的想关库函数全在一个包内:strconv包

一图胜千言:

os相关信息

os包里面封装了很多和操作系统相关的内容。

如下:

package main

import (
	"fmt"
	"os"
	"os/exec"
)
func main() 
	fmt.Println(os.Args) //打印命令行参数
	fmt.Println(os.Getenv("PATH")) //打印环境变量
	fmt.Println(os.Setenv("AA","BB")) //设置环境变量,key,val形式设置
	buf,err := exec.Command("grep").CombinedOutput() //执行cmd命令
	if err != nil 
		panic(err)
	
	fmt.Println(string(buf))

其他pass跳过

实战项目

猜谜游戏(pass,过于简单)

在线词典

想要实现在线词典,首先就得用到别人的翻译引擎

第一步:抓包得得到数据进行分析

以彩云词典为例:

从网页调试工具里面查看随时收发的网络数据包,挨个查看它们的response,如果里面的json数据出现翻译结果,那么说明这个包就是返回的翻译结果!

那么我们只需要让go语言来做同样的两件事:

  1. 发起请求。
  2. 解析返回的json内容。

只要做好了这两件事,那么就很快得到了一个单词的翻译了。

第二步:利用工具生成代码

在此之前我们需要清楚有两个神器般存在的网站:

  1. curlconverter 把curl请求直接转为go的请求代码。
  2. oktools JSON转Golang Struct

那么我们先肯定是要得到请求的代码,然后稍作更改,解析body后得出想要的结果。

curl请求直接转为go的请求代码

如下图进到刚才我们捕捉到的目标包,然后复制cURL,到网站进行解析得到最终代码。

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

func main() 
	client := &http.Client
	var data = strings.NewReader(`"trans_type":"en2zh","source":"hello"`)
	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("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`)
	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/99.0.4844.51 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)
	
	fmt.Printf("%s\\n", bodyText)

观察代码的改变我们只需对source部分的内容进行更改,即可得到对应的翻译结果。

JSON转Golang Struct

得到翻译结果,body内容后,我们需要把body内容解析为本地的sturct才能正常使用(当然你头铁的话可以直接找对应的字符串即可,也不需要反序列化。

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"`

第三步:更改代码实现功能

通过前面生成的代码已经能够实现请求和接收响应,且可以直接把响应内容反序列化为结构体,那么接下来,只需要把想要的部分遍历打印即可。

最终代码如下:

package src

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

func QueryCaiyun(word string) 
	client := &http.Client
	request := DictRequestCaiyunTransType: "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("Connection", "keep-alive")
	req.Header.Set("sec-ch-ua", `" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"`)
	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/99.0.4844.51 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)
	
	if resp.StatusCode != 200  //防止返回错误
		log.Fatal("bad Status code:", resp.StatusCode, "body", string(bodyText))
	
	var dictResponse DictResponseCaiyun
	err = json.Unmarshal(bodyText, &dictResponse)
	if err != nil 
		log.Fatal(err)
	

	fmt.Println("translate from Caiyun\\n", "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
	for _, item := range dictResponse.Dictionary.Explanations 
		fmt.Println(item)
	

homework

后面我依次通过这个方式还弄了其他的翻译引擎,但是有很多翻译引擎的请求头都是动态实时的,或者加了密。我做的第二个有道的翻译引擎使用就是动态实时的,然后去查阅了破解方法,发现是通过阅读原本的js源码进行反推,得出请求头里面动态变化的内容。

最终写了Deepl和有道两个搜索引擎。

源码实现链接:https://github.com/ACking-you/TraningCamp/tree/master/lesson1/homework/simple_dict/src

SOCKS5代理服务器

SOCKS5简单介绍

SOCKS5是代理协议,在使用TCP/IP协议通信的前端机器和服务器之间发挥中介作用,使内部网络的前端机器能够访问互联网的服务器,使通信更加安全。SOCKS5服务器将前端发送的请求转发给真正的目标服务器,模拟前端行为。此处,前端与SOCKS5之间也是通过TCP/IP协议进行通信的,前端向SOCKS5服务器发送请求发送给SOCKS5服务器,然后SOCKS5服务器将请求发送给真正的服务器。SOCKS5服务器在将通讯请求发送给真正服务器的过程中,对于请求数据包本身不加任何改变(明文传输)。SOCKS5服务器在收到真实服务器响应后,也原样转发到前端。

它的用途是, 比如某些企业的内网为了确保安全性,有很严格的防火墙策略,但是带来的副作用就是访问某些资源会很麻烦。 socks5 相当于在防火墙开了个口子,让授权的用户可以通过单个端口去访问内部的所有资源。实际上很多翻墙软件,最终暴露的也是一个 socks5 协议的端口。

SOCKS5代理原理

正常浏览器访问一个网站,如果不经过代理服务器的话,就是先和对方的网站建立 TCP 连接,然后三次握手,握手完之后发起 HTTP 请求,然后服务返回 HTTP 响应。如果设置代理服务器之后,流程会变得复杂一些。 首先是浏览器和 socks5 代理建立 TCP 连接,代理再和真正的服务器建立 TCP 连接。这里可以分成四个阶段,握手阶段、认证阶段、请求阶段、 relay 阶段

  • 握手阶段:浏览器会向 socks5 代理发送请求,包的内容包括一个协议的版本号,还有支持的认证的种类,socks5 服务器会选中一个认证方式,返回给浏览器。如果返回的是 00 的话就代表不需要认证,返回其他类型的话会开始认证流程,这里我们就不对认证流程进行概述了。(本次课程跳过认证阶段)
  • 请求阶段:认证通过之后浏览器会对 socks5 服务器发起请求。主要信息包括 版本号,请求的类型,一般主要是 connection 请求,就代表代理服务器要和某个域名或者某个 IP 地址某个端口建立 TCP 连接。代理服务器收到响应之后,会真正和后端服务器建立连接,然后返回一个响应。
  • relay 阶段:此时浏览器会发送 正常发送请求,然后代理服务器接收到请求之后,会直接把请求转换到真正的服务器上。然后如果真正的服务器以后返回响应的话,那么也会把请求转发到浏览器这边。然后实际上 代理服务器并不关心流量的细节,可以是 HTTP流量,也可以是其它 TCP 流量。 这个就是 socks5 协议的工作原理。

以上是关于Go语言上手 | 青训营笔记的主要内容,如果未能解决你的问题,请参考以下文章

GO语言的实战学习(猜谜游戏和在线词典)| 青训营笔记

对于Go 语言的进阶与依赖管理| 青训营笔记

前端语言串讲 | 青训营笔记

青训营HTML基础 - 语义化标签 - 浏览器渲染过程 - 笔记及拓展

字节青训营-后端练习题-编程题1(36进制加法)

青训营月影老师告诉我写好JavaScript的三大原则之——各司其责