文章目录
在打车类应用中,一个重要的功能是寻找并匹配离用户最近的司机。为了提高查询效率和响应速度,使用Redis的Geo功能是一个不错的选择。在本文中,我将详细讲解如何通过Java代码结合Redis来实现这一需求,并逐步解析代码逻辑。
代码实现
首先,我们来看一下完整的代码实现:
@Override
public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
// 1. 获取用户的经纬度,定义point对象
Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(),
searchNearByDriverForm.getLatitude().doubleValue());
// 2. 定义距离范围为5公里
Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS, RedisGeoCommands.DistanceUnit.KILOMETERS);
// 3. 创建Circle对象,用于定义搜索范围
Circle circle = new Circle(point, distance);
// 4. 定义GeoRadiusCommandArgs参数,指定返回结果包含距离和坐标,并按距离升序排序
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.includeDistance()
.includeCoordinates()
.sortAscending();
// 5. 从Redis中搜索附近的司机
GeoResults<RedisGeoCommands.GeoLocation<String>> result = redisTemplate.opsForGeo()
.radius(RedisConstant.DRIVER_GEO_LOCATION,
circle, args);
// 6. 获取搜索结果列表
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = result.getContent();
// 7. 对搜索结果进行过滤,筛选符合条件的司机
List<NearByDriverVo> list = new ArrayList<>();
if (!CollectionUtils.isEmpty(content)) {
for (GeoResult<RedisGeoCommands.GeoLocation<String>> item : content) {
// 获取司机ID
Long driverId = Long.valueOf(item.getContent().getName());
// 远程调用获取司机的个性化设置信息
Result<DriverSet> driverSetResult = driverInfoFeignClient.getDriverSet(driverId);
DriverSet driverSet = driverSetResult.getData();
// 判断司机是否满足条件
// 订单里程判断
BigDecimal orderDistance = driverSet.getOrderDistance();
if (orderDistance.doubleValue() != 0 && orderDistance.subtract(
searchNearByDriverForm.getMileageDistance()).doubleValue() < 0) {
continue;
}
// 接单距离判断
BigDecimal acceptDistance = driverSet.getAcceptDistance();
BigDecimal currentDistance = new BigDecimal(item.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);
if (acceptDistance.doubleValue() != 0 && acceptDistance.subtract(currentDistance).doubleValue() < 0) {
continue;
}
// 封装符合条件的司机信息
NearByDriverVo nearByDriverVo = new NearByDriverVo();
nearByDriverVo.setDriverId(driverId);
nearByDriverVo.setDistance(currentDistance);
list.add(nearByDriverVo);
}
}
// 8. 返回最终的司机列表
return list;
}
代码解析
1. 获取用户的经纬度并定义 Point
对象
首先,我们从传入的 searchNearByDriverForm
表单中获取用户的经纬度信息,并将其封装到 Point
对象中。这一步的目的是确定用户的当前位置,后续的所有计算都基于这个位置进行。
Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(),
searchNearByDriverForm.getLatitude().doubleValue());
2. 定义搜索范围
在这一步,我们使用 Distance
类定义了一个半径为5公里的搜索范围。这个范围可以根据业务需求进行调整,比如增加或减少搜索半径。
Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS, RedisGeoCommands.DistanceUnit.KILOMETERS);
3. 创建 Circle
对象
Circle
对象将用户的位置 (Point
) 和搜索半径 (Distance
) 结合起来,定义了一个圆形区域,代表我们要搜索的地理范围。
Circle circle = new Circle(point, distance);
4. 定义Geo查询参数
我们通过 GeoRadiusCommandArgs
设置查询参数,指定查询结果需要包含每个司机与用户之间的距离和坐标,并按照距离从近到远进行排序。
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.includeDistance()
.includeCoordinates()
.sortAscending();
5. 在Redis中搜索附近的司机
通过 Redis 的 opsForGeo
操作,我们可以快速地在预定义的范围内查找所有符合条件的司机。
GeoResults<RedisGeoCommands.GeoLocation<String>> result = redisTemplate.opsForGeo()
.radius(RedisConstant.DRIVER_GEO_LOCATION,
circle, args);
6. 获取查询结果
查询结果返回的是一个 GeoResults
对象,其中包含了所有在指定范围内的司机信息。我们通过 getContent()
方法获取结果列表。
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = result.getContent();
7. 对结果进行过滤
在这个步骤中,我们会遍历查询结果,并根据业务需求对司机进行进一步的过滤。主要的过滤条件包括司机的订单里程和接单距离。我们还会远程调用一个服务,获取每个司机的个性化设置信息,以便进行更精确的过滤。
if (!CollectionUtils.isEmpty(content)) {
for (GeoResult<RedisGeoCommands.GeoLocation<String>> item : content) {
Long driverId = Long.valueOf(item.getContent().getName());
Result<DriverSet> driverSetResult = driverInfoFeignClient.getDriverSet(driverId);
DriverSet driverSet = driverSetResult.getData();
// 订单里程过滤
BigDecimal orderDistance = driverSet.getOrderDistance();
if (orderDistance.doubleValue() != 0 && orderDistance.subtract(
searchNearByDriverForm.getMileageDistance()).doubleValue() < 0) {
continue;
}
// 接单距离过滤
BigDecimal acceptDistance = driverSet.getAcceptDistance();
BigDecimal currentDistance = new BigDecimal(item.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);
if (acceptDistance.doubleValue() != 0 && acceptDistance.subtract(currentDistance).doubleValue() < 0) {
continue;
}
// 将符合条件的司机加入结果列表
NearByDriverVo nearByDriverVo = new NearByDriverVo();
nearByDriverVo.setDriverId(driverId);
nearByDriverVo.setDistance(currentDistance);
list.add(nearByDriverVo);
}
}
8. 返回最终结果
最后,将经过过滤的司机信息列表返回给调用者。这个列表中的司机都符合之前定义的所有条件,可以推荐给用户。
return list;
总结
通过以上的详细讲解,我们成功地实现了一个基于Redis的附近司机搜索功能。使用Redis的Geo功能,我们可以高效地在大规模数据中快速查找到符合条件的司机,并结合业务需求进行过滤和排序。这种方法在打车类应用中非常实用,既保证了查询的速度,也能够灵活地适应不同的业务场景。