Files
Qiniu/technical/mac-dev-env-setup/go-module-proxy.md
T

191 lines
6.0 KiB
Markdown
Raw Normal View History

2026-07-01 23:09:21 +08:00
---
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