Skip to content

第10章 包管理与 Go Module

📚 学习目标:理解包是什么、如何组织代码、用 Go Module 管理依赖。


🎯 章节导读

如果说前 9 章学的是"如何写一段 Go 代码",那这一章学的就是如何组织一个 Go 项目

生活类比

  • package(包):相当于一个文件夹里的"工具箱",里面放着相关的工具
  • import(导入):相当于"从超市买工具"
  • go.mod:相当于"购物清单",记录用了哪些工具、什么版本

📦 1. 包(package)的概念

1.1 一个文件 = 一个 package?

不对!一个文件夹下的所有 .go 文件必须属于同一个 package

mathx/
  add.go      // package mathx
  sub.go      // package mathx
  multi.go    // package mathx

如果同目录下文件 package 不同,编译会报错。


1.2 main 包

只有 package main 且包含 func main() 的程序才能编译成可执行文件。

go
package main

import "fmt"

func main() {
    fmt.Println("Hello")
}

其他名字的 package 都是,被别人引用。


1.3 公开 vs 私有:首字母大小写

Go 没有 public/private 关键字,靠标识符首字母大小写控制:

首字母可见性
大写(Add、Name)包外可见(导出)
小写(add、name)仅包内可见
go
package mathx

func Add(a, b int) int { return a + b }   // 别的包能调用
func sub(a, b int) int { return a - b }   // 只能本包用

📥 2. import:导入包

2.1 三种用法

go
// 单个导入
import "fmt"

// 多个导入(推荐)
import (
    "fmt"
    "strings"
    "myproject/utils"
)

// 别名导入
import (
    f "fmt"            // 用 f 代替 fmt
    . "math"           // 后续可省略 math. 前缀(不推荐)
    _ "image/png"      // 仅执行 init(),不引用
)

2.2 标准库 vs 第三方 vs 本项目

go
import (
    // 1. 标准库(直接写名字)
    "fmt"
    "net/http"

    // 2. 第三方库(完整路径)
    "github.com/gin-gonic/gin"

    // 3. 本项目其他包(用 module 名 + 子路径)
    "myproject/internal/service"
)

通常按这三类分组写,每组之间用空行分隔。gofmt 会自动排序。


2.3 init 函数

每个文件可以定义 func init(),在被导入时自动执行一次

go
package config

var DB *sql.DB

func init() {
    DB, _ = sql.Open("mysql", "...")
}

执行顺序:被依赖的包 → 当前包,按导入路径深度优先。


🚀 3. Go Module:依赖管理革命

3.1 GOPATH 时代的痛

Go 1.10 之前,所有项目必须放在 $GOPATH/src/ 下,依赖统一放 $GOPATH/pkg/不支持版本管理

如果项目 A 用 lib v1,项目 B 用 lib v2,怎么办?只能…痛苦地共存或冲突。

3.2 Module 时代

Go 1.11 引入 Go Module,每个项目独立管理依赖,类似:

  • Java 的 Maven
  • Node.js 的 npm/package.json
  • Rust 的 Cargo
  • Python 的 requirements.txt(更接近 Cargo)

3.3 创建一个 Module

bash
mkdir myapp && cd myapp
go mod init github.com/yourname/myapp

会生成 go.mod

module github.com/yourname/myapp

go 1.21

module 名 = 你这个项目对外的"包路径",通常用代码仓库地址。


3.4 添加依赖

bash
go get github.com/gin-gonic/gin
go get github.com/gin-gonic/gin@v1.9.1   # 指定版本
go get github.com/gin-gonic/gin@latest   # 最新

go.mod 会自动更新:

require (
    github.com/gin-gonic/gin v1.9.1
)

同时生成 go.sum,记录依赖的哈希值,防止依赖被恶意篡改。


3.5 移除未用依赖

bash
go mod tidy

会自动:

  • 添加代码里 import 但 go.mod 没有的依赖
  • 删除 go.mod 里有但代码没用的依赖

提交代码前记得跑一次 go mod tidy,保持干净。


📋 4. go.mod 文件解析

module github.com/yourname/myapp

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/spf13/viper v1.18.2
)

require (
    // 间接依赖(被 require 的依赖再依赖的库)
    github.com/spf13/cobra v1.8.0 // indirect
)

replace (
    // 用本地路径或 fork 的版本替换
    github.com/old/lib => github.com/new/lib v1.0.0
    github.com/somelib => ../local-fork/somelib
)

exclude (
    // 排除某个版本
    github.com/bad/lib v1.0.0
)

4.1 go.sum 文件

github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3v0pYJDk=

每个依赖记录两行:源码哈希 + go.mod 哈希。永远要把 go.sum 提交到 Git


📂 5. 标准项目布局

社区惯例(golang-standards/project-layout):

myapp/
├── cmd/                    # 主程序入口
│   └── server/
│       └── main.go
├── internal/               # 内部代码(外部不能 import)
│   ├── service/
│   ├── repository/
│   └── handler/
├── pkg/                    # 可被外部 import 的库
│   └── utils/
├── api/                    # API 定义(OpenAPI、proto)
├── configs/                # 配置文件
├── scripts/                # 脚本
├── test/                   # 测试数据
├── go.mod
├── go.sum
├── Makefile
└── README.md

internal/ 是 Go 编译器特殊处理的目录:放在里面的包只能被父目录及其子目录的代码 import,外部项目无法引用。这是 Go 内置的"私有包"机制。


🛠 6. 常用 go 命令

命令作用
go mod init <name>初始化 module
go mod tidy整理依赖
go mod download下载所有依赖到本地缓存
go mod vendor把依赖拷到 vendor/ 目录
go mod graph显示依赖关系图
go mod why <pkg>解释为什么需要这个依赖
go list -m all列出所有依赖
go list -m -u all列出可升级的依赖
go get -u升级所有依赖
go get pkg@version切换到指定版本

🌐 7. 国内镜像加速

国内访问 proxy.golang.org 慢,配置代理:

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

# 私有仓库不走代理
go env -w GOPRIVATE=*.mycompany.com,github.com/myorg/*

🧠 8. 语义化版本(SemVer)

Go Module 严格要求 v主版本.次版本.补丁

升级含义示例
主版本不兼容变更v1.x → v2.0.0
次版本兼容的新功能v1.0 → v1.1
补丁兼容的 bug 修复v1.0.0 → v1.0.1

大版本号 ≥ 2 时,import 路径需带版本号

go
import "github.com/some/lib/v2"   // v2.x 必须这样

📚 本章小结

概念一句话总结
package同一目录下文件的逻辑分组
import引入其他包
大写/小写决定标识符是否对外暴露
go.mod依赖清单
go.sum依赖哈希校验
go mod tidy整理依赖
internal/私有包目录

📁 本章配套

进入第11章 →