基于Redis实现-用户签到
基于Redis实现-用户签到
这个功能将使用到Redis中的BitMap来实现。
我们按照月来统计用户签到信息,签到记录为1,未签到则记录为0
把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路称为位图(BitMap)。
Redis中是利用String类型数据结构实现BitMap,因此最大上限是512M,转化为bit则是2的32次方个bit位。相比于使用数据库字段来存储,内存使用大大减小。
1.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出现的位置
语法演示:
1. SETBIT - 用户签到
# 用户ID 1001 在第5天签到(offset从0开始)
SETBIT user:sign:1001 4 1# 用户ID 1001 在第10天签到
SETBIT user:sign:1001 9 1
#-------------------------------------------------------------------------------------------
#2. GETBIT - 检查某天是否签到
# 检查用户ID 1001 第5天是否签到
GETBIT user:sign:1001 4
# 返回1表示已签到# 检查用户ID 1001 第6天是否签到
GETBIT user:sign:1001 5
# 返回0表示未签到
#-------------------------------------------------------------------------------------------
#3. BITCOUNT - 统计签到总天数
# 统计用户ID 1001 本月签到总天数
BITCOUNT user:sign:1001
# 返回签到的总天数
#-------------------------------------------------------------------------------------------
#4. BITFIELD - 批量操作签到数据
# 获取用户ID 1001 前5天的签到情况(以无符号5位整数形式返回)
BITFIELD user:sign:1001 GET u5 0# 同时设置多个签到日
BITFIELD user:sign:1001 SET u1 15 1 SET u1 16 1
#-------------------------------------------------------------------------------------------
#5. BITFIELD_RO - 只读方式获取签到数据
# 安全地获取用户ID 1001 前10天的签到情况
BITFIELD_RO user:sign:1001 GET u10 0
#-------------------------------------------------------------------------------------------
#6. BITOP - 多用户签到情况统计
# 创建两个用户的签到数据
SETBIT user:sign:1001 0 1
SETBIT user:sign:1001 1 1
SETBIT user:sign:1002 0 1# 统计哪些天数两个用户都签到了(按位与操作)
BITOP AND both_sign user:sign:1001 user:sign:1002# 查看结果
GETBIT both_sign 0 # 返回1,表示第1天都签到了
GETBIT both_sign 1 # 返回0,表示第2天不是都签到了
#-------------------------------------------------------------------------------------------
#7. BITPOS - 查找连续签到
# 查找用户ID 1001 第一次签到的位置(从第0位开始查找值为1的位)
BITPOS user:sign:1001 1# 查找用户ID 1001 第一次未签到的位置
BITPOS user:sign:1001 0
#-------------------------------------------------------------------------------------------
#完整签到系统示例
# 用户1001连续7天的签到情况(1已签,0未签)
SETBIT user:sign:1001 0 1 # 第1天
SETBIT user:sign:1001 1 1 # 第2天
SETBIT user:sign:1001 2 0 # 第3天未签
SETBIT user:sign:1001 3 1 # 第4天
SETBIT user:sign:1001 4 1 # 第5天
SETBIT user:sign:1001 5 0 # 第6天未签
SETBIT user:sign:1001 6 1 # 第7天# 查询签到情况
BITCOUNT user:sign:1001 # 返回5(共签到了5天)
BITPOS user:sign:1001 0 # 返回2(第一个未签到的位置)
2.使用Java实现简单的用户签到
注意:因为BitMap底层是基于String数据结构,因此其操作都封装在字符串操作中。
1.实现用户签到
//在ServiceImpl@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic 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.写入redisstringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);return Result.ok();}
2.Java实现统计连续签到
1.什么是;连续签到天数?
从最后一次签到开始向前统计,直到遇到第一次未签到为止,计算总的签到次数,就是连续的签到天数。
2.如何得到本月到今天为止的所有签到数据?
BITFIELD key GET u[dayOfMonth] 0
3.如何从后向前遍历每个bit位?
与1做与运算,就能得到最后一个bit位。
随后右移一位,下一个bit为就成为了最后一个bit位。
//在ServiceImpl@Autowiredprivate StringRedisTemplate stringRedisTemplate;
@Override
public Result signCount() {// 1.获取当前登录用户IDLong UserId = UserHolder.getUser().getId();// 2.获取当前日期时间(带时区)LocalDateTime now = LocalDateTime.now();// 3.拼接Redis键:sign:用户ID:年月(例如:sign:1001:202310)String KeySuffix = now.format(DateTimeFormatter.ofPattern("yyyyMM"));String key = "sign:" + UserId + ":" + KeySuffix;// 4.获取今天是本月的第几天(1-31)int dayOfMonth = now.getDayOfMonth();// 5.使用BITFIELD命令获取本月签到数据的位图(返回无符号整数)// 格式:BITFIELD key GET u<dayOfMonth> 0// 表示从偏移量0开始,获取dayOfMonth长度的无符号整数List<Long> longs = stringRedisTemplate.opsForValue().bitField(key,BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));// 5.1 处理空结果情况if (longs == null || longs.isEmpty()) {return Result.ok(0); // 无签到记录返回0}// 5.2 获取位图转换后的十进制数值Long num = longs.get(0);if (num == null || num == 0) {return Result.ok(0); // 数值为0表示无签到}// 6.通过位运算计算连续签到天数int count = 0;while (true) {// 6.1 检查最低位是否为1(与1做按位与运算)// 结果为0表示未签到,1表示已签到if ((num & 1) == 0) {break; // 遇到未签到日终止循环} else {count++; // 签到日计数器+1}// 6.2 无符号右移一位(相当于删除已检查的最低位)// 例如:1011(11) >>> 1 = 0101(5)num >>>= 1;}// 7.返回连续签到天数return Result.ok(count);
}
关于BITFIELD参数使用解释:
-
key
-
作用:Redis 中存储 BitMap 的键名
-
示例:
"sign:1001:202310"
(用户1001在2023年10月的签到数据) -
底层命令:
BITFIELD key [GET type offset]
-
说明:指定要操作的 BitMap 键
-
-
BitFieldSubCommands.create()
-
作用:创建
BITFIELD
命令的子命令构建器 -
说明:
- Spring Data Redis 的封装方法,用于构建复杂的
BITFIELD
操作 - 对应 Redis 原生命令中的
[GET/SET/INCR ...]
部分
- Spring Data Redis 的封装方法,用于构建复杂的
-
-
get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
-
作用:指定要获取的位段类型和长度
-
参数分解:
BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)
unsigned
:表示获取无符号整数(值始终 ≥0)dayOfMonth
:整数位数长度(例如今天是10月25日,则dayOfMonth=25
)- 底层逻辑:Redis 会将从偏移量0开始的25个bit转换为一个无符号整数
-
示例:
如果签到数据为1011...
(二进制),unsigned(25)
会将其转换为十进制整数(如123456
)。
-
valueAt(0)
-
作用:指定要获取的起始位偏移量(offset)
-
参数:
0
:表示从 BitMap 的第0位开始获取
-
关键点:
- 偏移量从0开始计数(与
SETBIT
/GETBIT
的偏移量规则一致) - 配合
unsigned(dayOfMonth)
表示:从第0位开始,获取连续dayOfMonth
个bit
- 偏移量从0开始计数(与
-
-
valueAt(0)
-
作用:指定要获取的起始位偏移量(offset)
-
参数:
0
:表示从 BitMap 的第0位开始获取
-
关键点:
- 偏移量从0开始计数(与
SETBIT
/GETBIT
的偏移量规则一致) - 配合
unsigned(dayOfMonth)
表示:从第0位开始,获取连续dayOfMonth
个bit
- 偏移量从0开始计数(与
-