如何使用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);
相关文章:

如何使用Redis实现附近商家查询
导读 在日常生活中,我们经常能看见查询附近商家的功能。 常见的场景有,比如你在点外卖的时候,就可能需要按照距离查询附近几百米或者几公里的商家。 本文将介绍如何使用Redis实现按照距离查询附近商户的功能,并以SpringBoot项目…...

于vue3+vite+element pro + pnpm开源项目
河码桌面是一个基于vue3viteelement pro pnpm 创建的monorepo项目,项目采用的是类操作系统的web界面,操作起来简单又方便,符合用户习惯,又没有操作系统的复杂! 有两个两个分支,一个是web版本,…...

18-组件化开发 根组件
组件化开发 & 根组件: 1. 组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为. 好处:便于维护,利于复用->提升开发效率 组件分类: 普通组件 , 根组件 2. 根组件:整个应用最上层的组件,包裹所有普通小组件…...

springboot集成ES
1.引入pom依赖2.application 配置3.JavaBean配置以及ES相关注解 3.1 Student实体类3.2 Teacher实体类3.3 Headmaster 实体类4. 启动类配置5.elasticsearchRestTemplate 新增 5.1 createIndex && putMapping 创建索引及映射 5.1.1 Controller层5.1.2 service层5.1.3 ser…...
Maven 生成编译时间和版本Java类
本文使用Maven插件来自动生成一个 Version.java 类,可以在Java代码中使用里面对应的常量,获取当前版本号和构建时间。 Maven编译后自动生成的 Version.java 文件内容如下所示: package com.shanhy.demo;public final class Version {public…...
关于uniapp微信小程序scroll-view组件使用show-scrollbar隐藏不了滚动条
这里关于使用 scroll-view组件 时候有滚动条 想要隐藏滚动条但是使用show-scrollbar没有效果 这时候又使用类名隐藏滚动条 使用id隐藏滚动条都不行 解决方法:在使用 scroll-view组件 的页面或者app 页面加上以下代码就可以了 ::-webkit-scrollbar {displa…...

CSS:filter滤镜 详解(用法 + 代码 + 例子 + 效果)
文章目录 filter 滤镜blur() 模糊度例子 渐变光晕 brightness() 元素亮度contrast() 对比度grayscale() 元素灰度hue-rorate() 色相opacity() 透明度invert() 反转颜色saturate() 饱和度 backdrop-filter 蒙版,滤镜例子 卷轴展开 filter 滤镜 动图为效果添加前后对…...

【Unity每日一记】Physics.Raycast 相关_Unity中的“X光射线”
👨💻个人主页:元宇宙-秩沅 👨💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨💻 本文由 秩沅 原创 👨💻 收录于专栏:uni…...

软件报错msvcr90.dll丢失的解决方法,亲测可以修复
我曾经遇到过一个令人头疼的问题:msvcr90.dll丢失。这个问题导致了我的程序无法正常运行,让我感到非常苦恼。然而,在经过一番努力后,我终于成功地修复了这个问题,这让我感到非常欣慰和满足。 msvcr90.dll丢失的原因可能…...
第一百一十八回 如何获取蓝牙连接状态
文章目录 知识回顾实现方法示例代码我们在上一章回中介绍了如何连接蓝牙设备相关的内容,本章回中将介绍如何获取蓝牙连接状态.闲话休提,让我们一起Talk Flutter吧。 知识回顾 我们在上一章回中介绍如何连接蓝牙设备,但是如何知道蓝牙设备是否连接成功呢?这就需要我们去获取…...
C++11并发与多线程笔记(12) windows临界区、其他各种mutex互斥量
C11并发与多线程笔记(12) windows临界区、其他各种mutex互斥量 1、windows临界区2、自动析构技术3、递归独占互斥量 std::recursive_mutex4、带超时的互斥量 std::timed_mutex 和 std::recursive_timed_mutex4.1 std::timed_mutex:是带超时的…...

[MAUI]在.NET MAUI中实现可拖拽排序列表
文章目录 创建可拖放控件创建绑定服务类拖拽(Drag)拖拽悬停,经过(DragOver)释放(Drop) 创建页面元素最终效果项目地址 .NET MAUI 中提供了拖放(drag-drop)手势识别器,允许用户通过拖…...
Linux 内核内存管理 pfn_to_online_page宏
文章目录 一、Memory Hotplug1.1 简介1.2 热插拔事件通知机制 二、pfn_to_online_page2.1 pfn_to_online_page2.2 pfn_to_section_nr2.3 online_section_nr 参考资料 一、Memory Hotplug 1.1 简介 Linux 内存热插拔(Memory Hotplug)是指在运行时动态增…...

『C语言初阶』第八章 -结构体
前言 今天小羊又来给铁汁们分享关于C语言的结构体,在C语言中,结构体类型属于一种构造类型(其他的构造类型还有:数组类型,联合类型),今天我们主要简单了解一下结构体。 一、结构体是什么&#x…...
chatgpt汇编,两个关于chatgpt的问题
1.用C写一个高级的可自主学习的AI语言模型 编写一个高级的可自主学习的AI语言模型是一个复杂且庞大的工程,需要深入的专业知识和大量的时间精力。下面是一个简化的示例,演示如何使用C实现一个基本的可自主学习的AI语言模型。 首先,你需要定…...

ps丢失d3dcompiler_47.dll怎么办,启动无反应,分享三个解决方法
d3dcompiler_47.dll64位是windows系统中重要的dll文件,缺少了它可能会引起部分软件或者游戏不能运行。 如果系统出现“找不到d3dcompiler_47.dll”或“d3dcompiler_47.dll丢失”等错误信息,那么我们就该着手修复它。 先带了解一下d3dcompiler_47.dll是什…...

第三章nginx详解
nginx:高性能,轻量级的web服务软件。 特点: 1,稳定性高。(没有apache稳定) 2,系统资源消耗地较低。(处理http请求的并发能力非常高,单台物理服务器可以处理30000-5000…...

【二叉树前沿篇】树
【二叉树前沿篇】树 1 树的概念2. 树的相关概念3. 树的表示4. 树在实际中的运用(表示文件系统的目录树结构) 1 树的概念 树是一种非线性的数据结构,它是由n(n>0)个有限结点组成一个具有层次关系的集合。把它叫做树是…...

python3 0基础学习----数据结构(基础+练习)
python 0基础学习笔记之数据结构 📚 几种常见数据结构列表 (List)1. 定义2. 实例:3. 列表中常用方法.append(要添加内容) 向列表末尾添加数据.extend(列表) 将可迭代对象逐个添加到列表中.insert(索引,插入内容) 向指定…...
计算机科学中的“旅行商问题”
题目:旅行商问题(Traveling Salesman Problem) 当初为何收藏:我收藏了这个题目是因为它是一个经典而富有挑战性的组合优化问题,涉及到计算机科学、算法设计和实际应用领域。我认为这个问题可以展示出算法设计的重要性…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...

Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...

数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)
名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 原创笔记:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 上一篇:《数据结构第4章 数组和广义表》…...
python打卡day49@浙大疏锦行
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 一、通道注意力模块复习 & CBAM实现 import torch import torch.nn as nnclass CBAM(nn.Module):def __init__…...
基于 HTTP 的单向流式通信协议SSE详解
SSE(Server-Sent Events)详解 🧠 什么是 SSE? SSE(Server-Sent Events) 是 HTML5 标准中定义的一种通信机制,它允许服务器主动将事件推送给客户端(浏览器)。与传统的 H…...