Redis实战—附近商铺、用户签到、UV统计

  本博客为个人学习笔记,学习网站与详细见:黑马程序员Redis入门到实战 P88 - P95

目录

附近商铺

数据导入 

功能实现

用户签到

签到功能

连续签到统计 

UV统计


附近商铺

利用Redis中的GEO数据结构实现附近商铺功能,常见命令如下图所示。 

key值由特定前缀与商户类型id组成,每个GEO存储一个店铺id与该店铺的经纬度信息,如下图所示。


数据导入 

编写单元测试,将MySql数据库中的所有商铺位置信息导入Redis中,代码如下。

@Test
void loadShopData() {// 1.查询所有店铺信息List<Shop> shops = shopService.list();// 2.将店铺按照typeId分组,typeId一致的放到一个集合中Map<Long, List<Shop>> map = shops.stream().collect(Collectors.groupingBy(Shop::getTypeId));// 3.分批完成写入Redisfor (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {// 3.1 获取类型idLong typeId = entry.getKey();String key = "shop:geo:" + typeId;// 3.2 获取同类型的店铺集合List<Shop> list = entry.getValue();// 3.3 写入redis( GEOADD key 经度 纬度 member)List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(list.size());for (Shop shop : list) {locations.add(new RedisGeoCommands.GeoLocation<>(shop.getId().toString(),new Point(shop.getX(), shop.getY())));}stringRedisTemplate.opsForGeo().add(key, locations);}
}

功能实现

由于SpringDataRedis的2.3.9版本并不支持Redis 6.2提供的GEOSEARCH命令,因此我们需要修改版本,代码如下。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><artifactId>spring-data-redis</artifactId><groupId>org.springframework.data</groupId></exclusion><exclusion><artifactId>lettuce-core</artifactId><groupId>io.lettuce</groupId></exclusion></exclusions>
</dependency>
<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>

Controller层代码如下。

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

接口方法的具体实现代码如下。

@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,distanceString key = "shop:geo:" + 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的部分ArrayList<Object> ids = new ArrayList<>(list.size());Map<String, Distance> distanceMap = new HashMap<>(list.size());list.stream().skip(from).forEach(result -> {// 4.2 获取店铺idString shopIdStr = result.getContent().getName();ids.add(Long.valueOf(shopIdStr));// 4.2 获取距离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);
}

用户签到

签到功能


Controller层代码如下。 

@PostMapping("/sign")
public Result sign() {return userService.sign();
}

接口方法的具体实现代码如下。

@Override
public Result sign() {// 1.获取当前登录用户信息Long userId = UserHolder.getUser().getId();// 2.获取当前日期LocalDateTime now = LocalDateTime.now();// 3.拼接keyString keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String key = "sign:" + userId + keySuffix;// 4.计算今天是本月的第几天int dayOfMonth = now.getDayOfMonth();// 5.写入Redis SETBIT key offset// true:写入1// false:写入0stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);return Result.ok();
}

连续签到统计 


Controller层代码如下。

@GetMapping("/sign/count")
public Result signCount() {return userService.signCount();
}

接口方法的具体实现代码如下。

@Override
public Result signCount() {// 1.获取当前登录用户信息Long userId = UserHolder.getUser().getId();// 2.获取当前日期LocalDateTime now = LocalDateTime.now();// 3.拼接keyString keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String key = "sign:" + userId + keySuffix;// 4.计算今天是本月的第几天int dayOfMonth = now.getDayOfMonth();// 5.获取本月截止今天为止的所有签到记录,返回结果是一个十进制数字 BITFIELD sign:5:202203 GET u14 0List<Long> result = stringRedisTemplate.opsForValue().bitField(key,BitFieldSubCommands.create() //创建子命令.get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)//选择子命令);if (result == null || result.isEmpty())return Result.ok(0);// 获取本月签到位图Long num = result.get(0);if (num == null || num == 0)return Result.ok(0);// 6.循环遍历int cnt = 0;//记录连续签到天数while (true) {if ((num & 1) == 0)break;num >>= 1;cnt++;}return Result.ok(cnt);
}

UV统计


测试
我们直接利用单元测试,向HyperLogLog中添加100万条数据,看看统计效果如何,测试代码如下。

@Test
void testHyperLogLog() {// 准备数组,装用户数据String[] users = new String[1000];// 数组角标int index = 0;for (int i = 1; i <= 1000000; i++) {// 赋值users[index++] = "user_" + i;// 每1000条发送一次if (i % 1000 == 0) {index = 0;stringRedisTemplate.opsForHyperLogLog().add("hll1", users);}}// 统计数量Long size = stringRedisTemplate.opsForHyperLogLog().size("hll1");System.out.println("size =" + size);
}

测试结果如下图所示。

误差 = 1 - (997593 / 1000000)≈ 0.002 可忽略不计

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

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

相关文章

vue3前端开发-如何让自己的网站适合SEO排名规则

vue3前端开发-如何让自己的网站适合SEO排名规则&#xff01;我们大家都知道&#xff0c;原始出生的vue3项目&#xff0c;原始代码层面&#xff0c;是没有meta标签的&#xff0c;也就是说&#xff0c;不适合SEO排名规则。那么我们能不能自己增加呢&#xff1f;答案是&#xff1a…

Photoneo 3D 网格划分

Photoneo 3D 网格划分是一种多功能软件解决方案&#xff0c;专为快速、精确的 3D 模型而设计 从多个 3D 扫描或来自 Photoneo 3D 传感器的连续 3D 数据流创建。它 旨在实现适用于各种应用的高级 3D 数据采集&#xff0c;例如 机器人引导、质量检查和逆向工程。 它以两个单独的库…

本地部署,edge-tts文本转语音解决方案

目录 什么是 edge-tts&#xff1f; 主要特点 应用场景 优势 开始使用 edge-tts 命令行安装 edge-tts 库&#xff1a; docker安装 未来展望 总结 https://github.com/rany2/edge-ttshttps://github.com/rany2/edge-tts 随着科技的进步&#xff0c;文本转语音&#xff…

leetcode145. 二叉树的后序遍历,递归法+迭代法,全过程图解+步步解析,一点点教会你迭代法后序遍历

leetcode145. 二叉树的后序遍历&#xff0c;递归法迭代法 给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[3,2,1] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#…

服务器系统盘存储不够,添加数据盘并挂载(阿里云)

目录 1.获取数据盘设备名称 2.为数据盘创建分区 3.为分区创建文件系统 4.配置开机自动挂载分区 阿里云数据盘挂载说明链接&#xff1a;在Linux系统中初始化小于等于2 TiB的数据盘_云服务器 ECS(ECS)-阿里云帮助中心 1.获取数据盘设备名称 sudo fdisk -lu 运行结果如下所示…

Preceptron感知机

前言 在上一章中&#xff0c;我们讨论了回归问题&#xff0c;主要的任务就是拟合出数据集分布的解析式。而这一次的学习中&#xff0c;我们将关注分类问题。 Classification classification分类有两种&#xff1a;二元分类和多类分类。 二元分类&#xff1a;预测二值目标&am…

k8s核心操作_存储抽象_K8S中使用ConfigMap抽取配置_实现配置热更新---分布式云原生部署架构搭建032

现在有个问题,是上面我们利用pv和pvc 就是持久卷 以及 持久卷申请,实现了对存储的,pod删除以后,对其使用的存储空间也进行了删除,那么还有个问题,对于redis这种我们希望,他的配置也管理起来. 比如这个redis的配置文件. 以后其他的配置文件也是这样. 使用配置文件的存储在k8s中…

Spring Boot 中使用 Resilience4j 实现弹性微服务的简单了解

1. 引言 在微服务架构中&#xff0c;服务的弹性是非常重要的。Resilience4j 是一个轻量级的容错库&#xff0c;专为函数式编程设计&#xff0c;提供了断路器、重试、舱壁、限流器和限时器等功能。 这里不做过多演示&#xff0c;只是查看一下官方案例并换成maven构建相关展示&…

Hadoop3:RPC通信原理及简单案例实现

一、场景介绍 我们知道&#xff0c;Hadoop中存在多种服务&#xff0c;那么&#xff0c;服务之间是如何通信的了&#xff1f; 比如&#xff0c;DN和NN之间如何通信&#xff1f; 这里&#xff0c;实际上是通过RPC实现进程间通信的了。 RPC属于Java网络编程范畴 需要编写客户端和…

AAD Connect自定义同步用户上云

使用场景&#xff1a;我想同步本地AD域的那些用户信息、账号上云端做SSO登录和权限管控&#xff0c;但是不希望使用快速上传一股脑传上去&#xff0c;所以使用自定义同步功能上传&#xff0c;这是一篇对AAD CONNECT这个应用的详解和配置步骤推荐 AD Connect如何自定义配置&…

隐性行为克隆——机器人的复杂行为模仿学习的新表述

介绍 论文地址&#xff1a;https://arxiv.org/pdf/2109.00137.pdf 源码地址&#xff1a;https://github.com/opendilab/DI-engine.git 近年来&#xff0c;人们对机器人学习进行了大量研究&#xff0c;并取得了许多成果。其中&#xff0c;模仿学习法尤其受到关注。这是一种从人…

iOS ------ 消息传递和消息转发

一&#xff0c;消息传递 在OC中&#xff0c;传递消息就是在对象上调用方法。 相对于C语言的方法就“静态绑定”的函数&#xff0c;在编译器就决定了运行时所要调用的函数。在OC中&#xff0c;如果向某对象传递消息&#xff0c;就会使用动态绑定机制来决定需要调用那个方法。调…

全球风味:红酒中的地域风情与特色

在红酒的世界里&#xff0c;每一滴琼浆玉液都承载着地域的风情与特色。它们不仅仅是葡萄酒&#xff0c;更是大自然的恩赐&#xff0c;是时间的馈赠&#xff0c;是人类智慧的结晶。今天&#xff0c;就让我们一起走进红酒的世界&#xff0c;感受那些来自不同地域的风情与魅力。 …

ROS2入门到精通—— 2-6 ROS2实战:可调节纯跟踪算法(局部规划)

1 Regulated Pure Pursuit 纯追踪算法变体&#xff1a;调节纯追踪算法 将自适应纯追踪&#xff08;Adaptive Pure Pursuit&#xff09;算法的特性与围绕线性速度的规则相结合&#xff0c;重点关注消费类、工业和服务型机器人的需求。我们还实现了几种常识性的安全机制&#xf…

业务终端动态分配IP-DHCP技术、DHCP中继技术

一、为什么需要DHCP? 1、许多设备(主机、无线WiFi终端等)需要动态地址的分配; 2、人工手工配置任务繁琐、容易出错,比如:IP地址冲突; 3、网络规模扩大、复杂度提高,网络配置越来越复杂,计算机的位置变化和数量超过可分配IP地址的数量,造成IP地址变法频繁以及IP地址…

Monaco 使用 DocumentHighlightProvider

Monaco 中有一个文字高亮的功能&#xff0c;就是选中一个单词&#xff0c;会高亮文字文档中所有出现该单词的位置&#xff0c;效果如下&#xff1a; Monaco 默认就有这个功能&#xff0c;可以根据具体需求进行定制。通过 registerDocumentHighlightProvider 进行注册 实现 pro…

【Java数据结构】初始线性表之一:链表

为什么要有链表 上一节我们描述了顺序表&#xff1a;【Java数据结构】初识线性表之一&#xff1a;顺序表-CSDN博客 并且进行了简单模拟实现。通过源码知道&#xff0c;ArrayList底层使用数组来存储元素。 由于其底层是一段连续空间&#xff0c;当在ArrayList任意位置插入或者…

代码随想录二刷复习(二分法)

二分法模板&#xff1a; 1&#xff1a;左闭右闭区间写法 第一种写法&#xff0c;我们定义 target 是在一个在左闭右闭的区间里&#xff0c;也就是[left, right] &#xff08;这个很重要非常重要&#xff09;。 区间的定义这就决定了二分法的代码应该如何写&#xff0c;因为定…

vue 给特定满足条件的表单数据添加背景颜色,组件的 row-class-name

1、:row-class-name"tableRowClassName" 可为表格每行根据后面的函数绑定class名 <!-- 列表框 --><div class"tableList"><el-table :data"teamModelListTable" style"width: 100%"selection-change"handleSele…

el-table表格操作列错行处理

解决方法&#xff1a; <style>::v-deep .el-table th.el-table__cell > .cell {white-space: nowrap !important;} </style>