#工程化目录
三种主流目录结构,按项目规模选。
#模板1:简单三层(小项目)
project/
├── main.go # 入口:初始化 DB、注册路由
├── handler/ # 路由层:解析参数 → 调 service → 返回响应
│ ├── user_handler.go
│ └── post_handler.go
├── service/ # 业务层:核心业务逻辑
│ ├── user_service.go
│ └── post_service.go
├── repository/ # 数据层:数据库 CRUD
│ ├── user_repository.go
│ └── post_repository.go
├── model/ # 数据模型(对应数据库表)
│ ├── user.go
│ └── post.go
├── middleware/ # 中间件
│ ├── auth.go # JWT 鉴权
│ └── cors.go # 跨域
├── config/
│ └── config.go # 读取配置
├── config.yaml # 配置文件
├── go.mod
└── go.sum#模板2:企业级分层(团队协作推荐)
myapp/
├── cmd/ # 程序入口(可有多入口)
│ └── server/
│ └── main.go # 只做:装配依赖 → 启动服务
│
├── internal/ # 应用私有代码(Go 编译器禁止外部 import)
│ ├── handler/ # 路由层:解析参数 → 调 service → 返回响应
│ │ ├── user_handler.go
│ │ └── auth_handler.go
│ │
│ ├── service/ # 业务层:核心业务逻辑
│ │ ├── user_service.go
│ │ └── auth_service.go
│ │
│ ├── repository/ # 数据层:数据库 CRUD
│ │ ├── user_repository.go
│ │ └── post_repository.go
│ │
│ ├── model/ # 数据模型(对应数据库表)
│ │ ├── user.go
│ │ └── base.go # 公共字段:ID/CreatedAt/UpdatedAt
│ │
│ ├── dto/ # Data Transfer Object(数据传输对象)
│ │ ├── request/ # 入参结构体(带 binding 校验标签)
│ │ │ ├── user_request.go
│ │ │ └── common_request.go # 分页等公共请求
│ │ └── response/ # 出参结构体(带 json 标签)
│ │ ├── user_response.go
│ │ └── common_response.go # 统一响应格式
│ │
│ ├── middleware/ # 中间件
│ │ ├── auth.go # JWT 认证
│ │ ├── cors.go # 跨域
│ │ ├── logger.go # 请求日志
│ │ ├── recovery.go # panic 恢复
│ │ ├── rate_limit.go # 限流
│ │ └── permission.go # 权限校验
│ │
│ └── router/ # 路由注册
│ ├── router.go # 主路由 + 路由分组
│ └── api_v1.go # v1 版本接口
│
├── pkg/ # 可被外部项目复用的公共库
│ ├── jwt/ # JWT 签发/解析
│ ├── logger/ # 日志封装(zap 等)
│ ├── database/ # 数据库连接初始化
│ ├── redis/ # Redis 连接
│ ├── response/ # 统一响应封装
│ └── utils/ # 通用工具函数
│
├── config/ # 配置
│ ├── config.go # 配置结构体定义 + 加载逻辑
│ ├── config.yaml # 本地开发
│ ├── config.prod.yaml # 生产环境
│ └── config.test.yaml # 测试环境
│
├── migrations/ # 数据库迁移(SQL 文件,配合 goose/migrate)
│ ├── 000001_create_users.up.sql
│ └── 000001_create_users.down.sql
│
├── api/ # API 定义(Swagger / Protobuf)
│ └── swagger.yaml
│
├── deployments/ # 部署相关
│ ├── docker/
│ │ └── Dockerfile
│ ├── kubernetes/
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ └── docker-compose.yaml # 本地起 MySQL+Redis+应用
│
├── scripts/ # 运维脚本
│ ├── build.sh
│ └── deploy.sh
│
├── logs/ # 日志输出(gitignore)
├── .env.example # 环境变量样例(提交到 git)
├── .env # 真实环境变量(gitignore)
├── .gitignore
├── Makefile # make run / make build / make test
├── go.mod
├── go.sum
└── README.md#模板3:按业务模块聚合(改一个功能只动一个目录)
跟模板2「按层切分」相反:一个业务的 handler/service/repository/model/dto 全放一起,做成自包含模块。改用户功能只在 user/ 目录里跳,不用满项目翻。
myapp/
├── cmd/
│ └── server/
│ └── main.go # 入口:装配依赖、启动服务
│
├── internal/
│ ├── module/ # 按业务模块聚合(每个模块自包含)
│ │ ├── user/ # 用户模块:user 相关代码全在这
│ │ │ ├── handler.go # 路由处理:解析参数 → 调 service → 返回响应
│ │ │ ├── service.go # 业务逻辑
│ │ │ ├── repository.go # 数据库 CRUD
│ │ │ ├── model.go # 数据模型(对应表)
│ │ │ └── dto.go # 请求/响应结构(带 binding/json 标签)
│ │ └── post/ # 帖子模块(结构同上)
│ │ ├── handler.go
│ │ ├── service.go
│ │ ├── repository.go
│ │ ├── model.go
│ │ └── dto.go
│ │
│ ├── router/ # 路由注册:把 URL 绑到各模块 handler
│ │ └── router.go
│ └── middleware/ # 公共中间件(跨模块复用)
│ ├── auth.go
│ └── cors.go
│
├── pkg/ # 公共库(日志、DB 连接、统一响应等,无业务)
├── config/
│ └── config.yaml
├── go.mod
└── go.sum调用链路:请求 → router → module/user/handler → service → repository → DB
#模板4:enter.go 入口聚合(中大型项目)
基于模板2 的分层,每个层目录加一个 enter.go 作为该层统一入口,按业务域(system / example 等)分子目录。新增功能不用改 main.go,只在自己的业务域里注册。
myapp/
├── main.go # 入口:调 initialize.InitApp() 完成所有装配
│
├── api/ # 路由处理层
│ └── v1/ # 接口版本
│ ├── enter.go # 【入口】聚合所有业务域的 handler 集合 ApiGroup
│ ├── system/ # 系统域
│ │ ├── enter.go # 聚合 sys_user、sys_menu 等 handler
│ │ ├── sys_user.go
│ │ └── sys_menu.go
│ └── example/ # 业务域(按业务拆)
│ ├── enter.go
│ └── exa_customer.go
│
├── service/ # 业务逻辑层(结构同 api,按域聚合)
│ ├── enter.go # 【入口】聚合所有业务域的 service 集合
│ ├── system/
│ │ ├── enter.go
│ │ ├── sys_user.go
│ │ └── sys_menu.go
│ └── example/
│ ├── enter.go
│ └── exa_customer.go
│
├── router/ # 路由层(按域拆,每个域一个 router 组)
│ ├── enter.go # 【入口】聚合所有 RouterGroup
│ ├── system/
│ │ ├── enter.go
│ │ ├── sys_user.go # 在这注册 /sys/user/* 路由
│ │ └── sys_menu.go
│ └── example/
│ └── enter.go
│
├── model/ # 数据模型
│ ├── common/ # 公共:统一请求/响应、分页
│ │ ├── request/common.go
│ │ └── response/response.go
│ ├── system/ # 系统域模型(含 request/response 子目录)
│ │ ├── sys_user.go # 数据库表结构
│ │ ├── request/sys_user.go # 入参(带 binding 标签)
│ │ └── response/sys_user.go # 出参(带 json 标签)
│ └── example/
│ └── exa_customer.go
│
├── middleware/ # 中间件(jwt、cors、operation 等)
├── initialize/ # 【装配层】所有初始化代码集中在这
│ ├── router.go # 注册路由(把 router/* 接进 Gin Engine)
│ ├── gorm.go # 初始化数据库
│ ├── redis.go # 初始化 Redis
│ ├── viper.go # 读配置
│ └── plugin.go # 挂载插件
│
├── core/ # 服务核心:启动 Gin Engine、Server
│ ├── server.go
│ └── viper.go
│
├── global/ # 全局变量(DB、Redis 实例等,启动后只读)
│ └── global.go
│
├── config/ # 配置结构体 + 配置文件
│ ├── config.go
│ └── config.yaml
│
├── plugin/ # 插件目录(每个插件自包含一套分层)
│ └── email/ # 例:邮件插件有自己的 api/router/service/model
│ ├── plugin.go # 插件入口(被 initialize/plugin.go 加载)
│ ├── api/
│ ├── router/
│ └── service/
│
├── source/ # 初始化种子数据(建库后插入初始菜单/角色)
├── utils/ # 工具函数
├── go.mod
└── go.sum#enter.go 是怎么工作的
以 api/v1/system/enter.go 为例:
package system
type ApiGroup struct {
SysUserApi
SysMenuApi
}上一层 api/v1/enter.go 再把各域聚合成总入口:
package v1
type ApiGroup struct {
system.ApiGroup
example.ApiGroup
}上层 service / router 同理。这样 handler/service 注册时只认 ApiGroup.ServiceGroup,新增一个业务域只动两个 enter.go,不用碰 main.go。
#三条核心约定
- 每个层目录必有 enter.go——作为该层对外的唯一入口。
- 按业务域分子目录(system/example/...)——同一域的 handler/service/model 放一起,跨域不混。
- 初始化集中在
initialize/——main.go 只调一句initialize.InitApp(),装配逻辑全收敛在这里。
调用链路:请求 → router/system → ApiGroup(system.SysUserApi) → ServiceGroup(system.SysUserService) → model → DB
#怎么选
| 项目特征 | 推荐 |
|---|---|
| 个人项目、Demo、10 接口以内 | 模板1 |
| 团队项目、要上线、有 CI/CD | 模板2 |
| 业务边界清晰、模块多、想改动隔离 | 模板3 |
| 业务域多、要插件化、长期迭代 | 模板4 |

