当前位置: 首页 > 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.lryc.cn/news/613481.html

相关文章:

  • JAVA,Maven继承
  • 力扣经典算法篇-46-阶乘后的零(正向步长遍历,逆向步长遍历)
  • Linux Shell为文件添加BOM并自动转换为unix格式
  • 数据分析——Pandas库
  • 什么是 TDengine IDMP?
  • 机试备考笔记 7/31
  • 学习设计模式《二十一》——装饰模式
  • 人生后半场:从广度到深度的精进之路
  • 设计模式中的行为模式
  • 多线程 future.get()的线程阻塞是什么意思?
  • tcpdump问题记录
  • 【多重BFS】Monsters
  • 【实时Linux实战系列】基于实时Linux的高频交易系统构建
  • 【C语言】深入理解编译与链接过程
  • 数据标注之数据集的类型与如何标注
  • 时间并非维度:论其作为空间变化的转换系数
  • 大模型LL04 微调prompt-Tuning方法入门(背景与发展)
  • 深度学习的视觉惯性里程计(VIO)算法优化实践
  • 数据结构学习之二叉树
  • 深度学习(2):自动微分
  • LSTM 单变量时序预测—pytorch
  • JAVA第六学:数组的使用
  • 【数据结构】二叉树练习
  • S7-1200 串行通信介绍
  • 一场 Dark Theme A/B 测试的复盘与提效实践
  • Linux上MySql CPU 占用异常
  • SpringBoot中的单例注入方式
  • windows有一个企业微信安装包,脚本执行并安装到d盘。
  • VSCode ssh一直在Setting up SSH Host xxx: Copying VS Code Server to host with scp等待
  • 开发避坑指南(20) :MyBatis操作Oracle插入NULL值异常“无效列类型1111“解决方案