CursorPagination

Swift Vapor Swift Package Manager License

CursorPagination 是一个用于 Vapor 的 Fluent 库,它允许你使用不透明的光标来分页查询。如果你正在寻找基于偏移量的 Fluent 分页,请查看 Pagination。如果你不确定什么是光标分页,或者你需要光标分页还是偏移量分页,我推荐这篇来自 Slack 团队的精彩 文章,它解释了两者之间的区别以及各自的优缺点。

安装

CursorPagination 可以通过 Swift Package Manager 获得。要安装,请将以下内容添加到你的 Package.swift 文件中。

let package = Package(
    name: "YourProject",
    dependencies: [
        ...
        .package(url: "https://github.com/Appsaurus/CursorPagination", from: "0.1.0"),
    ],
    targets: [
      .target(name: "YourApp", dependencies: ["CursorPagination", ... ])
    ]
)
        

用法

查看此项目中包含的 app 以获取完整的示例。以下是一些基本用法:

1. 导入库

import CusorPagination

2. 扩展你的模型类以采用 CusorPaginatable 协议

你可以简单地声明协议采用并继承默认实现。

extension KitchenSink: CursorPaginatable{}

或者你可以通过实现以下任何静态类变量来设置一些默认配置。

extension KitchenSink: CursorPaginatable{
	public static var defaultPageSorts: [CursorSort<KitchenSink>] {
		return [idKey.descendingSort]
	}
	public static var defaultPageSize: Int {
		return 20
	}

	public static var maxPageSize: Int? {
		return 50
	}
}

3. 设置你的路由

设置路由以返回一个 Future<CursorPage<YourModel>>。当你运行查询时,只需在你的类或 QueryBuilder 上调用 paginate(request: sorts: )

router.get("modelsByDate") { request -> Future<CursorPage<KitchenSink>> in
	return try KitchenSink.paginate(database: Database,
					 sorts: .descending(\.dateField))
}

在你的第一个请求中,省略光标告诉方法你正在从第一页开始。

curl "https://:8080/modelsByDate?limit=5"

结果:

{  
   "remaining":45,
   "data":[  
      {  
         "booleanField":false,
         "id":10,
         "stringField":"Hammes Glen",
         "doubleField":718.52944714909995,
         "dateField":"2102-06-07T12:58:40Z",
         "intField":82
      },
      {  
         "booleanField":false,
         "id":11,
         "stringField":"Stracke Green",
         "doubleField":808.52215080719998,
         "dateField":"2094-08-08T15:32:25Z",
         "intField":714
      },
      {  
         "intField":789,
         "id":30,
         "dateField":"2093-01-24T07:06:53Z",
         "doubleField":992.49123409219999,
         "booleanField":true,
         "stringField":"Schowalter Branch",
         "optionalStringField":"15"
      },
      {  
         "intField":282,
         "id":38,
         "dateField":"2092-03-01T03:59:20Z",
         "doubleField":194.4565969041,
         "booleanField":true,
         "stringField":"Ignacio Springs",
         "optionalStringField":"20"
      },
      {  
         "booleanField":true,
         "id":32,
         "stringField":"Braun Rapid",
         "doubleField":445.48755242620001,
         "dateField":"2078-05-13T15:50:20Z",
         "intField":583
      }
   ],
   "nextPageCursor":"W3sia2V5IjoiZGF0ZUZpZWxkIiwidmFsdWUiOjMzODQ2Nzc4NjgsImRpcmVjdGlvbiI6ImRlc2NlbmRpbmcifSx7ImtleSI6ImlkIiwidmFsdWUiOjQ2LCJkaXJlY3Rpb24iOiJhc2NlbmRpbmcifV0="
}

然后在你的下一个请求中使用 nextPageCursor

curl "https://:8080/modelsByDate?limit=5&cursor=W3sia2V5IjoiZGF0ZUZpZWxkIiwidmFsdWUiOjMzODQ2Nzc4NjgsImRpcmVjdGlvbiI6ImRlc2NlbmRpbmcifSx7ImtleSI6ImlkIiwidmFsdWUiOjQ2LCJkaXJlY3Rpb24iOiJhc2NlbmRpbmcifV0="

{  
   "remaining":40,
   "data":[  
      {  
         "booleanField":false,
         "id":46,
         "stringField":"Aditya Crossroad",
         "doubleField":226.63376066519999,
         "dateField":"2077-04-03T12:17:48Z",
         "intField":149
      },
      {  
         "booleanField":true,
         "id":18,
         "stringField":"Emmitt Ridges",
         "doubleField":90.362315319999993,
         "dateField":"2074-09-14T07:46:27Z",
         "intField":990
      },
      {  
         "booleanField":true,
         "id":27,
         "stringField":"Evelyn Rest",
         "doubleField":371.72649320490001,
         "dateField":"2072-11-21T19:26:05Z",
         "intField":77
      },
      {  
         "intField":351,
         "id":45,
         "dateField":"2071-04-18T00:59:09Z",
         "doubleField":45.889304030200002,
         "booleanField":true,
         "stringField":"Fisher Trail",
         "optionalStringField":"24"
      },
      {  
         "intField":476,
         "id":25,
         "dateField":"2070-04-10T15:16:10Z",
         "doubleField":810.14490844919999,
         "booleanField":false,
         "stringField":"Paucek Plains",
         "optionalStringField":"12"
      }
   ],
   "nextPageCursor":"W3sia2V5IjoiZGF0ZUZpZWxkIiwidmFsdWUiOjMwOTM3NTk5MzcsImRpcmVjdGlvbiI6ImRlc2NlbmRpbmcifSx7ImtleSI6ImlkIiwidmFsdWUiOjYsImRpcmVjdGlvbiI6ImFzY2VuZGluZyJ9XQ=="
}

当没有更多结果时,将不会返回光标。

复合排序

对多个属性进行排序也可以。

注意

为了打破平局,最后一个排序必须是对唯一属性的排序,否则将应用对默认唯一属性(Fluent id)的排序。

router.get("modelsByBooleanAndString") { request -> Future<CursorPage<KitchenSink>> in
	return try KitchenSink.paginate(database: Database,
					 sorts: .descending(\.booleanField), .ascending(\.stringField))
}

动态排序

你可以允许客户端通过查询参数动态地指定结果的排序方式。

⚠️警告

由于动态排序 API 无法从基于字符串的参数解析 KeyPaths,它使用运行时反射来构建光标。在 Swift ABI 稳定之前,你可能不想在生产环境中使用此 API。

像这样设置一个动态可排序的请求处理程序

router.get("dynamicModels") { request -> Future<CursorPage<KitchenSink>> in
	return try KitchenSink.paginate(dynamicdatabase: Database)
}

然后在你的请求中,分别通过 sort[]order[] 参数对每个内容进行排序和排序(顺序很重要):curl "https://:8080/dynamicModels?limit=5&sort[]=booleanField&order[]=descending&sort[]=stringField&order[]=ascending"

待办事项

贡献

我们欢迎你为 CursorPagination 做出贡献,请查看 CONTRIBUTING 文件以获取更多信息。

许可证

CursorPagination 在 MIT 许可下可用。请查看 LICENSE 文件以获取更多信息。