1.GEO數據結構
1.1實現附近的人的數據結構
????????Redis提供的專用的數據結構來實現附近的人的操作,這也是企業的主流解決方案,建議使用這種解決方案。
????????GEO就是Redis提供的地理坐標計算的一個數據結構,可以很方便的計算出來兩個地點的地理坐標,實現相應的功能。
1.2GEO數據結構
????????GEO就是Geolocation的簡寫形式,代表地理坐標。Redis在3.2版本中加入了對GEO的支持,允許存儲地理坐標信息,幫助我們根據經緯度來檢索數據。
????????要點:可以使用GEO來進行存儲地理坐標信息(允許通過經緯度進行檢索數據)
????????常見的命令:
????????GEOADD:添加一個地理空間信息,包含:經度(longitude),緯度(latitude),值(member)
????????GEODIST:計算指定兩點的距離并返回(根據經緯度進行計算)
????????GEOHASH:將指定member的坐標轉換為hash字符串形式并進行返回。
????????GEOPOS:返回指定member的坐標。
????????GEORADIUS:指定圓心,半徑,找到該圓內包含的所有member,并按照與圓心之間的距離排序后返回(6.2之后已經廢棄)
????????GEOSEARCH:在指定范圍內搜索member,并按照與指定點之間的距離排序后返回。范圍可以是圓形或者是矩形(6.2廢棄GEORADIUS后新增的功能)】
????????GEOSEARCHSTORE:與GEOSEARCH的功能一致,不過GEOSEARCHSTORE可以將結果存儲一個指定的key中,6.2增加的新功能。
1.3案例學習
1.3.1案例
????????北京南站(116.378248, 39.8)’
????????北京站(116.42803,39.903738)
????????北京西站(116.322287, 39.893729)
????????任務1:將這幾個站點的地理位置坐標存儲以GEO的形式存儲到Redis中。
????????任務2:計算北京西站到北京站的距離。
????????任務3:搜索天安門(116.397904, 39.909005)附近10km內所有的火車站,并按照距離升序排序。
1.3.2完成任務1
????????需要使用GEO提供的GEOADD指令進行添加GEO數據。
????????Redis的GEOADD指令是通過一個key可以進行存儲多個GEO數據(即一個key對應的是一個GEO數據列表),一個GEO數據是以()
????????GEOADD key 經度 緯度 member [經度 緯度 member...]
????????使用GEOADD進行對key為g1的GEO列表進行添加GEO數據·。
????????可以看到三個GEO數據已經添加成功了,Member是值,Score是Redis底層根據指定的經緯度進行計算出來的數據。
1.3.3完成任務2
????????使用GEODIST key member1 member2 [unit單位]進行計算一個GEOLIST中的兩個member的經緯度計算出來的距離,可以通過unit進行指定單位,也可以不進行指定單位,默認單位是m。
????????可以發現,當不指定單位的時候,默認是m,也可以將unit指定為km,計算出來就是以千米為單位的數據:
1.3.4完成任務3
????????任務三是需要搜索以天安門為中心點,半徑為五千米的圓范圍內的數據。
????????命令操作如下:
? ? ? GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radisu m|km|ft|mi] [BYBOX width height m|km|ft|mi] [ASC|DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]
????????1.搜索中心有兩種指定方式:
????????FROMMEMBER:從已經存在的key中讀取經緯度。
????????FROMLONLAT:用戶參數傳入經緯度
????????2.搜索條件也有兩種方式:
????????BYRADIUS:根據給定半徑長度按圓形進行搜素,命令等同于使用GEORADIUS
????????BYBOX:根據給定width和height按照舉行進行搜索,矩形是軸對稱矩形。
????????3.更多可選參數
????????WITHCOORD 返回匹配的經緯度坐標。
????????WITHDIST:返回距離,距離單位按照radius或者height/width單位轉換。
????????WITHHASH:但會GEOHASH計算的值(就是將經緯度進行計算為HASH值進行返回)
????????COUNT count:只返回count個元素。注意,這里的count是全部搜索完成才進行過濾的,也就是不同減少搜索的CPU消耗,但是返回的元素少,可以利用此來降低網絡帶寬的利用率(使用COUNT限制返回的數量,借此來進行降低網絡IO的壓力)
????????ASC|DESC:對滿足條件的點進行按按照距離升序/降序排序。
????????使用GEOSEARCH進行查詢指定的以北京天安門為中心的,10公里的車站。
1.3.5查看北京西的坐標數據
????????查看一個GEO集合中的GEO數據的經緯度的時候,要進行使用GEOPOS進行查看。
????????GEOPOS的使用命令如下:
????????GEOPOS key member [member...]
????????使用GEOPOS的時候可以一次返回一個member對應的經緯度,也可以一次返回多個member對應的經緯度:
1.3.6查看三個站點經緯度坐標數據對應的HASH數據
????????查看一個GEO集合中的GEO數據的經緯度對應的HASH數據的時候,要進行使用GEOHASH來進行他查看。
????????GEOHASH的使用命令如下:
????????GEOHASH key member [member...]
????????使用GEOHASH可以一次返回一個member對應的經緯度的HASH數據,也可以一次返回多個:
2.附近商戶搜索的業務實現
2.1業務介紹
????????在首頁中點擊某個頻道,即可看到頻道下的商戶:
????????查看頻道下的商戶的時候,要根據商戶的經緯度坐標,以及前端提交上來的用戶所在的經緯度坐標進行計算個人與商戶的距離,并按照距離進行排名,升序返回。
????????要點:前端進行傳遞到后端經緯度坐標,后端去進行查詢前端傳上來的經緯度坐標與相關頻道的餐廳數據的距離,進行一個距離排序后,按距離升序排序進行返回。
2.2將商戶數據的經緯度信息導入到Redis中
????????業務主要就是頻道的數據是按店鋪的類型進行展示的,需要進行獲取坐標信息的業務也是頻道,所以需要店鋪的經緯度數據按店鋪類型type進行緩存,查詢頻道的時候,也是按店鋪類型type進行查詢的。
????????挑幾個code中實現的比較好的部分說一下。
import com.hmdp.entity.Shop;
import com.hmdp.service.IShopService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.StringRedisTemplate;import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** 測試將頻道中的商鋪數據添加到Redis中* 以GEO的形式進行存儲到數據庫中*/
@SpringBootTest
public class TestLoadShopData {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate IShopService shopService;@Testvoid loadShopData() {// 1. 查看店鋪信息List<Shop> list = shopService.list();// 2. 把店鋪進行分組, 按照typeId進行分組, typeId一致的進行放入一個集合中Map<Long, List<Shop>> groupByTypeId = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));// 3. 分批將數據進行寫入到Redis中for (Map.Entry<Long, List<Shop>> group : groupByTypeId.entrySet()) {// 3.1 獲取改組的類型IDLong typeId = group.getKey();String key = "shop:geo:" + typeId;// 3.2 獲取同類型的店鋪的集合List<Shop> shopList = group.getValue();List<RedisGeoCommands.GeoLocation<String>> geoLocations = shopList.stream().map(shop -> new RedisGeoCommands.GeoLocation<>(shop.getName(), new Point(shop.getX(), shop.getY()))).collect(Collectors.toList());stringRedisTemplate.opsForGeo().add(key, geoLocations);}}}
2.2.1分組操作
????????使用Stream流進行分組操作,使用List集合的Stream流,將List集合按照一個分組規則,轉變為一個Map<Group標識, List>,完成分組存儲數據的操作。
??當所有的店鋪數據從數據庫中查詢回來后,使用list.stream().collect(Collectors.groupingBy(Shop::getTypeId)),進行根據Shop的TypeId進行分組轉換為Map<Long, List<Shop>>進行返回。
????????要點:當我們要將List根據List中數據的類別特征轉換為Map的時候,就可以使用stream流中提供的collect(Collectors.groupingBy(item => item中的字段/getter方法))進行轉換,最終進行轉換為Map。
// 2. 把店鋪進行分組, 按照typeId進行分組, typeId一致的進行放入一個集合中
Map<Long, List<Shop>> groupByTypeId = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
2.2.2RedisKey的設計
????????根據業務的設計,要根據Shop的type進行區分,設置對應的GEO緩存,所以key中要涉及到shop的typeId。
????????設計為:shop:geo:shopTypeId作為Redis中的緩存數據的key。
String key = "shop:geo:" + typeId;
2.2.3將Shop數據以GEO數據的形式存儲到Redis中
?????存儲GEO數據的時候,要使用RedisTemplate.opsForGeo().add(key, GeoLocation對象/Iterator<GeoLocation>對象),也就是說可以一次性向一個GEO集合中添加單條數據/多條數據。
List<RedisGeoCommands.GeoLocation<String>> geoLocations = shopList.stream().map(shop -> new RedisGeoCommands.GeoLocation<>(shop.getName(), new Point(shop.getX(), shop.getY()))).collect(Collectors.toList());
stringRedisTemplate.opsForGeo().add(key, geoLocations);
? ? ?GeoLocation中需要進行構造兩個字段:Member和org.springframework.data.geo.Point(double x, double y),存儲的是member數據和Point坐標數據。
2.3完成根據店鋪類型查詢數據(根據頻道查詢數據)
2.3.1目前現狀
????????目前這個接口僅僅是根據類型(頻道)分頁查詢店鋪數據,并沒有進行根據GEO存儲的地理坐標位置進行查詢數據排序。
/*** 根據商鋪類型分頁查詢商鋪信息* @param typeId 商鋪類型* @param current 頁碼* @return 商鋪列表*/
@GetMapping("/of/type")
public Result queryShopByType(@RequestParam("typeId") Integer typeId,@RequestParam(value = "current", defaultVqqqqqqqqqqqqqqqqalue = "1") Integer current
) {// 根據類型分頁查詢Page<Shop> page = shopService.query().eq("type_id", typeId).page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));// 返回數據return Result.ok(page.getRecords());
}
2.3.2排除依賴
????????目前進行使用spring-boot-starter-parent的版本是2.3.12.RELEASE版本:????????
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.12.RELEASE</version><relativePath/> <!-- lookup parent from repository -->
</parent>
????????其中內部進行集成的spring-boot-starter-data-redis版本也是2.3.12RELEASE版本的,這個版本的spring-boot-startter-data-redis是不支持GeoLocation中的GEOSEARCH功能的:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.3.12.RELEASE</version>
</dependency>
????????解決方法就是將當前SpringBoot進行集成的SpringDataRedis依賴進行排除,引入支持GEOSEARCH的SpringDataRedis版本(2.6.2版本)
? ? ? ? ? 排除當前SpringBoot集成的SpringDataRedis的依賴:
????????需要在spring-boot-starter-data-redis中進行使用exclusion進行排除spring-data-redis和io.letture的依賴(spring-data-redis和io.lettuce要配套使用)
<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>io.lettuce</artifactId></exclusion></exclusions>
</dependency>
2.3.3引入SpringDataRedis2.6.2
????????SpringDataRedis2.6.2版本是支持GEOSEARCH指令的使用的,所以可以引入SpringDataRedis2.6.2,如果要進行使用redis線程池lettuce,需要使用相應的配套版本6.1.6.RELEASE。
<!-- SpringDataRedis和Redis連接池 -->
<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.6.RELEASE</version>
</dependency>
????????更換了依賴之后就可以進行使用RedisTemplate.opsForGeo().search()方法了。
2.4完成附近商戶搜索的功能
2.4.1Controller層和Service層接口的定義
????????Controller層的定義:
????????進行定義接收了四個參數:typeId(店鋪類型ID),current(當前分頁 => SpringDataRedis中的opsForGeo的search方法并沒有實現相應的分頁功能,地理位置的相關的分頁查詢需要自己實現),x(緯度),y(經度)。
????????x和y這兩個參數均使用了@RequestParam(required = false)進行注解,標識該參數不是必須的。
/*** 根據商鋪類型分頁查詢商鋪信息* @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.queryShopByTypeAndGeo(typeId, current, x, y);
}
????????Service層接口的定義:
/*** 根據店鋪位置和地理位置進行查詢數據** @param typeId* @param current* @param x* @param y* @return*/
Result queryShopByTypeAndGeo(Integer typeId, Integer current, Double x, Double y);
????????Serivice層的實現比較復雜,進行分開進行分析:
2.4.2經緯度參數不全
????????當經緯度參數不全的時候,則直接使用分頁查詢即可,不要進行走redis中的GEO地理位置查詢的功能。
????????page中接收一個com.baomidou.mybatisplus.extension.plugins.pagiination.Page的對象參數,這個Page對象在進行實例化的時候需要接收current(當前分頁)和size(本頁查詢的數據數量)。
// 1.判斷是否要進行結合地理位置進行查詢
if (x == null || y == null) {Page<Shop> page = this.query().eq("typeId", typeId).page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));return Result.ok(page.getRecords());
}
2.4.3計算分頁參數
????????計算分頁參數是計算出從什么位置開始,到什么位置結束,其實是到end - 1的位置結束,后面也是這樣做的。
// 2. 計算分頁參數
int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
2.4.4從Redis中查詢符合條件的GEO數據
????????從key對應的GEO列表中進行查詢符合條件的GEO數據。
調用的是RedisTemplate.opsForGeo().search()方法進行檢索附近的店鋪位置,對應的就是Redis命令中的GEOSEARCH指令。
????這里指定給GEOSEARCH的參數是,查詢key對應的GEOLIST,使用GeoReference.fromCoordinate(x, y)進行指定中心點,直接傳入Distance指定范圍為圓形,半徑為5km,使用RedisCeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeCoordinates().limit(end)指定查詢的結果包含經緯度數據,并且限制網絡IO僅往回返回end個數據(主要是用于分頁查詢數據)
????????沒有指定查詢數據的排序,默認就是以Distance升序進行返回的(默認順序ASC)
// 3. 查詢redis, 按照距離和分頁進行查詢。結果: shopId和distance。
String key = SHOP_GEO_KEY + typeId;
// 查詢指定key中的GEO數據 使用指定x和y坐標進行查詢 距離在5000m內 限定查詢end個
GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(key,GeoReference.fromCoordinate(x, y),new Distance(5000),RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeCoordinates().limit(end)
);
????????接下來先進行分析一下RedisTemplate.opsForGeo().search方法:
2.4.4.1最原始的Search接口
????????先進行分析Redis的原始指令:
????????Redis的原始GEOSEARCH指令,需要進行指定的是:1.復用key對應的GEOList中GEO數據的坐標/指定相應的坐標。2.使用Circle/Box進行規定查找范圍的類型以及返回的Distance的單位。3.查詢出來的數據根據distance進行升序/降序。4.限制返回多少條數據(查詢出數據后,在CPU計算完需要返回什么數據后,再進行截取返回數據)。5.是否返回匹配到數據的經緯度坐標。6.是否返回匹配到數據的距離數據。7.距離數據是否以Hash的形式返回。
????????GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radisu m|km|ft|mi] [BYBOX width height m|km|ft|mi] [ASC|DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]
????????再看SpringDataRedis中進行封裝的原始接口:
????????里面接收四個參數:key,GeoReference,GeoShape,RedisGeoCommands。
????????簡述一下四個參數的作用:
????????key ?=> 從Redis中找到對應的GEOList
????????GeoReference => 指定使用GEOList中已經有的Member對應經緯度數據/傳入一個經緯度數據進行使用。
????????GeoShape => 圓形范圍/矩形范圍。
????????RedisGeoCommands => 指定GEOSEARCH中其它的參數,例如:查詢回去的數據的順序問題(升序 or 降序),是否在網路傳輸時使用count限制查詢數據的數量,受否攜帶坐標返回,是否攜帶距離返回,是否將返回的Distance距離數據轉換為Hash數據進行返回。
@Nullable
GeoResults<RedisGeoCommands.GeoLocation<M>> search(K key, GeoReference<M> reference, GeoShape geoPredicate, RedisGeoCommands.GeoSearchCommandArgs args);
2.4.4.2GeoReference參數的封裝
????????只看最核心的部分即可,GeoReference就是用來指定GEOSEARCH方法中作為中心點的經緯度數據。
????????GeoReference是一個接口,進行定義了一堆將擁有默認實現的靜態方法:
????????1.fromMember => 從key對應的GRO列表中找出member為指定值的GEO數據的經緯度作為中心點坐標。
????????fromMember可以直接接收一個T類型的member數據,或者一個GeoLocation數據(GeoLocation中封裝了Point對象和member數據),指定中心點。
static <T> GeoReference<T> fromMember(T member) {Assert.notNull(member, "Geoset member must not be null");return new GeoMemberReference<>(member);
}static <T> GeoReference<T> fromMember(RedisGeoCommands.GeoLocation<T> member) {Assert.notNull(member, "GeoLocation must not be null");return new GeoMemberReference<>(member.getName());
}
????????2.fromCircle => 允許指定一個有范圍的圓作為中心點,向外擴展。
????????fromCircle可以接收一個Circle類型的對象作為參數,這個對象可以進行設置Point對象(經緯度)和radius(Distance類型,半徑)作為中心點。
static <T> GeoReference<T> fromCircle(Circle within) {Assert.notNull(within, "Circle must not be null");return fromCoordinate(within.getCenter());
}
????????3.fomCoordinate => 允許指定具體的經緯度坐標作為中心點。
????????fromCoordinate可以接收三種那參數:
????????1.longitude(經度)和latitude(緯度),兩者均是double類型的。
????????2.GeoLocation(內置Point字段和member字段)
????????3.Point(內置經度x和緯度y)
static <T> GeoReference<T> fromCoordinate(double longitude, double latitude) {return new GeoCoordinateReference<>(longitude, latitude);
}static <T> GeoReference<T> fromCoordinate(RedisGeoCommands.GeoLocation<?> location) {Assert.notNull(location, "GeoLocation must not be null");Assert.notNull(location.getPoint(), "GeoLocation point must not be null");return fromCoordinate(location.getPoint());
}static <T> GeoReference<T> fromCoordinate(Point point) {Assert.notNull(point, "Reference point must not be null");return fromCoordinate(point.getX(), point.getY());
}
2.4.4.3GeoShape參數的封裝
????????GeoShape參數是被用來指定范圍形狀的,自從redis6.2開始,新增的GEOSEARCH開始支持以圓形/矩形作為范圍形狀。
????????GeoShape是一個接口,繼承自Shape接口,Shape接口是一個高層抽象接口,繼承了序列化的功能:
import java.io.Serializable;public interface Shape extends Serializable {
}
????????GeoShape主要進行定義了一些具有默認實現的static靜態方法:
????????1.GeoShape.byRadius(Distance radius)這個方法進行指定以圓形作為一個范圍時,圓形的半徑時是多少。
static GeoShape byRadius(Distance radius) {return new RadiusShape(radius);
}
?2.GeoShape.byBox(double width, double height, DistanceUnit distanceUnit)/GeoShape.byBox(BoundingBox boundingBox)
????????byBox的重載方法是一樣的,第一個重載方法傳遞進來三個參數,主要利用三個參數進行構建BoudingBox對象。
????????GeoShape.byBox()方法主要就是指定以矩形作為一個范圍的(指定矩形的width和height)
static GeoShape byBox(double width, double height, DistanceUnit distanceUnit) {return byBox(new BoundingBox(width, height, distanceUnit));
}static GeoShape byBox(BoundingBox boundingBox) {return new BoxShape(boundingBox);
}
2.4.4.4GeoSearchCommandArgs的封裝
????????GEOSEARCH中除了最重要的中心點的指定和范圍形狀的指定,其它的一些參數封裝在RedisGeoCommands.GeoSearchCommandArgs中。
????????RedisGeoCommands.GeoSearchCommandArgs中可以進行指定count參數(通過limit指定),sort參數(ASC升序,DESC降序),includeCoordinates(返回數據的時候是否攜帶includeCoordinate進行返回)
? ?使用的時候很簡單,調用RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()構造出一個GeoSearchCommandArgs類型的對象,再進行鏈式調用limit,sort,includeCoordinate方法進行指定args參數即可。
RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().limit().sort().includeCoordinates();
2.4.4.5GeoOperation中封裝的search默認實現
????????其實在GeoOperation接口中封裝的search的默認實現都是簡化了search方法的調用而已,參數還是哪些參數,只是簡化了調用。
????????注意:接口中封裝的字段,默認都是public static final 靜態常量。
????????1.想要直接以一個有范圍的圓作為中心點,可以使用search(K key,Circle within)這個重載方法:
@Nullable
default GeoResults<GeoLocation<M>> search(K key, Circle within) {return search(key, GeoReference.fromCircle(within), GeoShape.byRadius(within.getRadius()),GeoSearchCommandArgs.newGeoSearchArgs());
}
????????2.想要直接以一個經緯度坐標作為圓心,radius作為范圍半徑(范圍形狀為圓形),可以使用search(K key, GeoReference<M> reference, Distance radius)或者search(K key, GeoReference<M> reference, Distance radius, GeoSearchCommandArgs args)這兩個重載方法,一個需要傳入額外的args參數,一個不需要傳入額外的args參數,按需選擇即可:
@Nullable
default GeoResults<GeoLocation<M>> search(K key, GeoReference<M> reference, Distance radius) {return search(key, reference, radius, GeoSearchCommandArgs.newGeoSearchArgs());
}@Nullable
default GeoResults<GeoLocation<M>> search(K key, GeoReference<M> reference, Distance radius,GeoSearchCommandArgs args) {return search(key, reference, GeoShape.byRadius(radius), args);
}
????????3.和上面兩個方法同理,想要以矩形作為范圍形狀時可以使用下面的兩個方法:
@Nullable
default GeoResults<GeoLocation<M>> search(K key, GeoReference<M> reference,BoundingBox boundingBox) {return search(key, reference, boundingBox, GeoSearchCommandArgs.newGeoSearchArgs());
}@Nullable
default GeoResults<GeoLocation<M>> search(K key, GeoReference<M> reference, BoundingBox boundingBox,GeoSearchCommandArgs args) {return search(key, reference, GeoShape.byBox(boundingBox), args);
}
2.4.4.6返回的參數GeoResults
????????使用GEOSEARCH進行搜索返回的GeoResults封裝了一個數據結構,就是查詢回來的數據列表(使用getContent進行獲取)
????????GeoResults的getContent源碼:
????????這個方法會將查詢回來的數據,以List形式的數據結構返回。
?使用GEOSEARCH查詢的數據據返回的是List<GeoResult<RedisCommands.GeoLocation<String>>>
public List<GeoResult<T>> getContent() {return Collections.unmodifiableList(results);
}
2.4.5解析出返回數據中的ShopId和Distance數據
????????解析出數據的代碼流程:
????????1.先對search查詢出的GeoResults數據進行判空
????????2.使用GeoReults對象的getContent獲取查詢回來的GeoList數據
????????3.再對List數據進行判空,以及判斷查詢回來的數據有from個嘛
????????4.準備一個ArrayList解析ShopId數據,準備一個HashMap解析每個ShopId對應的Distance數據(數據庫中沒有Distance數據,需要給Shop實體進行設置Distance數據)
????????5.截取出form ~ end部分的id和distance數據
????????要點:為什么要進行判斷數據是否有from個?為什么要進行截取from ~ end部分的數據?
????????因為GEOSEARCH進行查詢數據后,會調度CPU查詢出所有的數據,最終智能借助limit截取數量,并不支持智能的分頁查詢等,只能自己通過這種方式實現分頁的功能呢。
????????6.根據通過GEOSEARCH查詢出的數據的ShopId,從數據庫中查詢數據,聚合返回。
// 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的部分
ArrayList<Long> ids = new ArrayList<>(list.size());
Map<String, Distance> distanceMap = new HashMap<>(list.size());
list.stream().skip(from).forEach(result -> {// 4.2 獲取到shopIdString idStr = result.getContent().getName();ids.add(Long.valueOf(idStr));// 4.3 獲取到distanceDistance distance = result.getDistance();distanceMap.put(idStr, distance);
});
// 5. 根據id查詢Shop
String idStr = StrUtil.join(",", ids);
List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list().stream().map(shop -> {shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());return shop;
}).collect(Collectors.toList());
????????截取from ~ end部分的代碼
????????要點1:借助stream流的skip(int number)進行跳過集合中的from個數據,前from個數據就不進行遍歷了。
????????要點2:遍歷出的每一條數據都是GeoReuslt<RedisCommands.GeoLoacation>,想要獲取member數據要使用:getContent().getName進行獲取,想要獲取Distance數據要使用:getDistance進行獲取。
list.stream().skip(from).forEach(result -> {// 4.2 獲取到shopIdString idStr = result.getContent().getName();ids.add(Long.valueOf(idStr));// 4.3 獲取到distanceDistance distance = result.getDistance();distanceMap.put(idStr, distance);
});