前言

第一次用go是大二的时候,那个时候招新用到github上的项目,但是万万没想到的是那个项目有个bug,redis里的session没有清除机制,一段时间后就会爆满导致网站服务崩溃,一开始测试的时候没问题,但是上线那一天一上强度就503了。那段时间压力爆炸,改时间已经来不及了已经有部分题目被学弟写了,他那个框架我也没学过修bug也来不及了。也是疯狂查资料,最后用的一个定时清除reids连接的go脚本,打包编译上传到docker里,才解决这个问题,这时我对go的印象是作为脚本可以一点不依靠环境配置,docker里面什么语言环境都没有,但是我用go生成的二进制文件直接就执行成功了

最近想写资产测绘工具当作毕设,并且平时挖src也能用上,就简单分析了一个go写的开源渗透工具,看完这个工具后也是感觉到了go语言的便利,有类似python的弱类型,有类似java的类和包结构,甚至还有类似C的指针和结构体,也正因如此就决定毕设用go写而不是用更熟悉的python写

go语言的基础命令

1
2
3
4
5
go run hello.go 
go build hello.go #生成二进制文件
./hello
go env -w GOPROXY="https://goproxy.cn,direct" #设置导包的代理
go install example.com/mytool@latest #相当于拉取源码后build,下载打包好的可执行文件到go\workpace\bin

Go语言环境配置

GOROOT:指向 Go 安装目录,通常是 Go 自带的安装路径,包含了 Go 的标准库、工具链等

GOPATH:指向你的工作空间目录
src:源代码存放目录,包括你自己的项目代码和go install导入的外部包。
pkg:编译后的包文件存放目录,包括使用 go install 命令生成的二进制文件。
bin:可执行文件存放目录,包括使用 go install 命令生成的可执行文件。

GOMODCACHE:但是GOPATH规定编程只能在$GOPATH/src下很是麻烦,所以从 Go 1.11 开始,Go 模块系统简化了依赖管理,并使得 GOPATH 不再是必需的,允许你在任何目录下开发 Go 项目
依赖管理通过go.mod(记录模块的依赖关系、版本等信息)和go.sum(保证了这些依赖的完整性和安全性)文件来完成(类似于python的pip install -r requirements.txt),下载的包在$GOPATH/pkg/mod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#开启GOMODCACHE
export GO111MODULE=on #on, 不管在不在gopath/src下,都采用的go mod包管理方式

#初始化 Go 模块:
go mod init <module-name>

#添加或更新依赖到$GOPATH/pkg/mod,修改 go.mod:
go get <package>@<version>

#清理未使用的依赖项,生成或更新 go.sum:
go mod tidy

#验证依赖的完整性:
go mod verify

go v1.5引入vendor模式,如果项目目录下有vendor目录,那么go工具链会优先使用vendor内的包进行编译、测试

go基础语法

变量类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//变量
var a int = 10
a := 10 //支持弱类型
var a = 10
a:=interface{} //空接口可以持有任何类型的值

//map
m1 := map[string]string{ //[键]值
"a": "test1",
"b": "test2",
}
m1 ["c"] = "test3"

//数组
var a [10] string //数组的大小是固定的
var b = [...] int {1,2,3,4,5} //自行推断数组的长度

//结构体
type conf struct {
name string
age int
}
a := conf{name: "d4m1ts", age: 18}//创建一个实例

conf := new(conf)//创建了一个指向 Conf 类型结构体的指针,并初始化了 Conf 结构体的零值
conf := &Conf{}
conf.name = "d4m1ts"//修改name字段的值

指针的使用

1
2
3
4
5
6
7
8
9
10
11
//指针
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */

func appendOne(s *[]int) { //不传入指针,就只是切片的拷贝
*s = append(*s, 1)
}
func main() {
s2 := []int{0, 0}
appendOne(&s2)
}

关于切片

1
2
3
4
5
6
7
8
//切片大小是不固定的
var c []string //定义切片
var numbers = make([]int,3,5) //[0 0 0] 长度为3,容量为5
c := [] string {"aa", "bb", "cc"} //切片是动态的
c = append(c, "ee","ff","gg")//同时添加多个元素
d1 := [10]int {1,3,5,7,9} //使用数组来初始化切片
x := d1[2:]
c = append(c, x...) //合并2个切片,注意的是要添加...

本来想着学完go语言之后刷刷ctf题目,结果在一道2019年题目中发现有一个有意思的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//空切片,同var a []int
a := make([]int, 0)//len=0 cap=0 slice=[]

a = append(a, 1)//len=1 cap=1 slice=[1]
a = append(a, 2)//len=2 cap=2 slice=[1 2]
a = append(a, 3)//len=3 cap=4 slice=[1 2 3]
a = append(a, 4)//len=4 cap=4 slice=[1 2 3 4]
a = append(a, 5)//len=5 cap=8 slice=[1 2 3 4 5]
b := append(a, 6)//len=6 cap=8 slice=[1 2 3 4 5 6]
c := append(a, 7)//len=6 cap=8 slice=[1 2 3 4 5 7]

fmt.Println(a)//[1,2,3,4,5]
fmt.Println(b)//[1,2,3,4,5,7]
fmt.Println(c)//[1,2,3,4,5,7]

为什么b最后一位是7拿,因为切片存储空间cap是2的倍数,如果是存储空间cap从5->9,它就会深拷贝到一个cap16的地方,在本例子中,是从cap5->6,那么b就是a的引用然后len为6,后面c跟b的情况一样都是更改了a的后一位,导致b也被改变了

很神奇,它这个地方居然是深拷贝和浅拷贝的混用,我第一眼看到那道题的时候也懵了

逻辑语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//for语句
func main() {
a := [] int {1,2,3,4,5}
for _,i := range a {
fmt.Println(i)
}
}

for sum <= 10{
sum += sum
}

func text() {
for i := 0; i < 5; i++ {
fmt.Printf("%d ", i)
defer fmt.Printf("%d ", i)
}
fmt.Printf("%d ", 11)
}//0 1 2 3 4 11 4 3 2 1 0

package test
//静态函数
func TestFunc1(a int, b int) int {
return a+b
}
s= test.TestFunc1(1,2)

// 定义接口
type testImpl interface {
testFunc1(a int, b int)
}
// 定义结构体
type testStruct struct {
}
// 实现结构体的接口函数,形成类
func (testMethod testStruct) TestFunc1(a int, b int) int {
return a+b
}
var test1 testImpl
test1 := new(testStruct) // 实例化
res1 := test1.TestFunc1(10,20)
fmt.Println(res1)

先写个大概,开发时遇到不会写的再往上加

go自带的常用包

并发与管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import (
"fmt"
"sync"
)
//select
ch1 := make(chan string) // 定义两个通道
ch2 := make(chan string)
select {
case msg1 := <-ch1://当通道有值时,case可以运行,如果有多个 case 都可以运行,Select 会随机公平地选出一个执行
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
default:// 如果两个通道都没有可用的数据,则执行这里的语句;
//如果没有default,并且没有case可运行,它将阻塞,直到有case可运行
fmt.Println("no message received")
}

//go程(go调用的协程):M 个 Go程映射到 N 个线程,减少了线程的创建和切换开销
func loop(res chan int) {
i := 1
for i < 5 {
i += 1
res <- i //把i发送到通道res
}
}

res := make(chan int, 5) //开启一个管道res类型为int,缓存区5代表最多可以开5个协程
go loop(res) //开一个go程
fmt.Println(<-res) // 2,从res通道中接收一个结果
fmt.Println(<-res) // 3
for r := range res{ // 遍历通道
fmt.Println(r)
}
fmt.Println(<-res) // 0,表示已经消费完了


//但主协程(main)结束时,子协程(Go程)也会终止,可以使用sync让所有子协程都可以执行完
var wg sync.WaitGroup
func worker(id int) {
defer wg.Done() // defer当 goroutine 完成时,调用 Done(),减少计数器
fmt.Printf("Worker %d is working\n", id)
}
for i := 1; i <= 5; i++ {
wg.Add(1) // 每次启动一个新的 goroutine,增加计数器
go worker(i) // 启动 goroutine
}

wg.Wait() // 等待所有 goroutine 完成
fmt.Println("All workers are done!")

//原子操作,保证同一时刻只有一个 goroutine 能够执行临界区代码
var mu sync.Mutex
var count int
func increment(){
mu.Lock() // 锁定
count++
mu.Unlock() // 解锁
}
var wg sync.WaitGroup
for i := 0; i < 10; i++ { // 启动 10 个 goroutine
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}

wg.Wait() // 等待所有 goroutine 完成
fmt.Println("Final count:", count)

context

context 可以在多个 goroutine 之间共享取消信号或超时信号,很多 Go 的标准库和第三方库(如 net/http、database/sql 等)都与 context 紧密集成。例如,HTTP 请求的超时、数据库查询的取消操作等都依赖于 context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import (
"context"
"fmt"
"time"
)

func main() {
// ctx:=context.Background()
// ctx, cancel := context.WithCancel(context.Background())
// ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) //创建了一个过期时间为 1s 的上下文
defer cancel() //cancel 是一个取消函数,它会在超时或手动调用时通知 ctx

go handle(ctx, 500*time.Millisecond)
select {
case <-ctx.Done(): //当超时发生时,ctx.Done() 会被触发
fmt.Println("main", ctx.Err()) //Err — 返回context.Context结束的原因
deadline, _ := ctx.Deadline(); //返回超时时间
}
}

func handle(ctx context.Context, duration time.Duration) {
select {
case <-ctx.Done():
fmt.Println("handle", ctx.Err())
case <-time.After(duration):
fmt.Println("process request with", duration)
}
}

输入输出

1
2
3
4
5
6
7
8
9
10
11
12
import "fmt"
/*
fmt.Scan 会读取空白分隔符分隔的值;
fmt.Scanln 会在遇到换行符时停止读取。
*/
fmt.Scan(&name)
fmt.Print("Enter your age: ")
fmt.Scanln(&age)

fmt.Println("Hello, World!") // 输出:Hello, World!/n
fmt.Printf("Hello, %v!\n", name) // 输出:Hello, Go!, %v:用于格式化任何类型的变量,以默认方式输出
var sentence = fmt.Sprintf("hello %s %s %d", keyword, name, age)//格式化拼接

基本数据类型处理

strconv类型转化

Go 的类型转换是显式的,你必须告诉编译器要如何转换类型,以避免类型不兼容问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import (
"fmt"
"strconv"
)
func main() {
// int 转字符串
str := strconv.Itoa(n)//Integer to ASCII
str := strconv.FormatInt(255, 10)//也可以用FormatInt
fmt.Println(str) // 输出字符串"255"

// 字符串转 int
num, err := strconv.Atoi(str)

// int 转 float64
var i int = 42
var f float64 = float64(i)

// float64 转 int
var x float64 = 42.58
var y int = int(x) //42

// 字符串转布尔值
str := "true"
b, err := strconv.ParseBool(str)

// 布尔值转字符串
var flag bool = true
s := strconv.FormatBool(flag)
}

strings处理字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import "strings"
import "fmt"

func main() {
fmt.Println(strings.Contains("hello world", "world")) // 输出: true
fmt.Println(strings.Contains("hello world", "golang")) // 输出: false
fmt.Println(strings.ToLower("HELLO WORLD")) // 输出: hello world
fmt.Println(strings.ToUpper("hello world")) // 输出: HELLO WORLD
result := strings.Split("a,b,c,d", ",")
fmt.Println(result) // 输出: [a b c d]fmt.Println(strings.Replace("hello world", "world", "golang", 1)) // 输出: hello golang
result := strings.Join([]string{"a", "b", "c", "d"}, ",")
fmt.Println(result) // 输出: a,b,c,d
fmt.Println(strings.Index("hello world", "world")) // 输出: 6
fmt.Println(strings.Index("hello world", "golang")) // 输出: -1
fmt.Println(strings.HasPrefix("hello world", "hello")) // 输出: true
fmt.Println(strings.HasSuffix("hello world", "world")) // 输出: true
}

文件操作

1
2
3
4
5
6
7
import (
"io"
"io/ioutil" //与java不同,go要求在导入子包时必须显式地导入
"path/filepath"
"os"
"bufio"
)

获得当前路径

1
2
3
cwd, _ := os.Getwd() // 获取当前工作目录,由你运行程序时所在的目录决定,比方说你在c路径运行一个e路径的文件,工作目录还是c路径
exePath:=os.Args[0]//运行该程序时指定的路径,注意:如果是本地调试运行没打包的化,会生成的临时可执行文件,如C:\Users\DELL\AppData\Local\Temp\GoLand\___go_build_test_go.exe
exePath, err := os.Executable()

路径处理

1
2
3
4
5
6
path := filepath.Join("api", "mayylu", "file.txt")//api\mayylu\file.txt
cleanPath := filepath.Clean(path) //规范化路由
dir, file := filepath.Split(path)
dir:=filepath.Dir(path)
absPath, err := filepath.Abs("file.txt") // 转换相对路径为绝对路径
ext := filepath.Ext(path)

展示路径下的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    f, err := os.Open("./directory") // 打开目录,和打开文件的方法一样
if err != nil {
fmt.Println("打开目录时出错:", err)
return
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() {//判断是否是目录
l, _ := f.Readdir(0)// 读取目录中的所有文件和子目录
}
for _, file := range l {
fmt.Println(file.Name())//打印目录中的文件和目录
}

files, _ := fileutil.ListFileNames("./directory")// 读取目录中的所有文件

获取文件的信息

1
2
3
4
5
6
7
_, err := os.Stat(filepath)  
if os.IsNotExist(err) { // 判断文件是否不存在
// 如果文件不存在
err := os.Mkdir("new_directory", 0755) // 创建目录,权限为 0755
}
if fileutil.MiMeType(filepath) == "application/zip" {//判断文件类型信息
}

读文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
file,_ := os.Open("test.txt") //简化的文件打开函数,通常用于以 只读 的方式打开文件
defer file.Close()
byteRes, _ := ioutil.ReadAll(file) // 返回存放结果
fmt.Println(string(byteRes))

data, _ := ioutil.ReadFile("test.txt")
fmt.Println(string(data))

//利用Scanner按行读取
datas := []string{}
scanner := bufio.NewScanner(file)
for scanner.Scan(){
datas = append(datas, scanner.Text())
}

//embed 包利用注释,嵌入文件
import (
_ "embed"
"fmt"
)
//go:embed hello.txt
var content string

func main() {
fmt.Println(content) // 输出嵌入的文件内容
}

写文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

fileutil.CreateDir(userDir)//创建目录
gobFile, _ := os.Create(userDir + "user.gob")//创建文件
/*
os.O_RDONLY:只读模式。
os.O_WRONLY:只写模式。
os.O_RDWR:读写模式。
os.O_CREATE:如果文件不存在,则创建文件。
os.O_APPEND:追加模式,写入内容时会自动追加到文件末尾。
*/
file,_ := os.OpenFile("test.txt", os.O_RDWR | os.O_APPEND | os.O_CREATE, 0666) // 按照特定权限打开,0666为新创建文件的权限
defer file.Close()

data := []byte("hello d4m1ts")
count, _ := file.Write(data) // 按字节写入,返回的count为写入的字节数
count, _ = file.WriteString("\nHello D4m1ts") // 按字符串写入
file.Sync() //如果没有调用 file.Sync(),默认在文件关闭时自动同步文件的内容

data := []byte("hello d4m1ts")
ioutil.WriteFile("test.txt", data, 0666)

系统信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import (
"os/exec"
"runtime"
"time"
"math/rand"
)
s:=os.Args[1:]//是用户在运行程序时传入的实际参数(如果有的话)
sysType := runtime.GOOS //获取当前程序运行的操作系统名称

i:=time.Now().Unix() //获得时间戳
duration := time.Duration(5) * time.Minute //5d
duration := 1*time.Second //1s
duration := 500*time.Millisecond //500ms
select {
case ...
.....
case <-time.After(duration)://阻塞代码,一般运用等待执行
fmt.Println("process request with", duration)
}

命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import (
"os/exec"
"runtime"
)
cmd := exec.Command("ls", "-l")//命令执行

output, err :=cmd.Output()// 只捕获标准输出 (stdout),如果命令失败,err 会包含错误
output, err := cmd.CombinedOutput() //执行命令并返回命令的标准输出和标准错误的合并输出
if err != nil {
fmt.Println("runcmd err : " + fmt.Sprint(err) + ":" + string(cmdOutput))
}
var outb, errs bytes.Buffer// 创建两个字节缓冲区,分别用于捕获标准输出和标准错误
cmd.Stdout = &outb
cmd.Stderr = &errs
err := cmd.Run()//只执行命令并返回 error。

os.Exit(0) // 正常退出
os.Exit(1) // 错误退出

网络请求

域名/ip处理

1
2
3
4
5
6
7
8
9
10
import (
"io/ioutil"
"net"
)
host := "127.0.0.1"
port := "8080"
address := net.JoinHostPort(host, port)
fmt.Println(address) // 输出 "127.0.0.1:8080"
host, port, err := net.SplitHostPort(address)

HTTP请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import (
"io/ioutil"
"net/http"
"net/url"
)
//直接http post
http.Post(u, "application/x-www-form-urlencoded", strings.NewReader("aa=bb"))//将字符串封装成io.Reader类型的对象
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))

//直接http get
baseURL := "https://www.baidu.com/"
Url, _ := baseURL.Parse(u)
param.Set("name", "d4m1ts")
Url.RawQuery = param.Encode()//url编码,并设置参数
//fmt.Sprintf("%s?%s", baseURL, params.Encode())
response,_ := http.Get(Url.String()) // 发起get请求
defer response.Body.Close()//可写可不写,不强制

// 创建一个HTTP请求,但是不发送请求
u := "http://httpbin.org/"
//req,_:= http.NewRequestWithContext(ctx, "GET", "https://example.com", nil)使得请求在context规定的时间内如果没有完成就会自动被取消
req, _ := http.NewRequest("GET", u, nil) //nil 在 Go 中表示一个零值,用于表示一个空的或未初始化的变量,这里指没有post body数据
req.Header.Set("User-Agent", "Test GO")//自定义header头

transport := &http.Transport{
Proxy: http.ProxyFromEnvironment, // 如果有代理,使用环境变量中配置的代理设置
DialContext: (&net.Dialer{
Timeout: 60 * time.Second, // 设置连接超时时间
KeepAlive: 60 * time.Second, // 设置连接保持时间
}).DialContext, // 使用 DialContext 来自定义连接建立过程
MaxIdleConnsPerHost: 100, // 限制每个主机的最大空闲连接数(负数表示不限制)
MaxIdleConns: 100, //整个连接池中可以保持的最大空闲连接数
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, //跳过 SSL/TLS 证书验证
},
DisableKeepAlives: true, // 禁用 HTTP Keep-Alive,关闭持久连接
}
client := http.Client{
Timeout: 3*time.Second, // 超时时间
Transport: transport, //http.Transport是一个结构体,保存了连接管理配置信息
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse /* 不进入重定向 */
},
}
// 发送刚才创建的请求
response, _ := client.Do(req)

fmt.Println(response.StatusCode)
fmt.Println(response.Header.Get("Server"))
body, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(body))

正则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import (
"fmt"
"regexp"
)
regex, _ := regexp.Compile("(\\d{1,3})\\d{1,3}") // 编译正则表达式,匹配1到3个数字字符
fmt.Println(regex.MatchString("123123123123")) //匹配, true

fmt.Println(regex.FindString("123213123123")) //返回第一个匹配的子字符串123213
fmt.Println(regex.FindStringIndex("123213123123")) //返回第一个匹配子串的起始和结束索引[0 6]
fmt.Println(regex.FindStringSubmatch("123213123123")) //返回第一个匹配的子串及所有子匹配[123213 123]
fmt.Println(regex.FindAllString("123213123123",-1)) //返回所有匹配的字符串[123213 123123] n表示为返回个数,-1则返回全部
fmt.Println(regex.FindAllStringSubmatch("123213123123",-1)) //返回所有匹配的子串以及它们的子匹配[[123213 123] [123123 123]]
fmt.Println(regex.FindAll([]byte("123123123123"), -1)) //通过字节去匹配,返回的也是字节的结果[[49 50 51 49 50 51] [49 50 51 49 50 51]]

fmt.Println(regex.ReplaceAllString("123123123213","a")) //替换, aa

配置文件解析

yaml

1
2
3
4
5
6
7
8
9
10
cache:
enable : false
list :
- aaa
- bbb
mysql:
user : root
password : haha
port : 3306
type: Fingerprint
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import (
"fmt"
"gopkg.in/yaml.v3"
"io/ioutil"
)
func main() {
// 定义结构体
type Yaml struct {
Cache struct{
Enable bool `yaml:"enable"`
List []string `yaml:"list,flow"`
}
Mysql struct{
User string `yaml:"user"`
Password string `yaml:"password"`
Port int `yaml:"port"`
}
Type string `yaml:"type,omitempty"`
}
result := new(Yaml)
yamlFile, _ := ioutil.ReadFile("test.yml")
_ = yaml.Unmarshal(yamlFile, result) //提取yaml
result.Cache.Enable:=True

yamlFile, _ := os.OpenFile("test.yml", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
defer yamlFile.Close()
enc := yaml.NewEncoder(yamlFile)
err := enc.Encode(result)//写yaml
}
//viper
import (
"github.com/spf13/viper"
)
vp := viper.New()
vp.SetConfigName("config")
vp.AddConfigPath("/var/")
vp.SetConfigType("yaml")
err := vp.ReadInConfig()
if err != nil {
return nil, err
}
// 使用UnmarshalKey来将"database"部分的配置反序列化到结构体config的Database属性
if err := vp.UnmarshalKey("database", &config.Database); err != nil {
fmt.Println("Error unmarshalling key:", err)
return
}
}

json快速解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import (
"encoding/json"
"fmt"
"github.com/json-iterator/go"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)

// 定义结构体,变量名注意大写,因为跨到json包了
type User struct {
Name string
Age int
}

func main() {
// 初始化结构体
user := User{
Name: "d4m1ts",
Age: 18,
}

// 序列化,转字符串
s1, _ := json.Marshal(user)
fmt.Println(string(s1))

// 反序列化,恢复为原来的结构
user2 := User{}
json.Unmarshal(s1, &user2)
fmt.Println(user2)

}
json := `{"name": "John", "age": 30, "address": {"city": {"country": "china", "name": "henu"}, "zip": "10001"}}`
// 标准库 encoding/json 在使用时需要预先定义结构体,使用时显得不够灵活。这时候可以尝试使用其他模块
address := gjson.Get(json, "address")
enList:= address.Get("city").Array()
// 修改字段值
json, _ = sjson.Set(json, "name", "Jane")
// 添加新字段
json, _ = sjson.Set(json, "address.city", "New York")
// 删除字段
json, _ = sjson.Delete(json, "address.city")

// github.com/json-iterator/go 模块
name := jsoniter.Get([]byte(json), "name")
fmt.Println("Name:", name.ToString())

go web

web系统编译设计原理都是相通的,翻来覆去就那些东西

路由和传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import (
"github.com/fvbock/endless" //允许你在不中断现有连接的情况下重启 Go HTTP 服务
"github.com/gin-gonic/gin"
"github.com/astaxie/beego/validation"//使用beego框架的校验器
)
type auth struct {
Username string `valid:"Required; MaxSize(50)"`//Required表示该字段是必填项,不能为空。MaxSize(50)表示最大长度为50个字符
Password string `valid:"Required; MaxSize(50)"`
}

func GetAuth(c *gin.Context) {
url := c.Request.URL.String()//如http://127.0.0.1:8088/example?1=1,获取/example?1=1

username := c.Query("username") //c.Param("name")读取路径中的参数,c.Query为get传参
username:=c.DefaultQuery("username", "admin")//如果没有传参就为默认值
password := c.DefaultPostForm("password", "0") // DefaultPostForm,只支持string类型,因此需要进行类型的转换
if err := c.ShouldBindJSON(&person); err != nil {}// 绑定JSON到结构体

session := sessions.Default(c)
session.Set("shallow", userDir)//写入session
shallow:=session.Get("shallow")//获取信息
session.Save()

file, err := c.FormFile("file")//从 HTTP 请求中获取上传的文件
ext := file.Filename[strings.LastIndex(file.Filename, "."):]//获取文件扩展名
filename := userUploadDir + file.Filename
err = c.SaveUploadedFile(file, filename)//将上传的文件保存到服务器上的指定位置


valid := validation.Validation{}// 创建一个验证器
a := auth{Username: username, Password: password}
ok, _ := valid.Valid(&a)// 验证数据的有效性
data := make(map[string]interface{})
code := 400
if ok {
} else {
c.Redirect(http.StatusFound, "/")//StatusFound=302
}
c.JSON(http.StatusOK, gin.H{ //返回json
"code" : code,
"data" : data,
})
}
//gin.Default()=gin.New()+r.Use(gin.Logger())+ r.Use(gin.Recovery())
r := gin.New() //创建一个没有任何默认中间件的 Gin 引擎实例
//use添加中间件,默认情况下请求将沿中间件的添加顺序依次处理
r.Use(gin.Logger()) // 添加日志中间件
r.Use(gin.Recovery()) // 添加恢复中间件
r.Use(Cors()) // 添加跨域中间件

gin.SetMode(global.ServerSetting.RunMode)//设置 Gin 的运行模式,可以是 "debug"、"release" 或 "test"
r.GET("/api/v1/auth", GetAuth) // 添加路由处理函数

apiv1 := r.Group("/api/v1") //创建一个路由组,所有在 /api/v1 下的路由都会归到这个组。
apiv1.GET("/downtasklog/:id", v1.DownTaskLog) //绑定到 /api/v1/downtasklog/:id 路由上
apiv1.Use(Midderware) //为路由组添加JWT中间件

endless.DefaultReadTimeOut = 60
endless.DefaultWriteTimeOut = 60
endless.DefaultMaxHeaderBytes = 1 << 20
server := endless.NewServer(8081, r) // 创建endless服务器
err := server.ListenAndServe()


func Midderware() (c *gin.Context) {
token := c.GetHeader("Authorization")
if token == '' {
c.Abort()//设置完成当前中间件后,不会再执行后面的中间件
return//结束当前中间件的处理,继续执行下一个中间件
}
c.Next()//继续执行下一个中间件,然后再回来
//.....
}


鉴权

jwt鉴权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import (
"github.com/dgrijalva/jwt-go"
"time"
)

var jwtSecret = []byte("213123dd1")

type Claims struct {
Username string `json:"username"`
Password string `json:"password"`
jwt.StandardClaims
}

func GenerateToken(username, password string) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(3 * time.Hour)

claims := Claims{
username,
password,
jwt.StandardClaims{
ExpiresAt: expireTime.Unix(),// 设置过期时间
Issuer: "linglong",
},
}

tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 创建一个新的 JWT Token
token, err := tokenClaims.SignedString(jwtSecret) // 使用密钥签名 JWT Token
return token, err//// 输出生成的 JWT
}

func ParseToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})

if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {// 如果 token 有效并且被解析
return claims, nil
}
}
return nil, err
}


func JWT() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
code = e.INVALID_PARAMS
} else {
claims, err := utils.ParseToken(token)
if claims == nil{
c.JSON(http.StatusUnauthorized, gin.H{
"code" : 401,
"data" : data,
})
c.Abort()//c.Abort():中止请求,不会继续流转,也不会执行后续的中间件或处理函数
return
}
if err != nil {
code = e.ERROR
} else if time.Now().Unix() > claims.ExpiresAt {
code =e.ERROR
}
}
c.Next()//c.Next() 会让当前的请求继续流转到下一个中间件或者最终的处理函数
}
}

数据库操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)

var db *gorm.DB
db, err = gorm.Open("mysql", "user:password@tcp(localhost:3306)/dbname?charset=utf8&parseTime=True&loc=Local") //连接数据库

gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
return global.DatabaseSetting.TablePrefix + defaultTableName//可以方便地为所有表名添加统一的前缀,便于管理和区分如app_user
}

db.SingularTable(true) //默认false,gorm 会将结构体名称的复数形式作为表名,如结构体 User 对应的表名是 users。设置true,结构体 User 对应的表名是 user
db.LogMode(true)

//操作
//插入单条数据
student := Student{Name: "1", Age: 18, Job: "1"}
tx := db.Create(&student) // 通过数据的指针来创建

//插入多条数据
studs := []*Student{
&Student{Name: "aa", Age: 1, Job: "aa"},
&Student{Name: "bb", Age: 2, Job: "bb"},
}
tx := db.Create(&studs)

tx.Error // 返回 error
tx.RowsAffected // 返回插入记录的条数

db.Create(&mainurllist)
db.Model(&User{}).Create(map[string]interface{}{ //默认 结构体 User 对应的表名是 users
"Name": "jinzhu", "Age": 18,
})

db.Delete(&user)
db.Where("id = " + strconv.Itoa(id)).Delete(&Finger{})

db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})

db.First(&user, "id = ?", "1") //First方法适用于查询满足条件的第一条记录
db.Where(maps).Order("created_time").Find(&portbruteres)//查询满足条件的多条记录,并将结果存储在切片中

db.Exec("DELETE FROM users WHERE id = ?", 1) //sql语句执行

其他不重要的包

github.com/olekukonko/tablewriter

在终端输出表格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import (
"github.com/olekukonko/tablewriter"
"os"
)
// 创建一个新的表格写入器,输出到标准输出(终端)
table := tablewriter.NewWriter(os.Stdout)
// 设置表头
table.SetHeader([]string{"Name", "Age", "Country"})
// 添加表格行
table.Append([]string{"John", "30", "USA"})
table.Append([]string{"Alice", "25", "Canada"})
table.Append([]string{"Bob", "35", "UK"})
// 渲染表格并输出
table.Render()

data := [][]string{
{"Alice", "30", "USA"},
{"Bob", "25", "UK"},
{"Charlie", "35", "Canada"},
}
table := tablewriter.NewWriter(os.Stdout) // 创建一个新的表格写入器,输出到标准输出(终端)
table.SetAlignment(tablewriter.ALIGN_CENTER) // 设置表格列的对齐方式为居中
table.AppendBulk(data) // 批量添加数据到表格
table.Render() // 渲染并输出表格