GEO数据结构
GEO就是Geolocation的简写形式,代表地理坐标。Redis在3.2版本加入了对GEO的支持,允许存储地理坐标消息,帮助我们根据经纬度来检索数据。常见的命令有:
GEOADD:添加一个地理空间信息,包含:经度(longitude),纬度(latitude),值(member)
GEODIST:计算指定的两个点之间的距离并返回
GEOHASH:将指定member的坐标转为hash字符串形式并返回
GEOPOS:返回指定member的坐标
GEOPADIUS:指定圆心,半径,找到该圆内包含的所有member,并按照与圆心之间的距离排序后返回。6.2以后已废弃
GEOSEARCH:在指定范围内搜索member,并按照与指定点之间的距离排序后返回。范围可以是圆形或矩形。6.2新功能
GEOSEARCHSTORE:与GEOSEARCH功能一致,不过可以把结果存储到一个指定的key。6.2.新功能
附近商户搜索
按照商户类型做分组,类型相同的商户作为同一组,以typeid为key存入同一GEO集合中即可
导入店铺信息数据到GEO
// 导入店铺信息数据到GEO@Testvoid loadShopData(){//1.查询店铺信息List list = shopService.list();//2.把店铺分组,按照typeId分组,id一致的放到一个集合Map> map =list.stream().collect(Collectors.groupingBy(Shop::getTypeId));//3.分批完成写入Redisfor (Map.Entry> entry : map.entrySet()) {//3.1.获取类型idLong typeId = entry.getKey();//3.2获取同类型的店铺的集合List shopList = entry.getValue();List> locations =new ArrayList<>(shopList.size());//3.3写人redis GEOADD key 经度 纬度 memberfor (Shop shop : shopList) {/*stringRedisTemplate.opsForGeo().add(SHOP_GEO_KEY+ typeId,new Point(shop.getX(),shop.getY()),shop.getId().toString());*/locations.add(new RedisGeoCommands.GeoLocation<>(shop.getId().toString(),new Point(shop.getX(),shop.getY())));}stringRedisTemplate.opsForGeo().add(SHOP_GEO_KEY+ typeId,locations);}}
}
注意:SpringDataRedis的2.3.9版本并不支持Redis6.2提供的GEOSEARCH命令,因此我们需要提升其版本,修改自己的pom文件,内容如下:
org.springframework.boot spring-boot-starter-data-redis org.springframework.data spring-data-redis io.lettuce lettuce-core org.springframework.data spring-data-redis 2.6.2 io.lettuce lettuce-core 6.1.9.RELEASE
代码实现附近商户功能:
public Result getShopByType(Integer typeId, Integer current, Double x, Double y) {//1.判断是否需要根据坐标查询if (x == null || y == null){// 不需要坐标查询,按数据库查询Page page = new Page<>(current, DEFAULT_BATCH_SIZE);LambdaQueryWrapper queryWrapper=new LambdaQueryWrapper<>();queryWrapper.eq(Shop::getTypeId,typeId);Page shopPage = page(page, queryWrapper);return Result.ok(shopPage.getRecords());}//2.计算分页参数int from =(current - 1) * DEFAULT_BATCH_SIZE;int end =current *DEFAULT_BATCH_SIZE;//3.查询redis,按照距离排序,分页,结果:shopId,distance//GEOSEARCH BYLONLAT x y BYRADIUS 10 WITHDISTANCEGeoResults> results = stringRedisTemplate.opsForGeo().search(SHOP_GEO_KEY + typeId,GeoReference.fromCoordinate(x, y),new Distance(5000),RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end));//4.解析出id//判空if (results == null || CollectionUtil.isEmpty(results)){return Result.ok(Collections.emptyList());}List>> list = results.getContent();if (list.size()<=from){//没有下一页,结束return Result.ok(Collections.emptyList());}//4.1截取from - end 的部分List ids =new ArrayList<>(list.size());Map distanceMap =new HashMap<>(list.size());list.stream().skip(from).forEach(result ->{//4.2获取店铺idString shopIdStr = result.getContent().getName();ids.add(Long.valueOf(shopIdStr));//4.3获取距离Distance distance = result.getDistance();distanceMap.put(shopIdStr,distance);});//5.根据id查询ShopLambdaQueryWrapper queryWrapper=new LambdaQueryWrapper<>();String idStr =StrUtil.join(",",ids);queryWrapper.in(Shop::getId,ids).last("ORDER BY FIELD(id,"+idStr+")");List shopList = list(queryWrapper);for (Shop shop : shopList) {shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());}//6.返回return Result.ok(shopList);}