mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-16 23:23:29 +08:00
docs(zh-CN): sync Chinese docs with latest upstream changes (#304)
* docs(zh-CN): sync Chinese docs with latest upstream changes * update --------- Co-authored-by: neo <neo.dowithless@gmail.com>
This commit is contained in:
267
docs/zh-CN/examples/go-microservice-CLAUDE.md
Normal file
267
docs/zh-CN/examples/go-microservice-CLAUDE.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# Go 微服务 — 项目 CLAUDE.md
|
||||
|
||||
> 一个使用 PostgreSQL、gRPC 和 Docker 的 Go 微服务真实示例。
|
||||
> 将此文件复制到您的项目根目录,并根据您的服务进行自定义。
|
||||
|
||||
## 项目概述
|
||||
|
||||
**技术栈:** Go 1.22+, PostgreSQL, gRPC + REST (grpc-gateway), Docker, sqlc (类型安全的 SQL), Wire (依赖注入)
|
||||
|
||||
**架构:** 采用领域、仓库、服务和处理器层的清晰架构。gRPC 作为主要传输方式,REST 网关用于外部客户端。
|
||||
|
||||
## 关键规则
|
||||
|
||||
### Go 规范
|
||||
|
||||
* 遵循 Effective Go 和 Go Code Review Comments 指南
|
||||
* 使用 `errors.New` / `fmt.Errorf` 配合 `%w` 进行包装 — 绝不对错误进行字符串匹配
|
||||
* 不使用 `init()` 函数 — 在 `main()` 或构造函数中进行显式初始化
|
||||
* 没有全局可变状态 — 通过构造函数传递依赖项
|
||||
* Context 必须是第一个参数,并在所有层中传播
|
||||
|
||||
### 数据库
|
||||
|
||||
* `queries/` 中的所有查询都使用纯 SQL — sqlc 生成类型安全的 Go 代码
|
||||
* 在 `migrations/` 中使用 golang-migrate 进行迁移 — 绝不直接更改数据库
|
||||
* 通过 `pgx.Tx` 为多步骤操作使用事务
|
||||
* 所有查询必须使用参数化占位符 (`$1`, `$2`) — 绝不使用字符串格式化
|
||||
|
||||
### 错误处理
|
||||
|
||||
* 返回错误,不要 panic — panic 仅用于真正无法恢复的情况
|
||||
* 使用上下文包装错误:`fmt.Errorf("creating user: %w", err)`
|
||||
* 在 `domain/errors.go` 中定义业务逻辑的哨兵错误
|
||||
* 在处理器层将领域错误映射到 gRPC 状态码
|
||||
|
||||
```go
|
||||
// Domain layer — sentinel errors
|
||||
var (
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrEmailTaken = errors.New("email already registered")
|
||||
)
|
||||
|
||||
// Handler layer — map to gRPC status
|
||||
func toGRPCError(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, domain.ErrUserNotFound):
|
||||
return status.Error(codes.NotFound, err.Error())
|
||||
case errors.Is(err, domain.ErrEmailTaken):
|
||||
return status.Error(codes.AlreadyExists, err.Error())
|
||||
default:
|
||||
return status.Error(codes.Internal, "internal error")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 代码风格
|
||||
|
||||
* 代码或注释中不使用表情符号
|
||||
* 导出的类型和函数必须有文档注释
|
||||
* 函数保持在 50 行以内 — 提取辅助函数
|
||||
* 对所有具有多个用例的逻辑使用表格驱动测试
|
||||
* 对于信号通道,优先使用 `struct{}`,而不是 `bool`
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
cmd/
|
||||
server/
|
||||
main.go # Entrypoint, Wire injection, graceful shutdown
|
||||
internal/
|
||||
domain/ # Business types and interfaces
|
||||
user.go # User entity and repository interface
|
||||
errors.go # Sentinel errors
|
||||
service/ # Business logic
|
||||
user_service.go
|
||||
user_service_test.go
|
||||
repository/ # Data access (sqlc-generated + custom)
|
||||
postgres/
|
||||
user_repo.go
|
||||
user_repo_test.go # Integration tests with testcontainers
|
||||
handler/ # gRPC + REST handlers
|
||||
grpc/
|
||||
user_handler.go
|
||||
rest/
|
||||
user_handler.go
|
||||
config/ # Configuration loading
|
||||
config.go
|
||||
proto/ # Protobuf definitions
|
||||
user/v1/
|
||||
user.proto
|
||||
queries/ # SQL queries for sqlc
|
||||
user.sql
|
||||
migrations/ # Database migrations
|
||||
001_create_users.up.sql
|
||||
001_create_users.down.sql
|
||||
```
|
||||
|
||||
## 关键模式
|
||||
|
||||
### 仓库接口
|
||||
|
||||
```go
|
||||
type UserRepository interface {
|
||||
Create(ctx context.Context, user *User) error
|
||||
FindByID(ctx context.Context, id uuid.UUID) (*User, error)
|
||||
FindByEmail(ctx context.Context, email string) (*User, error)
|
||||
Update(ctx context.Context, user *User) error
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
}
|
||||
```
|
||||
|
||||
### 使用依赖注入的服务
|
||||
|
||||
```go
|
||||
type UserService struct {
|
||||
repo domain.UserRepository
|
||||
hasher PasswordHasher
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewUserService(repo domain.UserRepository, hasher PasswordHasher, logger *slog.Logger) *UserService {
|
||||
return &UserService{repo: repo, hasher: hasher, logger: logger}
|
||||
}
|
||||
|
||||
func (s *UserService) Create(ctx context.Context, req CreateUserRequest) (*domain.User, error) {
|
||||
existing, err := s.repo.FindByEmail(ctx, req.Email)
|
||||
if err != nil && !errors.Is(err, domain.ErrUserNotFound) {
|
||||
return nil, fmt.Errorf("checking email: %w", err)
|
||||
}
|
||||
if existing != nil {
|
||||
return nil, domain.ErrEmailTaken
|
||||
}
|
||||
|
||||
hashed, err := s.hasher.Hash(req.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hashing password: %w", err)
|
||||
}
|
||||
|
||||
user := &domain.User{
|
||||
ID: uuid.New(),
|
||||
Name: req.Name,
|
||||
Email: req.Email,
|
||||
Password: hashed,
|
||||
}
|
||||
if err := s.repo.Create(ctx, user); err != nil {
|
||||
return nil, fmt.Errorf("creating user: %w", err)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 表格驱动测试
|
||||
|
||||
```go
|
||||
func TestUserService_Create(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
req CreateUserRequest
|
||||
setup func(*MockUserRepo)
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "valid user",
|
||||
req: CreateUserRequest{Name: "Alice", Email: "alice@example.com", Password: "secure123"},
|
||||
setup: func(m *MockUserRepo) {
|
||||
m.On("FindByEmail", mock.Anything, "alice@example.com").Return(nil, domain.ErrUserNotFound)
|
||||
m.On("Create", mock.Anything, mock.Anything).Return(nil)
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "duplicate email",
|
||||
req: CreateUserRequest{Name: "Alice", Email: "taken@example.com", Password: "secure123"},
|
||||
setup: func(m *MockUserRepo) {
|
||||
m.On("FindByEmail", mock.Anything, "taken@example.com").Return(&domain.User{}, nil)
|
||||
},
|
||||
wantErr: domain.ErrEmailTaken,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := new(MockUserRepo)
|
||||
tt.setup(repo)
|
||||
svc := NewUserService(repo, &bcryptHasher{}, slog.Default())
|
||||
|
||||
_, err := svc.Create(context.Background(), tt.req)
|
||||
|
||||
if tt.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
```bash
|
||||
# Database
|
||||
DATABASE_URL=postgres://user:pass@localhost:5432/myservice?sslmode=disable
|
||||
|
||||
# gRPC
|
||||
GRPC_PORT=50051
|
||||
REST_PORT=8080
|
||||
|
||||
# Auth
|
||||
JWT_SECRET= # Load from vault in production
|
||||
TOKEN_EXPIRY=24h
|
||||
|
||||
# Observability
|
||||
LOG_LEVEL=info # debug, info, warn, error
|
||||
OTEL_ENDPOINT= # OpenTelemetry collector
|
||||
```
|
||||
|
||||
## 测试策略
|
||||
|
||||
```bash
|
||||
/go-test # TDD workflow for Go
|
||||
/go-review # Go-specific code review
|
||||
/go-build # Fix build errors
|
||||
```
|
||||
|
||||
### 测试命令
|
||||
|
||||
```bash
|
||||
# Unit tests (fast, no external deps)
|
||||
go test ./internal/... -short -count=1
|
||||
|
||||
# Integration tests (requires Docker for testcontainers)
|
||||
go test ./internal/repository/... -count=1 -timeout 120s
|
||||
|
||||
# All tests with coverage
|
||||
go test ./... -coverprofile=coverage.out -count=1
|
||||
go tool cover -func=coverage.out # summary
|
||||
go tool cover -html=coverage.out # browser
|
||||
|
||||
# Race detector
|
||||
go test ./... -race -count=1
|
||||
```
|
||||
|
||||
## ECC 工作流
|
||||
|
||||
```bash
|
||||
# Planning
|
||||
/plan "Add rate limiting to user endpoints"
|
||||
|
||||
# Development
|
||||
/go-test # TDD with Go-specific patterns
|
||||
|
||||
# Review
|
||||
/go-review # Go idioms, error handling, concurrency
|
||||
/security-scan # Secrets and vulnerabilities
|
||||
|
||||
# Before merge
|
||||
go vet ./...
|
||||
staticcheck ./...
|
||||
```
|
||||
|
||||
## Git 工作流
|
||||
|
||||
* `feat:` 新功能,`fix:` 错误修复,`refactor:` 代码更改
|
||||
* 从 `main` 创建功能分支,需要 PR
|
||||
* CI: `go vet`, `staticcheck`, `go test -race`, `golangci-lint`
|
||||
* 部署: 在 CI 中构建 Docker 镜像,部署到 Kubernetes
|
||||
Reference in New Issue
Block a user