Redis实战案例25-附近商铺功能

1. GEO数据结构

Redis中Geohash功能应用

添加地理坐标


求两点之间距离

搜索天安门附近10km的火车站,按升序

2. 导入店铺数据到GEO

Redis中存储店铺的信息,将店铺的id和经纬度坐标存到GEO数据类型中去,其中member存id,经纬度对应x和y;

存储对应店铺信息之后,才能去计算对应的距离排序,之后得出附近店铺功能时,得到就是id,根据id查询店铺即可;

但是这里存储在GEO中的只有店铺id和地理坐标,并没有类型id,所以要对商铺类型进行分类;

这里直接写测试进行商户类型分组了,写到Redis中去

java 复制代码
/**
 * 进行店铺分组
 */
@Test
void loadShopData(){
    //查询店铺信息
    List<Shop> list = shopService.list();
    //把店铺分组,按照typeId分组,一致的放到一个集合中
    Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
    //分批写入Redis中
    for (Map.Entry<Long, List<Shop>> entry:map.entrySet()) {
        //获取类型id
        Long typeId = entry.getKey();
        String key = SHOP_GEO_KEY + typeId;
        //获取同类型的店铺的集合
        List<Shop> value = entry.getValue();
        ArrayList<RedisGeoCommands.GeoLocation<String>> geoLocations = new ArrayList<RedisGeoCommands.GeoLocation<String>>(value.size());
        //写入Redis GEOADD key 经度 纬度 member
        for (Shop shop:value) {
            //stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString());
            geoLocations.add(new RedisGeoCommands.GeoLocation<>(
                    shop.getId().toString(),
                    new Point(shop.getX(),shop.getY())
            ));
        }
        stringRedisTemplate.opsForGeo().add(key, geoLocations);
    }
}

3. 附近商铺搜索

x和y参数采用required = false,传了参数则采用GEO方式查,没传参数则采用别的方式查询排序

java 复制代码
/**
 * 根据商铺类型分页查询商铺信息
 * @param typeId 商铺类型
 * @param current 页码
 * @return 商铺列表
 */
@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);
}

有点难,等以后有水平了再来修改吧~

java 复制代码
/**
 * 根据商铺类型分页查询商铺信息
 * @param typeId
 * @param current
 * @param x
 * @param y
 * @return
 */
@Override
public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
    //因为前端不一定按照距离来做排序,所以坐标有可能为空
    //1.判断是否需要根据坐标查询
    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());
    }
    //2.计算分页参数
    //开始
    int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
    //结束
    int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
    //3.查询redis,按照距离排序,分页
    //结果:shopId,distance
    String key = SHOP_GEO_KEY + typeId;
    GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(
            key,
            GeoReference.fromCoordinate(x, y),
            new Distance(5000),
            //limit是限制范围,但是只能指定结束,都是从第一条开始到结束,只能对结果手动截取
            //默认单位为m,当前为5km,结果也是m
            RedisGeoCommands.GeoRadiusCommandArgs.newGeoSearchArgs().includeCoordinates().limit(end)
    );
    //4.解析出id
    if(results == null){
        return Result.ok(Collections.emptyList());
    }
    //       当前集合从0到end,需要手动截取        list.subList() 或者stream流
    List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
    if (list.size() <= from) {
        //没有下一页
        return Result.ok(Collections.emptyList());
    }
    // 4.1 截取from------end的部分
    List<Long> ids = new ArrayList<>(list.size());
    Map<String,Distance> distanceMap = new HashMap<>(list.size());
    list.stream().skip(from).forEach(result -> {
        //参见test测试单元的存储过程
        //获取店铺id
        String shopId = result.getContent().getName();
        //需要把id转换为long类型的进行查询店铺信息
        ids.add(Long.valueOf(shopId));
        //获取距离
        Distance distance = result.getDistance();
        //保证id与distance一一对应
        distanceMap.put(shopId,distance);
    });
    //5.根据id查询店铺
    String idStr = StrUtil.join(",", ids);
    List<Shop> shops = query().in("id", ids)
            .last("ORDER BY FIELD(id," + idStr + ")").list();//        6.返回
    //在实体类,distance是只属于实体类,用于返回给前端的字段
    for (Shop shop : shops) {
        //因为集合有存储,所以根据id来取出值,但是取出的是对象,需要调用getValue来转成相应的值
        shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
    }
    return Result.ok(shops);
}
相关推荐
IT项目管理1 小时前
达梦数据库DMHS介绍及安装部署
linux·数据库
你都会上树?1 小时前
MySQL MVCC 详解
数据库·mysql
大春儿的试验田1 小时前
高并发收藏功能设计:Redis异步同步与定时补偿机制详解
java·数据库·redis·学习·缓存
likeGhee1 小时前
python缓存装饰器实现方案
开发语言·python·缓存
hqxstudying1 小时前
Redis为什么是单线程
java·redis
C182981825752 小时前
OOM电商系统订单缓存泄漏,这是泄漏还是溢出
java·spring·缓存
Ein hübscher Kerl.2 小时前
虚拟机上安装 MariaDB 及依赖包
数据库·mariadb
醇醛酸醚酮酯2 小时前
Qt项目锻炼——TODO清单(二)
开发语言·数据库·qt
GreatSQL社区3 小时前
用systemd管理GreatSQL服务详解
数据库·mysql·greatsql
掘根3 小时前
【MySQL进阶】错误日志,二进制日志,mysql系统库
数据库·mysql