目录
1. GEOADD
2. GEODIST
3. GEOHASH
3. GEOHASH
4. GEOPOS
6. GEOSEARCH
7. GEOSEARCHSTORE
应用场景
代码的逻辑分解:
比较难懂的部分:
Redis GEO 查询与分页
results 的结构:
分页处理与截取数据
附加距离信息
功能:向指定的 key 中添加地理空间信息。
参数:
功能:计算两个位置之间的距离。
参数:
m
(米)、km
(千米)、mi
(英里)、ft
(英尺)。功能:返回指定成员的 GeoHash 值。
GeoHash 是一种将经纬度编码为字符串的算法,用于地理位置的高效存储和查询。
参数:
结果:返回两点之间的距离,单位为指定的单位。
功能:返回指定成员的 GeoHash 值。
GeoHash 是一种将经纬度编码为字符串的算法,用于地理位置的高效存储和查询。
参数:
结果:返回一个字符串表示的 GeoHash 值。
功能:返回指定成员的经纬度。
参数:
结果:返回对应的经纬度数组,例如
[13.361389, 38.115556]
功能:在指定的范围内搜索成员。
m
、km
等)。ASC
或 DESC
)。WITHDIST
、WITHCOORD
。功能:将 GEOSEARCH
的结果存储到新的 key 中。
GEOSEARCH
相同。注意事项:
判断是否存在地理坐标 (x
和 y
):
if(x == null && y == null){
// 数据库分页查询
}
x
和 y
均为空,说明不需要按地理位置查询店铺,此时直接从数据库中按店铺类型(typeId
)分页查询。current
为当前页,分页大小为常量 SystemConstants.DEFAULT_PAGE_SIZE
。数据库分页查询逻辑:
Page page = query()
.eq("type_id", typeId)
.page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
处理带有地理坐标的情况: 如果提供了地理坐标,则按以下步骤处理:
计算分页范围:
int from = (current - 1) * SystemConstants.DEFAULT_PAGE_SIZE;
int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
根据当前页计算数据截取的起始位置(from
)和结束位置(end
)。
从 Redis 查询 GEO 数据:
GeoResults> results = stringRedisTemplate.opsForGeo().search(
key,
GeoReference.fromCoordinate(x, y), // 圆心为地理坐标x, y
new Distance(5000), // 搜索范围5km
RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
);
通过 Redis 的 GEO 查询:
end
。检查 Redis 查询结果是否为空:
if(results == null) return Result.ok(Collections.emptyList());
截取分页内容:
List>> content = results.getContent();
if(content.size() < from) return Result.ok(Collections.emptyList());
检查总结果是否足够多以满足当前分页,如果不足则返回空列表。
提取店铺 ID 和距离:
content.stream().skip(from).forEach(result -> {
String shopId = result.getContent().getName(); // 获取店铺ID
Distance distance = result.getDistance(); // 获取距离
ids.add(Long.valueOf(shopId));
distanceMap.put(shopId, distance);
});
skip(from)
跳过 from
之前的结果。ids
和 distanceMap
。附加距离信息:
for (Shop shop : shops) {
shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
}
返回查询结果:
return Result.ok(shops);
GeoResults> results = stringRedisTemplate.opsForGeo().search(
key, // Redis 中存储地理位置数据的键
GeoReference.fromCoordinate(x, y), // 查询的圆心坐标 (经度, 纬度)
new Distance(5000), // 查询的半径范围为 5000 米(5 公里)
RedisGeoCommands.GeoSearchCommandArgs // 额外的查询参数
.newGeoSearchArgs()
.includeDistance() // 返回结果中包含距离
.limit(end) // 限制返回的最多结果数为 end
);
limit(end)
表示查询的结果最多为 end
条。(x, y)
为中心,半径 5 公里的店铺。end
条记录。results
是 GeoResults
类型,表示 Redis GEO 查询的结果集。它包含了多个 GeoResult
对象,每个 GeoResult
对应一个位置的详细信息。
我们来具体说明 results
中的内容及其结构。
results
的结构:results
的类型是 GeoResults
,它包含:
content
:查询结果的列表,是一个 List>>
。每个 GeoResult
包括:
content
:具体的位置信息,类型是 RedisGeoCommands.GeoLocation
。
distance
:从查询圆心到该位置的距离,类型是 Distance
。results
├── content: List>>
├── GeoResult 1
│ ├── content: RedisGeoCommands.GeoLocation
│ │ ├── name: "101" // 店铺 ID
│ │ ├── point: Point(x=120.5, y=30.0) // 经纬度坐标
│ ├── distance: Distance(value=1200.0, unit=METERS) // 距离
├── GeoResult 2
│ ├── content: RedisGeoCommands.GeoLocation
│ │ ├── name: "102"
│ │ ├── point: Point(x=120.6, y=30.1)
│ ├── distance: Distance(value=1500.0, unit=METERS)
└── ...
skip(from)
)以实现分页效果。以下代码的主要作用是从 content
中提取分页后的店铺信息,并将店铺的 ID 和 距离 分别存储到两个集合中,供后续使用。distanceMap
是一个 Map
,用于记录每个店铺的 ID 和距离。ids
是一个 ArrayList
,存储所有分页后的店铺 ID。
content.stream().skip(from).forEach(result -> {
String shopId = result.getContent().getName();
Distance distance = result.getDistance();
ids.add(Long.valueOf(shopId));
distanceMap.put(shopId, distance);
});
from
条数据,提取目标页的数据。skip(from)
,因为 Redis 查询结果已经包含了 end
条数据,但需要从 from
开始取当前页。for (Shop shop : shops) {
shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
}
distanceMap
中找到对应的距离值。distance
属性中。