paginator.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. // Package paginator 处理分页逻辑
  2. package paginator
  3. import (
  4. "fmt"
  5. "math"
  6. "strings"
  7. "github.com/gin-gonic/gin"
  8. "github.com/spf13/cast"
  9. "gorm.io/gorm"
  10. "gorm.io/gorm/clause"
  11. "github.com/runningwater/gohub/pkg/config"
  12. "github.com/runningwater/gohub/pkg/logger"
  13. )
  14. // Paging 分页数据
  15. type Paging struct {
  16. CurrentPage int // 当前页
  17. PageSize int // 每页条数
  18. TotalPage int // 总页数
  19. TotalCount int64 // 总条数
  20. NextPageURL string // 下一页的链接
  21. PrevPageURL string // 上一页的链接
  22. }
  23. // Paginator 分页操作类
  24. type Paginator struct {
  25. BaseURL string // 用以拼接 URL
  26. PageSize int // 每页条数
  27. CurrentPage int // 当前页
  28. Offset int // 数据库读取数据时 Offset 的值
  29. TotalCount int64 // 总条数
  30. TotalPage int // 总页数 = TotalCount/PerPage
  31. Sort string // 排序规则
  32. Order string // 排序顺序
  33. query *gorm.DB // db query 句柄
  34. ctx *gin.Context // gin context,方便调用
  35. }
  36. // Paginate 分页
  37. //
  38. // c —— gin.context 用来获取分页的 URL 参数
  39. // db —— GORM 查询句柄,用以查询数据集和获取数据总数
  40. // baseURL —— 用以分页链接
  41. // data —— 模型数组,传址获取数据
  42. // pageSize —— 每页条数,优先从 url 参数里取,否则使用 perPage 的值
  43. // 用法:
  44. //
  45. // query := database.DB.Model(Topic{}).Where("category_id = ?", cid)
  46. // var topics []Topic
  47. // paging := paginator.Paginate(
  48. // c,
  49. // query,
  50. // &topics,
  51. // app.APIURL(database.TableName(&Topic{})),
  52. // perPage,
  53. // )
  54. func Paginate(c *gin.Context, db *gorm.DB, data any, baseURL string, pageSize int) Paging {
  55. // 初始化 Paginator 实例
  56. p := &Paginator{
  57. query: db,
  58. ctx: c,
  59. }
  60. p.initProperties(pageSize, baseURL)
  61. // 查询数据库
  62. err := p.query.Preload(clause.Associations). // 读取关联
  63. Order(p.Sort + " " + p.Order). // 排序
  64. Limit(p.PageSize).
  65. Offset(p.Offset).
  66. Find(data).
  67. Error
  68. // 数据库出错
  69. if err != nil {
  70. logger.LogIf(err)
  71. return Paging{}
  72. }
  73. return Paging{
  74. CurrentPage: p.CurrentPage,
  75. PageSize: p.PageSize,
  76. TotalPage: p.TotalPage,
  77. TotalCount: p.TotalCount,
  78. NextPageURL: p.getNextPageURL(),
  79. PrevPageURL: p.getPrevPageURL(),
  80. }
  81. }
  82. // 初始化分页必须用到的属性,基于这些属性查询数据库
  83. func (p *Paginator) initProperties(pageSize int, baseURL string) {
  84. p.BaseURL = p.formatBaseURL(baseURL)
  85. p.PageSize = p.getPageSize(pageSize)
  86. // 排序参数(控制器中以验证过这些参数,可放心使用)
  87. p.Order = p.ctx.DefaultQuery(config.Get("paging.url_query_order"), "asc")
  88. p.Sort = p.ctx.DefaultQuery(config.Get("paging.url_query_sort"), "id")
  89. p.TotalCount = p.getTotalCount()
  90. p.TotalPage = p.getTotalPage()
  91. p.CurrentPage = p.getCurrentPage()
  92. p.Offset = (p.CurrentPage - 1) * p.PageSize
  93. }
  94. func (p *Paginator) getPageSize(pageSize int) int {
  95. // 优先使用请求 per_page 参数
  96. queryPerpage := p.ctx.Query(config.Get("paging.url_query_per_page"))
  97. if len(queryPerpage) > 0 {
  98. pageSize = cast.ToInt(queryPerpage)
  99. }
  100. // 没有传参,使用默认
  101. if pageSize <= 0 {
  102. pageSize = config.GetInt("paging.per_page")
  103. }
  104. return pageSize
  105. }
  106. // getCurrentPage 返回当前页码
  107. func (p *Paginator) getCurrentPage() int {
  108. // 优先取用户请求的 page
  109. page := cast.ToInt(p.ctx.Query(config.Get("paging.url_query_page")))
  110. if page <= 0 {
  111. // 默认为 1
  112. page = 1
  113. }
  114. // TotalPage 等于 0 ,意味着数据不够分页
  115. if p.TotalPage == 0 {
  116. return 0
  117. }
  118. // 请求页数大于总页数,返回总页数
  119. if page > p.TotalPage {
  120. return p.TotalPage
  121. }
  122. return page
  123. }
  124. // getTotalCount 返回的是数据库里的条数
  125. func (p *Paginator) getTotalCount() int64 {
  126. var count int64
  127. if err := p.query.Count(&count).Error; err != nil {
  128. return 0
  129. }
  130. return count
  131. }
  132. // getTotalPage 计算总页数
  133. func (p *Paginator) getTotalPage() int {
  134. if p.TotalCount == 0 {
  135. return 0
  136. }
  137. nums := int64(math.Ceil(float64(p.TotalCount) / float64(p.PageSize)))
  138. if nums == 0 {
  139. nums = 1
  140. }
  141. return int(nums)
  142. }
  143. // 兼容 URL 带与不带 `?` 的情况
  144. func (p *Paginator) formatBaseURL(baseURL string) string {
  145. if strings.Contains(baseURL, "?") {
  146. baseURL = baseURL + "&" + config.Get("paging.url_query_page") + "="
  147. } else {
  148. baseURL = baseURL + "?" + config.Get("paging.url_query_page") + "="
  149. }
  150. return baseURL
  151. }
  152. // 拼接分页链接
  153. func (p *Paginator) getPageLink(page int) string {
  154. return fmt.Sprintf("%v%v&%s=%s&%s=%s&%s=%v",
  155. p.BaseURL,
  156. page,
  157. config.Get("paging.url_query_sort"),
  158. p.Sort,
  159. config.Get("paging.url_query_order"),
  160. p.Order,
  161. config.Get("paging.url_query_per_page"),
  162. p.PageSize,
  163. )
  164. }
  165. // getNextPageURL 返回下一页的链接
  166. func (p *Paginator) getNextPageURL() string {
  167. if p.TotalPage > p.CurrentPage {
  168. return p.getPageLink(p.CurrentPage + 1)
  169. }
  170. return ""
  171. }
  172. // getPrevPageURL 返回下一页的链接
  173. func (p *Paginator) getPrevPageURL() string {
  174. if p.CurrentPage <= 1 || p.CurrentPage > p.TotalPage {
  175. return ""
  176. }
  177. return p.getPageLink(p.CurrentPage - 1)
  178. }