Skip to content

14|包与模块

Go 代码组织在包(package)里,依赖由 Go Modules 管理。Go Modules 是当前唯一的依赖管理方式,新项目应该直接用 go mod init 初始化。包管理解决两个问题:一是代码复用,把通用逻辑拆成独立的包;二是依赖版本控制,确保不同环境编译时使用相同的依赖版本。理解包的可见性规则、internal/ 目录的特殊含义和模块代理的配置,能避免在多人协作和跨环境部署时遇到依赖问题。

一、Go Modules

Go 1.11 引入的模块系统是当前的依赖管理标准。项目根目录下执行 go mod init 初始化:

bash
mkdir go-gateway
cd go-gateway
go mod init example.com/go-gateway

生成 go.mod

go
module example.com/go-gateway

go 1.23

module 是模块路径,通常写成代码仓库地址。go 行表示模块声明的 Go 语言版本,不影响编译器行为,主要用于依赖解析时判断兼容性。

添加依赖:

bash
go get github.com/gin-gonic/gin
go mod tidy

go mod tidy 根据源码里的 import 自动调整依赖,删除未使用的,补齐缺失的。提交代码前跑一次,能保持 go.modgo.sum 干净。

常用命令:

命令作用
go mod init初始化模块
go get添加或升级依赖
go mod tidy清理未使用依赖,补齐缺失依赖
go mod download下载依赖到本地缓存
go list -m all查看依赖列表
go mod vendor把依赖复制到 vendor/ 目录

二、模块代理和私有仓库

国内网络拉 Go 模块时经常慢或超时,配置 GOPROXY

bash
go env -w GOPROXY=https://goproxy.cn,direct
go env GOPROXY

direct 表示代理找不到时直接访问原始仓库。公司内部私有模块还要配置 GOPRIVATE,避免私有模块路径被发到公共代理和校验服务:

bash
go env -w GOPRIVATE=git.example.com/*
go env GOPRIVATE

排查模块下载问题时先看 go env。代理、私有仓库、认证、DNS 都可能影响 go mod tidy

三、包导入规则

同一个目录下的 .go 文件必须属于同一个包。包名通常和目录名一致,但也可以不同。

导入包时,使用模块路径 + 子目录路径:

go
import (
	"example.com/go-gateway/internal/config"
	"example.com/go-gateway/internal/router"
)

标准库包直接用短名:fmtosnet/http

internal/ 目录有特殊规则:里面的包只能被当前模块引用,其他模块无法导入。这个机制用来放项目的内部逻辑,防止外部代码依赖不稳定的内部 API。

go
// 可以:go-gateway 模块内部引用
import "example.com/go-gateway/internal/cache"

// 不可以:其他模块尝试引用时编译报错
import "github.com/other-project/go-gateway/internal/cache"

四、项目结构约定

目录用途
main.go / cmd/程序入口,多命令时放 cmd/
internal/只允许当前模块内部引用的包
pkg/对外复用的包,普通项目不一定需要
api/API 定义、protobuf、OpenAPI 规范
configs/配置文件示例
test/测试辅助工具和数据

小工具不需要一开始就拆得很复杂。单个 main.go 能写清楚时先保持简单;逻辑变多后再拆到 internal/

go-gateway 的目录大致如下:

text
go-gateway/
├── cmd/
│   └── gateway/
│       └── main.go          // 程序入口
├── internal/
│   ├── proxy/               // 代理转发逻辑
│   ├── router/              // 路由匹配
│   ├── middleware/          // 中间件
│   ├── config/              // 配置读取
│   └── backend/             // 后端池管理
├── pkg/
│   └── loadbalancer/        // 负载均衡算法(可复用)
├── go.mod
└── go.sum

五、包可见性

包内标识符的首字母决定可见性:

  • 首字母大写:导出,其他包可以访问
  • 首字母小写:未导出,只在当前包内可见
go
package config

var DefaultTimeout = 30     // 导出,其他包可通过 config.DefaultTimeout 访问
var defaultLogLevel = "info" // 未导出,只在 config 包内可见

func Load(path string) {}    // 导出
func validate(path string) {} // 未导出

这个规则适用于变量、常量、函数、类型、方法、结构体字段。没有 public/private 关键字,首字母大小写就是访问控制。

六、常见错误

循环导入

包 A 导入包 B,包 B 又导入包 A,形成循环依赖。Go 编译器不允许循环导入,遇到时需要重构代码,把公共部分抽成第三个包。

包名冲突

两个不同路径的包有相同名字时,导入语句里用别名区分:

go
import (
	myfmt "example.com/go-gateway/pkg/fmt"
	"fmt"
)

忽略 go mod tidy

提交代码前不跑 go mod tidy,可能导致 go.mod 里有多余的依赖,或者某些 import 对应的依赖缺失。CI 环境里 go build 会因此失败。

修改 go.mod 手动删依赖

不要手动编辑 go.mod 删除依赖。go mod tidy 会根据实际 import 自动管理。手动删除后,如果代码里还有 import,编译会报错。

internal 被外部引用

go
import "github.com/other-project/go-gateway/internal/config" // 编译错误

internal/ 的包只能被同一个模块引用。如果想让其他项目使用,需要把包移到 pkg/ 或根目录下。