实现思路:通过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、distanceString 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.解析出idif (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 获取店铺idString shopIdStr = result.getContent().getName();ids.add(Long.valueOf(shopIdStr));// 4.3 获取距离Distance distance = result.getDistance();distanceMap.put(shopIdStr,distance);});// 5.根据id查询ShopString 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();