如何在Java中使用Redis实现附近司机的搜索功能


在打车类应用中,一个重要的功能是寻找并匹配离用户最近的司机。为了提高查询效率和响应速度,使用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功能,我们可以高效地在大规模数据中快速查找到符合条件的司机,并结合业务需求进行过滤和排序。这种方法在打车类应用中非常实用,既保证了查询的速度,也能够灵活地适应不同的业务场景。

  • 23
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值