附近商铺

HeJin大约 5 分钟项目实战Redis项目实战

01.GEO数据结构的基本用法

GEO就是Geolocation的简写形式,代表地理坐标。Redis在3.2版本中加入了对GEO的支持,允许存储地理坐标信息,帮助我们根据经纬度来检索数据。常见的命令有:

  • GEOADD:添加一个地理空间信息,包含:经度(longitude)、纬度(latitude)、值(member)。
  • GEODIST:计算指定的两个点之间的距离并返回。
  • GEOHASH:将指定member的坐标转为hash字符串形式并返回。
  • GEOPOS:返回指定member的坐标。
  • GEORADIUS:指定圆心、半径,找到该圆内包含的所有member,并按照与圆心之间的距离排序后返回。6.2以后已废弃。
  • GEOSEARCH:在指定范围内搜索member,并按照与指定点之间的距离排序后返回。范围可以是圆形或矩形。6.2.新功能。
  • GEOSEARCHSTORE:与GEOSEARCH功能一致,不过可以把结果存储到一个指定的key。6.2.新功能。

不同的版本命令可能有所不同,下面是Redis 5.0 windows的命令:

image-20230204184107283
image-20230204184107283

02.导入店铺数据到GEO

按照商户类型做分组,类型相同的商户作为同一组,以typeId为key存入同一个GEO集合中即可。

@Test
public void loadShopData(){
    // 1.查询店铺信息
    List<Shop> list = shopService.list();
    // 2.店铺按typeId分组
    Map<Long, List<Shop>> map = list
            .stream()
            .collect(Collectors.groupingBy(Shop::getTypeId));
    // 3.分批完成写入Redis
    for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
        // 3.1获取类型id
        Long typeId = entry.getKey();
        String key = RedisConstants.SHOP_GEO_KEY + typeId;
        // 3.2获取同类型的店铺集合
        List<Shop> shopList = entry.getValue();
        List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(shopList.size());
        // 3.3写入Redis
        for (Shop shop : shopList) {
            // stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString());
            locations.add(new RedisGeoCommands.GeoLocation<>(
                    shop.getId().toString(),
                    new Point(shop.getX(), shop.getY())));
        }
        stringRedisTemplate.opsForGeo().add(key, locations);
    }
}

查看Redis中的数据:

image-20230204184410736
image-20230204184410736

03.实现附近商户功能

接口设计

完整请求URL:http://localhost:8080/api/shop/of/type?&typeId=1&current=1&x=120.149993&y=30.334229open in new window

  • 请求地址:/shop/of/type

  • 请求方式:GET

  • 请求参数

    参数说明
    typeId商户类型id
    current页码
    x商户位置:经度
    y商户位置:维度
  • 响应格式

    {
        "success": true,
        "data": [
            {
                "id": 2,
                "name": "蔡馬洪涛烤肉·老北京铜锅涮羊肉",
                "typeId": 1,
                "images": "https://p0.meituan.net/bbia/c1870d570e73accbc9fee90b48faca41195272.jpg,http://p0.meituan.net/mogu/397e40c28fc87715b3d5435710a9f88d706914.jpg,https://qcloud.dpfile.com/pc/MZTdRDqCZdbPDUO0Hk6lZENRKzpKRF7kavrkEI99OxqBZTzPfIxa5E33gBfGouhFuzFvxlbkWx5uwqY2qcjixFEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vmIU_8ZGOT1OjpJmLxG6urQ.jpg",
                "area": "拱宸桥/上塘",
                "address": "上塘路1035号(中国工商银行旁)",
                "x": 120.151505,
                "y": 30.333422,
                "avgPrice": 85,
                "sold": 2160,
                "comments": 1460,
                "score": 46,
                "openHours": "11:30-03:00",
                "createTime": "2021-12-22T19:00:13",
                "updateTime": "2022-01-11T16:12:26",
                "distance": 170.4498
            },
            {
                "id": 9,
                "name": "羊老三羊蝎子牛仔排北派炭火锅(运河上街店)",
                "typeId": 1,
                "images": "https://p0.meituan.net/biztone/163160492_1624251899456.jpeg,https://img.meituan.net/msmerchant/e478eb16f7e31a7f8b29b5e3bab6de205500837.jpg,https://img.meituan.net/msmerchant/6173eb1d18b9d70ace7fdb3f2dd939662884857.jpg",
                "area": "运河上街",
                "address": "台州路2号运河上街购物中心F5",
                "x": 120.150598,
                "y": 30.325251,
                "avgPrice": 101,
                "sold": 2763,
                "comments": 1363,
                "score": 44,
                "openHours": "11:00-21:30",
                "createTime": "2021-12-22T19:53:59",
                "updateTime": "2022-01-11T16:13:34",
                "distance": 1000.3119
            },
            {
                "id": 8,
                "name": "浅草屋寿司(运河上街店)",
                "typeId": 1,
                "images": "https://img.meituan.net/msmerchant/cf3dff697bf7f6e11f4b79c4e7d989e4591290.jpg,https://img.meituan.net/msmerchant/0b463f545355c8d8f021eb2987dcd0c8567811.jpg,https://img.meituan.net/msmerchant/c3c2516939efaf36c4ccc64b0e629fad587907.jpg",
                "area": "运河上街",
                "address": "拱墅区金华路80号运河上街B1",
                "x": 120.150526,
                "y": 30.325231,
                "avgPrice": 88,
                "sold": 2406,
                "comments": 1206,
                "score": 46,
                "openHours": " 11:00-21:30",
                "createTime": "2021-12-22T19:51:06",
                "updateTime": "2022-01-11T16:13:25",
                "distance": 1002.1991
            },
            {
                "id": 3,
                "name": "新白鹿餐厅(运河上街店)",
                "typeId": 1,
                "images": "https://p0.meituan.net/biztone/694233_1619500156517.jpeg,https://img.meituan.net/msmerchant/876ca8983f7395556eda9ceb064e6bc51840883.png,https://img.meituan.net/msmerchant/86a76ed53c28eff709a36099aefe28b51554088.png",
                "area": "运河上街",
                "address": "台州路2号运河上街购物中心F5",
                "x": 120.151954,
                "y": 30.32497,
                "avgPrice": 61,
                "sold": 12035,
                "comments": 8045,
                "score": 47,
                "openHours": "10:30-21:00",
                "createTime": "2021-12-22T19:10:05",
                "updateTime": "2022-01-11T16:12:42",
                "distance": 1046.9829
            },
            {
                "id": 6,
                "name": "幸福里老北京涮锅(丝联店)",
                "typeId": 1,
                "images": "https://img.meituan.net/msmerchant/e71a2d0d693b3033c15522c43e03f09198239.jpg,https://img.meituan.net/msmerchant/9f8a966d60ffba00daf35458522273ca658239.jpg,https://img.meituan.net/msmerchant/ef9ca5ef6c05d381946fe4a9aa7d9808554502.jpg",
                "area": "拱宸桥/上塘",
                "address": "金华南路189号丝联166号",
                "x": 120.148603,
                "y": 30.318618,
                "avgPrice": 130,
                "sold": 9531,
                "comments": 7324,
                "score": 46,
                "openHours": "11:00-13:50,17:00-20:50",
                "createTime": "2021-12-22T19:24:53",
                "updateTime": "2022-01-11T16:13:09",
                "distance": 1741.5767
            }
        ]
    }
    

实现

ShopController

/**
 * 根据商铺类型分页查询商铺信息
 * @param typeId 商铺类型
 * @param current 页码
 * @return 商铺列表
 */
@GetMapping("/of/type")
public Result queryShopByType(
        @RequestParam("typeId") Integer typeId,
        @RequestParam(value = "current", defaultValue = "1") Integer current,
        @RequestParam(value = "x", required = false) Double x,
        @RequestParam(value = "y", required = false) Double y
) {
    return shopService.queryShopByType(typeId, current, x, y);
}

ShopServiceImplopen in new window

/**
 * 根据店铺类型和地理坐标分页查询店铺信息
 *
 * @param typeId  店铺类型id
 * @param current 页码
 * @param x       x
 * @param y       y
 * @return R
 */
@Override
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 = RedisConstants.SHOP_GEO_KEY + typeId;
    GeoResults<RedisGeoCommands.GeoLocation<String>> results =
            stringRedisTemplate.opsForGeo().radius(key,
                    new Circle(new Point(x, y), new Distance(5000, RedisGeoCommands.DistanceUnit.METERS)),
            RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(end));
    if (null == results){
        return Result.ok(Collections.emptyList());
    }
    // 4.解析id
    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());
    Map<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 FIELD(id," + idStr + ")")
            .list();
    for (Shop shop : shops) {
        shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
    }
    // 6.返回
    return Result.ok(shops);
}

结果:

image-20230204190302194
image-20230204190302194