GORM + Gin 实现 RESTful 分页接口的完整实践
探讨如何使用 Go 语言的 Gin 框架与 GORM ORM 库协同工作,从结构体定义到数据库查询逻辑,逐步构建一个稳健、符合 RESTful 规范的标准化分页 API。
目标与方案概述
前端开发通常偏好基于“页码(Page)”和“每页数量(Size)”的访问方式,而后端底层操作则更依赖“偏移量(Offset)”和“限制数(Limit)”。本方案旨在通过 Go 语言的结构体标签(Tags)特性,将前端的友好请求直接映射为底层的数据库查询参数,并通过 GORM 完成数据的获取与 JSON 序列化。
第1步:设计基础分页响应结构
目的:定义统一的 API 返回格式及 URL 参数接收器。
- 入口:
model/pagination.go或项目全局模型文件。 - 动作:
form标签用于解析 URL Query 字符串;json标签控制序列化字段。对于业务数据列表,推荐使用interface{}类型以支持不同的数据模型。 - 重要参数:
PaginationQ包含页码(Page)、单页条数(Size)、实际查得的总数(Total)及数据列表。
// PaginationQ gin handler query binding struct
type PaginationQ struct {
Ok bool `json:"ok"`
Size uint `form:"size" json:"size"` // 每页显示的数量
Page uint `form:"page" json:"page"` // 当前页码
Data interface{} `json:"data" comment:"muster be a pointer of slice gorm.Model"` // save pagination list
Total uint `json:"total"`
}
第2步:定义数据表 Model 及搜索参数
目的:建立业务实体及其对应的搜索筛选规则。
- 入口:
model/m_ssh_log.go。 - 动作:
SshLog结构体通过form:标签允许用户通过 URL 指定过滤条件(如 IP、用户名等),同时配置gorm关联属性。
type SshLog struct {
BaseModel
UserId uint `gorm:"index" json:"user_id" form:"user_id"`
MachineId uint `gorm:"index" json:"machine_id" form:"machine_id"`
SshUser string `json:"ssh_user" comment:"ssh账号"`
ClientIp string `json:"client_ip" form:"client_ip"`
StartedAt time.Time `json:"started_at" form:"started_at"`
Status uint `json:"status" comment:"0-未标记 2-正常 4-警告 8-危险 16-致命"`
Remark string `json:"remark"`
Log string `gorm:"type:text" json:"log"`
Machine Machine `gorm:"association_autoupdate:false;association_autocreate:false" json:"machine"`
User User `gorm:"association_autoupdate:false;association_autocreate:false" json:"user"`
}
在此模型基础上,组合出专门的查询参数:
type SshLogQ struct {
SshLog
PaginationQ
FromTime string `form:"from_time"` // 搜索开始时间
ToTime string `form:"to_time"` // 搜索结束时间
}
第3步:HTTP Handler 中的参数绑定
目的:拦截并校验外部传入的请求参数。
- 入口:
handler/h_ssh_log.go。 - 动作:
c.ShouldBindQuery()会自动根据 URL Query 匹配formtag。 - 可观察状态:绑定成功继续执行数据库查询,失败则调用错误处理函数终止。
func SshLogAll(c *gin.Context) {
query := &model.SshLogQ{}
err := c.ShouldBindQuery(query)
if handleError(c, err) {
return
}
list, total, err := query.Search()
if handleError(c, err) {
return
}
// 组装最终结果
jsonPagination(c, list, total, &query.PaginationQ)
}
第4步:构建带有分页和过滤的查询逻辑
目的:生成动态 SQL 语句并执行。
- 入口:
model/m_ssh_log.go方法Search()。 - 动作:
- 初始化查询对象
db.Model(...)。 - 根据非空字段拼接
WHERE条件。 - 调用通用分页工具函数
crudAll完成计数和提取。
func (m SshLogQ) Search() (list *[]SshLog, total uint, err error) {
list = &[]SshLog{}
// 预加载关联数据提升性能
tx := db.Model(m.SshLog).Preload("User").Preload("Machine")
// 动态搜索客户端 IP
if m.ClientIp != "" {
tx = tx.Where("client_ip like ?", "%"+m.ClientIp+"%")
}
// 时间段过滤
if m.FromTime != "" && m.ToTime != "" {
tx = tx.Where("`created_at` BETWEEN ? AND ?", m.FromTime, m.ToTime)
}
// 核心分页查询
total, err = crudAll(&m.PaginationQ, tx, list)
return
}
第5步:封装分页工具函数
目的:统一处理分页逻辑、缺省值修正及 JSON 输出。
逻辑说明:首先对 Page 和 Size 进行边界检查,防止出现负数或极小值;随后分别执行 Count 统计总量和利用 Limit/Offset 截取数据。
func crudAll(p *PaginationQ, queryTx *gorm.DB, list interface{}) (uint, error) {
// 1. 设置默认参数
if p.Size < 1 { p.Size = 10 }
if p.Page < 1 { p.Page = 1 }
// 2. 获取全部搜索数量
var total uint
err := queryTx.Count(&total).Error
if err != nil {
return 0, err
}
// 计算偏移量
offset := p.Size * (p.Page - 1)
// 3. 获取偏移量的数据
err = queryTx.Limit(p.Size).Offset(offset).Find(list).Error
if err != nil {
return 0, err
}
return total, err
}
// 4. 拼装 JSON 分页数据并返回给前端
func jsonPagination(c *gin.Context, list interface{}, total uint, query *model.PaginationQ) {
c.JSON(200, gin.H{
"ok": true,
"data": list,
"total": total,
"page": query.Page,
"size": query.Size
})
}
完成判断与效果展示
当上述代码部署完成后,开发者可使用标准的 RESTful 风格发起 GET 请求。例如:http://localhost:3333/api/ssh-log?page=1&size=10&client_ip=10.18.60.16。预期返回如下格式的 JSON 数据:
{
"data": [
{
"id": 28,
"created_at": "2019-09-12T14:25:54+08:00",
"user_id": 26,
"client_ip": "10.18.60.16",
...
}
],
"ok": true,
"page": 1,
"size": 10,
"total": 1
}
简要总结
本文详细介绍了利用 Gin 强大的路由与参数绑定能力配合 GORM 优秀的查询构造器,实现了一套低维护成本、高扩展性的 RESTful 分页架构。这套模式不仅适用于 SSH 日志,也可广泛复用于各类业务实体列表接口中。
以上就是 GORM + Gin 实现 RESTful 分页接口的详细内容,更多关于 Go API 开发的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













