tonglin0325的个人主页

go学习笔记——基本语法

1.*和&的区别#

& 是取地址符号 , 即取得某个变量的地址 , 如 &a

  • 是指针运算符 , 可以表示一个变量是指针类型 , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值

参考:Go中*和&区别

println打印对象只能打印出其指针,需要使用fmt.Printf,如下

1
2
fmt.Printf("%+v\n", user)

参考:Golang 通过fmt包输出完整struct信息

2.defer#

defer是Go语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行。

defer语句通常用于一些成对操作的场景:打开连接/关闭连接;加锁/释放锁;打开文件/关闭文件等。

defer在一些需要回收资源的场景非常有用,可以很方便地在函数结束前做一些清理操作。在打开资源语句的下一行,直接一句defer就可以在函数返回前关闭资源,可谓相当优雅。

1
2
3
f, _ := os.Open("defer.txt")
defer f.Close()

参考:Golang之轻松化解defer的温柔陷阱

3.日志库#

1.sirupsen/logrus#

1
2
go get -u github.com/sirupsen/logrus

文档

1
2
https://pkg.go.dev/github.com/sirupsen/logrus#section-readme

使用

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
package main

import (
"os"
"github.com/sirupsen/logrus"
)

// Create a new instance of the logger. You can have any number of instances.
var log = logrus.New()

func main() {
// The API for setting attributes is a little different than the package level
// exported logger. See Godoc.
log.Out = os.Stdout

// You could set this to any `io.Writer` such as a file
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }

log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
}

2.rs/zerolog#

1
2
go get -u github.com/rs/zerolog/log

使用

1
2
3
4
5
log.Logger = log.With().Caller().Logger()
log.Info().Msg("hello world")

// Output: {"level": "info", "message": "hello world", "caller": "/go/src/your_project/some_file:21"}

3.uber/zap#

1
2
go get -u go.uber.org/zap

文档

1
2
https://pkg.go.dev/go.uber.org/zap

zap提供了2种logger,分别是Logger和SugaredLogger

在性能要求高但是不是很重要的场景下,适合使用SugaredLogger

1
2
3
4
5
6
7
8
9
10
11
logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
// Structured context as loosely typed key-value pairs.
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)

输出

1
2
3
{"level":"info","ts":1703922949.209576,"caller":"server/main.go:109","msg":"failed to fetch URL","url":"http://example.com","attempt":3,"backoff":1}
{"level":"info","ts":1703922949.209731,"caller":"server/main.go:115","msg":"Failed to fetch URL: http://example.com"}

在性能要求高且需要类型安全的场景下,适合使用Logger

1
2
3
4
5
6
7
8
9
10
logger, _ := zap.NewProduction()
defer logger.Sync()
url := "http://example.com"
logger.Info("failed to fetch URL",
// Structured context as strongly typed Field values.
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)

输出

1
2
{"level":"info","ts":1703923022.603034,"caller":"server/main.go:108","msg":"failed to fetch URL","url":"http://example.com","attempt":3,"backoff":1}

NewExample/NewDevelopment/NewProduction区别

NewExample适用于测试代码,它将DebugLevel及以上级别的日志以JSON格式标准输出,但省略了时间戳和调用函数,以保持示例输出简短和确定。

NewDevelopment适用于开发环境,它以人类友好的格式将DebugLevel及以上级别的日志写入标准错误。

NewProduction适用于生产环境,它将info level及以上级别的日志以JSON格式写入标准错误。

其他参考文档:Go 每日一库之 zap

golang常用库包:log日志记录-uber的Go日志库zap使用详解

4.kratos logrus#

参考:go各框架的log日志

5.日志文件回滚#

日志文件按时间回滚:natefinch/lumberjack

1
2
go get gopkg.in/natefinch/lumberjack.v2

4.strconv包#

int转string

1
2
s := strconv.Itoa(i)

int64转string

1
2
s := strconv.FormatInt(i, 10)

string转int

1
2
i, err := strconv.Atoi(s)

string转int64

1
2
i, err := strconv.ParseInt(s, 10, 64)

float转string

1
2
3
4
v := 3.1415926535
s1 := strconv.FormatFloat(v, 'E', -1, 32)//float32
s2 := strconv.FormatFloat(v, 'E', -1, 64)//float64

string转float

1
2
3
4
s := "3.1415926535"
v1, err := strconv.ParseFloat(v, 32)
v2, err := strconv.ParseFloat(v, 64)

参考:Go语言从入门到精通 - 【精华篇】strconv包详解

5.属性复制#

可以使用 jinzhu/copier

使用jinzhu/copier

1
2
go get github.com/jinzhu/copier

copy

1
2
copier.Copy(&employee, &user)

deepcopy,区别是deepcopy的时候,对dst的属性进行修改,是肯定不会影响src的

1
2
3
var dst User
copier.CopyWithOption(&dst, src, copier.Option{DeepCopy: true})

如果想在复制的时候,对属性进行修改,可以使用方法赋值,注意需要使用copier.Copy,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type User struct {
Name string
Age int
}

func (u *User) DoubleAge() int {
return 2 * u.Age
}

type Employee struct {
Name string
DoubleAge int
Role string
}

func main() {
user := User{Name: "dj", Age: 18}
employee := Employee{}

copier.Copy(&employee, &user)
fmt.Printf("%#v\n", employee)
}

参考:Go 每日一库之 copier

其他类似的库还有ulule/deepcoper,参考:golang struct拷贝工具(类似于java中 BeanUtils.copyProperties())

以及

1
2
3
4
5
6
7
8
9
10
11
12
13
https://github.com/jinzhu/copier
https://github.com/mohae/deepcopy
https://github.com/ulule/deepcopier
https://github.com/mitchellh/copystructure
https://github.com/globusdigital/deep-copy
https://github.com/getlantern/deepcopy
https://github.com/antlabs/deepcopy
https://github.com/go-toolsmith/astcopy
https://github.com/qdm12/reprint
https://github.com/huandu/go-clone
https://github.com/wzshiming/deepclone
https://github.com/davidwalter0/go-clone

6.其他数据结构#

golang没有set,priorityqueue这些数据结构,可以使用emirpasic/gods

参考:https://github.com/emirpasic/gods

7.切片处理#

可以使用samber/lo来对切片、数组或集合进行处理

1
2
go get github.com/samber/lo@v1

比如filter操作

1
2
3
4
5
even := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool {
return x%2 == 0
})
// []int{2, 4}

map操作

1
2
3
4
5
6
7
import "github.com/samber/lo"

lo.Map([]int64{1, 2, 3, 4}, func(x int64, index int) string {
return strconv.FormatInt(x, 10)
})
// []string{"1", "2", "3", "4"}

去重操作

1
2
3
uniqValues := lo.Uniq([]int{1, 2, 2, 1})
// []int{1, 2}

获得map的key,并转换成数组

不使用lo

1
2
3
4
5
6
7
8
9
m := map[string]int{
"foo": 1,
"bar": 2,
}
keys := make([]string, 0, len(m))
for key := range m {
keys = append(keys, key)
}

使用lo

1
2
3
4
5
6
7
8
9
m := map[string]int{
"foo": 1,
"bar": 2,
}
keys := lo.Keys[string, int](m)
fmt.Println(keys)

// [foo bar]

range操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
result := lo.Range(4)
// [0, 1, 2, 3]

result := lo.Range(-4)
// [0, -1, -2, -3]

result := lo.RangeFrom(1, 5)
// [1, 2, 3, 4, 5]

result := lo.RangeFrom[float64](1.0, 5)
// [1.0, 2.0, 3.0, 4.0, 5.0]

result := lo.RangeWithSteps(0, 20, 5)
// [0, 5, 10, 15]

result := lo.RangeWithSteps[float32](-1.0, -4.0, -1.0)
// [-1.0, -2.0, -3.0]

result := lo.RangeWithSteps(1, 4, -1)
// []

result := lo.Range(0)
// []

8.时间处理#

可以使用time或者carbon

1.time#

字符串转time.Time

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import (
"fmt"
"time"
)

func main() {
timeStr := "2023-07-21 14:30:00"
// DateTime = "2006-01-02 15:04:05"
// DateOnly = "2006-01-02"
// TimeOnly = "15:04:05"
// RFC3339 = "2006-01-02T15:04:05Z07:00"

parsedTime, err := time.Parse(time.DateTime, timeStr)
if err != nil {
fmt.Println("解析时间字符串时出错:", err)
return
}

fmt.Println("解析后的时间:", parsedTime)
}

time.Time转字符串

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

func main() {
// 获取当前时间
currentTime := time.Now()
// DateTime = "2006-01-02 15:04:05"
// DateOnly = "2006-01-02"
// TimeOnly = "15:04:05"
// RFC3339 = "2006-01-02T15:04:05Z07:00"
timeStr := currentTime.Format(time.DateOnly)
// 输出格式化后的时间字符串
fmt.Println("格式化后的时间字符串:", timeStr)
}

2.carbon#

1
2
go get -u github.com/golang-module/carbon/v2

文档

1
2
https://pkg.go.dev/github.com/golang-module/carbon/v2

9.断言#

可以使用stretchr/testify

1
2
go get -u github.com/stretchr/testify

使用assert

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
import (
"testing"
"github.com/stretchr/testify/assert"
)

func TestSomething(t *testing.T) {

// assert equality
assert.Equal(t, 123, 123, "they should be equal")

// assert inequality
assert.NotEqual(t, 123, 456, "they should not be equal")

// assert for nil (good for errors)
assert.Nil(t, object)

// assert for not nil (good when you expect something)
if assert.NotNil(t, object) {

// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal(t, "Something", object.Value)

}

}

10.resty#

在go中请求接口可以使用go-resty/resty框架

1
2
go get github.com/go-resty/resty/v2

get请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Create a Resty Client
client := resty.New()

resp, err := client.R().
SetQueryParams(map[string]string{
"page_no": "1",
"limit": "20",
"sort":"name",
"order": "asc",
"random":strconv.FormatInt(time.Now().Unix(), 10),
}).
SetHeader("Accept", "application/json").
SetResult(&Result{}).
SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F").
Get("/search_result")

post请求

1
2
3
4
5
6
7
8
9
10
11
// Create a Resty Client
client := resty.New()

// POST JSON string
// No need to set content type, if you have client level setting
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody(`{"username":"testuser", "password":"testpass"}`).
SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}).
Post("https://myapp.com/login")

11.单元测试#

使用testing框架进行单元测试

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

import (
"fmt"
"gin-template/internal/database"
"gin-template/internal/model"
"testing"
)

func Test_userDo_Create(t *testing.T) {
user := model.User{
Username: "test",
Email: "test@test",
}
SetDefault(database.DB)

err := User.Create(&user)
if err != nil {
fmt.Println("creat user")
}
}

如果遇到go testing flag provided but not defined: -test.v的报错,解决方法是添加一个init.go文件

1
2
3
4
5
6
7
8
package test

import "testing"

func init() {
testing.Init()
}

然后在使用了flag的地方添加的import中

1
2
_ "gin-template/internal/test"

参考:问题记录:flag provided but not defined: -test.v 异常处理过程

12.打桩工具——go monkey#

go monkey可以用于在单元测试中进行打桩(指补齐未实现的代码)

参考:Go单测从零到溜系列4—使用monkey打桩