// Package paginator 处理分页逻辑 package paginator import ( "fmt" "math" "strings" "github.com/gin-gonic/gin" "github.com/spf13/cast" "gorm.io/gorm" "gorm.io/gorm/clause" "github.com/runningwater/gohub/pkg/config" "github.com/runningwater/gohub/pkg/logger" ) // Paging 分页数据 type Paging struct { CurrentPage int // 当前页 PageSize int // 每页条数 TotalPage int // 总页数 TotalCount int64 // 总条数 NextPageURL string // 下一页的链接 PrevPageURL string // 上一页的链接 } // Paginator 分页操作类 type Paginator struct { BaseURL string // 用以拼接 URL PageSize int // 每页条数 CurrentPage int // 当前页 Offset int // 数据库读取数据时 Offset 的值 TotalCount int64 // 总条数 TotalPage int // 总页数 = TotalCount/PerPage Sort string // 排序规则 Order string // 排序顺序 query *gorm.DB // db query 句柄 ctx *gin.Context // gin context,方便调用 } // Paginate 分页 // // c —— gin.context 用来获取分页的 URL 参数 // db —— GORM 查询句柄,用以查询数据集和获取数据总数 // baseURL —— 用以分页链接 // data —— 模型数组,传址获取数据 // pageSize —— 每页条数,优先从 url 参数里取,否则使用 perPage 的值 // 用法: // // query := database.DB.Model(Topic{}).Where("category_id = ?", cid) // var topics []Topic // paging := paginator.Paginate( // c, // query, // &topics, // app.APIURL(database.TableName(&Topic{})), // perPage, // ) func Paginate(c *gin.Context, db *gorm.DB, data any, baseURL string, pageSize int) Paging { // 初始化 Paginator 实例 p := &Paginator{ query: db, ctx: c, } p.initProperties(pageSize, baseURL) // 查询数据库 err := p.query.Preload(clause.Associations). // 读取关联 Order(p.Sort + " " + p.Order). // 排序 Limit(p.PageSize). Offset(p.Offset). Find(data). Error // 数据库出错 if err != nil { logger.LogIf(err) return Paging{} } return Paging{ CurrentPage: p.CurrentPage, PageSize: p.PageSize, TotalPage: p.TotalPage, TotalCount: p.TotalCount, NextPageURL: p.getNextPageURL(), PrevPageURL: p.getPrevPageURL(), } } // 初始化分页必须用到的属性,基于这些属性查询数据库 func (p *Paginator) initProperties(pageSize int, baseURL string) { p.BaseURL = p.formatBaseURL(baseURL) p.PageSize = p.getPageSize(pageSize) // 排序参数(控制器中以验证过这些参数,可放心使用) p.Order = p.ctx.DefaultQuery(config.Get("paging.url_query_order"), "asc") p.Sort = p.ctx.DefaultQuery(config.Get("paging.url_query_sort"), "id") p.TotalCount = p.getTotalCount() p.TotalPage = p.getTotalPage() p.CurrentPage = p.getCurrentPage() p.Offset = (p.CurrentPage - 1) * p.PageSize } func (p *Paginator) getPageSize(pageSize int) int { // 优先使用请求 per_page 参数 queryPerpage := p.ctx.Query(config.Get("paging.url_query_per_page")) if len(queryPerpage) > 0 { pageSize = cast.ToInt(queryPerpage) } // 没有传参,使用默认 if pageSize <= 0 { pageSize = config.GetInt("paging.per_page") } return pageSize } // getCurrentPage 返回当前页码 func (p *Paginator) getCurrentPage() int { // 优先取用户请求的 page page := cast.ToInt(p.ctx.Query(config.Get("paging.url_query_page"))) if page <= 0 { // 默认为 1 page = 1 } // TotalPage 等于 0 ,意味着数据不够分页 if p.TotalPage == 0 { return 0 } // 请求页数大于总页数,返回总页数 if page > p.TotalPage { return p.TotalPage } return page } // getTotalCount 返回的是数据库里的条数 func (p *Paginator) getTotalCount() int64 { var count int64 if err := p.query.Count(&count).Error; err != nil { return 0 } return count } // getTotalPage 计算总页数 func (p *Paginator) getTotalPage() int { if p.TotalCount == 0 { return 0 } nums := int64(math.Ceil(float64(p.TotalCount) / float64(p.PageSize))) if nums == 0 { nums = 1 } return int(nums) } // 兼容 URL 带与不带 `?` 的情况 func (p *Paginator) formatBaseURL(baseURL string) string { if strings.Contains(baseURL, "?") { baseURL = baseURL + "&" + config.Get("paging.url_query_page") + "=" } else { baseURL = baseURL + "?" + config.Get("paging.url_query_page") + "=" } return baseURL } // 拼接分页链接 func (p *Paginator) getPageLink(page int) string { return fmt.Sprintf("%v%v&%s=%s&%s=%s&%s=%v", p.BaseURL, page, config.Get("paging.url_query_sort"), p.Sort, config.Get("paging.url_query_order"), p.Order, config.Get("paging.url_query_per_page"), p.PageSize, ) } // getNextPageURL 返回下一页的链接 func (p *Paginator) getNextPageURL() string { if p.TotalPage > p.CurrentPage { return p.getPageLink(p.CurrentPage + 1) } return "" } // getPrevPageURL 返回下一页的链接 func (p *Paginator) getPrevPageURL() string { if p.CurrentPage <= 1 || p.CurrentPage > p.TotalPage { return "" } return p.getPageLink(p.CurrentPage - 1) }