黑马点评9——附近商户-GEO数据结构
文章目录
- GEO数据结构的基本用法
- 导入店铺数据结构到GEO
- 实现附件商户功能
GEO数据结构的基本用法
导入店铺数据结构到GEO
数据库里没法实现按照地理坐标排序等复杂的搜索功能,我们把数据存到redis中,只需要保存id和对应的x以及y的坐标,就可以在查询的时候,从redis中根据坐标查询出id, 然后根据id去数据库中查询要的数据。
@Test
public 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();
String key = RedisConstants.SHOP_GEO_KEY + typeId;
// 3.2 获取同类型的店铺的集合
List<Shop> value = entry.getValue();
List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(value.size());
// 3.3 写入redis GEOADD key经度, 维度 member
for (Shop shop : value) {
// stringRedisTemplate.opsForGeo().add(key, 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(key, locations); // 批量存入
}
}
实现附件商户功能
<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.8.RELEASE</version>
</dependency>
@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 = RedisConstants.SHOP_GEO_KEY + typeId;
GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(key,
GeoReference.fromCoordinate(x, y),
new Distance(5000),
RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)); // 这里只能传入end, 返回的是0 - end, 我们接收后在手动分页
// 4. 解析出id
if(results == null){
return Result.ok(Collections.emptyList());
}
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = results.getContent();
if(content.size() <= from){
// 没有下一页
return Result.ok(Collections.emptyList());
}
// 4.1 截取从from到end部分
List<Long> ids = new ArrayList<>(content.size());
Map<String, Distance> distanceMap = new HashMap<>(content.size());
content.stream().skip(from).forEach(result -> {
// 4.2 获取店铺id
String shopId = result.getContent().getName();
ids.add(Long.valueOf(shopId));
// 4.3 获取距离
Distance distance = result.getDistance();
distanceMap.put(shopId, distance);
});
// 5. 根据id查询shop
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());
}
// 6. 返回
return Result.ok(shops);
}