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.pswp.cn/web/92385.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/92385.shtml

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

相关文章

Shell、Python对比

在 Shell 脚本&#xff08;sh/bash&#xff09; 和 Python 之间选择时&#xff0c;主要取决于具体的使用场景和需求。以下是两者的对比分析&#xff0c;帮助你判断哪种更方便&#xff1a;1. Shell 脚本&#xff08;sh/bash&#xff09;的优势适用场景系统管理任务&#xff1a;如…

自适应反步控制:理论与设计

自适应反步控制 文章目录自适应反步控制1. 基本思想A. 第一步B. 第二步1. 基本思想 基于传统反步法&#xff0c;考虑了系统方程中以线性形式出现的未知参数。核心思想包括参数估计率和控制率。 考虑二阶系统&#xff1a; {x˙1x2φ1T(x1)θx˙2uφ2T(x1,x2)θ(1)\begin{cases…

[Oracle] LEAST()函数

LEAST() 是 Oracle 中一个非常有用的函数&#xff0c;用于从一组表达式中返回最小值LEAST()函数会从给定的参数列表中返回最小的值&#xff0c;它与GREATEST()函数正好相反语法格式LEAST(expr1, expr2 [, expr3, ...])参数说明expr1, expr2, ...&#xff1a;要比较的表达式(至少…

SVM算法实战应用

目录 用 SVM 实现鸢尾花数据集分类&#xff1a;从代码到可视化全解析 一、算法原理简述 二、完整代码实现 三、代码解析 1. 导入所需库 2. 加载并处理数据 3. 划分训练集和测试集 4. 训练 SVM 模型 5. 计算决策边界参数 6. 生成决策边界数据 7. 绘制样本点 8. 绘制…

深度虚值期权合约有什么特点?

本文主要介绍深度虚值期权合约有什么特点&#xff1f;深度虚值期权合约是期权市场中一类特殊且风险收益特征鲜明的合约&#xff0c;其核心特点可归纳为以下六点。深度虚值期权合约有什么特点&#xff1f;一、定义&#xff1a;执行价与标的价差距极大深度虚值期权是指执行价&…

(LeetCode 面试经典 150 题) 86. 分隔链表(链表+双指针)

题目&#xff1a;86. 分隔链表 思路&#xff1a;双指针&#xff0c;时间复杂度0(n)。 双指针来维护小于x的链表和不小于x的链表即可&#xff0c;后面将两个链表连起来即可。 C版本&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* …

安全扫描:检测到目标站点存在javascript框架库漏洞问题(vue)

如果升级Vue版本有限制或者时间比较紧急&#xff0c;可以暂时用下面方式来&#xff0c;规避检测到目标站点存在javascript框架库vue漏洞。 在 vue.config.js 中配置: module.exports {configureWebpack: {optimization: {minimizer: [new (require(terser-webpack-plugin))({t…

【性能测试】-2- JMeter工具的使用

一、基础脚本搭建&#x1f3af; 核心目标&#xff1a;用 JMeter 搭性能测试 “骨架”&#xff08;测试计划、线程组、取样器、监听器&#xff09;&#x1f4cb; 测试计划&#xff1a;整个测试的 “容器”&#xff0c;所有测试组件都放这里&#x1f9f5; 线程组&#xff1a;模拟…

从零构建桌面写作软件的书籍管理系统:Electron + Vue 3 实战指南

&#x1f4da; 从零构建桌面写作软件的书籍管理系统&#xff1a;Electron Vue 3 实战指南 &#x1f4a1; 本文深入探讨了基于 Electron Vue 3 技术栈的桌面写作软件中书籍管理系统的设计与实现&#xff0c;涵盖了书籍的创建、编辑、删除等核心功能的完整技术方案&#xff0c;…

2025华数杯数学建模A题【 多孔膜光反射性能的优化与控制】原创论文讲解(含完整python代码)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2025华数杯数学建模A题【 多孔膜光反射性能的优化与控制】完整的成品论文。本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。A题论…

网络相关(AI回答)

网管交换机都支持带tag的以太帧吗DeepSeek-R1 回答完成深度思考完成 用时24秒&#xff0c;搜索全网20篇资料是的&#xff0c;所有网管交换机都支持处理和转发带Tag&#xff08;802.1Q VLAN标签&#xff09;的以太网帧&#xff0c;这是其核心功能之一。以下是关键要点&#xff1…

每日五个pyecharts可视化图表-bars(5)

探索pyecharts库中条形图的高级用法与定制技巧&#xff0c;打造精美数据可视化图表 在数据可视化中&#xff0c;条形图是最常用的图表类型之一。它能够清晰地展示不同类别的数据对比&#xff0c;帮助我们快速理解数据特征。本文将为您介绍pyecharts库中条形图的5种高级用法&…

分布式版本控制工具Git

一.开发中为什么需要Git因为在多人开发中Git可以管理代码&#xff0c;而且每个人都可以从库里面下载代码进行修改&#xff0c;每个人上传和修改Git都会有记录&#xff0c;如果出现大错误&#xff0c;还可以回退到正常版本。二.Git原理我们首先从代码库(Remote)下载代码到工作区…

OpenAI重磅开源GPT-oss:首款支持商用的AI Agent专属模型

今日凌晨&#xff0c;OpenAI宣布开源两款全新大模型——GPT-oss-120B&#xff08;1168亿参数&#xff09;与GPT-oss-20B&#xff08;209亿参数&#xff09;&#xff0c;成为全球首个支持商业化应用的开放权重推理模型。该模型专为AI智能体&#xff08;Agent&#xff09;设计&am…

【STM32】GPIO的输入输出

GPIO是通用的输入输出接口&#xff0c;可配置8种输入模式&#xff0c;输出模式下可控制端口输出高低电平&#xff0c;用于点亮LED、控制蜂鸣器、模拟通信协议等&#xff1b;输入模式下可以读取端口的高低电平或者电压&#xff0c;用于读取按键、外接模块的电平信号、ADC的电压采…

5分钟了解OpenCV

在数字化时代&#xff0c;图像和视频已经成为信息传递的核心载体。从手机拍照的美颜功能到自动驾驶的路况识别&#xff0c;从医学影像分析到安防监控系统&#xff0c;视觉技术正深刻改变着我们的生活。而在这背后&#xff0c;OpenCV 作为一款强大的开源计算机视觉库&#xff0c…

Oracle 关闭 impdp任务

Oracle 关闭 impdp任务 执行 impdp system/123456 attachSYS_EXPORT_TABLE_01 执行 stop_jobimmediate

数据结构——链表2

1.2 实现单链表 在上一篇文章中&#xff0c;单链表的实现只有一少部分&#xff0c;这一篇接着来了解单链表剩下的接口实现。 SList.h#pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h>//定义单链表就是定义节点&#xff0c;因为单链表…

Windows和Linux应急响应以及IP封堵

目录 1、Windows入侵排查思路 1.1 检查系统账号安全 1.2 检查异常端口、进程 1.3 检查启动项、计划任务、服务 1.4 检查系统相关信息 1.5 自动化查杀 1.6 日志分析 系统日志分析 Web 访问日志 2、Linux 入侵排查思路 2.1 账号安全 2.1.1、基本使用 2.1.2、入侵排查…

MIT成果登上Nature!液态神经网络YYDS

2025深度学习发论文&模型涨点之——液态神经网络液态神经网络&#xff08;Liquid Neural Networks&#xff0c;LNN&#xff09;是一种受生物神经系统启发的连续时间递归神经网络&#xff08;RNN&#xff09;&#xff0c;其核心创新在于将静态神经网络转化为由微分方程驱动的…