# Go 项目结构与工具链

随着 Go 项目规模的增长，良好的项目结构和工具链配置变得至关重要。合理的目录布局让团队成员快速理解代码组织，完善的工具链保证代码质量和构建效率。本篇介绍 Go 项目的标准目录结构、依赖管理、构建工具和常用开发工具。

## 标准项目目录布局

Go 社区形成了一套广泛认可的项目目录结构约定：

```
myproject/
├── cmd/                  # 主应用程序入口
│   ├── server/
│   │   └── main.go       # HTTP 服务器入口
│   └── cli/
│       └── main.go       # 命令行工具入口
├── internal/             # 私有应用代码（不可被外部导入）
│   ├── handler/          # HTTP 处理器
│   ├── service/          # 业务逻辑
│   ├── repository/       # 数据访问层
│   └── model/            # 数据模型
├── pkg/                  # 公共库代码（可被外部导入）
│   ├── httputil/
│   └── stringutil/
├── api/                  # API 定义（OpenAPI, protobuf 等）
│   └── openapi.yaml
├── configs/              # 配置文件模板
│   └── config.example.yaml
├── scripts/              # 构建和部署脚本
│   └── build.sh
├── deployments/          # 部署配置（Docker, K8s 等）
│   └── Dockerfile
├── go.mod                # 模块定义
├── go.sum                # 依赖校验
├── Makefile              # 构建命令
└── README.md
```

各目录的职责：
- **`cmd/`**：每个子目录对应一个可执行文件，`main.go` 应尽量精简，只做初始化和依赖注入
- **`internal/`**：Go 编译器强制的私有包，外部项目无法导入
- **`pkg/`**：可被其他项目复用的公共库
- **`api/`**：API 协议定义文件
- **`configs/`**：配置模板，不包含敏感信息

```go
// cmd/server/main.go - 入口文件示例
package main

import (
	"fmt"
	"net/http"
)

func main() {
	mux := http.NewServeMux()

	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprintln(w, `{"status": "ok"}`)
	})

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Welcome to My Service")
	})

	addr := ":8080"
	fmt.Printf("Server starting on %s\n", addr)
	if err := http.ListenAndServe(addr, mux); err != nil {
		fmt.Printf("Server failed: %v\n", err)
	}
}
```

## go mod 依赖管理

Go 1.11 引入模块系统（Go Modules），使用 `go.mod` 文件管理项目依赖。

```bash
# 初始化模块
go mod init github.com/myuser/myproject

# 添加依赖
go get github.com/gin-gonic/gin@v1.9.1

# 整理依赖（移除未使用的，添加缺失的）
go mod tidy

# 查看依赖列表
go list -m all

# 查看某个依赖的可用版本
go list -m -versions github.com/gin-gonic/gin
```

`go.mod` 文件示例：

```
module github.com/myuser/myproject

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/go-sql-driver/mysql v1.7.1
)

require (
    github.com/stretchr/testify v1.8.4 // indirect
)
```

`go.sum` 文件记录了每个依赖的哈希值，用于校验依赖完整性，应该提交到版本控制。

## 版本管理与 replace 指令

Go 模块使用语义化版本（SemVer）：`v主版本.次版本.补丁版本`。

```bash
# 升级到新版本
go get github.com/pkg/foo@v1.2.0

# 升级到最新
go get -u github.com/pkg/foo

# 降级版本
go get github.com/pkg/foo@v1.0.0
```

`replace` 指令用于替换依赖的版本或路径：

```
module github.com/myuser/myproject

go 1.21

require github.com/pkg/foo v1.2.0

// 使用本地路径替换（适合开发中的库）
replace github.com/pkg/foo => ../foo-local

// 或替换为其他版本
replace github.com/pkg/foo => github.com/pkg/foo v1.3.0
```

常见用途：
- 使用 fork 版本的依赖
- 本地开发未发布的库
- 修复依赖中的安全漏洞

## Makefile 编写

Makefile 统一项目的构建、测试和部署命令。

```makefile
# Makefile 示例
APP_NAME := myapp
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
BUILD_TIME := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
LDFLAGS := -ldflags "-X main.version=$(VERSION) -X main.buildTime=$(BUILD_TIME)"

# 构建所有入口
.PHONY: build
build:
	@echo "Building $(APP_NAME)..."
	go build $(LDFLAGS) -o bin/$(APP_NAME) ./cmd/server

# 运行测试
.PHONY: test
test:
	go test -v -race -cover ./...

# 代码检查
.PHONY: lint
lint:
	golangci-lint run ./...

# 格式化代码
.PHONY: fmt
fmt:
	go fmt ./...

# 清理构建产物
.PHONY: clean
clean:
	rm -rf bin/
	rm -f coverage.out

# 生成测试覆盖率
.PHONY: coverage
coverage:
	go test -coverprofile=coverage.out ./...
	go tool cover -html=coverage.out -o coverage.html
	@echo "Coverage report: coverage.html"

# 运行应用
.PHONY: run
run:
	go run ./cmd/server

# 下载依赖
.PHONY: deps
deps:
	go mod download
	go mod tidy

# 帮助信息
.PHONY: help
help:
	@echo "Available targets:"
	@echo "  build     - 构建应用"
	@echo "  test      - 运行测试"
	@echo "  lint      - 代码检查"
	@echo "  fmt       - 格式化代码"
	@echo "  clean     - 清理构建产物"
	@echo "  coverage  - 生成测试覆盖率报告"
	@echo "  run       - 运行应用"
	@echo "  deps      - 下载依赖"
```

使用方式：`make build`、`make test`、`make lint` 等。

## 交叉编译

Go 原生支持交叉编译，通过设置 `GOOS` 和 `GOARCH` 环境变量即可为不同平台构建。

```bash
# Linux amd64
GOOS=linux GOARCH=amd64 go build -o bin/myapp-linux-amd64 ./cmd/server

# Linux arm64
GOOS=linux GOARCH=arm64 go build -o bin/myapp-linux-arm64 ./cmd/server

# macOS arm64 (Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o bin/myapp-darwin-arm64 ./cmd/server

# Windows amd64
GOOS=windows GOARCH=amd64 go build -o bin/myapp-windows-amd64.exe ./cmd/server

# 查看所有支持的平台
go tool dist list
```

在 Makefile 中实现多平台构建：

```makefile
PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64

.PHONY: build-all
build-all:
	@for platform in $(PLATFORMS); do \
		os=$${platform%/*}; \
		arch=$${platform#*/}; \
		ext=""; \
		if [ "$$os" = "windows" ]; then ext=".exe"; fi; \
		echo "Building $$os/$$arch..."; \
		GOOS=$$os GOARCH=$$arch go build $(LDFLAGS) \
			-o bin/$(APP_NAME)-$$os-$$arch$$ext ./cmd/server; \
	done
```

## 常用工具

### go fmt

自动格式化代码，确保统一的代码风格。

```bash
# 格式化当前目录
go fmt ./...

# 检查是否有需要格式化的文件（CI 中使用）
gofmt -l .
```

### go vet

静态分析工具，检查代码中的常见错误。

```bash
go vet ./...
```

能检测的问题包括：`Printf` 格式不匹配、不可达代码、struct tag 错误、复制锁等。

### golangci-lint

集成了多种 linter 的综合检查工具。

```bash
# 安装
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# 运行检查
golangci-lint run

# 使用配置文件
golangci-lint run --config .golangci.yml
```

`.golangci.yml` 配置示例：

```yaml
linters:
  enable:
    - errcheck      # 检查未处理的错误
    - gosimple      # 简化代码建议
    - govet         # 静态分析
    - ineffassign   # 无效赋值
    - staticcheck   # 综合检查
    - unused        # 未使用的代码
    - gofmt         # 格式检查

run:
  timeout: 5m
  tests: true
```

## go build vs go install vs go run

三种构建方式的区别：

```bash
# go run：编译并立即运行（不生成文件）
go run ./cmd/server

# go build：编译生成可执行文件
go build -o bin/server ./cmd/server

# go install：编译并安装到 $GOPATH/bin
go install ./cmd/server
```

| 命令 | 生成文件 | 适用场景 |
|------|---------|---------|
| `go run` | 不保留 | 开发调试 |
| `go build` | 当前目录或指定路径 | 项目构建 |
| `go install` | `$GOPATH/bin` | 安装工具 |

## 环境变量配置总结

Go 的关键环境变量：

```bash
# 查看当前配置
go env

# 常用环境变量
echo $GOROOT      # Go 安装目录
echo $GOPATH      # 工作空间目录
echo $GOPROXY     # 模块代理（国内常用）
echo $GOSUMDB     # 校验数据库
echo $GOOS        # 目标操作系统
echo $GOARCH      # 目标架构
```

国内开发推荐设置模块代理加速依赖下载：

```bash
# 设置国内代理
go env -w GOPROXY=https://goproxy.cn,direct

# 设置校验（私有仓库可能需要关闭）
go env -w GOSUMDB=off

# 设置私有仓库（如果需要）
go env -w GOPRIVATE=gitlab.mycompany.com
```

## 完整项目示例

下面展示一个最小但完整的项目结构：

```go
// internal/service/user.go
package service

import "fmt"

type User struct {
	ID   int
	Name string
}

type UserService struct{}

func NewUserService() *UserService {
	return &UserService{}
}

func (s *UserService) GetUser(id int) (*User, error) {
	if id <= 0 {
		return nil, fmt.Errorf("invalid user id: %d", id)
	}
	return &User{ID: id, Name: fmt.Sprintf("User-%d", id)}, nil
}
```

```go
// internal/service/user_test.go
package service

import "testing"

func TestGetUser(t *testing.T) {
	svc := NewUserService()

	tests := []struct {
		name    string
		id      int
		wantErr bool
	}{
		{"正常用户", 1, false},
		{"无效ID", 0, true},
		{"负数ID", -1, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			user, err := svc.GetUser(tt.id)
			if (err != nil) != tt.wantErr {
				t.Errorf("GetUser(%d) error = %v, wantErr %v", tt.id, err, tt.wantErr)
			}
			if !tt.wantErr && user.ID != tt.id {
				t.Errorf("GetUser(%d).ID = %d, want %d", tt.id, user.ID, tt.id)
			}
		})
	}
}
```

## 总结

Go 项目的标准目录结构以 `cmd/` 存放入口、`internal/` 存放私有代码、`pkg/` 存放公共库为核心。`go mod` 管理依赖，`go.sum` 保证完整性。Makefile 统一构建、测试、检查命令。Go 原生支持交叉编译，通过 `GOOS`/`GOARCH` 即可构建多平台二进制。开发工具方面，`go fmt` 格式化代码，`go vet` 静态分析，`golangci-lint` 综合检查。理解 `go build`/`go install`/`go run` 的区别有助于选择合适的构建方式。合理的项目结构和工具链是团队高效协作的基础。

