当前位置: 首页 > news >正文

JAVA-实战8 Redis实战项目—雷神点评(10)附近商铺

だってだって、いまが最高!

附近商铺

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.新功能

基本命令用法

添加地理位置成员命令

GEOADD key [NX | XX] [CH] longitude latitude member [longitude latitude member ...]

key‌: 存储地理位置的键名。
longitude‌: 经度。
latitude‌: 纬度。
member‌: 位置元素的名称(唯一标识)。
选项‌:

NX: 仅添加新元素,不更新已存在的元素。
XX: 仅更新已存在的元素,不添加新元素。NXXX互斥,不能同时使用
CH: 修改返回值,返回被更改的元素总数(包括新增的和坐标被更新的),而不仅仅是新增的数量。

注意:

经纬度顺序‌:必须遵循 先经度Longitude),后纬度Latitude)的顺序(即x, y格式)。
有效范围‌:

经度:-180180度。
纬度:-85.0511287885.05112878度。
注意:靠近两极的区域无法被索引,超出上述范围坐标会报错。

返回指定member的坐标的命令GEOPOS

GEOPOS key member [member ...]

image
image

计算指定的两个点之间的距离并返回的命令GEODIST

GEODIST key member1 member2 [unit]

m(米, 默认), km(千米), mi(英里), ft(英尺)

image

在指定范围内搜索的命令GEOSEARCH

-- 以指定坐标为中心查询
GEOSEARCH key FROMLONLAT longitude latitude BYRADIUS distance unit [WITHDIST] [WITHCOORD]-- 以指定成员为中心查询
GEOSEARCH key FROMMEMBER member BYRADIUS distance unit

longitudelatitude表示经纬度
distanceunit表示距离和单位
WITHDIST表示返回结果是否包含距离
WITHCOORD表示返回结果是否包含坐标
member表示成员

image

导入商铺数据到GEO

导入商铺数据时需要根据商铺类型进行分组
image

方法一

@Autowired
private ShopMapper shopMapper;
@Autowired
private StringRedisTemplate stringRedisTemplate;@Test
public void LoadShopData() {List<ShopData> ShopList = shopMapper.selectList(null);Map<Long,List<ShopData>> ShopMap = ShopList.stream().collect(Collectors.groupingBy(ShopData::getTypeId));for(Map.Entry<Long,List<ShopData>> ShopEntity : ShopMap.entrySet()) {Long TypeId = ShopEntity.getKey();String ShopGEOKey = SHOP_GEO_KEY+TypeId;List<ShopData> ValueList = ShopEntity.getValue();// 提取每一条数据然后保存到Redis中for(ShopData Shop : ValueList) {stringRedisTemplate.opsForGeo().add(ShopGEOKey,new Point(Shop.getX(),Shop.getY()),Shop.getId().toString());}}System.out.println(ShopMap);
}

方法二

@Autowired
private ShopMapper shopMapper;
@Autowired
private StringRedisTemplate stringRedisTemplate;@Test
public void LoadShopData() {List<ShopData> ShopList = shopMapper.selectList(null);Map<Long,List<ShopData>> ShopMap = ShopList.stream().collect(Collectors.groupingBy(ShopData::getTypeId));for(Map.Entry<Long,List<ShopData>> ShopEntity : ShopMap.entrySet()) {Long TypeId = ShopEntity.getKey();String ShopGEOKey = SHOP_GEO_KEY+TypeId;List<ShopData> ValueList = ShopEntity.getValue();List<RedisGeoCommands.GeoLocation<String>> ShopLocations = new ArrayList<>();// 先把数据提取到一个列表中,然后统一保存到Redis中for(ShopData Shop : ValueList) {ShopLocations.add(new RedisGeoCommands.GeoLocation<>(Shop.getId().toString(),new Point(Shop.getX(),Shop.getY())));}stringRedisTemplate.opsForGeo().add(ShopGEOKey,ShopLocations);}System.out.println(ShopMap);
}

封装常量工具类:

public class RedisConstants {public static final String SHOP_GEO_KEY = "shop:geo:";
}

执行后效果如下:
image
image

查看附近的商户

实现根据商铺类型,查询附近最近的商铺并且实现滚动分页查询

实现代码如下:

// 控制层方法
@RestController
@RequestMapping("/shop")
public class ShopController {@Autowiredprivate ShopService shopService;@GetMapping("/of/type")public ResultData QueryShopByType(@RequestParam("TypeId") Integer TypeId,@RequestParam(value="CurrentPage",defaultValue="1") Integer CurrentPage,@RequestParam(value="x",required = false) Double x,@RequestParam(value="y",required = false) Double y){return shopService.QueryShopByType(TypeId,CurrentPage,x,y);}
}// 服务层方法
@Service
public class ShopServiceImpl implements ShopService {@Autowiredprivate ShopMapper shopMapper;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic ResultData QueryShopByType(Integer TypeId, Integer CurrentPage, Double x, Double y) {// 如果没给位置根据列表进行查询if(x==null || y==null) {IPage page = new Page(CurrentPage,DEFAULT_PAGE_SIZE);LambdaQueryWrapper<ShopData> ew = new LambdaQueryWrapper<ShopData>();ew.eq(ShopData::getTypeId,TypeId);shopMapper.selectPage(page,ew);return ResultData.success(page.getRecords());}int Start = (CurrentPage-1)*DEFAULT_PAGE_SIZE;int End = CurrentPage*DEFAULT_PAGE_SIZE;String ShopGeoKey = SHOP_GEO_KEY+TypeId;GeoResults<RedisGeoCommands.GeoLocation<String>> ShopResults =stringRedisTemplate.opsForGeo().search(ShopGeoKey,GeoReference.fromCoordinate(x,y),new Distance(5000),RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(End));List<GeoResult<RedisGeoCommands.GeoLocation<String>>> ShopResultsList = ShopResults.getContent().stream().skip(Start).limit(DEFAULT_PAGE_SIZE).toList();return ResultData.success(ShopResultsList);}
}

其中值得注意的点:

stringRedisTemplate.opsForGeo().search()方法执行查询时只能使用limit方法规定结束位置,无法规定开始位置,需要后续使用stream()流重新截取

封装常量工具类

public class SystemConstants {public static final int DEFAULT_PAGE_SIZE = 5;public static final int MAX_PAGE_SIZE = 10;
}

配置分页拦截器

@Configuration
public class MpConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 添加分页插件return interceptor;}
}

执行后查看效果如下,可以看到GeoResult<RedisGeoCommands.GeoLocation<String>>封装的类型:
image

接下来,完善查询方法,从中间结果集中提取店铺Id,然后传递店铺Id列表查询店铺完整信息,同时使用map结构维护店铺和当前位置的距离信息

@Override
public ResultData QueryShopByType(Integer TypeId, Integer CurrentPage, Double x, Double y) {if(x==null || y==null) {IPage page = new Page(CurrentPage,DEFAULT_PAGE_SIZE);LambdaQueryWrapper<ShopData> ew = new LambdaQueryWrapper<ShopData>();ew.eq(ShopData::getTypeId,TypeId);shopMapper.selectPage(page,ew);return ResultData.success(page.getRecords());}int Start = (CurrentPage-1)*DEFAULT_PAGE_SIZE;int End = CurrentPage*DEFAULT_PAGE_SIZE;String ShopGeoKey = SHOP_GEO_KEY+TypeId;GeoResults<RedisGeoCommands.GeoLocation<String>> ShopResults =stringRedisTemplate.opsForGeo().search(ShopGeoKey,GeoReference.fromCoordinate(x,y),new Distance(5000),RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(End));System.out.println(ShopResults);if(ShopResults==null) {return ResultData.success("There is no Shop nearby!");}Map<Long,Distance> DistanceMap = new HashMap<>();List<Long> ShopResultsList = ShopResults.getContent().stream().map(ShopResult ->{DistanceMap.put(Long.parseLong(ShopResult.getContent().getName()),ShopResult.getDistance());return Long.parseLong(ShopResult.getContent().getName());}).skip(Start).limit(DEFAULT_PAGE_SIZE).toList();// 需要进行列表判空,否则传递的IdStr生成的SQL语句执行查询时会报错if(ShopResultsList.isEmpty()) {return ResultData.success("There is no Shop nearby!");}String IdStr = ShopResultsList.stream().map(String::valueOf).collect(Collectors.joining(","));QueryWrapper<ShopData> ew = new QueryWrapper<ShopData>();ew.in("id",ShopResultsList).orderByAsc("FIELD(id,"+IdStr+")");List<ShopData> ShopList = shopMapper.selectList(ew);for(ShopData Shop : ShopList) {Shop.setDistance(DistanceMap.get(Shop.getId()).getValue());}return ResultData.success(ShopList);
}

重新测试,效果如下:
image
image

http://www.jsqmd.com/news/779611/

相关文章:

  • 内存标准演进:如何平衡性能、功耗与尺寸,塑造消费电子体验
  • 基于注意力机制的时间序列异常检测实践与优化
  • 静态分析工具smellcheck:自动检测代码坏味道,提升软件质量
  • Cursor文档自动生成钩子:基于事件驱动实现代码与文档同步
  • 【LSF集群搭建】10-部署FlexNet许可证服务器
  • Cursr:多屏多设备无缝交互的鼠标门户工具配置指南
  • 茉莉花插件:3大功能彻底解决Zotero中文文献管理难题
  • 商业信任构建:从制度、声誉到技术工具的系统性实践
  • Helius Core AI:Solana 开发者的AI智能体工具集深度解析
  • TC3xx汽车以太网实战:手把手教你用MCAL配置RGMII接口与125MHz时钟(避坑GETH初始化失败)
  • 20260508(2)
  • 3DThinker:几何直觉与视觉语言模型的融合创新
  • ArmForge并行程序Profile工具
  • Youtu-VL:统一自回归框架的视觉语言模型解析
  • 前端实战:从设计稿到高性能网页的全链路开发指南
  • 如何用AI生成Logo?我对比了7个AI Logo生成器,简单、高效、专业 - 企业数字化观察家
  • 自建LinkVault:打造私有化链接管理系统的技术架构与部署实践
  • Skill 学习篇(九)| 编排框架 · OpenSpec 专篇(1→10 阶段)
  • V-Bridge:视频生成先验驱动的少样本图像修复技术
  • 对比直接使用官方API通过聚合平台管理成本的优势体验
  • QOwnNotes:基于Markdown文件与脚本的本地知识管理方案解析
  • Awesome MCP Hub:AI应用开发者的MCP服务器资源导航与实战指南
  • Mac/Windows系统下Jupyter Notebook报500错误的终极排查指南(附conda环境解决方案)
  • Matsumiko/runbook:代码化运维手册,实现故障处理自动化与知识沉淀
  • 从图像到数据:如何用WebPlotDigitizer解锁科研图表中的隐藏信息宝库
  • 【LSF集群搭建】7-为集群打补丁
  • iGRPO:基于自反馈机制的大语言模型推理优化方法
  • 别再被AUTOSAR通信协议栈搞懵了!手把手教你从DBC导入到无错配置(CAN/CANIF/PDUR/COM全流程)
  • Robert Griesemer 亲述:只解决 90% 问题的“箭头函数”该长什么样?
  • 2026 年教育培训行业 GEO 服务商排行榜,五大实力机构深度盘点 - GEO优化