php api分页首选游标(cursor)而非page/limit,因其基于唯一有序字段避免漏数跳页;安全cursor需签名+载荷+过期时间组合,防篡改重放;且仅短期有效,须明确告知前端并妥善处理null、解码失败、软删除等边界情况。

PHP API分页用什么令牌?cursor 是首选,不是 page 和 limit
传统 page=3&limit=20 在高并发或数据动态增删时容易漏数、重复或跳页,API 分页应优先用游标(cursor)——它基于上一页最后一条记录的唯一、有序字段(如 id 或 created_at),服务端只返回下一页的起始标记。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
-
cursor值必须是 URL 安全编码的(如base64_encode("12345")),不能直接暴露原始主键或时间戳 - 首次请求不带
cursor,服务端默认从第一条开始;后续请求必须携带上一页响应中的next_cursor - 不要用
offset做游标,它随删除/插入漂移,cursor必须绑定不可变排序字段值 - 前端需忽略
cursor内容,只透传;后端解码后严格校验格式和长度(防注入或越界)
怎么生成安全的 cursor 字符串?
安全的 cursor 不是简单 base64 编码 ID,而是「签名 + 有效载荷 + 过期时间」组合,防止篡改和重放。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 构造载荷数组:
['id' => 12345, 'ts' => time(), 'exp' => time() + 300](5 分钟过期) - 用
hash_hmac('sha256', json_encode($payload), $secret_key)签名,拼接成$payload_b64 . '.' . $signature_b64 - 解码时先分割,验证签名,再检查
exp是否过期、id是否为正整数、是否在合理范围(如不超过最大 ID 的 2 倍) - 避免把数据库密码、密钥硬编码进逻辑;用
$_ENV['CURSOR_SECRET']或配置中心加载
为什么不能把 cursor 当作永久分页句柄?
cursor 本质是临时快照锚点,不是数据库游标。底层数据变更、索引重建、归档策略都可能让旧 cursor 失效。
常见错误现象:
- 用户下拉到底后停留 10 分钟再点“加载更多”,返回
400 Bad Request或空结果 - 后台执行了大批量
DELETE,导致某段cursor对应的记录已不存在,查询无匹配行 - 同一
cursor多次请求返回不同结果(因新插入记录挤入排序位置)
所以必须明确告知前端:游标仅短期有效;服务端要返回 next_cursor 或 null(表示末页),而不是尝试“恢复”失效游标。
PHP 实现里最容易被忽略的三个细节
真正上线后出问题的,往往不是逻辑主干,而是这几个边界点:
- MySQL 排序字段含
NULL:用ORDER BY created_at DESC NULLS LAST(PostgreSQL 支持),但 MySQL 不支持NULLS LAST,得改写为ORDER BY ISNULL(created_at), created_at DESC - 游标解码失败时,别直接
throw new Exception(),应统一返回400+{"error": "invalid_cursor"},避免泄露内部错误栈 - 当用户传入的
cursor指向的记录已被软删除(is_deleted = 1),查询要跳过它并找下一个有效记录,否则会卡死在“不可见”的断点上
游标分页的安全性和稳定性,不在加密多强,而在对数据一致性、时效性、异常路径的诚实处理。越想“无缝续传”,越要接受“断点可丢”。











