附近商铺
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
@Test
void loadShopData(){
//1.查询店铺信息
List<Shop> list = shopService.list();
//2.把店铺分组,按照typeId分组,id一致的放到一个集合
Map<Long,List<Shop>> map =list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
//3.分批完成写入Redis
for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
//3.1.获取类型id
Long typeId = entry.getKey();
//3.2获取同类型的店铺的集合
List<Shop> shopList = entry.getValue();
List<RedisGeoCommands.GeoLocation<String>> locations =new ArrayList<>(shopList.size());
//3.3写人redis GEOADD key 经度 纬度 member
for (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文件,内容如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</exclusion>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</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>
代码实现附近商户功能:
public Result getShopByType(Integer typeId, Integer current, Double x, Double y) {
//1.判断是否需要根据坐标查询
if (x == null || y == null){
// 不需要坐标查询,按数据库查询
Page<Shop> page = new Page<>(current, DEFAULT_BATCH_SIZE);
LambdaQueryWrapper<Shop> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(Shop::getTypeId,typeId);
Page<Shop> 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 WITHDISTANCE
GeoResults<RedisGeoCommands.GeoLocation<String>> 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<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 ->{
//4.2获取店铺id
String shopIdStr = result.getContent().getName();
ids.add(Long.valueOf(shopIdStr));
//4.3获取距离
Distance distance = result.getDistance();
distanceMap.put(shopIdStr,distance);
});
//5.根据id查询Shop
LambdaQueryWrapper<Shop> queryWrapper=new LambdaQueryWrapper<>();
String idStr =StrUtil.join(",",ids);
queryWrapper.in(Shop::getId,ids).last("ORDER BY FIELD(id,"+idStr+")");
List<Shop> shopList = list(queryWrapper);
for (Shop shop : shopList) {
shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
}
//6.返回
return Result.ok(shopList);
}