tianyuan-api-server/cmd目录详解.md
2025-07-13 20:37:12 +08:00

390 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 等不同形态