关于 pagination
为开发者提供 API 分页实现参考,比较游标、偏移和键集分页,并涵盖过滤、排序及生产实践。
主要功能
- 01游标、偏移与键集分页模式
- 02过滤和排序示例
- 03页大小与输入验证
- 04生产检查清单
适用场景
- 01为大型数据集选择分页方式
- 02为管理端点添加分页
- 03检查分页边界情况
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill pagination安装后在 Claude Code 中输入/pagination确认已加载。
详细介绍
分页
基于游标(推荐用于大型数据集)
app.get('/api/products', async (req, res) => {
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const cursor = req.query.cursor as string | undefined;
const where: any = {};
if (cursor) {
where.id = { gt: cursor };
}
const items = await db.product.findMany({
where,
take: limit + 1, // Fetch one extra to check hasMore
orderBy: { id: 'asc' },
});
const hasMore = items.length > limit;
if (hasMore) items.pop();
res.json({
data: items,
pagination: {
hasMore,
nextCursor: hasMore ? items[items.length - 1].id : null,
},
});
});
基于偏移量(简单,适合小型数据集)
app.get('/api/products', async (req, res) => {
const page = Math.max(parseInt(req.query.page as string) || 1, 1);
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const offset = (page - 1) * limit;
const [items, total] = await Promise.all([
db.product.findMany({ skip: offset, take: limit, orderBy: { createdAt: 'desc' } }),
db.product.count(),
]);
res.json({
data: items,
pagination: {
page, limit, total,
totalPages: Math.ceil(total / limit),
hasMore: offset + items.length < total,
},
});
});
筛选和排序
app.get('/api/products', async (req, res) => {
const { sort = 'createdAt', order = 'desc', category, minPrice, maxPrice, search } = req.query;
const where: any = {};
if (category) where.category = category;
if (minPrice || maxPrice) {
where.price = {};
if (minPrice) where.price.gte = parseFloat(minPrice as string);
if (maxPrice) where.price.lte = parseFloat(maxPrice as string);
}
if (search) where.name = { contains: search, mode: 'insensitive' };
const items = await db.product.findMany({
where,
orderBy: { [sort as string]: order },
take: limit,
skip: offset,
});
res.json({ data: items, pagination: { /* ... */ } });
});
Spring Boot(Pageable)
@GetMapping("/products")
public Page<ProductDto> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "createdAt,desc") String[] sort) {
Pageable pageable = PageRequest.of(page, Math.min(size, 100),
Sort.by(Sort.Direction.fromString(sort[1]), sort[0]));
return productRepo.findAll(pageable).map(mapper::toDto);
}
对比
| 策略 | 优点 | 缺点 | 最适用于 |
|---|---|---|---|
| 偏移量 | 简单,可跳转到指定页 | 大型表中速度慢,跳过的数据会漂移 | 管理面板、小型数据集 |
| 游标 | 速度快,插入数据时结果稳定 | 无法跳转到第 N 页 | 信息流、无限滚动、大型数据集 |
| 键集 | 速度快,不会出现跳过的数据漂移 | 多列排序较复杂 | 时间序列、有序数据 |
反模式
| 反模式 | 修正方法 |
|---|---|
| 未限制最大页面大小 | 限制 limit(例如,最大为 100) |
| 在超大表上使用 COUNT(*) | 使用游标分页,跳过总数统计 |
| 在数百万行数据上使用偏移量 | 使用游标或键集分页 |
| 返回所有字段 | 仅选择所需字段,支持 fields 参数 |
| 未设置默认排序 | 始终定义默认排序,以确保结果稳定 |
生产环境检查清单
- 强制限制最大页面大小(例如,100)
- 定义默认排序顺序
- 对大型或持续增长的数据集使用游标分页
- 验证 page/limit/sort 参数
- 使用一致的响应封装(
data、pagination)
评论
支持 Markdown