接口状态化设计--游标分页的优雅方案

什么是有状态接口(Stateful)

特征

  • 服务端保存会话或上下文
  • 当前请求依赖之前的请求结果
  • 扩展复杂,需要会话同步或粘性会话

例子 1:登录态

1. POST /login  -> 服务端生成 session_id
2. GET /profile -> 依赖 session_id 查用户

● session 存在服务端(内存 / redis)

● 没有之前的登录状态,请求会失败

例子 2:游标式分页

3. GET /items -> 返回 items + cursor
4. GET /items?cursor=xxx -> 基于上一次结果继续查

● cursor 对应服务端保存的查询上下文

● 请求顺序不能乱

有状态接口设计

大部分接口都应该是无状态的,每次请求都是独立的,这样利于接口在不同场景都可以灵活调用。但有些情况必须联系上下文进行处理,这就要把接口改为“有状态”。

想象一个最简单的场景:提供一个接口,获取一个会根据用户或端口的不同有所变化的列表数据,接口需要分页返回,但是每次请求需要返回的数据必须之前没出现过。

前提条件:

  • 数据量极大,后端无法预存全量数据并主动过滤已返回内容;
  • 数据动态变化,每次请求返回的数据集不一致;
  • 需保证分页结果无重复。

菜鸟方案:前端携带全量历史ID

一个简单的思路是让前端保存每次返回数据的ids,调用接口时携带,形成一个“游标”,后端可以根据历史id排除。

但这显然是菜鸟做法!因为这样做就把状态存储的职责放给了前端,这是不可控的,前端可能因为各种原因丢失这个游标,并且依赖太长,需要保存历史每一次请求,最好的情况应该是只和上一次请求有关。更重要的是,如果后续有需求扩展,比如列表数据的型号,价格等等信息也要去重,前端就要重复维护新的历史数据,加上新的字段,接口无限臃肿膨胀。

  • 需持续存储全量历史ID,数据量越大,存储/传输成本越高;
  • 前端可能因缓存清理、页面刷新丢失历史ID,导致重复数据;
  • 若需按“型号、价格”等多维度去重,前端需同步维护多类历史数据,接口参数无限膨胀。

优雅方案:后端生成next_rule

更好的做法是后端接管状态存储,每次查询后,根据本次返回数据的特征(ID/型号/价格等) 构造一个next_rule(下一次查询的过滤规则),前端只需在下次请求时回传该next_rule,后端即可基于规则过滤已返回数据。

这样每次请求只和上一次请求强依赖,且每次执行都是可复现可回溯的。更重要的是将后端作为游标携带者,存储接口的状态,这在接口的安全性和后续接口的拓展上等方面都是更加工程化更可控的更优解。

  • 只需存储上一次的next_rule,无需关心规则内容,传输/存储成本固定;
  • 后端掌握所有过滤逻辑,即使前端丢失next_rule,也可通过规则设计实现“断点续传”;
  • 若需新增去重维度(如价格),仅需后端修改next_rule的生成逻辑,前端无需任何改动,接口参数始终简洁。

next_rule的具体设计示例

next_rule本质是后端加密/序列化后的“查询上下文”,无需前端理解,只需透传。

调用流程示例

前端请求后端处理后端返回
GET /api/data无排除ID,查询前10条数据data: [id1-id10], next_rule: xxx
GET /api/data?next_rule=xxx解析规则,排除id1-id10,查询id11-id20data: [id11-id20], next_rule: yyy
GET /api/data?next_rule=yyy解析规则,排除id11-id20,查询id21-id30data: [id21-id30], next_rule: zzz

将分页去重的状态从前端转移到后端,通过next_rule透传上下文,前端仅需“无脑回传”,大幅降低前端复杂度和状态丢失风险;

这样也实现了高拓展,next_rule封装了所有过滤逻辑,新增去重维度(型号、价格等)时,仅需后端修改规则生成逻辑,接口参数无需变更;

后端掌控状态,可实现规则签名验证(防篡改)、版本兼容(规则升级)、断点续传(状态可回溯),是更可控、更优雅的有状态接口设计方案。