前言: 在Golang1.11之前的版本中,官方没有提供依赖和包管理工具。开发者通常会使用vendor或者glide的方式来管理依赖(也有直接使用GOPATH多环境方式),而在Golang1.11之后官方终于出了名为go modules的版本管理机制。

注意: - 在Golang1.11版本中需要使用export GO111MODULE=on来显式开启go module - 在Golang1.12之后默认开启了module

Golang Module快速入门

初始化项目

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 在GOPATH之外创建一个项目目录
  mkdir cmdb-job

# 进入目录后初始化一个module
  cd cmdb-job
  go mod init cmdb-job
go: creating new go.mod: module cmdb-job

# 查看生成的mod.md
  cat go.mod
module cmdb-job

go 1.12

# 使用第三方模块编写一丢丢代码逻辑
# 一个字符串简单几种加密方式的简单工具
 cat main.go
/*================================================================
*Copyright (C) 2019 BGBiao Ltd. All rights reserved.
*
*FileName:main.go
*Author:BGBiao
*Date:2019年09月07日
*Description:
*
================================================================*/
package main
import (
    "fmt"
    "github.com/xxbandy/go-utils/crypto"
)

func main() {
	fmt.Println(crypto.Md5("hello"))
	fmt.Println(crypto.Hmac("key2", "hello"))
	fmt.Println(crypto.Sha1("hello"))
	fmt.Println(crypto.HmacSha1("key2", "hello"))
}

# 查看下项目结构和依赖
 tree -L 1 .
.
├── go.mod
├── go.sum
└── main.go

测试运行项目

 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
30
31
32
# 测试运行
# 第一次运行会自动检查代码里的第三方依赖包,并下载和接下项目依赖的包
➜  go run main.go
go: finding github.com/xxbandy/go-utils/crypto latest
go: finding github.com/xxbandy/go-utils latest
go: downloading github.com/xxbandy/go-utils v0.0.0-20190506113112-d88d469a26b2
go: extracting github.com/xxbandy/go-utils v0.0.0-20190506113112-d88d469a26b2
5d41402abc4b2a76b9719d911017c592
f1b90b4efd0e5c7db52dfa0efd6521a3
aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
79806c50bab4825a371e3c4fff80bae0c731d54e

# 第二次运行
➜  go run main.go
5d41402abc4b2a76b9719d911017c592
f1b90b4efd0e5c7db52dfa0efd6521a3
aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
79806c50bab4825a371e3c4fff80bae0c731d54e

# 查看管理mod的依赖配置
## go.mod会记录项目所需依赖以及具体的版本号记录(go-utils这个库没有版本,所以会根据提交日期和commitid来唯一标识版本)
➜  cat go.mod
module cmdb-job

go 1.12

require github.com/xxbandy/go-utils v0.0.0-20190506113112-d88d469a26b2 // indirect

## go.sum文件会记录依赖树的详细信息
➜  cat go.sum
github.com/xxbandy/go-utils v0.0.0-20190506113112-d88d469a26b2 h1:SD6OuR8EUvCmiBDoxItcFYXIMuse45HvuSh3ho0AAeU=
github.com/xxbandy/go-utils v0.0.0-20190506113112-d88d469a26b2/go.mod h1:XhyyBI+KH4XEIbw3EhMFjZfJcV3kDK4iSAT7zZYd+Uc=

Go Module的其他功能

注意:go mod 还有一些其他比较有意思的工具,比如可以打印依赖树,比如可以查看哪些模块在哪些包引用了

go mod的其他特性

 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
30
31
32
33
# 执行go help mod可以查看到以下子命令
➜ go help mod
	download    download modules to local cache       # 下载模块包到本地缓存
	edit        edit go.mod from tools or scripts     # 从工具或脚本中修改go.md内容(暂时没觉得很好用)
	graph       print module requirement graph        # 打印模块依赖树
	init        initialize new module in current directory # 在当前目录初始化一个模块
	tidy        add missing and remove unused modules      # 添加或略或删除不使用的包
	vendor      make vendored copy of dependencies         # 将依赖拷贝到vendor中(会将依赖打包到当前目录下的vendor目录)
	verify      verify dependencies have expected content  # 检查依赖内容
	why         explain why packages or modules are needed # 解释为什么哪些包或者模块被需要


# 查看当前项目的依赖树(项目复杂时比较有用)
➜  go mod graph
cmdb-job github.com/xxbandy/go-utils@v0.0.0-20190506113112-d88d469a26b2
# 查看模块或包在哪里被依赖了
➜  go mod why github.com/xxbandy/go-utils/crypto
# github.com/xxbandy/go-utils/crypto
cmdb-job
github.com/xxbandy/go-utils/crypto

# 拷贝一份依赖到项目本地
# 会将项目的依赖包拷贝到项目的vendor目录中
➜  go mod vendor
➜  tree -L 3 .
.
├── go.mod
├── go.sum
├── main.go
└── vendor
    ├── github.com
    │   └── xxbandy
    └── modules.txt

注意:这个时候可能会有人有疑问了,既然go mod vendor将依赖拷贝一份到项目家目录的vendor目录了,那原始的依赖存在哪里?比如我们之前没有依赖管理之前,默认的依赖都是在$GOPATH/src/下面的

其实,在使用了go mod之后,项目依赖的全部依赖包都会在$GOPATH/pkg/mod/cache/download/中统一缓存下来,而项目中的go.md中会记录真正的版本信息,去缓存中引用即可。

1
2
3
4
5
6
# 查看go mod之后缓存的依赖包
➜  tree -L 3   ${GOPATH}/pkg/mod/cache/download/
${GOPATH}/pkg/mod/cache/download/
└── github.com
    └── xxbandy
        └── go-utils

一个开源项目示例

我们就以golang-gin-vue项目为例来看下go mod的一些功能吧.

golang-gin-vue是一个使用gin框架编写后端接口,结合vue来渲染前端的一个示例项目.

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 克隆项目到本地
➜ git clone https://github.com/xxbandy/golang-gin-vue.git

➜  cd golang-gin-vue

# 使用go mod初始化项目
➜  golang-gin-vue git:(master) go mod init golang-gin-vue
go: creating new go.mod: module golang-gin-vue

# 初始化后会默认生成go.mod文件,但是不会加载依赖包,需要go run或者go build来自动加载依赖
➜  golang-gin-vue git:(master) ✗ go build main.go
go: finding github.com/gorilla/mux v1.7.3
go: downloading github.com/gorilla/mux v1.7.3
go: extracting github.com/gorilla/mux v1.7.3

# 查看go.mod文件
➜  golang-gin-vue git:(master) ✗ cat go.mod
module golang-gin-vue
go 1.12
require (
	github.com/gin-gonic/gin v1.4.0 // indirect
	github.com/gorilla/mux v1.7.3 // indirect
)

# 打印依赖树关系
## 基本上可以知道项目和库依赖了哪些库
➜  go mod graph
golang-gin-vue github.com/gin-gonic/gin@v1.4.0
golang-gin-vue github.com/gorilla/mux@v1.7.3
github.com/gin-gonic/gin@v1.4.0 github.com/gin-contrib/sse@v0.0.0-20190301062529-5545eab6dad3
github.com/gin-gonic/gin@v1.4.0 github.com/golang/protobuf@v1.3.1
github.com/gin-gonic/gin@v1.4.0 github.com/json-iterator/go@v1.1.6
github.com/gin-gonic/gin@v1.4.0 github.com/mattn/go-isatty@v0.0.7
github.com/gin-gonic/gin@v1.4.0 github.com/modern-go/concurrent@v0.0.0-20180306012644-bacd9c7ef1dd
github.com/gin-gonic/gin@v1.4.0 github.com/modern-go/reflect2@v1.0.1
github.com/gin-gonic/gin@v1.4.0 github.com/stretchr/testify@v1.3.0
github.com/gin-gonic/gin@v1.4.0 github.com/ugorji/go@v1.1.4
github.com/gin-gonic/gin@v1.4.0 golang.org/x/net@v0.0.0-20190503192946-f4e77d36d62c
github.com/gin-gonic/gin@v1.4.0 gopkg.in/go-playground/assert.v1@v1.2.1
github.com/gin-gonic/gin@v1.4.0 gopkg.in/go-playground/validator.v8@v8.18.2
github.com/gin-gonic/gin@v1.4.0 gopkg.in/yaml.v2@v2.2.2
github.com/stretchr/testify@v1.3.0 github.com/davecgh/go-spew@v1.1.0
github.com/stretchr/testify@v1.3.0 github.com/pmezard/go-difflib@v1.0.0
github.com/stretchr/testify@v1.3.0 github.com/stretchr/objx@v0.1.0
github.com/mattn/go-isatty@v0.0.7 golang.org/x/sys@v0.0.0-20190222072716-a9d3bda3a223
gopkg.in/yaml.v2@v2.2.2 gopkg.in/check.v1@v0.0.0-20161208181325-20d25e280405
golang.org/x/net@v0.0.0-20190503192946-f4e77d36d62c golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net@v0.0.0-20190503192946-f4e77d36d62c golang.org/x/text@v0.3.0
golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/sys@v0.0.0-20190215142949-d0b11bdaac8a

# 查看gin这个模块被哪里需要(golang-gin-vue项目和gin框架本身需要)
➜ go mod why github.com/gin-gonic/gin
# github.com/gin-gonic/gin
golang-gin-vue
github.com/gin-gonic/gin

Tips

我们在使用Golang进行开发过程中,通常会发现各种第三方库会依赖golang.org/x之类的原生库,或者一些其他国外大厂提供的共有库,由于政策原因,我们是无法直接访问国外网站来下载依赖库的(网速也有限制),因此在开发过程中也是比较头疼的. 这个时候通常大家都会去采用科学上网,或者通过一些代理的方式来解决. 而在Golang高版本中,包含了goproxy特性,用户可以直接指定代理来下载依赖的第三方库,一方面解决了下载速度的问题,另外一方面也解决了无法访问的第三方库的下载。

注意:goproxy必须在go1.11以上版本,并且开启了go module机制后才能使用的(因为我当前的环境都是1.12,大家可以验证下小版本是否支持)

而在国内,质量比较好的几个goproxy代理大概有如下几个:

go代理使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 开启go module支持
# golang1.11 需要手动开启,golang1.12之后默认会开启
$ export GO111MODULE=on

# 设置代理(上述三个代理地址都可以)
$ export GOPROXY=https://goproxy.cn

# 下载第三方库

$ go get -v golang.org/x/net/websocket
Fetching https://goproxy.cn/golang.org/x/net/websocket/@v/list
Fetching https://goproxy.cn/golang.org/x/net/@v/list
go: finding golang.org/x/net latest
Fetching https://goproxy.cn/golang.org/x/net/@latest
Fetching https://goproxy.cn/golang.org/x/net/@v/v0.0.0-20190827160401-ba9fcec4b297.mod
golang.org/x/net/websocket

自从使用了goproxy之后,妈妈再也不用担心无法下载第三方库拉,爽到不行.

知识星球

公众号