目录
如何将地理位置数据保存到 Redis 中以支持范围查询
Redis 中的 GEO 类型是什么?
如何保存 GEO 数据到 Redis
分段解释:
RedisKey.POSTS_ANIMALS_LOCATIONS
new Point(longitude, latitude)
如何进行范围搜索
Redis GEO 范围搜索核心语句
1.redisTemplate.opsForGeo().search(...)
2. RedisKey.POSTS_ANIMALS_LOCATIONS
3. GeoReference.fromCoordinate(x, y)
4. new Distance(5000)
5. RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()
6. .includeDistance()
搜索结果判空
提取 Redis 搜索结果
如何将地理位置数据保存到 Redis 中以支持范围查询
Redis 中的 GEO 类型是什么?
Redis 提供了 GEO
命令支持地理位置信息的存储和查询,底层使用 Geohash 编码 + Sorted Set(有序集合) 实现。你可以对其进行:
-
添加坐标(经纬度);
-
查询指定位置一定范围内的成员;
-
获取两个成员之间的距离;
-
查询某个成员的位置等。
如何保存 GEO 数据到 Redis
redisTemplate.opsForGeo().add(RedisKey.POSTS_ANIMALS_LOCATIONS, // Redis 中的 key,用来存所有帖子的位置new Point(animalsRescueDTO.getLongitude(), animalsRescueDTO.getLatitude()), // 经纬度(Point 构造顺序:经度, 纬度)posts.getPostId().toString() // 这个帖子的位置用帖子 ID 作为唯一标识(member)
);
👇 实际等价于 Redis 命令:
GEOADD POSTS_ANIMALS_LOCATIONS 113.1234 23.5678 "123"
-
POSTS_ANIMALS_LOCATIONS
是存储地理数据的 Redis 键 -
113.1234
是经度(longitude) -
23.5678
是纬度(latitude) -
"123"
是帖子 ID(即你posts.getPostId()
)
分段解释:
redisTemplate.opsForGeo()
这是 Spring Data Redis 提供的模板方法,用于执行 GEO 类型操作 的入口。
它返回的是一个 GeoOperations<K, V>
对象,可以执行下面这些操作:
方法 | 功能 |
---|---|
add(...) | 添加地理位置点(经度、纬度 + 名称) |
search(...) | 按照范围搜索附近的点 |
distance(...) | 计算两个点之间的距离 |
position(...) | 获取某个 member 的坐标 |
hash(...) | 获取某个 member 的 geohash 值 |
RedisKey.POSTS_ANIMALS_LOCATIONS
这是一个 Redis 的 Key,表示你所有帖子位置都保存在这个 key 下的 GEO 类型中。
new Point(longitude, latitude)
这是一个 Spring 提供的 org.springframework.data.geo.Point
类型,用于封装经纬度。
⚠️ 注意顺序:
Point(x, y)
中,x 是经度,y 是纬度
Redis 是使用底层的 ZSet + Geohash 来实现这个数据结构的:
-
每个点会被编码成 geohash,对应 ZSet 的
score
-
可以用
ZRANGE
、GEORADIUS
等方式高效查询范围
如何进行范围搜索
Redis GEO 范围搜索核心语句
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().search(RedisKey.POSTS_ANIMALS_LOCATIONS, // Redis 中 GEO 数据所在的 keyGeoReference.fromCoordinate(x, y), // 查询圆心:经度 x,纬度 ynew Distance(5000), // 查询半径:5000 米(5 公里)RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance() // 查询结果中返回每个点的距离
);
参数 | 类型 | 说明 |
---|
key | String | Redis 中存储 GEO 数据的 key,也就是你存储地理数据时使用的那个 key。例如你之前可能使用 RedisKey.POSTS_ANIMALS_LOCATIONS 存储了所有帖子的地理位置信息。 |
geoReference | GeoReference | 查询圆心坐标(经纬度)。这代表你要查询的中心点的位置。 |
distance | Distance | 查询的范围半径,单位是米(m ),表示你想要查询中心点 geoReference 周围多少米以内的所有地理位置。 |
args | GeoSearchCommandArgs | 查询参数的设置,可以设置排序、返回距离等额外选项。 |
1.redisTemplate.opsForGeo().search(...)
-
opsForGeo()
:Spring Data Redis 提供的 GEO 操作 API -
search(...)
:封装了 Redis 6.2+ 的GEOSEARCH
命令,是范围查找的入口
2. RedisKey.POSTS_ANIMALS_LOCATIONS
-
这是 Redis 中用于保存 GEO 数据的键名(key)
-
对应 Redis 的底层命令示例:
3. GeoReference.fromCoordinate(x, y)
这是查询的中心点:
GeoReference.fromCoordinate(double longitude, double latitude)
注意顺序是:
经度在前(x),纬度在后(y)
这个代表:
“我现在的位置是 (经度, 纬度),请查我附近的 member”
你传入的 (x, y)
一般来源于用户定位(如小程序的 wx.getLocation()
)
4. new Distance(5000)
-
Distance
是 Spring 提供的距离对象 -
默认单位是 米(Meters)
-
所以这是一个 以中心点为圆心、半径为 5000 米(5 公里) 的范围查找
5. RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()
这是搜索的“附加参数”,你可以在里面设置返回什么信息、是否排序、返回几个等。
是一个“无参的静态方法”,作用是:
返回一个新的
GeoSearchCommandArgs
对象,用来配置 Redis GEO 查询参数。
你得到了一个 GeoSearchCommandArgs
类型的对象,后面可以继续链式调用:
举例:
RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()
.includeDistance().limit(5).sortAscending();
6. .includeDistance()
这个非常重要!
-
它的作用是:让返回结果中附带每个点距离圆心的“实际距离”
-
否则默认只返回哪些 member 在范围内,不告诉你它们距离多远
搜索结果判空
if (results == null || results.getContent().isEmpty()) {return nearbyRescues; // 无结果,直接返回空列表
}
提取 Redis 搜索结果
Map<Integer, Distance> distanceMap = new HashMap<>();
List<Integer> ids = new ArrayList<>();for (GeoResult<RedisGeoCommands.GeoLocation<String>> location : results.getContent()) {Integer postId = Integer.valueOf(location.getContent().getName());Distance distance = location.getDistance();ids.add(postId);distanceMap.put(postId, distance);
}
👉 这里构建:
-
ids
:用于批量查数据库 -
distanceMap
:用于后续给每个PostsVo
设置距离属性
GeoResults
└── getContent() → List<GeoResult>└── GeoResult├── getContent() → GeoLocation│ ├── getName() → 帖子ID("123")│ └── getPoint() → 经纬度└── getDistance() → 到查询中心的距离
🔹 每个 GeoResult
里包含:
-
.getName()
→ Redis GEO 中的 member(这里是帖子 ID),实际就是 Redis GEO 中的 member -
.getDistance()
→ 距离圆心的距离
GeoResults<GeoLocation<String>> results = {List<GeoResult<GeoLocation<String>>> content = [GeoResult {content: GeoLocation {name: "123", // member → 帖子IDpoint: Point(113.2345, 23.4567) // 坐标},distance: Distance(4213.5) // 到查询点的距离(单位米)},GeoResult {content: GeoLocation {name: "124",point: Point(...)},distance: ...},...]
}
完整范围搜索的例子
@Override@Transactionalpublic ArrayList<PostsVo> getNearbyRescue(double y, double x) {GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().search(RedisKey.POSTS_ANIMALS_LOCATIONS, // Redis 中存储地理位置数据的键GeoReference.fromCoordinate(x, y), // 查询的圆心坐标 (经度, 纬度)new Distance(5000), // 查询的半径范围为 5000 米(5 公里)RedisGeoCommands.GeoSearchCommandArgs // 额外的查询参数.newGeoSearchArgs().includeDistance() // 返回结果中包含距离);//一系列的封装返回前端的操作ArrayList<PostsVo> nearbyRescues = new ArrayList<>();// 检查查询是否有结果if (results == null || results.getContent().isEmpty()) {return nearbyRescues; // 没有找到附近的帖子,返回空列表}// 用于存储 postId 和其对应的 DistanceMap<Integer, Distance> distanceMap = new HashMap<>();List<Integer> ids = new ArrayList<>();// 遍历 Geo 结果,提取 postId 和 distancefor (GeoResult<RedisGeoCommands.GeoLocation<String>> location : results.getContent()) {Integer postId = Integer.valueOf(location.getContent().getName());Distance distance = location.getDistance();ids.add(postId);distanceMap.put(postId, distance);}// 批量查询 postsList<Posts> postsList = postsMapper.selectBatchIds(ids);// 转换为 PostsVo 并将距离赋值进去nearbyRescues = postsList.stream().map(post -> {PostsVo postsVo = BeanUtil.copyProperties(post, PostsVo.class);// 假设 posts 对象中有 getPostId() 方法获取 idDistance d = distanceMap.get(post.getPostId());if(d != null){// 将距离单位转换为公里(如果需要),这里假设 d.getValue() 返回的是米postsVo.setDistance(d.getValue() / 1000.0);}// 根据animalId查询动物信息Animals animals = animalsMapper.selectById(post.getAnimalId());AnimalsVo animalsVo = BeanUtil.copyProperties(animals, AnimalsVo.class);String[] photos = animals.getPhotos().split(",");animalsVo.setPhotos(photos);postsVo.setAnimalsVo(animalsVo);return postsVo;}).collect(Collectors.toCollection(ArrayList::new));return nearbyRescues;}
Redis GEO 本质上是在 Redis 中保存地理位置数据(使用经纬度 + key + member 的形式),然后通过 GEORADIUS
/ GEOSEARCH
实现地理范围搜索。