瀏覽代碼

feat: paginator 分页功能

runningwater 6 月之前
父節點
當前提交
5d6a0da021
共有 2 個文件被更改,包括 224 次插入0 次删除
  1. 21 0
      config/paging.go
  2. 203 0
      pkg/paginator/paginator.go

+ 21 - 0
config/paging.go

@@ -0,0 +1,21 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/7/15 10:44
+// Desc: 分页设置
+
+package config
+
+import (
+	"github.com/runningwater/gohub/pkg/config"
+)
+
+func init() {
+	config.Add("paging", func() map[string]any {
+		return map[string]any{
+			"per_page":           10,         // 每页条数
+			"url_query_page":     "page",     // URL 中每页条数的参数
+			"url_query_sort":     "sort",     // URL 中排序的参数
+			"url_query_order":    "order",    // URL 中排序顺序的参数
+			"url_query_per_page": "per_page", // URL 中页码的参数
+		}
+	})
+}

+ 203 - 0
pkg/paginator/paginator.go

@@ -0,0 +1,203 @@
+// 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)
+}