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是个什么?
我们输入go env
命令行后可以查看到 GOPATH 变量的结果,我们进入到该目录下进行查看,如下:
GOPATH目录下一共包含了三个子目录,分别是:
.go
文件或源代码。在编写 Go 应用程序,程序包和库时,一般会以$GOPATH/src/github.com/foo/bar
的路径进行存放。因此在使用 GOPATH 模式下,我们需要将应用代码存放在固定的$GOPATH/src
目录下,并且如果执行go get
来拉取外部依赖会自动下载并安装到$GOPATH
目录下。
在 GOPATH 的 $GOPATH/src
下进行 .go
文件或源代码的存储,我们可以称其为 GOPATH 的模式,这个模式拥有一些弊端.
go get
的时候,你无法传达任何的版本信息的期望,也就是说你也无法知道自己当前更新的是哪一个版本,也无法通过指定来拉取自己所期望的具体版本。github.com/foo/bar
。我们接下来用Go Modules的方式创建一个项目, 建议为了与GOPATH分开,不要将项目创建在GOPATH/src
下.
命令 | 作用 |
---|---|
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 env
命令来进行查看
$ go env
GO111MODULE="auto"
GOPROXY="https://proxy.golang.org,direct"
GONOPROXY=""
GOSUMDB="sum.golang.org"
GONOSUMDB=""
GOPRIVATE=""
...
设置GO111MODULE;
$ go env -w GO111MODULE=on
https://proxy.golang.org,direct
proxy.golang.org
国内访问不了,需要设置国内的代理.
https://mirrors.aliyun.com/goproxy/
https://goproxy.cn,direct
设置GOPROXY:
$ 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” 是一个特殊指示符,用于指示 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 的值有自定义需求,其支持如下格式:
<SUMDB_NAME>+<PUBLIC_KEY>
。<SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>
。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
本身。
vim ~/.bashrc
修改完后需加载配置文件:
source .bashrc
GOPATH
下创建文件夹go mod
初始化后,项目文件夹中会出现一个go.mod
文件;go.mod
文件内容:包括模块名称和go的版本go mod初始化:
go mod init 模块名称
模块名称是自定义的,决定之后的代码在导入本包的时候import的名称;
package main
import (
"fmt"
"github.com/aceld/zinx/ziface"
"github.com/aceld/zinx/znet"
)
// 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()
}
我们先不要关注代码本身,我们看当前的main.go
也就是我们的aceld/modules_test
项目,是依赖一个叫github.com/aceld/zinx
库的, znet和ziface只是zinx的两个模块;
$HOME/aceld/modules_test
,本项目的根目录执行go get
指令go get github.com/aceld/zinx/znet
不主动执行go get
指令,也会自动下载
go.mod
被修改,同时多了一个go.sum
文件.go.mod
文件"github.com/aceld/zinx/znet"
和"github.com/aceld/zinx/ziface"
,所以就间接的依赖了github.com/aceld/zinx
go.sum
文件go.sum
文件,其详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。h1:hash
情况:go.mod hash
情况:为了作尝试,假定我们现在对zinx版本作了升级, 由zinx v1.2.1
升级到zinx v1.2.2
(注意zinx是一个没有打版本tag打第三方库,如果有的版本号是有tag的,那么可以直接对应v后面的版本号即可)
那么,如果我们想获得1.2.1版本的包,该如何执行命令呢?
$HOME/aceld/modules_test
,本项目的根目录执行[lmx@lmx-CentOS modules_go]$ go get github.com/aceld/zinx/znet@v1.2.1
go: downloading github.com/aceld/zinx v1.2.1
go: downgraded github.com/aceld/zinx v1.2.2 => v1.2.1
go get package@version
在包后面加上@就可以下载指定版本的包;
如果要升级最新版本,可以执行
go get -u package
会更新所有依赖该包的版本
这样我们,下载了之前版本的zinx, 版本是v1.2.1
go.mod
中:$GOPATH/pkg/mod/github.com/aceld
下,已经有了两个版本的zinx库zinx@v1.2.1
这个不是最新版, 我们要改成最新版本zinx@v1.2.2
./goProject/modules_go
项目目录下,执行$ go mod edit -replace=zinx@v1.2.1=zinx@v1.2.2
replace
关键字.用于将一个模块版本替换为另外一个模块版本module function_go
go 1.20
package Initlib1
import "fmt"
// lib1提供的API
func lib1Test() {
fmt.Println("lib1Test()...")
}
func init() {
fmt.Println("lib1")
}
package Initlib2
import "fmt"
// lib2提供的API
func lib2Test() {
fmt.Println("lib2Test()...")
}
func init() {
fmt.Println("lib2")
}
go.mod
中模块名是function_go
,导入包的路径就是function_go/InitLib1
包名.方法()
package main
import (
"function_go/InitLib1"
"function_go/InitLib2"
)
func main() {
InitLib1.Lib1Test()
InitLib2.Lib2Test()
}
lib1
lib2
libmain init
libmian main
fun_go
和function_go
是两个不同的模块,如果要在fun_go
下的main.go
中导入function_go/InitLib1
包,也需要指定模块名:package main
import (
"fmt"
"function_go/InitLib1" // 指定模块名
)
func main() {
InitLib1.Lib1Test()
fmt.Println("go")
}