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

redis--黑马点评--用户签到模块详解

用户签到

假如我们使用一张表来存储用户签到信息,其结构应该如下:

CREATE TABLE `tb_sign` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',`user_id` bigint unsigned NOT NULL COMMENT '用户id',`year` year NOT NULL COMMENT '签到的年',`month` tinyint NOT NULL COMMENT '签到的月',`date` date NOT NULL COMMENT '签到的日期',`is_backup` tinyint unsigned DEFAULT NULL COMMENT '是否补签',PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT

假设有1000万用户,平均每人每年签到次数为10次,那么这张表一年的数据量为1亿条。还是保守估计,因此,用数据库表来存储太过浪费内存空间。

并且每一个用户签到一次需要使用(8+8+1+1+3+1)共22字节的内存,并且没有包括隐藏字段,一个月最多需要600多字节。

因此这种方式既耗内存,数据库压力还大。

那有没有比较好的方法呢?

我们按照月来统计用户签到信息,签到记录为1,未签到记录为0,这样我们只需要最多31bit就可以表示一个用户一个月的签到情况,非常节省空间,这种做法的核心思想就是把每一个比特位对应当月的每一天,形成了映射关系,用0和1表示业务状态。

这种思路就叫做位图BitMap)。

而在redis底层是利用String类型数据结构实现BitMap,因此最大上限是512M,转换为bit则是2^32个bit位。

BitMap用法

BitMap的操作命令有:

SETBIT:向指定位置(offset)存入一个0或者1

GETBIT:获取指定位置(offset)的bit值

BITCOUNT:统计BitMap中值为1的bit位的数量

BITFIELD:操作(查询、修改、自增)BitMap中bit数组中的指定位置(offset)的值

BITFIELD_RO:获取BitMap中bit数组,并以十进制形式返回

BITOP:将多个BItMap的结果做位运算(与、或、异或)

BITPOS:查找bit数组中指定范围内的第一个0或1出现的位置

命令演示:

添加

setbit:签到则为1,不签到可以不输入,默认为0

image-20250806220447565

查看redis客户端:

image-20250806220545220

查询

image-20250806220836773

BITFIELD

image-20250806221103566

在查询时 offset指定从哪读,type指定读多少bit位,并且还要指定返回的是否带符号。(因为返回的是十进制,因此要说明是否带符号,如果带符号,二进制第一位则为符号位,因此u代表无符号,i代表有符号,一般使用无符号)

举例说明:

image-20250806221721635

BITPOS

image-20250806221953803

签到功能

案例实现:签到功能

需求:实现签到接口,将当前用户当天签到信息保存到redis中

接口请求解析:

说明
请求方式Post
请求路径/user/login
请求参数
返回值

在请求解析中,我们发现请求参数与返回值都为空,这是因为我们签到所需的用户以及当天日期都可以在后端直接获取,因此不需要前端传参,也不需要返回值,但如果是补签功能的话,就需要前端传递日期参数了

注意:因为BitMap底层是基于String数据结构,因此其操作也都被封装在字符串相关操作中了。

key组成:用户+日期(原因:签到往往是以月为统计单位的,因此每个用户每个月的签到情况放在一个BitMap中,方便统计)

代码实现:

controller层:

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

Service层:

 @Overridepublic Result sign() {//1.获取当前登录用户Long id  = UserHolder.getUser().getId();//2.获取当前日期LocalDateTime now = LocalDateTime.now();//3.拼接keyString keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyy/MM"));String key = USER_SIGN_KEY + id + keySuffix;//4.获取今天是本月的第几天int dayOfMouth = now.getDayOfMonth();//5.写入Redis,setbit key offset 1stringRedisTemplate.opsForValue().setBit(key,dayOfMouth-1,true);return Result.ok();}

运行效果:

image-20250806224553581

image-20250806231348363

至此签到功能完成。

签到统计

签到统计有很多种:比如统计该月总签到次数、该月截止今天的连续签到次数等等,

那么什么叫做连续签到天数呢?

从最后一次签到开始向前统计,直到遇到第一次未签到为止,计算的总的签到次数,就是连续签到天数。

那么如何使用Java代码实现统计连续签到天数?

方法1:给每个bit位拼接逗号,然后spit(0),最后一个数组长度就是连续天数,最长的数组就是最长连续天数

方法2:从最后一个比特位开始遍历,并定义一个计数器,为1则加一,为0则终止。其中有些关键问题:

问题1:如何得到本月到今天为止的所有签到数据?

在BitMap的指令中:bitfield可以获取指定范围内的所有签到数据,而该指令需要两个参数,一个是从哪开始,另一个是查多少。因为要得到本月到今天为止的所有签到数据,因此起始脚标为0,而offset则为日期值,

由此得到指令:bitfield key get u[dayOfMonth] 0

问题2:如何从后往前的遍历每一个bit位

解答:与1做与运算,就能得到最后一个比特位。随后在右移一位,下一个bit位就成为了最后一个bit位,随后同上操作,以此类推,便可以从后向前的遍历每一个bit位。

至此,思路理顺,付诸实践

案例展示:实现签到统计功能

需求:实现下面接口,统计当前用户截止当前时间在本月的连续签到天数

请求解析:

说明
请求方式GET
请求路径/user/sign/out
请求参数
返回值连续签到天数

代码实现:

Controller层:

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

Service层:

 public Result signCount() {//1.获取当前登录用户Long id  = UserHolder.getUser().getId();//2.获取当前日期LocalDateTime now = LocalDateTime.now();//3.拼接keyString keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyy/MM"));String key = USER_SIGN_KEY + id + keySuffix;//4.获取今天是本月的第几天int dayOfMouth = now.getDayOfMonth();//5.获取本月截止今天为止的所有的签到记录 返回的是一个十进制的数字 bitfield sign:1:2025/08 get u6 0List<Long> result = stringRedisTemplate.opsForValue().bitField(key,BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMouth)).valueAt(0));if (result == null || result.isEmpty()){return Result.ok(0);}Long number = result.get(0);if (number == null || number == 0){return Result.ok(0);}//6.循环遍历int count = 0;while (true){//6.1让这个数字与1做与运算,得到数字的最后一个bit位  //判断bit位是否为0if ((number & 1) == 0) {//如果为0,说明未签到,结束break;}else {//如果不为0,说明已签到,计数器加一count++;}//把数字右移一位,抛弃最后一个bit位,继续下一个bit位的判断// 将number无符号右移一位,相当于将number除以2,并将结果赋值给numbernumber >>>= 1;}return Result.ok(count);}

效果展示:

image-20250807212259015

至此用户签到功能完成

希望对大家有所帮助

http://www.dtcms.com/a/320217.html

相关文章:

  • dubbo源码之编解码逻辑
  • 一场 Dark Theme A/B 测试的复盘与提效实践
  • 聚集索引VS非聚集索引:核心差异详解
  • rebase 和pull的通俗区别是什么
  • 一个基于固定 IP地址查询天气的 C 语言程序,通过调用第三方天气 API:
  • React 多语言(i18n)方案全面指南
  • 计算机英语详细总结
  • 本地化密码恢复工具的技术实现与应用边界
  • RabbitMQ面试精讲 Day 13:HAProxy与负载均衡配置
  • Git `cherry-pick` 工具汇总
  • Docker 加载镜像时出现 “no space left on device” 错误的解决方法
  • Java Lambda表达式:简洁高效的函数式编程
  • 关于光猫研究
  • 【代码随想录day 14】 力扣 101. 对称二叉树
  • 技法笔记3 | 验证交互式shell连接
  • LocalSqueeze(图片压缩工具) v1.0.4 压缩
  • 美图复现|Science:添加显著性的GO富集分析美图
  • Nuxt 4.0 完全指南:Nitro 引擎升级与 Composable API 深度解析
  • 关于Android studio调试功能使用
  • 如何选择适合中小企业的OA系统?XKOA低成本高定制化方案详解
  • 数据可视化Matplotlib
  • 【AI智能编程】Cursor IDE工具学习
  • P1037 [NOIP 2002 普及组] 产生数
  • vue-plugin-hiprint 打印模版使用
  • 【IQA技术专题】大模型评级IQA:Q-Align
  • 深入理解“进程屏蔽字“(Signal Mask)
  • 利用OpenVINO™ Day0快速部署端侧可用的MiniCPM-V4.0视觉大模型
  • 【代码随想录day 14】 力扣 226.反转二叉树
  • C语言memcpy函数详解:高效内存复制的实用工具
  • uniapp-vue2导航栏全局自动下拉变色