不能直接用page和size做简单范围校验,因offset=(page-1)*size可能溢出或超数据库承载能力,且pagehelper等插件对integer.max_value有特殊语义(如触发全表扫描),需拦截page==1&&size==integer.max_value等高危组合。

为什么不能直接用 page 和 size 做关系运算就放行
很多开发者看到“页码大于 0”“每页条数在 1–100 之间”就写 if (page 100),然后 return error。这看似合理,但漏掉了两个关键事实:
一是 page 和 size 会参与计算偏移量 offset = (page - 1) * size,而这个乘积可能溢出或远超数据库承载能力;二是某些分页插件(如 PageHelper)对 Integer.MAX_VALUE 有特殊语义(比如触发全量返回),此时仅做范围判断反而绕过了校验逻辑。
PageHelper 场景下必须拦截的非法组合
PageHelper 的 startPage(page, size) 并非只校验单个参数,而是依赖 Page 构造函数中的隐式逻辑。以下组合必须在进入拦截器前拦截:
-
page == 1 && size == Integer.MAX_VALUE:会被识别为pageSizeZero模式,跳过物理分页,等价于全表扫描——高风险行为,应拒绝 page 或 <code>size :MyBatis-PageHelper 虽会 fallback 到默认值,但属于未定义行为,容易掩盖前端传参错误-
(page - 1L) * size > 1000000L:防止深度分页导致 offset 过大,MySQL 在LIMIT 1000000,20级别已明显变慢,建议硬性限制乘积上限
Spring Data JPA 中 Pageable 的关系校验陷阱
Spring Data JPA 的 PageRequest.of(page, size) 默认以 0 起始页码,但开发者常把前端传的“第 1 页”直接塞进 page 参数,导致实际查第 0 页(空结果)。更危险的是,size 若为 0,JPA 不报错,但生成的 SQL 可能变成 LIMIT 0,返回空列表却不提示异常。
推荐在校验层统一转换并约束:
- 将前端传的
pageNo(从 1 开始)转为page(从 0 开始):int page = Math.max(0, pageNo - 1) - 强制
size落入 [1, 100] 区间,超出则截断并记录 warn 日志(避免静默失败) - 拒绝
page > 1000:超过 1000 页基本意味着用户在刷数据或爬虫试探,应限流或拒绝
游标分页(cursor + limit)时如何用关系运算预筛
游标分页不依赖页码,但 limit 和游标值之间的关系更敏感。例如 Dify 的 offset + limit 组合,若 limit 为负数或 0,会导致查询逻辑失效;若 offset 本身是字符串(如时间戳游标),却用数值比较(offset > 0),会抛 NumberFormatException。
正确做法是先类型解析,再关系判断:
- 尝试将游标解析为
Long或Instant,失败则立即拒绝请求 -
limit必须满足limit >= 1 && limit (业务允许的最大单次拉取量) - 若游标是时间戳,需确保
cursor >= Instant.now().minusSeconds(3600 * 24 * 7),防止查过期快照引发索引失效
真正难的不是写几个 > 或 ,而是搞清每个参数在下游框架里会被怎么解释、在哪一步塌缩成 SQL —— 校验点必须卡在框架开始“信任”这些值之前。










