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. 封装距离字段,统一返回前端。

相关推荐
BullSmall1 小时前
异构数据库(通俗 + 核心知识点)
数据库
Rick19931 小时前
索引下推(ICP):在已经用到联合索引的前提下,减少回表次数,提升查询效率
数据库
金海境科技1 小时前
实践分享!虚拟化数据恢复前三标准
数据库
不剪发的Tony老师2 小时前
RedisME:一个现代化、轻量级Redis管理工具
数据库·redis
消失的旧时光-19432 小时前
企业认证与安全体系(四):企业登录认证流程全解析——JWT、Redis、Spring Security 如何协同工作?
redis·安全·spring·spring security·jwt
愤怒的苹果ext2 小时前
Spring Boot Redis Stream队列
spring boot·redis·消息队列·stream
金海境科技2 小时前
实践分享!服务器数据恢复口碑榜
数据库
llilay2 小时前
企业级FastAPI后端模板搭建(三)整合日志Log
数据库·python·fastapi
小江的记录本2 小时前
【Spring AI】Spring AI中RAG误触发与系统提示词泄露问题解决方案(完整版+代码方案)
java·人工智能·spring boot·后端·python·spring·面试