XYGo Admin 后端分层架构:Controller → Service → Logic → DAO 实战解析
引言
在使用 GoFrame 开发中后台系统时,很多开发者会被一个问题困扰:代码到底该写在哪一层?Controller 里能不能写数据库查询?Logic 和 Service 有什么区别?DAO 是什么?这些困惑在团队协作中尤为突出------每个人有自己的"习惯写法",最终项目变成难以维护的"大泥球"。
XYGo Admin 作为一款基于 GoFrame + Vue3 的企业级中后台框架,提供了一套清晰的分层架构规范。本文通过实际代码示例,带你彻底理解这套分层设计,并掌握正确的开发姿势。
一、整体架构概览
XYGo Admin 的后端采用四层架构,请求处理流程如下:
```
HTTP Request
→ Middleware(鉴权、日志、响应包装)
→ Controller(参数校验、调用 Service)
→ Service(接口定义,自动生成)
→ Logic(业务逻辑实现)
→ DAO(数据库操作)
→ Database
```
每一层各司其职,职责边界非常清晰。下面逐层剖析。
二、Controller 层:只做"接"和"返"
Controller 是请求的入口,它的职责只有两件事:
-
接收并校验参数 :检查请求参数格式是否正确
-
调用 Service 并返回结果:不写任何业务逻辑
来看一个用户管理的 Controller 示例:
```go
// api/admin/user.go --- API 接口定义
type UserReq struct {
g.Meta `path:"/user/list" method:"GET" summary:"用户列表"`
Page int `json:"page" d:"1"`
PageSize int `json:"pageSize" d:"20"`
}
type UserRes struct {
List \[\]*userListVO `json:"list"`
Total int `json:"total"`
}
```
```go
// controller/admin/user.go --- 控制器
func (c *userController) List(ctx context.Context, req *v1.UserReq) (res *v1.UserRes, err error) {
return c.userService.List(ctx, req)
}
```
关键点:Controller 函数体只有一行,就是把参数传给 Service。如果你在 Controller 里写 `g.DB().Model("xy_user").Scan(...)`,那就破坏了架构。
三、Service 层:接口契约,自动生成
Service 层在 GoFrame 中是一个接口层,由 `gf gen service` 命令自动生成。开发者不需要手写它,相应的实现写在 Logic 中。
```go
// service/user.go --- 由 gf gen service 自动生成,不要手动修改
type IUser interface {
List(ctx context.Context, req *v1.UserReq) (res *v1.UserRes, err error)
}
```
这层的价值在于:接口与实现分离。Logic 层的实现只要满足这个接口,Controller 就不需要关心具体实现细节。后续替换实现(比如从 MySQL 迁到 TiDB),只需要改 Logic 层即可。
四、Logic 层:业务逻辑的核心战场
Logic 层是项目中最核心的代码层。所有业务逻辑都在这里实现。
```go
// logic/user/user.go
type sUser struct{}
func init() {
service.RegisterUser(New())
}
func New() *sUser {
return &sUser{}
}
func (s *sUser) List(ctx context.Context, req *v1.UserReq) (res *v1.UserRes, err error) {
// 分页查询
res = &v1.UserRes{}
err = dao.User.Ctx(ctx).
Page(req.Page, req.PageSize).
Scan(&res.List)
if err != nil {
return nil, err
}
count, err := dao.User.Ctx(ctx).Count()
if err != nil {
return nil, err
}
res.Total = count
return
}
```
Logic 层的优势在于:
-
可测试性 :业务逻辑集中在一层,可以针对 Logic 编写单元测试
-
可复用性 :多个 Controller 可以调用同一段 Logic 代码
-
可维护性:业务变更时只需要修改 Logic 层的代码
五、DAO 层:数据库访问抽象
DAO(Data Access Object)是对数据库操作的封装,由 `gf gen dao` 命令自动生成。DAO 层位于 `internal/dao/` 目录下,而自动生成的内部实现则在 `dao/internal/` 中。
核心规则 :
-
`dao/internal/` 下的文件由 CLI 自动生成,不要手动修改
-
扩展的查询方法写在 `dao/` 外层
```go
// dao/internal/user.go --- 自动生成,不要修改
type User struct {
Table string
Columns userColumns
}
type userColumns struct {
Id string
Username string
Password string
Nickname string
Email string
CreatedAt string
UpdatedAt string
}
```
当需要扩展自定义查询方法时,不在自动生成的文件里改,而是在外层扩展:
```go
// dao/user.go --- 自定义扩展方法
package dao
func (d *userDao) GetActiveUsers(ctx context.Context) (\[\]entity.User, error) {
return d.User.Ctx(ctx).
Where("status", 1).
Where("deleted_at", 0).
All()
}
```
六、实用开发建议
1. 使用 CRUD 生成器加速开发
XYGo Admin 内置了代码生成器,从数据表生成完整的前后端代码(包括 DAO、Entity、Logic、Controller 和 Vue 页面)。推荐开发流程是:
-
设计好数据库表结构
-
使用代码生成器生成初始代码
-
在 Logic 层补充业务逻辑
-
调整前端页面细节
这在大多数 CRUD 场景下,能节省 70% 以上的重复工作量。
2. 规范命名
-
Controller 文件:`controller/admin/模块名.go`
-
Logic 文件:`logic/模块名/模块名.go`
-
DAO 文件:`dao/表名.go`
3. 理解 Middleware 层
公共逻辑放在中间件,不要在 Controller 里重复处理。例如:
```go
// middleware/admin_auth.go --- 权限校验中间件
func AdminAuth(r *ghttp.Request) {
token := r.Cookie.Get("admin_token")
if token.IsEmpty() {
r.Response.WriteJson(g.Map{"code": 401, "msg": "未登录"})
r.Exit()
}
// 解析 token、获取用户信息、注入上下文
ctxUser := parseToken(token.String())
r.SetCtx(ghttp.PutCtx(r.Context(), "user", ctxUser))
r.Middleware.Next()
}
```
七、常见误区
| 误区 | 正确做法 |
|------|---------|
| Controller 里写 SQL 查询 | Controller 只做参数接收和返回,查询写在 Logic 层 |
| 手动修改 `dao/internal/` 文件 | 不要改,改完后下次 `gf gen dao` 会被覆盖 |
| 在 Logic 里直接调用另一个 Logic | 通过 Service 接口调用,保持层级清晰 |
| 手写 Service 接口 | Service 由 `gf gen service` 自动生成,手写容易不一致 |
总结
XYGo Admin 的四层架构设计,本质是约定大于配置的思想体现。每层都有明确的职责边界,开发者不需要纠结"这段代码放哪",按照规范写即可。这种分层带来的好处在大规模项目中尤为明显------代码可读性高、团队协作顺畅、重构成本低。
如果你是 GoFrame 新手,建议从 CRUD 生成器入手,先自动生成一套标准代码,然后逐层阅读生成的代码,感受清晰的分层结构。等熟悉之后再尝试手写业务板,你会发现自己已经自然而然地遵循了这套规范。
> 更多 XYGo Admin 开发文档和最佳实践,可参考官方网站。