Go modules 是 Go 语言的依赖解决方案,发布于 Go1.11,成长于 Go1.12,丰富于 Go1.13,正式于 Go1.14 推荐在生产上使用。
Go moudles 目前集成在 Go 的工具链中,只要安装了 Go,自然而然也就可以使用 Go moudles 了,而 Go modules 的出现也解决了在 Go1.11 前的几个常见争议问题:
Go Modoules的目的之一就是淘汰GOPATH, 那么GOPATH是个什么?
为什么在 Go1.11 前就使用 GOPATH,而 Go1.11 后就开始逐步建议使用 Go modules,不再推荐 GOPATH 的模式了呢?
我们输入go env命令行后可以查看到 GOPATH 变量的结果,我们进入到该目录下进行查看,如下:
$ go env
...
set GOPATH=D:\GoWorkSpace
...
go
├── bin
├── pkg
└── src
├── github.com
├── golang.org
├── google.golang.org
├── gopkg.in
....
GOPATH目录下一共包含了三个子目录,分别是:
$GOPATH/src/github.com/foo/bar
的路径进行存放。因此在使用 GOPATH 模式下,我们需要将应用代码存放在固定的 $GOPATH/src
目录下,并且如果执行 go get
来拉取外部依赖会自动下载并安装到 $GOPATH
目录下。
在 GOPATH 的 $GOPATH/src
下进行 .go
文件或源代码的存储,我们可以称其为 GOPATH 的模式,这个模式拥有一些弊端.
我们接下来用 Go Modules 的方式创建一个项目, 建议为了与GOPATH分开,不要将项目创建在 GOPATH/src
下.
go mod命令
命令 | 作用 |
---|---|
go mod init | 生成 go.mod 文件 |
go mod download | 下载 go.mod 文件中指明的所有依赖 |
go mod tidy | 整理现有的依赖 |
go mod graph | 查看现有的依赖结构 |
go mod edit | 编辑 go.mod 文件 |
go mod vendor | 导出项目所有的依赖到vendor目录 |
go mod verify | 校验一个模块是否被篡改过 |
go mod why | 查看为什么需要依赖某模块 |
go mod环境变量
可以通过 go env 命令来进行查看
$ go env
...
set GO111MODULE=on
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=D:\GoWorkSpace
set GOPRIVATE=
set GOPROXY=https://goproxy.cn,direct
set GOROOT=D:\Environment\Go
set GOSUMDB=sum.golang.org
...
GO111MODULE
Go语言提供了 GO111MODULE这个环境变量来作为 Go modules 的开关,其允许设置以下参数:
可以通过以下命令来设置
$ go env -w GO111MODULE=on
GOPROXY
这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是 用于使 Go 在后续拉取模块版本时直接通过镜像站点来快速拉取。
GOPROXY 的默认值是:https://proxy.golang.org,direct
proxy.golang.org
国内访问不了,需要设置国内的代理.
如:
$ go env -w GOPROXY=https://goproxy.cn,direct
GOPROXY 的值是一个以英文逗号 “,” 分割的 Go 模块代理列表,允许设置多个模块代理,假设你不想使用,也可以将其设置为 “off” ,这将会禁止 Go 在后续操作中使用任何 Go 模块代理。
如:
$ go env -w GOPROXY=https://goproxy.cn,https://mirrors.aliyun.com/goproxy/,direct
direct
而在刚刚设置的值中,我们可以发现值列表中有 “direct” 标识,它又有什么作用呢?
实际上 “direct” 是一个特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等),场景如下:当值列表中上一个 Go 模块代理返回 404 或 410 错误时,Go 自动尝试列表中的下一个,遇见 “direct” 时回源,也就是回到源地址去抓取,而遇见 EOF 时终止并抛出类似 “invalid version: unknown revision…” 的错误。
GOSUMDB
它的值是一个 Go checksum database,用于在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经过篡改,若发现不一致,也就是可能存在篡改,将会立即中止。
GOSUMDB 的默认值为:sum.golang.org
,在国内也是无法访问的,但是 GOSUMDB 可以被 Go 模块代理所代理(详见:Proxying a Checksum Database)。
因此我们可以通过设置 GOPROXY 来解决,而先前我们所设置的模块代理 goproxy.cn
就能支持代理 sum.golang.org
,所以这一个问题在设置 GOPROXY 后,你可以不需要过度关心。
另外若对 GOSUMDB 的值有自定义需求,其支持如下格式:
也可以将其设置为“off”,也就是禁止 Go 在后续操作中校验模块版本。
GONOPROXY/GONOSUMDB/GOPRIVATE
这三个环境变量都是用在当前项目依赖了私有模块,例如像是你公司的私有 git 仓库,又或是 github 中的私有库,都是属于私有模块,都是要进行设置的,否则会拉取失败。
更细致来讲,就是依赖了由 GOPROXY 指定的 Go 模块代理或由 GOSUMDB 指定 Go checksum database 都无法访问到的模块时的场景。
而 一般建议直接设置 GOPRIVATE,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是直接使用 GOPRIVATE。
并且它们的值都是一个以英文逗号 “,” 分割的模块路径前缀,也就是可以设置多个,例如:
$ go env -w GOPRIVATE="git.example.com,github.com/eddycjy/mquote"
设置后,前缀为 git.xxx.com
和 github.com/eddycjy/mquote
的模块都会被认为是私有模块。
如果不想每次都重新设置,我们也可以利用通配符,例如:
$ go env -w GOPRIVATE="*.example.com"
这样子设置的话,所有模块路径为 example.com 的子域名(例如:git.example.com)都将不经过 Go module proxy 和 Go checksum database,需要注意的是不包括 example.com 本身。
$ go env -w GO111MODULE=on
接下来我们在该项目根目录下创建 main.go 文件,如下:
package main
import (
"fmt"
"github.com/aceld/zinx/znet"
"github.com/aceld/zinx/ziface"
)
//ping test 自定义路由
type PingRouter struct {
znet.BaseRouter
}
//Ping Handle
func (this *PingRouter) Handle(request ziface.IRequest) {
//先读取客户端的数据
fmt.Println("recv from client : msgId=", request.GetMsgID(),
", data=", string(request.GetData()))
//再回写ping...ping...ping
err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping"))
if err != nil {
fmt.Println(err)
}
}
func main() {
//1 创建一个server句柄
s := znet.NewServer()
//2 配置路由
s.AddRouter(0, &PingRouter{})
//3 开启服务
s.Serve()
}
OK, 我们先不要关注代码本身,我们看当前的 main.go
,是依赖一个叫 github.com/aceld/zinx库 的。znet
和 ziface
只是 zinx
的两个模块.
接下来我们在本项目的根目录执行
$ go get github.com/aceld/zinx/znet
---------- 输出结果: ----------
go: added github.com/aceld/zinx v1.2.6
go: added github.com/gorilla/websocket v1.5.0
go: added github.com/klauspost/cpuid/v2 v2.1.1
go: added github.com/klauspost/reedsolomon v1.11.8
go: added github.com/pkg/errors v0.9.1
go: added github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161
go: added github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b
go: added github.com/tjfoc/gmsm v1.4.1
go: added github.com/xtaci/kcp-go v5.4.20+incompatible
go: added golang.org/x/crypto v0.21.0
go: added golang.org/x/net v0.23.0
go: added golang.org/x/sys v0.18.0
我们会看到 我们的go.mod被修改,同时多了一个go.sum文件.
查看go.mod文件
module module_test
go 1.24
require (
github.com/aceld/zinx v1.2.6 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
github.com/klauspost/reedsolomon v1.11.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/xtaci/kcp-go v5.4.20+incompatible // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect
)
我们来简单看一下这里面的关键字
// indirect: 表示该模块为 间接依赖,也就是在当前应用程序中的 import 语句中,并没有发现这个模块的明确引用,有可能是你先手动 go get 拉取下来的,也有可能是你所依赖的模块所依赖的.我们的代码很明显是依赖的 “github.com/aceld/zinx/znet” 和 “github.com/aceld/zinx/ziface” ,所以就间接的依赖了github.com/aceld/zinx
查看go.sum文件
在第一次拉取模块依赖后,会发现多出了一个 go.sum 文件,其详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aceld/zinx v1.2.6 h1:NYlcQ5OzjhxYXOsVNUzjCchry5A8fjCDHVrI9jMX7jk=
github.com/aceld/zinx v1.2.6/go.mod h1:agiZ6AuWONUDr/M/Rla9wQ9gP1vPvLacHLisBx0Cw7g=
我们可以看到一个模块路径可能有如下两种:
h1:hash情况
github.com/aceld/zinx v1.2.6 h1:NYlcQ5OzjhxYXOsVNUzjCchry5A8fjCDHVrI9jMX7jk=
go.mod hash情况
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
h1 hash 是 Go modules 将目标模块版本的 zip 文件开包后,针对所有包内文件依次进行 hash,然后再把它们的 hash 结果按照固定格式和算法组成总的 hash 值。
而 h1 hash 和 go.mod hash 两者,要不就是同时存在,要不就是只存在 go.mod hash。那什么情况下会不存在 h1 hash 呢,就是当 Go 认为肯定用不到某个模块版本的时候就会省略它的 h1 hash,就会出现不存在 h1 hash,只存在 go.mod hash 的情况。
在 Go module 中,修改依赖版本通常通过 go get
命令完成。以下是修改依赖版本的详细步骤和方法:
go get
修改依赖版本go get
是最常用的方式来修改依赖版本。你可以指定目标模块的版本号、分支、或 commit hash。
示例:升级到最新版本
运行以下命令将依赖升级到最新的正式版本:
$ go get github.com/aceld/zinx@latest
示例:降级到特定版本
如果你想将 github.com/aceld/zinx
降级到 v1.2.0
:
$ go get github.com/aceld/[email protected]
示例:切换到某个分支
如果你想使用某个开发分支(例如 dev
分支):
$ go get github.com/aceld/zinx@dev
示例:锁定到某个 commit hash
如果你想锁定到某个具体的 commit hash(例如 abc1234
):
$ go get github.com/aceld/zinx@abc1234
go.mod
文件你也可以直接编辑 go.mod
文件来修改依赖版本。完成后运行 go mod tidy
来同步更改。
示例:手动修改版本号
假设你的 go.mod
文件中有如下内容:
require (
github.com/aceld/zinx v1.2.6
)
将其修改为:
require (
github.com/aceld/zinx v1.3.0
)
然后运行以下命令:
go mod tidy
这会更新 go.sum
文件并下载新的依赖版本。
go mod edit
go mod edit
提供了一种更灵活的方式来修改 go.mod
文件,而无需手动编辑文件。
示例:修改依赖版本
运行以下命令将 github.com/aceld/zinx
的版本修改为 v1.3.0
:
$ go mod edit -require=github.com/aceld/[email protected]
然后运行以下命令以同步更改:
$ go mod tidy
replace
指令)如果你需要临时替换某个依赖(例如本地开发版本或非标准仓库中的版本),可以使用 replace
指令。
示例:替换为本地路径
假设你在本地开发了一个自定义版本的 github.com/aceld/zinx
,位于 /path/to/local/zinx
。你可以在 go.mod
文件中添加以下内容:
replace github.com/aceld/zinx => /path/to/local/zinx
示例:替换为其他远程仓库
如果你想将依赖替换为另一个远程仓库中的版本:
replace github.com/aceld/zinx => github.com/yourfork/zinx v1.3.0
然后运行以下命令:
$ go mod tidy
go.mod
文件中标注为 // indirect
。你可以通过 go get
或 go mod edit
将其升级或降级。$ go mod tidy
v1.x
切换到 v2.x
),可能需要额外调整代码以适配新版本。@latest
不会包含预发布版本(例如 v1.5.0-beta
)。如果需要拉取预发布版本,可以显式指定版本号。假设你想将 github.com/aceld/zinx
从 v1.2.6
升级到 v1.3.0
:
$ go get github.com/aceld/[email protected]
go.mod
文件是否已更新:require (
github.com/aceld/zinx v1.3.0
)
$ go mod tidy
参考视频