老板让我手搓游标………………

最后让老板帮我搓,反正我搓不出来……………

游标实现


主要用于分页查询

目前主流的分页模式有两种——基于偏移量的分页基于游标的分页。

基于偏移量的分页:

1
SELECT * FROM metric_type WHERE type_name LIKE %name% ORDERBY id LIMIT offset,limit

客户端需要提供本次请求每页所需的结果数(limit)和偏移量(offset),偏移量通常由服务端通过page和size计算得出。这种分页方式十分简单,只需跳过前面Offset指定的结果数,按需返回Limit个结果数就可以了,它很容易与数据库查询语句对应。

你有100本书,每一页展示10本,那么基于偏移量的分页方式如下。

  • 第一页:Offset:0, Limit:10
  • 第二页:Offset:10, Limit:10
  • 第三页:Offset:20, Limit:10
  • 第十页:Offset:90, Limit:10

我们可以写出查询第二页图书的SQL:

1
SELECT id, title FROM books ORDER BY id ASC LIMIT 10 OFFSET 10;

基于游标的分页:

基于游标的分页是指接口在返回响应数据的同时返回一个cursor——通常是一个不透明字符串。它表示的是这一页数据的最后那个元素(这就像是我们玩单机游戏的存档点,这一次我们从这里离开,下一次将从这里继续),通过这个cursor API 就能准确的返回下一页的数据。

用于游标的字段必须是唯一的、连续的列,数据集将基于该列进行排序。在处理实时数据时使用基于游标的分页。第一页请求不需要提供游标,但是后续的请求必须携带游标。

你有100本书,每一页展示10本,那么基于游标的分页方式如下。

  • 第一页:Limit:10
  • 第二页:cursor:10, Limit:10
  • 第三页:cursor:20, Limit:10
  • 第十页:cursor:90, Limit:10

查询第二页图书的SQL可能是:

1
SELECT id, title FROM books WHERE id > 10 ORDER BY id ASC LIMIT 10;

简单点说就是,游标有个一个指针是指向了数据库之前遍历到的地方

误区:

​ 刚刚我开始写游标的时候,一直觉得都是应该全程后端来完成指针的移动保存以及相应的切片返回,就导致一个问题就是如果切片返回就会中断函数,指针就不能保存了,本来想设立全局变量,后来觉得老板的项目,全局变量不能随便设立。。。。。

​ 真正的策略是把指针交给前端,然后再下一次访问路由的时候,把之前的指针交还给后端,后端继续遍历。太秒了

代码呈现


主要实现了一个ProjectUserList 返回的功能(主要看游标实现)

交互结构

1
2
3
4
5
6
7
8
9
10
type StaffListResponse struct {
List []StaffListItem `json:"list"`
NextId *uint `json:"nextId,omitempty"`
}
type StaffListItem struct {
ID uint `json:"id"`
Batch *string `json:"batch"`
StaffId string `json:"staffId"`
Taints datatypes.JSONSlice[string] `json:"taints"`
}

注意这里的NextId,就是交互的部分

前端交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func HandleProjectStaffList(r flamego.Render, c flamego.Context, auth auth.Info) {
projectId := c.Param("id")
staffId := c.Query("staffId")
limit := c.QueryInt("limit", 50)
startId := c.QueryInt("startId", 0)

if !rbac.CheckStaffProjectPermission(auth.StaffId, projectId, "staff", "read") {
response.Forbidden(r)
return
}

data, nextId, err := dao.Project.GetStaffs(projectId, staffId, limit, int64(startId))
if err != nil {
response.ServiceErr(r, err)
return
}
var resp dto.StaffListResponse
copier.Copy(&resp.List, &data)
resp.NextId = nextId
response.HTTPSuccess(r, resp)
}

游标操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (u *project) GetStaffs(projectId string, staffId string, limit int, startId int64) ([]model.ProjectUser, *uint, error) {
var users []model.ProjectUser
err := u.DB.Model(&model.ProjectUser{}).
Where(" project_id = ?", projectId).
Scopes(func(db *gorm.DB) *gorm.DB {
if len(staffId) > 0 {
db.Where("staff_id LIKE ?", "%"+staffId+"%")
}
if startId > 0 {
db.Where("id >= ?", startId)
}
return db
}).Limit(limit + 1).
Find(&users).Error
if err != nil {
return nil, nil, err
}
if len(users) > limit {
return users[:len(users)-1], &users[len(users)-1].ID, nil
}
return users, nil, nil
}

这里使用了子查询的方法

传入页面的呈现条数 limit

startId 开始遍历的指针

总结


基于游标的分页 | 李文周的博客 (liwenzhou.com)—-感谢李文周老师的博客帮助

昨天辛辛苦苦写的代码–has many关系,后来被老板认为Taint没必要存在,然后代码就简单了很多,真尼玛啊。

明天看看v3的代码

我啥时候去考科目四,好问题。。