Files
zsp-project/backend/docs/DEVELOPMENT.md
2026-06-03 20:59:39 +08:00

10 KiB
Executable File
Raw Blame History

开发指南

本文档提供 ZSP 后端系统的详细开发说明。

开发环境设置

1. 环境准备

使用 Docker推荐

# 1. 克隆项目
git clone <repository-url>
cd zsp-backend

# 2. 复制环境变量文件
cp .env.dev.example .env.dev

# 3. 启动开发环境
docker-compose -f docker-compose.dev.yml up --build

# 4. 查看日志
docker-compose -f docker-compose.dev.yml logs -f app

本地开发

# 1. 安装 Go 1.25.4+
# 2. 安装 Air
go install github.com/air-verse/air@latest

# 3. 启动 MySQL
docker run --name mysql-dev \
  -e MYSQL_ROOT_PASSWORD=password \
  -e MYSQL_DATABASE=myapp \
  -p 3306:3306 \
  -d mysql:8.0

# 4. 运行应用
air -c .air.toml

2. 项目结构说明

internal/
├── handler/     # HTTP 请求处理器
├── middleware/  # 中间件(认证、日志等)
├── model/       # 数据模型定义
├── repository/  # 数据访问层
├── service/     # 业务逻辑层
└── server/      # 服务器配置和路由

开发流程

1. 添加新功能模块

步骤 1: 创建数据模型

internal/model/ 目录下创建新的模型文件:

// internal/model/product.go
package model

type Product struct {
    ID          uint   `gorm:"primarykey" json:"id"`
    Name        string `gorm:"size:100" json:"name"`
    Description string `gorm:"size:255" json:"description"`
    Price       int    `json:"price"`
    // ... 其他字段
}

步骤 2: 创建 Repository

internal/repository/ 目录下创建数据访问层:

// internal/repository/product_repository.go
package repository

import (
    "github.com/rovina/zsp-backend/internal/model"
    "gorm.io/gorm"
)

type ProductRepository interface {
    Create(product *model.Product) error
    FindByID(id uint) (*model.Product, error)
    FindAll() ([]model.Product, error)
    Update(product *model.Product) error
    Delete(id uint) error
}

type productRepository struct {
    db *gorm.DB
}

func NewProductRepository(db *gorm.DB) ProductRepository {
    return &productRepository{db: db}
}

// 实现接口方法...

步骤 3: 创建 Service

internal/service/ 目录下创建业务逻辑层:

// internal/service/product_service.go
package service

import (
    "github.com/rovina/zsp-backend/internal/model"
    "github.com/rovina/zsp-backend/internal/repository"
)

type ProductService interface {
    CreateProduct(req *model.CreateProductRequest) (*model.Product, error)
    GetProduct(id uint) (*model.Product, error)
    ListProducts() ([]model.Product, error)
}

type productService struct {
    repo repository.ProductRepository
}

func NewProductService(repo repository.ProductRepository) ProductService {
    return &productService{repo: repo}
}

// 实现业务逻辑...

步骤 4: 创建 Handler

internal/handler/ 目录下创建 HTTP 处理器:

// internal/handler/product_handler.go
package handler

import (
    "net/http"
    
    "github.com/gin-gonic/gin"
    "github.com/rovina/zsp-backend/internal/service"
)

type ProductHandler struct {
    service service.ProductService
}

func NewProductHandler(service service.ProductService) *ProductHandler {
    return &ProductHandler{service: service}
}

func (h *ProductHandler) CreateProduct(c *gin.Context) {
    var req model.CreateProductRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    product, err := h.service.CreateProduct(&req)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusCreated, product)
}

// 其他处理器方法...

步骤 5: 注册路由

internal/server/router.go 中添加路由:

// internal/server/router.go
func SetupRouter(userHandler *handler.UserHandler, 
                 contractHandler *handler.ContractHandler,
                 productHandler *handler.ProductHandler, // 新增
                 authMiddleware gin.HandlerFunc) *gin.Engine {
    router := gin.Default()
    
    // 公共路由
    public := router.Group("/api/v1")
    {
        public.POST("/users/register", userHandler.Register)
        public.POST("/users/login", userHandler.Login)
    }
    
    // 需要认证的路由
    authorized := router.Group("/api/v1")
    authorized.Use(authMiddleware)
    {
        // 用户相关
        authorized.GET("/users/profile", userHandler.GetProfile)
        
        // 合同相关
        authorized.POST("/contracts", contractHandler.CreateContract)
        authorized.GET("/contracts", contractHandler.GetContracts)
        
        // 产品相关(新增)
        authorized.POST("/products", productHandler.CreateProduct)
        authorized.GET("/products", productHandler.GetProducts)
        authorized.GET("/products/:id", productHandler.GetProduct)
        authorized.PUT("/products/:id", productHandler.UpdateProduct)
        authorized.DELETE("/products/:id", productHandler.DeleteProduct)
    }
    
    return router
}

步骤 6: 在主程序中初始化

cmd/api/main.go 中初始化新的组件:

// cmd/api/main.go
func main() {
    // ... 现有代码
    
    // 初始化新的 Repository 和 Service
    productRepo := repository.NewProductRepository(db)
    productService, err := service.NewProductService(productRepo)
    if err != nil {
        log.Fatalf("error in productService Create: %s", err.Error())
        return
    }
    
    // 创建 Handler
    productHandler := handler.NewProductHandler(productService)
    
    // 更新路由设置
    router := server.SetupRouter(userHandler, contractHandler, productHandler, authMiddleware)
    
    // ... 剩余代码
}

2. 数据库迁移

自动迁移

在开发环境中,设置 reset_database: true 会自动执行 GORM 的 AutoMigrate。

手动创建迁移文件

# 创建迁移目录
mkdir -p migrations

# 创建迁移文件
cat > migrations/001_create_products_table.sql << 'EOF'
CREATE TABLE IF NOT EXISTS products (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    description VARCHAR(255),
    price INT NOT NULL DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
EOF

3. 测试开发

单元测试

// internal/service/product_service_test.go
package service

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
)

// 创建 Mock Repository
type mockProductRepository struct {
    mock.Mock
}

func (m *mockProductRepository) Create(product *model.Product) error {
    args := m.Called(product)
    return args.Error(0)
}

func TestProductService_CreateProduct(t *testing.T) {
    // 测试用例...
}

API 测试

使用 curl 或 Postman 测试 API

# 测试用户登录
curl -X POST http://localhost:8080/api/v1/users/login \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"password123"}'

# 测试创建合同(需要认证)
curl -X POST http://localhost:8080/api/v1/contracts \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <jwt-token>" \
  -d '{
    "name": "测试合同",
    "contract_type": "采购",
    "contract_code": "TEST001"
  }'

代码规范

1. 命名约定

  • 包名:小写,单数形式
  • 接口名:以 "er" 结尾(如 Repository, Handler
  • 结构体名:首字母大写
  • 变量名:驼峰式

2. 错误处理

// 正确:返回错误
func GetUser(id uint) (*model.User, error) {
    user, err := repo.FindByID(id)
    if err != nil {
        return nil, fmt.Errorf("failed to get user: %w", err)
    }
    return user, nil
}

// 错误panic 或忽略错误
func BadExample() {
    user, _ := repo.FindByID(1) // 不要忽略错误
    // 或
    panic("something went wrong") // 不要使用 panic
}

3. 日志记录

import "log"

// 使用结构化日志
log.Printf("User %d logged in", userID)
log.Printf("Failed to create contract: %v", err)

// 在生产环境中考虑使用更高级的日志库

调试技巧

1. 使用 Air 热重载

Air 会自动检测文件变化并重新编译。修改代码后无需手动重启。

2. 数据库调试

# 进入 MySQL 容器
docker exec -it zsp-backend-mysql-1 mysql -uroot -ppassword myapp

# 查看表结构
DESCRIBE users;
DESCRIBE contracts;

# 查询数据
SELECT * FROM users;
SELECT * FROM contracts;

3. 查看应用日志

# Docker 环境
docker-compose -f docker-compose.dev.yml logs -f app

# 查看特定服务的日志
docker-compose -f docker-compose.dev.yml logs app --tail=100

# 本地环境
tail -f tmp/air.log

常见问题

1. 数据库连接失败

问题: Failed to connect to database 解决:

  1. 检查 MySQL 容器是否运行: docker ps | grep mysql
  2. 检查连接配置: configs/config.dev.yaml
  3. 检查网络: docker network ls

2. 热重载不工作

问题: Air 不检测文件变化 解决:

  1. 检查 .air.toml 配置
  2. 确保文件在挂载的卷中
  3. 重启 Air: docker-compose restart app

3. 权限问题

问题: 文件写入权限错误 解决:

# 在容器内运行
docker exec -it zsp-backend-app-1 chmod -R 755 /workspace/uploads

性能优化建议

1. 数据库优化

  • 为常用查询字段添加索引
  • 使用连接池配置
  • 避免 N+1 查询问题

2. API 优化

  • 实现分页查询
  • 使用缓存Redis
  • 压缩响应数据

3. 内存优化

  • 及时关闭数据库连接
  • 使用连接池
  • 避免内存泄漏

下一步

  1. 完成基础框架搭建
  2. 实现用户认证模块
  3. 实现合同管理模块
  4. 🔄 添加文件上传功能
  5. 🔄 实现权限控制RBAC
  6. 🔄 添加 API 文档Swagger
  7. 🔄 实现监控和日志收集
  8. 🔄 编写完整的测试套件