黑马点评商铺分页查询异常排查与修复
在开发过程中遇到了一个棘手的前端交互问题。页面中选择美食类别后往下翻,无法自动滚动分页查询所有的美食店铺数据,前端滚动没反应。

还有个比较诡异的点:前端点击'距离'排序后,滚动查询第一页时数据被查询了两次,后续翻页则正常。如下图所示。
排查许久后,定位到根源在于后端分页配置的大小设定。我的后端代码如下:
Controller 层
/**
* 根据商铺类型分页查询商铺信息
* @param typeId 商铺类型
* @param current 页码
* @param x 经度
* @param y 纬度
* @return 商铺列表
*/
@GetMapping("/of/type")
public Result queryShopByType(
@RequestParam("typeId") Integer typeId,
@RequestParam(value = "current", defaultValue = "1") Integer current,
@RequestParam("x") Double x,
@RequestParam("y") Double y
) {
// 调用 Service 层方法(修正方法名驼峰规范)
return shopService.queryShopByType(typeId, current, x, y);
}
ServiceImpl 层
@Override
public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
if (x == null || y == null) {
// 直接返回对应类型的店铺就行了
Page<Shop> page = query()
.eq("type_id", typeId)
.page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
return Result.ok(page.getRecords());
}
int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
String key = "geo:shop:type:" + typeId;
// 构造 5000 米的距离对象(核心:指定米为单位,5000m)
Distance distance = new Distance(5000, Metrics.METERS);
// 完整的 GEO 圆形区域查询(补全所有参数)
GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = stringRedisTemplate.opsForGeo()
.search(key,
GeoReference.fromCoordinate(new Point(x, y)),
distance,
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.sortAscending() // 按距离从近到远排序(附近商户必备)
.limit(end) // 预查足够数据,为后续分页准备
.includeDistance() // 核心:让 Redis 返回每个元素到圆心的距离
.includeCoordinates() // 预查数据,避免分页漏条
);
// 判空:geoResults 为空或无内容,直接返回空列表
if (geoResults == null || geoResults.getContent().isEmpty()) {
return Result.ok(Collections.emptyList());
}
// 核心:Stream 分页(skip+limit)并接收结果,提取【商铺 ID+距离】
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> pageGeoList = geoResults.getContent().stream()
.skip(from) // 跳过前 from 条,实现分页
.limit(SystemConstants.DEFAULT_PAGE_SIZE) // 截取分页条数(一页的数量)
.collect(Collectors.toList()); // 必须 collect 接收结果,否则跳过不生效
// 提取商铺 ID(批量查库,替代循环单查,提升性能 10 倍+)
List<Long> shopIds = pageGeoList.stream()
.map(geoResult -> Long.valueOf(geoResult.getContent().getName()))
.collect(Collectors.toList());
if (shopIds.isEmpty()) {
return Result.ok(Collections.emptyList());
}
String shopIdstr = StringUtil.join(shopIds, ",");
// 批量查询商铺详情(MyBatis-Plus 批量查,避免循环 eq 单查)
List<Shop> shopList = query().in("id", shopIds).last(("ORDER BY FIELD(id, " + shopIdstr + "))).list();
// 给商铺赋值距离(并保证 shopList 顺序和 pageGeoList 一致)
for (int i = 0; i < pageGeoList.size(); i++) {
GeoResult<RedisGeoCommands.GeoLocation<String>> geoResult = pageGeoList.get(i);
Shop shop = shopList.get(i);
// 距离转 Double,加非空判断(防止空指针)
Double distValue = geoResult.getDistance().getValue();
shop.setDistance(distValue);
}
return Result.ok(shopList);
}
将 DEFAULT_PAGE_SIZE 的大小改为 5 以上即可解决该问题。这主要是因为 Redis GEO 搜索返回的数据量如果小于预期,会导致分页偏移计算出现偏差,适当增大初始查询范围能覆盖更多潜在数据,从而保证分页逻辑稳定。


