Week 08: Go 工程化 - Modules、Git 与 CI/CD
掌握 Go Module 依赖管理, 学习 Git 工作流, 配置 GitHub Actions 实现自动化 CI/CD.
1. Go Module 基础
1.1 初始化模块
go mod init github.com/user/project生成 go.mod 文件:
module github.com/user/project
go 1.221.2 添加依赖
go get github.com/gin-gonic/gin@v1.9.1 # 指定版本
go get github.com/gin-gonic/gin@latest # 最新版本
go get github.com/gin-gonic/gin # 最新稳定版1.3 go.mod 文件结构
module github.com/user/project
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
)
require (
// indirect 表示间接依赖
github.com/go-playground/validator/v10 v10.14.0 // indirect
)
replace (
// 本地开发时替换依赖
github.com/old/package => ../local-package
)
exclude (
// 排除有问题的版本
github.com/broken/package v1.0.0
)1.4 go.sum 文件
记录依赖的哈希值, 确保构建可复现:
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQn...
github.com/gin-gonic/gin v1.9.1/go.mod h1:XCj2p...原理: Go 从 GOSUMDB (默认 sum.golang.org) 获取哈希值, 与本地比对. 如果不匹配, 构建失败.
2. 依赖管理命令
| 命令 | 说明 |
|---|---|
go mod init | 初始化模块 |
go mod tidy | 添加缺失依赖, 移除未使用依赖 |
go mod download | 下载依赖到缓存 |
go mod verify | 验证依赖完整性 |
go mod graph | 显示依赖图 |
go mod vendor | 将依赖复制到 vendor 目录 |
go list -m all | 列出所有依赖 |
go list -m -versions github.com/gin-gonic/gin | 列出可用版本 |
3. 语义化版本 (Semantic Versioning)
Go Module 遵循 SemVer:
v1.2.3
│ │ └── PATCH: bug 修复, 向后兼容
│ └──── MINOR: 新功能, 向后兼容
└────── MAJOR: 破坏性变更Major Version Suffix: 当主版本 >= 2 时, 模块路径必须包含版本后缀:
import "github.com/user/project/v2"4. Workspace 模式 (Go 1.18+)
Workspace 用于同时开发多个相关模块:
# 初始化 workspace
go work init ./api ./pkg ./cmd生成的 go.work 文件:
go 1.22
use (
./api
./pkg
./cmd
)
replace example.com/common => ../common适用场景:
- 本地开发多个模块
- 临时替换依赖进行调试
- Monorepo 开发
5. 构建时注入版本信息
使用 -ldflags 在编译时注入变量:
// main.go
var (
version string
commit string
buildTime string
)
func main() {
fmt.Printf("Version: %s, Commit: %s, Built: %s\n",
version, commit, buildTime)
}go build -ldflags="\
-X main.version=1.0.0 \
-X main.commit=$(git rev-parse --short HEAD) \
-X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-o app6. Go 项目结构最佳实践
6.1 标准项目布局
project/
├── cmd/ # 主应用入口
│ ├── myapp/
│ │ └── main.go # myapp 的 main 函数
│ └── cli/
│ └── main.go # CLI 工具的 main 函数
├── internal/ # 私有代码, 不可被外部导入
│ ├── app/ # 应用初始化, DI 容器
│ ├── config/ # 配置加载
│ ├── handler/ # HTTP/gRPC 处理器
│ ├── service/ # 业务逻辑层
│ ├── repository/ # 数据访问层
│ └── model/ # 领域模型
├── pkg/ # 公共库, 可被外部导入
│ ├── logger/ # 日志封装
│ └── httpclient/ # HTTP 客户端封装
├── api/ # API 定义 (OpenAPI, protobuf)
│ └── openapi.yaml
├── web/ # Web 静态资源
├── scripts/ # 构建/部署脚本
├── deployments/ # 部署配置
│ ├── docker/
│ └── kubernetes/
├── docs/ # 文档
├── test/ # 集成测试/E2E 测试
├── .github/
│ └── workflows/ # GitHub Actions
├── .golangci.yml # Linter 配置
├── Makefile # 常用命令
├── Dockerfile
├── go.mod
├── go.sum
└── README.md6.2 internal 的作用
internal/ 目录下的包只能被同一模块内的代码导入:
github.com/user/project/
├── internal/
│ └── secret/ # 只能被 github.com/user/project 内部导入
└── pkg/
└── public/ # 可以被任何模块导入6.3 Makefile 示例
.PHONY: all build test lint clean run
all: lint test build
build:
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/myapp ./cmd/myapp
test:
go test -v -race -cover ./...
lint:
golangci-lint run
run:
go run ./cmd/myapp
clean:
rm -rf bin/
# 生成代码 (mock, protobuf 等)
generate:
go generate ./...
# 依赖更新
deps:
go mod tidy
go mod verify
# Docker 构建
docker:
docker build -t myapp:latest .6.4 分层架构
┌─────────────────┐
│ Handler │ HTTP/gRPC 入口
└────────┬────────┘
│ 调用
┌────────▼────────┐
│ Service │ 业务逻辑
└────────┬────────┘
│ 调用
┌────────▼────────┐
│ Repository │ 数据访问
└────────┬────────┘
│ 调用
┌────────▼────────┐
│ Database │ 持久化
└─────────────────┘依赖方向: Handler → Service → Repository (单向依赖)
7. 代码审查 (Code Review)
7.1 Pull Request 规范
标题: [类型] 简短描述
[feat] 添加用户认证功能
[fix] 修复订单金额计算错误
[refactor] 重构数据库连接池
[docs] 更新 API 文档描述模板:
## 变更内容
- 添加 JWT 认证中间件
- 添加登录/注册 API
## 测试
- [ ] 单元测试通过
- [ ] 集成测试通过
- [ ] 本地手动测试
## 关联 Issue
Closes #1237.2 Code Review Checklist
功能正确性:
- 代码是否实现了需求?
- 边界条件是否处理?
- 错误场景是否覆盖?
代码质量:
- 是否遵循 Go 惯用法?
- 命名是否清晰?
- 是否有重复代码?
- 函数是否过长 (建议 < 50 行)?
并发安全:
- 共享状态是否正确同步?
- 是否存在数据竞争?
- Context 是否正确传递?
测试:
- 是否有充分的测试?
- 测试是否覆盖边界情况?
性能:
- 是否有不必要的内存分配?
- 是否有 N+1 查询?
7.3 团队协作约定
// 1. 错误处理: 返回 error, 不要 panic
func GetUser(id int64) (*User, error) {
// ...
}
// 2. Context 作为第一个参数
func (s *Service) Create(ctx context.Context, user *User) error {
// ...
}
// 3. 使用选项模式处理可选参数
type ServerOption func(*Server)
func WithPort(port int) ServerOption {
return func(s *Server) { s.port = port }
}
// 4. 接口定义在使用方, 而非实现方
// 定义小接口, 只包含被使用的方法
type UserGetter interface {
GetByID(ctx context.Context, id int64) (*User, error)
}8. Git 基础
8.1 常用命令
git init # 初始化仓库
git clone URL # 克隆仓库
git status # 查看状态
git add . # 暂存所有变更
git commit -m "message" # 提交
git push origin main # 推送
git pull # 拉取更新
git log --oneline -10 # 查看最近 10 条提交8.2 分支操作
git branch feature/login # 创建分支
git checkout feature/login # 切换分支
git checkout -b feature/login # 创建并切换
git merge feature/login # 合并分支
git branch -d feature/login # 删除分支8.3 远程操作
git remote -v # 查看远程仓库
git fetch # 获取远程更新 (不合并)
git pull --rebase # 拉取并变基
git push -u origin feature # 推送并设置跟踪9. Git 工作流
9.1 Git Flow
- main: 生产环境代码
- develop: 开发分支
- feature/xxx: 功能分支
- release/xxx: 发布分支
- hotfix/xxx: 热修复分支
9.2 GitHub Flow (简化版)
- 从
main创建功能分支:feature/add-login - 开发并提交
- 创建 Pull Request
- 代码审查 (Code Review)
- 合并到
main - 删除功能分支
9.3 Commit 规范 (Conventional Commits)
<type>(<scope>): <subject>
<body>
<footer>常用 type:
feat: 新功能fix: Bug 修复docs: 文档更新style: 格式调整 (不影响逻辑)refactor: 重构test: 测试chore: 构建/工具变更
示例:
feat(auth): add JWT authentication
- Add JWT middleware
- Add login endpoint
- Add token refresh endpoint
Closes #12310. GitHub Actions (CI/CD)
10.1 基本结构
在 .github/workflows/ 下创建 YAML 文件:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- run: go test -v ./...10.2 完整 CI 流程
name: CI
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: latest
test:
runs-on: ubuntu-latest
needs: lint # 等待 lint 完成
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- run: go test -v -race -coverprofile=coverage.out ./...
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
file: ./coverage.out
build:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- run: CGO_ENABLED=0 go build -ldflags="-s -w" -o app
- uses: actions/upload-artifact@v4
with:
name: app
path: app10.3 矩阵构建
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: ['1.21', '1.22']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- run: go test ./...11. 代码质量工具
11.1 golangci-lint
最流行的 Go linter 聚合器:
# 安装
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# 运行
golangci-lint run11.2 配置 .golangci.yml
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gofmt
- goimports
- misspell
linters-settings:
errcheck:
check-blank: true
issues:
exclude-rules:
- path: _test\.go
linters:
- errcheck11.3 go vet
内置的静态分析工具:
go vet ./...11.4 gofmt & goimports
gofmt -s -w . # 格式化并简化
goimports -w . # 格式化 + 自动管理 import12. Docker 容器化
project/
├── cmd/ # 主应用入口
│ ├── myapp/
│ │ └── main.go # myapp 的 main 函数
│ └── cli/
│ └── main.go # CLI 工具的 main 函数
├── internal/ # 私有代码, 不可被外部导入
│ ├── app/ # 应用初始化, DI 容器
│ ├── config/ # 配置加载
│ ├── handler/ # HTTP/gRPC 处理器
│ ├── service/ # 业务逻辑层
│ ├── repository/ # 数据访问层
│ └── model/ # 领域模型
├── pkg/ # 公共库, 可被外部导入
│ ├── logger/ # 日志封装
│ └── httpclient/ # HTTP 客户端封装
├── api/ # API 定义 (OpenAPI, protobuf)
│ └── openapi.yaml
├── web/ # Web 静态资源
├── scripts/ # 构建/部署脚本
├── deployments/ # 部署配置
│ ├── docker/
│ └── kubernetes/
├── docs/ # 文档
├── test/ # 集成测试/E2E 测试
├── .github/
│ └── workflows/ # GitHub Actions
├── .golangci.yml # Linter 配置
├── Makefile # 常用命令
├── Dockerfile
├── go.mod
├── go.sum
└── README.md4.2 internal 的作用
internal/ 目录下的包只能被同一模块内的代码导入:
github.com/user/project/
├── internal/
│ └── secret/ # 只能被 github.com/user/project 内部导入
└── pkg/
└── public/ # 可以被任何模块导入4.3 Makefile 示例
.PHONY: all build test lint clean run
all: lint test build
build:
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/myapp ./cmd/myapp
test:
go test -v -race -cover ./...
lint:
golangci-lint run
run:
go run ./cmd/myapp
clean:
rm -rf bin/
# 生成代码 (mock, protobuf 等)
generate:
go generate ./...
# 依赖更新
deps:
go mod tidy
go mod verify
# Docker 构建
docker:
docker build -t myapp:latest .4.4 分层架构
┌─────────────────┐
│ Handler │ HTTP/gRPC 入口
└────────┬────────┘
│ 调用
┌────────▼────────┐
│ Service │ 业务逻辑
└────────┬────────┘
│ 调用
┌────────▼────────┐
│ Repository │ 数据访问
└────────┬────────┘
│ 调用
┌────────▼────────┐
│ Database │ 持久化
└─────────────────┘依赖方向: Handler → Service → Repository (单向依赖)
13. 练习
13.1 创建 Go Module
创建一个新项目, 添加依赖 github.com/fatih/color, 编写带颜色输出的程序.
13.2 配置 GitHub Actions
为一个 Go 项目配置完整的 CI: lint, test, build.
13.3 Docker 化
为一个简单的 HTTP 服务编写多阶段 Dockerfile.
14. 思考题
- 为什么 go.sum 需要提交到 Git?
- 直接依赖和间接依赖有什么区别?
- 为什么 Dockerfile 使用多阶段构建?
- golangci-lint 和 go vet 有什么区别?
- CGO_ENABLED=0 的作用是什么?
15. 本周小结
- Go Module: go.mod, go.sum, 语义化版本.
- 依赖管理: go get, go mod tidy, go mod vendor.
- Git 工作流: GitHub Flow, Conventional Commits.
- GitHub Actions: CI/CD 自动化, 矩阵构建.
- 代码质量: golangci-lint, go vet, gofmt.
- 容器化: 多阶段 Dockerfile, distroless.
工程化是将代码从"能跑"变成"可靠"的关键. CI/CD 确保每次变更都经过验证.