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);
});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/95825.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/95825.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/95825.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML第七课:发展史

HTML第七课&#xff1a;发展史发展史快速学习平台发展史 示例 HTML 发展史 前端三件套&#xff1a;html 、css、javascript(Js) HTML 发展史 HTML 1.0&#xff08;1993 年&#xff09; 蒂姆伯纳斯 - 李&#xff08;Tim Berners - Lee&#xff09;发明了万维网&#xff0c;同…

中国生成式引擎优化(GEO)市场分析:领先企业格局与未来趋势分析

一、GEO市场变革中国生成式引擎优化&#xff08;Generative Engine Optimization, GEO&#xff09;市场正经历一场深刻的变革&#xff0c;其核心在于生成式人工智能&#xff08;Generative AI&#xff09;对传统搜索引擎和数字营销模式的颠覆性影响。传统搜索引擎以“提供链接”…

好看的背景颜色 uniapp+小程序

<view class"bg-decoration"><view class"circle-1"></view><view class"circle-2"></view><view class"circle-3"></view> </view>/* 背景装饰 */.container{background: linear-gr…

《驾驭云原生复杂性:隐性Bug的全链路防御体系构建》

容器、服务网格、动态配置等抽象层为系统赋予了弹性与效率,但也像深海中的暗礁,将技术风险隐藏在标准化的接口之下。那些困扰开发者的隐性Bug,往往并非源于底层技术的缺陷,而是对抽象层运行逻辑的理解偏差、配置与业务特性的错配,或是多组件交互时的协同失效。它们以“偶发…

vosk语音识别实战

一、简介 Vosk 是一个由 Alpha Cephei 团队开发的开源离线语音识别&#xff08;ASR&#xff09;工具包。它的核心优势在于完全离线运行和轻量级&#xff0c;使其非常适合在资源受限的环境、注重隐私的场景或需要低延迟的应用中使用。 二、核心特点 离线运行 (Offline) 这是…

鸿蒙ABC开发中的名称混淆与反射处理策略:安全与效率的平衡

在当今的软件开发中&#xff0c;代码安全是一个至关重要的议题。随着鸿蒙系统&#xff08;HarmonyOS&#xff09;的广泛应用&#xff0c;开发者们在追求功能实现的同时&#xff0c;也必须考虑如何保护代码不被轻易破解。名称混淆是一种常见的代码保护手段&#xff0c;但当反射机…

css页面顶部底部固定,中间自适应几种方法

以下是实现页面顶部和底部固定、中间内容自适应的几种常见方法&#xff0c;附代码示例和适用场景分析&#xff1a;方法一&#xff1a;Flexbox 弹性布局 <body style"margin:0; min-height:100vh; display:flex; flex-direction:column;"><header style"…

彻底拆解 CSS accent-color:一个属性,省下一堆“重造轮子”的苦工

我有一支技术全面、经验丰富的小型团队&#xff0c;专注高效交付中等规模外包项目&#xff0c;有需要外包项目的可以联系我既要原生控件、又要品牌配色&#xff0c;还不想伪造组件&#xff1f;能不能讲透 accent-color。下面给出一版尽量“到骨头里”的解析&#xff1b;对讨厌从…

在选择iOS代签服务前,你必须了解的三大安全风险

选iOS代签服务&#xff1f;这三个安全坑千万别踩&#xff01;关于iOS代签那些你可能忽略的安全风险。多少次因为测试设备限制、紧急分发或者企业账号年费肉疼&#xff0c;我们不得不考虑第三方代签服务&#xff1f;但这里头的水&#xff0c;比想象中深。风险一&#xff1a;证书…

GitHub 热榜项目 - 日榜(2025-09-04)

GitHub 热榜项目 - 日榜(2025-09-04) 生成于&#xff1a;2025-09-04 统计摘要 共发现热门项目&#xff1a;20 个 榜单类型&#xff1a;日榜 本期热点趋势总结 本期GitHub热榜呈现三大技术热点&#xff1a;AI智能体开发、架构工程化和开发者工具革新。JetBrains Koog、DeepC…

在 vue-vben-admin(v5 版本)中,使用 ECharts 图表(豆包版)

在 vue-vben-admin&#xff08;v5版本&#xff09;中&#xff0c;使用 ECharts 图表的方式已通过框架封装的 vben/plugins/echarts 模块简化&#xff0c;结合官方示例&#xff0c;具体使用步骤如下&#xff1a; 1. 核心组件与工具导入 框架提供了封装后的 EchartsUI 组件&#…

本地 Ai 离线视频去水印字幕!支持字幕、动静态水印去除!

这款功能强大的AI视频处理工具&#xff0c;能够有效地去除视频中的静态水印、动态水印以及字幕。 针对不同类型的水印和字幕&#xff0c;提供了多种去除方式&#xff0c;操作简单&#xff0c;效果显著。 首先【打开视频】&#xff0c;然后在识别模式里面选择识别模式&#xf…

1个工具管好15+网盘(批量转存/分享实测)工具实测:批量转存 + 自动换号 + 资源监控 账号添加失败 / 转存中断?这样解决(含功能详解)

电脑里装了N个网盘客户端&#xff1a;百度网盘存工作文件、阿里云盘放家庭照片、夸克网盘塞学习资料&#xff0c;还有迅雷、天翼云盘散落在各处——每次找文件要在5个软件间反复切换&#xff0c;手动转存10个文件得点几十次鼠标&#xff0c;网盘多了反倒成了“数字负担”。直到…

2025-09-04 CSS2——常见选择器

文章目录1 元素选择器2 id 选择器3 class 选择器4 通用选择器5 子元素选择器6 后代选择器7 相邻兄弟选择器8 后续兄弟选择器9 伪类选择器10 伪元素选择器11 属性选择器11.1 [attribute]11.2 [attribute"value"]11.3 [attribute~"value"]与[attribute*"…

计算机网络:概述层---OSI参考模型

&#x1f310; OSI七层参考模型详解&#xff1a;从物理层到应用层的完整剖析 &#x1f4c5; 更新时间&#xff1a;2025年9月3日 &#x1f3f7;️ 标签&#xff1a;OSI模型 | 网络协议 | 七层模型 | 计算机网络 | 网络架构 | 协议栈 | 王道考研 摘要: 本文将用最通俗易懂的语言&…

JVM相关 2|Java 垃圾回收机制(GC算法、GC收集器如G1、CMS)的必会知识点汇总

目录&#xff1a;&#x1f9e0; 一、GC基础概念1. 什么是垃圾回收&#xff08;Garbage Collection, GC&#xff09;&#xff1f;2. 判断对象是否为垃圾的方法&#x1f9e9; 二、GC核心算法1. 标记-清除算法&#xff08;Mark-Sweep&#xff09;2. 标记-整理算法&#xff08;Mark…

04 - 【HTML】- 常用标签(下篇)

表格标签 1 表格 table 在HTML中&#xff0c;表格是通过<table>标签来创建的&#xff0c;它允许在html中以行和列的形式组织数据。HTML提供了一套完整的标签来创建功能丰富的表格。 2 表格的 结构 3 表格table代码结构 4 表格结构解析 <thead></thead>&…

nVisual从入门到精通—应用实例

五、应用实例 5.1 数据中心的规划设计 5.1.1 规划设计流程5.1.2 创建模型库 5.1.2.1 设备模型库 设备模型库基于组织内实际使用的设备型号进行构建&#xff0c;主要包含以下对象类型&#xff1a;机柜、网络设备、板卡、组合模型。 设备属性字段&#xff1a;除系统保留字段&…

代码可读性的详细入门

&#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录前言一、可读性的重要性二、用名字表达代码含义三、避免名字歧义四、良好的代码风格五、注释的价值六、如何编写注释七、提高控制流的可读性八、拆分长表达式九、变量与可读性十、抽取函数十一、一次只做一件事十二、用自然…

轮轨法向接触斑计算

轮轨法向接触斑计算 &#xff0c;同时输出 接触斑面积、长轴 a、短轴 b、最大 Hertz 压力 pmax 等关键指标 算法基于 Hertz 接触理论&#xff08;适用于单点椭圆接触&#xff09;&#xff0c;并给出如何扩展到 非 Hertz / 有限元验证的提示。1 理论回顾&#xff08;Hertz 椭圆…