「Golang进阶之路」依赖管理篇
[toc]
写在前面
本文将重点介绍Go一些包管理方式,这些方式在日常工作中都是常见的场景,主要介绍Go包管理、多项目依赖开发,企业或者组织私有包的管理方式和工作区模式等,如果你是一个Go初级开发者,那么我强烈推荐本文
包管理
先抛出一个问题:Go的依赖管理有哪些?
我们知道目前Go的主流的管理是Go Modules, 下图是主要的发展史:
我们直接看Go Modules
Go Modules
go1.11发布Go Modules,Go Modules的功能默认是关闭的,直到go1.13才开启。所以在go1.11和go1.12版本中我们需要通过go提供的变量GO111MODULE来开启Go Modules,这跟我们上面介绍的GO15VENDOREXPERIMENT环境变量类似。GO111MODULE也属于Go的段性变量,在某个功能发布之初的几个版本是需要手动开启的,等稳定下来被开发者接受后会在后续的版本中默认开启。
GO111MODULE 这个环境变量有三个值:
- on 开启Go modules
- off 关闭Go modules
- auto 当项目路径在 $GOPATH 目录外部时, 设置为 GO111MODULE = on;当项目路径位于 GOPATH 内部时,即使存在 go.mod文件, 也会设置 GO111MODULE = off
我们可以使用go env
查看:
> go env
GO111MODULE='on'
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/iceymoss/.cache/go-build'
GOENV='/home/iceymoss/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/iceymoss/go/pkg/mod'
GONOPROXY='gitlab.iceymoss.com'
GONOSUMDB='gitlab.iceymoss.com'
GOOS='linux'
GOPATH='/home/iceymoss/go'
GOPRIVATE='gitlab.iceymoss.com'
GOPROXY='https://goproxy.cn,direct'
GOROOT='/usr/lib/go-1.22'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/go-1.22/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.22.2'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-L/home/'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build327796706=/tmp/go-build -gno-record-gcc-switches'
开启Go Modules:
Unix/Linux环境
export GO111MODULE=on
windows环境
set GO111MODULE=on
或者使用go env 指令进行设置
go env -w GO111MODULE=on
需要注意的是,使用go env如果出现下面的错误,则说明在操作系统的环境变量中已经配置这个变量,go env指令无法覆盖操作系统级别的环境变量。
warning: go env -w GO111MODULE=... does not override conflicting OS environment variable
通常这些环境变量可能会在下面这些文件中设置:
/etc/profile ,/.profile,/etc/bashrc,/.bashrc,~/.bash_profile
需要注意的是,仅仅设置 GO111MODULE=on并不能代表项目就使用了Go modules的方式来管理包,还应该使用go mod init将项目初始化成一个Go modules工程。这个命令需要在项目的根目录下执行,执行成功后会在当前目录下生成go.mod的文件。
下面我们就来看看Go modules的具体用法:
环境配置
注意:我们我们的Go版本最低要求Go1.11, 我这里使用的是:
> go version
go version go1.22.2 linux/amd64
设置代理
我们可以需要设置一下加速代理,这里使用阿里云的代理:
export GOPROXY=https://goproxy.cn/,https://mirrors.aliyun.com/goproxy/,direct set GOPROXY=https://goproxy.cn/,https://mirrors.aliyun.com/goproxy/,direct```
或者
go env -w GOPROXY=https://goproxy.cn/,https://mirrors.aliyun.com
初始化工程
使用命令:
mkdir go-mod-demo
go mod init go-mod-demo
按照一下步骤:
> mkdir go-mod-demo
~/icey/demo/go-project ......................................................................... 11:50:05> cd go-mod-demo
~/icey/demo/go-project/go-mod-demo ............................................................. 11:50:07> ls
~/icey/demo/go-project/go-mod-demo ............................................................. 11:50:08> go mod init go-mod-demo
go: creating new go.mod: module go-mod-demo
~/icey/demo/go-project/go-mod-demo ............................................................. 11:50:58> ls
go.mod
~/icey/demo/go-project/go-mod-demo ............................................................. 11:51:01> cat go.mod
module go-mod-demo
go 1.22.2
这样我们就创建了一个Go Modules依赖管理方式的项目
接着我们来导入一个依赖,执行以下:
go get github.com/gin-gonic/gin
然后我们在项目根目录下创建一个main.go入口程序:
package main
import (
"net/http"
"github.com/gin-gonic/gin" // 引入依赖
)
//handle方法
func Pong(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ "name": "ice_moss", "age": 18, "school": "家里蹲大学",
})}
func main() {
//初始化一个gin的server对象
//Default实例化对象具有日志和返回状态功能
r := gin.Default() //注册路由,并编写处理方法
r.GET("/ping", Pong) //监听端口:默认端口listen and serve on 0.0.0.0:8080
r.Run(":8083")
}
然后执行:go run main.go
:
> go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.Pong (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
我们再来看一下mod文件:
module go-mod-demo
go 1.22.2
require (
github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.10.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect)
并且他还生成了一个go.sum文件
可以看到他多了很多的依赖了,当然如果我们刚clone了到本地项目,我们可以在go.mod同级目录下执行:
go mod tidy
该命令会根据mod中的依赖情况去拉取对应的内容
这里关于go mod的几个指令我们需要掌握:
go mod init
用来初始化一个新的go modules工程, 并创建go.mod文件
go mod tidy
用来解决工程中包的依赖关系,如果是缺少的包会下载并将依赖包的信息维护到go.mod和go.sum文件中;如果是项目中没有用到的依赖包则会将依赖包的信息从go.mod和go.sum文件中移除。
go mod download
下载依赖包到本地缓存。如果go.mod和go.sum文件中已经包含依赖包的信息,而依赖包还没下载到本地就可以执行这个指令。
go mod vendor
这个指令是为了兼容Go Vendor模式,在Go Modules发布之前,Go Vendor使用比较普遍,所以go mod也支持将依赖包通过go mod vendor指令复制到项目的vendor目录中。
当使用go mod vender后:
> go mod vendor
> ls
LICENSE README.md apps config deploy docker-compose.yaml docs go.mod go.sum hichat2.sh pkg resources vendor
可以看到多了一个vender命令,下面都是各个依赖包了
最常用的指令就是上面4个,还有一些其他指令我们可以通过go help mod来查看相关的帮助文档,主要包含go mod graph,go mod why和go mod verify等查看和校验依赖的指令。go mod edit 用来编辑 go.mod文件的指令。平时很少使用,了解即可。
接着我们需要了解Go modules中两个重要的文件:go.mod和go.sum
go.mod文件
上面我们执行go mod init go-mod-demo之后,在当前目录下生成了一个go.mod的文件。每个 Go 模块都由一个 go.mod 文件定义,这个文件描述模块的属性,包括它对其他模块和 Go 版本的依赖性
// module声明工程名
module go-mod-demo
// 记录当前环境的go版本
go 1.22.2
require (
// 直接依赖包(即代码中import的包)
gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.25.12
// 后面有indirect的表示项目的间接依赖(直接依赖包的依赖)
github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.10.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect)
对于一个标准的go.mod文件而言,必须有module定义。如果项目只用了go内置的库,则不会有require的定义。当然go.mod文件还有include,exclude和replace的定义, 将在后面会详细介绍。
这个文件中我们需要注意的是:
- module命名一般使用git仓库的路径,比如上面gin的包,它的仓库地址是:github.com/gin-gonic/gin ,则模块名就是 github.com/gin-gonic/gin 其中github.com是git仓库的域名,gin-gonic/gin是代码在仓库中的路径。这样定义模块名才能让工程可以通过远程去下载。内部的私有仓库也是这种命名规则,使用git仓库的域名+工程路径的方式命名。
- 第二行的go 1.22.2的版本号在官方文档中指的是当前工程所依赖的最低的go版本。但实际上它只是获取了你当前环境的go版本,只要代码兼容,上面的代码在低版本的go环境中也是可以运行的。所以这个版本定义其实不是必须的,去掉也不会报错。
- require指的是导入的非go内置的库,或者非工程代码内部定义的包。后面有indirect注释的代表间接依赖包,没有的代表直接依赖包。需要注意的是只有当间接依赖包版本不包含go.mod文件时候才会记录到go.mod文件中。 require后面首先跟的是包的路径,然后是版本号。版本号可以是远程库中存在的版本也可以是go mod生成的伪版本号,上面的间接依赖就是一个伪版本号。
什么情况下会产生伪版本呢?
当源代码中
import
指向的模块不存在于 go.mod 文件中时,Go 命令行工具会自动搜索这个模块,并将最新版本(最后一个 tag 且非预发布的稳定版本)添加到 go.mod 文件中。如果这个包没有打tag,则使用伪版本,专门用于标记没有 tag 的提交。比如v0.0.0-20151119151921-a422bbe96644,它其实是由3部分组成的:前面部分为语义化版本号,用于标记版本;中间部分为 UTC 的提交时间,用于比较两个伪版本以其确定先后顺序;后面部分是 commit 哈希的前缀,用于标记该版本位于哪个 commit。
go.sum文件
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
go.sum文件是我们触发项目依赖校验时生成,比如我们执行了go build,go run,go get,go mod tidy等指令。 它详细罗列了当前项目直接或间接依赖的所有模块的版本
go.sum文件每行由模块导入路径、模块的特定版本和预期哈希组成并通过空格分隔。
<module> <version>[/go.mod] <hash>
一般情况下,每个依赖包版本会包含两条记录,
- 第一条记录为该依赖包所有文件的哈希值,
- 第二条记录仅表示该依赖包版本中go.mod文件的哈希值,
如果该依赖包版本没有go.mod文件,一般只有第一条记录。比如上面的gin包,第一行的版本v1.10.1后面的hash值表示该依赖包版本整体的哈希值,
而v1.10.1/go.mod后面的hash值表示该依赖包版本中go.mod文件的hash值。
go.sum的校验规则
当我们拿到某项目的源代码并尝试在本地构建,go命令会从本地缓存中查找所有go.mod中记录的依赖包,并计算本地依赖包的哈希值,然后与go.sum中的记录进行对比,即检测本地缓存中使用的依赖包版本是否满足项目go.sum文件的期望。如果校验失败,则说明本地缓存目录中依赖包版本的哈希值和项目中go.sum中记录的哈希值不一致,这时候构建就会被拒绝。 这样就可以确保相同的依赖包在任何环境下使用的依赖包的源码都是一样的。
依赖包版本中任何一个文件(包括go.mod)改动,都会改变其整体哈希值,此处再额外记录依赖包版本的go.mod文件主要用于计算依赖树时不必下载完整的依赖包版本,只根据go.mod即可计算依赖树。
每条记录中的哈希值前均有一个表示哈希算法的h1:,表示后面的哈希值是由算法SHA-256计算出来的。
模块版本的 SHA-256 哈希值用来验证当前缓存的模块,以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。在每次缺少模块时,如果缓存中不存在,则需要下载并计算其哈希添加到 go.sum 中;如果缓存中存在,则需要匹配 go.sum中已有的记录。
需要注意的是每次缺少模块时,即依赖包不在go.sum
文件中(即新增的依赖包),且是一个公网可下载的包,Go 命令会去 Go 校验数据库(如默认配置的 sum.golang.org 或中国大陆官方校验和数据库 sum.golang.google.cn )查询并获取该模块的校验和,如果下载的模块代码与校验和不匹配,则报告不匹配相关信息并退出,如果匹配,则将校验和写入 go.sum
文件中。
有三种情况不会对依赖包做哈希校验:
- 配置了 GOPRIVATE时,GOPRIVATE匹配到的包将不会做checksum校验,GOPRIVATE变量主要是用来设置内部的包不使用GOPROXY配置的代理,因为内部一般都不会上传到公网。
- 使用go mod vendor命令可以将依赖的包全部下载到当前工程根目录下的vendor目录下,可供离线编译,打包到vendor目录中的包也不会再做校验。
- GOSUMDB设置为off时,所有的包都不会做校验
需要注意的是同一个模块版本的数据只缓存一份,所有其它模块共享使用。如果你希望清理所有已缓存的模块版本数据,可以执行 go clean -modcache
命令。
为什么go.sum文件中记录的依赖包版本数量会比go.mod文件中要多呢?
这是因为go.mod记录的是直接依赖的依赖包版本,只在依赖包版本不包含go.mod文件时候才会记录间接依赖包版本,而go.sum则是要记录构建用到的所有依赖包版本。
如果不希望进行依赖包检验,也可以关闭,将GOSUMDB设置为off即可:
将环境变量添加到/etc/profile
export GOSUMDB=off (unix环境)
或者使用:
go env -w GOSUMDB=off
需要注意的是,GOPATH模式下和Go Modules模式下依赖包存储的路径是不一样的。
GOPATH
模式下,依赖包存储在$GOPATH/src
,该目录下只保存特定依赖包的一个版本,而且通过go get下载的包是完整的仓库。
而在GOMODULE
模式下,依赖包存储在$GOPATH/pkg/mod
,该目录中可以存储特定依赖包的多个版本,每个版本占用一个目录,目录中只包含依赖包文件,不包含.git
目录。
除了上面介绍的这些Go包管理工具外,我们在构建Go项目时也需要遵循一些原则和规范性的建议:
- 一个目录名下只能有一个package,否则编译器会报错。
- 建议一个package名的内容放在一个目录下面,便于项目管理。
- 建议目录名和package名相同,便于项目管理。
内部包管理
这里说的内部包有两层函数:
- 项目中internal目录的包
- 是公司内部开发的私有包(内部私有依赖)
internal包
internal其实是用来控制包的可见性的,可以回忆一下Go的函数和变量的可见性:
小写字母开头的函数变量结构体只能在本包内访问
大写字母开头的函数变量结构体可以在其它包访问
如果结构体是大写字母开头,但它的字段或方法名是小写字母开头,则这些字段或者方法也只能在本包内访问
大小写只能控制包内的常量变量函数方法等能不能被其他包调用,如何限制整个包不被外部包导入呢?
在 Go 语言中,通过标识符的大小写(如大写 Public、小写 private)可以控制包内元素的访问权限,但这仅限制包内的常量、变量、函数等能否被外部包调用。
若想完全禁止外部包导入整个包本身,就需要使用 internal 目录机制(Go 1.4+ 引入)。
说到这里我们先看看为什么需要internal:
假设项目中有敏感模块(如管理后台接口),若被普通业务代码误导入可能导致安全隐患(例如越权操作)。常规的大小写控制无法阻止整个包的导入,这时 internal 目录就能强制实现包级别的隔离。
internal核心规则
- 作用:声明 internal 目录下的包仅允许其父目录及其子孙目录导入。
- 强制力:编译器在编译时会严格校验,违反规则会直接报错。
- 嵌套性:internal 目录可多层嵌套,每层规则一致。
internal文件夹内的代码包中声明的公开程序实体(比如大写开头的常量,变量,函数,方法等)只能被其父目录下面的包或者子包引用。
├── project-demo
│ ├── a
│ │ ├── a.go
│ │ └── b
│ │ ├── b.go
│ │ ├── c
│ │ │ ├── c.go
│ │ │ └── internal # 关键点!
│ │ │ └── d
│ │ │ ├── d.go
│ │ │ └── e
│ │ │ ├── e.go
│ │ ├── g
│ │ └── g.go
│ ├── go.mod
│ └── main.go
看分析:
✅ 允许导入 internal/d 的位置:
- c.go(父目录c/ 下的文件)
- d.go、e.go(internal/d/ 内部的子孙文件)
- 所有 c/ 的子目录中的代码(如 c/ 下新建的 x/x.go)
❌ 禁止导入 internal/d 的位置:
- a.go(目录层级超过 c/ 的父级)
- b.go(b/ 与 c/ 同级,但不在 c/ 内部)
- g.go(g/ 是 c/ 的兄弟目录,但非其子目录)
- main.go(项目根目录,与 c/ 无直接子孙关系)
其实就这个规律:
c目录下的go文件可以导入c/internal/的任意包或者文件,c/internal/下的go文件之间可以任意导入(当然不能循环导入)
再来看一个场景示例:安全隔离案例
业务代码误导入管理后台包(admin),导致普通用户能调用高危接口,我们可以创建一个internal目录来限制管理后台代码的访问
project/
├── app
│ ├── user/
│ │ └── user.go // 普通用户业务
│ ├── admin/ // 管理模块
│ │ └── internal/ // 添加可见性admin对外不可见
│ │ └── admin.go // 真正的管理代码
│ └── internal/ // 业务共享代码
│ └── utils.go // 通用工具utils可以为app/下使用
└── main.go
总结
- internal 的父目录(示例中的 c/)是权限的“锚点”,其外的代码均无权访问。
- 若多个目录都需要隔离,可为每个功能模块单独建立 internal 目录(如 pkg1/internal/、pkg2/internal/)。
- 公共工具包(如 utils)应放在非 internal 路径下,避免过度隔离导致重复造轮子。
内部开发的包(依赖)
在实际工作中公司或者组织有非常多的公用或者特殊业务的依赖包,这些包都是公司内部的,看到不能发到github上去吧,那么这些包如何为各个业务组导入使用呢?
- 通过本地包的方式导入,这就需要用到go mod的另一个语法replace
- 通过私有仓库的方式导入
通过本地包的方式导入
我们可以使用replace 将源码import的包名替换成本地包的路径,具体实现方式如下:
构建依赖项目(包)
首先我们需要初始化一个Go Modules公共依赖包
> mkdir private-lib
> cd private-lib
> go mod init gitlab.iceymoss.com/iceymoss/private-pkg
go: creating new go.mod: module gitlab.iceymoss.com/iceymoss/private-pkg
> ls
go.mod
然后我们在这个各个依赖项目中写一个功能:
> mkdir pkg
> ls
go.mod pkg
> cd pkg
> vim work.go
写入:
package pkg
var Address string
构建业务项目
> ls
private-lib
> mkdir go-demo-api
> cd go-demo-api
> go mod init go-demo-api
go: creating new go.mod: module go-demo-api
导入我们的依赖,在go.mod添加:
replace gitlab.iceymoss.com/iceymoss/private-pkg => ../private-lib
然后使用这个包main.go
package main
import (
"fmt"
"gitlab.iceymoss.com/iceymoss/private-pkg/pkg"
)
func main() {
pkg.Address = "127.0.0.1" fmt.Println("address:", pkg.Address)}
构建依赖
> go mod tidy
go: found gitlab.iceymoss.com/iceymoss/private-pkg/pkg in gitlab.iceymoss.com/iceymoss/private-pkg v0.0.0-00010101000000-000000000000
我们可以看到Go Modules为我们处理依赖后添加了require这行,并生成了一个版本号,这时我们就可以正常运行这个程序了。
module go-demo-api
go 1.22.2
replace gitlab.iceymoss.com/iceymoss/private-pkg => ../private-lib
require gitlab.iceymoss.com/iceymoss/private-pkg v0.0.0-00010101000000-000000000000
运行:
> go run main.go
address: 127.0.0.1
这样我们的依赖的通过本地的方式导入了,这种方式适合公共依赖包进行快速开发,测试和验证
除了上面的用法还可以:
// 替换为 GitHub 分支
replace example.com/pkg => github.com/user/fork@branch
// 替换为不同版本
replace example.com/pkg v1.0.0 => v1.1.2
exclude github.com/google/uuid v1.1.0 // 排除有安全漏洞的版本
私有仓库的方式导入
通过本地replace的方式导入开发包存在一个很大的弊端,由于replace引入的是本地环境的路径,当其他人使用这个工程时,他必须先将这些代码拉取到本地放到跟工程对应的相对目录下才能构建成功,
当replace的包发生变更时也需要及时的手动同步最新的版本代码,非常的麻烦。加上企业内部为了避免各业务组重复造轮,
也会考虑将内部开发的这些包在企业内实现共享,这样,企业内部的这些包就不需要走GOPROXY代理,直接到企业内部git平台上拉起源码。
确保Go版本>=1.11,且在Go1.13之前的版本确保开启了GO111MODULE。
设置GOPROXY
go env -w GO111MODULE=on go env -w GOPROXY=https://goproxy.cn/,https://mirrors.aliyun.com/goproxy/,direct
设置GOPRIVATE,这里我们将gitlab.iceymoss.com作为我们内部的git平台,根据情况替换你的平台即可
# linux终端 export GOPRIVATE=*.gitlab.iceymoss.com #windows终端 set GOPRIVATE=*.gitlab.iceymoss.com 或者 go env -w GOPRIVATE=*.gitlab.iceymoss.com
开发机器上可以直接在编辑器的环境变量中配置:
GOSUMDB也可以设置成国内的,为了加快构建速度,我们也可以直接关闭
go env -w GOSUMDB=sum.golang.google.cn
上传公共依赖包
上传公共依赖就和平常开发一样,这里不做介绍了,上传完成后,直接把之前项目go.mod的依赖去掉:replace gitlab.iceymoss.com/iceymoss/private-pkg => ../private-lib require gitlab.iceymoss.com/iceymoss/private-pkg v0.0.0-00010101000000-000000000000
然后执行:
go mod tidy
因为我们业务项目main.go中存在导入
gitlab.iceymoss.com/iceymoss/private-pkg
他会去对应平台拉取
GOPROXY
GOPROXY就是Go用来下载依赖包的代理,go拉取包的时候,可以根据 GOPROXY
环境变量的设置,从代理获取模块或直接连接到源代码管理服务器拉取代码。
GOPROXY是从Go1.11版本开始支持的,也就是跟随Go Modules诞生的。
GOPROXY
的默认设置是 https://proxy.golang.org,direct
,这表示go在下载依赖包时先会从proxy.golang.org 这个代理服务器上拉取镜像代码,
如果代理服务器上没有这个依赖包(HTTP响应404或410)则直接到源码地址拉取依赖包(模块)。
GOPROXY 设置为 "direct"
会直接到源代码管理服务器下载依赖包(模块)。
GOPROXY设置为 "off"
表示不允许从任何源下载依赖包(模块)
需要注意的是proxy.golang.org
为 Go
官方模块代理网站,国内访问会很慢,也很容易超时。
七牛云推出的非盈利性 Go
模块代理网站,为中国和世界上其他地方的 Gopher 们提供一个免费的、可靠的、持续在线的且经过 CDN 加速的模块代理:https://goproxy.cn
或者使用阿里云提供的镜像网站:
mirrors.aliyun.com/goproxy/
需要注意的是,在实际工作中,不是所有的包都可以走公网的代理网站拉取包的,有些包我们不希望走代理该怎么办呢? 通过配置GONOPROXY和GOPRIVATE来实现
GOPRIVATE
对于GOPRIVATE我们需要直到下面几点:
1. GOPRIVATE对go语言版本有要求吗?
2. GOPRIVATE的作用是什么?它是怎么配置的?
3. GOPRIVATE和GONOPROXY有啥区别?
GOPRIVATE是从go1.13开始支持的。
GOPRIVATE的作用是让指定域名不走代理,也不进行Go Modules的包校验。
配置方式跟GOPROXY类似,可以使用域名,也可以使用域名的通配符,·*
表示匹配任意长度任意字符串。
比如GOPRIVATE=gitee.com表示所有域名是gitee.com的包都不走代理;
比如GOPRIVATE=*gitee.com表示所有域名中以gitee.com结尾的包都不走代理, project.gitee.com这个域名在上面第一种配置下是无效的,在第二个配置下就是有效的.GOPRIVATE跟GONOPROXY都是从Go1.13开始支持的,都可以用来配置让指定域名的包不走GOPROXY配置的代理。但GOPRIVATE的区别是当你设置了GOPRIVATE后,
其实GONOPROXY和GONOSUMDB都使用了GOPRIVATE配置,而这里GONOSUMDB是用来配置指定包不走校验和数据库进行包的签名校验的。前面讲到过Go Modules在解析包依赖时会通过GOSUMDB配置的地址 查询并获取依赖包(模块)的校验和与本地代码包计算出的 SHA-256 哈希值进行匹配,通过检验才会将包的哈希值写入到go.sum文件中。
还需要注意的是,
GONOPROXY
是可以覆盖GOPRIVATE配置的,如果将GONOPROXY
设置成none
,则GOPROXY将会失效,所有的包拉取,都会走GOPROXY
.所以我们如果希望内部包不走校验和数据库也不走GOPROXY代理,我们也可以使用下面的这种配置方式:
GOPROXY=https://goproxy.cn,direct GONOPROXY=xx.gitlab.com GONOSUMDB=$GONOPROXY
实际工作中的最佳实践
由于github上的开源包是一些个人或者其他企业或组织维护的,相关的版本tag有被删除的风险,一旦我们选择直接从github源码站上拉取这些代码,就可能存在版本拉不到的风险,所以在企业内部一般都会建立自己的go镜像仓库,将企业内部使用的代码包的版本全部同步到企业内部的镜像仓库中,同时会对这些包进行安全审核,避免使用具有重大安全漏洞的包。而企业内部开发的一些公共包则不走镜像代理,直接到企业内部的git仓库中拉取。
工作区模式
通过replace的方式导入,需要开发完成后将replcae去掉,再提交到仓库,如果有多个内部包这种操作还是很麻烦。第二种方式也一样,需要提交到仓库后才能调试,不合适开发环境。本节我们就来介绍一种既不需要使用replace也不需要将代码提交到git仓库就可以在本地开发调试内部私有包的方法。
这种方法就是Go1.8版本之后的新特性:Go的工作区模式,在开发中是非常便捷且实用的,Go的工作区模式可以支持本地多个Go Modules开发。
创建工作区模式
这里使用上面replace的示例来直接创建工作区模式,首先把go-demo-api项目中的go.mod的replace相关内容全部移除,在业务中正常像导入远程包那样导入:
import "gitlab.iceymoss.com/iceymoss/private-pkg/pkg"
进入目录多个项目的上级目录:
> ls
go-demo-api private-lib
>cd go-demo-api
>go run main.gomain.go:6:2: no required module provides package gitlab.iceymoss.com/iceymoss/private-pkg/pkg; to add it:go get gitlab.iceymoss.com/iceymoss/private-pkg/pkg
> go work init go-demo-api private-lib #使用go-demo-api private-lib创建工作区
> cat go.workgo 1.24.2
use (./go-demo-api
./private-lib
)
> cd go-demo-api
> go run main.go
address: 127.0.0.1
没错工作区模式就这么简单,这样我们就不用时刻移除replace
go.work 文件的语法和 go.mod 类似,因此也支持 replace。
在实际项目中,多个模块之间可能还有其他依赖,我们可以在 go.work 所在目录执行go work sync
来同步依赖。
这样,当我们开发完代码,只需要提交我们的工程代码和我们开发的内部私有包的代码就可以了
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: