文章目录
- 包管理
-
- [1.1 go.mod](#1.1 go.mod)
- [1.2 go.sum](#1.2 go.sum)
- [1.3 初始化一个项目](#1.3 初始化一个项目)
- [1.4 三种编译方式](#1.4 三种编译方式)
-
- [1.4.1 File 模式](#1.4.1 File 模式)
- [1.4.2 Package 模式](#1.4.2 Package 模式)
- [1.4.3 Directory 模式](#1.4.3 Directory 模式)
- [1.5 查看依赖信息](#1.5 查看依赖信息)
包管理
Go 的包管理体系经历了「无官方管理 → 第三方工具混战 → 官方标准化」的演进过程,核心是解决 依赖隔离、版本控制、环境统一 三大问题。
- GOPATH :GO 1.0 发布仅仅提供
GOPATH环境变量机制,所有项目和依赖必须放在GOPATH/src目录下(固定目录结构:GOPATH/{src/bin/pkg}),依赖通过go get下载,但仅拉取最新版本 ,无版本号概念,也无法指定版本,项目共享GOPATH下的依赖,不同项目依赖同一个包的不同版本会冲突. - vendor :GO 1.5 引入,GO 1.6 默认,核心作用是本地化存储项目依赖的源代码副本 ,解决依赖版本一致性问题,比如 Java 的
lib版本,本质上是让项目自带依赖,避免版本不一样导致的编译或者运行异常.- 项目根目录下的
vendor文件夹会优先被go编译器识别,存放当前项目的专属依赖.
- 项目根目录下的
- GO MODULE:
Go 1.11正式引入、Go 1.13成为默认依赖管理工具的官方依赖管理方案,默认开启 GO111MODULE=auto,GO 1.16成为默认模式,废弃 GOPATH 默认,彻底解决了传统 GOPATH 模式的核心痛点(全局依赖、版本冲突、多版本共存难)。它的核心目标是:让每个项目拥有独立的依赖版本管理,实现依赖的可复现、可追溯、可隔离。
1.1 go.mod
一个包含 go.mod 文件的目录就是一个 Module,是 Go 依赖管理的基本单元,比如下面 1.3 节生成的 go.mod:
go
module go-learn-1
go 1.25.0
require github.com/gin-gonic/gin v1.12.0
require (
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // 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.30.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // 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.4 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
)
- module :项目模块名称为
go-learn-1,相当于项目的唯一 ID,在项目内导入自己的包时,需要以这个名称为前缀(比如 import "go-learn-1/utils"),Go Modules 基于这个名称识别项目边界,管理依赖. - require:声明项目依赖的模块.
- indirect:注释表示这是间接依赖(不是直接导入的,而是 gin 框架本身依赖的包).
1.2 go.sum
go.sum 是依赖的 "哈希校验清单",自动生成,记录每个依赖版本的加密哈希值,作用:
- 校验依赖完整性:下载依赖时,Go 会对比本地哈希与 go.sum 中的值,防止依赖被篡改(中间人攻击).
- 锁定版本:确保同一模块在不同环境下载的依赖版本完全一致.
比如:
go
github.com/gin-gonic/gin v1.9.1 h1:k4675tC5Uf2H54fx43Cfd1UoTWpAa8lS8DMhBjAp+ro=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
每行格式:模块路径 版本 哈希值(h1: 开头是二进制哈希,go.mod 哈希单独列).
为什么需要两种哈希值:h1 开头的是用于校验源码完整性,而 go.mod 命令开头的是轻量校验依赖声明。 简单来说就是 h1 是源码文件生成的,用于确保源码没有被篡改,而 go.mod 不需要下载源码,只通过 go.mod 生成哈希,这种就是只校验依赖清单,比如执行 go mod graph,Go 只需要知道各个模块的依赖关系,不会下载完整源码,此时只校验 go.mod 的哈希即可,如果校验通过就可以直接从 go.mod 里面读出依赖,大幅减少网络请求。
那如果哈希值被修改了,比如我们修改 go.sum 里面的一个哈希,这种情况下 go 会探测到缓存(GOMODCACHE )里面的依赖的哈希和 go.sum 不一致,编译失败,比如下面的例子,我们修改下一个依赖的 hash,将后面的 = 去掉:

下面就是 1.3 节初始化项目之后生成的 go.sum:
go
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
......
1.3 初始化一个项目
创建项目使用的是 goland,初始化的时候指定文件路径以及 GOROOT,左边选第一个 Go,默认就是 GO MODULE,如果选第二个就是通过 GOPATH 管理的项目.

项目创建成功之后会生成 go.mod 和一个 main 文件.

下面改下 main 文件.
go
// main.go(示例代码,导入gin框架)
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello Go Module!")
})
r.Run(":8080")
}
通过 go get 获取依赖,默认最新版本,也可以指定版本:
go
go get "github.com/gin-gonic/gin"
// 格式:go get 模块路径@版本号
// go get github.com/gin-gonic/gin@v1.8.1 // 指定版本 1.8.1
// go get github.com/gin-gonic/gin@latest // 升级到最新版
// go get github.com/gin-gonic/gin@master // 拉取master分支(自动生成伪版本)
然后通过 go mod tidy 清理不需要的依赖,通过删除 go.mod 中没有被代码引用的依赖,同时补充代码中使用但是 go.mod 中没有声明的依赖,保持 go.mod/go.sum 简洁.
go
go mod tidy

下面直接运行 main 方法,如果是用的 go 命令,就需要使用 go build 先编译,再通过 go run main.go 运行 main 方法:
go
// 编译
go build -o go-learn-1
// 运行 main 方法
go run main.go

接下来浏览器访问 localhost:8080,输出如下:

1.4 三种编译方式

goland 编译项目的时候提供了三种方式,下面看看这三种方式有什么不同:
- File :单个
.go源文件,命令等价:go build main.go,仅仅编译当前选中的文件,同包的其他文件不会被自动包含,依赖同包的代码会报错undefined,就是用来测试单个 main 文件. - Package :编译某个
go包,比如go build go-learn-1/learn-1/pkg,编译该包下所有 .go 文件(除测试文件),自动处理同包依赖,这种模式是正式 Go 项目的标准包结构,main 包在指定目录. - Directory :编译本地文件系统的目录路径,比如
go build D:\go-code\go-learn-1\learn-1\directory,和 Package 一致,因为 Go 中目录与包一一对应,会编译该目录下所有 .go 文件,但是这种模式下所有的 go 文件必须要定义成同一个 package 才能运行,比如下面项目结构中的 pkg 如果用 Directory 模式去编译就没办法通过,因为 app.go 和 util.go 不是在同一个包下的.
下面就是几个例子,首先项目结构如下:

1.4.1 File 模式
learn-1/single/main.go 的代码如下:
go
package main
import "fmt"
func main() {
fmt.Println("✅ Running as [Single]: learn-1/single")
}
将 goland 配置改成 File 编译,指定这个 main 文件:

输出结果如下:
go
✅ Running as [Single]: learn-1/single
1.4.2 Package 模式
package 模式下可以指定某个包进行编译,下面我们指定编译包为:go-learn-1/learn-1/pkg.

app.go 代码如下:
go
package cmd
func GetHello2() string {
return "✅ Running as [pkg]: learn-1/pkg/cmd/app.go"
}
util.go 代码如下:
go
package cmd
func GetHello1() string {
return "✅ Running as [pkg]: learn-1/pkg/cmd/util.go"
}
注意一个目录只能又一个 main 包,main 包对应可执行文件,同一个目录下是不能生成多个可执行文件的,同时非同包的情况下引用其他包的方法需要通过 import 引入,比如下面的 "go-learn-1/learn-1/pkg" 里面定义的两个方法.
go
package main
import (
"fmt"
"go-learn-1/learn-1/pkg/cmd"
)
func main() {
fmt.Println(cmd.GetHello1())
fmt.Println(cmd.GetHello2())
}
执行结果如下:
go
✅ Running as [pkg]: learn-1/pkg/cmd/util.go
✅ Running as [pkg]: learn-1/pkg/cmd/app.go
1.4.3 Directory 模式
将模式改成 Directory,指定目录为 D:\go-code\go-learn-1\learn-1\directory.

app.go 文件如下:
go
package main
func GetHello2() string {
return "✅ Running as [directory]: learn-1/directory/app.go"
}
util.go 文件如下:
go
package main
func GetHello1() string {
return "✅ Running as [directory]: learn-1/directory/util.go"
}
main.go 文件如下:
go
package main
import (
"fmt"
)
func main() {
fmt.Println(GetHello1())
fmt.Println(GetHello2())
}
输出结果如下:
go
✅ Running as [directory]: learn-1/directory/util.go
✅ Running as [directory]: learn-1/directory/app.go
1.4.4 总结
| 表项 | |||
|---|---|---|---|
| 对比维度 | File | Package | Directory |
| 指定目标 | 单个 .go 源文件 |
Go 包的导入路径,如:go-learn-1/learn-1/pkg |
本地文件系统的目录路径,如:D:\go-code\go-learn-1\learn-1\directory |
| 等价命令 | go build main.go |
go build go-learn-1/learn-1/pkg |
go build D:\go-code\go-learn-1\learn-1\directory |
| 依赖处理 | 仅编译当前选中文件,同包其他文件不会被自动包含,依赖同包代码会报 undefined 错误 | 编译该包下所有 .go 文件(除测试文件),自动处理同包依赖 | 和 Package 一致,因为 Go 中目录与包一一对应,会编译该目录下所有 .go 文件 |
| 适用场景 | 快速测试无同包依赖的单文件 Demo | 正式 Go 项目,标准包结构,main 包在指定目录 | 不想手动输入包路径,直接通过 IDE 浏览选择目录 |
| 可运行性 | 必须是 main 包且包含 main() 函数 |
若为 main 包可运行;若为库包,仅编译检查,无法运行 |
同 Package,必须是 main 包才能运行 |
1.5 查看依赖信息
最后来看一些命令,可以查询相关的依赖信息:
-
go mod graph:查看当前模块的所有依赖

-
go mod why github.com/gin-gonic/gin:查看为什么依赖这个库

-
go list -m github.com/gin-gonic/gin:查看依赖版本

-
go list -m all:列出所有依赖以及版本

go 查找依赖的时候,优先级从高到底是:
- 项目根目录的
vendor/→GOMODCACHE(go env GOMODCACHE 查看,默认 $GOPATH/pkg/mod)→ 报错.
依赖下载流程:
- 解析 go.mod 中的依赖 → 检查 GOMODCACHE 是否已有该版本(有则直接用)→ 无则从远程仓库下载 → 校验哈希(对比 go.sum)→ 存入 GOMODCACHE.
版本缓存:
GOMODCACHE中按 模块路径@版本 存储,不同版本互不干扰,例:github.com/gin-gonic/gin@v1.8.1 和 github.com/gin-gonic/gin@v1.9.1 是两个独立目录.