在提供列表接口时一般要用到分页,对于存储在某些数据库中的数据进行分页起来非常的方便,下文给出一个通过gorm进行分页并通过http返回数据的例子。
gorm官方文档地址:https://gorm.io/zh_CN/docs/,库下载如下:
go get -u gorm.io/gorm
本文使用的数据库是mysql,因此要下载mysql驱动:
go get -u gorm.io/driver/mysql
这里使用gin框架来提供查询列表的接口想,库下载如下:
go get -u github.com/gin-gonic/gin
建一个用来测试分页的表,sql如下:
CREATE TABLE test_page
(
id INT AUTO_INCREMENT PRIMARY KEY,
number INT NOT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT '测试分页表';
插入30条number和create_time字段不同的数据,sql如下:
INSERT INTO test_page (number, create_time)
VALUES (1, NOW() + INTERVAL 0 SECOND),
(2, NOW() + INTERVAL 1 SECOND),
(3, NOW() + INTERVAL 2 SECOND),
(4, NOW() + INTERVAL 3 SECOND),
(5, NOW() + INTERVAL 4 SECOND),
(6, NOW() + INTERVAL 5 SECOND),
(7, NOW() + INTERVAL 6 SECOND),
(8, NOW() + INTERVAL 7 SECOND),
(9, NOW() + INTERVAL 8 SECOND),
(10, NOW() + INTERVAL 9 SECOND),
(11, NOW() + INTERVAL 10 SECOND),
(12, NOW() + INTERVAL 11 SECOND),
(13, NOW() + INTERVAL 12 SECOND),
(14, NOW() + INTERVAL 13 SECOND),
(15, NOW() + INTERVAL 14 SECOND),
(16, NOW() + INTERVAL 15 SECOND),
(17, NOW() + INTERVAL 16 SECOND),
(18, NOW() + INTERVAL 17 SECOND),
(19, NOW() + INTERVAL 18 SECOND),
(20, NOW() + INTERVAL 19 SECOND),
(21, NOW() + INTERVAL 20 SECOND),
(22, NOW() + INTERVAL 21 SECOND),
(23, NOW() + INTERVAL 22 SECOND),
(24, NOW() + INTERVAL 23 SECOND),
(25, NOW() + INTERVAL 24 SECOND),
(26, NOW() + INTERVAL 25 SECOND),
(27, NOW() + INTERVAL 26 SECOND),
(28, NOW() + INTERVAL 27 SECOND),
(29, NOW() + INTERVAL 28 SECOND),
(30, NOW() + INTERVAL 29 SECOND);
查看创建出来的30条数据:
mysql [xxx]> select * from test_page;
+----+--------+---------------------+---------------------+
| id | number | create_time | update_time |
+----+--------+---------------------+---------------------+
| 1 | 1 | 2024-10-21 07:11:19 | 2024-10-21 07:11:19 |
| 2 | 2 | 2024-10-21 07:11:20 | 2024-10-21 07:11:19 |
| 3 | 3 | 2024-10-21 07:11:21 | 2024-10-21 07:11:19 |
| 4 | 4 | 2024-10-21 07:11:22 | 2024-10-21 07:11:19 |
| 5 | 5 | 2024-10-21 07:11:23 | 2024-10-21 07:11:19 |
| 6 | 6 | 2024-10-21 07:11:24 | 2024-10-21 07:11:19 |
| 7 | 7 | 2024-10-21 07:11:25 | 2024-10-21 07:11:19 |
| 8 | 8 | 2024-10-21 07:11:26 | 2024-10-21 07:11:19 |
| 9 | 9 | 2024-10-21 07:11:27 | 2024-10-21 07:11:19 |
| 10 | 10 | 2024-10-21 07:11:28 | 2024-10-21 07:11:19 |
| 11 | 11 | 2024-10-21 07:11:29 | 2024-10-21 07:11:19 |
| 12 | 12 | 2024-10-21 07:11:30 | 2024-10-21 07:11:19 |
| 13 | 13 | 2024-10-21 07:11:31 | 2024-10-21 07:11:19 |
| 14 | 14 | 2024-10-21 07:11:32 | 2024-10-21 07:11:19 |
| 15 | 15 | 2024-10-21 07:11:33 | 2024-10-21 07:11:19 |
| 16 | 16 | 2024-10-21 07:11:34 | 2024-10-21 07:11:19 |
| 17 | 17 | 2024-10-21 07:11:35 | 2024-10-21 07:11:19 |
| 18 | 18 | 2024-10-21 07:11:36 | 2024-10-21 07:11:19 |
| 19 | 19 | 2024-10-21 07:11:37 | 2024-10-21 07:11:19 |
| 20 | 20 | 2024-10-21 07:11:38 | 2024-10-21 07:11:19 |
| 21 | 21 | 2024-10-21 07:11:39 | 2024-10-21 07:11:19 |
| 22 | 22 | 2024-10-21 07:11:40 | 2024-10-21 07:11:19 |
| 23 | 23 | 2024-10-21 07:11:41 | 2024-10-21 07:11:19 |
| 24 | 24 | 2024-10-21 07:11:42 | 2024-10-21 07:11:19 |
| 25 | 25 | 2024-10-21 07:11:43 | 2024-10-21 07:11:19 |
| 26 | 26 | 2024-10-21 07:11:44 | 2024-10-21 07:11:19 |
| 27 | 27 | 2024-10-21 07:11:45 | 2024-10-21 07:11:19 |
| 28 | 28 | 2024-10-21 07:11:46 | 2024-10-21 07:11:19 |
| 29 | 29 | 2024-10-21 07:11:47 | 2024-10-21 07:11:19 |
| 30 | 30 | 2024-10-21 07:11:48 | 2024-10-21 07:11:19 |
+----+--------+---------------------+---------------------+
可以根据在线sql转go结构体来实现,转换之后如下:
package models
import (
"time"
)
type TestPage struct {
Id int `gorm:"column:id;type:int(11);AUTO_INCREMENT;primary_key" json:"id"`
Number int `gorm:"column:number;type:int(11);NOT NULL" json:"number"`
CreateTime time.Time `gorm:"column:create_time;type:datetime;default:CURRENT_TIMESTAMP" json:"create_time"`
UpdateTime time.Time `gorm:"column:update_time;type:datetime;default:CURRENT_TIMESTAMP" json:"update_time"`
}
func (m *TestPage) TableName() string {
return "test_page"
}
package types
import "GoTest/gorm_demo/models"
type PageReq struct {
Page int `form:"page,default=1"`
PageSize int `form:"page_size,default=4"`
}
type PageResult struct {
TotalPage int `json:"total_page"` //总页数
TotalCount int `json:"total_count"` //总条数
Page int `json:"page"` //当前页
PageSize int `json:"page_size"` //当前页数据量
PrevPage bool `json:"prev_page"` //是否存在上页
NextPage bool `json:"next_page"` //是否存在下页
}
//要测试的分页结构
type TestPageResp struct {
PageResult
List []models.TestPage `json:"list"`
}
package page
import (
"math"
"GoTest/gorm_demo/types"
)
// GetOffsetAndLimit
//
// @Description: 获取查数据库的偏移量和限制数
// @param page 当前页
// @param pageSize 业数据量
// @return offset 数据库偏移
// @return limit 数据库限制量
func GetOffsetAndLimit(page, pageSize int) (offset, limit int) {
limit = pageSize
if limit <= 0 {
limit = 4
}
if page > 1 {
offset = (page - 1) * limit
}
return
}
// GetPageResult
//
// @Description: 获取分页结果
// @param pg 当前页
// @param pageSize 页数据量
// @param totalCount 根据此数量进行分页
// @return types.PageResult 分页结构
func GetPageResult(pg, pageSize, totalCount int) types.PageResult {
res := types.PageResult{}
res.TotalCount = totalCount
res.PageSize = pageSize
res.Page = pg
if pageSize > 0 && totalCount > 0 {
res.TotalPage = int(math.Ceil(float64(totalCount) / float64(pageSize)))
}
if res.Page < res.TotalPage {
res.NextPage = true
}
if res.Page > 1 {
res.PrevPage = true
}
return res
}
使用gorm连接mysql:
package conn
import (
"fmt"
"runtime"
"sync"
"time"
"GoTest/comm/logger"
"go.uber.org/zap"
"gorm.io/driver/mysql"
"gorm.io/gorm"
gormLog "gorm.io/gorm/logger"
)
type MysqlDb string
const (
Xxx MysqlDb = "xxx"
)
var (
mysqlConn = map[MysqlDb]*gorm.DB{}
mysqlLock = sync.RWMutex{}
)
func GetXxxDbConn() *gorm.DB {
return getDbPool(Xxx)
}
func getDbPool(dbKey MysqlDb) *gorm.DB {
mysqlLock.RLock()
if conn, ok := mysqlConn[dbKey]; ok {
mysqlLock.RUnlock()
return conn
}
mysqlLock.RUnlock()
mysqlLock.Lock()
defer mysqlLock.Unlock()
var dsn string
switch dbKey {
case Xxx:
dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=5s", "xxx", "xxx", "127.0.0.1:3306", "xxx")
default:
logger.Error("unknown db", zap.String("db", string(dbKey)))
return nil
}
conn := newConnect(dsn)
mysqlConn[dbKey] = conn
return conn
}
func newConnect(dsn string) *gorm.DB {
conn, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: gormLog.Default.LogMode(gormLog.Info),
})
if err != nil {
logger.Error("connect mysql error", zap.String("dsn", dsn), zap.Error(err))
return nil
}
dbPool, err := conn.DB()
if err != nil {
logger.Error("get mysql db error", zap.String("dsn", dsn), zap.Error(err))
return nil
}
logger.Info("conn mysql success")
//连接池中中最大空闲时间
dbPool.SetConnMaxIdleTime(time.Hour)
//连接池中空闲连接的最大数量
dbPool.SetMaxIdleConns(runtime.NumCPU() * 5)
//数据库连接的最大数量
dbPool.SetMaxOpenConns(runtime.NumCPU() * 10)
//连接可复用最大时间
dbPool.SetConnMaxLifetime(time.Hour * 24)
return conn
}
使用时只需要调用GetXxxDbConn函数获取连接即可。
使用gin框架启动一个http服务,并注册一条路由用于查询列表接口:
r := gin.New()
r.GET("/page/list", GetPageListHandler)
if err := r.Run(":8888"); err != nil {
panic(err)
}
查询列表接口回调函数实现如下:
func GetPageListHandler(c *gin.Context) {
var req types.PageReq
if err := c.ShouldBind(&req); err != nil {
logger.Error("invalid req", zap.Error(err))
c.String(500, "invalid req")
return
}
logger.Info("get page list req", zap.Any("req", req))
db := conn.GetXxxDbConn()
resp := types.TestPageResp{}
resp.List = make([]models.TestPage, 0)
//获取所有数量
var count int64
if err := db.Model(&models.TestPage{}).Count(&count).Error; err != nil {
logger.Error("get count error", zap.Error(err))
c.String(500, "get count error")
return
}
//计算分页数据
resp.PageResult = page.GetPageResult(req.Page, req.PageSize, int(count))
//计算偏移量
offset, limit := page.GetOffsetAndLimit(req.Page, req.PageSize)
//数据库查询
if err := db.Model(&models.TestPage{}).Order("create_time DESC").Offset(offset).Limit(limit).Find(&resp.List).Error; err != nil {
logger.Error("get list error", zap.Error(err))
c.String(500, "get list error")
return
}
//返回数据
c.JSON(http.StatusOK, resp)
}
控制台输出如下:
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /page/list --> main.GetPageListHandler (1 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8888
这里使用的是gin框架的debug模式,生产环境中不要用gin框架的debug模式,会影响程序的qps。
使用curl命令来调用,获取第2页,页数据量为6条的数据,控制台输出如下:
[xxx@xxx ~]# curl -v -X GET --location "http://127.0.0.1:8888/page/list?page=2&page_size=6"
* About to connect() to 127.0.0.1 port 8888 (#0)
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8888 (#0)
> GET /page/list?page=2&page_size=6 HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1:8888
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Mon, 21 Oct 2024 09:23:13 GMT
< Content-Length: 735
<
* Connection #0 to host 127.0.0.1 left intact
{
"total_page": 5, //总共有5页
"total_count": 30, //根据30条数据进行分页
"page": 2, //当前页为第2页
"page_size": 6, //每一页的大小为6
"prev_page": true, //上一页存在
"next_page": true, //下一页存在
"list": [ //根据创建时间倒序的列表
{
"id": 24,
"number": 24,
"create_time": "2024-10-21T07:11:42+08:00",
"update_time": "2024-10-21T07:11:19+08:00"
},
{
"id": 23,
"number": 23,
"create_time": "2024-10-21T07:11:41+08:00",
"update_time": "2024-10-21T07:11:19+08:00"
},
{
"id": 22,
"number": 22,
"create_time": "2024-10-21T07:11:40+08:00",
"update_time": "2024-10-21T07:11:19+08:00"
},
{
"id": 21,
"number": 21,
"create_time": "2024-10-21T07:11:39+08:00",
"update_time": "2024-10-21T07:11:19+08:00"
},
{
"id": 20,
"number": 20,
"create_time": "2024-10-21T07:11:38+08:00",
"update_time": "2024-10-21T07:11:19+08:00"
},
{
"id": 19,
"number": 19,
"create_time": "2024-10-21T07:11:37+08:00",
"update_time": "2024-10-21T07:11:19+08:00"
}
]
}
此时gin程序控制台输出:
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /page/list --> main.GetPageListHandler (1 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8888
[2024-10-21 17:23:13.435] | INFO | Goroutine:34 | [gorm_demo/main.go:131] | get page list req | {"req": {"Page":2,"PageSize":6}} //分页请求数据
[2024-10-21 17:23:13.437] | INFO | Goroutine:34 | [conn/mysql.go:74] | conn mysql success
2024/10/21 17:23:13 D:/GoTest/gorm_demo/main.go:139
[0.640ms] [rows:1] SELECT count(*) FROM `test_page` //查询所有数量的sql
2024/10/21 17:23:13 D:/GoTest/gorm_demo/main.go:152
[0.325ms] [rows:6] SELECT * FROM `test_page` ORDER BY create_time DESC LIMIT 6 OFFSET 6 //根据分页请求计算出的偏移位置和限制量sql
上面打印了gorm库中的日志信息,可以通过自定义zap对象来实现gorm的logger对象来自定义gorm输出日志。
分页的方法有很多,上面只是给出了其中一种,根据自己项目的业务场景选择一种最喜欢的方法就行。