10 KiB
Executable File
10 KiB
Executable File
开发指南
本文档提供 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
解决:
- 检查 MySQL 容器是否运行:
docker ps | grep mysql - 检查连接配置:
configs/config.dev.yaml - 检查网络:
docker network ls
2. 热重载不工作
问题: Air 不检测文件变化 解决:
- 检查
.air.toml配置 - 确保文件在挂载的卷中
- 重启 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. 内存优化
- 及时关闭数据库连接
- 使用连接池
- 避免内存泄漏
下一步
- ✅ 完成基础框架搭建
- ✅ 实现用户认证模块
- ✅ 实现合同管理模块
- 🔄 添加文件上传功能
- 🔄 实现权限控制(RBAC)
- 🔄 添加 API 文档(Swagger)
- 🔄 实现监控和日志收集
- 🔄 编写完整的测试套件