GEO数据结构

  • geoadd:添加地理位置的坐标。
  • geopos:获取地理位置的坐标。
  • geodist:计算两个位置之间的距离。
  • georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
  • georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
  • geohash:返回一个或多个位置对象的 geohash 值。

需求:
image.png

GEOADD g1 116.378248 39.865275 bjn 116.42803 39.903738 bjz 116.322287 39.893729 bjx GEODIST g1 bjz bjx GEODIST g1 bjz bjx km GEOSEARCH g1 FROMLONLAT 116.39704 39.909005 BYRADIUS 10 km WITHDIST GEOPOS g1 bjz GEOHASH g1 bjz

导入店铺数据到GEO

在首页点击某个频道,即可看到频道下的商户:
image.png
按照商户类型做分组,类型相同的商户作为同一组,以typeId为key存入同一个GEO集合中
image.png图片是指存店铺id

在Test中导入商铺信息:

  1. @Test
  2. void loadShopData(){
  3. //1.查询店铺信息
  4. List<Shop> list = shopService.list();
  5. //2.按typeId分组存入Redis集合
  6. Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
  7. //3.分批完成写入Redis
  8. for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
  9. //获取类型id和同类型集合
  10. Long typeId = entry.getKey();
  11. String key = "shop:geo:" + typeId;
  12. List<Shop> value = entry.getValue();
  13. List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(value.size());
  14. // 3.3.写入redis GEOADD key 经度 纬度 member
  15. for (Shop shop : value) {
  16. // stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString());
  17. locations.add(new RedisGeoCommands.GeoLocation<>(
  18. shop.getId().toString(),
  19. new Point(shop.getX(), shop.getY())
  20. ));
  21. }
  22. stringRedisTemplate.opsForGeo().add(key, locations);
  23. }
  24. }

查看Redis:
image.png

实现附近商户功能

SpringDataRedis的2.3.9版本不支持Redis6.2提供的GEOSEARCH功能,因此需要提示其版本,修改pom.xml
添加Maven Helper插件
image.png

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. <exclusions>
  5. <exclusion>
  6. <artifactId>spring-data-redis</artifactId>
  7. <groupId>org.springframework.data</groupId>
  8. </exclusion>
  9. <exclusion>
  10. <artifactId>lettuce-core</artifactId>
  11. <groupId>io.lettuce</groupId>
  12. </exclusion>
  13. </exclusions>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.springframework.data</groupId>
  17. <artifactId>spring-data-redis</artifactId>
  18. <version>2.6.2</version>
  19. </dependency>
  20. <dependency>
  21. <groupId>io.lettuce</groupId>
  22. <artifactId>lettuce-core</artifactId>
  23. <version>6.1.6.RELEASE</version>
  24. </dependency>

修改ShopController:

  1. @GetMapping("/of/name")
  2. public Result queryShopByName(
  3. @RequestParam(value = "name", required = false) String name,
  4. @RequestParam(value = "current", defaultValue = "1") Integer current
  5. ) {
  6. // 根据类型分页查询
  7. Page<Shop> page = shopService.query()
  8. .like(StrUtil.isNotBlank(name), "name", name)
  9. .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
  10. // 返回数据
  11. return Result.ok(page.getRecords());
  12. }
  1. @Override
  2. public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
  3. //1.判断是否需要根据坐标查询
  4. if(x == null || y == null){
  5. // 根据类型分页查询
  6. Page<Shop> page = query()
  7. .eq("type_id", typeId)
  8. .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
  9. return Result.ok(page.getRecords());
  10. }
  11. //2.计算分页参数
  12. int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
  13. int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
  14. //3.查询Redis、按照距离排序、分页,结果:shopId、distance
  15. String key = SHOP_GEO_KEY + typeId;
  16. GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo() //GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHSTANCE
  17. .search(
  18. key,
  19. GeoReference.fromCoordinate(x, y),
  20. new Distance(5000),
  21. RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
  22. );
  23. //4.解析出id
  24. if(results == null){
  25. return Result.ok(Collections.emptyList());
  26. }
  27. List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
  28. if(list.size() <= from){
  29. //没有下一页了
  30. return Result.ok(Collections.emptyList());
  31. }
  32. Map<String, Distance> distanceMap = new HashMap<>(list.size());
  33. //4.1 截取 from ~ end部分
  34. List<Long> ids = new ArrayList<>(list.size());
  35. list.stream().skip(from).forEach(result -> {
  36. String shopIdStr = result.getContent().getName();
  37. ids.add(Long.valueOf(shopIdStr));
  38. Distance distance = result.getDistance();
  39. });
  40. //5.根据id查询店铺Shop
  41. String idStr = StrUtil.join(",", ids);
  42. List<Shop> shops = query().in("ids", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
  43. for (Shop shop : shops) {
  44. shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
  45. }
  46. return Result.ok(shops);
  47. }

Bug: ERR unknown command GEOSEARCH, with args beginning with