这是我参与「第三届青训营 -后端场」笔记创作活动的第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 ..... }
基本使用
- 定义:var 变量名 结构体名称
- 使用:变量名.属性
注意事项
结构体名称首字母大写,表示这个结构体别的导入之后可以使用。
结构体方法
有两种写法,一种是和对象绑定,一种是和对象指针绑定,后者在方法中改变对象属性,前者相当于拷贝一份对象进行操作。
方式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包下操作
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))
}
猜谜游戏
思路
- 使用标准库"math/rand"生成一个范围在[0,100]的数字,注意要注入随机种子才行,不然每次获取的随机数都是一样的
- 通过标准库"bufio"读取用户输入
- 处理用户输入的字符串,并转成数字和秘密树进行比较
- 比较正确,游戏结束
代码
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翻译
工具
- 在线翻译:fanyi.caiyunapp.com/
- JSON转GO结构体:oktools.net/json2go
- curl转Go代码:curlconverter.com/#go
思路
-
进入彩云翻译
-
将复制到的东西粘贴到”curl转Go代码“那个网站,然后我们就得到对应的Go代码了
-
我们运行这个Go程序,可以获取JSON格式数据
-
将这个数据粘贴到”JSON转GO结构体“网站获取对应的结构体,然后我们就可以进行代码的修改了
-
首先:生成请求体:var reqData = DictRequest{TransType: "en2zh", Source: word}
-
然后:解析JSON:err = json.Unmarshal(bodyText, &res)
代码
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代理
- 参考文章:cloud.tencent.com/developer/a…
- 我也不是很理解
代码
- 使用tcp协议,监听本地的1080端口
- 每次有一个客户端来,我们就开启一个协程来处理,连接的周期==process函数周期
- 在process函数里面,我们进行两个操作,一个是认证,一个是连接为客户端做代理
- 在认证方法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
}
作业
我是没搞懂这个,也花了大量时间,还是我太菜了。