项目结构规范
核心原则
项目结构必须清晰分层,遵循 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.go | main.go |
| 配置 | config.go | config/config.go |
| 路由 | routes.go | httpapi/routes/routes.go |
| 响应 | response.go | httpapi/response/response.go |
| 错误 | errors.go | clients/storage/errors.go |
| 测试 | *_test.go | user_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 |
| GORM | ORM 映射、数据库操作 |
| 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 # 同目录测试