实现思路:通过Redis中的GEO数据结构进行实现
一、GEO命令:
1.命令示例:
GEOADD g1 116.378248 39.865275 bjn 116.42803 39.903738 bjz 116.322287 39.893729 bjx
输出结果:
2.计算bjx(北京西站)到bjn(北京南站)的举例
3.查询天安门附近10公里的火车站
二、举例
问题场景:用户需要查询附近的商铺、KTV等,商铺和KTV属于不同类型,用户会选择一种类型。
问题解析:
- 可能用户不会开启定位功能,所以我们只需要根据类型查询
- 需要进行分页返回
代码:
Shop实体类
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_shop")
public class Shop implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 商铺名称
*/
private String name;
/**
* 商铺类型的id
*/
private Long typeId;
/**
* 商铺图片,多个图片以','隔开
*/
private String images;
/**
* 商圈,例如陆家嘴
*/
private String area;
/**
* 地址
*/
private String address;
/**
* 经度
*/
private Double x;
/**
* 维度
*/
private Double y;
/**
* 均价,取整数
*/
private Long avgPrice;
/**
* 销量
*/
private Integer sold;
/**
* 评论数量
*/
private Integer comments;
/**
* 评分,1~5分,乘10保存,避免小数
*/
private Integer score;
/**
* 营业时间,例如 10:00-22:00
*/
private String openHours;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
// 该注解设置为FALSE,代表数据库中是不存在的,是用于方便向前端返回数据
@TableField(exist = false)
private Double distance;
}
逻辑代码:
public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
// 1.判断是否需要根据坐标查询
if (x == null || y == null) {
// 不需要坐标查询,按数据库查询
Page<Shop> page = query()
.eq("type_id",typeId)
.page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
// 返回数据
return Result.ok(page.getRecords());
}
// 2.计算分页参数
int from = (current -1) * SystemConstants.DEFAULT_PAGE_SIZE;
int end = current * SystemConstants.DEFAULT_PAGE_SIZE;
// 3.查询redis,按照举例排序,分页。结果:shopId、distance
String key = SHOP_GEO_KEY + typeId;
GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()
.search(
key,
GeoReference.fromCoordinate(x, y),
// 这里默认单位是米,这里的单位和查询结果的单位一致
new Distance(5000),
// 这里的limit只能从最开始查询到end,所以后面需要截取from ~ end的部分
RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end)
);
// 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的部分
List<Long> ids = new ArrayList<>(list.size());
HashMap<String, Distance> distanceMap = new HashMap<>(list.size());
list.stream().skip(from).forEach(result -> {
// 4.2 获取店铺id
String shopIdStr = result.getContent().getName();
ids.add(Long.valueOf(shopIdStr));
// 4.3 获取距离
Distance distance = result.getDistance();
distanceMap.put(shopIdStr,distance);
});
// 5.根据id查询Shop
String idStr = StrUtil.join(",",ids);
List<Shop> shops = query().in("id", ids).last("ORDER BY FIEID(id," + idStr + ")").list();
for (Shop shop : shops) {
shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
}
// 6.返回
return Result.ok(shops);
}
5中根据id查询shop,使用last在最后拼接sql语句是为了保障查询的有序性,详情见博客
List<Shop> shops = query().in("id", ids).last("ORDER BY FIEID(id," + idStr + ")").list();