280 lines
6.4 KiB
Markdown
280 lines
6.4 KiB
Markdown
|
|
# 👨💻 开发指南
|
|||
|
|
|
|||
|
|
## 项目结构理解
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
tyapi-server-gin/
|
|||
|
|
├── cmd/api/ # 应用入口
|
|||
|
|
├── internal/ # 内部代码
|
|||
|
|
│ ├── app/ # 应用层
|
|||
|
|
│ ├── config/ # 配置管理
|
|||
|
|
│ ├── container/ # 依赖注入
|
|||
|
|
│ ├── domains/ # 业务域
|
|||
|
|
│ │ └── user/ # 用户域示例
|
|||
|
|
│ └── shared/ # 共享基础设施
|
|||
|
|
├── pkg/ # 外部包
|
|||
|
|
├── scripts/ # 脚本文件
|
|||
|
|
├── test/ # 测试文件
|
|||
|
|
└── docs/ # 文档目录
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 开发流程
|
|||
|
|
|
|||
|
|
### 1. 创建新的业务域
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 使用Makefile创建新域
|
|||
|
|
make new-domain DOMAIN=product
|
|||
|
|
|
|||
|
|
# 手动创建目录结构
|
|||
|
|
mkdir -p internal/domains/product/{entities,dto,services,repositories,handlers,routes,events}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 实现业务实体
|
|||
|
|
|
|||
|
|
创建 `internal/domains/product/entities/product.go`:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type Product struct {
|
|||
|
|
BaseEntity
|
|||
|
|
Name string `gorm:"not null;size:100"`
|
|||
|
|
Description string `gorm:"size:500"`
|
|||
|
|
Price float64 `gorm:"not null"`
|
|||
|
|
CategoryID uint `gorm:"not null"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (p *Product) Validate() error {
|
|||
|
|
if p.Name == "" {
|
|||
|
|
return errors.New("产品名称不能为空")
|
|||
|
|
}
|
|||
|
|
if p.Price <= 0 {
|
|||
|
|
return errors.New("产品价格必须大于0")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 定义仓储接口
|
|||
|
|
|
|||
|
|
创建 `internal/domains/product/repositories/product_repository.go`:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type ProductRepository interface {
|
|||
|
|
shared.BaseRepository[entities.Product]
|
|||
|
|
FindByCategory(ctx context.Context, categoryID uint) ([]*entities.Product, error)
|
|||
|
|
FindByPriceRange(ctx context.Context, min, max float64) ([]*entities.Product, error)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 实现业务服务
|
|||
|
|
|
|||
|
|
创建 `internal/domains/product/services/product_service.go`:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type ProductService interface {
|
|||
|
|
CreateProduct(ctx context.Context, req *dto.CreateProductRequest) (*dto.ProductResponse, error)
|
|||
|
|
GetProduct(ctx context.Context, id uint) (*dto.ProductResponse, error)
|
|||
|
|
UpdateProduct(ctx context.Context, id uint, req *dto.UpdateProductRequest) (*dto.ProductResponse, error)
|
|||
|
|
DeleteProduct(ctx context.Context, id uint) error
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. 实现 HTTP 处理器
|
|||
|
|
|
|||
|
|
创建 `internal/domains/product/handlers/product_handler.go`:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func (h *ProductHandler) CreateProduct(c *gin.Context) {
|
|||
|
|
var req dto.CreateProductRequest
|
|||
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|||
|
|
response.Error(c, http.StatusBadRequest, "请求参数无效", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
product, err := h.productService.CreateProduct(c.Request.Context(), &req)
|
|||
|
|
if err != nil {
|
|||
|
|
response.Error(c, http.StatusInternalServerError, "创建产品失败", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
response.Success(c, product)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6. 配置路由
|
|||
|
|
|
|||
|
|
创建 `internal/domains/product/routes/product_routes.go`:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func RegisterProductRoutes(router shared.Router, handler *handlers.ProductHandler) {
|
|||
|
|
v1 := router.Group("/api/v1")
|
|||
|
|
{
|
|||
|
|
products := v1.Group("/products")
|
|||
|
|
{
|
|||
|
|
products.POST("", handler.CreateProduct)
|
|||
|
|
products.GET("/:id", handler.GetProduct)
|
|||
|
|
products.PUT("/:id", handler.UpdateProduct)
|
|||
|
|
products.DELETE("/:id", handler.DeleteProduct)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 常用开发命令
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 代码格式化
|
|||
|
|
make fmt
|
|||
|
|
|
|||
|
|
# 代码检查
|
|||
|
|
make lint
|
|||
|
|
|
|||
|
|
# 运行测试
|
|||
|
|
make test
|
|||
|
|
|
|||
|
|
# 测试覆盖率
|
|||
|
|
make test-coverage
|
|||
|
|
|
|||
|
|
# 生成API文档
|
|||
|
|
make docs
|
|||
|
|
|
|||
|
|
# 热重载开发
|
|||
|
|
make dev
|
|||
|
|
|
|||
|
|
# 构建二进制文件
|
|||
|
|
make build
|
|||
|
|
|
|||
|
|
# 清理临时文件
|
|||
|
|
make clean
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 测试编写
|
|||
|
|
|
|||
|
|
### 单元测试示例
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func TestProductService_CreateProduct(t *testing.T) {
|
|||
|
|
// 设置测试数据
|
|||
|
|
mockRepo := mocks.NewProductRepository(t)
|
|||
|
|
service := services.NewProductService(mockRepo, nil)
|
|||
|
|
|
|||
|
|
req := &dto.CreateProductRequest{
|
|||
|
|
Name: "测试产品",
|
|||
|
|
Description: "测试描述",
|
|||
|
|
Price: 99.99,
|
|||
|
|
CategoryID: 1,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置Mock期望
|
|||
|
|
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*entities.Product")).
|
|||
|
|
Return(&entities.Product{}, nil)
|
|||
|
|
|
|||
|
|
// 执行测试
|
|||
|
|
result, err := service.CreateProduct(context.Background(), req)
|
|||
|
|
|
|||
|
|
// 断言结果
|
|||
|
|
assert.NoError(t, err)
|
|||
|
|
assert.NotNil(t, result)
|
|||
|
|
assert.Equal(t, req.Name, result.Name)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 集成测试示例
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func TestProductAPI_Integration(t *testing.T) {
|
|||
|
|
// 启动测试服务器
|
|||
|
|
testApp := setupTestApp(t)
|
|||
|
|
defer testApp.Cleanup()
|
|||
|
|
|
|||
|
|
// 创建测试请求
|
|||
|
|
reqBody := `{"name":"测试产品","price":99.99,"category_id":1}`
|
|||
|
|
req := httptest.NewRequest("POST", "/api/v1/products", strings.NewReader(reqBody))
|
|||
|
|
req.Header.Set("Content-Type", "application/json")
|
|||
|
|
|
|||
|
|
// 执行请求
|
|||
|
|
w := httptest.NewRecorder()
|
|||
|
|
testApp.ServeHTTP(w, req)
|
|||
|
|
|
|||
|
|
// 验证响应
|
|||
|
|
assert.Equal(t, http.StatusCreated, w.Code)
|
|||
|
|
|
|||
|
|
var response map[string]interface{}
|
|||
|
|
json.Unmarshal(w.Body.Bytes(), &response)
|
|||
|
|
assert.Equal(t, "success", response["status"])
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 开发规范
|
|||
|
|
|
|||
|
|
### 代码风格
|
|||
|
|
|
|||
|
|
- 使用 `gofmt` 格式化代码
|
|||
|
|
- 遵循 Go 命名规范
|
|||
|
|
- 添加必要的注释和文档
|
|||
|
|
- 函数长度不超过 50 行
|
|||
|
|
|
|||
|
|
### Git 提交规范
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 功能开发
|
|||
|
|
git commit -m "feat: 添加用户注册功能"
|
|||
|
|
|
|||
|
|
# 问题修复
|
|||
|
|
git commit -m "fix: 修复登录验证问题"
|
|||
|
|
|
|||
|
|
# 文档更新
|
|||
|
|
git commit -m "docs: 更新API文档"
|
|||
|
|
|
|||
|
|
# 重构代码
|
|||
|
|
git commit -m "refactor: 重构用户服务层"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 分支管理
|
|||
|
|
|
|||
|
|
- `main`: 主分支,用于生产发布
|
|||
|
|
- `develop`: 开发分支,用于集成测试
|
|||
|
|
- `feature/xxx`: 功能分支,用于新功能开发
|
|||
|
|
- `hotfix/xxx`: 热修复分支,用于紧急修复
|
|||
|
|
|
|||
|
|
## 调试技巧
|
|||
|
|
|
|||
|
|
### 使用 Delve 调试器
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 安装 Delve
|
|||
|
|
go install github.com/go-delve/delve/cmd/dlv@latest
|
|||
|
|
|
|||
|
|
# 启动调试
|
|||
|
|
dlv debug ./cmd/api/main.go
|
|||
|
|
|
|||
|
|
# 设置断点
|
|||
|
|
(dlv) break main.main
|
|||
|
|
(dlv) continue
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 日志调试
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 添加调试日志
|
|||
|
|
logger.Debug("Processing user request",
|
|||
|
|
zap.String("user_id", userID),
|
|||
|
|
zap.String("action", "create_product"))
|
|||
|
|
|
|||
|
|
// 临时调试信息
|
|||
|
|
fmt.Printf("Debug: %+v\n", debugData)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 性能分析
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 启用 pprof
|
|||
|
|
go tool pprof http://localhost:8080/debug/pprof/profile
|
|||
|
|
|
|||
|
|
# 内存分析
|
|||
|
|
go tool pprof http://localhost:8080/debug/pprof/heap
|
|||
|
|
|
|||
|
|
# 协程分析
|
|||
|
|
go tool pprof http://localhost:8080/debug/pprof/goroutine
|
|||
|
|
```
|