redis_点评(26.附近店铺——实现附近商家功能)

附近店铺功能实现(基于Redis GEO)

一、功能简介

结合数据库分页Redis GEO 地理位置检索,实现按店铺类型、用户经纬度查询周边店铺。不传经纬度时走普通数据库分页;传入经纬度时,利用 Redis GEO 按距离由近到远排序查询,提升检索性能。

二、依赖引入

引入 Redis 相关依赖,解决版本冲突,保证 GEO 相关方法正常使用:

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>lettuce-core</artifactId>
            <groupId>io.lettuce</groupId>
        </exclusion>
        <exclusion>
            <artifactId>spring-data-redis</artifactId>
            <groupId>org.springframework.data</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.6.2</version>
</dependency>
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.1.9.RELEASE</version>
</dependency>

三、接口与分层代码

1. Controller 接口

接收类型ID、页码、用户经纬度,转发业务请求:

java 复制代码
@GetMapping("/of/type")
public Result queryShopByType(
    @RequestParam("typeId") Integer typeId,
    @RequestParam(value = "current", defaultValue = "1") Integer current,
    @RequestParam(value = "x", required = false) Double x,
    @RequestParam(value = "y", required = false) Double y
) {
    return shopService.queryShopByType(typeId, current, x, y);
}

2. Service 接口

java 复制代码
public interface IShopService extends IService<Shop> {
    Result queryShopByType(Integer typeId, Integer current, Double x, Double y);
}

3. Service 实现类

java 复制代码
@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());
    }

    // 有经纬度:Redis GEO 地理位置查询
    int pageSize = SystemConstants.DEFAULT_PAGE_SIZE;
    int from = (current - 1) * pageSize;
    int end = current * pageSize;
    String key = SHOP_GEO_KEY + typeId;

    // GEO 检索:5公里范围内店铺,返回距离信息,限制最大条数
    GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()
            .search(
                    key,
                    GeoReference.fromCoordinate(x, y),
                    new Distance(5000),
                    RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()
                            .includeDistance()
                            .limit(end)
            );

    if (results == null) {
        return Result.ok(Collections.emptyList());
    }
    List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
    if (list.size() <= from) {
        return Result.ok(Collections.emptyList());
    }

    // 内存分页,截取当前页数据
    List<Long> ids = new ArrayList<>();
    Map<String, Distance> distanceMap = new HashMap<>();
    list.stream().skip(from).forEach(result -> {
        String shopIdStr = result.getContent().getName();
        ids.add(Long.valueOf(shopIdStr));
        distanceMap.put(shopIdStr, result.getDistance());
    });

    // 根据ID查库,保留距离排序
    String idStr = StrUtil.join(",", ids);
    List<Shop> shops = query()
            .in("id", ids)
            .last("ORDER BY FIELD(id," + idStr + ")")
            .list();

    // 封装距离字段
    for (Shop shop : shops) {
        shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
    }
    return Result.ok(shops);
}

四、核心知识点说明

1. MyBatis-Plus 分页对象 Page

  • page.getRecords():获取当前页数据列表

  • page.getTotal():数据总条数

  • page.getPages():总页数

  • page.getCurrent():当前页码

底层数组、数据库索引从0 开始,业务页码从1 开始,分页计算需做 页码-1 偏移换算。

2. Redis GEO 核心 API

  1. stringRedisTemplate.opsForGeo():专门操作 Redis GEO 地理位置结构。

  2. .search():Redis6.2+ 附近地点检索,以用户坐标为圆心、指定半径范围查询;

    1. .includeDistance():返回两点间距离;

    2. .limit():限制返回数据条数。

  3. GeoResults.getContent():剥离包装对象,转为普通集合。

3. GEO 分页方案

Redis GEO 不支持原生偏移分页,采用内存分页

  1. Redis 查询当前页及之前所有数据;

  2. 通过 Stream.skip() 跳过前置数据,截取当前页内容。

4. 距离字段处理

  • Distance.getValue():提取距离数值(double 类型)。

  • 实体类非数据库字段定义:

java 复制代码
@TableField(exist = false)
private Double distance;

五、关键设计要点

  1. Redis Key 设计shop:geo:{typeId},按店铺类型拆分 GEO 集合,提升查询效率。

  2. 排序保证 :使用 ORDER BY FIELD 强制沿用 Redis 距离排序,避免数据库打乱顺序。

  3. 数据一致性:Redis 仅存储店铺ID与经纬度,店铺详情从数据库查询。

六、整体流程

  1. 未传经纬度:直接使用 MyBatis-Plus 完成数据库分页;

  2. 传入经纬度:通过 Redis GEO 按距离检索店铺ID与距离;

  3. Java 内存分页截取当前页数据,根据ID查询店铺详情;

  4. 封装距离字段,统一返回前端。

相关推荐
程序员清风4 小时前
Java 后端高并发设计:线程池、限流、熔断与降级
java·数据库·oracle
天衍四九-4 小时前
MySQL中如何定位慢查询?如果SQL语句执行很慢,如何分析和优化?
数据库·sql·mysql
尚久龙4 小时前
mysql-5.7.26-winx64的安装步骤
数据库·mysql
医疗信息化王工4 小时前
DataForge:基于 Python 的数据库批量导出 Excel 工具——从架构到部署的全流程实战
数据库·python·excel
实心儿儿4 小时前
MySQL 的安装
数据库·mysql·adb
程序员阿鹏6 小时前
为什么MySQL InnoDB选择B+树?
数据结构·数据库·b树·sql·mysql·算法·缓存
瀚高PG实验室7 小时前
使用pg_stat_statements抓取数据库TOP SQL
数据库·sql·postgresql·瀚高数据库
Flynt8 小时前
把公司项目迁到 Spring Boot 4.0:编译通过只是开始
java·spring boot·后端
码哥字节8 小时前
AI一天出百本书,写不出我被Redis坑的那三天三夜
redis·ai编程工具
yanwumuxi9 小时前
【数据库系列】opensearch数据备份和恢复
数据库