如何使用Redis实现附近商家查询

 

导读

在日常生活中,我们经常能看见查询附近商家的功能。

常见的场景有,比如你在点外卖的时候,就可能需要按照距离查询附近几百米或者几公里的商家。

本文将介绍如何使用Redis实现按照距离查询附近商户的功能,并以SpringBoot项目作为举例。

想知道这样的功能是如何实现的吗?接着往下看吧!

Redis地理位置功能

Redis是一种高性能的键值存储数据库,具有快速读写能力和丰富的数据结构支持。在Redis 3.2版本之后,它引入了地理位置(Geospatial)功能,使其可以轻松处理与地理位置相关的数据。

地理位置功能的核心数据结构是有序集合(Sorted Set),它将元素与分数(score)关联起来。在地理位置功能中,分数表示地理位置的经度和纬度,而元素则是一个标识符,比如商户的ID。

我们只需要在数据库中存储商家的经纬度,以商家id作为key,经纬度作为value存入redis中,就可以通过redis命令来获得以某一个点为圆心一定范围内的商家,以及他们之间的距离。

 

常用命令

1. GEOADD:将地理位置添加到有序集合中
   使用GEOADD命令,可以将一个或多个地理位置添加到有序集合中。语法如下:

GEOADD key longitude latitude member [longitude latitude member ...]示例:GEOADD stores 116.404 39.915 "storeA"GEOADD stores 116.418 39.917 "storeB"

2. GEODIST:计算两个位置之间的距离

  GEODIST命令用于计算两个位置之间的距离,可以指定单位(米、千米、英里、英尺等)。

GEODIST key member1 member2 [unit]示例:GEODIST stores storeA storeB km

3. GEORADIUS:按照距离查询位置范围内的元素
   GEORADIUS命令用于在指定的地理位置范围内查询元素。它可以按照经纬度坐标和半径来查询,还可以限制返回的结果数量。

 GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key]示例:GEORADIUS stores 116.408 39.916 1 km WITHDIST COUNT 5

4. GEOHASH:获取位置的geohash值
   GEOHASH命令用于获取指定位置的geohash值,geohash是一种将地理位置编码成字符串的方法,可以用于快速近似的位置计算。

 GEOHASH key member [member ...]示例:GEOHASH stores storeA storeB

5. GEOPOS:获取一个或多个位置的经纬度坐标
   GEOPOS命令用于获取一个或多个位置的经纬度坐标。

GEOPOS key member [member ...]示例:GEOPOS stores storeA storeB

6. GEORADIUSBYMEMBER:根据成员获取范围内的元素
   这个命令与GEORADIUS类似,但是它以一个已有的成员作为中心点进行查询。

 GEORADIUSBYMEMBER key member radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key]示例:GEORADIUSBYMEMBER stores storeA 1 km

地理位置功能不仅在查询附近商户等实际应用中非常有用,还可以应用于地理分析、位置推荐等领域。它通过利用Redis强大的有序集合数据结构,使得处理地理信息变得高效、灵活,并且易于集成到现有的应用中。无论是构建LBS应用还是处理位置相关数据,Redis的地理位置功能都能为开发者提供强大的支持。

Java代码实现

将数据库中的商家经纬度存入redis

数据库中有一张商家表,其中有经度,纬度这两个字段。我们可以通过单元测试批量将这些商家的经纬度数据存入redis。key为商家id,value为经纬度。

/*** 将数据库中的商户坐标添加到缓存*/@Testvoid addShopGeo2Redis(){//获取商户集合List<Shop> list = shopService.list();//根据商户类型分类Map<Long, List<Shop>> collect = list.stream().collect(Collectors.groupingBy(Shop::getTypeId));for (Map.Entry<Long, List<Shop>> longListEntry : collect.entrySet()) {Long typeId = longListEntry.getKey();String key = "shop:geo:" + typeId;//获取商户经纬度List<Shop> shopList = longListEntry.getValue();List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(shopList.size());for (Shop shop : shopList) {
//                stringRedisTemplate.opsForGeo().add(key,new Point(shop.getX(),shop.getY()),shop.getId().toString());//先收集完所有商户的地理位置,再一次性添加到redislocations.add(new RedisGeoCommands.GeoLocation<>(shop.getId().toString(),new Point(shop.getX(),shop.getY())));}stringRedisTemplate.opsForGeo().add(key,locations);}}

接口类:queryShopByType(typeId,current,x,y)

定义一个根据商家类型查询所有商家的接口,如果前端传来的参数中携带该用户的经纬度,则代表需要根据距离查询附近商家。

  /*** 根据商铺类型分页查询商铺信息* @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);}

服务类:queryShopByType(typeId,current,x,y)

1.首先判断是否经纬度参数x和y是否为空

2.计算分页参数(redis无法分页,需要手动分页)

3.查询redis

4.获取商户id集合

5.根据商户id查询数据库

6.返回

  @Overridepublic 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),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());Map<String, Distance> distanceMap = new HashMap<>(list.size());list.stream().skip(from).forEach(result -> {String shopIdStr = result.getContent().getName();ids.add(Long.valueOf(shopIdStr));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 FIELD(id," + idStr + ")").list();for (Shop shop : shops){shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());}//6.返回return Result.ok(shops);}
}

注意点

1.redis查询的结果是从第1条到第end条,不能直接返回第begin条到第end条。

那么如何跳过begin前面的记录呢?

可以使用stream()流的skip()方法,skip()方法中指定参数begin,就会跳过前面的begin条记录。

2.通过redis获取的ids集合,再使用mybatis-plus使用query().in()进行查询时,会破坏数据顺序,如何解决?

手动指定顺序。在后面加上last("ORDER BY FIELD(id," + idStr + ")").list()。而idStr = StrUtil.join(",",ids);

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

时序预测 | MATLAB实现WOA-CNN-LSTM鲸鱼算法优化卷积长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现WOA-CNN-LSTM鲸鱼算法优化卷积长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现WOA-CNN-LSTM鲸鱼算法优化卷积长短期记忆神经网络时间序列预测预测效果基本介绍模型描述程序设计学习总结参考资料 预测效果 基本介绍 时序预测 | MATLAB实现WOA-…

IDEA开发项目时一直出现http404错误的解决方法

系列文章目录 安装cv2库时出现错误的一般解决方法_cv2库安装失败 SQL&#xff1e; conn sys/root as sysdbaERROR:ORA-12560: TNS: 协议适配器错误的解决方案 虚拟机启动时出现“已启用侧通道缓解”的解决方法 Hypervisor launch failed&#xff1b; Processor does not pr…

git压缩/合并多次commit提交为1次commit提交

git压缩/合并N次commit提交为1次commit提交 假设有最近3次提交&#xff1a; commit_id1 commit_id2 commit_id3目标是把以上3次commit合并成1个commit&#xff0c;注意&#xff0c;最新的commit提交在最上面。 在git bash里面的操作步骤&#xff1a; &#xff08;1&#xff0…

Hyperledger Fabric的使用及开发

Hyperledger Fabric是Linux基金会发起的一种跨行业的区块链技术&#xff0c;目前在多家大型公司有着应用&#xff0c;这里就不多做HF本身的介绍了&#xff0c;有兴趣可关注其官网。 1. 准备工作&#xff1a; 开始前需要一定的准备工作&#xff0c;安装各类中间件&#xff1a;…

Python学习笔记_基础篇(五)_数据类型之字典

一.基本数据类型 整数&#xff1a;int 字符串&#xff1a;str(注&#xff1a;\t等于一个tab键) 布尔值&#xff1a; bool 列表&#xff1a;list 列表用[] 元祖&#xff1a;tuple 元祖用&#xff08;&#xff09; 字典&#xff1a;dict 注&#xff1a;所有的数据类型都存在想对…

【自创】关于前端js的“嵌套地狱”的遍历算法

欢迎大家关注我的CSDN账号 欢迎大家关注我的哔哩哔哩账号&#xff1a;卢淼儿的个人空间-卢淼儿个人主页-哔哩哔哩视频 此saas系统我会在9月2号之前&#xff0c;在csdn及哔哩哔哩上发布成套系列教学视频。敬请期待&#xff01;&#xff01;&#xff01; 首先看图 这是我们要解…

使用nrm快速切换npm源以及解决Method Not Implemented

文章目录 什么是nrm如何使用nrm查看本机目前使用的npm 源安装nrm查看可选源查看当前使用源切换源添加源删除源测试源的响应时间 如果你遇到这个报错&#xff0c;就可以采用这种方案解决哦解决方案&#xff1a;1. 切换为官方源2. 查看漏洞3. 修复漏洞4. 下面命令慎重使用&#x…

Jmeter 分布式性能测试避坑指南

在做后端服务器性能测试中&#xff0c;我们会经常听到分布式。那你&#xff0c;是否了解分布式呢&#xff1f;今天&#xff0c;我们就来给大家讲讲&#xff0c;在企业实战中&#xff0c;如何使用分布式进行性能测试&#xff0c;实战过程中&#xff0c;又有哪些地方要特别注意&a…

Docker 练习2 安装MySQL

一、实验要求 1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 2、安装搭建私有仓库 Harbor 3、编写Dockerfile制作Web应用系统nginx镜像&#xff0c;生成镜像nginx:v1.1&#xff0c;并推送其到私有仓库。具体要求如下&#xff1a; &#xff08;1&#xff09…

ES的索引结构与算法解析

提到ES&#xff0c;大多数爱好者想到的都是搜索引擎&#xff0c;但是明确一点&#xff0c;ES不等同于搜索引擎。不管是谷歌、百度、必应、搜狗为代表的自然语言处理(NLP)、爬虫、网页处理、大数据处理的全文搜索引擎&#xff0c;还是有明确搜索目的的搜索行为&#xff0c;如各大…

SpringBoot + Vue 微人事(十二)

职位批量删除实现 编写后端接口 PositionController DeleteMapping("/")public RespBean deletePositionByIds(Integer[] ids){if(positionsService.deletePositionsByIds(ids)ids.length){return RespBean.ok("删除成功");}return RespBean.err("删…

数据结构 - 算法的时间效率和空间效率

一、时间效率 程序在计算机上执行所消耗的时间。 两种估算方式&#xff1a; 事后统计事前分析 算法运行时间 一个简单操作所需的时间X简单操作次数 算法运行总时间 Σ每条语句执行次数&#xff08;即&#xff1a;每条语句频度&#xff09;X该语句执行一次所需的时间 每条语…

[国产MCU]-W801开发实例-开发环境搭建

W801开发环境搭建 文章目录 W801开发环境搭建1、W801芯片介绍2、W801芯片特性3、W801芯片结构4、开发环境搭建1、W801芯片介绍 W801芯片是联盛德微电子推出的一款高性价比物联网芯片。 W801 芯片是一款安全 IoT Wi-Fi/蓝牙 双模 SoC芯片。芯片提供丰富的数字功能接口。支持2.…

域名解析和代理

购买域名 这里使用腾讯云进行购买。 对域名进行解析 通过添加记录接口对域名进行解析。 此时我们的服务器地址就被解析到域名上了。 我们可以通过以下格式进行访问&#xff1a; [域名]:[对应的项目端口] 效果为下: 通过nginx进行代理 如果我们使用上述的方式进行访问还是…

city walk结合VR全景,打造新时代下的智慧城市

近期爆火的city walk是什么梗&#xff1f;它其实是近年来备受追捧的城市漫步方式&#xff0c;一种全新的城市探索方式&#xff0c;与传统的旅游观光不同&#xff0c;城市漫步更注重与城市的亲密接触&#xff0c;一步步地感受城市的脉动。其实也是一种自由、休闲的方式&#xff…

aardio开发语言Excel数据表读取修改保存实例练习

import win.ui; /*DSG{{*/ var winform win.form(text"aardio form";right759;bottom479) winform.add( buttonEnd{cls"button";text"末页";left572;top442;right643;bottom473;z6}; buttonExcelRead{cls"button";text"读取Exce…

adb devices存在连接emulator-5554怎么办

执行adb kill-server 发现还是有5554这条数据&#xff0c;可以采用window杀死端口号的方法。 netstat -ano | findstr 5554 &#xff0c;去查看pid是什么 得到pid&#xff0c;杀死这个pid taskkill /f /pid xxx

C语言笔试训练【第12天】

文章目录 1、请阅读以下程序&#xff0c;其运行结果是&#xff08; &#xff09;2、假设编译器规定 int 和 short 类型长度分别为32位和16位&#xff0c;若有下列C语言语句&#xff0c;则 y 的机器数为&#xff08; &#xff09;3、下列程序的输出结果是什么&#xff08; &…

腾讯云GPU服务器GN7实例NVIDIA T4 GPU卡

腾讯云GPU服务器GN7实例搭载1颗 NVIDIA T4 GPU&#xff0c;8核32G配置&#xff0c;系统盘为100G 高性能云硬盘&#xff0c;自带5M公网带宽&#xff0c;系统镜像可选Linux和Windows&#xff0c;地域可选广州/上海/北京/新加坡/南京/重庆/成都/首尔/中国香港/德国/东京/曼谷/硅谷…

信号

信号也是IPC中的一种&#xff0c;是和管道&#xff0c;消息队列&#xff0c;共享内存并列的概念。 本文参考&#xff1a; Linux中的信号_linux中信号_wolf鬼刀的博客-CSDN博客 Linux系统编程&#xff08;信号处理 sigacation函数和sigqueue函数 )_花落已飘的博客-CSDN博客 Linu…