Skip to content

项目结构规范

核心原则

项目结构必须清晰分层,遵循 Handler → Service → GORM 架构,确保代码可维护性和安全性。

允许的做法

整体结构

project/
├── frontend/          # 前端项目
├── backend/           # 后端项目
├── docs/              # 文档
├── .gitignore
└── README.md

前端项目结构(UmiJS)

frontend/
├── src/
│   ├── pages/              # 页面组件(约定式路由)
│   │   ├── index.tsx       # 首页
│   │   └── users/
│   │       ├── index.tsx   # 用户列表
│   │       └── [id].tsx    # 用户详情
│   ├── components/         # 通用组件
│   ├── services/           # API 服务
│   ├── models/             # 数据模型(dva)
│   ├── utils/              # 工具函数
│   ├── hooks/              # 自定义 Hooks
│   ├── types/              # TypeScript 类型
│   └── constants/          # 常量定义
├── public/                 # 静态资源
├── config/                 # UmiJS 配置
├── package.json
└── tsconfig.json

后端项目结构(Go + Echo)

backend/
├── main.go                 # 应用入口
├── go.mod
├── go.sum
├── config.toml             # 配置文件(不提交 Git)
├── config.toml.example     # 配置示例
├── config/                 # 配置管理
│   ├── config.go           # 配置结构定义
│   └── loader.go           # 配置加载逻辑
├── app/                    # 依赖注入容器
│   └── app.go              # App 容器实现
├── httpapi/                # HTTP API 层
│   ├── routes/             # 路由注册
│   │   └── routes.go
│   ├── middleware/         # 中间件
│   │   ├── auth.go
│   │   └── logger.go
│   ├── handlers/           # 请求处理器
│   │   ├── user.go
│   │   ├── product.go
│   │   └── order.go
│   └── response/           # 统一响应体
│       └── response.go
├── services/               # 业务逻辑层
│   ├── user.go
│   ├── user_test.go
│   ├── product.go
│   └── order.go
├── models/                 # 数据模型
│   ├── user.go
│   ├── product.go
│   └── order.go
├── clients/                # 第三方服务客户端
│   ├── storage/            # 对象存储 Client
│   │   ├── client.go
│   │   ├── models.go
│   │   └── errors.go
│   ├── email/              # 邮件 Client
│   │   └── client.go
│   └── ai/                 # AI 服务 Client
│       └── client.go
├── utils/                  # 工具函数
│   ├── crypto.go
│   └── time.go
└── migrations/             # 数据库迁移
    ├── 001_create_users_table.up.sql
    └── 001_create_users_table.down.sql

目录组织原则

原则 1:按功能模块划分

按业务功能而非技术层划分顶层目录:

✅ 正确 - 按功能划分
backend/
├── httpapi/           # HTTP 层
├── services/          # 业务层
├── models/            # 数据层
└── clients/           # 集成层

❌ 错误 - 按技术层过度细分
backend/
├── controllers/
├── repositories/
├── entities/
├── dtos/
└── interfaces/

原则 2:Client 层独立目录

第三方服务 Client 使用独立子目录

✅ 正确
clients/
├── storage/
│   ├── client.go      # Client 实现
│   ├── models.go      # 请求/响应模型
│   └── errors.go      # 错误定义
├── email/
│   └── client.go
└── ai/
    └── client.go

❌ 错误 - 所有 Client 在一个文件
clients/
└── clients.go         # 包含所有 Client

原则 3:每个 Handler 一个文件

✅ 正确
httpapi/handlers/
├── user.go            # UserHandler 及其所有方法
├── product.go         # ProductHandler
└── order.go           # OrderHandler

❌ 错误 - 按 HTTP 方法划分
httpapi/handlers/
├── get.go
├── post.go
└── delete.go

原则 4:测试文件同目录

✅ 正确
services/
├── user.go
├── user_test.go       # 同目录
├── product.go
└── product_test.go

❌ 错误 - 测试文件独立目录
services/
├── user.go
└── product.go
test/
├── user_test.go
└── product_test.go

文件命名规范

文件名规则

  • 使用 snake_case(小写 + 下划线)
  • 使用 单数名词
  • 使用 有意义的描述性名称
✅ 正确
user.go
product.go
order_service.go
storage_client.go
user_test.go

❌ 错误
User.go              # 大写开头
users.go             # 复数
userService.go       # 驼峰命名
u.go                 # 名称过短

特殊文件命名

文件用途命名规范示例
主入口main.gomain.go
配置config.goconfig/config.go
路由routes.gohttpapi/routes/routes.go
响应response.gohttpapi/response/response.go
错误errors.goclients/storage/errors.go
测试*_test.gouser_test.go

模块划分原则

Handler 模块划分

按资源类型划分 Handler

go
// httpapi/handlers/user.go
type UserHandler struct {
    app *app.App
}

func NewUserHandler(app *app.App) *UserHandler {
    return &UserHandler{app: app}
}

func (h *UserHandler) Create(c echo.Context) error { }
func (h *UserHandler) List(c echo.Context) error { }
func (h *UserHandler) GetByID(c echo.Context) error { }
func (h *UserHandler) Update(c echo.Context) error { }
func (h *UserHandler) Delete(c echo.Context) error { }

Service 模块划分

每个业务领域一个 Service

go
// services/user.go
type UserService struct {
    db          *gorm.DB
    emailClient *email.Client
}

func NewUserService(db *gorm.DB, emailClient *email.Client) *UserService {
    return &UserService{
        db:          db,
        emailClient: emailClient,
    }
}

func (s *UserService) Create(ctx context.Context, req *CreateUserRequest) (*models.User, error) { }
func (s *UserService) List(ctx context.Context, page, pageSize int) ([]models.User, int64, error) { }

Model 模块划分

每个数据表一个 Model 文件

go
// models/user.go
type User struct {
    ID        int64  `gorm:"column:id" json:"id"`
    Name      string `gorm:"column:name" json:"name"`
    Email     string `gorm:"column:email" json:"email"`
    Password  string `gorm:"column:password" json:"-"`
    CreatedAt int64  `gorm:"column:created_at" json:"created_at"`
}

func (User) TableName() string {
    return "users"
}

包导入规范

导入顺序

按以下顺序分组导入

go
import (
    // 1. 标准库
    "context"
    "fmt"
    "time"

    // 2. 第三方库
    "github.com/labstack/echo/v4"
    "gorm.io/gorm"

    // 3. 项目内部包
    "your-project/app"
    "your-project/models"
    "your-project/services"
)

包别名规范

go
// ✅ 正确 - 有冲突时使用别名
import (
    userModels "your-project/models/user"
    userServices "your-project/services/user"
)

// ❌ 错误 - 无冲突时不需要别名
import (
    m "your-project/models"         // 不必要的缩写
    s "your-project/services"
)

分层职责

层级职责
Handler参数绑定、校验、调用 Service、返回响应
Service业务逻辑、事务管理、直接调用 GORM
GORMORM 映射、数据库操作
Model数据结构定义

关键原则

  • Handler 只调用 Service,不直接操作 GORM
  • Service 直接使用 GORM 操作数据库
  • 跨 Service 调用通过依赖注入

DTO 分离原则

  • Handler 层定义绑定结构体(DTO),暴露给外部
  • Service 层定义请求结构体,内部使用
  • Handler 手动转换 DTO → Service Request
go
// ✅ 正确 - Handler 层绑定结构体
type CreateUserRequest struct {
    Name     string `json:"name" validate:"required"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=6"`
    // 不包含敏感字段 IsAdmin
}

func (h *UserHandler) Create(c echo.Context) error {
    // 1. 参数绑定
    req := new(CreateUserRequest)
    if err := c.Bind(req); err != nil {
        return c.JSON(400, response.Fail(1000, "参数格式错误"))
    }

    // 2. 参数校验
    if err := c.Validate(req); err != nil {
        return c.JSON(400, response.Fail(1001, err.Error()))
    }

    // 3. DTO 转换
    serviceReq := &services.CreateUserRequest{
        Name:     req.Name,
        Email:    req.Email,
        Password: req.Password,
    }

    // 4. 调用 Service
    user, err := h.userService.Create(c.Request().Context(), serviceReq)
    if err != nil {
        return c.JSON(500, response.Fail(500, "创建失败"))
    }

    // 5. 统一响应
    return c.JSON(201, response.Success(user))
}
go
// ✅ 正确 - Service 层
func (s *UserService) Create(ctx context.Context, req *CreateUserRequest) (*models.User, error) {
    // 业务逻辑:加密密码
    hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)

    user := &models.User{
        Name:      req.Name,
        Email:     req.Email,
        Password:  string(hashedPassword),
        IsAdmin:   false,  // 安全字段内部赋值
        CreatedAt: time.Now().Unix(),
    }

    // 直接使用 GORM
    if err := s.db.WithContext(ctx).Create(user).Error; err != nil {
        return nil, err
    }

    return user, nil
}

禁止的做法

  • 禁止 Handler 直接操作 GORM
  • 禁止 绑定结构体包含敏感字段(IsAdmin、Role 等)
  • 禁止 所有代码在单一文件
go
// ❌ 错误 - Handler 直接操作数据库
func CreateUser(c echo.Context) error {
    var user User
    db.Create(&user)  // 应该通过 Service
}

// ❌ 错误 - 绑定结构体包含敏感字段
type CreateUserRequest struct {
    IsAdmin bool `json:"isAdmin"`  // 敏感字段禁止绑定
}

特殊场景

依赖注入

  • 使用 App 结构体作为依赖注入容器
go
// ✅ 正确 - App 结构体
type App struct {
    config      *config.Config
    db          *gorm.DB
    userService *services.UserService
    // 其他 Service...
}

func (a *App) initServices() {
    a.userService = services.NewUserService(a.db, a.config)
}

func (a *App) UserService() *services.UserService {
    return a.userService
}

测试文件组织

services/
├── user.go
└── user_test.go     # 同目录测试

相关文档