Redis:原理速成+项目实战——Redis实战13(GEO实现附近商铺、滚动分页查询)

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:Redis:原理速成+项目实战——Redis实战12(好友关注、Feed流(关注推送)、滚动分页查询)
📚订阅专栏:Redis:原理速成+项目实战
希望文章对你们有所帮助

附近的人、附近商铺这种功能现实中很常见,很显然,这种功能需要地理坐标,Redis中刚好就有实现这类功能的数据结构——GEO。

GEO实现附近商铺

  • GEO数据结构基本用法
  • 导入店铺数据到GEO
  • 实现附近商户功能

GEO数据结构基本用法

GEO全称Geolocation,代表地理坐标,Redis允许其存储地理坐标,帮助我们根据经纬度来检索数据。

GEOADD:添加地理空间信息,包含经度、维度、值
GEODIST:计算两点距离并返回
GEOHASH:将指定member的坐标转为hash字符串形式并返回
GEOPOS:返回指定member的坐标
GEOSEARCH:在指定返回内搜索member,并按照与指定点之间的距离排序后并返回。范围可以是圆形或矩形
GEOSEARCHSTORE:与GEOSEARCH功能一致,不过可以把结果存储到一个指定的key

搜索北京天安门附近10km内的所有火车站,并按照升序排序,即可用GEO相关命令,其底层也正好是SortedSet:

GEOSEARCH g1 FROMLONLAT 经度 维度 BYRADIUS 10 km WHITDIST

其中,g1存储了北京所有火车站的经纬度,FROMLONLAT表示输入的内容是经纬度,BYRADIUS表示按照圆来搜索,WHITDIST表示带上半径。

导入店铺数据到GEO

当点击某种类型的商户的时候,就应该要发出GET请求,将商户类型,页码,经纬度都作为请求参数,并且最后根据距离位置来排序,返回List<Shop>。
因为我们要利用Redis来实现距离的计算,因此所有的商户的经纬度信息都应该要存储进去,而GEO的存储结构key-value结构,其中value是经纬度和member,这里的member我们只需要将商铺的id传进去即可。
商铺的查询是根据商铺类型来做分组的,所以要将类型相同的商铺作为同一组,将typeId为key存入GEO集合即可。

编写测试类直接运行即可:

	@Test
    void loadShopData(){
        //查询店铺信息
        List<Shop> list = shopService.list();
        //把店铺分组,按照typeId分组,id一致的放到一个集合
        Map<Long, List<Shop>> map = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));
        //分批完成导入Redis
        for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
            //获取类型id
            Long typeId = entry.getKey();
            String key = "shop:geo:" + typeId;
            //获取同类型的店铺的集合
            List<Shop> value = entry.getValue();
            //写入Redis GEOADD key 经度 维度 member
//            for (Shop shop : value) {
//                stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString());
//            }
            //上述方式更慢,可以直接传位置集合的迭代器
            List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(value.size());
            for (Shop shop : value){
                locations.add(new RedisGeoCommands.GeoLocation<>(
                        shop.getId().toString(),
                        new Point(shop.getX(), shop.getY())));
            }
            stringRedisTemplate.opsForGeo().add(key, locations);
        }
    }

在这里插入图片描述

实现附近商户功能

SpringDataRedis2.3.9不支持GEOSEARCH命令,一次你我们需要提示其版本,修改POM文件,首先我们需要将下面两个依赖exclude:
在这里插入图片描述
在这里插入图片描述
接着手动添加依赖,并指定版本:

	<dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
            <version>6.1.6.RELEASE</version>
        </dependency>

ShopController:

	@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);
    }

ShopServiceImlp:

    @Override
    public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
        //判断是否是要根据坐标查询
        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());
        }
        //计算分页参数
        int start = (current - 1) * DEFAULT_BATCH_SIZE;
        int end = current * DEFAULT_BATCH_SIZE;
        //查询Redis,按照距离来进行排序、分页,结果:shopId与distance
        String key = SHOP_GEO_KEY + typeId;//SHOP_GEO_KEY = "shop:geo:"
        //GEOSEARCH key BYLONLAT x y BYRADIUS 5000 WITHDISTANCE
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()
                .search(key,
                        GeoReference.fromCoordinate(x, y),
                        new Distance(5000),//方圆5公里以内的店铺
                        RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance()//WITHDISTANCE
                                .limit(end)//查询到的结果还要满足分页的情况,但是只能指定[0,end],剩下要逻辑分页
                );
        if(results == null){
            return Result.ok(Collections.emptyList());
        }
        //解析出id
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
        //System.out.println(list);
        if(list.size() <= start){
            //没有下一页了,没办法执行skip,直接返回
            return Result.ok(Collections.emptyList());
        }

        //收集Long类型的店铺id
        List<Long> ids = new ArrayList<>(list.size());
        Map<String, Distance> distanceMap = new HashMap<>(list.size());

        //截取start到end的分页部分,可以用stream的skip,效率更高
        list.stream().skip(start).forEach(result -> {
            //获取店铺id
            String shopIdStr = result.getContent().getName();
            //收集起来
            ids.add(Long.valueOf(shopIdStr));
            //获取距离
            Distance distance = result.getDistance();
            distanceMap.put(shopIdStr, distance);
        });
        //System.out.println(distanceMap);
        //根据id查询shop
        String idStr = StrUtil.join(",", ids);
        List<Shop> shops = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
        //需要将距离参数传入shops,返还到前端
        for (Shop shop : shops) {
            shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
        }

        return Result.ok(shops);
    }

这个代码中,查询很容易,比较有难度的地方就是做分页的时候,除了要用limit限定最低的end的范围,还要自己手动去写逻辑分页的代码,这部分比较复杂,而且我们必须要判断list.size()是否比start小,是的话才能实现这部分的逻辑分页,否则直接返回到end的查询结果,否则会报错。

如下所示,当分页的时候就会做分页查询:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/331550.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

拼多多无货源中转仓项目真的靠谱吗?发展前景如何?

阿阳最近一直在关注无货源电商这一块&#xff0c;尤其是拼多多无货源中转仓&#xff0c; 现如今也有了自己的运营团队和交付团队&#xff0c;整体来看这个项目还算不错&#xff01; 说实话&#xff0c;在考察这个项目的时候&#xff0c;看到市面上很多人在做&#xff0c;包括我…

JAVA基础-----认识异常

文章目录 1. 异常的概念与体系结构1.1 异常的概念1.2 异常的体系结构1.3 异常的分类 2. 异常的处理2.1 防御式编程2.2 异常的抛出2.3 异常的捕获2.3.1 异常声明throws2.3.2 try-catch捕获并处理2.3.3 finally 2.4 异常的处理流程 3. 自定义异常类 1. 异常的概念与体系结构 1.1…

C/C++ 基本数据类型的范围

一、常见的数据类型及其范围 数据类型Size(64位)范围int4Byteunsigned int4Bytelong4Byteunsigned long4Bytelong long8Byteunsigned long long8Byte 查询Size代码&#xff1a;sizeof(类型) 查询范围代码&#xff1a;numeric_limits<类型>::max和numeric_limits<类…

使用 mybatis-plus 的mybaits的一对多时, total和record的不匹配问题

应该是框架的问题&#xff0c;去官方仓库提了个issues&#xff0c;等回复 https://github.com/baomidou/mybatis-plus/issues/5923 回复来了&#xff1a; 背景 发现 record是两条&#xff0c;但是total显示3 使用resultMap一对多时&#xff0c;三条数据会变成两条&#xff0…

redis原理(四)redis命令

目录 一、字符串命令&#xff1a; 二、列表命令&#xff1a; 三、集合命令&#xff1a; 四、散列命令&#xff1a; 五、有序集合命令&#xff1a; 六、redis发布与订阅命令&#xff1a; 七、事务命令 八、其他命令 1、排序&#xff1a;SORT 2、键的过期时间&#xff…

代码随想录算法训练营Day23 | 455.分发饼干、376.摆动子序列、53.最大子数组和

LeetCode 455 分发饼干 本题思路&#xff1a;分发饼干的时候&#xff0c;外层循环是胃口&#xff0c;内层是饼干&#xff0c;按照大饼干满足大胃口的思维来投递饼干。 需要将 两个数组&#xff0c;一开始就进行排序处理。 class Solution {public int findContentChildren(int…

java转义字符

//转义字符的使用 public class ChangeChar{//编写一个main方法public static void main(String[] args){// \t :一个制表位&#xff0c;实现对齐的功能System.out.println("北京\t天津\t上海");// \n :换行符&#xff0c;实现换行System.out.println("jack\nsm…

SSH隧道技术

SSH隧道 简介 SSH隧道是一种通过SSH协议在两个网络节点之间建立安全通信的技术。它可以用于多种用途&#xff0c;包括加密和保护敏感数据传输、绕过防火墙限制、远程访问内部服务等。 应用&#xff1a; 端口转发&#xff1a;SSH隧道可以将本地端口转发到远程主机上&#xf…

Python学习从0到1 day5 python基础语法3 数据类型及数据类型转换

一切都会好的&#xff0c;我一直相信 ——24.1.17 一、数据类型 1.数据是有类型的 目前主要接触如下三类数据类型&#xff1a; 2.type()语句 我们可以通过type()语句来得到数据的类型 语法&#xff1a;type(被查看类型的数据) a 10 type(a) print(type(a)) print(type(11…

分子动力学模拟—LAMMPS 模拟(固体和液体)数据后处理软件(六)

记录一下检索到一篇分子动力学模拟数据后处理的软件。 感谢论文的原作者&#xff01; 主要功能&#xff1a; Structure Analysis Ackland Jones Analysis CentroSymmetry Parameter Common Neighbor Analysis Common Neighbor Parameter Atomic Structure Entropy Stein…

IOS-高德地图连续定位-Swift

使用定位功能需要需要接入高德地图定位Api&#xff1a; pod AMapLocation配置Info 在info中新建一个名为Privacy - Location Temporary Usage Description Dictionary的字典&#xff0c;然后在这个字典下新建Privacy - Location When In Use Usage Description、Privacy - Lo…

【​电力电子在电力系统中的应用​】6 滞环电流控制的PWM整流器 + STATCOM整流器 + APF仿真

【仅供参考】 【2023.06西南交大电力电子在电力系统中的应用】 目录 步骤一&#xff1a;基于滞环电流控制的PWM整流器仿真 1.1 仿真要求 1.2 仿真电路原理及设计 1.2.1 主电路的搭建 1.2.2 控制电路的搭建 1.3 波形分析 步骤二&#xff1a;从PWM整流器到STATCOM仿真 2…

使用ProxySql实现Mysql的读写分离 详细安装步骤 亲测可行

主机ip说明192.168.168.109ProxySql192.168.168.77mysql master(主&#xff09;192.168.168.78mysql slave&#xff08;从&#xff09; 1.下载ProxySql安装包 在192.168.168.109机器上操作 https://github.com/sysown/proxysql/releases/download/v2.5.5/proxysql-2.5.5-1-ce…

阿里云ack集群管理及故障处理

一、集群管理维护 二、常见故障处理 存储&#xff1a; 网络 弹性伸缩 service

Microsoft Word 设置底纹

Microsoft Word 设置底纹 References 打开文档页面&#xff0c;选中特定段落或全部文档 在“段落”中单击“边框”下三角按钮 在列表中选择“边框和底纹”选项 在“边框和底纹”对话框中单击“底纹”选项卡 在图案样式和图案颜色列表中设置合适颜色的底纹&#xff0c;单击“确…

利用蓝图直接提升客户服务体验的方法

简单地说&#xff0c;流程就是按顺序执行的一系列操作过程。每项行动都有一个结果&#xff0c;而这个结果又会成为该序列中下一项行动的输入。客户服务流程的建立目的是为了保持一致性、提高效率并帮助组织管理规模。不过&#xff0c;在实际操作的过程中&#xff0c;他们会遇到…

C#判断输入的数字是否符合货币格式

目录 一、用正则表达式判断输入是否符合货币格式 二、用double.TryParse()判断输入是否符合货币格式 一、用正则表达式判断输入是否符合货币格式 // 判断输入是否货币合格 using System.Text.RegularExpressions; namespace IsCurrency_Format {partial class Program{stati…

直播间的秒杀狂热背后,猫眼电影如何接住10w+并发运营活动?

“倒数&#xff0c;5、4、3、2、1” “10万张&#xff01;” “20秒没了” 上周末&#xff0c;张家辉和导演马浴柯带着新电影《怒潮》上了疯狂小杨哥的直播间&#xff0c;人数一度冲破80万人。 这次直播&#xff0c;是猫眼电影为新电影《怒潮》准备的一次宣传活动。 随着小…

44.5K Star,简单易用自动化运维监控工具

Hi&#xff0c;骚年&#xff0c;我是大 G&#xff0c;公众号「GitHub指北」会推荐 GitHub 上有趣有用的项目&#xff0c;一分钟 get 一个优秀的开源项目&#xff0c;挖掘开源的价值&#xff0c;欢迎关注。 今天介绍一个开源的自动化运维监控工具&#xff0c;它是一个轻量的开源…

【银行测试】银行项目,信用卡业务测试+常问面试(三)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 银行测试-信用卡业…