GoLang经验
# 前言
本文是记录在Go
开发中遇到的一些小经验,持续更新。
# 一行代码过多如何处理
# 字符串
//使用 `
str := `hello
world
v2.0`
//使用 +
//注意使用连接符的时候需要把连接符放在上一行占位,不然在编译的时候会自动加上当前代码结束的标示
str := "hello" +
"world" +
"v2.0"
2
3
4
5
6
7
8
9
# 链式表达式
把链式的连接符放在上一行的末尾即可
model := g.DB().Model(table).
Where("mobile", req.Mobile).
Where("id_no", req.IdNo)
2
3
# 递归方法注意事项
在go
中使用递归的时候会一层层的返回,而不是在递归内部返回时直接返回到顶部的形态
//输出
//3
//3
//1
//0
func main() {
fmt.Println(RandomPort(1))
}
// RandomPort 随机产生端口号,10000<=port<=65535
func RandomPort(m int) int {
randomStr := ""
for i := 0; i < 5; i++ {
value := time.Now().UnixMicro()
lastNum := value % 10
var build strings.Builder
if randomStr == "" {
build.WriteString(strconv.FormatInt(lastNum, 10))
randomStr = build.String()
} else {
build.WriteString(randomStr)
build.WriteString(strconv.FormatInt(lastNum, 10))
randomStr = build.String()
}
}
port, err := strconv.Atoi(randomStr)
if err != nil {
//如果获取失败则重新再来
port = RandomPort(2)
}
if 10e3 <= port && port <= 65535 {
return port
}
port = RandomPort(3)
fmt.Println(m)
return 0
}
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
# 字符串拼接
# +
s1 := "字符串"
s2 := "拼接"
s3 := s1 + s2
fmt.Print(s3)
//s3 = "打印字符串"
2
3
4
5
# sprintf
s1 := "字符串"
s2 := "拼接"
s3 := fmt.Sprintf("%s%s", s1, s2)
//s3 = "打印字符串"
2
3
4
# Join
//需要先导入strings包
s1 := "字符串"
s2 := "拼接"
//定义一个字符串数组包含上述的字符串
var str []string = []string{s1, s2}
//调用Join函数
s3 := strings.Join(str, "")
fmt.Print(s3)
2
3
4
5
6
7
8
# buffer.WriteString
//需要先导入bytes包
s1 := "字符串"
s2 := "拼接"
//定义Buffer类型
var bt bytes.Buffer
向bt中写入字符串
bt.WriteString(s1)
bt.WriteString(s2)
//获得拼接后的字符串
s3 := bt.String()
2
3
4
5
6
7
8
9
10
# * buffer.Builder
官方推荐使用
//需要先导入Strings包
s1 := "字符串"
s2 := "拼接"
var build strings.Builder
build.WriteString(s1)
build.WriteString(s2)
s3 := build.String()
2
3
4
5
6
7
# Slice
转[]byte
# 方法一
package main
import (
"fmt"
"strings"
)
func convert(){
stringSlice := []string{"通知中心","perfect!"}
stringByte := "\x00" + strings.Join(stringSlice, "\x20\x00") // x20 = space and x00 = null
fmt.Println([]byte(stringByte))
fmt.Println(string([]byte(stringByte)))
}
func main() {
convert()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
显示结果
[0 233 128 154 231 159 165 228 184 173 229 191 131 2 0 112 101 114 102 101 99 116 33]
通知中心 perfect!
2
# 方法二
package main
import (
"bytes"
"encoding/gob"
"fmt"
)
func convert(){
stringSlice := []string{"通知中心","perfect!"}
buffer := &bytes.Buffer{}
gob.NewEncoder(buffer).Encode(stringSlice)
byteSlice := buffer.Bytes()
fmt.Printf("%q\n", byteSlice)
fmt.Println("---------------------------")
backToStringSlice := []string{}
gob.NewDecoder(buffer).Decode(&backToStringSlice)
fmt.Printf("%v\n", backToStringSlice)
}
func main() {
convert()
}
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
显示结果
[0 233 128 154 231 159 165 228 184 173 229 191 131 2 0 112 101 114 102 101 99 116 33]
通知中心 perfect!
2
# 如何给golang
项目打包
将${name}
替换为要生成的名字
Windows
# 普通打包
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ${name}.exe
# 压缩打包
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o ${name}.exe
2
3
4
Linux
# 普通打包
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${name}
# 压缩打包
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o ${name}
2
3
4
macOS
# 普通打包
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ${name}
# 压缩打包
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o ${name}
2
3
4
# 如何让 golang
打包后的可执行文件体积变小
注意:
- 假设可执行文件名叫做
bqxw
- 当前执行命令下的目录要有对应的文件
- 安装
upx
- 执行上面的
golang
打包命令 - 然后再执行命令
upx --best --lzma bqxw
# 切片增加数据
arryV := make([]string, 1, 1)
for _, site := range list {
arryV = append(arryV, site.String())
}
2
3
4
# 获取struct
中的别名
type connectionType struct {
alias string `tag:"连接名"` //别名
hostURL string `tag:"连接地址"` //连接地址
port string `tag:"端口号"` //端口号
username string `tag:"用户名"` //用户名
password string `tag:"密码"` //密码
}
fieldV, _ := reflect.TypeOf(connectionType{}).FieldByName("alias")
tag := fieldV.Tag.Get("tag")
// 输出: 连接名
2
3
4
5
6
7
8
9
10
# struct
使用json.Marshal
转换后值是空的
需要转换的struct
必须是可以被引用的,即struct
的首字母大写
# json
数据格式化/缩进格式写入到json
文件中
把数据进行序列话的时候使用MarshalIndent
方法,该方法按照json
格式 缩进
info := `{"alias":"11","hostURL":"11","port":"11","username":"11","password":"11","savePassword":true}`
_v, _ := json.MarshalIndent(info, "", " ")
fmt.Println(string(_v))
//显示效果
/**
{
"alias": "11",
"hostURL": "11",
"port": "11",
"username": "11",
"password": "11"
}
*/
2
3
4
5
6
7
8
9
10
11
12
13
# Go
解析JSON
时Key
没有「引号」
解决方案:
# 自行手动增加引号
reg:=regexp.MustCompile(`[\w]+[:]`)
reg.ReplaceAllStringFunc(resStr,func(s string) string{
reg2:=regexp.MustCompile(`[\w]+`)
s=reg2.FindAllString(s,-1)[0]
return `"`+s+`":`
})
2
3
4
5
6
# 使用JSON5
进行处理
具体包请搜索Github
# 将字符串中Unicode
字符转换为汉字
func UnicodeToZH(raw []byte) ([]byte, error) {
str, err := strconv.Unquote(strings.Replace(strconv.Quote(string(raw)), `\\u`, `\u`, -1))
if err != nil {
return nil, err
}
return []byte(str), nil
}
2
3
4
5
6
7
# interface
转 string
,int
,float64
func interface2String(inter interface{}) {
switch inter.(type) {
case string:
fmt.Println("string", inter.(string))
break
case int:
fmt.Println("int", inter.(int))
break
case float64:
fmt.Println("float64", inter.(float64))
break
}
}
func main() {
interface2String("jack")
interface2String(1)
interface2String(12.223)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
输出
string jack
int 1
float64 12.223
2
3
# 防止在go
开发中出现import cycle not allowed
不同文件夹中代码中的交叉引用算是比较常见的,如果是映射出现这类问题我还能理解,但是go
偏偏是直接语法上干掉了交叉引用的可能性,导致不可用的形态。想要处理这类问题,最好的方案莫过于在即将饮用的文件上添加一个文件夹,让其代码更深一层,从go
的逻辑出发的话,这两个文件并不属于同一个包内
优点:解决了交叉引用的问题
缺点:文件结构变复杂了
# 类型转换
其它类型进行转换也类似
interface{}
转map
# interface{}
转map
// 假定变量a目前表现的变量是interface{},底层实际变量是map[string]interface{}
a.(map[string]interface{})
2
# 如何定义类似Java
的枚举
使用golang
自定义类型
type TimeUnit string
const(
TimeSecond TimeUnit = 'Seconds'
TimeMinute TimeUnit = 'Minute'
TimeHour TimeUnit = 'Hour'
)
2
3
4
5
6
7
# 递归函数的灵异事件
在函数体中使用递归发现莫名的被中断,日志也未抛出,结果发现递归函数执行后直接向下执行了而不是进入递归函数中,此现象目前在递归函数后追加了一个return
临时处理
# http.ResponseWriter
407
浏览器不识别
在调用 WriteHeader(407)
方法之前,要先设置所有的响应头部字段。这是因为 WriteHeader
方法会立即将响应头部发送给客户端,所以在调用该方法之后设置头部字段可能无法生效。
// 错误写法
...
w.Header().Set("Connection", "close")
w.WriteHeader(407)
w.Header().Set("Proxy-Connection", "close")
...
// 正确写法
w.Header().Set("Server", "JuLiang Dynamic Proxy Server")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Connection", "close")
w.Header().Set("Proxy-Authenticate", `Basic realm="Authorization Required"`)
w.Header().Set("Proxy-Connection", "close")
w.WriteHeader(407)
2
3
4
5
6
7
8
9
10
11
12
13
14
# golang
代码中如何标记函数弃用(废弃、过时)
// abc 测试
// Deprecated: xxxx
func abc() {
.....
}
2
3
4
5
# golang
进行 http
请求时参数写入到 body
中的 form-data
类型中
// 使用 github.com/xingcxb/goKit
payload := &bytes.Buffer{}
writer := multipart.NewWriter(payload)
_ = writer.WriteField("abc", queryUrl)
err := writer.Close()
if err != nil {
return "", err
}
headers["Content-Type"] = writer.FormDataContentType()
result, err := httpKit.HttpProxyPostFull(url, headers, nil, payload.String(), -1, "http", "", "", proxyIpPort)
2
3
4
5
6
7
8
9
10
# 在 golang
代码中如何理解空的 main
函数
首先,这类程序基本上属于编译成库文件,然后在其它程序中引用。引用的库文件的时候,我们需要知道在代码中使用 import
语句来导入库文件,然后就可以使用库文件中定义的函数和变量了。
假设我们已经将 yichengchen/clashX
项目编译成了一个名为 goClash
的库文件,现在我们想在另外一个项目中引用该库文件。我们可以按照以下步骤来完成:
在代码中使用
import
语句导入goClash
库文件:import "path/to/goClash"
1其中,
"path/to/goClash"
表示goClash
库文件的路径。如果goClash
库文件在当前项目的vendor
目录下,可以直接使用相对路径导入。在代码中使用
goClash
库文件中定义的函数和变量:func main() { // 使用 goClash 库文件中定义的函数和变量 goClash.FunctionName() goClash.VariableName }
1
2
3
4
5需要注意的是,如果
goClash
库文件中定义的函数和变量名称与当前项目中的其他函数和变量名称相同,可能会导致命名冲突。为了避免这种情况,我们可以使用别名来区分不同的函数和变量。例如:import goClashLib "path/to/goClash" func main() { // 使用 goClash 库文件中定义的函数和变量 goClashLib.FunctionName() goClashLib.VariableName }
1
2
3
4
5
6
7
# 在 golang
中函数上面携带 //export <函数名>
是什么意思
//export run
func run() {
// Function body
}
2
3
4
函数上面携带 //export
注释是为了将该函数导出为 C
语言可调用的函数。这个注释告诉 Go
编译器将该函数编译成一个符号,以便可以从 C
语言或其他支持C语言调用的语言调用。
其中, run
是需要导出的函数名。在这个函数上面添加 //export run
注释后,编译器会将该函数编译成一个符号,以便从 C
语言或其他支持C语言调用的语言调用。在 C
语言中调用该函数时,需要使用 extern "C"
语法来声明该函数的符号名称。
需要注意的是,只有被导出的函数才能从 C
语言或支持其他支持C语言调用的语言调用。
注意:导出的函数首字母大小写无视
Golang
的函数访问权限
# 在 golang
中使用命令
import (
"fmt"
"os/exec"
)
func main() {
command := exec.Command("/bin/bash", "-c", "ifconfig | awk '$1 ~ /^ppp[0-9]/'")
output, err := command.CombinedOutput()
if err != nil {
fmt.Println(err)
}
fmt.Println(string(output))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 在一个文件中存在两个 init
初始化函数怎么执行
当一个文件中存在两个 init
函数的时候实际上是按照代码顺序进行执行的
# golang
修改 Linux
中的 /etc/resolv.conf
失效
原本使用的是 golang
的 os
包中的 WriteFile
方法进行写入,但是发现写入后无效,后来发现是因为 golang
的 WriteFile
方法默认是以 0644
权限进行写入的,而 resolv.conf
文件的权限是 0644
,后来即使使用了 0666
这个权限也是失败,于是采用以下方案来处理
openResolv,err := os.Open("/etc/resolv.conf")
if err != nil {
....
}
defer openResolv.Close()
_,err = openResolv.WriteAt([]byte("..."),0)
if err != nil{
...
}
2
3
4
5
6
7
8
9
# 如何做一个接受 Ctrl+C
信号的 golang
程序
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
// 创建一个接收信号的通道
c := make(chan os.Signal, 1)
// 监听 SIGINT 和 SIGTERM 信号
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
// 创建一个用于等待 goroutine 完成的通道
done := make(chan struct{})
go for{
// goroutine 完成后,关闭 done 通道
close(done)
}
// 使用 select 语句等待信号或 goroutine 完成
select {
case <-c:
fmt.Println("收到信号,退出程序")
case <-done:
fmt.Println("goroutine 完成")
}
os.Exit(0)
}
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
# go:generate
有什么用
go:generate
是一个特殊的注释,用于在 go generate
命令中执行命令。go generate
命令会扫描当前目录及其子目录中的所有 go
文件,查找包含 go:generate
注释的文件,然后执行注释中的命令。
如果要启动的时候执行类似 go mod tidy
、go env -w GO111MODULE=on
等命令可以考虑采用 go:generate
方式来进行处理
//go:generate go env -w GO111MODULE=on
//go:generate go env -w GOPROXY=https://goproxy.cn,direct
//go:generate go mod tidy
//go:generate go mod download
func main() {
...
}
2
3
4
5
6
7
8
# 如何直接运行未编译的 golang
文件
假设
test
文件中的测试方法为TestSwap
假设go
文件名为main.go
- 运行
go test
文件- 终端进入到
go test
文件目录下 - 终端上执行
go test -run TestSwap
- 终端进入到
- 运行
go
文件- 直接执行
go run main.go
- 直接执行
# 无效接收器类型'*xxx.xxx' ('xxx.xxx' 为 非本地类型)
在非本包中引入了一个结构体,然后在本包中使用该结构体作为接收器,这样会导致上述的编译错误
# 解决方案
在当前包中定义一个新的结构体,然后在该结构体中嵌入引入的结构体,然后使用新的结构体作为接收器即可
# Golang
定义全局变量时数据丢失
个人猜测属于 golang
的 GC
问题,目前我这边出现的是定义了 string
类型的变量结果在使用的时候数据丢失了
# 解决方案
将变量放入到 runtime.KeepAlive
中即可
var (
a string
b string
)
func init() {
a = "123"
runtime.keepAlive(a)
}
2
3
4
5
6
7
8