191 lines
6.0 KiB
Markdown
191 lines
6.0 KiB
Markdown
---
|
||
tags: [go, module, proxy, dev-env]
|
||
create time: 2026-07-01 00:00
|
||
---
|
||
|
||
# Go Module 代理与依赖管理
|
||
|
||
## 概述
|
||
|
||
Go Modules 是 Go 自 v1.11 引入的官方依赖管理方案,GOPROXY 环境变量决定了模块下载的来源。国内用户通过配置代理加速拉取,本文深入 Module 体系与代理机制。
|
||
|
||
## 核心概念
|
||
|
||
### GOPROXY 的工作原理
|
||
|
||
```
|
||
go get ./...
|
||
│
|
||
▼
|
||
GOPROXY 列表依次查询 → 命中第一个返回
|
||
│ (类似 DNS 回退链)
|
||
▼
|
||
缓存到 ~/go/pkg/mod/cache/
|
||
│
|
||
▼
|
||
写入 go.sum(校验哈希)
|
||
```
|
||
|
||
```bash
|
||
# 默认值(中国大陆不可达)
|
||
go env GOFLAGS # 应设为空或不设
|
||
|
||
# 推荐:阿里 + goproxy.io 备用
|
||
go env -w GOPROXY=https://goproxy.cn,direct
|
||
|
||
# 也可以指定多个(逗号分隔,从左到右优先级递减)
|
||
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,https://goproxy.io,direct
|
||
```
|
||
|
||
为什么需要 `direct` 作为兜底?当模块是私有仓库、不在任何公开代理上时,`direct` 告诉 Go 直接通过 VCS(git/svn/hg)从源码仓库拉取。
|
||
|
||
### GOSUMDB 校验链
|
||
|
||
每个 `go.mod` 旁边都会有一个 `go.sum`,它记录了所有直接和间接依赖的 **模块版本 + 哈希**:
|
||
|
||
```bash
|
||
# 当前 GOSUMDB 设置
|
||
go env GOSUMDB # sum.golang.org(Google 托管的全球信任根)
|
||
|
||
# 离线模式(跳过哈希校验,开发阶段方便但不建议用于 CI)
|
||
go env -w GONOSUMCHECK=* # 跳过校验
|
||
go env -w GONOSUMDB=* # 不向 sumdb 请求
|
||
go env -w GOSUM=off # 完全关闭 sum 检查
|
||
```
|
||
|
||
> [!warning] GONOSUMCHECK vs GONOSUMDB vs GOSUM=off
|
||
> | 变量 | 效果 | 适用场景 |
|
||
> |------|------|---------|
|
||
> | `GONOSUMCHECK=*` | 跳过对 `go.sum` 比对,但仍查询 GOSUMDB | 不推荐使用 |
|
||
> | `GONOSUMDB=*` | 不向 sum.golang.org 查询,但仍做 local checksum | 自建镜像时使用 |
|
||
> | `GOSUM=off` | 完全禁用 hash 校验 | 仅限纯本地实验项目 |
|
||
|
||
生产环境务必保留 `GOSUMDB=sum.golang.org`,否则无法防御依赖供应链攻击(比如有人往公共 repo 推送恶意版本)。
|
||
|
||
### Go Workspace(Go 1.18+)
|
||
|
||
当你在本地同时修改多个内部模块时,Go Workspace 可以绕过 GOPROXY,用本地路径解析替代网络下载:
|
||
|
||
```bash
|
||
# 初始化 workspace
|
||
go work init ./cmd/server ./internal/lib
|
||
|
||
# 添加已有模块
|
||
go work use ./pkg/utils
|
||
|
||
# 生成的 go.work 文件
|
||
cat go.work
|
||
# go 1.22
|
||
#
|
||
# use (
|
||
# ./cmd/server
|
||
# ./internal/lib
|
||
# ./pkg/utils
|
||
# )
|
||
```
|
||
|
||
Workspace 模式下,`go build` / `go test` 会优先使用 `use` 列表中本地路径对应的模块版本,而非 `GOPROXY` 下载的缓存版本。这在做 monorepo 或多模块协同开发时非常有用。
|
||
|
||
### vendor 模式:离线构建
|
||
|
||
```bash
|
||
# 将所有依赖下载到本地 vendor/ 目录
|
||
go mod vendor
|
||
|
||
# 用 vendor 目录构建(完全离线)
|
||
go build -mod=vendor ./...
|
||
```
|
||
|
||
`vendor/` 会被纳入代码仓库并提交,这样新成员 clone 后即使没有网络也能编译。对比:
|
||
|
||
| 方式 | 是否需要网络 | 是否提交 | 适用场景 |
|
||
|------|-------------|---------|---------|
|
||
| **Module cache** | 首次需要 | 否(本地磁盘) | 日常开发 |
|
||
| **vendor/** | 不需要 | 是 | CI/CD、内网隔离 |
|
||
| **Go Workspace** | 不需要 | 是(go.work) | 多模块本地协同 |
|
||
|
||
## 代码示例
|
||
|
||
### 批量检查过期依赖
|
||
|
||
```bash
|
||
# 使用 depaware 第三方工具可视化依赖关系并发现重复模块
|
||
go install github.com/tailscale/depaware@latest
|
||
depaware --print ./...
|
||
```
|
||
|
||
### 清理未使用的依赖
|
||
|
||
```bash
|
||
# 移除 go.mod 中不再需要的 require 条目
|
||
go mod tidy
|
||
|
||
# 清理本地 module cache 中无人引用的旧版本
|
||
go clean -modcache # ⚠️ 谨慎使用:会删掉整个 ~/go/pkg/mod/cache
|
||
```
|
||
|
||
`go mod tidy` 的核心逻辑:扫描 `*.go` 文件的 import 路径 → 去 `go.mod` 中查找对应 require → 未被导入且无 build tag 使用的条目会被自动删除,新增的导入会自动添加。
|
||
|
||
## 常见陷阱与最佳实践
|
||
|
||
### 1. GOPROXY 配错导致 "bad response"
|
||
|
||
当代理地址不可达时,Go 会报 403/404/502。排查步骤:
|
||
|
||
```bash
|
||
# 第一步:手动 curl 测试代理健康度
|
||
curl -i https://goproxy.cn/github.com/aws/aws-sdk-go/@v/v1.45.0.zipinfo
|
||
|
||
# 期望:HTTP 200 + 响应体包含 {"Path":"github.com/aws/aws-sdk-go", ...}
|
||
# 失败:检查代理 URL 是否正确、是否有防火墙拦截
|
||
```
|
||
|
||
阿里云的 `https://mirrors.aliyun.com/goproxy/` 在某些地区更稳定,可切换测试。
|
||
|
||
### 2. go.sum 丢失或不同步
|
||
|
||
`go.sum` 不应手动编辑。如果不小心修改了导致冲突:
|
||
|
||
```bash
|
||
# 重建 go.sum(基于 go.mod + 当前可用 GOPROXY 重新拉取)
|
||
rm go.sum
|
||
go mod download
|
||
go mod verify # 确认每个依赖的哈希都匹配
|
||
```
|
||
|
||
### 3. 私有仓库需要结合 GOPRIVATE
|
||
|
||
```bash
|
||
# 跳过代理和 sumdb 校验,走 VCS 直接拉取
|
||
go env -w GOPRIVATE=git.internal.company.com/*
|
||
```
|
||
|
||
> [!tip] GOPRIVATE 与 GOPROXY 的关系
|
||
> `GOPRIVATE` 模块不走 GOPROXY,也触发 GONOSUMCHECK/GONOSUMDB。如果你只想屏蔽特定域名而不全部关闭校验,可以:
|
||
> ```bash
|
||
> go env -w GOPRIVATE=git.internal.company.com
|
||
> # GOPROXY 仍然走 goproxy.cn,只有匹配的域名例外
|
||
> ```
|
||
|
||
### 4. Module 语义化版本陷阱
|
||
|
||
Go Modules 使用 [Semantic Import Versioning](https://github.com/golang/go/wiki/Modules#semantic-import-versioning):如果一个模块 v2+ 发生了 breaking change,它的 import path 应带上 `/v2` 后缀:
|
||
|
||
```
|
||
# v1.x → import "example.com/foo"
|
||
# v2.x → import "example.com/foo/v2"
|
||
```
|
||
|
||
很多开源库(如 `gin-gonic/gin`)忽略了这一约定,直接用 `v2.x` 但没有在 import path 上加 `/v2`。这时需要在 `go.mod` 里显式声明:
|
||
|
||
```go
|
||
require github.com/example/broken-module v2.3.4 // indirect
|
||
// 或用 replace 纠正
|
||
replace github.com/example/broken-module v2.3.4 => github.com/example/broken-module v2.3.4
|
||
```
|
||
|
||
## 延伸阅读
|
||
|
||
- [[mac-dev-env-setup/homebrew-intro]] — 通过 Homebrew 安装 Go
|
||
- [[mac-dev-env-setup/git-ssh-config]] — 私有仓库需先配置 SSH Key
|