依赖管理

31.1 模块代理

什么是模块代理

模块代理是一个 HTTP 服务器,用于响应 Go 工具的模块请求。使用代理可以:

  • 加速依赖下载
  • 缓存依赖包
  • 访问被墙的模块
  • 保护私有模块

配置 GOPROXY

# 设置代理
go env -w GOPROXY=https://goproxy.cn,direct

# 多个代理(按顺序尝试)
go env -w GOPROXY=https://goproxy.cn,https://proxy.golang.org,direct

# 查看当前配置
go env GOPROXY

常用代理

代理地址说明
https://goproxy.cn七牛云代理(国内推荐)
https://goproxy.io社区维护的代理
https://proxy.golang.orgGo 官方代理
direct直接从源下载

代理配置详解

# 完整配置
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GOSUMDB=sum.golang.org
go env -w GOPRIVATE=github.com/myorg/*

# 查看所有配置
go env

GOSUMDB

校验和数据库,用于验证下载的模块:

# 使用默认校验和数据库
go env -w GOSUMDB=sum.golang.org

# 使用国内镜像
go env -w GOSUMDB=sum.golang.google.cn

# 关闭校验(不推荐)
go env -w GOSUMDB=off

31.2 版本选择算法

最小版本选择

Go 使用最小版本选择算法:

  1. 收集所有模块的版本要求
  2. 选择满足所有要求的最低版本

版本选择示例

// go.mod
module example.com/myapp

go 1.21

require (
    example.com/A v1.2.0
    example.com/B v1.3.0
)

如果 A 需要 C v1.1.0,B 需要 C v1.2.0:

  • Go 会选择 C v1.2.0(满足两者的最低版本)

间接依赖

module example.com/myapp

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    gorm.io/gorm v1.25.5 // indirect
)

// indirect 注释表示这是间接依赖。

查看依赖图

# 查看依赖图
go mod graph

# 输出示例
github.com/example/myapp github.com/gin-gonic/gin@v1.9.1
github.com/gin-gonic/gin@v1.9.1 github.com/golang/protobuf@v1.5.3
github.com/gin-gonic/gin@v1.9.1 golang.org/x/crypto@v0.11.0
...

31.3 依赖版本冲突解决

版本冲突场景

当不同模块需要同一依赖的不同版本时:

module example.com/myapp

go 1.21

require (
    example.com/A v1.0.0  // 需要 C v1.0.0
    example.com/B v1.0.0  // 需要 C v2.0.0
)

解决方案

1. 升级到兼容版本

# 升级所有依赖
go get -u ./...
go mod tidy

2. 使用 replace 指令

module example.com/myapp

go 1.21

require (
    example.com/A v1.0.0
    example.com/B v1.0.0
)

replace example.com/C => example.com/C v2.0.0

3. 使用 exclude 排除版本

module example.com/myapp

go 1.21

require (
    example.com/A v1.0.0
    example.com/B v1.0.0
)

exclude example.com/C v1.0.0

31.4 主版本升级

Go 的主版本规则

在 Go Modules 中,主版本 v2+ 需要使用不同的模块路径:

// v1.x.x
module github.com/example/pkg

// v2.x.x
module github.com/example/pkg/v2

// v3.x.x
module github.com/example/pkg/v3

使用不同主版本

module example.com/myapp

go 1.21

require (
    github.com/example/pkg v1.2.0
    github.com/example/pkg/v2 v2.0.0
    github.com/example/pkg/v3 v3.0.0
)

发布 v2+ 版本

# 创建 v2 分支
git checkout -b v2

# 修改 go.mod
echo 'module github.com/example/pkg/v2' > go.mod

# 提交并打标签
git add go.mod
git commit -m "prepare v2"
git tag v2.0.0
git push origin v2 --tags

31.5 依赖分析工具

go mod graph

查看完整的依赖图:

go mod graph | head -20

go mod why

查看为什么需要某个依赖:

go mod why github.com/golang/protobuf

输出:

# github.com/golang/protobuf
github.com/example/myapp
github.com/gin-gonic/gin
github.com/gin-gonic/gin/binding
github.com/golang/protobuf/proto

go list

列出模块信息:

# 列出所有依赖
go list -m all

# 列出特定模块的所有版本
go list -m -versions github.com/gin-gonic/gin

# 列出模块的详细信息
go list -m -json github.com/gin-gonic/gin

go mod edit

编辑 go.mod 文件:

# 添加依赖
go mod edit -require=github.com/gin-gonic/gin@v1.9.1

# 删除依赖
go mod edit -droprequire=github.com/gin-gonic/gin

# 添加 replace
go mod edit -replace=github.com/example/pkg=../pkg

# 删除 replace
go mod edit -dropreplace=github.com/example/pkg

# 设置 Go 版本
go mod edit -go=1.21

# 格式化 go.mod
go mod edit -fmt

31.6 依赖管理最佳实践

1. 定期更新依赖

# 更新所有依赖到最新版本
go get -u ./...
go mod tidy

# 只更新补丁版本
go get -u=patch ./...
go mod tidy

2. 使用 go mod tidy

每次修改代码后运行:

go mod tidy

3. 提交 go.sum

go.sum 文件提交到版本控制:

git add go.mod go.sum
git commit -m "update dependencies"

4. 使用语义化版本

# 安装特定版本
go get example.com/pkg@v1.2.3

# 安装最新补丁版本
go get example.com/pkg@v1.2

# 安装最新次版本
go get example.com/pkg@v1

5. 使用 vendor 目录(可选)

# 生成 vendor 目录
go mod vendor

# 使用 vendor 构建
go build -mod=vendor

31.7 常见问题解决

问题1:依赖下载失败

# 检查代理配置
go env GOPROXY

# 设置国内代理
go env -w GOPROXY=https://goproxy.cn,direct

# 清理缓存
go clean -modcache

问题2:版本冲突

# 查看依赖图
go mod graph

# 更新特定依赖
go get example.com/pkg@latest

# 整理依赖
go mod tidy

问题3:私有模块访问

# 设置私有模块
go env -w GOPRIVATE=github.com/myorg/*

# 配置 Git 使用 SSH
git config --global url."git@github.com:".insteadOf "https://github.com/"

问题4:校验和错误

# 清理缓存
go clean -modcache

# 重新下载
go mod download

31.8 依赖管理工具对比

工具说明推荐度
Go Modules官方工具⭐⭐⭐⭐⭐
dep旧工具,已废弃
glide旧工具,已废弃
godep旧工具,已废弃

31.9 实战示例

创建一个完整的项目

# 1. 创建项目
mkdir myapp && cd myapp

# 2. 初始化模块
go mod init github.com/example/myapp

# 3. 创建 main.go
cat > main.go << 'EOF'
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

func main() {
    fmt.Println("Gin version:", gin.Version)
    fmt.Println("GORM version:", gorm.Version)
}
EOF

# 4. 下载依赖
go mod tidy

# 5. 运行项目
go run main.go

# 6. 查看依赖
go list -m all

# 7. 查看依赖图
go mod graph

使用本地模块

# 项目结构
# workspace/
# ├── go.work
# ├── app/
# │   ├── go.mod
# │   └── main.go
# └── lib/
#     ├── go.mod
#     └── lib.go

# 创建工作区
go work init ./app ./lib

# go.work 内容
cat > go.work << 'EOF'
go 1.21

use (
    ./app
    ./lib
)
EOF

31.10 小结

本章深入讲解了 Go 语言的依赖管理:

  1. 模块代理:配置 GOPROXY 加速依赖下载
  2. 版本选择:理解最小版本选择算法
  3. 版本冲突:使用 replace 和 exclude 解决冲突
  4. 主版本升级:遵循 Go 的主版本规则
  5. 依赖分析:使用各种工具分析依赖
  6. 最佳实践:定期更新、提交 go.sum、使用语义化版本

掌握依赖管理是构建可靠 Go 项目的基础。在下一章中,我们将学习如何创建和发布自己的模块。