????宏夏Coding網(wǎng)站,致力于為編程學習者、互聯(lián)網(wǎng)求職者提供最需要的內(nèi)容!網(wǎng)站內(nèi)容包括求職秘籍,葵花寶典(學習筆記),資源推薦等內(nèi)容。在線閱讀:https://hongxiac.com????
導讀
在日常生活中,我們經(jīng)常能看見查詢附近商家的功能。
常見的場景有,比如你在點外賣的時候,就可能需要按照距離查詢附近幾百米或者幾公里的商家。
本文將介紹如何使用Redis實現(xiàn)按照距離查詢附近商戶的功能,并以SpringBoot項目作為舉例。
想知道這樣的功能是如何實現(xiàn)的嗎?接著往下看吧!
Redis地理位置功能
Redis是一種高性能的鍵值存儲數(shù)據(jù)庫,具有快速讀寫能力和豐富的數(shù)據(jù)結(jié)構(gòu)支持。在Redis 3.2版本之后,它引入了地理位置(Geospatial)功能,使其可以輕松處理與地理位置相關的數(shù)據(jù)。
地理位置功能的核心數(shù)據(jù)結(jié)構(gòu)是有序集合(Sorted Set),它將元素與分數(shù)(score)關聯(lián)起來。在地理位置功能中,分數(shù)表示地理位置的經(jīng)度和緯度,而元素則是一個標識符,比如商戶的ID。
我們只需要在數(shù)據(jù)庫中存儲商家的經(jīng)緯度,以商家id作為key,經(jīng)緯度作為value存入redis中,就可以通過redis命令來獲得以某一個點為圓心一定范圍內(nèi)的商家,以及他們之間的距離。
常用命令
1. GEOADD:將地理位置添加到有序集合中
? ?使用GEOADD命令,可以將一個或多個地理位置添加到有序集合中。語法如下:
GEOADD key longitude latitude member [longitude latitude member ...]
示例:
? ?GEOADD stores 116.404 39.915 "storeA"
? ?GEOADD stores 116.418 39.917 "storeB"
2. GEODIST:計算兩個位置之間的距離
? GEODIST命令用于計算兩個位置之間的距離,可以指定單位(米、千米、英里、英尺等)。
? ?GEODIST key member1 member2 [unit]
? ?示例:
? ?GEODIST stores storeA storeB km
3. GEORADIUS:按照距離查詢位置范圍內(nèi)的元素
? ?GEORADIUS命令用于在指定的地理位置范圍內(nèi)查詢元素。它可以按照經(jīng)緯度坐標和半徑來查詢,還可以限制返回的結(jié)果數(shù)量。
?GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key]
? ?示例:
?
? ?GEORADIUS stores 116.408 39.916 1 km WITHDIST COUNT 5
?
4. GEOHASH:獲取位置的geohash值
? ?GEOHASH命令用于獲取指定位置的geohash值,geohash是一種將地理位置編碼成字符串的方法,可以用于快速近似的位置計算。
?GEOHASH key member [member ...]
? ?示例:
? ?GEOHASH stores storeA storeB
5. GEOPOS:獲取一個或多個位置的經(jīng)緯度坐標
? ?GEOPOS命令用于獲取一個或多個位置的經(jīng)緯度坐標。
? ?GEOPOS key member [member ...]
? ?示例:
? ?GEOPOS stores storeA storeB
? ?
6. GEORADIUSBYMEMBER:根據(jù)成員獲取范圍內(nèi)的元素
? ?這個命令與GEORADIUS類似,但是它以一個已有的成員作為中心點進行查詢。
?
? ?GEORADIUSBYMEMBER key member radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key]
?
? ?示例:
? ?
? ?GEORADIUSBYMEMBER stores storeA 1 km
? ?
地理位置功能不僅在查詢附近商戶等實際應用中非常有用,還可以應用于地理分析、位置推薦等領域。它通過利用Redis強大的有序集合數(shù)據(jù)結(jié)構(gòu),使得處理地理信息變得高效、靈活,并且易于集成到現(xiàn)有的應用中。無論是構(gòu)建LBS應用還是處理位置相關數(shù)據(jù),Redis的地理位置功能都能為開發(fā)者提供強大的支持。
Java代碼實現(xiàn)
將數(shù)據(jù)庫中的商家經(jīng)緯度存入redis
數(shù)據(jù)庫中有一張商家表,其中有經(jīng)度,緯度這兩個字段。我們可以通過單元測試批量將這些商家的經(jīng)緯度數(shù)據(jù)存入redis。key為商家id,value為經(jīng)緯度。
/**
* 將數(shù)據(jù)庫中的商戶坐標添加到緩存
*/
@Test
void addShopGeo2Redis(){
//獲取商戶集合
List<Shop> list = shopService.list();
//根據(jù)商戶類型分類
Map<Long, List<Shop>> collect = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
for (Map.Entry<Long, List<Shop>> longListEntry : collect.entrySet()) {
Long typeId = longListEntry.getKey();
String key = "shop:geo:" + typeId;
//獲取商戶經(jīng)緯度
List<Shop> shopList = longListEntry.getValue();
List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(shopList.size());
for (Shop shop : shopList) {
// stringRedisTemplate.opsForGeo().add(key,new Point(shop.getX(),shop.getY()),shop.getId().toString());
//先收集完所有商戶的地理位置,再一次性添加到redis
locations.add(new RedisGeoCommands.GeoLocation<>(shop.getId().toString(),new Point(shop.getX(),shop.getY())));
}
stringRedisTemplate.opsForGeo().add(key,locations);
}
}
接口類:queryShopByType(typeId,current,x,y)
定義一個根據(jù)商家類型查詢所有商家的接口,如果前端傳來的參數(shù)中攜帶該用戶的經(jīng)緯度,則代表需要根據(jù)距離查詢附近商家。
/**
* 根據(jù)商鋪類型分頁查詢商鋪信息
* @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);
}
服務類:queryShopByType(typeId,current,x,y)
1.首先判斷是否經(jīng)緯度參數(shù)x和y是否為空
2.計算分頁參數(shù)(redis無法分頁,需要手動分頁)
3.查詢redis
4.獲取商戶id集合
5.根據(jù)商戶id查詢數(shù)據(jù)庫
6.返回
@Override
public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
//1.判斷是否需要根據(jù)坐標查詢
if(x == null || y == null){
//直接數(shù)據(jù)庫查詢
Page<Shop> page = query().eq("type_id", typeId).page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
return Result.ok(page.getRecords());
}
//2.計算分頁參數(shù)
int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
//3.查詢redis,按照距離排序,分頁。結(jié)果:shopId,distance
String key = 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)
);
//4.解析出id
if(results == null){
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 -> {
String shopIdStr = result.getContent().getName();
ids.add(Long.valueOf(shopIdStr));
Distance distance = result.getDistance();
distanceMap.put(shopIdStr,distance);
});
//5.根據(jù)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);
}
}
注意點
1.redis查詢的結(jié)果是從第1條到第end條,不能直接返回第begin條到第end條。
那么如何跳過begin前面的記錄呢?
可以使用stream()流的skip()方法,skip()方法中指定參數(shù)begin,就會跳過前面的begin條記錄。
2.通過redis獲取的ids集合,再使用mybatis-plus使用query().in()進行查詢時,會破壞數(shù)據(jù)順序,如何解決?文章來源:http://www.zghlxwxcb.cn/news/detail-658299.html
手動指定順序。在后面加上last("ORDER BY FIELD(id," + idStr + ")").list()。而idStr = StrUtil.join(",",ids);文章來源地址http://www.zghlxwxcb.cn/news/detail-658299.html
到了這里,關于如何使用Redis實現(xiàn)附近商家查詢的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!