390 lines
9.2 KiB
Markdown
390 lines
9.2 KiB
Markdown
|
# Go 项目中的 cmd 目录详解
|
|||
|
|
|||
|
## 🎯 cmd 目录的作用
|
|||
|
|
|||
|
`cmd` 目录是 Go 项目的标准目录布局,专门用来存放**可执行程序的入口点**。每个子目录代表一个不同的应用程序。
|
|||
|
|
|||
|
## 📁 目录结构示例
|
|||
|
|
|||
|
```
|
|||
|
user-service/
|
|||
|
├── cmd/ # 应用程序入口目录
|
|||
|
│ ├── server/ # HTTP/gRPC 服务器
|
|||
|
│ │ └── main.go # 服务器启动入口
|
|||
|
│ ├── cli/ # 命令行工具
|
|||
|
│ │ └── main.go # CLI 工具入口
|
|||
|
│ ├── worker/ # 后台任务处理器
|
|||
|
│ │ └── main.go # Worker 入口
|
|||
|
│ └── migrator/ # 数据库迁移工具
|
|||
|
│ └── main.go # 迁移工具入口
|
|||
|
├── internal/ # 内部业务逻辑
|
|||
|
├── api/ # API 定义
|
|||
|
└── pkg/ # 可复用的包
|
|||
|
```
|
|||
|
|
|||
|
## 🔧 具体示例
|
|||
|
|
|||
|
### 1. 服务器入口 (cmd/server/main.go)
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import (
|
|||
|
"context"
|
|||
|
"flag"
|
|||
|
"fmt"
|
|||
|
"log"
|
|||
|
"net"
|
|||
|
"os"
|
|||
|
"os/signal"
|
|||
|
"syscall"
|
|||
|
|
|||
|
"google.golang.org/grpc"
|
|||
|
"user-service/internal/config"
|
|||
|
"user-service/internal/server"
|
|||
|
"user-service/internal/service"
|
|||
|
)
|
|||
|
|
|||
|
func main() {
|
|||
|
// 1. 解析命令行参数
|
|||
|
var (
|
|||
|
configFile = flag.String("config", "configs/config.yaml", "配置文件路径")
|
|||
|
port = flag.Int("port", 8080, "服务端口")
|
|||
|
)
|
|||
|
flag.Parse()
|
|||
|
|
|||
|
// 2. 加载配置
|
|||
|
cfg, err := config.Load(*configFile)
|
|||
|
if err != nil {
|
|||
|
log.Fatal("加载配置失败:", err)
|
|||
|
}
|
|||
|
|
|||
|
// 3. 初始化服务
|
|||
|
userService := service.NewUserService(cfg)
|
|||
|
|
|||
|
// 4. 创建 gRPC 服务器
|
|||
|
grpcServer := grpc.NewServer()
|
|||
|
server.RegisterUserServer(grpcServer, userService)
|
|||
|
|
|||
|
// 5. 启动服务器
|
|||
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
|||
|
if err != nil {
|
|||
|
log.Fatal("监听端口失败:", err)
|
|||
|
}
|
|||
|
|
|||
|
// 6. 优雅关闭
|
|||
|
go func() {
|
|||
|
log.Printf("用户服务启动在端口 %d", *port)
|
|||
|
if err := grpcServer.Serve(lis); err != nil {
|
|||
|
log.Fatal("服务启动失败:", err)
|
|||
|
}
|
|||
|
}()
|
|||
|
|
|||
|
// 7. 等待中断信号
|
|||
|
quit := make(chan os.Signal, 1)
|
|||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|||
|
<-quit
|
|||
|
|
|||
|
log.Println("正在关闭服务...")
|
|||
|
grpcServer.GracefulStop()
|
|||
|
log.Println("服务已关闭")
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 2. CLI 工具入口 (cmd/cli/main.go)
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import (
|
|||
|
"flag"
|
|||
|
"fmt"
|
|||
|
"os"
|
|||
|
|
|||
|
"user-service/internal/config"
|
|||
|
"user-service/internal/service"
|
|||
|
)
|
|||
|
|
|||
|
func main() {
|
|||
|
var (
|
|||
|
action = flag.String("action", "", "执行的动作: create-user, list-users, reset-password")
|
|||
|
userID = flag.Int64("user-id", 0, "用户ID")
|
|||
|
username = flag.String("username", "", "用户名")
|
|||
|
)
|
|||
|
flag.Parse()
|
|||
|
|
|||
|
cfg, err := config.Load("configs/config.yaml")
|
|||
|
if err != nil {
|
|||
|
fmt.Printf("加载配置失败: %v\n", err)
|
|||
|
os.Exit(1)
|
|||
|
}
|
|||
|
|
|||
|
userService := service.NewUserService(cfg)
|
|||
|
|
|||
|
switch *action {
|
|||
|
case "create-user":
|
|||
|
if *username == "" {
|
|||
|
fmt.Println("用户名不能为空")
|
|||
|
os.Exit(1)
|
|||
|
}
|
|||
|
user, err := userService.CreateUser(*username)
|
|||
|
if err != nil {
|
|||
|
fmt.Printf("创建用户失败: %v\n", err)
|
|||
|
os.Exit(1)
|
|||
|
}
|
|||
|
fmt.Printf("用户创建成功: ID=%d, Username=%s\n", user.ID, user.Username)
|
|||
|
|
|||
|
case "list-users":
|
|||
|
users, err := userService.ListUsers()
|
|||
|
if err != nil {
|
|||
|
fmt.Printf("获取用户列表失败: %v\n", err)
|
|||
|
os.Exit(1)
|
|||
|
}
|
|||
|
for _, user := range users {
|
|||
|
fmt.Printf("ID: %d, Username: %s, Email: %s\n", user.ID, user.Username, user.Email)
|
|||
|
}
|
|||
|
|
|||
|
case "reset-password":
|
|||
|
if *userID == 0 {
|
|||
|
fmt.Println("用户ID不能为空")
|
|||
|
os.Exit(1)
|
|||
|
}
|
|||
|
newPassword, err := userService.ResetPassword(*userID)
|
|||
|
if err != nil {
|
|||
|
fmt.Printf("重置密码失败: %v\n", err)
|
|||
|
os.Exit(1)
|
|||
|
}
|
|||
|
fmt.Printf("密码重置成功,新密码: %s\n", newPassword)
|
|||
|
|
|||
|
default:
|
|||
|
fmt.Println("不支持的操作,支持的操作: create-user, list-users, reset-password")
|
|||
|
os.Exit(1)
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 3. Worker 入口 (cmd/worker/main.go)
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import (
|
|||
|
"context"
|
|||
|
"log"
|
|||
|
"os"
|
|||
|
"os/signal"
|
|||
|
"syscall"
|
|||
|
"time"
|
|||
|
|
|||
|
"user-service/internal/config"
|
|||
|
"user-service/internal/worker"
|
|||
|
)
|
|||
|
|
|||
|
func main() {
|
|||
|
cfg, err := config.Load("configs/config.yaml")
|
|||
|
if err != nil {
|
|||
|
log.Fatal("加载配置失败:", err)
|
|||
|
}
|
|||
|
|
|||
|
// 创建上下文
|
|||
|
ctx, cancel := context.WithCancel(context.Background())
|
|||
|
defer cancel()
|
|||
|
|
|||
|
// 初始化 Worker
|
|||
|
emailWorker := worker.NewEmailWorker(cfg)
|
|||
|
notificationWorker := worker.NewNotificationWorker(cfg)
|
|||
|
|
|||
|
// 启动 Workers
|
|||
|
go func() {
|
|||
|
log.Println("启动邮件处理 Worker...")
|
|||
|
if err := emailWorker.Start(ctx); err != nil {
|
|||
|
log.Printf("邮件 Worker 错误: %v", err)
|
|||
|
}
|
|||
|
}()
|
|||
|
|
|||
|
go func() {
|
|||
|
log.Println("启动通知处理 Worker...")
|
|||
|
if err := notificationWorker.Start(ctx); err != nil {
|
|||
|
log.Printf("通知 Worker 错误: %v", err)
|
|||
|
}
|
|||
|
}()
|
|||
|
|
|||
|
// 优雅关闭
|
|||
|
quit := make(chan os.Signal, 1)
|
|||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|||
|
<-quit
|
|||
|
|
|||
|
log.Println("正在关闭 Workers...")
|
|||
|
cancel()
|
|||
|
|
|||
|
// 等待 Workers 完成
|
|||
|
time.Sleep(5 * time.Second)
|
|||
|
log.Println("Workers 已关闭")
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 4. 数据库迁移工具 (cmd/migrator/main.go)
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import (
|
|||
|
"flag"
|
|||
|
"log"
|
|||
|
|
|||
|
"user-service/internal/config"
|
|||
|
"user-service/internal/migration"
|
|||
|
)
|
|||
|
|
|||
|
func main() {
|
|||
|
var (
|
|||
|
direction = flag.String("direction", "up", "迁移方向: up 或 down")
|
|||
|
steps = flag.Int("steps", 0, "迁移步数,0表示全部")
|
|||
|
)
|
|||
|
flag.Parse()
|
|||
|
|
|||
|
cfg, err := config.Load("configs/config.yaml")
|
|||
|
if err != nil {
|
|||
|
log.Fatal("加载配置失败:", err)
|
|||
|
}
|
|||
|
|
|||
|
migrator := migration.NewMigrator(cfg.Database.URL)
|
|||
|
|
|||
|
switch *direction {
|
|||
|
case "up":
|
|||
|
log.Println("执行数据库迁移...")
|
|||
|
if err := migrator.Up(*steps); err != nil {
|
|||
|
log.Fatal("迁移失败:", err)
|
|||
|
}
|
|||
|
log.Println("迁移成功")
|
|||
|
|
|||
|
case "down":
|
|||
|
log.Println("回滚数据库迁移...")
|
|||
|
if err := migrator.Down(*steps); err != nil {
|
|||
|
log.Fatal("回滚失败:", err)
|
|||
|
}
|
|||
|
log.Println("回滚成功")
|
|||
|
|
|||
|
default:
|
|||
|
log.Fatal("不支持的迁移方向:", *direction)
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 🎯 为什么这样设计?
|
|||
|
|
|||
|
### 1. **关注点分离**
|
|||
|
|
|||
|
- `cmd/` 只负责程序启动和配置解析
|
|||
|
- `internal/` 负责具体的业务逻辑
|
|||
|
- 每个应用程序有独立的入口点
|
|||
|
|
|||
|
### 2. **多种运行模式**
|
|||
|
|
|||
|
```bash
|
|||
|
# 启动 HTTP/gRPC 服务器
|
|||
|
./user-service-server -port=8080 -config=prod.yaml
|
|||
|
|
|||
|
# 使用 CLI 工具
|
|||
|
./user-service-cli -action=create-user -username=john
|
|||
|
|
|||
|
# 启动后台 Worker
|
|||
|
./user-service-worker
|
|||
|
|
|||
|
# 执行数据库迁移
|
|||
|
./user-service-migrator -direction=up
|
|||
|
```
|
|||
|
|
|||
|
### 3. **构建和部署灵活性**
|
|||
|
|
|||
|
```dockerfile
|
|||
|
# Dockerfile 示例
|
|||
|
FROM golang:1.20-alpine AS builder
|
|||
|
|
|||
|
WORKDIR /app
|
|||
|
COPY . .
|
|||
|
|
|||
|
# 分别构建不同的应用程序
|
|||
|
RUN go build -o server ./cmd/server
|
|||
|
RUN go build -o cli ./cmd/cli
|
|||
|
RUN go build -o worker ./cmd/worker
|
|||
|
RUN go build -o migrator ./cmd/migrator
|
|||
|
|
|||
|
FROM alpine:latest
|
|||
|
RUN apk --no-cache add ca-certificates
|
|||
|
WORKDIR /root/
|
|||
|
|
|||
|
# 根据需要复制不同的可执行文件
|
|||
|
COPY --from=builder /app/server .
|
|||
|
COPY --from=builder /app/migrator .
|
|||
|
|
|||
|
# 可以选择启动不同的程序
|
|||
|
CMD ["./server"]
|
|||
|
```
|
|||
|
|
|||
|
### 4. **Makefile 示例**
|
|||
|
|
|||
|
```makefile
|
|||
|
# Makefile
|
|||
|
.PHONY: build-server build-cli build-worker build-migrator
|
|||
|
|
|||
|
build-server:
|
|||
|
go build -o bin/server ./cmd/server
|
|||
|
|
|||
|
build-cli:
|
|||
|
go build -o bin/cli ./cmd/cli
|
|||
|
|
|||
|
build-worker:
|
|||
|
go build -o bin/worker ./cmd/worker
|
|||
|
|
|||
|
build-migrator:
|
|||
|
go build -o bin/migrator ./cmd/migrator
|
|||
|
|
|||
|
build-all: build-server build-cli build-worker build-migrator
|
|||
|
|
|||
|
run-server:
|
|||
|
./bin/server -port=8080
|
|||
|
|
|||
|
run-worker:
|
|||
|
./bin/worker
|
|||
|
|
|||
|
migrate-up:
|
|||
|
./bin/migrator -direction=up
|
|||
|
|
|||
|
migrate-down:
|
|||
|
./bin/migrator -direction=down -steps=1
|
|||
|
```
|
|||
|
|
|||
|
## 🚀 在你的项目中的应用
|
|||
|
|
|||
|
在你当前的项目中,每个服务都应该有这样的结构:
|
|||
|
|
|||
|
```
|
|||
|
apps/user/
|
|||
|
├── cmd/
|
|||
|
│ ├── server/main.go # gRPC 服务器
|
|||
|
│ ├── cli/main.go # 用户管理 CLI
|
|||
|
│ └── migrator/main.go # 数据库迁移
|
|||
|
├── internal/ # 业务逻辑
|
|||
|
├── user.proto # API 定义
|
|||
|
└── Dockerfile
|
|||
|
|
|||
|
apps/gateway/
|
|||
|
├── cmd/
|
|||
|
│ ├── server/main.go # HTTP 网关服务器
|
|||
|
│ └── cli/main.go # 网关管理 CLI
|
|||
|
├── internal/
|
|||
|
├── gateway.api
|
|||
|
└── Dockerfile
|
|||
|
```
|
|||
|
|
|||
|
## 📋 总结
|
|||
|
|
|||
|
`cmd` 目录的核心作用:
|
|||
|
|
|||
|
1. **程序入口点** - 每个 main.go 是一个独立的应用程序
|
|||
|
2. **配置解析** - 处理命令行参数和配置文件
|
|||
|
3. **依赖注入** - 初始化和连接各个组件
|
|||
|
4. **生命周期管理** - 启动、运行、优雅关闭
|
|||
|
5. **多种运行模式** - 服务器、CLI、Worker 等不同形态
|