api/backend/rotation.go
package backend
import "github.com/gogf/gf/v2/frame/g"
type RotationReq struct {
g.Meta `path:"/backend/rotation/add" method:"post" summary:"轮播图的添加"
PicUrl string `json:"pic_url" v:"required#图片链接不能空" dc:"图片链接"`
Link string `json:"link" v:"required#跳转链接不能空" dc:"跳转链接"`
Sort int `json:"sort" dc:"排序"`
}
type RotationRes struct {
RotationId int `json:"rotation_id"`
}
internal/model/ratation.go
package model
type RotationCreateUpdateBase struct {
PicUrl string
Link string
Sort int
}
type RotationCreateInput struct {
RotationCreateUpdateBase
}
type RotationCreateOutput struct {
RotationId int
}
GoFrame的api层和model层的数据结构有什么区别,各有什么作用以及为什么这么设计?
先想像一个「照相馆」的接客流程
- 前台柜台:负责接待顾客,登记预约信息。
- 内部影棚:摄影师根据预约信息布景、拍照、后期处理。
- 前台再把成品照片交给顾客。
在代码里,我们也把“前台”与“后台”职责拆开,避免一份数据结构同时承担太多角色,导致未来难维护。
api/backend/rotation.go
= 程序的「前台柜台」
• 里面的 RotationReq
就像「顾客填写的预约单」。
- 标签
path:"/backend/rotation/add" method:"post"
告诉框架:只要浏览器 POST 这个地址,就把请求内容解析到RotationReq
里。 v:"required#图片链接不能空"
相当于柜台当场检查:这栏必填哦,空了就直接退回,不会让后台为无效数据白忙活。
• RotationRes
就像柜台最后给顾客的小票或回执,上面只写他们关心的东西(这里就是生成的 rotation_id
)。
特点:
- 只服务 HTTP/REST 接口;
- 专注参数校验、接口文档;
- 一旦路由地址或校验提示要改,只动这里即可。
internal/model/rotation.go
= 程序的「内部影棚」
• RotationCreateInput
、RotationCreateUpdateBase
像摄影师拿到的「拍摄指令」——只写业务必须的信息,不带路由、校验、Json 标签这些“前台”元素。
• RotationCreateOutput
是摄影师拍完照后给柜台的“成片编号”。
特点:
- 只为业务逻辑、数据库操作服务;
- 可被别的渠道复用(例如定时任务、RPC 调用),不依赖 HTTP;
- 写单元测试时,可直接 new 一个
RotationCreateInput
,不需要构造 HTTP 请求。
- 整个流程怎么串起来?
一次「新增轮播图」大致流程:
① 浏览器发来 JSON:{ pic_url:"...", link:"...", sort:1 }
② 框架把它塞进 RotationReq
➜ 自动做字段校验
③ 控制器把 RotationReq
转成 RotationCreateInput
➜ 调用 Service
④ Service 干完活,返回 RotationCreateOutput
⑤ 控制器把它包装成 RotationRes
➜ 返回浏览器
于是:
- 前台结构体照顾「网络通信」这件事;
- 后台结构体专注「业务处理」这件事;
- 二者字段可以重叠,但职责泾渭分明,改动互不牵连。
为什么要这样多写一份,不能共用吗?
- 解耦:HTTP 规则、校验文案变化频繁,写在业务层会到处产生连锁反应。
- 复用:业务函数将来用在 gRPC / 消息队列时,肯定不需要 HTTP 标签。
- 可测:单测只关注业务,不想和校验、路由搞在一起。
- 文档自动化:
g.Meta
能生成 Swagger;如果放到业务层,纯后端任务也被强塞 Swagger 标签,显得累赘。
一句话:
• api/backend
的结构体 = “让前端/外部世界能顺畅沟通”
• internal/model
的结构体 = “让后台业务能稳定地做事”
它们长得像,但各司其职,这就是这套目录与结构体设计的核心理念。