Go 语言技巧
gofmt 格式化代码
gofmt -s -w xxx.go
嵌套结构体屏蔽指定字段 json 输出
type User struct {
Pwd string `json:"pwd"`
Age int `json:"age"`
}
type UserOut struct {
User
Pwd *struct{} `json:"pwd,omitempty"`// 这里的字段json名需要和嵌套的字段json名一致,否则无效
}
func TestJson(t *testing.T) {
u := User{Pwd: "123", Age: 1}
bb := UserOut{User: u}
b, _ := json.MarshalIndent(bb, "", " ")
t.Log(string(b))
}
github action 使用 ubuntu-latest 执行 make xxx 操作时,需要在 Makefile 指定 shell
在首行添加
SHELL := /bin/bash
go get 指定版本
从 Go 1.11 开始,使用 Go modules 可以做到这一点。在为 Go 模块安装依赖项时,你可以指定一个模块查询,其中可能包含分支或标记名称:
拉取最新的版本(优先择取 tag)
go get golang.org/x/text@latest
拉取 master 分支的最新 commit
go get golang.org/x/text@master
# 如果分支带斜杠需要设置直连
GOPROXY=direct GOSUMDB=off go install github.com/gogf/gf/cmd/gf/v2@feat/docrun
拉取 tag 为 v0.3.2 的 commit
go get golang.org/x/text@v0.3.2
拉取 hash 为 342b231 的 commit,最终会被转换为 v0.3.2:
go get golang.org/x/text@342b2e
指定版本拉取,拉取 v3 版本
go get github.com/smartwalle/alipay/v3
go install 安装工具
go install 用于编译并安装 Go 包到 $GOPATH/bin(或 $GOBIN)目录下。
安装最新版本
go install github.com/gogf/gf/cmd/gf/v2@latest
安装指定版本
go install github.com/gogf/gf/cmd/gf/v2@v2.5.0
安装指定分支
go install github.com/gogf/gf/cmd/gf/v2@master
# 分支带斜杠需要设置直连
GOPROXY=direct GOSUMDB=off go install github.com/gogf/gf/cmd/gf/v2@feat/docrun
安装指定 commit
go install github.com/gogf/gf/cmd/gf/v2@342b2e
go install 与 go get 的区别
从 Go 1.17 开始,go get 已弃用安装二进制的功能,仅用于管理 go.mod 依赖;安装可执行工具应统一使用 go install。
go install | go get | |
|---|---|---|
| 用途 | 编译并安装二进制到 $GOBIN | 管理 go.mod 中的依赖版本 |
| 是否修改 go.mod | 不修改 | 会修改 |
| 推荐场景 | 安装 CLI 工具 | 添加/更新/降级项目依赖 |
查看安装路径
# 查看 GOBIN 目录
go env GOBIN
# 如果 GOBIN 为空,则安装到 GOPATH/bin
go env GOPATH
确保 $GOPATH/bin 或 $GOBIN 已加入系统 PATH,否则安装的工具无法直接执行。
go clean 清理缓存
清理构建缓存
清理 go build 产生的缓存文件(存储在 $GOCACHE 目录下):
go clean -cache
# 查看缓存目录位置
go env GOCACHE
清理模块下载缓存
清理 go mod download 下载的模块缓存(存储在 $GOPATH/pkg/mod 下):
go clean -modcache
清理测试缓存
清理测试结果缓存,下次运行测试时将重新执行而非使用缓存结果:
go clean -testcache
也可以在运行测试时通过 -count=1 跳过缓存:
go test -count=1 ./...
清理 fuzzing 缓存
清理 fuzz 测试产生的缓存文件:
go clean -fuzzcache
全部清理
同时清理构建缓存、模块缓存、测试缓存和 fuzzing 缓存:
go clean -cache -modcache -testcache -fuzzcache
其他参数
# -n 仅打印将要执行的删除命令,不实际执行(dry run)
go clean -n -cache
# -x 打印并执行删除命令,用于查看实际清理了哪些内容
go clean -x -cache
# -i 删除 go install 安装的二进制文件
go clean -i github.com/xxx/xxx
查看模块有哪些版本
go list -m -versions github.com/gogf/gf/v2
go list -m -versions github.com/gogf/gf/cmd/gf/v2
go mod 查看间接依赖来源
go mod why github.com/gogf/gf/v2
# 再详细的依赖树
go mod graph | grep github.com/gogf/gf/v2
go mod 通过 replace 使用来自 fork 的包
例如这个驱动没有合并到主库https://github.com/okyer/gf/blob/master/contrib/drivers/gaussdb/go.mod
# import _ "github.com/gogf/gf/contrib/drivers/gaussdb/v2"
# 在不知道版本号的情况下
go mod edit -replace github.com/gogf/gf/contrib/drivers/gaussdb/v2=github.com/okyer/gf/contrib/drivers/gaussdb/v2@latest
go mod tidy
构建
windows
在 Windows 下如果开启了 cgo,那么极大概率是无法交叉编译。参考
set GOOS=linux
set GOARCH=amd64
GCC
https://jmeubank.github.io/tdm-gcc/download/
目标平台
https://golang.google.cn/doc/install/source
darwin amd64
darwin arm64
ios amd64
ios arm64
freebsd 386
freebsd amd64
freebsd arm
linux 386
linux amd64
linux arm
linux arm64
linux ppc64
linux ppc64le
linux mips
linux mipsle
linux mips64
linux mips64le
netbsd 386
netbsd amd64
netbsd arm
openbsd 386
openbsd amd64
openbsd arm
windows 386
windows amd64
android arm
dragonfly amd64
plan9 386
plan9 amd64
solaris amd64
pprof 使用
gf 的配置方式,https://wiki.goframe.org/pages/viewpage.action?pageId=1114350
开启后使用视图形式查看
需要下载安装graphviz
内存分析
切换视图,通过不同的视图查看内存使用情况,定位内存占用过高的地方
# 直接执行
go tool pprof -http="127.0.0.1:8080" http://127.0.0.1:8000/debug/pprof/heap
# 或者先下载 http://localhost/debug/pprof/heap
go tool pprof -http=":8080" heap
# 实时内存
# http://127.0.0.1:8080/ui/peek?si=inuse_space
# http://127.0.0.1:8080/ui/top?si=inuse_space
# 历史内存
# http://127.0.0.1:8080/ui/?si=alloc_space
CPU 分析
# 直接执行
go tool pprof -http="127.0.0.1:8081" http://127.0.0.1:8000/debug/pprof/profile
# 或者先下载 http://localhost/debug/pprof/profile
go tool pprof -http=":8081" profile
显示 init 顺序
GODEBUG=inittrace=1 go run main.go
$env:GODEBUG="inittrace=1"
go run main.go
set GODEBUG=inittrace=1
go run main.go
判断是否 Windows 双击启动
// +build windows
//go:generate go build -ldflags "-s -w -extldflags '-static'" $GOFILE
package main
import (
"fmt"
"syscall"
"unsafe"
)
func main() {
clickRun := isDoubleClickRun()
fmt.Println("Double click run:", clickRun)
if clickRun {
fmt.Print("press Enter to exit")
var b byte
_, _ = fmt.Scanf("%v", &b)
}
}
// Detect if windows golang executable file is running via double click or from cmd/shell terminator
// https://stackoverflow.com/questions/8610489/distinguish-if-program-runs-by-clicking-on-the-icon-typing-its-name-in-the-cons?rq=1
// https://github.com/shirou/w32/blob/master/kernel32.go
// https://github.com/kbinani/win/blob/master/kernel32.go#L3268
// win.GetConsoleProcessList(new(uint32), win.DWORD(2))
func isDoubleClickRun() bool {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
lp := kernel32.NewProc("GetConsoleProcessList")
if lp != nil {
var pids [2]uint32
var maxCount uint32 = 2
ret, _, _ := lp.Call(uintptr(unsafe.Pointer(&pids)), uintptr(maxCount))
if ret > 1 {
return false
}
}
return true
}
protobuf
下载对应的 protoc,https://github.com/protocolbuffers/protobuf/releases
配置到环境变量 PATH 中
protoc --version
安装 go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
生成 go 代码
protoc --go_out=. *.proto
带执行权 限
非 root,执行二进制带 root,再运行对应的 shell
// chmod 4755 restart
func main() {
syscall.Setuid(0)
syscall.Setgid(0)
RunShell("restart.sh")
}
func RunShell(name string, arg ...string) (string, error) {
cmd := exec.Command(name, arg...)
fmt.Println(name, arg)
out, err := cmd.Output()
if err != nil {
fmt.Printf("out: %s\n", string(out))
fmt.Printf("err: %s\n", err)
if exitErr, ok := err.(*exec.ExitError); ok {
// 命令执行出错,获取错误输出
fmt.Println("命令执行出错:\n", string(exitErr.Stderr))
} else {
// 其他错误
fmt.Printf("执行命令时发生错误: %v", err)
}
return "", err
} else {
fmt.Printf("out: %s\n", string(out))
return string(out), nil
}
}
win10 修改时区报错
可以通过引入 import \_ "time/tzdata"
或者构建时加入 -tags timetzdata
go build -tags timetzdata
https://github.com/gogf/gf/issues/3676
触发 context canceled
package main
import (
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.WriteTplContent(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="connect-src 'self'">
<title>页面标题</title>
</head>
<body>
<button onclick="req(500)">正常请求</button>
<button onclick="req(50)">快速取消</button>
</body>
<script>
function req(delay) {
const controller = new AbortController();
const signal = controller.signal;
fetch('/test', { signal })
.then(response => {
// 处理响应数据
console.log('请求成功');
})
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求被取消');
} else {
console.error('请求失败', error);
}
});
setTimeout(() => {
// 取消请求
controller.abort();
}, delay);
};
</script>
</html>
`)
})
s.BindHandler("/test", func(r *ghttp.Request) {
ctx := r.GetCtx()
g.Log().Info(ctx, "请求开始")
select {
case <-ctx.Done():
g.Log().Info(ctx, "请求取消")
return
case <-time.After(300 * time.Millisecond):
g.Log().Info(ctx, "请求结束")
return
}
})
s.SetPort(80)
s.Run()
}
// http://127.0.0.1